gvl-tracing 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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