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 +4 -4
- data/README.adoc +7 -9
- data/ext/gvl_tracing_native_extension/extconf.rb +4 -3
- data/ext/gvl_tracing_native_extension/gvl_tracing.c +40 -27
- data/gems.rb +1 -0
- data/lib/gvl-tracing.rb +56 -0
- data/lib/gvl_tracing/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5ec59682543834d531647bea2601457de0d8f8d57fe039181375891ef29ae60
|
4
|
+
data.tar.gz: ebfa80876458868eb9780bdf655c75ff7c3b649f0661799533e6dcd12f5b06ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
31
|
+
sleep(0.05)
|
31
32
|
|
32
|
-
|
33
|
+
3.times.map { Thread.new { fib(37) } }.map(&:join)
|
33
34
|
|
34
|
-
|
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(
|
40
|
-
append_cflags(
|
41
|
-
append_cflags(
|
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
|
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
|
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, "
|
69
|
-
rb_define_singleton_method(gvl_tracing_module, "
|
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
|
-
|
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\": %
|
95
|
-
process_id,
|
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
|
-
|
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 (!
|
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\": %
|
196
|
+
" {\"ph\": \"E\", \"pid\": %"PRId64", \"tid\": %"PRIu64", \"ts\": %f},\n" \
|
184
197
|
// Current event
|
185
|
-
" {\"ph\": \"B\", \"pid\": %
|
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
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
|
data/lib/gvl_tracing/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2023-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|