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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ffb320bec5fdc8a0d4940d8b370af1607a20383e1a9a8f939cc759588ca6627
4
- data.tar.gz: 223bee0c124c17296b8386bbdda94406f91b84ad0adcf3acd09921424bb8eaf8
3
+ metadata.gz: 93f82e0950c9e2c02690e92984d20654d36dd91b3dffee80d0e85965a0bbc0b3
4
+ data.tar.gz: c6db2e33663b3c3fe290889eb91a71ac336d75a1005e90903b5c54e663446f70
5
5
  SHA512:
6
- metadata.gz: 1c988c2e9d2c17925b178e4201f99530e75328cda648b3e3b50664e1d751ff5ad086139293a22dab5a54ae18e86bdd7d6e14064f4a4833eae18d3d948f134f20
7
- data.tar.gz: 3b56ad91bf7784d0c61e4d321964eeaaae8ca97ec6c6c86c1934e8e95ad9d720b00933a7b0f7e91ec45e7ffd2a07665800c6f616250b7251318dc889a5ba94ca
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
@@ -2,11 +2,10 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- # Specify your gem's dependencies in gvltools.gemspec
6
5
  gemspec
7
6
 
8
- gem "rake", "~> 13.0"
7
+ gem "rake"
8
+ gem "rake-compiler"
9
9
 
10
10
  gem "minitest", "~> 5.0"
11
-
12
11
  gem "rubocop", "~> 1.21"
data/Gemfile.lock CHANGED
@@ -1,48 +1,57 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gvltools (0.3.0)
4
+ gvltools (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- ast (2.4.2)
10
- minitest (5.15.0)
11
- parallel (1.22.1)
12
- parser (3.1.2.0)
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.0.6)
16
- rake-compiler (1.2.0)
21
+ rake (13.4.2)
22
+ rake-compiler (1.3.1)
17
23
  rake
18
- regexp_parser (2.5.0)
19
- rexml (3.2.5)
20
- rubocop (1.30.1)
21
- parallel (~> 1.10)
22
- parser (>= 3.1.0.0)
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 (>= 1.8, < 3.0)
25
- rexml (>= 3.2.5, < 4.0)
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 (>= 1.4.0, < 3.0)
29
- rubocop-ast (1.18.0)
30
- parser (>= 3.1.1.0)
31
- ruby-progressbar (1.11.0)
32
- unicode-display_width (2.1.0)
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-21
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 (~> 13.0)
52
+ rake
44
53
  rake-compiler
45
54
  rubocop (~> 1.21)
46
55
 
47
56
  BUNDLED WITH
48
- 2.3.14
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.
@@ -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 local_timer_monotonic_time(VALUE module) {
86
- return ULL2NUM(local_timer_total);
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 module) {
90
- local_timer_total = 0;
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
- gt_reset_thread_local_state();
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
- if (!was_ready) was_ready = true;
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
- if (!was_ready) break; // In case we registered the hook while some threads were already waiting on the GVL
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(&current_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
- local_timer_total += diff;
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
- VALUE rb_mLocalTimer = rb_const_get(rb_mGVLTools, rb_intern("LocalTimer"));
177
- rb_define_singleton_method(rb_mLocalTimer, "reset", local_timer_reset, 0);
178
- rb_define_singleton_method(rb_mLocalTimer, "monotonic_time", local_timer_monotonic_time, 0);
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
@@ -30,6 +30,4 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
  spec.extensions = ["ext/gvltools/extconf.rb"]
33
-
34
- spec.add_development_dependency "rake-compiler"
35
33
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GVLTools
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/gvltools.rb CHANGED
@@ -70,7 +70,7 @@ module GVLTools
70
70
  end
71
71
  end
72
72
 
73
- module LocalTimer
73
+ class LocalTimer
74
74
  extend AbstractInstrumenter
75
75
 
76
76
  class << self
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.3.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: 2023-04-11 00:00:00.000000000 Z
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: 3.4.10
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: []