gvltools 0.3.0 → 0.4.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/CHANGELOG.md +3 -0
- data/Gemfile +2 -3
- data/Gemfile.lock +22 -17
- data/README.md +20 -0
- data/ext/gvltools/extconf.rb +2 -1
- data/ext/gvltools/instrumentation.c +116 -29
- data/gvltools.gemspec +0 -2
- data/lib/gvltools/version.rb +1 -1
- data/lib/gvltools.rb +1 -1
- metadata +4 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b6af92049391130d2248c5d14ec3c93e859f4ab92d6af9842e2a88c289b3e50
|
4
|
+
data.tar.gz: 4681a32780409d180b6727a791cdc122a9a03d21eba7f28f2070c8de6e52ddb1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 005a90c02765c716c97f9165f777dd6b26b3853037e6c64f13e3255278db5a7c5d378855850758a7e391d9957389d18db7edb24a3658a46a5ec6105668238fa7
|
7
|
+
data.tar.gz: e16793a8f452f958cd0dde468c5aff337a78719485c8657bc9f5e1e91bd393b0bc1a523e56b5a1e06246c963e8661ed34707ef49e05cb15d6e94e57865c9932c
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,46 +1,51 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
gvltools (0.
|
4
|
+
gvltools (0.4.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
ast (2.4.2)
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
json (2.7.1)
|
11
|
+
language_server-protocol (3.17.0.3)
|
12
|
+
minitest (5.21.2)
|
13
|
+
parallel (1.24.0)
|
14
|
+
parser (3.3.0.2)
|
13
15
|
ast (~> 2.4.1)
|
16
|
+
racc
|
17
|
+
racc (1.7.3)
|
14
18
|
rainbow (3.1.1)
|
15
19
|
rake (13.0.6)
|
16
20
|
rake-compiler (1.2.0)
|
17
21
|
rake
|
18
|
-
regexp_parser (2.
|
19
|
-
rexml (3.2.
|
20
|
-
rubocop (1.
|
22
|
+
regexp_parser (2.9.0)
|
23
|
+
rexml (3.2.6)
|
24
|
+
rubocop (1.59.0)
|
25
|
+
json (~> 2.3)
|
26
|
+
language_server-protocol (>= 3.17.0)
|
21
27
|
parallel (~> 1.10)
|
22
|
-
parser (>= 3.
|
28
|
+
parser (>= 3.2.2.4)
|
23
29
|
rainbow (>= 2.2.2, < 4.0)
|
24
30
|
regexp_parser (>= 1.8, < 3.0)
|
25
31
|
rexml (>= 3.2.5, < 4.0)
|
26
|
-
rubocop-ast (>= 1.
|
32
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
27
33
|
ruby-progressbar (~> 1.7)
|
28
|
-
unicode-display_width (>=
|
29
|
-
rubocop-ast (1.
|
30
|
-
parser (>= 3.
|
31
|
-
ruby-progressbar (1.
|
32
|
-
unicode-display_width (2.
|
34
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
35
|
+
rubocop-ast (1.30.0)
|
36
|
+
parser (>= 3.2.1.0)
|
37
|
+
ruby-progressbar (1.13.0)
|
38
|
+
unicode-display_width (2.5.0)
|
33
39
|
|
34
40
|
PLATFORMS
|
35
41
|
aarch64-linux
|
36
|
-
arm64-darwin
|
37
|
-
arm64-darwin-22
|
42
|
+
arm64-darwin
|
38
43
|
x86_64-linux
|
39
44
|
|
40
45
|
DEPENDENCIES
|
41
46
|
gvltools!
|
42
47
|
minitest (~> 5.0)
|
43
|
-
rake
|
48
|
+
rake
|
44
49
|
rake-compiler
|
45
50
|
rubocop (~> 1.21)
|
46
51
|
|
data/README.md
CHANGED
@@ -59,6 +59,26 @@ class GVLInstrumentationMiddleware
|
|
59
59
|
end
|
60
60
|
```
|
61
61
|
|
62
|
+
Starting from Ruby 3.3, a thread local timer can be accessed from another thread:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
def fibonacci(n)
|
66
|
+
if n < 2
|
67
|
+
n
|
68
|
+
else
|
69
|
+
fibonacci(n - 1) + fibonacci(n - 2)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
GVLTools::LocalTimer.enable
|
74
|
+
thread = Thread.new do
|
75
|
+
fibonacci(20)
|
76
|
+
end
|
77
|
+
thread.join(1)
|
78
|
+
local_timer = GVLTools::LocalTimer.for(thread)
|
79
|
+
local_timer.monotonic_time # => 127000
|
80
|
+
```
|
81
|
+
|
62
82
|
### GlobalTimer
|
63
83
|
|
64
84
|
`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,44 @@ 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
|
+
|
30
|
+
static size_t thread_local_state_memsize(const void *data) {
|
31
|
+
return sizeof(thread_local_state);
|
32
|
+
}
|
33
|
+
|
34
|
+
static const rb_data_type_t thread_local_state_type = {
|
35
|
+
.wrap_struct_name = "GVLTools::ThreadLocal",
|
36
|
+
.function = {
|
37
|
+
.dmark = NULL,
|
38
|
+
.dfree = RUBY_DEFAULT_FREE,
|
39
|
+
.dsize = thread_local_state_memsize,
|
40
|
+
},
|
41
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
|
42
|
+
};
|
43
|
+
|
44
|
+
static inline thread_local_state *GT_LOCAL_STATE(VALUE thread, bool allocate) {
|
45
|
+
thread_local_state *state = rb_internal_thread_specific_get(thread, thread_storage_key);
|
46
|
+
if (!state && allocate) {
|
47
|
+
VALUE wrapper = TypedData_Make_Struct(rb_cLocalTimer, thread_local_state, &thread_local_state_type, state);
|
48
|
+
rb_thread_local_aset(thread, rb_intern("__gvltools_local_state"), wrapper);
|
49
|
+
RB_GC_GUARD(wrapper);
|
50
|
+
rb_internal_thread_specific_set(thread, thread_storage_key, state);
|
51
|
+
}
|
52
|
+
return state;
|
53
|
+
}
|
54
|
+
|
55
|
+
#define GT_EVENT_LOCAL_STATE(event_data, allocate) GT_LOCAL_STATE(event_data->thread, allocate)
|
56
|
+
#define GT_CURRENT_THREAD_LOCAL_STATE() GT_LOCAL_STATE(rb_thread_current(), true)
|
57
|
+
#else
|
18
58
|
#if __STDC_VERSION__ >= 201112
|
19
59
|
#define THREAD_LOCAL_SPECIFIER _Thread_local
|
20
60
|
#elif defined(__GNUC__) && !defined(RB_THREAD_LOCAL_SPECIFIER_IS_UNSUPPORTED)
|
@@ -22,11 +62,17 @@ static unsigned int enabled_mask = 0;
|
|
22
62
|
#define THREAD_LOCAL_SPECIFIER __thread
|
23
63
|
#endif
|
24
64
|
|
65
|
+
static THREAD_LOCAL_SPECIFIER thread_local_state __thread_local_state = {0};
|
66
|
+
#undef THREAD_LOCAL_SPECIFIER
|
67
|
+
|
68
|
+
#define GT_LOCAL_STATE(thread) (&__thread_local_state)
|
69
|
+
#define GT_EVENT_LOCAL_STATE(event_data, allocate) (&__thread_local_state)
|
70
|
+
#define GT_CURRENT_THREAD_LOCAL_STATE() (&__thread_local_state)
|
71
|
+
#endif
|
72
|
+
|
25
73
|
// Common
|
26
74
|
#define SECONDS_TO_NANOSECONDS (1000 * 1000 * 1000)
|
27
75
|
|
28
|
-
static THREAD_LOCAL_SPECIFIER bool was_ready = 0;
|
29
|
-
|
30
76
|
static inline void gt_gettime(struct timespec *time) {
|
31
77
|
if (clock_gettime(CLOCK_MONOTONIC, time) == -1) {
|
32
78
|
rb_sys_fail("clock_gettime");
|
@@ -50,7 +96,7 @@ static VALUE gt_enable_metric(VALUE module, VALUE metric) {
|
|
50
96
|
if (!gt_hook) {
|
51
97
|
gt_hook = rb_internal_thread_add_event_hook(
|
52
98
|
gt_thread_callback,
|
53
|
-
RUBY_INTERNAL_THREAD_EVENT_EXITED | RUBY_INTERNAL_THREAD_EVENT_READY | RUBY_INTERNAL_THREAD_EVENT_RESUMED,
|
99
|
+
RUBY_INTERNAL_THREAD_EVENT_STARTED | RUBY_INTERNAL_THREAD_EVENT_EXITED | RUBY_INTERNAL_THREAD_EVENT_READY | RUBY_INTERNAL_THREAD_EVENT_RESUMED,
|
54
100
|
NULL
|
55
101
|
);
|
56
102
|
}
|
@@ -70,8 +116,6 @@ static VALUE gt_disable_metric(VALUE module, VALUE metric) {
|
|
70
116
|
|
71
117
|
// GVLTools::LocalTimer and GVLTools::GlobalTimer
|
72
118
|
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
119
|
|
76
120
|
static VALUE global_timer_monotonic_time(VALUE module) {
|
77
121
|
return ULL2NUM(global_timer_total);
|
@@ -82,19 +126,38 @@ static VALUE global_timer_reset(VALUE module) {
|
|
82
126
|
return Qtrue;
|
83
127
|
}
|
84
128
|
|
85
|
-
static VALUE
|
86
|
-
return ULL2NUM(
|
129
|
+
static VALUE local_timer_m_monotonic_time(VALUE module) {
|
130
|
+
return ULL2NUM(GT_CURRENT_THREAD_LOCAL_STATE()->timer_total);
|
87
131
|
}
|
88
132
|
|
89
|
-
static VALUE
|
90
|
-
|
133
|
+
static VALUE local_timer_m_reset(VALUE module) {
|
134
|
+
GT_CURRENT_THREAD_LOCAL_STATE()->timer_total = 0;
|
91
135
|
return Qtrue;
|
92
136
|
}
|
93
137
|
|
138
|
+
#ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET
|
139
|
+
static VALUE local_timer_for(VALUE module, VALUE thread) {
|
140
|
+
GT_LOCAL_STATE(thread, true);
|
141
|
+
return rb_thread_local_aref(thread, rb_intern("__gvltools_local_state"));
|
142
|
+
}
|
143
|
+
|
144
|
+
static VALUE local_timer_monotonic_time(VALUE timer) {
|
145
|
+
thread_local_state *state;
|
146
|
+
TypedData_Get_Struct(timer, thread_local_state, &thread_local_state_type, state);
|
147
|
+
return ULL2NUM(state->timer_total);
|
148
|
+
}
|
149
|
+
|
150
|
+
static VALUE local_timer_reset(VALUE timer) {
|
151
|
+
thread_local_state *state;
|
152
|
+
TypedData_Get_Struct(timer, thread_local_state, &thread_local_state_type, state);
|
153
|
+
state->timer_total = 0;
|
154
|
+
return Qtrue;
|
155
|
+
}
|
156
|
+
#endif
|
157
|
+
|
94
158
|
// Thread counts
|
95
159
|
static _Atomic counter_t waiting_threads_total = 0;
|
96
160
|
static _Atomic counter_t waiting_threads_current_generation = 1;
|
97
|
-
static THREAD_LOCAL_SPECIFIER counter_t waiting_threads_ready_generation = 0;
|
98
161
|
|
99
162
|
static VALUE waiting_threads_count(VALUE module) {
|
100
163
|
return ULL2NUM(waiting_threads_total);
|
@@ -107,38 +170,51 @@ static VALUE waiting_threads_reset(VALUE module) {
|
|
107
170
|
}
|
108
171
|
|
109
172
|
// 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
173
|
static void gt_thread_callback(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *user_data) {
|
118
174
|
switch(event) {
|
119
|
-
case RUBY_INTERNAL_THREAD_EVENT_STARTED:
|
175
|
+
case RUBY_INTERNAL_THREAD_EVENT_STARTED: {
|
176
|
+
// The STARTED event is triggered from the parent thread with the GVL held
|
177
|
+
// so we can allocate the struct.
|
178
|
+
GT_EVENT_LOCAL_STATE(event_data, true);
|
179
|
+
}
|
180
|
+
break;
|
120
181
|
case RUBY_INTERNAL_THREAD_EVENT_EXITED: {
|
121
|
-
|
182
|
+
#ifndef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET
|
183
|
+
thread_local_state *state = GT_EVENT_LOCAL_STATE(event_data, false);
|
184
|
+
if (state) {
|
185
|
+
// MRI can re-use native threads, so we need to reset thread local state,
|
186
|
+
// otherwise it will leak from one Ruby thread from another.
|
187
|
+
state->was_ready = false;
|
188
|
+
state->timer_total = 0;
|
189
|
+
}
|
190
|
+
#endif
|
122
191
|
}
|
123
192
|
break;
|
124
193
|
case RUBY_INTERNAL_THREAD_EVENT_READY: {
|
125
|
-
|
194
|
+
thread_local_state *state = GT_EVENT_LOCAL_STATE(event_data, false);
|
195
|
+
if (!state) {
|
196
|
+
break;
|
197
|
+
}
|
198
|
+
state->was_ready = true;
|
126
199
|
|
127
200
|
if (ENABLED(WAITING_THREADS)) {
|
128
|
-
waiting_threads_ready_generation = waiting_threads_current_generation;
|
201
|
+
state->waiting_threads_ready_generation = waiting_threads_current_generation;
|
129
202
|
waiting_threads_total++;
|
130
203
|
}
|
131
204
|
|
132
205
|
if (ENABLED(TIMER_GLOBAL | TIMER_LOCAL)) {
|
133
|
-
gt_gettime(&timer_ready_at);
|
206
|
+
gt_gettime(&state->timer_ready_at);
|
134
207
|
}
|
135
208
|
}
|
136
209
|
break;
|
137
210
|
case RUBY_INTERNAL_THREAD_EVENT_RESUMED: {
|
138
|
-
|
211
|
+
thread_local_state *state = GT_EVENT_LOCAL_STATE(event_data, true);
|
212
|
+
if (!state->was_ready) {
|
213
|
+
break; // In case we registered the hook while some threads were already waiting on the GVL
|
214
|
+
}
|
139
215
|
|
140
216
|
if (ENABLED(WAITING_THREADS)) {
|
141
|
-
if (waiting_threads_ready_generation == waiting_threads_current_generation) {
|
217
|
+
if (state->waiting_threads_ready_generation == waiting_threads_current_generation) {
|
142
218
|
waiting_threads_total--;
|
143
219
|
}
|
144
220
|
}
|
@@ -146,10 +222,10 @@ static void gt_thread_callback(rb_event_flag_t event, const rb_internal_thread_e
|
|
146
222
|
if (ENABLED(TIMER_GLOBAL | TIMER_LOCAL)) {
|
147
223
|
struct timespec current_time;
|
148
224
|
gt_gettime(¤t_time);
|
149
|
-
counter_t diff = gt_time_diff_ns(timer_ready_at, current_time);
|
225
|
+
counter_t diff = gt_time_diff_ns(state->timer_ready_at, current_time);
|
150
226
|
|
151
227
|
if (ENABLED(TIMER_LOCAL)) {
|
152
|
-
|
228
|
+
state->timer_total += diff;
|
153
229
|
}
|
154
230
|
|
155
231
|
if (ENABLED(TIMER_GLOBAL)) {
|
@@ -162,6 +238,10 @@ static void gt_thread_callback(rb_event_flag_t event, const rb_internal_thread_e
|
|
162
238
|
}
|
163
239
|
|
164
240
|
void Init_instrumentation(void) {
|
241
|
+
#ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET // 3.3+
|
242
|
+
thread_storage_key = rb_internal_thread_specific_key_create();
|
243
|
+
#endif
|
244
|
+
|
165
245
|
VALUE rb_mGVLTools = rb_const_get(rb_cObject, rb_intern("GVLTools"));
|
166
246
|
|
167
247
|
VALUE rb_mNative = rb_const_get(rb_mGVLTools, rb_intern("Native"));
|
@@ -173,9 +253,16 @@ void Init_instrumentation(void) {
|
|
173
253
|
rb_define_singleton_method(rb_mGlobalTimer, "reset", global_timer_reset, 0);
|
174
254
|
rb_define_singleton_method(rb_mGlobalTimer, "monotonic_time", global_timer_monotonic_time, 0);
|
175
255
|
|
176
|
-
|
177
|
-
|
178
|
-
|
256
|
+
rb_global_variable(&rb_cLocalTimer);
|
257
|
+
rb_cLocalTimer = rb_const_get(rb_mGVLTools, rb_intern("LocalTimer"));
|
258
|
+
rb_undef_alloc_func(rb_cLocalTimer);
|
259
|
+
rb_define_singleton_method(rb_cLocalTimer, "reset", local_timer_m_reset, 0);
|
260
|
+
rb_define_singleton_method(rb_cLocalTimer, "monotonic_time", local_timer_m_monotonic_time, 0);
|
261
|
+
#ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET
|
262
|
+
rb_define_singleton_method(rb_cLocalTimer, "for", local_timer_for, 1);
|
263
|
+
rb_define_method(rb_cLocalTimer, "reset", local_timer_reset, 0);
|
264
|
+
rb_define_method(rb_cLocalTimer, "monotonic_time", local_timer_monotonic_time, 0);
|
265
|
+
#endif
|
179
266
|
|
180
267
|
VALUE rb_mWaitingThreads = rb_const_get(rb_mGVLTools, rb_intern("WaitingThreads"));
|
181
268
|
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,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gvltools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
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'
|
11
|
+
date: 2024-01-19 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
27
13
|
description:
|
28
14
|
email:
|
29
15
|
- jean.boussier@gmail.com
|
@@ -67,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
53
|
- !ruby/object:Gem::Version
|
68
54
|
version: '0'
|
69
55
|
requirements: []
|
70
|
-
rubygems_version: 3.
|
56
|
+
rubygems_version: 3.5.3
|
71
57
|
signing_key:
|
72
58
|
specification_version: 4
|
73
59
|
summary: Set of GVL instrumentation tools
|