gvl-tracing 1.1.0 → 1.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: 1eedec6bc1503bdc8fabde77ac69167de69a9fe1cc8c211720ce7fa34d5fd66a
4
- data.tar.gz: 6e504dfe9f06f97b3247e00cf5cbc0fa17ffec39136474dfc5e9ff9914a10351
3
+ metadata.gz: 643b4b237db640d34ecf842459246640517ca9d58118ca891c76a629f1c6ab73
4
+ data.tar.gz: 10c2613b6b66681b46f7d32a143e5742ed98b969d9aca00e3ceb6b22c5579f4b
5
5
  SHA512:
6
- metadata.gz: 34fcfe11c2c1dca8307442e026420dea3a630b19c652164c9095143414226227e0198ae69b44f6e53ca3cf5325cd832e8a5450ce824ba7c5a99f37cb9e604300
7
- data.tar.gz: 01dc10ae34c5398889d531313f3faceda12bbe37890193cfd64560cd99f352149e3afea3d6ebb553e0d3116aa202bb34b9c7d403cbc0c368e272ed7053e6daad
6
+ metadata.gz: f3265c1719bd2dd7859f4df78a4b9096277b45f26b9a6bd515c5a3222f3bdb98e56461eef274f5987a61cfb7ec331ac44dac58a94fd8dadf7474a51b9033f3d6
7
+ data.tar.gz: 40a336509af944962fc2c48ae2f40c2ae40ff133b53b7e006f63f1a453e4106d68f6283e008462088f39afd3c5653556ef064fb6bf312aeffc09cd282ba90ba9
@@ -36,6 +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")
42
+ append_cflags("-Wall")
43
+ append_cflags("-Wextra")
44
+ append_cflags("-Werror") if ENV['ENABLE_WERROR'] == 'true'
39
45
 
40
46
  create_header
41
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,11 +144,11 @@ 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
 
150
- output_file == NULL;
151
+ output_file = NULL;
151
152
 
152
153
  return Qtrue;
153
154
  }
@@ -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,52 @@ 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
+ end
41
+
42
+ def stop
43
+ thread_list = Thread.list
44
+
45
+ _stop
46
+
47
+ append_thread_names(thread_list)
48
+ end
49
+
50
+ private
51
+
52
+ def append_thread_names(list)
53
+ threads_name = aggreate_thread_list(list).join(",\n")
54
+ File.open(@path, 'a') do |f|
55
+ f.puts(threads_name)
56
+ f.puts("]")
57
+ end
58
+ end
59
+
60
+ def aggreate_thread_list(list)
61
+ list.each_with_object([]) do |t, acc|
62
+ next unless t.name || t == Thread.main
63
+
64
+ acc << " {\"ph\": \"M\", \"pid\": #{Process.pid}, \"tid\": #{t.native_thread_id}, \"name\": \"thread_name\", \"args\": {\"name\": \"#{thread_label(t)}\"}}"
65
+ end
66
+ end
67
+
68
+ REGEX = /lib(?!.*lib)\/([a-zA-Z-]+)/
69
+ def thread_label(thread)
70
+ if thread == Thread.main
71
+ return thread.name ? thread.name : "Main Thread"
72
+ end
73
+
74
+ lib_name = thread.to_s.match(REGEX)
75
+
76
+ return thread.name if lib_name.nil?
77
+
78
+ "#{thread.name} from #{lib_name[1]}"
79
+ end
80
+ end
33
81
  end
@@ -26,5 +26,5 @@
26
26
  # frozen_string_literal: true
27
27
 
28
28
  module GvlTracing
29
- VERSION = "1.1.0"
29
+ VERSION = "1.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: 1.1.0
4
+ version: 1.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: 2023-02-16 00:00:00.000000000 Z
11
+ date: 2023-06-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -52,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  requirements: []
55
- rubygems_version: 3.4.1
55
+ rubygems_version: 3.4.6
56
56
  signing_key:
57
57
  specification_version: 4
58
58
  summary: Get a timeline view of Global VM Lock usage in your Ruby app