aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--ChangeLog57
-rw-r--r--Makefile.in13
-rw-r--r--array.c18
-rw-r--r--common.mk22
-rw-r--r--compile.c6
-rw-r--r--configure.in3
-rw-r--r--eval.c6
-rw-r--r--gc.c30
-rw-r--r--hash.c13
-rw-r--r--insns.def33
-rw-r--r--internal.h1
-rw-r--r--load.c40
-rw-r--r--object.c9
-rw-r--r--parse.y13
-rw-r--r--probes.d37
-rw-r--r--string.c20
-rw-r--r--test/dtrace/dummy.rb1
-rw-r--r--test/dtrace/helper.rb33
-rw-r--r--test/dtrace/test_array_create.rb35
-rw-r--r--test/dtrace/test_function_entry.rb58
-rw-r--r--test/dtrace/test_gc.rb26
-rw-r--r--test/dtrace/test_hash_create.rb52
-rw-r--r--test/dtrace/test_load.rb52
-rw-r--r--test/dtrace/test_object_create_start.rb35
-rw-r--r--test/dtrace/test_raise.rb29
-rw-r--r--test/dtrace/test_require.rb34
-rw-r--r--test/dtrace/test_singleton_function.rb55
-rw-r--r--test/dtrace/test_string.rb27
-rw-r--r--tool/gen_dummy_probes.sed24
-rw-r--r--vm.c42
-rw-r--r--vm_core.h30
-rw-r--r--vm_dump.c1
-rw-r--r--vm_eval.c5
-rw-r--r--vm_exec.c2
-rw-r--r--vm_insnhelper.c7
-rw-r--r--vm_insnhelper.h5
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*.*
diff --git a/ChangeLog b/ChangeLog
index 15925907f6..1586239bcf 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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)
diff --git a/array.c b/array.c
index df0a0a43aa..153a430c20 100644
--- a/array.c
+++ b/array.c
@@ -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);
diff --git a/common.mk b/common.mk
index d43c2816c6..adf1c246ae 100644
--- a/common.mk
+++ b/common.mk
@@ -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
diff --git a/compile.c b/compile.c
index 16bfcefdbb..2f7fc1d6b4 100644
--- a/compile.c
+++ b/compile.c
@@ -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
diff --git a/eval.c b/eval.c
index 9288917b27..0f1d3d97d0 100644
--- a/eval.c
+++ b/eval.c
@@ -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);
}
}
diff --git a/gc.c b/gc.c
index c1e06f841c..0265b8e3fd 100644
--- a/gc.c
+++ b/gc.c
@@ -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;
diff --git a/hash.c b/hash.c
index c6ce897e53..a9047be968 100644
--- a/hash.c
+++ b/hash.c
@@ -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);
diff --git a/insns.def b/insns.def
index a096c5b843..32d6c9a873 100644
--- a/insns.def
+++ b/insns.def
@@ -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 */
diff --git a/load.c b/load.c
index 827679ebac..98a5900b5b 100644
--- a/load.c
+++ b/load.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;
}
diff --git a/object.c b/object.c
index 4d4daaf006..7bfc7895f1 100644
--- a/object.c
+++ b/object.c
@@ -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");
}
diff --git a/parse.y b/parse.y
index a025a51db7..5741eb9ff6 100644
--- a/parse.y
+++ b/parse.y
@@ -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
diff --git a/string.c b/string.c
index 66af80d0bb..c63f59ad90 100644
--- a/string.c
+++ b/string.c
@@ -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\)/
diff --git a/vm.c b/vm.c
index 23f6996895..9ba7be364b 100644
--- a/vm.c
+++ b/vm.c
@@ -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
+
diff --git a/vm_core.h b/vm_core.h
index dd9e3872e2..757a3a5a15 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -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
diff --git a/vm_dump.c b/vm_dump.c
index 1c1a36eca9..bfb58dd4d5 100644
--- a/vm_dump.c
+++ b/vm_dump.c
@@ -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
diff --git a/vm_eval.c b/vm_eval.c
index 775539a97d..2f3e66eefd 100644
--- a/vm_eval.c
+++ b/vm_eval.c
@@ -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);
diff --git a/vm_exec.c b/vm_exec.c
index 7cdd96ff39..cdf0fb11bc 100644
--- a/vm_exec.c
+++ b/vm_exec.c
@@ -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