diff options
Diffstat (limited to 'ext/-test-/thread/instrumentation/instrumentation.c')
-rw-r--r-- | ext/-test-/thread/instrumentation/instrumentation.c | 215 |
1 files changed, 147 insertions, 68 deletions
diff --git a/ext/-test-/thread/instrumentation/instrumentation.c b/ext/-test-/thread/instrumentation/instrumentation.c index d2a2c2740b..d81bc0f2a7 100644 --- a/ext/-test-/thread/instrumentation/instrumentation.c +++ b/ext/-test-/thread/instrumentation/instrumentation.c @@ -2,90 +2,137 @@ #include "ruby/atomic.h" #include "ruby/thread.h" -static rb_atomic_t started_count = 0; -static rb_atomic_t ready_count = 0; -static rb_atomic_t resumed_count = 0; -static rb_atomic_t suspended_count = 0; -static rb_atomic_t exited_count = 0; - -#if __STDC_VERSION__ >= 201112 - #define RB_THREAD_LOCAL_SPECIFIER _Thread_local -#elif defined(__GNUC__) && !defined(RB_THREAD_LOCAL_SPECIFIER_IS_UNSUPPORTED) - /* note that ICC (linux) and Clang are covered by __GNUC__ */ - #define RB_THREAD_LOCAL_SPECIFIER __thread -#else - #define RB_THREAD_LOCAL_SPECIFIER +#ifndef RB_THREAD_LOCAL_SPECIFIER +# define RB_THREAD_LOCAL_SPECIFIER #endif -static RB_THREAD_LOCAL_SPECIFIER unsigned int local_ready_count = 0; -static RB_THREAD_LOCAL_SPECIFIER unsigned int local_resumed_count = 0; -static RB_THREAD_LOCAL_SPECIFIER unsigned int local_suspended_count = 0; +static VALUE last_thread = Qnil; +static VALUE timeline_value = Qnil; + +struct thread_event { + VALUE thread; + rb_event_flag_t event; +}; + +#define MAX_EVENTS 1024 +static struct thread_event event_timeline[MAX_EVENTS]; +static rb_atomic_t timeline_cursor; static void -ex_callback(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *user_data) +event_timeline_gc_mark(void *ptr) { + rb_atomic_t cursor; + for (cursor = 0; cursor < timeline_cursor; cursor++) { + rb_gc_mark(event_timeline[cursor].thread); + } +} + +static const rb_data_type_t event_timeline_type = { + "TestThreadInstrumentation/event_timeline", + {event_timeline_gc_mark, NULL, NULL,}, + 0, 0, + RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static void +reset_timeline(void) +{ + timeline_cursor = 0; + memset(event_timeline, 0, sizeof(struct thread_event) * MAX_EVENTS); +} + +static rb_event_flag_t +find_last_event(VALUE thread) +{ + rb_atomic_t cursor = timeline_cursor; + if (cursor) { + do { + if (event_timeline[cursor].thread == thread){ + return event_timeline[cursor].event; + } + cursor--; + } while (cursor > 0); + } + return 0; +} + +static const char * +event_name(rb_event_flag_t event) { switch (event) { case RUBY_INTERNAL_THREAD_EVENT_STARTED: - RUBY_ATOMIC_INC(started_count); - break; + return "started"; case RUBY_INTERNAL_THREAD_EVENT_READY: - RUBY_ATOMIC_INC(ready_count); - local_ready_count++; - break; + return "ready"; case RUBY_INTERNAL_THREAD_EVENT_RESUMED: - RUBY_ATOMIC_INC(resumed_count); - local_resumed_count++; - break; + return "resumed"; case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED: - RUBY_ATOMIC_INC(suspended_count); - local_suspended_count++; - break; + return "suspended"; case RUBY_INTERNAL_THREAD_EVENT_EXITED: - RUBY_ATOMIC_INC(exited_count); - break; + return "exited"; } + return "no-event"; } -static rb_internal_thread_event_hook_t * single_hook = NULL; - -static VALUE -thread_counters(VALUE thread) +static void +unexpected(bool strict, const char *format, VALUE thread, rb_event_flag_t last_event) { - VALUE array = rb_ary_new2(5); - rb_ary_push(array, UINT2NUM(started_count)); - rb_ary_push(array, UINT2NUM(ready_count)); - rb_ary_push(array, UINT2NUM(resumed_count)); - rb_ary_push(array, UINT2NUM(suspended_count)); - rb_ary_push(array, UINT2NUM(exited_count)); - return array; + const char *last_event_name = event_name(last_event); + if (strict) { + rb_bug(format, thread, last_event_name); + } + else { + fprintf(stderr, format, thread, last_event_name); + fprintf(stderr, "\n"); + } } -static VALUE -thread_local_counters(VALUE thread) +static void +ex_callback(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *user_data) { - VALUE array = rb_ary_new2(3); - rb_ary_push(array, UINT2NUM(local_ready_count)); - rb_ary_push(array, UINT2NUM(local_resumed_count)); - rb_ary_push(array, UINT2NUM(local_suspended_count)); - return array; -} + rb_event_flag_t last_event = find_last_event(event_data->thread); + bool strict = (bool)user_data; -static VALUE -thread_reset_counters(VALUE thread) -{ - RUBY_ATOMIC_SET(started_count, 0); - RUBY_ATOMIC_SET(ready_count, 0); - RUBY_ATOMIC_SET(resumed_count, 0); - RUBY_ATOMIC_SET(suspended_count, 0); - RUBY_ATOMIC_SET(exited_count, 0); - local_ready_count = 0; - local_resumed_count = 0; - local_suspended_count = 0; - return Qtrue; + if (last_event != 0) { + switch (event) { + case RUBY_INTERNAL_THREAD_EVENT_STARTED: + unexpected(strict, "[thread=%"PRIxVALUE"] `started` event can't be preceded by `%s`", event_data->thread, last_event); + break; + case RUBY_INTERNAL_THREAD_EVENT_READY: + if (last_event != RUBY_INTERNAL_THREAD_EVENT_STARTED && last_event != RUBY_INTERNAL_THREAD_EVENT_SUSPENDED) { + unexpected(strict, "[thread=%"PRIxVALUE"] `ready` must be preceded by `started` or `suspended`, got: `%s`", event_data->thread, last_event); + } + break; + case RUBY_INTERNAL_THREAD_EVENT_RESUMED: + if (last_event != RUBY_INTERNAL_THREAD_EVENT_READY) { + unexpected(strict, "[thread=%"PRIxVALUE"] `resumed` must be preceded by `ready`, got: `%s`", event_data->thread, last_event); + } + break; + case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED: + if (last_event != RUBY_INTERNAL_THREAD_EVENT_RESUMED) { + unexpected(strict, "[thread=%"PRIxVALUE"] `suspended` must be preceded by `resumed`, got: `%s`", event_data->thread, last_event); + } + break; + case RUBY_INTERNAL_THREAD_EVENT_EXITED: + if (last_event != RUBY_INTERNAL_THREAD_EVENT_RESUMED && last_event != RUBY_INTERNAL_THREAD_EVENT_SUSPENDED) { + unexpected(strict, "[thread=%"PRIxVALUE"] `exited` must be preceded by `resumed` or `suspended`, got: `%s`", event_data->thread, last_event); + } + break; + } + } + + rb_atomic_t cursor = RUBY_ATOMIC_FETCH_ADD(timeline_cursor, 1); + if (cursor >= MAX_EVENTS) { + rb_bug("TestThreadInstrumentation: ran out of event_timeline space"); + } + + event_timeline[cursor].thread = event_data->thread; + event_timeline[cursor].event = event; } +static rb_internal_thread_event_hook_t * single_hook = NULL; + static VALUE -thread_register_callback(VALUE thread) +thread_register_callback(VALUE thread, VALUE strict) { single_hook = rb_internal_thread_add_event_hook( ex_callback, @@ -94,13 +141,33 @@ thread_register_callback(VALUE thread) RUBY_INTERNAL_THREAD_EVENT_RESUMED | RUBY_INTERNAL_THREAD_EVENT_SUSPENDED | RUBY_INTERNAL_THREAD_EVENT_EXITED, - NULL + (void *)RTEST(strict) ); return Qnil; } static VALUE +event_symbol(rb_event_flag_t event) +{ + switch (event) { + case RUBY_INTERNAL_THREAD_EVENT_STARTED: + return rb_id2sym(rb_intern("started")); + case RUBY_INTERNAL_THREAD_EVENT_READY: + return rb_id2sym(rb_intern("ready")); + case RUBY_INTERNAL_THREAD_EVENT_RESUMED: + return rb_id2sym(rb_intern("resumed")); + case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED: + return rb_id2sym(rb_intern("suspended")); + case RUBY_INTERNAL_THREAD_EVENT_EXITED: + return rb_id2sym(rb_intern("exited")); + default: + rb_bug("TestThreadInstrumentation: Unexpected event"); + break; + } +} + +static VALUE thread_unregister_callback(VALUE thread) { if (single_hook) { @@ -108,7 +175,18 @@ thread_unregister_callback(VALUE thread) single_hook = NULL; } - return Qnil; + VALUE events = rb_ary_new_capa(timeline_cursor); + rb_atomic_t cursor; + for (cursor = 0; cursor < timeline_cursor; cursor++) { + VALUE pair = rb_ary_new_capa(2); + rb_ary_push(pair, event_timeline[cursor].thread); + rb_ary_push(pair, event_symbol(event_timeline[cursor].event)); + rb_ary_push(events, pair); + } + + reset_timeline(); + + return events; } static VALUE @@ -132,10 +210,11 @@ Init_instrumentation(void) { VALUE mBug = rb_define_module("Bug"); VALUE klass = rb_define_module_under(mBug, "ThreadInstrumentation"); - rb_define_singleton_method(klass, "counters", thread_counters, 0); - rb_define_singleton_method(klass, "local_counters", thread_local_counters, 0); - rb_define_singleton_method(klass, "reset_counters", thread_reset_counters, 0); - rb_define_singleton_method(klass, "register_callback", thread_register_callback, 0); + rb_global_variable(&timeline_value); + timeline_value = TypedData_Wrap_Struct(0, &event_timeline_type, 0); + + rb_global_variable(&last_thread); + rb_define_singleton_method(klass, "register_callback", thread_register_callback, 1); rb_define_singleton_method(klass, "unregister_callback", thread_unregister_callback, 0); rb_define_singleton_method(klass, "register_and_unregister_callbacks", thread_register_and_unregister_callback, 0); } |