summaryrefslogtreecommitdiff
path: root/ext/-test-/thread/instrumentation/instrumentation.c
blob: d2a2c2740b5a1b2d99b20e7104f06b83c1a767bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include "ruby/ruby.h"
#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
#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 void
ex_callback(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *user_data)
{
    switch (event) {
      case RUBY_INTERNAL_THREAD_EVENT_STARTED:
        RUBY_ATOMIC_INC(started_count);
        break;
      case RUBY_INTERNAL_THREAD_EVENT_READY:
        RUBY_ATOMIC_INC(ready_count);
        local_ready_count++;
        break;
      case RUBY_INTERNAL_THREAD_EVENT_RESUMED:
        RUBY_ATOMIC_INC(resumed_count);
        local_resumed_count++;
        break;
      case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED:
        RUBY_ATOMIC_INC(suspended_count);
        local_suspended_count++;
        break;
      case RUBY_INTERNAL_THREAD_EVENT_EXITED:
        RUBY_ATOMIC_INC(exited_count);
        break;
    }
}

static rb_internal_thread_event_hook_t * single_hook = NULL;

static VALUE
thread_counters(VALUE thread)
{
    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;
}

static VALUE
thread_local_counters(VALUE thread)
{
    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;
}

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;
}

static VALUE
thread_register_callback(VALUE thread)
{
    single_hook = rb_internal_thread_add_event_hook(
        ex_callback,
        RUBY_INTERNAL_THREAD_EVENT_STARTED |
        RUBY_INTERNAL_THREAD_EVENT_READY |
        RUBY_INTERNAL_THREAD_EVENT_RESUMED |
        RUBY_INTERNAL_THREAD_EVENT_SUSPENDED |
        RUBY_INTERNAL_THREAD_EVENT_EXITED,
        NULL
    );

    return Qnil;
}

static VALUE
thread_unregister_callback(VALUE thread)
{
    if (single_hook) {
        rb_internal_thread_remove_event_hook(single_hook);
        single_hook = NULL;
    }

    return Qnil;
}

static VALUE
thread_register_and_unregister_callback(VALUE thread)
{
    rb_internal_thread_event_hook_t * hooks[5];
    for (int i = 0; i < 5; i++) {
        hooks[i] = rb_internal_thread_add_event_hook(ex_callback, RUBY_INTERNAL_THREAD_EVENT_READY, NULL);
    }

    if (!rb_internal_thread_remove_event_hook(hooks[4])) return Qfalse;
    if (!rb_internal_thread_remove_event_hook(hooks[0])) return Qfalse;
    if (!rb_internal_thread_remove_event_hook(hooks[3])) return Qfalse;
    if (!rb_internal_thread_remove_event_hook(hooks[2])) return Qfalse;
    if (!rb_internal_thread_remove_event_hook(hooks[1])) return Qfalse;
    return Qtrue;
}

void
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_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);
}