diff options
37 files changed, 850 insertions, 25 deletions
diff --git a/.gitignore b/.gitignore index d743d60510..de8f2a61fe 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ y.tab.c /exts.mk /goruby /id.h +/probes.h /largefile.h /lex.c /libruby*.* @@ -1,3 +1,60 @@ +Tue Nov 13 06:50:02 2012 Aaron Patterson <aaron@tenderlovemaking.com> + + * probes.d: add DTrace probe declarations. [ruby-core:27448] + + * array.c (empty_ary_alloc, ary_new): added array create DTrace probe. + + * compile.c (rb_insns_name): allowing DTrace probes to access + instruction sequence name. + + * Makefile.in: translate probes.d file to appropriate header file. + + * common.mk: declare dependencies on the DTrace header. + + * configure.in: add a test for existence of DTrace. + + * eval.c (setup_exception): add a probe for when an exception is + raised. + + * gc.c: Add DTrace probes for mark begin and end, and sweep begin and + end. + + * hash.c (empty_hash_alloc): Add a probe for hash allocation. + + * insns.def: Add probes for function entry and return. + + * internal.h: function declaration for compile.c change. + + * load.c (rb_f_load): add probes for `load` entry and exit, require + entry and exit, and wrapping search_required for load path search. + + * object.c (rb_obj_alloc): added a probe for general object creation. + + * parse.y (yycompile0): added a probe around parse and compile phase. + + * string.c (empty_str_alloc, str_new): DTrace probes for string + allocation. + + * test/dtrace/*: tests for DTrace probes. + + * vm.c (vm_invoke_proc): add probes for function return on exception + raise, hash create, and instruction sequence execution. + + * vm_core.h: add probe declarations for function entry and exit. + + * vm_dump.c: add probes header file. + + * vm_eval.c (vm_call0_cfunc, vm_call0_cfunc_with_frame): add probe on + function entry and return. + + * vm_exec.c: expose instruction number to instruction name function. + + * vm_insnshelper.c: add function entry and exit probes for cfunc + methods. + + * vm_insnhelper.h: vm usage information is always collected, so + uncomment the functions. + Mon Nov 12 19:14:50 2012 Akinori MUSHA <knu@iDaemons.org> * configure.in (isinf, isnan): isinf() and isnan() are macros on diff --git a/Makefile.in b/Makefile.in index 8d36063c97..2c8e7d2252 100644 --- a/Makefile.in +++ b/Makefile.in @@ -147,6 +147,7 @@ OBJDUMP = @OBJDUMP@ OBJCOPY = @OBJCOPY@ VCS = @VCS@ VCSUP = @VCSUP@ +DTRACE = @DTRACE@ OBJEXT = @OBJEXT@ ASMEXT = S @@ -263,6 +264,8 @@ reconfig config.status: $(srcdir)/configure $(srcdir)/enc/Makefile.in \ $(srcdir)/configure: $(srcdir)/configure.in $(CHDIR) $(srcdir) && exec $(AUTOCONF) +incs: id.h probes.h + # Things which should be considered: # * with gperf v.s. without gperf # * committers may have various versions of gperf @@ -318,6 +321,16 @@ enc/unicode/name2ctype.h: enc/unicode/name2ctype.kwd @$(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) @@ -16,6 +16,7 @@ #include "ruby/st.h" #include "ruby/encoding.h" #include "internal.h" +#include "probes.h" #ifndef ARRAY_DEBUG # define NDEBUG @@ -373,6 +374,16 @@ ary_alloc(VALUE klass) } static VALUE +empty_ary_alloc(VALUE klass) +{ + if(RUBY_DTRACE_ARRAY_CREATE_ENABLED()) { + RUBY_DTRACE_ARRAY_CREATE(0, rb_sourcefile(), rb_sourceline()); + } + + return ary_alloc(klass); +} + +static VALUE ary_new(VALUE klass, long capa) { VALUE ary; @@ -383,6 +394,11 @@ ary_new(VALUE klass, long capa) if (capa > ARY_MAX_SIZE) { rb_raise(rb_eArgError, "array size too big"); } + + if(RUBY_DTRACE_ARRAY_CREATE_ENABLED()) { + RUBY_DTRACE_ARRAY_CREATE(capa, rb_sourcefile(), rb_sourceline()); + } + ary = ary_alloc(klass); if (capa > RARRAY_EMBED_LEN_MAX) { FL_UNSET_EMBED(ary); @@ -5282,7 +5298,7 @@ Init_Array(void) rb_cArray = rb_define_class("Array", rb_cObject); rb_include_module(rb_cArray, rb_mEnumerable); - rb_define_alloc_func(rb_cArray, ary_alloc); + rb_define_alloc_func(rb_cArray, empty_ary_alloc); rb_define_singleton_method(rb_cArray, "[]", rb_ary_s_create, -1); rb_define_singleton_method(rb_cArray, "try_convert", rb_ary_s_try_convert, 1); rb_define_method(rb_cArray, "initialize", rb_ary_initialize, -1); @@ -2,7 +2,7 @@ bin: $(PROGRAM) $(WPROGRAM) 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 @@ -609,13 +609,14 @@ RUBY_H_INCLUDES = {$(VPATH)}ruby.h {$(VPATH)}config.h {$(VPATH)}defines.h \ {$(VPATH)}subst.h ENCODING_H_INCLUDES= {$(VPATH)}encoding.h {$(VPATH)}oniguruma.h ID_H_INCLUDES = {$(VPATH)}id.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)}ruby_atomic.h \ - $(ID_H_INCLUDES) + $(ID_H_INCLUDES) $(PROBES_H_INCLUDES) addr2line.$(OBJEXT): {$(VPATH)}addr2line.c {$(VPATH)}addr2line.h {$(VPATH)}config.h array.$(OBJEXT): {$(VPATH)}array.c $(RUBY_H_INCLUDES) {$(VPATH)}util.h \ - $(ENCODING_H_INCLUDES) {$(VPATH)}internal.h + $(ENCODING_H_INCLUDES) {$(VPATH)}internal.h $(PROBES_H_INCLUDES) bignum.$(OBJEXT): {$(VPATH)}bignum.c $(RUBY_H_INCLUDES) {$(VPATH)}util.h \ {$(VPATH)}thread.h {$(VPATH)}internal.h class.$(OBJEXT): {$(VPATH)}class.c $(RUBY_H_INCLUDES) \ @@ -648,11 +649,11 @@ error.$(OBJEXT): {$(VPATH)}error.c {$(VPATH)}known_errors.inc \ 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 @@ -660,9 +661,9 @@ gc.$(OBJEXT): {$(VPATH)}gc.c $(RUBY_H_INCLUDES) {$(VPATH)}re.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) {$(VPATH)}internal.h + $(ENCODING_H_INCLUDES) {$(VPATH)}internal.h $(PROBES_H_INCLUDES) inits.$(OBJEXT): {$(VPATH)}inits.c $(RUBY_H_INCLUDES) \ {$(VPATH)}internal.h io.$(OBJEXT): {$(VPATH)}io.c $(RUBY_H_INCLUDES) {$(VPATH)}io.h \ @@ -679,7 +680,7 @@ node.$(OBJEXT): {$(VPATH)}node.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 \ @@ -730,7 +731,7 @@ st.$(OBJEXT): {$(VPATH)}st.c $(RUBY_H_INCLUDES) strftime.$(OBJEXT): {$(VPATH)}strftime.c $(RUBY_H_INCLUDES) \ {$(VPATH)}timev.h $(ENCODING_H_INCLUDES) string.$(OBJEXT): {$(VPATH)}string.c $(RUBY_H_INCLUDES) {$(VPATH)}re.h \ - {$(VPATH)}regex.h $(ENCODING_H_INCLUDES) {$(VPATH)}internal.h + {$(VPATH)}regex.h $(ENCODING_H_INCLUDES) {$(VPATH)}internal.h $(PROBES_H_INCLUDES) struct.$(OBJEXT): {$(VPATH)}struct.c $(RUBY_H_INCLUDES) {$(VPATH)}internal.h thread.$(OBJEXT): {$(VPATH)}thread.c {$(VPATH)}eval_intern.h \ $(RUBY_H_INCLUDES) {$(VPATH)}gc.h $(VM_CORE_H_INCLUDES) \ @@ -761,13 +762,14 @@ compile.$(OBJEXT): {$(VPATH)}compile.c {$(VPATH)}iseq.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 \ {$(VPATH)}vm_insnhelper.c {$(VPATH)}vm_insnhelper.h {$(VPATH)}vm_exec.c \ {$(VPATH)}vm_exec.h {$(VPATH)}insns.def {$(VPATH)}vmtc.inc \ {$(VPATH)}vm.inc {$(VPATH)}insns.inc {$(VPATH)}debug.h \ - {$(VPATH)}internal.h {$(VPATH)}vm.h {$(VPATH)}constant.h + {$(VPATH)}internal.h {$(VPATH)}vm.h {$(VPATH)}constant.h $(PROBES_H_INCLUDES) vm_dump.$(OBJEXT): {$(VPATH)}vm_dump.c $(RUBY_H_INCLUDES) \ $(VM_CORE_H_INCLUDES) {$(VPATH)}debug.h {$(VPATH)}addr2line.h \ {$(VPATH)}internal.h @@ -5348,6 +5348,12 @@ dump_disasm_list(struct iseq_link_element *link) printf("---------------------\n"); } +const char * +rb_insns_name(int i) +{ + return insn_name_info[i]; +} + VALUE rb_insns_name_array(void) { diff --git a/configure.in b/configure.in index 9c0a246628..aafada5e03 100644 --- a/configure.in +++ b/configure.in @@ -372,6 +372,9 @@ AS_CASE(["$host_os:$build_os"], if test x"${build}" != x"${host}"; then AC_CHECK_TOOL(CC, gcc) fi + +AC_CHECK_TOOL(DTRACE, dtrace) + AC_PROG_CC AC_PROG_CXX RUBY_MINGW32 @@ -18,6 +18,7 @@ #include "ruby/encoding.h" #include "internal.h" #include "vm_core.h" +#include "probes.h" #define numberof(array) (int)(sizeof(array) / sizeof((array)[0])) @@ -499,6 +500,11 @@ setup_exception(rb_thread_t *th, int tag, volatile VALUE mesg) } 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); } } @@ -23,6 +23,7 @@ #include "gc.h" #include "constant.h" #include "ruby_atomic.h" +#include "probes.h" #include <stdio.h> #include <setjmp.h> #include <sys/types.h> @@ -120,7 +121,6 @@ typedef struct gc_profile_record { #endif } gc_profile_record; - #if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__) #pragma pack(push, 1) /* magic for reducing sizeof(RVALUE): 24 -> 20 */ #endif @@ -1294,6 +1294,7 @@ define_final(int argc, VALUE *argv, VALUE os) rb_raise(rb_eArgError, "wrong type argument %s (should be callable)", rb_obj_classname(block)); } + return define_final0(obj, block); } @@ -3017,6 +3018,9 @@ garbage_collect(rb_objspace_t *objspace) during_gc++; gc_marks(objspace); + if (RUBY_DTRACE_GC_SWEEP_BEGIN_ENABLED()) { + RUBY_DTRACE_GC_SWEEP_BEGIN(); + } gc_prof_sweep_timer_start(objspace); gc_sweep(objspace); gc_prof_sweep_timer_stop(objspace); @@ -3901,21 +3905,33 @@ gc_prof_timer_stop(rb_objspace_t *objspace, int marked) static inline void gc_prof_mark_timer_start(rb_objspace_t *objspace) { + if (RUBY_DTRACE_GC_MARK_BEGIN_ENABLED()) { + RUBY_DTRACE_GC_MARK_BEGIN(); + } } static inline void gc_prof_mark_timer_stop(rb_objspace_t *objspace) { + if (RUBY_DTRACE_GC_MARK_END_ENABLED()) { + RUBY_DTRACE_GC_MARK_END(); + } } static inline void gc_prof_sweep_timer_start(rb_objspace_t *objspace) { + if (RUBY_DTRACE_GC_SWEEP_BEGIN_ENABLED()) { + RUBY_DTRACE_GC_SWEEP_BEGIN(); + } } static inline void gc_prof_sweep_timer_stop(rb_objspace_t *objspace) { + if (RUBY_DTRACE_GC_SWEEP_END_ENABLED()) { + RUBY_DTRACE_GC_SWEEP_END(); + } } static inline void @@ -3949,6 +3965,9 @@ gc_prof_dec_live_num(rb_objspace_t *objspace) static inline void gc_prof_mark_timer_start(rb_objspace_t *objspace) { + if (RUBY_DTRACE_GC_MARK_BEGIN_ENABLED()) { + RUBY_DTRACE_GC_MARK_BEGIN(); + } if (objspace->profile.run) { size_t count = objspace->profile.count; @@ -3959,6 +3978,9 @@ gc_prof_mark_timer_start(rb_objspace_t *objspace) static inline void gc_prof_mark_timer_stop(rb_objspace_t *objspace) { + if (RUBY_DTRACE_GC_MARK_END_ENABLED()) { + RUBY_DTRACE_GC_MARK_END(); + } if (objspace->profile.run) { double mark_time = 0; size_t count = objspace->profile.count; @@ -3973,6 +3995,9 @@ gc_prof_mark_timer_stop(rb_objspace_t *objspace) static inline void gc_prof_sweep_timer_start(rb_objspace_t *objspace) { + if (RUBY_DTRACE_GC_SWEEP_BEGIN_ENABLED()) { + RUBY_DTRACE_GC_SWEEP_BEGIN(); + } if (objspace->profile.run) { size_t count = objspace->profile.count; @@ -3983,6 +4008,9 @@ gc_prof_sweep_timer_start(rb_objspace_t *objspace) static inline void gc_prof_sweep_timer_stop(rb_objspace_t *objspace) { + if (RUBY_DTRACE_GC_SWEEP_END_ENABLED()) { + RUBY_DTRACE_GC_SWEEP_END(); + } if (objspace->profile.run) { double sweep_time = 0; size_t count = objspace->profile.count; @@ -17,6 +17,7 @@ #include "ruby/encoding.h" #include "internal.h" #include <errno.h> +#include "probes.h" #ifdef __APPLE__ #include <crt_externs.h> @@ -215,6 +216,16 @@ hash_alloc(VALUE klass) return (VALUE)hash; } +static VALUE +empty_hash_alloc(VALUE klass) +{ + if(RUBY_DTRACE_HASH_CREATE_ENABLED()) { + RUBY_DTRACE_HASH_CREATE(0, rb_sourcefile(), rb_sourceline()); + } + + return hash_alloc(klass); +} + VALUE rb_hash_new(void) { @@ -3413,7 +3424,7 @@ Init_Hash(void) rb_include_module(rb_cHash, rb_mEnumerable); - rb_define_alloc_func(rb_cHash, hash_alloc); + rb_define_alloc_func(rb_cHash, empty_hash_alloc); rb_define_singleton_method(rb_cHash, "[]", rb_hash_s_create, -1); rb_define_singleton_method(rb_cHash, "try_convert", rb_hash_s_try_convert, 1); rb_define_method(rb_cHash,"initialize", rb_hash_initialize, -1); @@ -524,6 +524,11 @@ newhash (VALUE val) // inc += 1 - num; { rb_num_t i; + + if(RUBY_DTRACE_HASH_CREATE_ENABLED()) { + RUBY_DTRACE_HASH_CREATE(num, rb_sourcefile(), rb_sourceline()); + } + val = rb_hash_new(); for (i = num; i > 0; i -= 2) { @@ -838,6 +843,34 @@ trace { rb_event_flag_t flag = (rb_event_flag_t)nf; + 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 */); } diff --git a/internal.h b/internal.h index f925f94825..1ea0ca9f45 100644 --- a/internal.h +++ b/internal.h @@ -69,6 +69,7 @@ int rb_dvar_defined(ID); int rb_local_defined(ID); int rb_parse_in_eval(void); int rb_parse_in_main(void); +const char * rb_insns_name(int i); VALUE rb_insns_name_array(void); /* cont.c */ @@ -7,6 +7,7 @@ #include "internal.h" #include "dln.h" #include "eval_intern.h" +#include "probes.h" VALUE ruby_dln_librefs; @@ -621,6 +622,14 @@ rb_f_load(int argc, VALUE *argv) 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))) @@ -628,6 +637,11 @@ rb_f_load(int argc, VALUE *argv) path = fname; } rb_load_internal(path, RTEST(wrap)); + + if(RUBY_DTRACE_LOAD_RETURN_ENABLED()) { + RUBY_DTRACE_LOAD_RETURN(StringValuePtr(fname)); + } + return Qtrue; } @@ -861,6 +875,13 @@ rb_require_safe(VALUE fname, int safe) } 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) { @@ -871,7 +892,22 @@ rb_require_safe(VALUE fname, int safe) rb_set_safe_level_force(safe); FilePathValue(fname); rb_set_safe_level_force(0); + + if(RUBY_DTRACE_FIND_REQUIRE_ENTRY_ENABLED()) { + RUBY_DTRACE_FIND_REQUIRE_ENTRY( + StringValuePtr(fname), + rb_sourcefile(), + rb_sourceline()); + } + found = search_required(fname, &path, safe); + + if(RUBY_DTRACE_FIND_REQUIRE_RETURN_ENABLED()) { + RUBY_DTRACE_FIND_REQUIRE_RETURN( + StringValuePtr(fname), + rb_sourcefile(), + rb_sourceline()); + } if (found) { if (!path || !(ftptr = load_lock(RSTRING_PTR(path)))) { result = Qfalse; @@ -907,6 +943,10 @@ rb_require_safe(VALUE fname, int safe) th->errinfo = errinfo; + if(RUBY_DTRACE_REQUIRE_RETURN_ENABLED()) { + RUBY_DTRACE_REQUIRE_RETURN(StringValuePtr(fname)); + } + return result; } @@ -22,6 +22,7 @@ #include <float.h> #include "constant.h" #include "internal.h" +#include "probes.h" VALUE rb_cBasicObject; VALUE rb_mKernel; @@ -1685,7 +1686,15 @@ rb_obj_alloc(VALUE klass) klass); } + if (RUBY_DTRACE_OBJECT_CREATE_ENABLED()) { + const char * file = rb_sourcefile(); + RUBY_DTRACE_OBJECT_CREATE(rb_class2name(klass), + file ? file : "", + rb_sourceline()); + } + obj = (*allocator)(klass); + if (rb_obj_class(obj) != rb_class_real(klass)) { rb_raise(rb_eTypeError, "wrong instance allocation"); } @@ -29,6 +29,7 @@ #include <stdio.h> #include <errno.h> #include <ctype.h> +#include "probes.h" #define numberof(array) (int)(sizeof(array) / sizeof((array)[0])) @@ -5310,7 +5311,19 @@ yycompile0(VALUE arg) #ifndef RIPPER parser->parser_token_info_enabled = !compile_for_eval && RTEST(ruby_verbose); #endif +#ifndef RIPPER + if(RUBY_DTRACE_PARSE_BEGIN_ENABLED()) { + RUBY_DTRACE_PARSE_BEGIN(parser->parser_ruby_sourcefile, + parser->parser_ruby_sourceline); + } +#endif n = yyparse((void*)parser); +#ifndef RIPPER + if(RUBY_DTRACE_PARSE_END_ENABLED()) { + RUBY_DTRACE_PARSE_END(parser->parser_ruby_sourcefile, + parser->parser_ruby_sourceline); + } +#endif ruby_debug_lines = 0; ruby_coverage = 0; compile_for_eval = 0; diff --git a/probes.d b/probes.d new file mode 100644 index 0000000000..b3e8c1be5b --- /dev/null +++ b/probes.d @@ -0,0 +1,37 @@ +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 find__require__entry(const char *, const char *, int); + probe find__require__return(const char *, const char *, int); + + probe load__entry(const char *, const char *, int); + probe load__return(const char *); + + probe raise(const char *, const char *, int); + + probe object__create(const char *, const char *, int); + probe array__create(long, const char *, int); + probe hash__create(long, const char *, int); + probe string__create(long, const char *, int); + + probe parse__begin(const char *, int); + probe parse__end(const char *, int); + + probe insn(const char *); + probe insn__operand(const char *, const char *); + + probe gc__mark__begin(); + probe gc__mark__end(); + probe gc__sweep__begin(); + probe gc__sweep__end(); +}; + +#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 @@ -17,6 +17,7 @@ #include "node.h" #include "eval_intern.h" #include "internal.h" +#include "probes.h" #include <assert.h> #define BEG(no) (regs->beg[(no)]) @@ -381,6 +382,15 @@ str_alloc(VALUE klass) return (VALUE)str; } +static inline VALUE +empty_str_alloc(VALUE klass) +{ + if(RUBY_DTRACE_STRING_CREATE_ENABLED()) { + RUBY_DTRACE_STRING_CREATE(0, rb_sourcefile(), rb_sourceline()); + } + return str_alloc(klass); +} + static VALUE str_new(VALUE klass, const char *ptr, long len) { @@ -390,6 +400,10 @@ str_new(VALUE klass, const char *ptr, long len) rb_raise(rb_eArgError, "negative string size (or size too big)"); } + if(RUBY_DTRACE_STRING_CREATE_ENABLED()) { + RUBY_DTRACE_STRING_CREATE(len, rb_sourcefile(), rb_sourceline()); + } + str = str_alloc(klass); if (len > RSTRING_EMBED_LEN_MAX) { RSTRING(str)->as.heap.aux.capa = len; @@ -918,6 +932,10 @@ rb_str_dup(VALUE str) VALUE rb_str_resurrect(VALUE str) { + if(RUBY_DTRACE_STRING_CREATE_ENABLED()) { + RUBY_DTRACE_STRING_CREATE( + RSTRING_LEN(str), rb_sourcefile(), rb_sourceline()); + } return str_replace(str_alloc(rb_cString), str); } @@ -7920,7 +7938,7 @@ Init_String(void) rb_cString = rb_define_class("String", rb_cObject); rb_include_module(rb_cString, rb_mComparable); - rb_define_alloc_func(rb_cString, str_alloc); + rb_define_alloc_func(rb_cString, empty_str_alloc); rb_define_singleton_method(rb_cString, "try_convert", rb_str_s_try_convert, 1); rb_define_method(rb_cString, "initialize", rb_str_init, -1); rb_define_method(rb_cString, "initialize_copy", rb_str_replace, 1); diff --git a/test/dtrace/dummy.rb b/test/dtrace/dummy.rb new file mode 100644 index 0000000000..e85614228c --- /dev/null +++ b/test/dtrace/dummy.rb @@ -0,0 +1 @@ +# this is a dummy file used by test/dtrace/test_require.rb diff --git a/test/dtrace/helper.rb b/test/dtrace/helper.rb new file mode 100644 index 0000000000..ef691b5e32 --- /dev/null +++ b/test/dtrace/helper.rb @@ -0,0 +1,33 @@ +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 diff --git a/test/dtrace/test_array_create.rb b/test/dtrace/test_array_create.rb new file mode 100644 index 0000000000..22d52d4393 --- /dev/null +++ b/test/dtrace/test_array_create.rb @@ -0,0 +1,35 @@ +require 'dtrace/helper' + +module DTrace + class TestArrayCreate < TestCase + def test_lit + trap_probe(probe, '[]') { |_,rbfile,saw| + saw = saw.map(&:split).find_all { |num, file, line| + file == rbfile && num == '0' + } + assert_equal([rbfile], saw.map { |line| line[1] }) + assert_equal(['1'], saw.map { |line| line[2] }) + } + end + + def test_many_lit + trap_probe(probe, '[1,2,3,4]') { |_,rbfile,saw| + saw = saw.map(&:split).find_all { |num, file, line| + file == rbfile && num == '4' && line == '1' + } + assert_operator saw.length, :>, 0 + } + end + + private + def probe type = 'array' + <<-eoprobe +ruby$target:::#{type}-create +/arg1/ +{ + printf("%d %s %d\\n", arg0, copyinstr(arg1), arg2); +} + eoprobe + end + end +end diff --git a/test/dtrace/test_function_entry.rb b/test/dtrace/test_function_entry.rb new file mode 100644 index 0000000000..b9ba0477f7 --- /dev/null +++ b/test/dtrace/test_function_entry.rb @@ -0,0 +1,58 @@ +require 'dtrace/helper' + +module DTrace + class TestFunctionEntry < TestCase + def test_function_entry + probe = <<-eoprobe +ruby$target:::function-entry +/arg0 && arg1 && arg2/ +{ + 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 +/arg0 && arg1 && arg2/ +{ + 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 diff --git a/test/dtrace/test_gc.rb b/test/dtrace/test_gc.rb new file mode 100644 index 0000000000..24251c1a68 --- /dev/null +++ b/test/dtrace/test_gc.rb @@ -0,0 +1,26 @@ +require 'dtrace/helper' + +module DTrace + class TestGC < TestCase + %w{ + 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 diff --git a/test/dtrace/test_hash_create.rb b/test/dtrace/test_hash_create.rb new file mode 100644 index 0000000000..6f6738195a --- /dev/null +++ b/test/dtrace/test_hash_create.rb @@ -0,0 +1,52 @@ +require 'dtrace/helper' + +module DTrace + class TestHashCreate < TestCase + def test_hash_new + trap_probe(probe, 'Hash.new') { |_,rbfile,saw| + saw = saw.map(&:split).find_all { |num, file, line| + file == rbfile && num == '0' + } + assert_operator saw.length, :>, 0 + } + end + + def test_hash_lit + trap_probe(probe, '{}') { |_,rbfile,saw| + saw = saw.map(&:split).find_all { |num, file, line| + file == rbfile && num == '0' + } + assert_operator saw.length, :>, 0 + } + end + + def test_hash_lit_elements + trap_probe(probe, '{ :foo => :bar }') { |_,rbfile,saw| + saw = saw.map(&:split).find_all { |num, file, line| + file == rbfile && num == '2' + } + assert_operator saw.length, :>, 0 + } + end + + def test_hash_lit_elements_string + trap_probe(probe, '{ :foo => :bar, :bar => "baz" }') { |_,rbfile,saw| + saw = saw.map(&:split).find_all { |num, file, line| + file == rbfile && num == '4' + } + assert_operator saw.length, :>, 0 + } + end + + private + def probe + <<-eoprobe +ruby$target:::hash-create +/arg1/ +{ + printf("%d %s %d\\n", arg0, copyinstr(arg1), arg2); +} + eoprobe + end + end +end diff --git a/test/dtrace/test_load.rb b/test/dtrace/test_load.rb new file mode 100644 index 0000000000..8ac7e39119 --- /dev/null +++ b/test/dtrace/test_load.rb @@ -0,0 +1,52 @@ +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 diff --git a/test/dtrace/test_object_create_start.rb b/test/dtrace/test_object_create_start.rb new file mode 100644 index 0000000000..756b3b3545 --- /dev/null +++ b/test/dtrace/test_object_create_start.rb @@ -0,0 +1,35 @@ +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 + + private + def probe + <<-eoprobe +ruby$target:::object-create +{ + printf("%s %s %d\\n", copyinstr(arg0), copyinstr(arg1), arg2); +} + eoprobe + end + end +end diff --git a/test/dtrace/test_raise.rb b/test/dtrace/test_raise.rb new file mode 100644 index 0000000000..fedae8e1a7 --- /dev/null +++ b/test/dtrace/test_raise.rb @@ -0,0 +1,29 @@ +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 diff --git a/test/dtrace/test_require.rb b/test/dtrace/test_require.rb new file mode 100644 index 0000000000..c50037e334 --- /dev/null +++ b/test/dtrace/test_require.rb @@ -0,0 +1,34 @@ +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 diff --git a/test/dtrace/test_singleton_function.rb b/test/dtrace/test_singleton_function.rb new file mode 100644 index 0000000000..98fe7d3a77 --- /dev/null +++ b/test/dtrace/test_singleton_function.rb @@ -0,0 +1,55 @@ +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 diff --git a/test/dtrace/test_string.rb b/test/dtrace/test_string.rb new file mode 100644 index 0000000000..ca8453d38d --- /dev/null +++ b/test/dtrace/test_string.rb @@ -0,0 +1,27 @@ +require 'dtrace/helper' + +module DTrace + class TestStringProbes < TestCase + def test_object_create_start_string_lit + trap_probe(probe, '"omglolwutbbq"') { |_,rbfile,saw| + saw = saw.map(&:split).find_all { |klass, file, line, len| + file == rbfile && len == '12' && line == '1' + } + 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:::string-create +/arg1/ +{ + printf("String %s %d %d\\n", copyinstr(arg1), arg2, arg0); +} + eoprobe + end + end +end diff --git a/tool/gen_dummy_probes.sed b/tool/gen_dummy_probes.sed new file mode 100644 index 0000000000..476f6f633b --- /dev/null +++ b/tool/gen_dummy_probes.sed @@ -0,0 +1,24 @@ + +# 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\)/ @@ -18,6 +18,7 @@ #include "vm_core.h" #include "iseq.h" #include "eval_intern.h" +#include "probes.h" static inline VALUE * VM_EP_LEP(VALUE *ep) @@ -65,10 +66,11 @@ rb_vm_control_frame_block_ptr(rb_control_frame_t *cfp) #define VM_COLLECT_USAGE_DETAILS 0 #endif -#if VM_COLLECT_USAGE_DETAILS static void vm_collect_usage_operand(int insn, int n, VALUE op); -static void vm_collect_usage_register(int reg, int isset); static void vm_collect_usage_insn(int insn); + +#if VM_COLLECT_USAGE_DETAILS +static void vm_collect_usage_register(int reg, int isset); #endif static VALUE @@ -1179,6 +1181,7 @@ vm_exec(rb_thread_t *th) if (UNLIKELY(VM_FRAME_TYPE(th->cfp) == VM_FRAME_MAGIC_CFUNC)) { const rb_method_entry_t *me = th->cfp->me; EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, th->cfp->self, me->called_id, me->klass); + RUBY_DTRACE_FUNC_RETURN_HOOK(me->klass, me->called_id); } th->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); } @@ -1983,6 +1986,10 @@ m_core_hash_from_ary(VALUE self, VALUE ary) VALUE hash = rb_hash_new(); int i; + if(RUBY_DTRACE_HASH_CREATE_ENABLED()) { + RUBY_DTRACE_HASH_CREATE(RARRAY_LEN(ary), rb_sourcefile(), rb_sourceline()); + } + for (i=0; i<RARRAY_LEN(ary); i+=2) { rb_hash_aset(hash, RARRAY_PTR(ary)[i], RARRAY_PTR(ary)[i+1]); } @@ -2308,6 +2315,11 @@ rb_ruby_debug_ptr(void) return ruby_vm_debug_ptr(GET_VM()); } +/* iseq.c */ +VALUE insn_operand_intern(rb_iseq_t *iseq, + VALUE insn, int op_no, VALUE op, + int len, size_t pos, VALUE *pnop, VALUE child); + #if VM_COLLECT_USAGE_DETAILS #define HASH_ASET(h, k, v) st_insert(RHASH_TBL(h), (st_data_t)(k), (st_data_t)(v)) @@ -2365,11 +2377,6 @@ vm_analysis_insn(int insn) prev_insn = insn; } -/* iseq.c */ -VALUE insn_operand_intern(rb_iseq_t *iseq, - VALUE insn, int op_no, VALUE op, - int len, size_t pos, VALUE *pnop, VALUE child); - static void vm_analysis_operand(int insn, int n, VALUE op) { @@ -2476,10 +2483,21 @@ usage_analysis_register_stop(VALUE self) return Qnil; } +#else + +void (*ruby_vm_collect_usage_func_insn)(int insn) = NULL; +void (*ruby_vm_collect_usage_func_operand)(int insn, int n, VALUE op) = NULL; +void (*ruby_vm_collect_usage_func_register)(int reg, int isset) = NULL; + +#endif + /* @param insn instruction number */ static void vm_collect_usage_insn(int insn) { + if (RUBY_DTRACE_INSN_ENABLED()) { + RUBY_DTRACE_INSN(rb_insns_name(insn)); + } if (ruby_vm_collect_usage_func_insn) (*ruby_vm_collect_usage_func_insn)(insn); } @@ -2491,10 +2509,18 @@ vm_collect_usage_insn(int insn) static void vm_collect_usage_operand(int insn, int n, VALUE op) { + if (RUBY_DTRACE_INSN_OPERAND_ENABLED()) { + VALUE valstr; + + valstr = insn_operand_intern(GET_THREAD()->cfp->iseq, insn, n, op, 0, 0, 0, 0); + + RUBY_DTRACE_INSN_OPERAND(RSTRING_PTR(valstr), rb_insns_name(insn)); + } if (ruby_vm_collect_usage_func_operand) (*ruby_vm_collect_usage_func_operand)(insn, n, op); } +#if VM_COLLECT_USAGE_DETAILS /* @param reg register id. see code of vm_analysis_register() */ /* @param iseset 0: read, 1: write */ static void @@ -2503,5 +2529,5 @@ vm_collect_usage_register(int reg, int isset) if (ruby_vm_collect_usage_func_register) (*ruby_vm_collect_usage_func_register)(reg, isset); } - #endif + @@ -23,6 +23,7 @@ #include "id.h" #include "method.h" #include "ruby_atomic.h" +#include "probes.h" #if defined(_WIN32) #include "thread_win32.h" @@ -898,6 +899,35 @@ rb_threadptr_exec_event_hooks(rb_thread_t *th, rb_event_flag_t flag, VALUE self, } \ } while (0) +#define RUBY_DTRACE_FUNC_ENTRY_HOOK(klass, id) \ + 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()); \ + } \ + } \ + +#define RUBY_DTRACE_FUNC_RETURN_HOOK(klass, id) \ + 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()); \ + } \ + } \ + + #if defined __GNUC__ && __GNUC__ >= 4 #pragma GCC visibility push(default) #endif @@ -13,6 +13,7 @@ #include "addr2line.h" #include "vm_core.h" #include "internal.h" +#include "probes.h" /* see vm_insnhelper.h for the values */ #ifndef VMDEBUG @@ -55,6 +55,7 @@ vm_call0_cfunc(rb_thread_t* th, rb_call_info_t *ci, const VALUE *argv) { VALUE val; + RUBY_DTRACE_FUNC_ENTRY_HOOK(ci->defined_class, ci->mid); EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, ci->recv, ci->mid, ci->defined_class); { rb_control_frame_t *reg_cfp = th->cfp; @@ -84,6 +85,7 @@ vm_call0_cfunc(rb_thread_t* th, rb_call_info_t *ci, const VALUE *argv) } } EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, ci->recv, ci->mid, ci->defined_class); + RUBY_DTRACE_FUNC_RETURN_HOOK(ci->defined_class, ci->mid); return val; } @@ -93,6 +95,7 @@ vm_call0_cfunc_with_frame(rb_thread_t* th, rb_call_info_t *ci, const VALUE *argv { VALUE val; + RUBY_DTRACE_FUNC_ENTRY_HOOK(ci->defined_class, ci->mid); EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, ci->recv, ci->mid, ci->defined_class); { rb_control_frame_t *reg_cfp = th->cfp; @@ -116,6 +119,7 @@ vm_call0_cfunc_with_frame(rb_thread_t* th, rb_call_info_t *ci, const VALUE *argv vm_pop_frame(th); } EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, ci->recv, ci->mid, ci->defined_class); + RUBY_DTRACE_FUNC_RETURN_HOOK(ci->defined_class, ci->mid); return val; } @@ -1007,6 +1011,7 @@ rb_iterate(VALUE (* it_proc) (VALUE), VALUE data1, if (UNLIKELY(VM_FRAME_TYPE(th->cfp) == VM_FRAME_MAGIC_CFUNC)) { const rb_method_entry_t *me = th->cfp->me; EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, th->cfp->self, me->called_id, me->klass); + RUBY_DTRACE_FUNC_RETURN_HOOK(me->klass, me->called_id); } th->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); @@ -11,6 +11,8 @@ #include <math.h> +void vm_analysis_insn(int insn); + #if VMDEBUG > 0 #define DECL_SC_REG(type, r, reg) register type reg_##r diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 554aad47b1..32b0851320 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -13,6 +13,7 @@ #include <math.h> #include "constant.h" #include "internal.h" +#include "probes.h" /* control stack frame */ @@ -1431,6 +1432,7 @@ vm_call_cfunc_with_frame(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_i int len = cfunc->argc; VALUE recv = ci->recv; + RUBY_DTRACE_FUNC_ENTRY_HOOK(me->klass, me->called_id); EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, me->called_id, me->klass); vm_push_frame(th, 0, VM_FRAME_MAGIC_CFUNC, recv, ci->defined_class, @@ -1449,6 +1451,7 @@ vm_call_cfunc_with_frame(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_i vm_pop_frame(th); EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, recv, me->called_id, me->klass); + RUBY_DTRACE_FUNC_RETURN_HOOK(me->klass, me->called_id); return val; } @@ -1496,6 +1499,7 @@ vm_call_cfunc(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci) if (len >= 0) rb_check_arity(ci->argc, len, len); + RUBY_DTRACE_FUNC_ENTRY_HOOK(me->klass, me->called_id); EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, me->called_id, me->klass); if (!(ci->me->flag & NOEX_PROTECTED) && @@ -1505,6 +1509,7 @@ vm_call_cfunc(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci) val = vm_call_cfunc_latter(th, reg_cfp, ci); EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, recv, me->called_id, me->klass); + RUBY_DTRACE_FUNC_RETURN_HOOK(me->klass, me->called_id); return val; } @@ -1553,6 +1558,7 @@ vm_call_bmethod_body(rb_thread_t *th, rb_call_info_t *ci, const VALUE *argv) rb_proc_t *proc; VALUE val; + RUBY_DTRACE_FUNC_ENTRY_HOOK(ci->me->klass, ci->me->called_id); EXEC_EVENT_HOOK(th, RUBY_EVENT_CALL, ci->recv, ci->me->called_id, ci->me->klass); /* control block frame */ @@ -1561,6 +1567,7 @@ vm_call_bmethod_body(rb_thread_t *th, rb_call_info_t *ci, const VALUE *argv) val = vm_invoke_proc(th, proc, ci->recv, ci->defined_class, ci->argc, argv, ci->blockptr); EXEC_EVENT_HOOK(th, RUBY_EVENT_RETURN, ci->recv, ci->me->called_id, ci->me->klass); + RUBY_DTRACE_FUNC_RETURN_HOOK(ci->me->klass, ci->me->called_id); return val; } diff --git a/vm_insnhelper.h b/vm_insnhelper.h index b87d8a68ae..2748cd57c7 100644 --- a/vm_insnhelper.h +++ b/vm_insnhelper.h @@ -62,13 +62,12 @@ enum { extern char ruby_vm_redefined_flag[BOP_LAST_]; extern VALUE ruby_vm_const_missing_count; -#if VM_COLLECT_USAGE_DETAILS #define COLLECT_USAGE_INSN(insn) vm_collect_usage_insn(insn) #define COLLECT_USAGE_OPERAND(insn, n, op) vm_collect_usage_operand((insn), (n), ((VALUE)(op))) + +#if VM_COLLECT_USAGE_DETAILS #define COLLECT_USAGE_REGISTER(reg, s) vm_collect_usage_register((reg), (s)) #else -#define COLLECT_USAGE_INSN(insn) /* none */ -#define COLLECT_USAGE_OPERAND(insn, n, op) /* none */ #define COLLECT_USAGE_REGISTER(reg, s) /* none */ #endif |