From 680f0b5ba4eb4ba4f542ebc8d1204da61b71eb92 Mon Sep 17 00:00:00 2001 From: ko1 Date: Mon, 27 May 2013 00:21:02 +0000 Subject: * include/ruby/ruby.h, gc.c, vm_trace.c: add internal events. * RUBY_INTERNAL_EVENT_NEWOBJ: object created. * RUBY_INTERNAL_EVENT_FREE: object freeed. * RUBY_INTERNAL_EVENT_GC_START: GC started. And rename `RUBY_EVENT_SWITCH' to `RUBY_INTERNAL_EVENT_SWITCH'. Internal events can not invoke any Ruby program because the tracing timing may be critical (under huge restriction). These events can be hooked only by C-extensions. We recommend to use rb_potponed_job_register() API to call Ruby program safely. This change is mostly written by Aman Gupta (tmm1). https://bugs.ruby-lang.org/issues/8107#note-12 [Feature #8107] * include/ruby/debug.h, vm_trace.c: added two new APIs. * rb_tracearg_event_flag() returns rb_event_flag_t of this event. * rb_tracearg_object() returns created/freeed object. * ext/-test-/tracepoint/extconf.rb, ext/-test-/tracepoint/tracepoint.c, test/-ext-/tracepoint/test_tracepoint.rb: add a test. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@40946 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 26 +++++++++++++ ext/-test-/tracepoint/extconf.rb | 1 + ext/-test-/tracepoint/tracepoint.c | 65 ++++++++++++++++++++++++++++++++ gc.c | 30 ++++++++++++++- include/ruby/debug.h | 2 + include/ruby/ruby.h | 24 ++++++++---- internal.h | 1 + test/-ext-/tracepoint/test_tracepoint.rb | 40 ++++++++++++++++++++ thread.c | 2 +- vm_trace.c | 27 ++++++++++++- 10 files changed, 207 insertions(+), 11 deletions(-) create mode 100644 ext/-test-/tracepoint/extconf.rb create mode 100644 ext/-test-/tracepoint/tracepoint.c create mode 100644 test/-ext-/tracepoint/test_tracepoint.rb diff --git a/ChangeLog b/ChangeLog index c34d653e94..9d695f7fe3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +Mon May 27 09:05:17 2013 Koichi Sasada + + * include/ruby/ruby.h, gc.c, vm_trace.c: add internal events. + * RUBY_INTERNAL_EVENT_NEWOBJ: object created. + * RUBY_INTERNAL_EVENT_FREE: object freeed. + * RUBY_INTERNAL_EVENT_GC_START: GC started. + And rename `RUBY_EVENT_SWITCH' to `RUBY_INTERNAL_EVENT_SWITCH'. + + Internal events can not invoke any Ruby program because the tracing + timing may be critical (under huge restriction). + These events can be hooked only by C-extensions. + We recommend to use rb_potponed_job_register() API to call Ruby + program safely. + + This change is mostly written by Aman Gupta (tmm1). + https://bugs.ruby-lang.org/issues/8107#note-12 + [Feature #8107] + + * include/ruby/debug.h, vm_trace.c: added two new APIs. + * rb_tracearg_event_flag() returns rb_event_flag_t of this event. + * rb_tracearg_object() returns created/freeed object. + + * ext/-test-/tracepoint/extconf.rb, + ext/-test-/tracepoint/tracepoint.c, + test/-ext-/tracepoint/test_tracepoint.rb: add a test. + Mon May 27 08:38:21 2013 Koichi Sasada * ext/-test-/postponed_job/postponed_job.c: fix `init' function name. diff --git a/ext/-test-/tracepoint/extconf.rb b/ext/-test-/tracepoint/extconf.rb new file mode 100644 index 0000000000..c0c2399eb4 --- /dev/null +++ b/ext/-test-/tracepoint/extconf.rb @@ -0,0 +1 @@ +create_makefile("-test-/tracepoint") diff --git a/ext/-test-/tracepoint/tracepoint.c b/ext/-test-/tracepoint/tracepoint.c new file mode 100644 index 0000000000..220280c951 --- /dev/null +++ b/ext/-test-/tracepoint/tracepoint.c @@ -0,0 +1,65 @@ +#include "ruby/ruby.h" +#include "ruby/debug.h" + +static size_t newobj_count; +static size_t free_count; +static size_t gc_start_count; +static size_t objects_count; +static VALUE objects[10]; + +void +tracepoint_track_objspace_events_i(VALUE tpval, void *data) +{ + rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval); + switch (rb_tracearg_event_flag(tparg)) { + case RUBY_INTERNAL_EVENT_NEWOBJ: + { + VALUE obj = rb_tracearg_object(tparg); + if (objects_count < sizeof(objects)/sizeof(VALUE)) objects[objects_count++] = obj; + newobj_count++; + break; + } + case RUBY_INTERNAL_EVENT_FREE: + { + free_count++; + break; + } + case RUBY_INTERNAL_EVENT_GC_START: + { + gc_start_count++; + break; + } + default: + rb_raise(rb_eRuntimeError, "unknown event"); + } +} + +VALUE +tracepoint_track_objspace_events(VALUE self) +{ + VALUE tpval = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ | RUBY_INTERNAL_EVENT_FREE | RUBY_INTERNAL_EVENT_GC_START, tracepoint_track_objspace_events_i, 0); + VALUE result = rb_ary_new(); + int i; + + newobj_count = free_count = gc_start_count = objects_count = 0; + + rb_tracepoint_enable(tpval); + rb_yield(Qundef); + rb_tracepoint_disable(tpval); + + rb_ary_push(result, SIZET2NUM(newobj_count)); + rb_ary_push(result, SIZET2NUM(free_count)); + rb_ary_push(result, SIZET2NUM(gc_start_count)); + for (i=0; ihook_events = event & RUBY_INTERNAL_EVENT_OBJSPACE_MASK; +} + +static void +gc_event_hook_body(rb_objspace_t *objspace, const rb_event_flag_t event, VALUE data) +{ + rb_thread_t *th = GET_THREAD(); + EXEC_EVENT_HOOK(th, event, th->cfp->self, 0, 0, data); +} + +#define gc_event_hook(objspace, event, data) do { \ + if (UNLIKELY((objspace)->hook_events & (event))) { \ + gc_event_hook_body((objspace), (event), (data)); \ + } \ +} while (0) + + static VALUE newobj_of(VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3) { @@ -870,7 +892,6 @@ newobj_of(VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3) RANY(obj)->file = rb_sourcefile(); RANY(obj)->line = rb_sourceline(); #endif - objspace->total_allocated_object_num++; #if RGENGC_PROFILE if (flags & FL_WB_PROTECTED) objspace->profile.generated_sunny_object_count++; @@ -889,6 +910,9 @@ newobj_of(VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3) if (rgengc_remembered(objspace, (VALUE)obj)) rb_bug("newobj: %p (%s) is remembered.\n", (void *)obj, obj_type_name(obj)); #endif + objspace->total_allocated_object_num++; + gc_event_hook(objspace, RUBY_INTERNAL_EVENT_NEWOBJ, obj); + return obj; } @@ -1097,6 +1121,8 @@ make_io_deferred(RVALUE *p) static int obj_free(rb_objspace_t *objspace, VALUE obj) { + gc_event_hook(objspace, RUBY_INTERNAL_EVENT_FREE, obj); + switch (BUILTIN_TYPE(obj)) { case T_NIL: case T_FIXNUM: @@ -3785,6 +3811,8 @@ garbage_collect_body(rb_objspace_t *objspace, int full_mark, int immediate_sweep objspace->rgengc.oldgen_object_count = 0; } + gc_event_hook(objspace, RUBY_INTERNAL_EVENT_GC_START, 0 /* TODO: pass minor/immediate flag? */); + gc_prof_timer_start(objspace, reason | (minor_gc ? GPR_FLAG_MINOR : 0)); { assert(during_gc > 0); diff --git a/include/ruby/debug.h b/include/ruby/debug.h index 5663448b6c..dca0f1087b 100644 --- a/include/ruby/debug.h +++ b/include/ruby/debug.h @@ -56,6 +56,7 @@ VALUE rb_tracepoint_enabled_p(VALUE tpval); typedef struct rb_trace_arg_struct rb_trace_arg_t; rb_trace_arg_t *rb_tracearg_from_tracepoint(VALUE tpval); +rb_event_flag_t rb_tracearg_event_flag(rb_trace_arg_t *trace_arg); VALUE rb_tracearg_event(rb_trace_arg_t *trace_arg); VALUE rb_tracearg_lineno(rb_trace_arg_t *trace_arg); VALUE rb_tracearg_path(rb_trace_arg_t *trace_arg); @@ -65,6 +66,7 @@ VALUE rb_tracearg_binding(rb_trace_arg_t *trace_arg); VALUE rb_tracearg_self(rb_trace_arg_t *trace_arg); VALUE rb_tracearg_return_value(rb_trace_arg_t *trace_arg); VALUE rb_tracearg_raised_exception(rb_trace_arg_t *trace_arg); +VALUE rb_tracearg_object(rb_trace_arg_t *trace_arg); /* Postponed Job API */ typedef void (*rb_postponed_job_func_t)(void *arg); diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index 9404d97d80..33c6e8a694 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -1715,16 +1715,24 @@ int ruby_native_thread_p(void); #define RUBY_EVENT_ALL 0x00ff /* for TracePoint extended events */ -#define RUBY_EVENT_B_CALL 0x0100 -#define RUBY_EVENT_B_RETURN 0x0200 -#define RUBY_EVENT_THREAD_BEGIN 0x0400 -#define RUBY_EVENT_THREAD_END 0x0800 -#define RUBY_EVENT_TRACEPOINT_ALL 0xFFFF +#define RUBY_EVENT_B_CALL 0x0100 +#define RUBY_EVENT_B_RETURN 0x0200 +#define RUBY_EVENT_THREAD_BEGIN 0x0400 +#define RUBY_EVENT_THREAD_END 0x0800 +#define RUBY_EVENT_TRACEPOINT_ALL 0xffff /* special events */ -#define RUBY_EVENT_SPECIFIED_LINE 0x10000 -#define RUBY_EVENT_SWITCH 0x20000 -#define RUBY_EVENT_COVERAGE 0x40000 +#define RUBY_EVENT_SPECIFIED_LINE 0x010000 +#define RUBY_EVENT_COVERAGE 0x020000 + +/* internal events */ +#define RUBY_INTERNAL_EVENT_SWITCH 0x040000 + /* 0x080000 */ +#define RUBY_INTERNAL_EVENT_NEWOBJ 0x100000 +#define RUBY_INTERNAL_EVENT_FREE 0x200000 +#define RUBY_INTERNAL_EVENT_GC_START 0x400000 +#define RUBY_INTERNAL_EVENT_OBJSPACE_MASK 0x700000 +#define RUBY_INTERNAL_EVENT_MASK 0xfffe0000 typedef unsigned long rb_event_flag_t; typedef void (*rb_event_hook_func_t)(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass); diff --git a/internal.h b/internal.h index b49e748f16..89cce7e69a 100644 --- a/internal.h +++ b/internal.h @@ -188,6 +188,7 @@ void rb_w32_init_file(void); /* gc.c */ void Init_heap(void); void *ruby_mimmalloc(size_t size); +void rb_objspace_set_event_hook(const rb_event_flag_t event); /* hash.c */ struct st_table *rb_hash_tbl_raw(VALUE hash); diff --git a/test/-ext-/tracepoint/test_tracepoint.rb b/test/-ext-/tracepoint/test_tracepoint.rb new file mode 100644 index 0000000000..058809471f --- /dev/null +++ b/test/-ext-/tracepoint/test_tracepoint.rb @@ -0,0 +1,40 @@ +require 'test/unit' +require '-test-/tracepoint' + +class TestTracepointObj < Test::Unit::TestCase + def test_not_available_from_ruby + assert_raises ArgumentError do + TracePoint.trace(:obj_new){} + end + end + + def test_tracks_objspace_events + result = Bug.tracepoint_track_objspace_events{ + 99 + 'abc' + v="foobar" + Object.new + nil + } + + newobj_count, free_count, gc_start_count, *newobjs = *result + assert_equal 2, newobj_count + assert_equal 2, newobjs.size + assert_equal 'foobar', newobjs[0] + assert_equal Object, newobjs[1].class + + stat1 = {} + stat2 = {} + GC.stat(stat1) + result = Bug.tracepoint_track_objspace_events{ + 1_000_000.times{''} + } + GC.stat(stat2) + + newobj_count, free_count, gc_start_count, *newobjs = *result + + assert_operator stat2[:total_allocated_object] - stat1[:total_allocated_object], :>=, newobj_count + assert_operator stat2[:total_freed_object] - stat1[:total_freed_object], :>=, free_count + assert_operator stat2[:count] - stat1[:count], :==, gc_start_count + end +end diff --git a/thread.c b/thread.c index 5f7f90f61a..c845fd2bbe 100644 --- a/thread.c +++ b/thread.c @@ -1989,7 +1989,7 @@ rb_threadptr_execute_interrupts(rb_thread_t *th, int blocking_timing) if (th->status == THREAD_RUNNABLE) th->running_time_us += TIME_QUANTUM_USEC; - EXEC_EVENT_HOOK(th, RUBY_EVENT_SWITCH, th->cfp->self, 0, 0, Qundef); + EXEC_EVENT_HOOK(th, RUBY_INTERNAL_EVENT_SWITCH, th->cfp->self, 0, 0, Qundef); rb_thread_schedule_limits(limits_us); } diff --git a/vm_trace.c b/vm_trace.c index 0c54426368..9c77153c3e 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -72,6 +72,8 @@ recalc_add_ruby_vm_event_flags(rb_event_flag_t events) } ruby_vm_event_flags |= ruby_event_flag_count[i] ? (1<hooks; hook; hook = hook->next) { - if (LIKELY(!(hook->hook_flags & RUBY_EVENT_HOOK_FLAG_DELETED)) && (trace_arg->event & hook->events)) { + if (!(hook->hook_flags & RUBY_EVENT_HOOK_FLAG_DELETED) && (trace_arg->event & hook->events)) { if (!(hook->hook_flags & RUBY_EVENT_HOOK_FLAG_RAW_ARG)) { (*hook->func)(trace_arg->event, hook->data, trace_arg->self, trace_arg->id, trace_arg->klass); } @@ -692,6 +696,12 @@ rb_tracearg_from_tracepoint(VALUE tpval) return get_trace_arg(); } +rb_event_flag_t +rb_tracearg_event_flag(rb_trace_arg_t *trace_arg) +{ + return trace_arg->event; +} + VALUE rb_tracearg_event(rb_trace_arg_t *trace_arg) { @@ -813,6 +823,21 @@ rb_tracearg_raised_exception(rb_trace_arg_t *trace_arg) return trace_arg->data; } +VALUE +rb_tracearg_object(rb_trace_arg_t *trace_arg) +{ + if (trace_arg->event & (RUBY_INTERNAL_EVENT_NEWOBJ | RUBY_INTERNAL_EVENT_FREE)) { + /* ok */ + } + else { + rb_raise(rb_eRuntimeError, "not supported by this event"); + } + if (trace_arg->data == Qundef) { + rb_bug("tp_attr_raised_exception_m: unreachable"); + } + return trace_arg->data; +} + /* * Type of event * -- cgit v1.2.3