gvl-tracing 1.1.1 → 1.3.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: 2a0dac3d832e96a125efe05ac98e4b1129fd0ece7bdbc73dc27c01d07c1f63f8
4
- data.tar.gz: 91df78cab18be886527f64b273ed3f149ba75775f3bf9079270e6d59bb5b09b1
3
+ metadata.gz: a5ec59682543834d531647bea2601457de0d8f8d57fe039181375891ef29ae60
4
+ data.tar.gz: ebfa80876458868eb9780bdf655c75ff7c3b649f0661799533e6dcd12f5b06ea
5
5
  SHA512:
6
- metadata.gz: 2b98df0961029daa7ca1680e3a474afde760070216918c2514c76b50af8d62f9da1dcab18201b175e4e996b5bdac72dcd549df3601049a11d7ad636db9741783
7
- data.tar.gz: 967bfb4503e5553468598348829aa288eee2e4baac2c3b3f618a57c92cc51cb62ee5bdc5cc798238cf54bbc020d08cce85a5f1fb171e329dc5d6e1d32cc98c14
6
+ metadata.gz: 259ae5c91f2d750ed61ec938a3eb3f92fc813391393420648288626d824408f1678a534de2185c73a6ea8fc2a695aa46135f5529a8fa9bd32e00c0f878b5a538
7
+ data.tar.gz: ead5ce15933a39684675705a60983dbcd5fc8afb7752822103799261d05d5e3b3f6da8562ca496b9929f7bebf9ed203c31ccc5b778be59bf2c724f7bab9c4d49
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].
@@ -36,11 +36,12 @@ have_func("gettid", "unistd.h")
36
36
  have_header("pthread.h")
37
37
  have_func("pthread_getname_np", "pthread.h")
38
38
  have_func("pthread_threadid_np", "pthread.h")
39
- append_cflags('-Werror-implicit-function-declaration')
40
- append_cflags('-Wunused-parameter')
41
- append_cflags('-Wold-style-definition')
39
+ append_cflags("-Werror-implicit-function-declaration")
40
+ append_cflags("-Wunused-parameter")
41
+ append_cflags("-Wold-style-definition")
42
42
  append_cflags("-Wall")
43
43
  append_cflags("-Wextra")
44
+ append_cflags("-Werror") if ENV['ENABLE_WERROR'] == 'true'
44
45
 
45
46
  create_header
46
47
  create_makefile "gvl_tracing_native_extension"
@@ -28,6 +28,7 @@
28
28
  #include <ruby/thread.h>
29
29
  #include <ruby/atomic.h>
30
30
  #include <errno.h>
31
+ #include <inttypes.h>
31
32
  #include <stdbool.h>
32
33
  #include <sys/types.h>
33
34
 
@@ -41,23 +42,32 @@
41
42
  #include <unistd.h>
42
43
  #endif
43
44
 
45
+ // Used to mark function arguments that are deliberately left unused
46
+ #ifdef __GNUC__
47
+ #define UNUSED_ARG __attribute__((unused))
48
+ #else
49
+ #define UNUSED_ARG
50
+ #endif
51
+
44
52
  static VALUE tracing_start(VALUE _self, VALUE output_path);
45
53
  static VALUE tracing_stop(VALUE _self);
46
54
  static double timestamp_microseconds(void);
55
+ static void set_native_thread_id(void);
47
56
  static void render_event(const char *event_name);
48
57
  static void on_thread_event(rb_event_flag_t event, const rb_internal_thread_event_data_t *_unused1, void *_unused2);
49
58
  static void on_gc_event(VALUE tpval, void *_unused1);
50
59
 
51
60
  // Thread-local state
52
- static _Thread_local bool current_thread_serial_set = false;
61
+ static _Thread_local bool current_thread_seen = false;
53
62
  static _Thread_local unsigned int current_thread_serial = 0;
63
+ static _Thread_local uint64_t thread_id = 0;
54
64
 
55
65
  // Global mutable state
56
66
  static rb_atomic_t thread_serial = 0;
57
67
  static FILE *output_file = NULL;
58
68
  static rb_internal_thread_event_hook_t *current_hook = NULL;
59
69
  static double started_tracing_at_microseconds = 0;
60
- static pid_t process_id = 0;
70
+ static int64_t process_id = 0;
61
71
  static VALUE gc_tracepoint = Qnil;
62
72
 
63
73
  void Init_gvl_tracing_native_extension(void) {
@@ -65,25 +75,17 @@ void Init_gvl_tracing_native_extension(void) {
65
75
 
66
76
  VALUE gvl_tracing_module = rb_define_module("GvlTracing");
67
77
 
68
- rb_define_singleton_method(gvl_tracing_module, "start", tracing_start, 1);
69
- rb_define_singleton_method(gvl_tracing_module, "stop", tracing_stop, 0);
78
+ rb_define_singleton_method(gvl_tracing_module, "_start", tracing_start, 1);
79
+ rb_define_singleton_method(gvl_tracing_module, "_stop", tracing_stop, 0);
70
80
  }
71
81
 
72
82
  static inline void initialize_thread_id(void) {
73
- current_thread_serial_set = true;
83
+ current_thread_seen = true;
74
84
  current_thread_serial = RUBY_ATOMIC_FETCH_ADD(thread_serial, 1);
85
+ set_native_thread_id();
75
86
  }
76
87
 
77
88
  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
89
  char native_thread_name_buffer[64] = "(unnamed)";
88
90
 
89
91
  #ifdef HAVE_PTHREAD_GETNAME_NP
@@ -91,12 +93,11 @@ static inline void render_thread_metadata(void) {
91
93
  #endif
92
94
 
93
95
  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
- );
96
+ " {\"ph\": \"M\", \"pid\": %"PRId64", \"tid\": %"PRIu64", \"name\": \"thread_name\", \"args\": {\"name\": \"%s\"}},\n",
97
+ process_id, thread_id, native_thread_name_buffer);
97
98
  }
98
99
 
99
- static VALUE tracing_start(VALUE _self, VALUE output_path) {
100
+ static VALUE tracing_start(UNUSED_ARG VALUE _self, VALUE output_path) {
100
101
  Check_Type(output_path, T_STRING);
101
102
 
102
103
  if (output_file != NULL) rb_raise(rb_eRuntimeError, "Already started");
@@ -135,7 +136,7 @@ static VALUE tracing_start(VALUE _self, VALUE output_path) {
135
136
  return Qtrue;
136
137
  }
137
138
 
138
- static VALUE tracing_stop(VALUE _self) {
139
+ static VALUE tracing_stop(UNUSED_ARG VALUE _self) {
139
140
  if (output_file == NULL) rb_raise(rb_eRuntimeError, "Tracing not running");
140
141
 
141
142
  rb_internal_thread_remove_event_hook(current_hook);
@@ -143,7 +144,7 @@ static VALUE tracing_stop(VALUE _self) {
143
144
  gc_tracepoint = Qnil;
144
145
 
145
146
  render_event("stopped_tracing");
146
- fprintf(output_file, "]\n");
147
+ // closing the json syntax in the output file is handled in GvlTracing.stop code
147
148
 
148
149
  if (fclose(output_file) != 0) rb_syserr_fail(errno, "Failed to close GvlTracing output file");
149
150
 
@@ -158,19 +159,31 @@ static double timestamp_microseconds(void) {
158
159
  return (current_monotonic.tv_nsec / 1000.0) + (current_monotonic.tv_sec * 1000.0 * 1000.0);
159
160
  }
160
161
 
162
+ static void set_native_thread_id(void) {
163
+ uint64_t native_thread_id = 0;
164
+
165
+ #ifdef HAVE_PTHREAD_THREADID_NP
166
+ pthread_threadid_np(pthread_self(), &native_thread_id);
167
+ #elif HAVE_GETTID
168
+ native_thread_id = gettid();
169
+ #else
170
+ native_thread_id = current_thread_serial; // TODO: Better fallback for Windows?
171
+ #endif
172
+
173
+ thread_id = native_thread_id;
174
+ }
175
+
161
176
  // Render output using trace event format for perfetto:
162
177
  // https://chromium.googlesource.com/catapult/+/refs/heads/main/docs/trace-event-format.md
163
178
  static void render_event(const char *event_name) {
164
179
  // Event data
165
180
  double now_microseconds = timestamp_microseconds() - started_tracing_at_microseconds;
166
181
 
167
- if (!current_thread_serial_set) {
182
+ if (!current_thread_seen) {
168
183
  initialize_thread_id();
169
184
  render_thread_metadata();
170
185
  }
171
186
 
172
- unsigned int thread_id = current_thread_serial;
173
-
174
187
  // Each event is converted into two events in the output: one that signals the end of the previous event
175
188
  // (whatever it was), and one that signals the start of the actual event we're processing.
176
189
  // Yes, this seems to be slightly bending the intention of the output format, but it seemed easier to do this way.
@@ -180,9 +193,9 @@ static void render_event(const char *event_name) {
180
193
 
181
194
  fprintf(output_file,
182
195
  // Finish previous duration
183
- " {\"ph\": \"E\", \"pid\": %u, \"tid\": %u, \"ts\": %f},\n" \
196
+ " {\"ph\": \"E\", \"pid\": %"PRId64", \"tid\": %"PRIu64", \"ts\": %f},\n" \
184
197
  // Current event
185
- " {\"ph\": \"B\", \"pid\": %u, \"tid\": %u, \"ts\": %f, \"name\": \"%s\"},\n",
198
+ " {\"ph\": \"B\", \"pid\": %"PRId64", \"tid\": %"PRIu64", \"ts\": %f, \"name\": \"%s\"},\n",
186
199
  // Args for first line
187
200
  process_id, thread_id, now_microseconds,
188
201
  // Args for second line
@@ -190,7 +203,7 @@ static void render_event(const char *event_name) {
190
203
  );
191
204
  }
192
205
 
193
- static void on_thread_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *_unused1, void *_unused2) {
206
+ 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) {
194
207
  const char* event_name = "bug_unknown_event";
195
208
  switch (event_id) {
196
209
  case RUBY_INTERNAL_THREAD_EVENT_READY: event_name = "wants_gvl"; break;
@@ -202,7 +215,7 @@ static void on_thread_event(rb_event_flag_t event_id, const rb_internal_thread_e
202
215
  render_event(event_name);
203
216
  }
204
217
 
205
- static void on_gc_event(VALUE tpval, void *_unused1) {
218
+ static void on_gc_event(VALUE tpval, UNUSED_ARG void *_unused1) {
206
219
  const char* event_name = "bug_unknown_event";
207
220
  switch (rb_tracearg_event_flag(rb_tracearg_from_tracepoint(tpval))) {
208
221
  case RUBY_INTERNAL_EVENT_GC_ENTER: event_name = "gc"; break;
data/gems.rb CHANGED
@@ -8,3 +8,4 @@ gem "rake", "~> 13.0"
8
8
  gem "rake-compiler", "~> 1.2"
9
9
  gem "pry"
10
10
  gem "standard", "~> 1.12"
11
+ gem "concurrent-ruby"
data/lib/gvl-tracing.rb CHANGED
@@ -30,4 +30,60 @@ require_relative "gvl_tracing/version"
30
30
  require "gvl_tracing_native_extension"
31
31
 
32
32
  module GvlTracing
33
+ class << self
34
+ private :_start
35
+ private :_stop
36
+
37
+ def start(file)
38
+ _start(file)
39
+ @path = file
40
+
41
+ return unless block_given?
42
+
43
+ begin
44
+ yield
45
+ ensure
46
+ _stop
47
+ end
48
+ end
49
+
50
+ def stop
51
+ thread_list = Thread.list
52
+
53
+ _stop
54
+
55
+ append_thread_names(thread_list)
56
+ end
57
+
58
+ private
59
+
60
+ def append_thread_names(list)
61
+ threads_name = aggreate_thread_list(list).join(",\n")
62
+ File.open(@path, 'a') do |f|
63
+ f.puts(threads_name)
64
+ f.puts("]")
65
+ end
66
+ end
67
+
68
+ def aggreate_thread_list(list)
69
+ list.each_with_object([]) do |t, acc|
70
+ next unless t.name || t == Thread.main
71
+
72
+ acc << " {\"ph\": \"M\", \"pid\": #{Process.pid}, \"tid\": #{t.native_thread_id}, \"name\": \"thread_name\", \"args\": {\"name\": \"#{thread_label(t)}\"}}"
73
+ end
74
+ end
75
+
76
+ REGEX = /lib(?!.*lib)\/([a-zA-Z-]+)/
77
+ def thread_label(thread)
78
+ if thread == Thread.main
79
+ return thread.name ? thread.name : "Main Thread"
80
+ end
81
+
82
+ lib_name = thread.to_s.match(REGEX)
83
+
84
+ return thread.name if lib_name.nil?
85
+
86
+ "#{thread.name} from #{lib_name[1]}"
87
+ end
88
+ end
33
89
  end
@@ -26,5 +26,5 @@
26
26
  # frozen_string_literal: true
27
27
 
28
28
  module GvlTracing
29
- VERSION = "1.1.1"
29
+ VERSION = "1.3.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.1.1
4
+ version: 1.3.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-03-15 00:00:00.000000000 Z
11
+ date: 2023-07-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: