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 +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:
|