gvl-tracing 0.1.1 → 1.0.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: 64d9b437af5f15e4a865a580486df67974e42b22b9836fa37e4de608597bf8d8
4
+ data.tar.gz: 52e7ecb9f89e613acd619a4225cfa2ac1f83cbf42e9173d59ccb91db09a1e7c6
5
5
  SHA512:
6
- metadata.gz: 544ada893bf0c5b3152bd36a2a8fe6496b0609910d5b4e75044ea00c7c15acab999bc5708e8efff46f4b7022621108237bc700990d54c67c9a206401ce18cb56
7
- data.tar.gz: 61de24e198a80220fe2d408aaa267b515032ddda686452501c445dd1a6af754c7fded4bb43ca9ba25091bcc6493e63b547d543bd2f0b5af5bfaf2fe1ca2dd755
6
+ metadata.gz: 529be0892156b7060427359d3e4386ed4f2bf0d96a3671896a6fcde4d347370e061567b8f88718ec894520ff9dc4b3bc667810da6d16b3ed171ef004ea3ecaf3
7
+ data.tar.gz: 8702d542f6962be8f0b471ed15e66c6ff4da1a41eadd121edacb3ade280109eeb219b4b7a6c1786ba13518948406f5182102af1fc363c7327e9a3c5bfe5e2cd4
data/README.adoc CHANGED
@@ -8,9 +8,9 @@ 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
- 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.
13
+ NOTE: This gem only works on Ruby 3.2 and above because it depends on the https://github.com/ruby/ruby/pull/5500[GVL Instrumentation API].
14
14
 
15
15
  == Quickest start
16
16
 
@@ -43,10 +43,6 @@ To do so:
43
43
  1. Download link:https://github.com/ivoanjo/gvl-tracing/blob/master/examples/example1.json.gz?raw=true[`examples/example1.json.gz`]
44
44
  2. Navigate to https://ui.perfetto.dev/ and use the **Open trace file** option to load the file
45
45
 
46
- == Quick start using docker
47
-
48
- **FIXME: TODO**
49
-
50
46
  == Installation
51
47
 
52
48
  Install the gem and add to the application's `Gemfile` or `gems.rb` file by executing:
@@ -32,4 +32,10 @@ end
32
32
 
33
33
  require "mkmf"
34
34
 
35
+ have_func("gettid", "unistd.h")
36
+ have_header("pthread.h")
37
+ have_func("pthread_getname_np", "pthread.h")
38
+ have_func("pthread_threadid_np", "pthread.h")
39
+
40
+ create_header
35
41
  create_makefile "gvl_tracing_native_extension"
@@ -24,31 +24,78 @@
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>
33
+
34
+ #include "extconf.h"
35
+
36
+ #ifdef HAVE_PTHREAD_H
37
+ #include <pthread.h>
38
+ #endif
39
+
40
+ #ifdef HAVE_GETTID
41
+ #include <unistd.h>
42
+ #endif
32
43
 
33
44
  static VALUE tracing_start(VALUE _self, VALUE output_path);
34
45
  static VALUE tracing_stop(VALUE _self);
35
46
  static double timestamp_microseconds(void);
36
47
  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);
48
+ static void on_thread_event(rb_event_flag_t event, const rb_internal_thread_event_data_t *_unused1, void *_unused2);
49
+ static void on_gc_event(VALUE tpval, void *_unused1);
50
+
51
+ // Thread-local state
52
+ static _Thread_local bool current_thread_serial_set = false;
53
+ static _Thread_local unsigned int current_thread_serial = 0;
38
54
 
39
55
  // Global mutable state
56
+ static rb_atomic_t thread_serial = 0;
40
57
  static FILE *output_file = NULL;
41
58
  static rb_internal_thread_event_hook_t *current_hook = NULL;
42
59
  static double started_tracing_at_microseconds = 0;
43
60
  static pid_t process_id = 0;
61
+ static VALUE gc_tracepoint = Qnil;
44
62
 
45
63
  void Init_gvl_tracing_native_extension(void) {
64
+ rb_global_variable(&gc_tracepoint);
65
+
46
66
  VALUE gvl_tracing_module = rb_define_module("GvlTracing");
47
67
 
48
68
  rb_define_singleton_method(gvl_tracing_module, "start", tracing_start, 1);
49
69
  rb_define_singleton_method(gvl_tracing_module, "stop", tracing_stop, 0);
50
70
  }
51
71
 
72
+ static inline void initialize_thread_id(void) {
73
+ current_thread_serial_set = true;
74
+ current_thread_serial = RUBY_ATOMIC_FETCH_ADD(thread_serial, 1);
75
+ }
76
+
77
+ static inline void render_thread_metadata(void) {
78
+ uint64_t native_thread_id = 0;
79
+ #ifdef HAVE_GETTID
80
+ native_thread_id = gettid();
81
+ #elif HAVE_PTHREAD_THREADID_NP
82
+ pthread_threadid_np(pthread_self(), &native_thread_id);
83
+ #else
84
+ native_thread_id = current_thread_serial; // TODO: Better fallback for Windows?
85
+ #endif
86
+
87
+ char native_thread_name_buffer[64] = "(unnamed)";
88
+
89
+ #ifdef HAVE_PTHREAD_GETNAME_NP
90
+ pthread_getname_np(pthread_self(), native_thread_name_buffer, sizeof(native_thread_name_buffer));
91
+ #endif
92
+
93
+ fprintf(output_file,
94
+ " {\"ph\": \"M\", \"pid\": %u, \"tid\": %u, \"name\": \"thread_name\", \"args\": {\"name\": \"%lu %s\"}},\n",
95
+ process_id, current_thread_serial, native_thread_id, native_thread_name_buffer
96
+ );
97
+ }
98
+
52
99
  static VALUE tracing_start(VALUE _self, VALUE output_path) {
53
100
  Check_Type(output_path, T_STRING);
54
101
 
@@ -63,7 +110,7 @@ static VALUE tracing_start(VALUE _self, VALUE output_path) {
63
110
  render_event("started_tracing");
64
111
 
65
112
  current_hook = rb_internal_thread_add_event_hook(
66
- on_event,
113
+ on_thread_event,
67
114
  (
68
115
  RUBY_INTERNAL_THREAD_EVENT_READY |
69
116
  RUBY_INTERNAL_THREAD_EVENT_RESUMED |
@@ -74,6 +121,17 @@ static VALUE tracing_start(VALUE _self, VALUE output_path) {
74
121
  NULL
75
122
  );
76
123
 
124
+ gc_tracepoint = rb_tracepoint_new(
125
+ 0,
126
+ (
127
+ RUBY_INTERNAL_EVENT_GC_ENTER |
128
+ RUBY_INTERNAL_EVENT_GC_EXIT
129
+ ),
130
+ on_gc_event,
131
+ (void *) NULL
132
+ );
133
+ rb_tracepoint_enable(gc_tracepoint);
134
+
77
135
  return Qtrue;
78
136
  }
79
137
 
@@ -81,6 +139,8 @@ static VALUE tracing_stop(VALUE _self) {
81
139
  if (output_file == NULL) rb_raise(rb_eRuntimeError, "Tracing not running");
82
140
 
83
141
  rb_internal_thread_remove_event_hook(current_hook);
142
+ rb_tracepoint_disable(gc_tracepoint);
143
+ gc_tracepoint = Qnil;
84
144
 
85
145
  render_event("stopped_tracing");
86
146
  fprintf(output_file, "]\n");
@@ -103,11 +163,20 @@ static double timestamp_microseconds(void) {
103
163
  static void render_event(const char *event_name) {
104
164
  // Event data
105
165
  double now_microseconds = timestamp_microseconds() - started_tracing_at_microseconds;
106
- pid_t thread_id = gettid();
166
+
167
+ if (!current_thread_serial_set) {
168
+ initialize_thread_id();
169
+ render_thread_metadata();
170
+ }
171
+
172
+ unsigned int thread_id = current_thread_serial;
107
173
 
108
174
  // Each event is converted into two events in the output: one that signals the end of the previous event
109
175
  // (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.
176
+ // Yes, this seems to be slightly bending the intention of the output format, but it seemed easier to do this way.
177
+
178
+ // Important note: We've observed some rendering issues in perfetto if the tid or pid are numbers that are "too big",
179
+ // see https://github.com/ivoanjo/gvl-tracing/pull/4#issuecomment-1196463364 for an example.
111
180
 
112
181
  fprintf(output_file,
113
182
  // Finish previous duration
@@ -121,14 +190,24 @@ static void render_event(const char *event_name) {
121
190
  );
122
191
  }
123
192
 
124
- static void on_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *_unused1, void *_unused2) {
193
+ static void on_thread_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *_unused1, void *_unused2) {
125
194
  const char* event_name = "bug_unknown_event";
126
195
  switch (event_id) {
127
196
  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;
197
+ case RUBY_INTERNAL_THREAD_EVENT_RESUMED: event_name = "running"; break;
198
+ case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED: event_name = "waiting"; break;
130
199
  case RUBY_INTERNAL_THREAD_EVENT_STARTED: event_name = "started"; break;
131
200
  case RUBY_INTERNAL_THREAD_EVENT_EXITED: event_name = "exited"; break;
132
201
  };
133
202
  render_event(event_name);
134
203
  }
204
+
205
+ static void on_gc_event(VALUE tpval, void *_unused1) {
206
+ const char* event_name = "bug_unknown_event";
207
+ switch (rb_tracearg_event_flag(rb_tracearg_from_tracepoint(tpval))) {
208
+ case RUBY_INTERNAL_EVENT_GC_ENTER: event_name = "gc"; break;
209
+ // TODO: is it possible the thread wasn't running? Might need to save the last state.
210
+ case RUBY_INTERNAL_EVENT_GC_EXIT: event_name = "running"; break;
211
+ }
212
+ render_event(event_name);
213
+ }
data/gvl-tracing.gemspec CHANGED
@@ -36,13 +36,13 @@ Gem::Specification.new do |spec|
36
36
  spec.summary = "Get a timeline view of Global VM Lock usage in your Ruby app"
37
37
  spec.homepage = "https://github.com/ivoanjo/gvl-tracing"
38
38
  spec.license = "MIT"
39
- spec.required_ruby_version = ">= 3.2.0.dev"
39
+ spec.required_ruby_version = ">= 3.2.0"
40
40
 
41
41
  # Specify which files should be added to the gem when it is released.
42
42
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
43
43
  spec.files = Dir.chdir(__dir__) do
44
44
  `git ls-files -z`.split("\x0").reject do |f|
45
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
45
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|examples)/|\.(?:git|travis|circleci)|appveyor)})
46
46
  end
47
47
  end
48
48
  spec.require_paths = ["lib", "ext"]
@@ -26,5 +26,5 @@
26
26
  # frozen_string_literal: true
27
27
 
28
28
  module GvlTracing
29
- VERSION = "0.1.1"
29
+ VERSION = "1.0.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: 1.0.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: 2023-02-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -24,8 +24,6 @@ files:
24
24
  - LICENSE
25
25
  - README.adoc
26
26
  - Rakefile
27
- - examples/example1.json.gz
28
- - examples/example1.rb
29
27
  - ext/gvl_tracing_native_extension/extconf.rb
30
28
  - ext/gvl_tracing_native_extension/gvl_tracing.c
31
29
  - gems.rb
@@ -46,14 +44,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
46
44
  requirements:
47
45
  - - ">="
48
46
  - !ruby/object:Gem::Version
49
- version: 3.2.0.dev
47
+ version: 3.2.0
50
48
  required_rubygems_version: !ruby/object:Gem::Requirement
51
49
  requirements:
52
50
  - - ">="
53
51
  - !ruby/object:Gem::Version
54
52
  version: '0'
55
53
  requirements: []
56
- rubygems_version: 3.4.0.dev
54
+ rubygems_version: 3.4.1
57
55
  signing_key:
58
56
  specification_version: 4
59
57
  summary: Get a timeline view of Global VM Lock usage in your Ruby app
Binary file
data/examples/example1.rb DELETED
@@ -1,18 +0,0 @@
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("example1.json")
9
-
10
- Thread.new { sleep(0.05) while true }
11
-
12
- sleep(0.05)
13
-
14
- 3.times.map { Thread.new { fib(37) } }.map(&:join)
15
-
16
- sleep(0.05)
17
-
18
- GvlTracing.stop