ruby-changes:28894
From: ko1 <ko1@a...>
Date: Mon, 27 May 2013 09:21:16 +0900 (JST)
Subject: [ruby-changes:28894] ko1:r40946 (trunk): * include/ruby/ruby.h, gc.c, vm_trace.c: add internal events.
ko1 2013-05-27 09:21:02 +0900 (Mon, 27 May 2013) New Revision: 40946 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=40946 Log: * 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. Added directories: trunk/ext/-test-/tracepoint/ trunk/test/-ext-/tracepoint/ Added files: trunk/ext/-test-/tracepoint/extconf.rb trunk/ext/-test-/tracepoint/tracepoint.c trunk/test/-ext-/tracepoint/test_tracepoint.rb Modified files: trunk/ChangeLog trunk/gc.c trunk/include/ruby/debug.h trunk/include/ruby/ruby.h trunk/internal.h trunk/thread.c trunk/vm_trace.c Index: include/ruby/ruby.h =================================================================== --- include/ruby/ruby.h (revision 40945) +++ include/ruby/ruby.h (revision 40946) @@ -1715,16 +1715,24 @@ int ruby_native_thread_p(void); https://github.com/ruby/ruby/blob/trunk/include/ruby/ruby.h#L1715 #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); Index: include/ruby/debug.h =================================================================== --- include/ruby/debug.h (revision 40945) +++ include/ruby/debug.h (revision 40946) @@ -56,6 +56,7 @@ VALUE rb_tracepoint_enabled_p(VALUE tpva https://github.com/ruby/ruby/blob/trunk/include/ruby/debug.h#L56 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 https://github.com/ruby/ruby/blob/trunk/include/ruby/debug.h#L66 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); Index: ChangeLog =================================================================== --- ChangeLog (revision 40945) +++ ChangeLog (revision 40946) @@ -1,3 +1,29 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Mon May 27 09:05:17 2013 Koichi Sasada <ko1@a...> + + * 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 <ko1@a...> * ext/-test-/postponed_job/postponed_job.c: fix `init' function name. Index: thread.c =================================================================== --- thread.c (revision 40945) +++ thread.c (revision 40946) @@ -1989,7 +1989,7 @@ rb_threadptr_execute_interrupts(rb_threa https://github.com/ruby/ruby/blob/trunk/thread.c#L1989 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); } Index: gc.c =================================================================== --- gc.c (revision 40945) +++ gc.c (revision 40946) @@ -348,6 +348,7 @@ typedef struct rb_objspace { https://github.com/ruby/ruby/blob/trunk/gc.c#L348 size_t count; size_t total_allocated_object_num; size_t total_freed_object_num; + rb_event_flag_t hook_events; /* this place may be affinity with memory cache */ int gc_stress; struct mark_func_data_struct { @@ -826,6 +827,27 @@ heaps_increment(rb_objspace_t *objspace) https://github.com/ruby/ruby/blob/trunk/gc.c#L827 return FALSE; } +void +rb_objspace_set_event_hook(const rb_event_flag_t event) +{ + rb_objspace_t *objspace = &rb_objspace; + objspace->hook_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, VALU https://github.com/ruby/ruby/blob/trunk/gc.c#L892 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, VALU https://github.com/ruby/ruby/blob/trunk/gc.c#L910 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) https://github.com/ruby/ruby/blob/trunk/gc.c#L1121 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 *objs https://github.com/ruby/ruby/blob/trunk/gc.c#L3811 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); Index: ext/-test-/tracepoint/extconf.rb =================================================================== --- ext/-test-/tracepoint/extconf.rb (revision 0) +++ ext/-test-/tracepoint/extconf.rb (revision 40946) @@ -0,0 +1 @@ +create_makefile("-test-/tracepoint") Index: ext/-test-/tracepoint/tracepoint.c =================================================================== --- ext/-test-/tracepoint/tracepoint.c (revision 0) +++ ext/-test-/tracepoint/tracepoint.c (revision 40946) @@ -0,0 +1,65 @@ https://github.com/ruby/ruby/blob/trunk/ext/-test-/tracepoint/tracepoint.c#L1 +#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; i<objects_count; i++) { + rb_ary_push(result, objects[i]); + } + + return result; +} + +void +Init_tracepoint(void) +{ + VALUE mBug = rb_define_module("Bug"); + rb_define_module_function(mBug, "tracepoint_track_objspace_events", tracepoint_track_objspace_events, 0); +} Index: vm_trace.c =================================================================== --- vm_trace.c (revision 40945) +++ vm_trace.c (revision 40946) @@ -72,6 +72,8 @@ recalc_add_ruby_vm_event_flags(rb_event_ https://github.com/ruby/ruby/blob/trunk/vm_trace.c#L72 } ruby_vm_event_flags |= ruby_event_flag_count[i] ? (1<<i) : 0; } + + rb_objspace_set_event_hook(ruby_vm_event_flags); } static void @@ -86,6 +88,8 @@ recalc_remove_ruby_vm_event_flags(rb_eve https://github.com/ruby/ruby/blob/trunk/vm_trace.c#L88 } ruby_vm_event_flags |= ruby_event_flag_count[i] ? (1<<i) : 0; } + + rb_objspace_set_event_hook(ruby_vm_event_flags); } /* add/remove hooks */ @@ -260,7 +264,7 @@ exec_hooks(rb_thread_t *th, rb_hook_list https://github.com/ruby/ruby/blob/trunk/vm_trace.c#L264 rb_event_hook_t *hook; for (hook = list->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) https://github.com/ruby/ruby/blob/trunk/vm_trace.c#L696 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) { @@ -805,6 +815,21 @@ rb_tracearg_raised_exception(rb_trace_ar https://github.com/ruby/ruby/blob/trunk/vm_trace.c#L815 /* 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; +} + +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) { Index: internal.h =================================================================== --- internal.h (revision 40945) +++ internal.h (revision 40946) @@ -188,6 +188,7 @@ void rb_w32_init_file(void); https://github.com/ruby/ruby/blob/trunk/internal.h#L188 /* 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); Index: test/-ext-/tracepoint/test_tracepoint.rb =================================================================== --- test/-ext-/tracepoint/test_tracepoint.rb (revision 0) +++ test/-ext-/tracepoint/test_tracepoint.rb (revision 40946) @@ -0,0 +1,40 @@ https://github.com/ruby/ruby/blob/trunk/test/-ext-/tracepoint/test_tracepoint.rb#L1 +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 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/