gvltools 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +2 -3
- data/Gemfile.lock +33 -24
- data/README.md +31 -0
- data/ext/gvltools/extconf.rb +2 -1
- data/ext/gvltools/instrumentation.c +134 -30
- data/gvltools.gemspec +0 -2
- data/lib/gvltools/version.rb +1 -1
- data/lib/gvltools.rb +1 -1
- metadata +5 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 93f82e0950c9e2c02690e92984d20654d36dd91b3dffee80d0e85965a0bbc0b3
|
|
4
|
+
data.tar.gz: c6db2e33663b3c3fe290889eb91a71ac336d75a1005e90903b5c54e663446f70
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fcb609efc5575f85fd66fd57e223204a3206905b724940bf59dfc75e7421dab91faf0b9c7ed6e10083be728c17e5b083445ac59196fdfaa9ff629f4b05f53523
|
|
7
|
+
data.tar.gz: f71c390eaa0d6d72f8b06d58a70802f18ce7ec8347b84e42b4e5abcea5ec0c4a7bf598790080abd3b847e99537692caba4923651a2822d8cf1865a3588c5e962
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
-
TargetRubyVersion: 3.0
|
|
3
2
|
NewCops: enable
|
|
4
3
|
SuggestExtensions: false
|
|
5
4
|
|
|
@@ -25,6 +24,9 @@ Style/GlobalVars:
|
|
|
25
24
|
Style/EmptyMethod:
|
|
26
25
|
Enabled: false
|
|
27
26
|
|
|
27
|
+
Naming/PredicateMethod:
|
|
28
|
+
Enabled: false
|
|
29
|
+
|
|
28
30
|
Style/IfUnlessModifier:
|
|
29
31
|
Enabled: false
|
|
30
32
|
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.3.0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-06-15
|
|
4
|
+
|
|
5
|
+
- Fixed a `LocalTimer` deadlock with fiber schedulers by not allocating (and potentially triggering GC) during the `RESUMED` event (#34).
|
|
6
|
+
- Store the `LocalTimer` counter in a thread instance variable instead of fiber-local storage, so it is shared across fibers of the same thread (#37).
|
|
7
|
+
|
|
8
|
+
## [0.4.0] - 2024-01-19
|
|
9
|
+
|
|
10
|
+
- Fixed compatibility with Ruby 3.3.0.
|
|
11
|
+
- Added `GVLTools::LocalTimer.for(thread)` to access another thread counter (Ruby 3.3+ only).
|
|
12
|
+
|
|
3
13
|
## [0.3.0] - 2023-04-11
|
|
4
14
|
|
|
5
15
|
- Automatically reset the `WaitingThreads` counter when enabling it (#7).
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,48 +1,57 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
gvltools (0.
|
|
4
|
+
gvltools (0.5.0)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
|
-
ast (2.4.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
ast (2.4.3)
|
|
10
|
+
json (2.19.8)
|
|
11
|
+
language_server-protocol (3.17.0.5)
|
|
12
|
+
lint_roller (1.1.0)
|
|
13
|
+
minitest (5.27.0)
|
|
14
|
+
parallel (2.1.0)
|
|
15
|
+
parser (3.3.11.1)
|
|
13
16
|
ast (~> 2.4.1)
|
|
17
|
+
racc
|
|
18
|
+
prism (1.9.0)
|
|
19
|
+
racc (1.8.1)
|
|
14
20
|
rainbow (3.1.1)
|
|
15
|
-
rake (13.
|
|
16
|
-
rake-compiler (1.
|
|
21
|
+
rake (13.4.2)
|
|
22
|
+
rake-compiler (1.3.1)
|
|
17
23
|
rake
|
|
18
|
-
regexp_parser (2.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
regexp_parser (2.12.0)
|
|
25
|
+
rubocop (1.87.0)
|
|
26
|
+
json (~> 2.3)
|
|
27
|
+
language_server-protocol (~> 3.17.0.2)
|
|
28
|
+
lint_roller (~> 1.1.0)
|
|
29
|
+
parallel (>= 1.10)
|
|
30
|
+
parser (>= 3.3.0.2)
|
|
23
31
|
rainbow (>= 2.2.2, < 4.0)
|
|
24
|
-
regexp_parser (>=
|
|
25
|
-
|
|
26
|
-
rubocop-ast (>= 1.18.0, < 2.0)
|
|
32
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
33
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
27
34
|
ruby-progressbar (~> 1.7)
|
|
28
|
-
unicode-display_width (>=
|
|
29
|
-
rubocop-ast (1.
|
|
30
|
-
parser (>= 3.
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
36
|
+
rubocop-ast (1.49.1)
|
|
37
|
+
parser (>= 3.3.7.2)
|
|
38
|
+
prism (~> 1.7)
|
|
39
|
+
ruby-progressbar (1.13.0)
|
|
40
|
+
unicode-display_width (3.2.0)
|
|
41
|
+
unicode-emoji (~> 4.1)
|
|
42
|
+
unicode-emoji (4.2.0)
|
|
33
43
|
|
|
34
44
|
PLATFORMS
|
|
35
45
|
aarch64-linux
|
|
36
|
-
arm64-darwin
|
|
37
|
-
arm64-darwin-22
|
|
46
|
+
arm64-darwin
|
|
38
47
|
x86_64-linux
|
|
39
48
|
|
|
40
49
|
DEPENDENCIES
|
|
41
50
|
gvltools!
|
|
42
51
|
minitest (~> 5.0)
|
|
43
|
-
rake
|
|
52
|
+
rake
|
|
44
53
|
rake-compiler
|
|
45
54
|
rubocop (~> 1.21)
|
|
46
55
|
|
|
47
56
|
BUNDLED WITH
|
|
48
|
-
2.3
|
|
57
|
+
2.5.3
|
data/README.md
CHANGED
|
@@ -44,6 +44,8 @@ It is particularly useful to detect wether an application use too many threads,
|
|
|
44
44
|
For instance as a Rack middleware:
|
|
45
45
|
|
|
46
46
|
```ruby
|
|
47
|
+
# lib/gvl_instrumentation_middleware.rb
|
|
48
|
+
|
|
47
49
|
class GVLInstrumentationMiddleware
|
|
48
50
|
def initialize(app)
|
|
49
51
|
@app = app
|
|
@@ -59,6 +61,35 @@ class GVLInstrumentationMiddleware
|
|
|
59
61
|
end
|
|
60
62
|
```
|
|
61
63
|
|
|
64
|
+
```ruby
|
|
65
|
+
# config/initializers/gvl_instrumentation.rb
|
|
66
|
+
|
|
67
|
+
GVLTools::LocalTimer.enable
|
|
68
|
+
|
|
69
|
+
require "gvl_instrumentation_middleware"
|
|
70
|
+
config.middleware.use GVLInstrumentationMiddleware
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Starting from Ruby 3.3, a thread local timer can be accessed from another thread:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
def fibonacci(n)
|
|
77
|
+
if n < 2
|
|
78
|
+
n
|
|
79
|
+
else
|
|
80
|
+
fibonacci(n - 1) + fibonacci(n - 2)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
GVLTools::LocalTimer.enable
|
|
85
|
+
thread = Thread.new do
|
|
86
|
+
fibonacci(20)
|
|
87
|
+
end
|
|
88
|
+
thread.join(1)
|
|
89
|
+
local_timer = GVLTools::LocalTimer.for(thread)
|
|
90
|
+
local_timer.monotonic_time # => 127000
|
|
91
|
+
```
|
|
92
|
+
|
|
62
93
|
### GlobalTimer
|
|
63
94
|
|
|
64
95
|
`GlobalTimer` records the overall time spent waiting on the GVL by all threads combined.
|
data/ext/gvltools/extconf.rb
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
require "mkmf"
|
|
4
4
|
if RUBY_ENGINE == "ruby" &&
|
|
5
5
|
have_header("stdatomic.h") &&
|
|
6
|
-
have_func("rb_internal_thread_add_event_hook", ["ruby/thread.h"])
|
|
6
|
+
have_func("rb_internal_thread_add_event_hook", ["ruby/thread.h"]) # 3.1+
|
|
7
7
|
|
|
8
8
|
$CFLAGS << " -O3 -Wall "
|
|
9
|
+
have_func("rb_internal_thread_specific_get", "ruby/thread.h") # 3.3+
|
|
9
10
|
create_makefile("gvltools/instrumentation")
|
|
10
11
|
else
|
|
11
12
|
File.write("Makefile", dummy_makefile($srcdir).join)
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
typedef unsigned long long counter_t;
|
|
7
7
|
|
|
8
|
+
VALUE rb_cLocalTimer = Qnil;
|
|
9
|
+
|
|
8
10
|
// Metrics
|
|
9
11
|
static rb_internal_thread_event_hook_t *gt_hook = NULL;
|
|
10
12
|
|
|
@@ -15,6 +17,46 @@ static unsigned int enabled_mask = 0;
|
|
|
15
17
|
|
|
16
18
|
#define ENABLED(metric) (enabled_mask & (metric))
|
|
17
19
|
|
|
20
|
+
typedef struct {
|
|
21
|
+
bool was_ready;
|
|
22
|
+
_Atomic counter_t timer_total;
|
|
23
|
+
_Atomic counter_t waiting_threads_ready_generation;
|
|
24
|
+
struct timespec timer_ready_at;
|
|
25
|
+
} thread_local_state;
|
|
26
|
+
|
|
27
|
+
#ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET // 3.3+
|
|
28
|
+
static int thread_storage_key = 0;
|
|
29
|
+
static ID id_local_state;
|
|
30
|
+
|
|
31
|
+
static size_t thread_local_state_memsize(const void *data) {
|
|
32
|
+
return sizeof(thread_local_state);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static const rb_data_type_t thread_local_state_type = {
|
|
36
|
+
.wrap_struct_name = "GVLTools::ThreadLocal",
|
|
37
|
+
.function = {
|
|
38
|
+
.dmark = NULL,
|
|
39
|
+
.dfree = RUBY_DEFAULT_FREE,
|
|
40
|
+
.dsize = thread_local_state_memsize,
|
|
41
|
+
},
|
|
42
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
static inline thread_local_state *GT_LOCAL_STATE(VALUE thread, bool allocate) {
|
|
46
|
+
thread_local_state *state = rb_internal_thread_specific_get(thread, thread_storage_key);
|
|
47
|
+
if (!state && allocate) {
|
|
48
|
+
VALUE wrapper = TypedData_Make_Struct(rb_cLocalTimer, thread_local_state, &thread_local_state_type, state);
|
|
49
|
+
// Anchor to the thread object, not fiber-local storage, so it outlives the allocating fiber.
|
|
50
|
+
rb_ivar_set(thread, id_local_state, wrapper);
|
|
51
|
+
RB_GC_GUARD(wrapper);
|
|
52
|
+
rb_internal_thread_specific_set(thread, thread_storage_key, state);
|
|
53
|
+
}
|
|
54
|
+
return state;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#define GT_EVENT_LOCAL_STATE(event_data, allocate) GT_LOCAL_STATE(event_data->thread, allocate)
|
|
58
|
+
#define GT_CURRENT_THREAD_LOCAL_STATE() GT_LOCAL_STATE(rb_thread_current(), true)
|
|
59
|
+
#else
|
|
18
60
|
#if __STDC_VERSION__ >= 201112
|
|
19
61
|
#define THREAD_LOCAL_SPECIFIER _Thread_local
|
|
20
62
|
#elif defined(__GNUC__) && !defined(RB_THREAD_LOCAL_SPECIFIER_IS_UNSUPPORTED)
|
|
@@ -22,11 +64,17 @@ static unsigned int enabled_mask = 0;
|
|
|
22
64
|
#define THREAD_LOCAL_SPECIFIER __thread
|
|
23
65
|
#endif
|
|
24
66
|
|
|
67
|
+
static THREAD_LOCAL_SPECIFIER thread_local_state __thread_local_state = {0};
|
|
68
|
+
#undef THREAD_LOCAL_SPECIFIER
|
|
69
|
+
|
|
70
|
+
#define GT_LOCAL_STATE(thread) (&__thread_local_state)
|
|
71
|
+
#define GT_EVENT_LOCAL_STATE(event_data, allocate) (&__thread_local_state)
|
|
72
|
+
#define GT_CURRENT_THREAD_LOCAL_STATE() (&__thread_local_state)
|
|
73
|
+
#endif
|
|
74
|
+
|
|
25
75
|
// Common
|
|
26
76
|
#define SECONDS_TO_NANOSECONDS (1000 * 1000 * 1000)
|
|
27
77
|
|
|
28
|
-
static THREAD_LOCAL_SPECIFIER bool was_ready = 0;
|
|
29
|
-
|
|
30
78
|
static inline void gt_gettime(struct timespec *time) {
|
|
31
79
|
if (clock_gettime(CLOCK_MONOTONIC, time) == -1) {
|
|
32
80
|
rb_sys_fail("clock_gettime");
|
|
@@ -50,9 +98,23 @@ static VALUE gt_enable_metric(VALUE module, VALUE metric) {
|
|
|
50
98
|
if (!gt_hook) {
|
|
51
99
|
gt_hook = rb_internal_thread_add_event_hook(
|
|
52
100
|
gt_thread_callback,
|
|
53
|
-
RUBY_INTERNAL_THREAD_EVENT_EXITED | RUBY_INTERNAL_THREAD_EVENT_READY | RUBY_INTERNAL_THREAD_EVENT_RESUMED,
|
|
101
|
+
RUBY_INTERNAL_THREAD_EVENT_STARTED | RUBY_INTERNAL_THREAD_EVENT_EXITED | RUBY_INTERNAL_THREAD_EVENT_READY | RUBY_INTERNAL_THREAD_EVENT_RESUMED,
|
|
54
102
|
NULL
|
|
55
103
|
);
|
|
104
|
+
|
|
105
|
+
#ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET
|
|
106
|
+
// Eagerly allocate the per-thread state for every thread that already
|
|
107
|
+
// exists at enable time. Threads created later get their state allocated from the
|
|
108
|
+
// STARTED event (also GVL-held). This guarantees the RESUMED event always finds an
|
|
109
|
+
// existing state and never has to allocate. Allocating from RESUMED is
|
|
110
|
+
// illegal because it doesn't necessarily run with the GVL.
|
|
111
|
+
VALUE threads = rb_funcall(rb_const_get(rb_cObject, rb_intern("Thread")), rb_intern("list"), 0);
|
|
112
|
+
long count = RARRAY_LEN(threads);
|
|
113
|
+
for (long i = 0; i < count; i++) {
|
|
114
|
+
GT_LOCAL_STATE(RARRAY_AREF(threads, i), true);
|
|
115
|
+
}
|
|
116
|
+
RB_GC_GUARD(threads);
|
|
117
|
+
#endif
|
|
56
118
|
}
|
|
57
119
|
return Qtrue;
|
|
58
120
|
}
|
|
@@ -70,8 +132,6 @@ static VALUE gt_disable_metric(VALUE module, VALUE metric) {
|
|
|
70
132
|
|
|
71
133
|
// GVLTools::LocalTimer and GVLTools::GlobalTimer
|
|
72
134
|
static _Atomic counter_t global_timer_total = 0;
|
|
73
|
-
static THREAD_LOCAL_SPECIFIER counter_t local_timer_total = 0;
|
|
74
|
-
static THREAD_LOCAL_SPECIFIER struct timespec timer_ready_at = {0};
|
|
75
135
|
|
|
76
136
|
static VALUE global_timer_monotonic_time(VALUE module) {
|
|
77
137
|
return ULL2NUM(global_timer_total);
|
|
@@ -82,19 +142,38 @@ static VALUE global_timer_reset(VALUE module) {
|
|
|
82
142
|
return Qtrue;
|
|
83
143
|
}
|
|
84
144
|
|
|
85
|
-
static VALUE
|
|
86
|
-
return ULL2NUM(
|
|
145
|
+
static VALUE local_timer_m_monotonic_time(VALUE module) {
|
|
146
|
+
return ULL2NUM(GT_CURRENT_THREAD_LOCAL_STATE()->timer_total);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static VALUE local_timer_m_reset(VALUE module) {
|
|
150
|
+
GT_CURRENT_THREAD_LOCAL_STATE()->timer_total = 0;
|
|
151
|
+
return Qtrue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET
|
|
155
|
+
static VALUE local_timer_for(VALUE module, VALUE thread) {
|
|
156
|
+
GT_LOCAL_STATE(thread, true);
|
|
157
|
+
return rb_ivar_get(thread, id_local_state);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static VALUE local_timer_monotonic_time(VALUE timer) {
|
|
161
|
+
thread_local_state *state;
|
|
162
|
+
TypedData_Get_Struct(timer, thread_local_state, &thread_local_state_type, state);
|
|
163
|
+
return ULL2NUM(state->timer_total);
|
|
87
164
|
}
|
|
88
165
|
|
|
89
|
-
static VALUE local_timer_reset(VALUE
|
|
90
|
-
|
|
166
|
+
static VALUE local_timer_reset(VALUE timer) {
|
|
167
|
+
thread_local_state *state;
|
|
168
|
+
TypedData_Get_Struct(timer, thread_local_state, &thread_local_state_type, state);
|
|
169
|
+
state->timer_total = 0;
|
|
91
170
|
return Qtrue;
|
|
92
171
|
}
|
|
172
|
+
#endif
|
|
93
173
|
|
|
94
174
|
// Thread counts
|
|
95
175
|
static _Atomic counter_t waiting_threads_total = 0;
|
|
96
176
|
static _Atomic counter_t waiting_threads_current_generation = 1;
|
|
97
|
-
static THREAD_LOCAL_SPECIFIER counter_t waiting_threads_ready_generation = 0;
|
|
98
177
|
|
|
99
178
|
static VALUE waiting_threads_count(VALUE module) {
|
|
100
179
|
return ULL2NUM(waiting_threads_total);
|
|
@@ -107,38 +186,51 @@ static VALUE waiting_threads_reset(VALUE module) {
|
|
|
107
186
|
}
|
|
108
187
|
|
|
109
188
|
// General callback
|
|
110
|
-
static void gt_reset_thread_local_state(void) {
|
|
111
|
-
// MRI can re-use native threads, so we need to reset thread local state,
|
|
112
|
-
// otherwise it will leak from one Ruby thread from another.
|
|
113
|
-
was_ready = false;
|
|
114
|
-
local_timer_total = 0;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
189
|
static void gt_thread_callback(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *user_data) {
|
|
118
190
|
switch(event) {
|
|
119
|
-
case RUBY_INTERNAL_THREAD_EVENT_STARTED:
|
|
191
|
+
case RUBY_INTERNAL_THREAD_EVENT_STARTED: {
|
|
192
|
+
// The STARTED event is triggered from the parent thread with the GVL held
|
|
193
|
+
// so we can allocate the struct.
|
|
194
|
+
GT_EVENT_LOCAL_STATE(event_data, true);
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
120
197
|
case RUBY_INTERNAL_THREAD_EVENT_EXITED: {
|
|
121
|
-
|
|
198
|
+
#ifndef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET
|
|
199
|
+
thread_local_state *state = GT_EVENT_LOCAL_STATE(event_data, false);
|
|
200
|
+
if (state) {
|
|
201
|
+
// MRI can re-use native threads, so we need to reset thread local state,
|
|
202
|
+
// otherwise it will leak from one Ruby thread from another.
|
|
203
|
+
state->was_ready = false;
|
|
204
|
+
state->timer_total = 0;
|
|
205
|
+
}
|
|
206
|
+
#endif
|
|
122
207
|
}
|
|
123
208
|
break;
|
|
124
209
|
case RUBY_INTERNAL_THREAD_EVENT_READY: {
|
|
125
|
-
|
|
210
|
+
thread_local_state *state = GT_EVENT_LOCAL_STATE(event_data, false);
|
|
211
|
+
if (!state) {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
state->was_ready = true;
|
|
126
215
|
|
|
127
216
|
if (ENABLED(WAITING_THREADS)) {
|
|
128
|
-
waiting_threads_ready_generation = waiting_threads_current_generation;
|
|
217
|
+
state->waiting_threads_ready_generation = waiting_threads_current_generation;
|
|
129
218
|
waiting_threads_total++;
|
|
130
219
|
}
|
|
131
220
|
|
|
132
221
|
if (ENABLED(TIMER_GLOBAL | TIMER_LOCAL)) {
|
|
133
|
-
gt_gettime(&timer_ready_at);
|
|
222
|
+
gt_gettime(&state->timer_ready_at);
|
|
134
223
|
}
|
|
135
224
|
}
|
|
136
225
|
break;
|
|
137
|
-
case RUBY_INTERNAL_THREAD_EVENT_RESUMED: {
|
|
138
|
-
|
|
226
|
+
case RUBY_INTERNAL_THREAD_EVENT_RESUMED: { // Must not allocate
|
|
227
|
+
thread_local_state *state = GT_EVENT_LOCAL_STATE(event_data, false);
|
|
228
|
+
if (!state || !state->was_ready) {
|
|
229
|
+
break; // In case we registered the hook while some threads were already waiting on the GVL
|
|
230
|
+
}
|
|
139
231
|
|
|
140
232
|
if (ENABLED(WAITING_THREADS)) {
|
|
141
|
-
if (waiting_threads_ready_generation == waiting_threads_current_generation) {
|
|
233
|
+
if (state->waiting_threads_ready_generation == waiting_threads_current_generation) {
|
|
142
234
|
waiting_threads_total--;
|
|
143
235
|
}
|
|
144
236
|
}
|
|
@@ -146,10 +238,10 @@ static void gt_thread_callback(rb_event_flag_t event, const rb_internal_thread_e
|
|
|
146
238
|
if (ENABLED(TIMER_GLOBAL | TIMER_LOCAL)) {
|
|
147
239
|
struct timespec current_time;
|
|
148
240
|
gt_gettime(¤t_time);
|
|
149
|
-
counter_t diff = gt_time_diff_ns(timer_ready_at, current_time);
|
|
241
|
+
counter_t diff = gt_time_diff_ns(state->timer_ready_at, current_time);
|
|
150
242
|
|
|
151
243
|
if (ENABLED(TIMER_LOCAL)) {
|
|
152
|
-
|
|
244
|
+
state->timer_total += diff;
|
|
153
245
|
}
|
|
154
246
|
|
|
155
247
|
if (ENABLED(TIMER_GLOBAL)) {
|
|
@@ -162,6 +254,11 @@ static void gt_thread_callback(rb_event_flag_t event, const rb_internal_thread_e
|
|
|
162
254
|
}
|
|
163
255
|
|
|
164
256
|
void Init_instrumentation(void) {
|
|
257
|
+
#ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET // 3.3+
|
|
258
|
+
thread_storage_key = rb_internal_thread_specific_key_create();
|
|
259
|
+
id_local_state = rb_intern("__gvltools_local_state");
|
|
260
|
+
#endif
|
|
261
|
+
|
|
165
262
|
VALUE rb_mGVLTools = rb_const_get(rb_cObject, rb_intern("GVLTools"));
|
|
166
263
|
|
|
167
264
|
VALUE rb_mNative = rb_const_get(rb_mGVLTools, rb_intern("Native"));
|
|
@@ -173,9 +270,16 @@ void Init_instrumentation(void) {
|
|
|
173
270
|
rb_define_singleton_method(rb_mGlobalTimer, "reset", global_timer_reset, 0);
|
|
174
271
|
rb_define_singleton_method(rb_mGlobalTimer, "monotonic_time", global_timer_monotonic_time, 0);
|
|
175
272
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
273
|
+
rb_global_variable(&rb_cLocalTimer);
|
|
274
|
+
rb_cLocalTimer = rb_const_get(rb_mGVLTools, rb_intern("LocalTimer"));
|
|
275
|
+
rb_undef_alloc_func(rb_cLocalTimer);
|
|
276
|
+
rb_define_singleton_method(rb_cLocalTimer, "reset", local_timer_m_reset, 0);
|
|
277
|
+
rb_define_singleton_method(rb_cLocalTimer, "monotonic_time", local_timer_m_monotonic_time, 0);
|
|
278
|
+
#ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET
|
|
279
|
+
rb_define_singleton_method(rb_cLocalTimer, "for", local_timer_for, 1);
|
|
280
|
+
rb_define_method(rb_cLocalTimer, "reset", local_timer_reset, 0);
|
|
281
|
+
rb_define_method(rb_cLocalTimer, "monotonic_time", local_timer_monotonic_time, 0);
|
|
282
|
+
#endif
|
|
179
283
|
|
|
180
284
|
VALUE rb_mWaitingThreads = rb_const_get(rb_mGVLTools, rb_intern("WaitingThreads"));
|
|
181
285
|
rb_define_singleton_method(rb_mWaitingThreads, "_reset", waiting_threads_reset, 0);
|
data/gvltools.gemspec
CHANGED
data/lib/gvltools/version.rb
CHANGED
data/lib/gvltools.rb
CHANGED
metadata
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gvltools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jean Boussier
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: rake-compiler
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0'
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0'
|
|
27
|
-
description:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
28
12
|
email:
|
|
29
13
|
- jean.boussier@gmail.com
|
|
30
14
|
executables: []
|
|
@@ -33,6 +17,7 @@ extensions:
|
|
|
33
17
|
extra_rdoc_files: []
|
|
34
18
|
files:
|
|
35
19
|
- ".rubocop.yml"
|
|
20
|
+
- ".ruby-version"
|
|
36
21
|
- CHANGELOG.md
|
|
37
22
|
- Gemfile
|
|
38
23
|
- Gemfile.lock
|
|
@@ -52,7 +37,6 @@ metadata:
|
|
|
52
37
|
homepage_uri: https://github.com/Shopify/gvltools
|
|
53
38
|
source_code_uri: https://github.com/Shopify/gvltools
|
|
54
39
|
changelog_uri: https://github.com/Shopify/gvltools/blob/master/CHANGELOG.md
|
|
55
|
-
post_install_message:
|
|
56
40
|
rdoc_options: []
|
|
57
41
|
require_paths:
|
|
58
42
|
- lib
|
|
@@ -67,8 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
67
51
|
- !ruby/object:Gem::Version
|
|
68
52
|
version: '0'
|
|
69
53
|
requirements: []
|
|
70
|
-
rubygems_version:
|
|
71
|
-
signing_key:
|
|
54
|
+
rubygems_version: 4.0.10
|
|
72
55
|
specification_version: 4
|
|
73
56
|
summary: Set of GVL instrumentation tools
|
|
74
57
|
test_files: []
|