gvl-tracing 0.1.1 → 0.2.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: be2bff3cbad042f549ffee919cb659fe201d49714ae64924ae3fa30aba79d331
4
- data.tar.gz: d138181409369aed6507b63efa33da6d4ccc261c7e0aae469fe250bcb2b062ae
3
+ metadata.gz: 1adc64642ec1cb371df66afa9f928cafe106349f981a3b8ec643232a0618b769
4
+ data.tar.gz: 9b250c7a07df1f9a1f907100554fa74eba043edb639ad1cc777b33ba705d98f3
5
5
  SHA512:
6
- metadata.gz: 544ada893bf0c5b3152bd36a2a8fe6496b0609910d5b4e75044ea00c7c15acab999bc5708e8efff46f4b7022621108237bc700990d54c67c9a206401ce18cb56
7
- data.tar.gz: 61de24e198a80220fe2d408aaa267b515032ddda686452501c445dd1a6af754c7fded4bb43ca9ba25091bcc6493e63b547d543bd2f0b5af5bfaf2fe1ca2dd755
6
+ metadata.gz: 5016d9734e5eeef9578fc83211bb68cbc26f68f1b93b41c3ff0fbd9f5a3b93745cb7de23c69cc228a903b17db565ee369ec3d41f7d9575e3107a04a9bf6368a0
7
+ data.tar.gz: 821d89427465c6496e09a773a1277d35388d4205fd5e0e5f054fd8da8d1a46d902e4f464764a0e5891bb9ebb63451295ea2bf1986c085616b2ff60926dcbde7a
data/README.adoc CHANGED
@@ -8,7 +8,7 @@ A Ruby gem for getting a timeline view of Global VM Lock usage in your Ruby app
8
8
 
9
9
  image::preview.png[]
10
10
 
11
- See my blog post **FIXME** for more details!
11
+ See my blog post https://ivoanjo.me/blog/2022/07/17/tracing-ruby-global-vm-lock/[tracing ruby's (global) vm lock] for more details!
12
12
 
13
13
  NOTE: This gem only works on Ruby 3.2 and above because it depends on the new https://github.com/ruby/ruby/pull/5500[GVL Instrumentation API]. See below for an easy way to run Ruby 3.2 inside docker.
14
14
 
@@ -45,7 +45,23 @@ To do so:
45
45
 
46
46
  == Quick start using docker
47
47
 
48
- **FIXME: TODO**
48
+ The `gvl-tracing` gem requires Ruby 3.2, which is still under development. If you have docker installed on your machine, you can use the https://hub.docker.com/r/rubylang/ruby[ruby-lang development images] to try it out.
49
+
50
+ Here's how you can use them:
51
+
52
+ [source,bash]
53
+ ----
54
+ $ cd my_ruby_app/
55
+ $ docker run -v $(pwd):/app -it rubylang/ruby:master-focal
56
+ root@0e0b07edf906:/# cd app/
57
+ root@0e0b07edf906:/app# ruby -v
58
+ ruby 3.2.0dev (2022-07-23T12:42:05Z master 721d154e2f) [x86_64-linux]
59
+ root@0e0b07edf906:/app# gem install gvl-tracing
60
+ Building native extensions. This could take a while...
61
+ Successfully installed gvl-tracing-0.1.1
62
+ 1 gem installed
63
+ root@0e0b07edf906:/app# ruby <your app>
64
+ ----
49
65
 
50
66
  == Installation
51
67
 
Binary file
@@ -0,0 +1,14 @@
1
+ require "gvl-tracing"
2
+
3
+ def fib(n)
4
+ return n if n <= 1
5
+ fib(n - 1) + fib(n - 2)
6
+ end
7
+
8
+ GvlTracing.start("example2.json")
9
+
10
+ other_thread = Thread.new { fib(37) } # runs in other thread
11
+ fib(37) # runs in main thread
12
+
13
+ other_thread.join
14
+ GvlTracing.stop
Binary file
@@ -0,0 +1,14 @@
1
+ require "gvl-tracing"
2
+
3
+ def fib(n)
4
+ return n if n <= 1
5
+ fib(n - 1) + fib(n - 2)
6
+ end
7
+
8
+ GvlTracing.start("example3.json")
9
+
10
+ other_ractor = Ractor.new { fib(37) } # runs in other ractor
11
+ fib(37) # runs in main thread
12
+
13
+ other_ractor.take
14
+ GvlTracing.stop
Binary file
data/examples/gc.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "gvl-tracing"
2
+
3
+ def alloc(n)
4
+ n.times { Object.new }
5
+ end
6
+
7
+ GvlTracing.start("gc.json")
8
+
9
+ 3.times.map { Thread.new { alloc(100_000) } }.map(&:join)
10
+
11
+ sleep(0.05)
12
+
13
+ GvlTracing.stop
@@ -24,31 +24,49 @@
24
24
  // SOFTWARE.
25
25
 
26
26
  #include <ruby/ruby.h>
27
+ #include <ruby/debug.h>
27
28
  #include <ruby/thread.h>
29
+ #include <ruby/atomic.h>
28
30
  #include <errno.h>
29
31
  #include <stdbool.h>
30
32
  #include <sys/types.h>
31
- #include <unistd.h>
32
33
 
33
34
  static VALUE tracing_start(VALUE _self, VALUE output_path);
34
35
  static VALUE tracing_stop(VALUE _self);
35
36
  static double timestamp_microseconds(void);
36
37
  static void render_event(const char *event_name);
37
- static void on_event(rb_event_flag_t event, const rb_internal_thread_event_data_t *_unused1, void *_unused2);
38
+ static void on_thread_event(rb_event_flag_t event, const rb_internal_thread_event_data_t *_unused1, void *_unused2);
39
+ static void on_gc_event(VALUE tpval, void *_unused1);
40
+
41
+ // Thread-local state
42
+ static _Thread_local bool current_thread_serial_set = false;
43
+ static _Thread_local unsigned int current_thread_serial = 0;
38
44
 
39
45
  // Global mutable state
46
+ static rb_atomic_t thread_serial = 0;
40
47
  static FILE *output_file = NULL;
41
48
  static rb_internal_thread_event_hook_t *current_hook = NULL;
42
49
  static double started_tracing_at_microseconds = 0;
43
50
  static pid_t process_id = 0;
51
+ static VALUE gc_tracepoint = Qnil;
44
52
 
45
53
  void Init_gvl_tracing_native_extension(void) {
54
+ rb_global_variable(&gc_tracepoint);
55
+
46
56
  VALUE gvl_tracing_module = rb_define_module("GvlTracing");
47
57
 
48
58
  rb_define_singleton_method(gvl_tracing_module, "start", tracing_start, 1);
49
59
  rb_define_singleton_method(gvl_tracing_module, "stop", tracing_stop, 0);
50
60
  }
51
61
 
62
+ static inline unsigned int current_thread_id(void) {
63
+ if (!current_thread_serial_set) {
64
+ current_thread_serial_set = true;
65
+ current_thread_serial = RUBY_ATOMIC_FETCH_ADD(thread_serial, 1);
66
+ }
67
+ return (unsigned int)current_thread_serial;
68
+ }
69
+
52
70
  static VALUE tracing_start(VALUE _self, VALUE output_path) {
53
71
  Check_Type(output_path, T_STRING);
54
72
 
@@ -63,7 +81,7 @@ static VALUE tracing_start(VALUE _self, VALUE output_path) {
63
81
  render_event("started_tracing");
64
82
 
65
83
  current_hook = rb_internal_thread_add_event_hook(
66
- on_event,
84
+ on_thread_event,
67
85
  (
68
86
  RUBY_INTERNAL_THREAD_EVENT_READY |
69
87
  RUBY_INTERNAL_THREAD_EVENT_RESUMED |
@@ -74,6 +92,17 @@ static VALUE tracing_start(VALUE _self, VALUE output_path) {
74
92
  NULL
75
93
  );
76
94
 
95
+ gc_tracepoint = rb_tracepoint_new(
96
+ 0,
97
+ (
98
+ RUBY_INTERNAL_EVENT_GC_ENTER |
99
+ RUBY_INTERNAL_EVENT_GC_EXIT
100
+ ),
101
+ on_gc_event,
102
+ (void *) NULL
103
+ );
104
+ rb_tracepoint_enable(gc_tracepoint);
105
+
77
106
  return Qtrue;
78
107
  }
79
108
 
@@ -81,6 +110,8 @@ static VALUE tracing_stop(VALUE _self) {
81
110
  if (output_file == NULL) rb_raise(rb_eRuntimeError, "Tracing not running");
82
111
 
83
112
  rb_internal_thread_remove_event_hook(current_hook);
113
+ rb_tracepoint_disable(gc_tracepoint);
114
+ gc_tracepoint = Qnil;
84
115
 
85
116
  render_event("stopped_tracing");
86
117
  fprintf(output_file, "]\n");
@@ -103,11 +134,14 @@ static double timestamp_microseconds(void) {
103
134
  static void render_event(const char *event_name) {
104
135
  // Event data
105
136
  double now_microseconds = timestamp_microseconds() - started_tracing_at_microseconds;
106
- pid_t thread_id = gettid();
137
+ unsigned int thread_id = current_thread_id();
107
138
 
108
139
  // Each event is converted into two events in the output: one that signals the end of the previous event
109
140
  // (whatever it was), and one that signals the start of the actual event we're processing.
110
- // Yes this is seems to be bending a bit the intention of the output format, but it seemed easier to do this way.
141
+ // Yes, this seems to be slightly bending the intention of the output format, but it seemed easier to do this way.
142
+
143
+ // Important note: We've observed some rendering issues in perfetto if the tid or pid are numbers that are "too big",
144
+ // see https://github.com/ivoanjo/gvl-tracing/pull/4#issuecomment-1196463364 for an example.
111
145
 
112
146
  fprintf(output_file,
113
147
  // Finish previous duration
@@ -121,14 +155,24 @@ static void render_event(const char *event_name) {
121
155
  );
122
156
  }
123
157
 
124
- static void on_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *_unused1, void *_unused2) {
158
+ static void on_thread_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *_unused1, void *_unused2) {
125
159
  const char* event_name = "bug_unknown_event";
126
160
  switch (event_id) {
127
161
  case RUBY_INTERNAL_THREAD_EVENT_READY: event_name = "ready"; break;
128
- case RUBY_INTERNAL_THREAD_EVENT_RESUMED: event_name = "resumed"; break;
129
- case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED: event_name = "suspended"; break;
162
+ case RUBY_INTERNAL_THREAD_EVENT_RESUMED: event_name = "running"; break;
163
+ case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED: event_name = "waiting"; break;
130
164
  case RUBY_INTERNAL_THREAD_EVENT_STARTED: event_name = "started"; break;
131
165
  case RUBY_INTERNAL_THREAD_EVENT_EXITED: event_name = "exited"; break;
132
166
  };
133
167
  render_event(event_name);
134
168
  }
169
+
170
+ static void on_gc_event(VALUE tpval, void *_unused1) {
171
+ const char* event_name = "bug_unknown_event";
172
+ switch (rb_tracearg_event_flag(rb_tracearg_from_tracepoint(tpval))) {
173
+ case RUBY_INTERNAL_EVENT_GC_ENTER: event_name = "gc"; break;
174
+ // TODO: is it possible the thread wasn't running? Might need to save the last state.
175
+ case RUBY_INTERNAL_EVENT_GC_EXIT: event_name = "running"; break;
176
+ }
177
+ render_event(event_name);
178
+ }
@@ -26,5 +26,5 @@
26
26
  # frozen_string_literal: true
27
27
 
28
28
  module GvlTracing
29
- VERSION = "0.1.1"
29
+ VERSION = "0.2.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: 0.1.1
4
+ version: 0.2.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: 2022-07-24 00:00:00.000000000 Z
11
+ date: 2022-07-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -26,6 +26,12 @@ files:
26
26
  - Rakefile
27
27
  - examples/example1.json.gz
28
28
  - examples/example1.rb
29
+ - examples/example2.json.gz
30
+ - examples/example2.rb
31
+ - examples/example3.json.gz
32
+ - examples/example3.rb
33
+ - examples/gc.json.gz
34
+ - examples/gc.rb
29
35
  - ext/gvl_tracing_native_extension/extconf.rb
30
36
  - ext/gvl_tracing_native_extension/gvl_tracing.c
31
37
  - gems.rb