Feature #2565 » dtrace.patch
.gitignore | ||
---|---|---|
/exts.mk
|
||
/goruby
|
||
/id.h
|
||
/probes.h
|
||
/largefile.h
|
||
/lex.c
|
||
/libruby*.*
|
Makefile.in | ||
---|---|---|
OBJCOPY = @OBJCOPY@
|
||
VCS = @VCS@
|
||
VCSUP = @VCSUP@
|
||
DTRACE = @DTRACE@
|
||
OBJEXT = @OBJEXT@
|
||
ASMEXT = S
|
||
... | ... | |
$(srcdir)/configure: $(srcdir)/configure.in
|
||
$(CHDIR) $(srcdir) && exec $(AUTOCONF)
|
||
incs: id.h
|
||
incs: id.h probes.h
|
||
# Things which should be considered:
|
||
# * with gperf v.s. without gperf
|
||
... | ... | |
@$(ECHO) preprocessing $<
|
||
$(Q) $(CPP) $(warnflags) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -E $< > $@
|
||
.d.h:
|
||
@$(ECHO) translating probes $<
|
||
$(Q)if test -n '$(DTRACE)'; then\
|
||
$(DTRACE) -o $@.tmp -h -s $<; \
|
||
sed -e 's/RUBY_/RUBY_DTRACE_/g' $@.tmp | sed -e 's/PROBES_H_TMP/PROBES_H/g' >$@; \
|
||
rm $@.tmp; \
|
||
else \
|
||
sed -f $(srcdir)/tool/gen_dummy_probes.sed $< > $@; \
|
||
fi
|
||
clean-local::
|
||
$(Q)$(RM) ext/extinit.c ext/extinit.$(OBJEXT) ext/ripper/y.output \
|
||
enc/encinit.c enc/encinit.$(OBJEXT)
|
common.mk | ||
---|---|---|
lib: $(LIBRUBY)
|
||
dll: $(LIBRUBY_SO)
|
||
.SUFFIXES: .inc .h .c .y .i
|
||
.SUFFIXES: .inc .h .c .y .i .d
|
||
# V=0 quiet, V=1 verbose. other values don't work.
|
||
V = 0
|
||
... | ... | |
{$(VPATH)}subst.h
|
||
ENCODING_H_INCLUDES= {$(VPATH)}encoding.h {$(VPATH)}oniguruma.h
|
||
ID_H_INCLUDES = {$(VPATH)}id.h {$(VPATH)}vm_opts.h
|
||
PROBES_H_INCLUDES = {$(VPATH)}probes.h
|
||
VM_CORE_H_INCLUDES = {$(VPATH)}vm_core.h {$(VPATH)}thread_$(THREAD_MODEL).h \
|
||
{$(VPATH)}node.h {$(VPATH)}method.h {$(VPATH)}atomic.h \
|
||
$(ID_H_INCLUDES)
|
||
... | ... | |
eval.$(OBJEXT): {$(VPATH)}eval.c {$(VPATH)}eval_intern.h {$(VPATH)}vm.h \
|
||
$(RUBY_H_INCLUDES) $(VM_CORE_H_INCLUDES) {$(VPATH)}eval_error.c \
|
||
{$(VPATH)}eval_jump.c {$(VPATH)}debug.h {$(VPATH)}gc.h {$(VPATH)}iseq.h \
|
||
$(ENCODING_H_INCLUDES) {$(VPATH)}internal.h
|
||
$(ENCODING_H_INCLUDES) {$(VPATH)}internal.h $(PROBES_H_INCLUDES)
|
||
load.$(OBJEXT): {$(VPATH)}load.c {$(VPATH)}eval_intern.h \
|
||
{$(VPATH)}util.h $(RUBY_H_INCLUDES) $(VM_CORE_H_INCLUDES) \
|
||
{$(VPATH)}dln.h {$(VPATH)}debug.h \
|
||
{$(VPATH)}internal.h
|
||
{$(VPATH)}internal.h $(PROBES_H_INCLUDES)
|
||
file.$(OBJEXT): {$(VPATH)}file.c $(RUBY_H_INCLUDES) {$(VPATH)}io.h \
|
||
$(ENCODING_H_INCLUDES) {$(VPATH)}util.h {$(VPATH)}dln.h \
|
||
{$(VPATH)}internal.h
|
||
... | ... | |
{$(VPATH)}regex.h $(ENCODING_H_INCLUDES) $(VM_CORE_H_INCLUDES) \
|
||
{$(VPATH)}gc.h {$(VPATH)}io.h {$(VPATH)}eval_intern.h {$(VPATH)}util.h \
|
||
{$(VPATH)}debug.h {$(VPATH)}internal.h {$(VPATH)}constant.h \
|
||
{$(VPATH)}thread.h
|
||
{$(VPATH)}thread.h $(PROBES_H_INCLUDES)
|
||
hash.$(OBJEXT): {$(VPATH)}hash.c $(RUBY_H_INCLUDES) {$(VPATH)}util.h \
|
||
$(ENCODING_H_INCLUDES)
|
||
inits.$(OBJEXT): {$(VPATH)}inits.c $(RUBY_H_INCLUDES) \
|
||
... | ... | |
numeric.$(OBJEXT): {$(VPATH)}numeric.c $(RUBY_H_INCLUDES) \
|
||
{$(VPATH)}util.h $(ENCODING_H_INCLUDES) {$(VPATH)}internal.h
|
||
object.$(OBJEXT): {$(VPATH)}object.c $(RUBY_H_INCLUDES) {$(VPATH)}util.h \
|
||
{$(VPATH)}internal.h {$(VPATH)}constant.h $(ENCODING_H_INCLUDES)
|
||
{$(VPATH)}internal.h {$(VPATH)}constant.h $(ENCODING_H_INCLUDES) $(PROBES_H_INCLUDES)
|
||
pack.$(OBJEXT): {$(VPATH)}pack.c $(RUBY_H_INCLUDES) {$(VPATH)}encoding.h \
|
||
{$(VPATH)}oniguruma.h
|
||
parse.$(OBJEXT): {$(VPATH)}parse.c $(RUBY_H_INCLUDES) {$(VPATH)}node.h \
|
||
... | ... | |
iseq.$(OBJEXT): {$(VPATH)}iseq.c {$(VPATH)}gc.h {$(VPATH)}iseq.h \
|
||
$(RUBY_H_INCLUDES) $(VM_CORE_H_INCLUDES) {$(VPATH)}insns.inc \
|
||
{$(VPATH)}insns_info.inc {$(VPATH)}node_name.inc {$(VPATH)}debug.h {$(VPATH)}internal.h
|
||
{$(VPATH)}vm_insnhelper.c:$(PROBES_H_INCLUDES)
|
||
vm.$(OBJEXT): {$(VPATH)}vm.c {$(VPATH)}gc.h {$(VPATH)}iseq.h \
|
||
{$(VPATH)}eval_intern.h $(RUBY_H_INCLUDES) $(ENCODING_H_INCLUDES) \
|
||
$(VM_CORE_H_INCLUDES) {$(VPATH)}vm_method.c {$(VPATH)}vm_eval.c \
|
configure.in | ||
---|---|---|
if test x"${build}" != x"${host}"; then
|
||
AC_CHECK_TOOL(CC, gcc)
|
||
fi
|
||
AC_CHECK_TOOL(DTRACE, dtrace)
|
||
RUBY_MINGW32
|
||
AC_PROG_CC
|
||
AC_PROG_CXX
|
eval.c | ||
---|---|---|
#include "ruby/encoding.h"
|
||
#include "internal.h"
|
||
#include "vm_core.h"
|
||
#include "probes.h"
|
||
#define numberof(array) (int)(sizeof(array) / sizeof((array)[0]))
|
||
... | ... | |
}
|
||
if (tag != TAG_FATAL) {
|
||
if(RUBY_DTRACE_RAISE_ENABLED()) {
|
||
RUBY_DTRACE_RAISE(rb_obj_classname(th->errinfo),
|
||
rb_sourcefile(),
|
||
rb_sourceline());
|
||
}
|
||
EXEC_EVENT_HOOK(th, RUBY_EVENT_RAISE, th->cfp->self, 0, 0);
|
||
}
|
||
}
|
gc.c | ||
---|---|---|
#include "gc.h"
|
||
#include "constant.h"
|
||
#include "atomic.h"
|
||
#include "probes.h"
|
||
#include <stdio.h>
|
||
#include <setjmp.h>
|
||
#include <sys/types.h>
|
||
... | ... | |
}
|
||
#define GC_PROF_TIMER_START do {\
|
||
if (RUBY_DTRACE_GC_BEGIN_ENABLED()) { \
|
||
RUBY_DTRACE_GC_BEGIN(); \
|
||
} \
|
||
if (objspace->profile.run) {\
|
||
if (!objspace->profile.record) {\
|
||
objspace->profile.size = 1000;\
|
||
... | ... | |
} while(0)
|
||
#define GC_PROF_TIMER_STOP(marked) do {\
|
||
if (RUBY_DTRACE_GC_END_ENABLED()) { \
|
||
RUBY_DTRACE_GC_END(); \
|
||
} \
|
||
if (objspace->profile.run) {\
|
||
gc_time = getrusage_time() - gc_time;\
|
||
if (gc_time < 0) gc_time = 0;\
|
||
... | ... | |
during_gc++;
|
||
GC_PROF_TIMER_START;
|
||
if (RUBY_DTRACE_GC_SWEEP_BEGIN_ENABLED()) {
|
||
RUBY_DTRACE_GC_SWEEP_BEGIN();
|
||
}
|
||
GC_PROF_SWEEP_TIMER_START;
|
||
if (objspace->heap.sweep_slots) {
|
||
res = lazy_sweep(objspace);
|
||
if (res) {
|
||
if (RUBY_DTRACE_GC_SWEEP_END_ENABLED()) {
|
||
RUBY_DTRACE_GC_SWEEP_END();
|
||
}
|
||
GC_PROF_SWEEP_TIMER_STOP;
|
||
GC_PROF_SET_MALLOC_INFO;
|
||
GC_PROF_TIMER_STOP(Qfalse);
|
||
... | ... | |
set_heaps_increment(objspace);
|
||
}
|
||
if (RUBY_DTRACE_GC_SWEEP_BEGIN_ENABLED()) {
|
||
RUBY_DTRACE_GC_SWEEP_BEGIN();
|
||
}
|
||
GC_PROF_SWEEP_TIMER_START;
|
||
if (!(res = lazy_sweep(objspace))) {
|
||
after_gc_sweep(objspace);
|
||
... | ... | |
during_gc = 0;
|
||
}
|
||
}
|
||
if (RUBY_DTRACE_GC_SWEEP_END_ENABLED()) {
|
||
RUBY_DTRACE_GC_SWEEP_END();
|
||
}
|
||
GC_PROF_SWEEP_TIMER_STOP;
|
||
GC_PROF_TIMER_STOP(Qtrue);
|
||
... | ... | |
{
|
||
struct gc_list *list;
|
||
rb_thread_t *th = GET_THREAD();
|
||
if (RUBY_DTRACE_GC_MARK_BEGIN_ENABLED()) {
|
||
RUBY_DTRACE_GC_MARK_BEGIN();
|
||
}
|
||
GC_PROF_MARK_TIMER_START;
|
||
objspace->heap.live_num = 0;
|
||
... | ... | |
gc_mark_rest(objspace);
|
||
}
|
||
}
|
||
if (RUBY_DTRACE_GC_MARK_END_ENABLED()) {
|
||
RUBY_DTRACE_GC_MARK_END();
|
||
}
|
||
GC_PROF_MARK_TIMER_STOP;
|
||
}
|
||
... | ... | |
during_gc++;
|
||
gc_marks(objspace);
|
||
if (RUBY_DTRACE_GC_SWEEP_BEGIN_ENABLED()) {
|
||
RUBY_DTRACE_GC_SWEEP_BEGIN();
|
||
}
|
||
GC_PROF_SWEEP_TIMER_START;
|
||
gc_sweep(objspace);
|
||
if (RUBY_DTRACE_GC_SWEEP_END_ENABLED()) {
|
||
RUBY_DTRACE_GC_SWEEP_END();
|
||
}
|
||
GC_PROF_SWEEP_TIMER_STOP;
|
||
GC_PROF_TIMER_STOP(Qtrue);
|
insns.def | ||
---|---|---|
()
|
||
(VALUE val)
|
||
{
|
||
if(RUBY_DTRACE_OBJECT_CREATE_START_ENABLED()) {
|
||
RUBY_DTRACE_OBJECT_CREATE_START("String", rb_sourcefile(), rb_sourceline());
|
||
}
|
||
val = rb_str_resurrect(str);
|
||
if(RUBY_DTRACE_OBJECT_CREATE_DONE_ENABLED()) {
|
||
RUBY_DTRACE_OBJECT_CREATE_DONE("String", rb_sourcefile(), rb_sourceline());
|
||
}
|
||
}
|
||
/**
|
||
... | ... | |
(...)
|
||
(VALUE val) // inc += 1 - num;
|
||
{
|
||
if(RUBY_DTRACE_OBJECT_CREATE_START_ENABLED()) {
|
||
RUBY_DTRACE_OBJECT_CREATE_START("Array", rb_sourcefile(), rb_sourceline());
|
||
}
|
||
val = rb_ary_new4((long)num, STACK_ADDR_FROM_TOP(num));
|
||
if(RUBY_DTRACE_OBJECT_CREATE_DONE_ENABLED()) {
|
||
RUBY_DTRACE_OBJECT_CREATE_DONE("Array", rb_sourcefile(), rb_sourceline());
|
||
}
|
||
POPN(num);
|
||
}
|
||
... | ... | |
(VALUE val) // inc += 1 - num;
|
||
{
|
||
rb_num_t i;
|
||
if(RUBY_DTRACE_OBJECT_CREATE_START_ENABLED()) {
|
||
RUBY_DTRACE_OBJECT_CREATE_START("Hash", rb_sourcefile(), rb_sourceline());
|
||
}
|
||
val = rb_hash_new();
|
||
if(RUBY_DTRACE_OBJECT_CREATE_DONE_ENABLED()) {
|
||
RUBY_DTRACE_OBJECT_CREATE_DONE("Hash", rb_sourcefile(), rb_sourceline());
|
||
}
|
||
for (i = num; i > 0; i -= 2) {
|
||
const VALUE v = TOPN(i - 2);
|
||
const VALUE k = TOPN(i - 1);
|
||
... | ... | |
{
|
||
rb_event_flag_t flag = (rb_event_flag_t)nf;
|
||
if (RUBY_DTRACE_LINE_ENABLED()) {
|
||
if (flag == RUBY_EVENT_LINE) {
|
||
RUBY_DTRACE_LINE(rb_sourcefile(), rb_sourceline());
|
||
}
|
||
}
|
||
if (RUBY_DTRACE_FUNCTION_ENTRY_ENABLED()) {
|
||
if (flag == RUBY_EVENT_CALL || flag == RUBY_EVENT_C_CALL) {
|
||
VALUE klass;
|
||
ID called_id;
|
||
rb_thread_method_id_and_class(th, &called_id, &klass);
|
||
RUBY_DTRACE_FUNCTION_ENTRY(
|
||
RSTRING_PTR(rb_inspect(klass)),
|
||
rb_id2name(called_id),
|
||
rb_sourcefile(),
|
||
rb_sourceline());
|
||
}
|
||
}
|
||
if (RUBY_DTRACE_FUNCTION_RETURN_ENABLED()) {
|
||
if (flag == RUBY_EVENT_RETURN || flag == RUBY_EVENT_C_RETURN) {
|
||
VALUE klass;
|
||
ID called_id;
|
||
rb_thread_method_id_and_class(th, &called_id, &klass);
|
||
RUBY_DTRACE_FUNCTION_RETURN(
|
||
RSTRING_PTR(rb_inspect(klass)),
|
||
rb_id2name(called_id),
|
||
rb_sourcefile(),
|
||
rb_sourceline());
|
||
}
|
||
}
|
||
EXEC_EVENT_HOOK(th, flag, GET_SELF(), 0, 0 /* TODO: id, klass */);
|
||
}
|
||
load.c | ||
---|---|---|
#include "internal.h"
|
||
#include "dln.h"
|
||
#include "eval_intern.h"
|
||
#include "probes.h"
|
||
VALUE ruby_dln_librefs;
|
||
... | ... | |
VALUE fname, wrap, path;
|
||
rb_scan_args(argc, argv, "11", &fname, &wrap);
|
||
if(RUBY_DTRACE_LOAD_ENTRY_ENABLED()) {
|
||
RUBY_DTRACE_LOAD_ENTRY(
|
||
StringValuePtr(fname),
|
||
rb_sourcefile(),
|
||
rb_sourceline());
|
||
}
|
||
path = rb_find_file(FilePathValue(fname));
|
||
if (!path) {
|
||
if (!rb_file_load_ok(RSTRING_PTR(fname)))
|
||
... | ... | |
path = fname;
|
||
}
|
||
rb_load_internal(path, RTEST(wrap));
|
||
if(RUBY_DTRACE_LOAD_RETURN_ENABLED()) {
|
||
RUBY_DTRACE_LOAD_RETURN(StringValuePtr(fname));
|
||
}
|
||
return Qtrue;
|
||
}
|
||
... | ... | |
} volatile saved;
|
||
char *volatile ftptr = 0;
|
||
if(RUBY_DTRACE_REQUIRE_ENTRY_ENABLED()) {
|
||
RUBY_DTRACE_REQUIRE_ENTRY(
|
||
StringValuePtr(fname),
|
||
rb_sourcefile(),
|
||
rb_sourceline());
|
||
}
|
||
PUSH_TAG();
|
||
saved.safe = rb_safe_level();
|
||
if ((state = EXEC_TAG()) == 0) {
|
||
... | ... | |
th->errinfo = errinfo;
|
||
if(RUBY_DTRACE_REQUIRE_RETURN_ENABLED()) {
|
||
RUBY_DTRACE_REQUIRE_RETURN(StringValuePtr(fname));
|
||
}
|
||
return result;
|
||
}
|
||
object.c | ||
---|---|---|
#include <float.h>
|
||
#include "constant.h"
|
||
#include "internal.h"
|
||
#include "probes.h"
|
||
VALUE rb_cBasicObject;
|
||
VALUE rb_mKernel;
|
||
... | ... | |
if (FL_TEST(klass, FL_SINGLETON)) {
|
||
rb_raise(rb_eTypeError, "can't create instance of singleton class");
|
||
}
|
||
if (RUBY_DTRACE_OBJECT_CREATE_START_ENABLED()) {
|
||
const char * file = rb_sourcefile();
|
||
RUBY_DTRACE_OBJECT_CREATE_START(rb_class2name(klass),
|
||
file ? file : "",
|
||
rb_sourceline());
|
||
}
|
||
obj = rb_funcall(klass, ID_ALLOCATOR, 0, 0);
|
||
if (RUBY_DTRACE_OBJECT_CREATE_DONE_ENABLED()) {
|
||
const char * file = rb_sourcefile();
|
||
RUBY_DTRACE_OBJECT_CREATE_DONE(rb_class2name(klass),
|
||
file ? file : "",
|
||
rb_sourceline());
|
||
}
|
||
if (rb_obj_class(obj) != rb_class_real(klass)) {
|
||
rb_raise(rb_eTypeError, "wrong instance allocation");
|
||
}
|
probes.d | ||
---|---|---|
provider ruby {
|
||
probe function__entry(const char *, const char *, const char *, int);
|
||
probe function__return(const char *, const char *, const char *, int);
|
||
probe require__entry(const char *, const char *, int);
|
||
probe require__return(const char *);
|
||
probe load__entry(const char *, const char *, int);
|
||
probe load__return(const char *);
|
||
probe raise(const char *, const char *, int);
|
||
probe object__create__start(const char *, const char *, int);
|
||
probe object__create__done(const char *, const char *, int);
|
||
probe gc__begin();
|
||
probe gc__end();
|
||
probe gc__mark__begin();
|
||
probe gc__mark__end();
|
||
probe gc__sweep__begin();
|
||
probe gc__sweep__end();
|
||
probe line(const char *, int);
|
||
};
|
||
#pragma D attributes Stable/Evolving/Common provider ruby provider
|
||
#pragma D attributes Stable/Evolving/Common provider ruby module
|
||
#pragma D attributes Stable/Evolving/Common provider ruby function
|
||
#pragma D attributes Evolving/Evolving/Common provider ruby name
|
||
#pragma D attributes Evolving/Evolving/Common provider ruby args
|
test/dtrace/dummy.rb | ||
---|---|---|
# this is a dummy file used by test/dtrace/test_require.rb
|
test/dtrace/helper.rb | ||
---|---|---|
require 'minitest/autorun'
|
||
require 'tempfile'
|
||
module DTrace
|
||
class TestCase < MiniTest::Unit::TestCase
|
||
INCLUDE = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
||
def setup
|
||
skip "must be setuid 0 to run dtrace tests" unless Process.euid == 0
|
||
end
|
||
def trap_probe d_program, ruby_program
|
||
d = Tempfile.new('probe.d')
|
||
d.write d_program
|
||
d.flush
|
||
rb = Tempfile.new('probed.rb')
|
||
rb.write ruby_program
|
||
rb.flush
|
||
d_path = d.path
|
||
rb_path = rb.path
|
||
cmd = "dtrace -q -s #{d_path} -c '#{Gem.ruby} -I#{INCLUDE} #{rb_path}'"
|
||
probes = IO.popen(cmd) do |io|
|
||
io.readlines
|
||
end
|
||
d.close(true)
|
||
rb.close(true)
|
||
yield(d_path, rb_path, probes)
|
||
end
|
||
end
|
||
end
|
test/dtrace/test_function_entry.rb | ||
---|---|---|
require 'dtrace/helper'
|
||
module DTrace
|
||
class TestFunctionEntry < TestCase
|
||
def test_function_entry
|
||
probe = <<-eoprobe
|
||
ruby$target:::function-entry
|
||
{
|
||
printf("%s %s %s %d\\n", copyinstr(arg0), copyinstr(arg1), copyinstr(arg2), arg3);
|
||
}
|
||
eoprobe
|
||
trap_probe(probe, ruby_program) { |d_file, rb_file, probes|
|
||
foo_calls = probes.map { |line| line.split }.find_all { |row|
|
||
row.first == 'Foo' && row[1] == 'foo'
|
||
}
|
||
assert_equal 10, foo_calls.length
|
||
line = '2'
|
||
foo_calls.each { |f| assert_equal line, f[3] }
|
||
foo_calls.each { |f| assert_equal rb_file, f[2] }
|
||
}
|
||
end
|
||
def test_function_return
|
||
probe = <<-eoprobe
|
||
ruby$target:::function-return
|
||
{
|
||
printf("%s %s %s %d\\n", copyinstr(arg0), copyinstr(arg1), copyinstr(arg2), arg3);
|
||
}
|
||
eoprobe
|
||
trap_probe(probe, ruby_program) { |d_file, rb_file, probes|
|
||
foo_calls = probes.map { |line| line.split }.find_all { |row|
|
||
row.first == 'Foo' && row[1] == 'foo'
|
||
}
|
||
assert_equal 10, foo_calls.length
|
||
line = '2'
|
||
foo_calls.each { |f| assert_equal line, f[3] }
|
||
foo_calls.each { |f| assert_equal rb_file, f[2] }
|
||
}
|
||
end
|
||
private
|
||
def ruby_program
|
||
<<-eoruby
|
||
class Foo
|
||
def foo; end
|
||
end
|
||
x = Foo.new
|
||
10.times { x.foo }
|
||
eoruby
|
||
end
|
||
end
|
||
end
|
test/dtrace/test_gc.rb | ||
---|---|---|
require 'dtrace/helper'
|
||
module DTrace
|
||
class TestGC < TestCase
|
||
%w{
|
||
gc-begin
|
||
gc-end
|
||
gc-mark-begin
|
||
gc-mark-end
|
||
gc-sweep-begin
|
||
gc-sweep-end
|
||
}.each do |probe_name|
|
||
define_method(:"test_#{probe_name.gsub(/-/, '_')}") do
|
||
probe = "ruby$target:::#{probe_name} { printf(\"#{probe_name}\\n\"); }"
|
||
trap_probe(probe, ruby_program) { |_, _, saw|
|
||
assert_operator saw.length, :>, 0
|
||
}
|
||
end
|
||
end
|
||
private
|
||
def ruby_program
|
||
"100000.times { Object.new }"
|
||
end
|
||
end
|
||
end
|
test/dtrace/test_line.rb | ||
---|---|---|
require 'dtrace/helper'
|
||
module DTrace
|
||
class TestLine < TestCase
|
||
def test_line
|
||
probe = "ruby$target:::line { printf(\"%s %d\\n\", copyinstr(arg0), arg1); }"
|
||
program = 'x = 2; 10.times { x = x ** 2 }'
|
||
trap_probe(probe, program) { |_, rbpath, saw|
|
||
saw = saw.map(&:split).find_all { |file, _|
|
||
file == rbpath
|
||
}
|
||
assert_operator saw.length, :>=, 10
|
||
saw.each { |f,l| assert_equal '1', l }
|
||
}
|
||
end
|
||
end
|
||
end
|
test/dtrace/test_load.rb | ||
---|---|---|
require 'dtrace/helper'
|
||
require 'tempfile'
|
||
module DTrace
|
||
class TestLoad < TestCase
|
||
def setup
|
||
super
|
||
@rbfile = Tempfile.new(['omg', 'rb'])
|
||
@rbfile.write 'x = 10'
|
||
end
|
||
def teardown
|
||
super
|
||
@rbfile.close(true) if @rbfile
|
||
end
|
||
def test_load_entry
|
||
probe = <<-eoprobe
|
||
ruby$target:::load-entry
|
||
{
|
||
printf("%s %s %d\\n", copyinstr(arg0), copyinstr(arg1), arg2);
|
||
}
|
||
eoprobe
|
||
trap_probe(probe, program) { |dpath, rbpath, saw|
|
||
saw = saw.map(&:split).find_all { |loaded, _, _|
|
||
loaded == @rbfile.path
|
||
}
|
||
assert_equal 10, saw.length
|
||
}
|
||
end
|
||
def test_load_return
|
||
probe = <<-eoprobe
|
||
ruby$target:::load-return
|
||
{
|
||
printf("%s\\n", copyinstr(arg0));
|
||
}
|
||
eoprobe
|
||
trap_probe(probe, program) { |dpath, rbpath, saw|
|
||
saw = saw.map(&:split).find_all { |loaded, _, _|
|
||
loaded == @rbfile.path
|
||
}
|
||
assert_equal 10, saw.length
|
||
}
|
||
end
|
||
private
|
||
def program
|
||
"10.times { load '#{@rbfile.path}' }"
|
||
end
|
||
end
|
||
end
|
test/dtrace/test_object_create_done.rb | ||
---|---|---|
require 'dtrace/helper'
|
||
module DTrace
|
||
class TestObjectCreateDone < TestCase
|
||
def test_object
|
||
trap_probe(probe, '10.times { Object.new }') { |_,rbfile,saw|
|
||
saw = saw.map(&:split).find_all { |_, file, _|
|
||
file == rbfile
|
||
}
|
||
assert_equal 10, saw.length
|
||
}
|
||
end
|
||
def test_object_name
|
||
trap_probe(probe, 'Hash.new') { |_,rbfile,saw|
|
||
saw = saw.map(&:split).find_all { |klass, file, line|
|
||
file == rbfile
|
||
}
|
||
assert_equal(%w{ Hash }, saw.map(&:first))
|
||
assert_equal([rbfile], saw.map { |line| line[1] })
|
||
assert_equal(['1'], saw.map { |line| line[2] })
|
||
}
|
||
end
|
||
def test_hash_lit
|
||
trap_probe(probe, '{}') { |_,rbfile,saw|
|
||
saw = saw.map(&:split).find_all { |klass, file, line|
|
||
file == rbfile
|
||
}
|
||
assert_equal(%w{ Hash }, saw.map(&:first))
|
||
assert_equal([rbfile], saw.map { |line| line[1] })
|
||
assert_equal(['1'], saw.map { |line| line[2] })
|
||
}
|
||
end
|
||
def test_array_lit
|
||
trap_probe(probe, '[]') { |_,rbfile,saw|
|
||
saw = saw.map(&:split).find_all { |klass, file, line|
|
||
file == rbfile
|
||
}
|
||
assert_equal(%w{ Array }, saw.map(&:first))
|
||
assert_equal([rbfile], saw.map { |line| line[1] })
|
||
assert_equal(['1'], saw.map { |line| line[2] })
|
||
}
|
||
end
|
||
def test_string_lit
|
||
trap_probe(probe, '"omg"') { |_,rbfile,saw|
|
||
saw = saw.map(&:split).find_all { |klass, file, line|
|
||
file == rbfile
|
||
}
|
||
assert_equal(%w{ String }, saw.map(&:first))
|
||
assert_equal([rbfile], saw.map { |line| line[1] })
|
||
assert_equal(['1'], saw.map { |line| line[2] })
|
||
}
|
||
end
|
||
private
|
||
def probe
|
||
<<-eoprobe
|
||
ruby$target:::object-create-done
|
||
{
|
||
printf("%s %s %d\\n", copyinstr(arg0), copyinstr(arg1), arg2);
|
||
}
|
||
eoprobe
|
||
end
|
||
end
|
||
end
|
test/dtrace/test_object_create_start.rb | ||
---|---|---|
require 'dtrace/helper'
|
||
module DTrace
|
||
class TestObjectCreateStart < TestCase
|
||
def test_object_create_start
|
||
trap_probe(probe, '10.times { Object.new }') { |_,rbfile,saw|
|
||
saw = saw.map(&:split).find_all { |_, file, _|
|
||
file == rbfile
|
||
}
|
||
assert_equal 10, saw.length
|
||
}
|
||
end
|
||
def test_object_create_start_name
|
||
trap_probe(probe, 'Hash.new') { |_,rbfile,saw|
|
||
saw = saw.map(&:split).find_all { |klass, file, line|
|
||
file == rbfile
|
||
}
|
||
assert_equal(%w{ Hash }, saw.map(&:first))
|
||
assert_equal([rbfile], saw.map { |line| line[1] })
|
||
assert_equal(['1'], saw.map { |line| line[2] })
|
||
}
|
||
end
|
||
def test_object_create_start_hash_lit
|
||
trap_probe(probe, '{}') { |_,rbfile,saw|
|
||
saw = saw.map(&:split).find_all { |klass, file, line|
|
||
file == rbfile
|
||
}
|
||
assert_equal(%w{ Hash }, saw.map(&:first))
|
||
assert_equal([rbfile], saw.map { |line| line[1] })
|
||
assert_equal(['1'], saw.map { |line| line[2] })
|
||
}
|
||
end
|
||
def test_object_create_start_array_lit
|
||
trap_probe(probe, '[]') { |_,rbfile,saw|
|
||
saw = saw.map(&:split).find_all { |klass, file, line|
|
||
file == rbfile
|
||
}
|
||
assert_equal(%w{ Array }, saw.map(&:first))
|
||
assert_equal([rbfile], saw.map { |line| line[1] })
|
||
assert_equal(['1'], saw.map { |line| line[2] })
|
||
}
|
||
end
|
||
def test_object_create_start_string_lit
|
||
trap_probe(probe, '"omg"') { |_,rbfile,saw|
|
||
saw = saw.map(&:split).find_all { |klass, file, line|
|
||
file == rbfile
|
||
}
|
||
assert_equal(%w{ String }, saw.map(&:first))
|
||
assert_equal([rbfile], saw.map { |line| line[1] })
|
||
assert_equal(['1'], saw.map { |line| line[2] })
|
||
}
|
||
end
|
||
private
|
||
def probe
|
||
<<-eoprobe
|
||
ruby$target:::object-create-start
|
||
{
|
||
printf("%s %s %d\\n", copyinstr(arg0), copyinstr(arg1), arg2);
|
||
}
|
||
eoprobe
|
||
end
|
||
end
|
||
end
|
test/dtrace/test_raise.rb | ||
---|---|---|
require 'dtrace/helper'
|
||
module DTrace
|
||
class TestRaise < TestCase
|
||
def test_raise
|
||
probe = <<-eoprobe
|
||
ruby$target:::raise
|
||
{
|
||
printf("%s %s %d\\n", copyinstr(arg0), copyinstr(arg1), arg2);
|
||
}
|
||
eoprobe
|
||
trap_probe(probe, program) { |dpath, rbpath, saw|
|
||
saw = saw.map(&:split).find_all { |_, source_file, _|
|
||
source_file == rbpath
|
||
}
|
||
assert_equal 10, saw.length
|
||
saw.each do |klass, _, source_line|
|
||
assert_equal 'RuntimeError', klass
|
||
assert_equal '1', source_line
|
||
end
|
||
}
|
||
end
|
||
private
|
||
def program
|
||
'10.times { raise rescue nil }'
|
||
end
|
||
end
|
||
end
|
test/dtrace/test_require.rb | ||
---|---|---|
require 'dtrace/helper'
|
||
module DTrace
|
||
class TestRequire < TestCase
|
||
def test_require_entry
|
||
probe = <<-eoprobe
|
||
ruby$target:::require-entry
|
||
{
|
||
printf("%s %s %d\\n", copyinstr(arg0), copyinstr(arg1), arg2);
|
||
}
|
||
eoprobe
|
||
trap_probe(probe, ruby_program) { |d_file, rb_file, saw|
|
||
required = saw.map { |s| s.split }.find_all do |(required, _)|
|
||
required == 'dtrace/dummy'
|
||
end
|
||
assert_equal 10, required.length
|
||
}
|
||
end
|
||
def test_require_return
|
||
probe = <<-eoprobe
|
||
ruby$target:::require-return
|
||
{
|
||
printf("%s\\n", copyinstr(arg0));
|
||
}
|
||
eoprobe
|
||
end
|
||
private
|
||
def ruby_program
|
||
"10.times { require 'dtrace/dummy' }"
|
||
end
|
||
end
|
||
end
|
test/dtrace/test_singleton_function.rb | ||
---|---|---|
require 'dtrace/helper'
|
||
module DTrace
|
||
class TestSingletonFunctionEntry < TestCase
|
||
def test_entry
|
||
probe = <<-eoprobe
|
||
ruby$target:::function-entry
|
||
/strstr(copyinstr(arg0), "Foo") != NULL/
|
||
{
|
||
printf("%s %s %s %d\\n", copyinstr(arg0), copyinstr(arg1), copyinstr(arg2), arg3);
|
||
}
|
||
eoprobe
|
||
trap_probe(probe, ruby_program) { |d_file, rb_file, probes|
|
||
foo_calls = probes.map { |line| line.split }.find_all { |row|
|
||
row.first == '#<Class:Foo>' && row[1] == 'foo'
|
||
}
|
||
assert_equal 10, foo_calls.length
|
||
line = '2'
|
||
foo_calls.each { |f| assert_equal line, f[3] }
|
||
foo_calls.each { |f| assert_equal rb_file, f[2] }
|
||
}
|
||
end
|
||
def test_exit
|
||
probe = <<-eoprobe
|
||
ruby$target:::function-return
|
||
{
|
||
printf("%s %s %s %d\\n", copyinstr(arg0), copyinstr(arg1), copyinstr(arg2), arg3);
|
||
}
|
||
eoprobe
|
||
trap_probe(probe, ruby_program) { |d_file, rb_file, probes|
|
||
foo_calls = probes.map { |line| line.split }.find_all { |row|
|
||
row.first == '#<Class:Foo>' && row[1] == 'foo'
|
||
}
|
||
assert_equal 10, foo_calls.length
|
||
line = '2'
|
||
foo_calls.each { |f| assert_equal line, f[3] }
|
||
foo_calls.each { |f| assert_equal rb_file, f[2] }
|
||
}
|
||
end
|
||
def ruby_program
|
||
<<-eoruby
|
||
class Foo
|
||
def self.foo; end
|
||
end
|
||
10.times { Foo.foo }
|
||
eoruby
|
||
end
|
||
end
|
||
end
|
tool/gen_dummy_probes.sed | ||
---|---|---|
# upper case everything
|
||
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
|
||
# remove the pragma declarations
|
||
s/^#PRAGMA.*$//
|
||
# replace the provider section with the start of the header file
|
||
s/PROVIDER RUBY {/#ifndef _PROBES_H\
|
||
#define _PROBES_H/
|
||
# finish up the #ifndef sandwich
|
||
s/};/#endif \/* _PROBES_H *\//
|
||
s/__/_/g
|
||
s/([^,)]\{1,\})/(arg0)/
|
||
s/([^,)]\{1,\},[^,)]\{1,\})/(arg0, arg1)/
|
||
s/([^,)]\{1,\},[^,)]\{1,\},[^,)]\{1,\})/(arg0, arg1, arg2)/
|
||
s/([^,)]\{1,\},[^,)]\{1,\},[^,)]\{1,\},[^,)]\{1,\})/(arg0, arg1, arg2, arg3)/
|
||
s/([^,)]\{1,\},[^,)]\{1,\},[^,)]\{1,\},[^,)]\{1,\},[^,)]\{1,\})/(arg0, arg1, arg2, arg3, arg4)/
|
||
s/[ ]*PROBE[ ]\([^\(]*\)\(([^\)]*)\);/#define RUBY_DTRACE_\1_ENABLED() 0\
|
||
#define RUBY_DTRACE_\1\2\ do \{ \} while\(0\)/
|
vm_eval.c | ||
---|---|---|
}
|
||
case VM_METHOD_TYPE_NOTIMPLEMENTED:
|
||
case VM_METHOD_TYPE_CFUNC: {
|
||
if (RUBY_DTRACE_FUNCTION_ENTRY_ENABLED()) {
|
||
const char * classname = rb_class2name(klass);
|
||
const char * methodname = rb_id2name(id);
|
||
const char * filename = rb_sourcefile();
|
||
if (classname && methodname && filename) {
|
||
RUBY_DTRACE_FUNCTION_ENTRY(
|
||
classname,
|
||
methodname,
|
||
filename,
|
||
rb_sourceline());
|
||
}
|
||
}
|
||
EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, id, klass);
|
||
{
|
||
rb_control_frame_t *reg_cfp = th->cfp;
|
||
... | ... | |
}
|
||
vm_pop_frame(th);
|
||
}
|
||
if (RUBY_DTRACE_FUNCTION_RETURN_ENABLED()) {
|
||
const char * classname = rb_class2name(klass);
|
||
const char * methodname = rb_id2name(id);
|
||
const char * filename = rb_sourcefile();
|
||
if (classname && methodname && filename) {
|
||
RUBY_DTRACE_FUNCTION_RETURN(
|
||
classname,
|
||
methodname,
|
||
filename,
|
||
rb_sourceline());
|
||
}
|
||
}
|
||
EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, recv, id, klass);
|
||
break;
|
||
}
|
||
... | ... | |
rb_bug("vm_call0: unsupported method type (%d)", def->type);
|
||
val = Qundef;
|
||
}
|
||
RUBY_VM_CHECK_INTS();
|
||
return val;
|
||
}
|
vm_insnhelper.c | ||
---|---|---|
#include <math.h>
|
||
#include "constant.h"
|
||
#include "internal.h"
|
||
#include "probes.h"
|
||
/* control stack frame */
|
||
... | ... | |
volatile VALUE val = 0;
|
||
const rb_method_definition_t *def = me->def;
|
||
if (RUBY_DTRACE_FUNCTION_ENTRY_ENABLED()) {
|
||
const char * classname = rb_class2name(me->klass);
|
||
const char * methodname = rb_id2name(me->called_id);
|
||
const char * filename = rb_sourcefile();
|
||
if (classname && methodname && filename) {
|
||
RUBY_DTRACE_FUNCTION_ENTRY(
|
||
classname,
|
||
methodname,
|
||
filename,
|
||
rb_sourceline());
|
||
}
|
||
}
|
||
EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, me->called_id, me->klass);
|
||
vm_push_frame(th, 0, VM_FRAME_MAGIC_CFUNC, recv,
|
||
... | ... | |
vm_pop_frame(th);
|
||
if (RUBY_DTRACE_FUNCTION_RETURN_ENABLED()) {
|
||
const char * classname = rb_class2name(me->klass);
|
||
const char * methodname = rb_id2name(me->called_id);
|
||
const char * filename = rb_sourcefile();
|
||
if (classname && methodname && filename) {
|
||
RUBY_DTRACE_FUNCTION_RETURN(
|
||
classname,
|
||
methodname,
|
||
filename,
|
||
rb_sourceline());
|
||
}
|
||
}
|
||
EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, recv, me->called_id, me->klass);
|
||
return val;
|
||
... | ... | |
rb_proc_t *proc;
|
||
VALUE val;
|
||
if (RUBY_DTRACE_FUNCTION_ENTRY_ENABLED()) {
|
||
RUBY_DTRACE_FUNCTION_ENTRY(
|
||
rb_class2name(me->klass),
|
||
rb_id2name(me->called_id),
|
||
rb_sourcefile(),
|
||
rb_sourceline());
|
||
}
|
||
EXEC_EVENT_HOOK(th, RUBY_EVENT_CALL, recv, me->called_id, me->klass);
|
||
/* control block frame */
|
||
... | ... | |
GetProcPtr(me->def->body.proc, proc);
|
||
val = rb_vm_invoke_proc(th, proc, recv, argc, argv, blockptr);
|
||
if (RUBY_DTRACE_FUNCTION_RETURN_ENABLED()) {
|
||
RUBY_DTRACE_FUNCTION_RETURN(
|
||
rb_class2name(me->klass),
|
||
rb_id2name(me->called_id),
|
||
rb_sourcefile(),
|
||
rb_sourceline());
|
||
}
|
||
EXEC_EVENT_HOOK(th, RUBY_EVENT_RETURN, recv, me->called_id, me->klass);
|
||
return val;
|