gvl-tracing 1.1.1 → 1.3.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: 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: