gvl-tracing 1.2.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 643b4b237db640d34ecf842459246640517ca9d58118ca891c76a629f1c6ab73
4
- data.tar.gz: 10c2613b6b66681b46f7d32a143e5742ed98b969d9aca00e3ceb6b22c5579f4b
3
+ metadata.gz: d4945e5419655b2e69f310845e6214ff0bd6eb66ae820383f9cbe97855f15cdb
4
+ data.tar.gz: dd95a9431c53c3904b054227a305da7433a789d8828b0340abc2b48864361735
5
5
  SHA512:
6
- metadata.gz: f3265c1719bd2dd7859f4df78a4b9096277b45f26b9a6bd515c5a3222f3bdb98e56461eef274f5987a61cfb7ec331ac44dac58a94fd8dadf7474a51b9033f3d6
7
- data.tar.gz: 40a336509af944962fc2c48ae2f40c2ae40ff133b53b7e006f63f1a453e4106d68f6283e008462088f39afd3c5653556ef064fb6bf312aeffc09cd282ba90ba9
6
+ metadata.gz: eca1b710f5918e887f06c44dfa970100e77c99bbc63318321a72e76dfffc4b44022b055dae4de05992494fc40910a467f4aa3ed045ab891018303a15fe8169e1
7
+ data.tar.gz: 59deb53b041211513979526345af62c3e94495e73deae38d1f1e7272a11469695ae5c2435331bcf811f1c943c18f9602c4e3199c97fef9963cb3e3e4cd25f5cd
data/README.adoc CHANGED
@@ -25,17 +25,15 @@ def fib(n)
25
25
  fib(n - 1) + fib(n - 2)
26
26
  end
27
27
 
28
- GvlTracing.start("example1.json")
28
+ GvlTracing.start("example1.json") do
29
+ Thread.new { sleep(0.05) while true }
29
30
 
30
- Thread.new { sleep(0.05) while true }
31
+ sleep(0.05)
31
32
 
32
- sleep(0.05)
33
+ 3.times.map { Thread.new { fib(37) } }.map(&:join)
33
34
 
34
- 3.times.map { Thread.new { fib(37) } }.map(&:join)
35
-
36
- sleep(0.05)
37
-
38
- GvlTracing.stop
35
+ sleep(0.05)
36
+ end
39
37
  ----
40
38
 
41
39
  To do so:
@@ -65,7 +63,7 @@ Use `require "gvl-tracing"` to load the gem.
65
63
 
66
64
  This gem only provides a single module (`GvlTracing`) with methods:
67
65
 
68
- * `start(filename)`: Starts tracing, writing the results to the provided filename
66
+ * `start(filename, &block)`: Starts tracing, writing the results to the provided filename. When a block is passed, yields the block and calls stop.
69
67
  * `stop`: Stops tracing
70
68
 
71
69
  The resulting traces can be analyzed by going to https://ui.perfetto.dev/[Perfetto UI].
@@ -56,11 +56,14 @@ static void set_native_thread_id(void);
56
56
  static void render_event(const char *event_name);
57
57
  static void on_thread_event(rb_event_flag_t event, const rb_internal_thread_event_data_t *_unused1, void *_unused2);
58
58
  static void on_gc_event(VALUE tpval, void *_unused1);
59
+ static VALUE mark_sleeping(VALUE _self);
59
60
 
60
61
  // Thread-local state
61
62
  static _Thread_local bool current_thread_seen = false;
62
63
  static _Thread_local unsigned int current_thread_serial = 0;
63
64
  static _Thread_local uint64_t thread_id = 0;
65
+ static _Thread_local rb_event_flag_t previous_state = 0; // Used to coalesce similar events
66
+ static _Thread_local bool sleeping = false; // Used to track when a thread is sleeping
64
67
 
65
68
  // Global mutable state
66
69
  static rb_atomic_t thread_serial = 0;
@@ -77,6 +80,7 @@ void Init_gvl_tracing_native_extension(void) {
77
80
 
78
81
  rb_define_singleton_method(gvl_tracing_module, "_start", tracing_start, 1);
79
82
  rb_define_singleton_method(gvl_tracing_module, "_stop", tracing_stop, 0);
83
+ rb_define_singleton_method(gvl_tracing_module, "mark_sleeping", mark_sleeping, 0);
80
84
  }
81
85
 
82
86
  static inline void initialize_thread_id(void) {
@@ -204,6 +208,23 @@ static void render_event(const char *event_name) {
204
208
  }
205
209
 
206
210
  static void on_thread_event(rb_event_flag_t event_id, UNUSED_ARG const rb_internal_thread_event_data_t *_unused1, UNUSED_ARG void *_unused2) {
211
+ // In some cases, Ruby seems to even multiple suspended events for the same thread in a row (e.g. when multiple threads)
212
+ // are waiting on a Thread::ConditionVariable.new that gets signaled. We coalesce these events to make the resulting
213
+ // timeline easier to see.
214
+ //
215
+ // I haven't observed other situations where we'd want to coalesce events, but we may apply this to all events in the
216
+ // future. One annoying thing to remember when generalizing this is how to reset the `previous_state` across multiple
217
+ // start/stop calls to GvlTracing.
218
+ if (event_id == RUBY_INTERNAL_THREAD_EVENT_SUSPENDED && event_id == previous_state) return;
219
+ previous_state = event_id;
220
+
221
+ if (event_id == RUBY_INTERNAL_THREAD_EVENT_SUSPENDED && sleeping) {
222
+ render_event("sleeping");
223
+ return;
224
+ } else {
225
+ sleeping = false;
226
+ }
227
+
207
228
  const char* event_name = "bug_unknown_event";
208
229
  switch (event_id) {
209
230
  case RUBY_INTERNAL_THREAD_EVENT_READY: event_name = "wants_gvl"; break;
@@ -224,3 +245,8 @@ static void on_gc_event(VALUE tpval, UNUSED_ARG void *_unused1) {
224
245
  }
225
246
  render_event(event_name);
226
247
  }
248
+
249
+ static VALUE mark_sleeping(VALUE _self) {
250
+ sleeping = true;
251
+ return Qnil;
252
+ }
data/lib/gvl-tracing.rb CHANGED
@@ -37,6 +37,14 @@ module GvlTracing
37
37
  def start(file)
38
38
  _start(file)
39
39
  @path = file
40
+
41
+ return unless block_given?
42
+
43
+ begin
44
+ yield
45
+ ensure
46
+ _stop
47
+ end
40
48
  end
41
49
 
42
50
  def stop
@@ -0,0 +1,12 @@
1
+ # Experimental: This monkey patch when loaded introduces a new state -- "sleeping" -- which is more specific than the
2
+ # regular "waiting". This can be useful to distinguish when waiting is happening based on time, vs for some event to
3
+ # happen.
4
+
5
+ module GvlTracing::SleepTracking
6
+ def sleep(...)
7
+ GvlTracing.mark_sleeping
8
+ super(...)
9
+ end
10
+ end
11
+
12
+ include GvlTracing::SleepTracking
@@ -26,5 +26,5 @@
26
26
  # frozen_string_literal: true
27
27
 
28
28
  module GvlTracing
29
- VERSION = "1.2.0"
29
+ VERSION = "1.4.0"
30
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gvl-tracing
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivo Anjo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-06 00:00:00.000000000 Z
11
+ date: 2023-09-05 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -30,6 +30,7 @@ files:
30
30
  - gems.rb
31
31
  - gvl-tracing.gemspec
32
32
  - lib/gvl-tracing.rb
33
+ - lib/gvl_tracing/sleep_tracking.rb
33
34
  - lib/gvl_tracing/version.rb
34
35
  - preview.png
35
36
  homepage: https://github.com/ivoanjo/gvl-tracing
@@ -52,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
53
  - !ruby/object:Gem::Version
53
54
  version: '0'
54
55
  requirements: []
55
- rubygems_version: 3.4.6
56
+ rubygems_version: 3.4.1
56
57
  signing_key:
57
58
  specification_version: 4
58
59
  summary: Get a timeline view of Global VM Lock usage in your Ruby app