fiber-profiler 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/ext/extconf.rb +37 -0
- data/ext/fiber/profiler/capture.c +670 -0
- data/ext/fiber/profiler/capture.h +12 -0
- data/ext/fiber/profiler/deque.h +272 -0
- data/ext/fiber/profiler/fiber.c +27 -0
- data/ext/fiber/profiler/fiber.h +16 -0
- data/ext/fiber/profiler/profiler.c +20 -0
- data/ext/fiber/profiler/profiler.h +8 -0
- data/ext/fiber/profiler/time.c +35 -0
- data/ext/fiber/profiler/time.h +17 -0
- data/lib/fiber/profiler/capture.rb +6 -0
- data/lib/fiber/profiler/native.rb +6 -0
- data/lib/fiber/profiler/version.rb +12 -0
- data/lib/fiber/profiler.rb +35 -0
- data/license.md +21 -0
- data/readme.md +41 -0
- data/releases.md +5 -0
- data.tar.gz.sig +3 -0
- metadata +86 -0
- metadata.gz.sig +3 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 242de388a621c02117a98ee14496714ce82c3ad8d8770a891789536fe4dc8fad
|
4
|
+
data.tar.gz: a911793eb422ac5e0092789263c58564e71831d769bf907d4db489777fbebdf6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e3b5be8bb62d92f11ae5262a526a351092920d2a64761b5a5ce2b38a2bae05edc4eed57283d0031f7e44c28e94427688ab602fca2e303fe0ec6c38f0a7ef68e1
|
7
|
+
data.tar.gz: e959a0bf539b76cea581a42842e8c08541d9b75b51c6cebbb85f18eb5ad7b15ce5d361f13a1c7466ff0053511c736c556ae22468263148279a10a10148193e1b
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Released under the MIT License.
|
5
|
+
# Copyright, 2025, by Samuel Williams.
|
6
|
+
|
7
|
+
require "mkmf"
|
8
|
+
|
9
|
+
gem_name = File.basename(__dir__)
|
10
|
+
extension_name = "Fiber_Profiler"
|
11
|
+
|
12
|
+
$CFLAGS << " -Wall -Wno-unknown-pragmas -std=c99"
|
13
|
+
|
14
|
+
if ENV.key?("RUBY_DEBUG")
|
15
|
+
$stderr.puts "Enabling debug mode..."
|
16
|
+
|
17
|
+
$CFLAGS << " -DRUBY_DEBUG -O0"
|
18
|
+
end
|
19
|
+
|
20
|
+
$srcs = ["fiber/profiler/profiler.c", "fiber/profiler/time.c", "fiber/profiler/fiber.c", "fiber/profiler/capture.c"]
|
21
|
+
$VPATH << "$(srcdir)/fiber/profiler"
|
22
|
+
|
23
|
+
have_func("rb_fiber_current")
|
24
|
+
have_func("rb_ext_ractor_safe")
|
25
|
+
|
26
|
+
if ENV.key?("RUBY_SANITIZE")
|
27
|
+
$stderr.puts "Enabling sanitizers..."
|
28
|
+
|
29
|
+
# Add address and undefined behaviour sanitizers:
|
30
|
+
$CFLAGS << " -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer"
|
31
|
+
$LDFLAGS << " -fsanitize=address -fsanitize=undefined"
|
32
|
+
end
|
33
|
+
|
34
|
+
create_header
|
35
|
+
|
36
|
+
# Generate the makefile to compile the native binary into `lib`:
|
37
|
+
create_makefile(extension_name)
|
@@ -0,0 +1,670 @@
|
|
1
|
+
// Released under the MIT License.
|
2
|
+
// Copyright, 2025, by Samuel Williams.
|
3
|
+
|
4
|
+
#include "profiler.h"
|
5
|
+
|
6
|
+
#include "time.h"
|
7
|
+
#include "fiber.h"
|
8
|
+
#include "deque.h"
|
9
|
+
|
10
|
+
#include <stdio.h>
|
11
|
+
#include <ruby/io.h>
|
12
|
+
#include <ruby/debug.h>
|
13
|
+
#include <stdio.h>
|
14
|
+
|
15
|
+
enum {
|
16
|
+
DEBUG_SKIPPED = 1,
|
17
|
+
};
|
18
|
+
|
19
|
+
int Fiber_Profiler_capture_p = 0;
|
20
|
+
double Fiber_Profiler_Capture_stall_threshold = 0.01;
|
21
|
+
int Fiber_Profiler_Capture_track_calls = 1;
|
22
|
+
double Fiber_Profiler_Capture_sample_rate = 1;
|
23
|
+
|
24
|
+
VALUE Fiber_Profiler_Capture = Qnil;
|
25
|
+
|
26
|
+
struct Fiber_Profiler_Capture_Call {
|
27
|
+
struct timespec enter_time;
|
28
|
+
struct timespec exit_time;
|
29
|
+
|
30
|
+
size_t nesting;
|
31
|
+
|
32
|
+
rb_event_flag_t event_flag;
|
33
|
+
ID id;
|
34
|
+
|
35
|
+
VALUE klass;
|
36
|
+
const char *path;
|
37
|
+
int line;
|
38
|
+
|
39
|
+
struct Fiber_Profiler_Capture_Call *parent;
|
40
|
+
};
|
41
|
+
|
42
|
+
struct Fiber_Profiler_Stream {
|
43
|
+
FILE *file;
|
44
|
+
char *buffer;
|
45
|
+
size_t size;
|
46
|
+
};
|
47
|
+
|
48
|
+
void Fiber_Profiler_Stream_initialize(struct Fiber_Profiler_Stream *stream) {
|
49
|
+
stream->file = open_memstream(&stream->buffer, &stream->size);
|
50
|
+
}
|
51
|
+
|
52
|
+
void Fiber_Profiler_Stream_free(struct Fiber_Profiler_Stream *stream) {
|
53
|
+
if (stream->file) {
|
54
|
+
fclose(stream->file);
|
55
|
+
stream->file = NULL;
|
56
|
+
}
|
57
|
+
|
58
|
+
if (stream->buffer) {
|
59
|
+
free(stream->buffer);
|
60
|
+
stream->buffer = NULL;
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
struct Fiber_Profiler_Capture;
|
65
|
+
typedef void(*Fiber_Profiler_Stream_Print)(struct Fiber_Profiler_Capture*, FILE* restrict);
|
66
|
+
|
67
|
+
struct Fiber_Profiler_Capture {
|
68
|
+
// Configuration:
|
69
|
+
double stall_threshold;
|
70
|
+
int track_calls;
|
71
|
+
double sample_rate;
|
72
|
+
|
73
|
+
// Calls that are shorter than this filter threshold will be ignored:
|
74
|
+
double filter_threshold;
|
75
|
+
|
76
|
+
VALUE output;
|
77
|
+
Fiber_Profiler_Stream_Print print;
|
78
|
+
struct Fiber_Profiler_Stream stream;
|
79
|
+
|
80
|
+
// Whether or not the profiler is currently running:
|
81
|
+
int running;
|
82
|
+
|
83
|
+
// Whether or not to capture call data:
|
84
|
+
int capture;
|
85
|
+
|
86
|
+
size_t stalls;
|
87
|
+
|
88
|
+
// From this point on, the state of any profile in progress:
|
89
|
+
struct timespec start_time;
|
90
|
+
struct timespec stop_time;
|
91
|
+
|
92
|
+
// The depth of the call stack:
|
93
|
+
size_t nesting;
|
94
|
+
|
95
|
+
// The current call frame:
|
96
|
+
struct Fiber_Profiler_Capture_Call *current;
|
97
|
+
|
98
|
+
struct Fiber_Profiler_Deque calls;
|
99
|
+
};
|
100
|
+
|
101
|
+
void Fiber_Profiler_Capture_reset(struct Fiber_Profiler_Capture *profiler) {
|
102
|
+
profiler->nesting = 0;
|
103
|
+
profiler->current = NULL;
|
104
|
+
Fiber_Profiler_Deque_truncate(&profiler->calls);
|
105
|
+
}
|
106
|
+
|
107
|
+
void Fiber_Profiler_Capture_Call_initialize(void *element) {
|
108
|
+
struct Fiber_Profiler_Capture_Call *call = element;
|
109
|
+
|
110
|
+
call->enter_time.tv_sec = 0;
|
111
|
+
call->enter_time.tv_nsec = 0;
|
112
|
+
call->exit_time.tv_sec = 0;
|
113
|
+
call->exit_time.tv_nsec = 0;
|
114
|
+
|
115
|
+
call->nesting = 0;
|
116
|
+
|
117
|
+
call->event_flag = 0;
|
118
|
+
call->id = 0;
|
119
|
+
|
120
|
+
call->path = NULL;
|
121
|
+
call->line = 0;
|
122
|
+
}
|
123
|
+
|
124
|
+
void Fiber_Profiler_Capture_Call_free(void *element) {
|
125
|
+
struct Fiber_Profiler_Capture_Call *call = element;
|
126
|
+
|
127
|
+
if (call->path) {
|
128
|
+
free((void*)call->path);
|
129
|
+
call->path = NULL;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
static void Fiber_Profiler_Capture_mark(void *ptr) {
|
134
|
+
struct Fiber_Profiler_Capture *profiler = (struct Fiber_Profiler_Capture*)ptr;
|
135
|
+
|
136
|
+
rb_gc_mark_movable(profiler->output);
|
137
|
+
|
138
|
+
// If `klass` is stored as a VALUE in calls, we need to mark them here:
|
139
|
+
Fiber_Profiler_Deque_each(&profiler->calls, struct Fiber_Profiler_Capture_Call, call) {
|
140
|
+
rb_gc_mark_movable(call->klass);
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
static void Fiber_Profiler_Capture_compact(void *ptr) {
|
145
|
+
struct Fiber_Profiler_Capture *profiler = (struct Fiber_Profiler_Capture*)ptr;
|
146
|
+
|
147
|
+
profiler->output = rb_gc_location(profiler->output);
|
148
|
+
|
149
|
+
// If `klass` is stored as a VALUE in calls, we need to update their locations here:
|
150
|
+
Fiber_Profiler_Deque_each(&profiler->calls, struct Fiber_Profiler_Capture_Call, call) {
|
151
|
+
call->klass = rb_gc_location(call->klass);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
static void Fiber_Profiler_Capture_free(void *ptr) {
|
156
|
+
struct Fiber_Profiler_Capture *profiler = (struct Fiber_Profiler_Capture*)ptr;
|
157
|
+
|
158
|
+
RUBY_ASSERT(profiler->running == 0);
|
159
|
+
|
160
|
+
Fiber_Profiler_Stream_free(&profiler->stream);
|
161
|
+
Fiber_Profiler_Deque_free(&profiler->calls);
|
162
|
+
|
163
|
+
free(profiler);
|
164
|
+
}
|
165
|
+
|
166
|
+
static size_t Fiber_Profiler_Capture_memsize(const void *ptr) {
|
167
|
+
const struct Fiber_Profiler_Capture *profiler = (const struct Fiber_Profiler_Capture*)ptr;
|
168
|
+
return sizeof(*profiler) + Fiber_Profiler_Deque_memory_size(&profiler->calls);
|
169
|
+
}
|
170
|
+
|
171
|
+
const rb_data_type_t Fiber_Profiler_Capture_Type = {
|
172
|
+
.wrap_struct_name = "IO::Event::Profiler",
|
173
|
+
.function = {
|
174
|
+
.dmark = Fiber_Profiler_Capture_mark,
|
175
|
+
.dcompact = Fiber_Profiler_Capture_compact,
|
176
|
+
.dfree = Fiber_Profiler_Capture_free,
|
177
|
+
.dsize = Fiber_Profiler_Capture_memsize,
|
178
|
+
},
|
179
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
|
180
|
+
};
|
181
|
+
|
182
|
+
struct Fiber_Profiler_Capture *Fiber_Profiler_Capture_get(VALUE self) {
|
183
|
+
struct Fiber_Profiler_Capture *profiler;
|
184
|
+
TypedData_Get_Struct(self, struct Fiber_Profiler_Capture, &Fiber_Profiler_Capture_Type, profiler);
|
185
|
+
return profiler;
|
186
|
+
}
|
187
|
+
|
188
|
+
int IO_istty(VALUE io) {
|
189
|
+
if (RB_TYPE_P(io, T_FILE)) {
|
190
|
+
int descriptor = rb_io_descriptor(io);
|
191
|
+
return isatty(descriptor);
|
192
|
+
}
|
193
|
+
|
194
|
+
return 0;
|
195
|
+
}
|
196
|
+
|
197
|
+
void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *profiler, FILE *restrict stream);
|
198
|
+
void Fiber_Profiler_Capture_print_json(struct Fiber_Profiler_Capture *profiler, FILE *restrict stream);
|
199
|
+
|
200
|
+
static void Fiber_Profiler_Capture_output_set(struct Fiber_Profiler_Capture *profiler, VALUE output) {
|
201
|
+
profiler->output = output;
|
202
|
+
|
203
|
+
if (IO_istty(profiler->output)) {
|
204
|
+
profiler->print = &Fiber_Profiler_Capture_print_tty;
|
205
|
+
} else {
|
206
|
+
profiler->print = &Fiber_Profiler_Capture_print_json;
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
VALUE Fiber_Profiler_Capture_allocate(VALUE klass) {
|
211
|
+
struct Fiber_Profiler_Capture *profiler = ALLOC(struct Fiber_Profiler_Capture);
|
212
|
+
|
213
|
+
// Initialize the profiler state:
|
214
|
+
Fiber_Profiler_Capture_output_set(profiler, rb_stderr);
|
215
|
+
Fiber_Profiler_Stream_initialize(&profiler->stream);
|
216
|
+
|
217
|
+
profiler->running = 0;
|
218
|
+
profiler->capture = 0;
|
219
|
+
profiler->stalls = 0;
|
220
|
+
profiler->nesting = 0;
|
221
|
+
profiler->current = NULL;
|
222
|
+
|
223
|
+
profiler->stall_threshold = Fiber_Profiler_Capture_stall_threshold;
|
224
|
+
profiler->track_calls = Fiber_Profiler_Capture_track_calls;
|
225
|
+
profiler->sample_rate = Fiber_Profiler_Capture_sample_rate;
|
226
|
+
|
227
|
+
// Filter calls that are less than 1% of the stall threshold:
|
228
|
+
profiler->filter_threshold = profiler->stall_threshold * 0.01;
|
229
|
+
|
230
|
+
profiler->calls.element_initialize = (void (*)(void*))Fiber_Profiler_Capture_Call_initialize;
|
231
|
+
profiler->calls.element_free = (void (*)(void*))Fiber_Profiler_Capture_Call_free;
|
232
|
+
Fiber_Profiler_Deque_initialize(&profiler->calls, sizeof(struct Fiber_Profiler_Capture_Call));
|
233
|
+
|
234
|
+
return TypedData_Wrap_Struct(klass, &Fiber_Profiler_Capture_Type, profiler);
|
235
|
+
}
|
236
|
+
|
237
|
+
ID Fiber_Profiler_Capture_initialize_options[4];
|
238
|
+
|
239
|
+
VALUE Fiber_Profiler_Capture_initialize(int argc, VALUE *argv, VALUE self) {
|
240
|
+
struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
|
241
|
+
|
242
|
+
VALUE arguments[4] = {0};
|
243
|
+
VALUE options = Qnil;
|
244
|
+
rb_scan_args(argc, argv, ":", &options);
|
245
|
+
rb_get_kwargs(options, Fiber_Profiler_Capture_initialize_options, 0, 4, arguments);
|
246
|
+
|
247
|
+
if (arguments[0] != Qundef) {
|
248
|
+
profiler->stall_threshold = NUM2DBL(arguments[0]);
|
249
|
+
}
|
250
|
+
|
251
|
+
if (arguments[1] != Qundef) {
|
252
|
+
profiler->track_calls = RB_TEST(arguments[1]);
|
253
|
+
}
|
254
|
+
|
255
|
+
if (arguments[2] != Qundef) {
|
256
|
+
profiler->sample_rate = NUM2DBL(arguments[2]);
|
257
|
+
}
|
258
|
+
|
259
|
+
if (arguments[3] != Qundef) {
|
260
|
+
Fiber_Profiler_Capture_output_set(profiler, arguments[3]);
|
261
|
+
}
|
262
|
+
|
263
|
+
return self;
|
264
|
+
}
|
265
|
+
|
266
|
+
VALUE Fiber_Profiler_Capture_default(VALUE klass) {
|
267
|
+
if (!Fiber_Profiler_capture_p) {
|
268
|
+
return Qnil;
|
269
|
+
}
|
270
|
+
|
271
|
+
VALUE profiler = Fiber_Profiler_Capture_allocate(klass);
|
272
|
+
Fiber_Profiler_Capture_initialize(0, NULL, profiler);
|
273
|
+
|
274
|
+
return profiler;
|
275
|
+
}
|
276
|
+
|
277
|
+
int event_flag_call_p(rb_event_flag_t event_flags) {
|
278
|
+
return event_flags & (RUBY_EVENT_CALL | RUBY_EVENT_C_CALL | RUBY_EVENT_B_CALL | RUBY_INTERNAL_EVENT_GC_START);
|
279
|
+
}
|
280
|
+
|
281
|
+
int event_flag_return_p(rb_event_flag_t event_flags) {
|
282
|
+
return event_flags & (RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN | RUBY_EVENT_B_RETURN | RUBY_INTERNAL_EVENT_GC_END_SWEEP);
|
283
|
+
}
|
284
|
+
|
285
|
+
const char *event_flag_name(rb_event_flag_t event_flag) {
|
286
|
+
switch (event_flag) {
|
287
|
+
case RUBY_EVENT_CALL: return "call";
|
288
|
+
case RUBY_EVENT_C_CALL: return "c-call";
|
289
|
+
case RUBY_EVENT_B_CALL: return "b-call";
|
290
|
+
case RUBY_EVENT_RETURN: return "return";
|
291
|
+
case RUBY_EVENT_C_RETURN: return "c-return";
|
292
|
+
case RUBY_EVENT_B_RETURN: return "b-return";
|
293
|
+
case RUBY_INTERNAL_EVENT_GC_START: return "gc-start";
|
294
|
+
case RUBY_INTERNAL_EVENT_GC_END_MARK: return "gc-end-mark";
|
295
|
+
case RUBY_INTERNAL_EVENT_GC_END_SWEEP: return "gc-end-sweep";
|
296
|
+
default: return "unknown";
|
297
|
+
}
|
298
|
+
}
|
299
|
+
|
300
|
+
static struct Fiber_Profiler_Capture_Call* profiler_event_record_call(struct Fiber_Profiler_Capture *profiler, rb_event_flag_t event_flag, ID id, VALUE klass) {
|
301
|
+
struct Fiber_Profiler_Capture_Call *call = Fiber_Profiler_Deque_push(&profiler->calls);
|
302
|
+
|
303
|
+
call->event_flag = event_flag;
|
304
|
+
|
305
|
+
call->parent = profiler->current;
|
306
|
+
profiler->current = call;
|
307
|
+
|
308
|
+
call->nesting = profiler->nesting;
|
309
|
+
profiler->nesting += 1;
|
310
|
+
|
311
|
+
if (id) {
|
312
|
+
call->id = id;
|
313
|
+
call->klass = klass;
|
314
|
+
} else {
|
315
|
+
rb_frame_method_id_and_class(&call->id, &call->klass);
|
316
|
+
}
|
317
|
+
|
318
|
+
const char *path = rb_sourcefile();
|
319
|
+
if (path) {
|
320
|
+
call->path = strdup(path);
|
321
|
+
}
|
322
|
+
call->line = rb_sourceline();
|
323
|
+
|
324
|
+
return call;
|
325
|
+
}
|
326
|
+
|
327
|
+
void Fiber_Profiler_Capture_fiber_switch(struct Fiber_Profiler_Capture *profiler);
|
328
|
+
|
329
|
+
static void Fiber_Profiler_Capture_callback(rb_event_flag_t event_flag, VALUE data, VALUE self, ID id, VALUE klass) {
|
330
|
+
struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(data);
|
331
|
+
|
332
|
+
if (event_flag & RUBY_EVENT_FIBER_SWITCH) {
|
333
|
+
Fiber_Profiler_Capture_fiber_switch(profiler);
|
334
|
+
return;
|
335
|
+
}
|
336
|
+
|
337
|
+
// We don't want to capture data if we're not running:
|
338
|
+
if (!profiler->capture) return;
|
339
|
+
|
340
|
+
if (event_flag_call_p(event_flag)) {
|
341
|
+
struct Fiber_Profiler_Capture_Call *call = profiler_event_record_call(profiler, event_flag, id, klass);
|
342
|
+
Fiber_Profiler_Time_current(&call->enter_time);
|
343
|
+
}
|
344
|
+
|
345
|
+
else if (event_flag_return_p(event_flag)) {
|
346
|
+
struct Fiber_Profiler_Capture_Call *call = profiler->current;
|
347
|
+
|
348
|
+
// We may encounter returns without a preceeding call. This isn't an error, but we should pretend like the call started at the beginning of the profiling session:
|
349
|
+
if (call == NULL) {
|
350
|
+
struct Fiber_Profiler_Capture_Call *last_call = Fiber_Profiler_Deque_last(&profiler->calls);
|
351
|
+
call = profiler_event_record_call(profiler, event_flag, id, klass);
|
352
|
+
|
353
|
+
if (last_call) {
|
354
|
+
call->enter_time = last_call->enter_time;
|
355
|
+
} else {
|
356
|
+
call->enter_time = profiler->start_time;
|
357
|
+
}
|
358
|
+
}
|
359
|
+
|
360
|
+
Fiber_Profiler_Time_current(&call->exit_time);
|
361
|
+
|
362
|
+
profiler->current = call->parent;
|
363
|
+
|
364
|
+
// We may encounter returns without a preceeding call.
|
365
|
+
if (profiler->nesting > 0)
|
366
|
+
profiler->nesting -= 1;
|
367
|
+
|
368
|
+
// If the call was < 1% of the stall threshold, we can ignore it:
|
369
|
+
double duration = Fiber_Profiler_Time_delta(&call->enter_time, &call->exit_time);
|
370
|
+
if (duration < profiler->filter_threshold) {
|
371
|
+
Fiber_Profiler_Deque_pop(&profiler->calls);
|
372
|
+
}
|
373
|
+
}
|
374
|
+
}
|
375
|
+
|
376
|
+
VALUE Fiber_Profiler_Capture_start(VALUE self) {
|
377
|
+
struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
|
378
|
+
|
379
|
+
if (profiler->running) return Qfalse;
|
380
|
+
|
381
|
+
profiler->running = 1;
|
382
|
+
|
383
|
+
Fiber_Profiler_Capture_reset(profiler);
|
384
|
+
Fiber_Profiler_Time_current(&profiler->start_time);
|
385
|
+
|
386
|
+
rb_event_flag_t event_flags = RUBY_EVENT_FIBER_SWITCH;
|
387
|
+
|
388
|
+
if (profiler->track_calls) {
|
389
|
+
event_flags |= RUBY_EVENT_CALL | RUBY_EVENT_RETURN;
|
390
|
+
event_flags |= RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN;
|
391
|
+
// event_flags |= RUBY_EVENT_B_CALL | RUBY_EVENT_B_RETURN;
|
392
|
+
}
|
393
|
+
|
394
|
+
VALUE thread = rb_thread_current();
|
395
|
+
rb_thread_add_event_hook(thread, Fiber_Profiler_Capture_callback, event_flags, self);
|
396
|
+
|
397
|
+
// if (profiler->track_garbage_collection) {
|
398
|
+
rb_thread_add_event_hook(thread, Fiber_Profiler_Capture_callback, RUBY_INTERNAL_EVENT_GC_START | RUBY_INTERNAL_EVENT_GC_END_SWEEP, self);
|
399
|
+
// }
|
400
|
+
|
401
|
+
return self;
|
402
|
+
}
|
403
|
+
|
404
|
+
VALUE Fiber_Profiler_Capture_stop(VALUE self) {
|
405
|
+
struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
|
406
|
+
|
407
|
+
if (!profiler->running) return Qfalse;
|
408
|
+
|
409
|
+
profiler->running = 0;
|
410
|
+
|
411
|
+
VALUE thread = rb_thread_current();
|
412
|
+
rb_thread_remove_event_hook_with_data(thread, Fiber_Profiler_Capture_callback, self);
|
413
|
+
|
414
|
+
Fiber_Profiler_Time_current(&profiler->stop_time);
|
415
|
+
Fiber_Profiler_Capture_reset(profiler);
|
416
|
+
|
417
|
+
return self;
|
418
|
+
}
|
419
|
+
|
420
|
+
static inline float Fiber_Profiler_Capture_duration(struct Fiber_Profiler_Capture *profiler) {
|
421
|
+
struct timespec duration;
|
422
|
+
|
423
|
+
Fiber_Profiler_Time_current(&profiler->stop_time);
|
424
|
+
Fiber_Profiler_Time_elapsed(&profiler->start_time, &profiler->stop_time, &duration);
|
425
|
+
|
426
|
+
return Fiber_Profiler_Time_duration(&duration);
|
427
|
+
}
|
428
|
+
|
429
|
+
void Fiber_Profiler_Capture_finish(struct Fiber_Profiler_Capture *profiler) {
|
430
|
+
profiler->capture = 0;
|
431
|
+
|
432
|
+
struct Fiber_Profiler_Capture_Call *current = profiler->current;
|
433
|
+
while (current) {
|
434
|
+
Fiber_Profiler_Time_current(¤t->exit_time);
|
435
|
+
|
436
|
+
current = current->parent;
|
437
|
+
}
|
438
|
+
}
|
439
|
+
|
440
|
+
void Fiber_Profiler_Capture_print(struct Fiber_Profiler_Capture *profiler);
|
441
|
+
|
442
|
+
int Fiber_Profiler_Capture_sample(struct Fiber_Profiler_Capture *profiler) {
|
443
|
+
VALUE fiber = Fiber_Profiler_Fiber_current();
|
444
|
+
|
445
|
+
// We don't want to capture data from blocking fibers:
|
446
|
+
if (Fiber_Profiler_Fiber_blocking(fiber)) return 0;
|
447
|
+
|
448
|
+
if (profiler->sample_rate < 1) {
|
449
|
+
return rand() < (RAND_MAX * profiler->sample_rate);
|
450
|
+
} else {
|
451
|
+
return 1;
|
452
|
+
}
|
453
|
+
}
|
454
|
+
|
455
|
+
void Fiber_Profiler_Capture_fiber_switch(struct Fiber_Profiler_Capture *profiler)
|
456
|
+
{
|
457
|
+
float duration = Fiber_Profiler_Capture_duration(profiler);
|
458
|
+
|
459
|
+
if (profiler->capture) {
|
460
|
+
Fiber_Profiler_Capture_finish(profiler);
|
461
|
+
|
462
|
+
if (duration > profiler->stall_threshold) {
|
463
|
+
profiler->stalls += 1;
|
464
|
+
Fiber_Profiler_Capture_print(profiler);
|
465
|
+
}
|
466
|
+
}
|
467
|
+
|
468
|
+
Fiber_Profiler_Capture_reset(profiler);
|
469
|
+
|
470
|
+
if (Fiber_Profiler_Capture_sample(profiler)) {
|
471
|
+
// Reset the start time:
|
472
|
+
Fiber_Profiler_Time_current(&profiler->start_time);
|
473
|
+
|
474
|
+
profiler->capture = 1;
|
475
|
+
}
|
476
|
+
}
|
477
|
+
|
478
|
+
static const float Fiber_Profiler_Capture_PRINT_MINIMUM_PROPORTION = 0.01;
|
479
|
+
|
480
|
+
void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *profiler, FILE *restrict stream) {
|
481
|
+
struct timespec total_duration = {};
|
482
|
+
Fiber_Profiler_Time_elapsed(&profiler->start_time, &profiler->stop_time, &total_duration);
|
483
|
+
|
484
|
+
fprintf(stderr, "Fiber stalled for %.3f seconds\n", Fiber_Profiler_Time_duration(&total_duration));
|
485
|
+
|
486
|
+
size_t skipped = 0;
|
487
|
+
|
488
|
+
Fiber_Profiler_Deque_each(&profiler->calls, struct Fiber_Profiler_Capture_Call, call) {
|
489
|
+
struct timespec duration = {};
|
490
|
+
Fiber_Profiler_Time_elapsed(&call->enter_time, &call->exit_time, &duration);
|
491
|
+
|
492
|
+
// Skip calls that are too short to be meaningful:
|
493
|
+
if (Fiber_Profiler_Time_proportion(&duration, &total_duration) < Fiber_Profiler_Capture_PRINT_MINIMUM_PROPORTION) {
|
494
|
+
|
495
|
+
if (!DEBUG_SKIPPED) {
|
496
|
+
skipped += 1;
|
497
|
+
continue;
|
498
|
+
} else {
|
499
|
+
fprintf(stream, "\e[2m");
|
500
|
+
}
|
501
|
+
}
|
502
|
+
|
503
|
+
for (size_t i = 0; i < call->nesting; i += 1) {
|
504
|
+
fputc('\t', stream);
|
505
|
+
}
|
506
|
+
|
507
|
+
VALUE class_inspect = rb_inspect(call->klass);
|
508
|
+
const char *name = rb_id2name(call->id);
|
509
|
+
|
510
|
+
fprintf(stream, "%s:%d in %s '%s#%s' (" Fiber_Profiler_TIME_PRINTF_TIMESPEC "s)\n", call->path, call->line, event_flag_name(call->event_flag), RSTRING_PTR(class_inspect), name, Fiber_Profiler_TIME_PRINTF_TIMESPEC_ARGUMENTS(duration));
|
511
|
+
|
512
|
+
if (DEBUG_SKIPPED) {
|
513
|
+
fprintf(stream, "\e[0m");
|
514
|
+
}
|
515
|
+
}
|
516
|
+
|
517
|
+
if (skipped > 0) {
|
518
|
+
fprintf(stream, "Skipped %zu calls that were too short to be meaningful.\n", skipped);
|
519
|
+
}
|
520
|
+
}
|
521
|
+
|
522
|
+
void Fiber_Profiler_Capture_print_json(struct Fiber_Profiler_Capture *profiler, FILE *restrict stream) {
|
523
|
+
struct timespec total_duration = {};
|
524
|
+
Fiber_Profiler_Time_elapsed(&profiler->start_time, &profiler->stop_time, &total_duration);
|
525
|
+
|
526
|
+
fputc('{', stream);
|
527
|
+
|
528
|
+
fprintf(stream, "\"duration\":" Fiber_Profiler_TIME_PRINTF_TIMESPEC, Fiber_Profiler_TIME_PRINTF_TIMESPEC_ARGUMENTS(total_duration));
|
529
|
+
|
530
|
+
size_t skipped = 0;
|
531
|
+
|
532
|
+
fprintf(stream, ",\"calls\":[");
|
533
|
+
int first = 1;
|
534
|
+
|
535
|
+
Fiber_Profiler_Deque_each(&profiler->calls, struct Fiber_Profiler_Capture_Call, call) {
|
536
|
+
struct timespec duration = {};
|
537
|
+
Fiber_Profiler_Time_elapsed(&call->enter_time, &call->exit_time, &duration);
|
538
|
+
|
539
|
+
// Skip calls that are too short to be meaningful:
|
540
|
+
if (Fiber_Profiler_Time_proportion(&duration, &total_duration) < Fiber_Profiler_Capture_PRINT_MINIMUM_PROPORTION) {
|
541
|
+
skipped += 1;
|
542
|
+
continue;
|
543
|
+
}
|
544
|
+
|
545
|
+
VALUE class_inspect = rb_inspect(call->klass);
|
546
|
+
const char *name = rb_id2name(call->id);
|
547
|
+
|
548
|
+
fprintf(stream, "%s{\"path\":\"%s\",\"line\":%d,\"class\":\"%s\",\"method\":\"%s\",\"duration\":" Fiber_Profiler_TIME_PRINTF_TIMESPEC ",\"nesting\":%zu}", first ? "" : ",", call->path, call->line, RSTRING_PTR(class_inspect), name, Fiber_Profiler_TIME_PRINTF_TIMESPEC_ARGUMENTS(duration), call->nesting);
|
549
|
+
|
550
|
+
first = 0;
|
551
|
+
}
|
552
|
+
|
553
|
+
fprintf(stream, "]");
|
554
|
+
|
555
|
+
if (skipped > 0) {
|
556
|
+
fprintf(stream, ",\"skipped\":%zu", skipped);
|
557
|
+
}
|
558
|
+
|
559
|
+
fprintf(stream, "}\n");
|
560
|
+
}
|
561
|
+
|
562
|
+
void Fiber_Profiler_Capture_print(struct Fiber_Profiler_Capture *profiler) {
|
563
|
+
FILE *stream = profiler->stream.file;
|
564
|
+
profiler->print(profiler, stream);
|
565
|
+
fflush(stream);
|
566
|
+
|
567
|
+
rb_io_write(profiler->output, rb_str_new_static(profiler->stream.buffer, profiler->stream.size));
|
568
|
+
|
569
|
+
fseek(stream, 0, SEEK_SET);
|
570
|
+
}
|
571
|
+
|
572
|
+
#pragma mark - Accessors
|
573
|
+
|
574
|
+
static VALUE Fiber_Profiler_Capture_stall_threshold_get(VALUE self) {
|
575
|
+
struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
|
576
|
+
|
577
|
+
return DBL2NUM(profiler->stall_threshold);
|
578
|
+
}
|
579
|
+
|
580
|
+
static VALUE Fiber_Profiler_Capture_track_calls_get(VALUE self) {
|
581
|
+
struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
|
582
|
+
|
583
|
+
return profiler->track_calls ? Qtrue : Qfalse;
|
584
|
+
}
|
585
|
+
|
586
|
+
static VALUE Fiber_Profiler_Capture_stalls_get(VALUE self) {
|
587
|
+
struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
|
588
|
+
|
589
|
+
return SIZET2NUM(profiler->stalls);
|
590
|
+
}
|
591
|
+
|
592
|
+
static VALUE Fiber_Profiler_Capture_sample_rate_get(VALUE self) {
|
593
|
+
struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
|
594
|
+
|
595
|
+
return DBL2NUM(profiler->sample_rate);
|
596
|
+
}
|
597
|
+
|
598
|
+
#pragma mark - Environment Variables
|
599
|
+
|
600
|
+
static int FIBER_PROFILER_CAPTURE(void) {
|
601
|
+
const char *value = getenv("FIBER_PROFILER_CAPTURE");
|
602
|
+
|
603
|
+
if (value && strcmp(value, "true") == 0) {
|
604
|
+
return 1;
|
605
|
+
} else {
|
606
|
+
return 0;
|
607
|
+
}
|
608
|
+
}
|
609
|
+
|
610
|
+
static double FIBER_PROFILER_CAPTURE_STALL_THRESHOLD(void) {
|
611
|
+
const char *value = getenv("FIBER_PROFILER_CAPTURE_STALL_THRESHOLD");
|
612
|
+
|
613
|
+
if (value) {
|
614
|
+
return atof(value);
|
615
|
+
} else {
|
616
|
+
return 0.01;
|
617
|
+
}
|
618
|
+
}
|
619
|
+
|
620
|
+
static int FIBER_PROFILER_CAPTURE_TRACK_CALLS(void) {
|
621
|
+
const char *value = getenv("FIBER_PROFILER_CAPTURE_TRACK_CALLS");
|
622
|
+
|
623
|
+
if (value && strcmp(value, "false") == 0) {
|
624
|
+
return 0;
|
625
|
+
} else {
|
626
|
+
return 1;
|
627
|
+
}
|
628
|
+
}
|
629
|
+
|
630
|
+
static double FIBER_PROFILER_CAPTURE_SAMPLE_RATE(void) {
|
631
|
+
const char *value = getenv("FIBER_PROFILER_CAPTURE_SAMPLE_RATE");
|
632
|
+
|
633
|
+
if (value) {
|
634
|
+
return atof(value);
|
635
|
+
} else {
|
636
|
+
return 1;
|
637
|
+
}
|
638
|
+
}
|
639
|
+
|
640
|
+
#pragma mark - Initialization
|
641
|
+
|
642
|
+
void Init_Fiber_Profiler_Capture(VALUE Fiber_Profiler) {
|
643
|
+
Fiber_Profiler_capture_p = FIBER_PROFILER_CAPTURE();
|
644
|
+
Fiber_Profiler_Capture_stall_threshold = FIBER_PROFILER_CAPTURE_STALL_THRESHOLD();
|
645
|
+
Fiber_Profiler_Capture_track_calls = FIBER_PROFILER_CAPTURE_TRACK_CALLS();
|
646
|
+
Fiber_Profiler_Capture_sample_rate = FIBER_PROFILER_CAPTURE_SAMPLE_RATE();
|
647
|
+
|
648
|
+
Fiber_Profiler_Capture_initialize_options[0] = rb_intern("stall_threshold");
|
649
|
+
Fiber_Profiler_Capture_initialize_options[1] = rb_intern("track_calls");
|
650
|
+
Fiber_Profiler_Capture_initialize_options[2] = rb_intern("sample_rate");
|
651
|
+
Fiber_Profiler_Capture_initialize_options[3] = rb_intern("output");
|
652
|
+
|
653
|
+
Fiber_Profiler_Capture = rb_define_class_under(Fiber_Profiler, "Capture", rb_cObject);
|
654
|
+
rb_define_alloc_func(Fiber_Profiler_Capture, Fiber_Profiler_Capture_allocate);
|
655
|
+
|
656
|
+
rb_define_singleton_method(Fiber_Profiler_Capture, "default", Fiber_Profiler_Capture_default, 0);
|
657
|
+
|
658
|
+
rb_define_method(Fiber_Profiler_Capture, "initialize", Fiber_Profiler_Capture_initialize, -1);
|
659
|
+
|
660
|
+
rb_define_method(Fiber_Profiler_Capture, "start", Fiber_Profiler_Capture_start, 0);
|
661
|
+
rb_define_method(Fiber_Profiler_Capture, "stop", Fiber_Profiler_Capture_stop, 0);
|
662
|
+
|
663
|
+
rb_define_method(Fiber_Profiler_Capture, "stall_threshold", Fiber_Profiler_Capture_stall_threshold_get, 0);
|
664
|
+
rb_define_method(Fiber_Profiler_Capture, "track_calls", Fiber_Profiler_Capture_track_calls_get, 0);
|
665
|
+
rb_define_method(Fiber_Profiler_Capture, "sample_rate", Fiber_Profiler_Capture_sample_rate_get, 0);
|
666
|
+
|
667
|
+
rb_define_method(Fiber_Profiler_Capture, "stalls", Fiber_Profiler_Capture_stalls_get, 0);
|
668
|
+
|
669
|
+
rb_define_singleton_method(Fiber_Profiler_Capture, "default", Fiber_Profiler_Capture_default, 0);
|
670
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
// Released under the MIT License.
|
2
|
+
// Copyright, 2025, by Samuel Williams.
|
3
|
+
|
4
|
+
#pragma once
|
5
|
+
|
6
|
+
#include <ruby.h>
|
7
|
+
|
8
|
+
extern int Fiber_Profiler_capture;
|
9
|
+
extern float Fiber_Profiler_Capture_stall_threshold;
|
10
|
+
extern int Fiber_Profiler_Capture_track_calls;
|
11
|
+
|
12
|
+
void Init_Fiber_Profiler_Capture(VALUE Fiber_Profiler);
|
@@ -0,0 +1,272 @@
|
|
1
|
+
// Released under the MIT License.
|
2
|
+
// Copyright, 2023, by Samuel Williams.
|
3
|
+
|
4
|
+
// Provides a simple implementation of unique pointers to elements of the given size.
|
5
|
+
|
6
|
+
#include <ruby.h>
|
7
|
+
#include <stdlib.h>
|
8
|
+
#include <assert.h>
|
9
|
+
|
10
|
+
enum {
|
11
|
+
#ifdef RUBY_DEBUG
|
12
|
+
Fiber_Profiler_Deque_DEBUG = 1,
|
13
|
+
#else
|
14
|
+
Fiber_Profiler_Deque_DEBUG = 0,
|
15
|
+
#endif
|
16
|
+
};
|
17
|
+
|
18
|
+
// A page of elements:
|
19
|
+
struct Fiber_Profiler_Deque_Page {
|
20
|
+
struct Fiber_Profiler_Deque_Page *head, *tail;
|
21
|
+
|
22
|
+
size_t size;
|
23
|
+
size_t capacity;
|
24
|
+
|
25
|
+
char elements[];
|
26
|
+
};
|
27
|
+
|
28
|
+
struct Fiber_Profiler_Deque_Page *Fiber_Profiler_Deque_Page_allocate(size_t element_size, size_t capacity)
|
29
|
+
{
|
30
|
+
struct Fiber_Profiler_Deque_Page *page = (struct Fiber_Profiler_Deque_Page *)malloc(sizeof(struct Fiber_Profiler_Deque_Page) + element_size * capacity);
|
31
|
+
|
32
|
+
if (page) {
|
33
|
+
page->head = page->tail = NULL;
|
34
|
+
|
35
|
+
page->size = 0;
|
36
|
+
page->capacity = capacity;
|
37
|
+
|
38
|
+
memset(page->elements, 0, element_size * capacity);
|
39
|
+
}
|
40
|
+
|
41
|
+
return page;
|
42
|
+
}
|
43
|
+
|
44
|
+
inline static void *Fiber_Profiler_Deque_Page_get(struct Fiber_Profiler_Deque_Page *page, size_t index, size_t element_size)
|
45
|
+
{
|
46
|
+
#ifdef RUBY_DEBUG
|
47
|
+
if (index >= page->size) {
|
48
|
+
fprintf(stderr, "Fiber_Profiler_Deque_Page_get: index=%lu, size=%lu\n", index, page->size);
|
49
|
+
}
|
50
|
+
#endif
|
51
|
+
|
52
|
+
RUBY_ASSERT(index < page->size);
|
53
|
+
|
54
|
+
return (void*)((char *)page->elements + (index * element_size));
|
55
|
+
}
|
56
|
+
|
57
|
+
inline static struct Fiber_Profiler_Deque_Page *Fiber_Profiler_Deque_Page_free(struct Fiber_Profiler_Deque_Page *page, size_t element_size, void (*element_free)(void*))
|
58
|
+
{
|
59
|
+
struct Fiber_Profiler_Deque_Page *tail = page->tail;
|
60
|
+
|
61
|
+
if (element_free) {
|
62
|
+
for (size_t i = 0; i < page->size; i += 1) {
|
63
|
+
void *element = Fiber_Profiler_Deque_Page_get(page, i, element_size);
|
64
|
+
element_free(element);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
free(page);
|
69
|
+
|
70
|
+
return tail;
|
71
|
+
}
|
72
|
+
|
73
|
+
inline static void Fiber_Profiler_Deque_Page_truncate(struct Fiber_Profiler_Deque_Page *page, size_t size, size_t element_size, void (*element_free)(void*))
|
74
|
+
{
|
75
|
+
if (size < page->size) {
|
76
|
+
if (element_free) {
|
77
|
+
for (size_t i = size; i < page->size; i += 1) {
|
78
|
+
void *element = Fiber_Profiler_Deque_Page_get(page, i, element_size);
|
79
|
+
element_free(element);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
page->size = size;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
struct Fiber_Profiler_Deque {
|
88
|
+
// The deque of elements:
|
89
|
+
struct Fiber_Profiler_Deque_Page *head, *tail;
|
90
|
+
|
91
|
+
// The current capacity:
|
92
|
+
size_t capacity;
|
93
|
+
|
94
|
+
// The size of each element that is allocated:
|
95
|
+
size_t element_size;
|
96
|
+
|
97
|
+
void (*element_initialize)(void*);
|
98
|
+
void (*element_free)(void*);
|
99
|
+
};
|
100
|
+
|
101
|
+
inline static int Fiber_Profiler_Deque_initialize(struct Fiber_Profiler_Deque *deque, size_t element_size)
|
102
|
+
{
|
103
|
+
deque->head = deque->tail = NULL;
|
104
|
+
|
105
|
+
deque->capacity = 0;
|
106
|
+
|
107
|
+
deque->element_size = element_size;
|
108
|
+
|
109
|
+
return 0;
|
110
|
+
}
|
111
|
+
|
112
|
+
inline static size_t Fiber_Profiler_Deque_memory_size(const struct Fiber_Profiler_Deque *deque)
|
113
|
+
{
|
114
|
+
struct Fiber_Profiler_Deque_Page *page = deque->head;
|
115
|
+
|
116
|
+
size_t size = 0;
|
117
|
+
while (page) {
|
118
|
+
size += sizeof(struct Fiber_Profiler_Deque_Page);
|
119
|
+
size += page->capacity * deque->element_size;
|
120
|
+
page = page->tail;
|
121
|
+
}
|
122
|
+
|
123
|
+
return size;
|
124
|
+
}
|
125
|
+
|
126
|
+
inline static void Fiber_Profiler_Deque_free(struct Fiber_Profiler_Deque *deque)
|
127
|
+
{
|
128
|
+
struct Fiber_Profiler_Deque_Page *page = deque->head;
|
129
|
+
|
130
|
+
deque->head = deque->tail = NULL;
|
131
|
+
|
132
|
+
while (page) {
|
133
|
+
page = Fiber_Profiler_Deque_Page_free(page, deque->element_size, deque->element_free);
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
inline static void Fiber_Profiler_Deque_debug(struct Fiber_Profiler_Deque *deque, const char * operation)
|
138
|
+
{
|
139
|
+
struct Fiber_Profiler_Deque_Page *page = deque->head;
|
140
|
+
|
141
|
+
while (page) {
|
142
|
+
// fprintf(stderr, "Fiber_Profiler_Deque: %s: page=%p, size=%lu, capacity=%lu\n", operation, page, page->size, page->capacity);
|
143
|
+
|
144
|
+
RUBY_ASSERT(page->size <= page->capacity);
|
145
|
+
|
146
|
+
RUBY_ASSERT(page->head == NULL || page->head->tail == page);
|
147
|
+
RUBY_ASSERT(page->tail == NULL || page->tail->head == page);
|
148
|
+
|
149
|
+
page = page->tail;
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
void Fiber_Profiler_Deque_truncate(struct Fiber_Profiler_Deque *deque)
|
154
|
+
{
|
155
|
+
struct Fiber_Profiler_Deque_Page *page = deque->tail;
|
156
|
+
|
157
|
+
while (page) {
|
158
|
+
Fiber_Profiler_Deque_Page_truncate(page, 0, deque->element_size, deque->element_free);
|
159
|
+
|
160
|
+
page = page->head;
|
161
|
+
}
|
162
|
+
|
163
|
+
deque->tail = deque->head;
|
164
|
+
|
165
|
+
if (Fiber_Profiler_Deque_DEBUG) Fiber_Profiler_Deque_debug(deque, __FUNCTION__);
|
166
|
+
}
|
167
|
+
|
168
|
+
inline static size_t Fiber_Profiler_Deque_default_capacity(struct Fiber_Profiler_Deque *deque)
|
169
|
+
{
|
170
|
+
static const size_t target_size = 4096*8;
|
171
|
+
|
172
|
+
return (target_size - sizeof(struct Fiber_Profiler_Deque_Page)) / deque->element_size;
|
173
|
+
}
|
174
|
+
|
175
|
+
void *Fiber_Profiler_Deque_push(struct Fiber_Profiler_Deque *deque)
|
176
|
+
{
|
177
|
+
struct Fiber_Profiler_Deque_Page *page = deque->tail;
|
178
|
+
|
179
|
+
if (page == NULL) {
|
180
|
+
page = Fiber_Profiler_Deque_Page_allocate(deque->element_size, Fiber_Profiler_Deque_default_capacity(deque));
|
181
|
+
|
182
|
+
if (page == NULL) {
|
183
|
+
return NULL;
|
184
|
+
}
|
185
|
+
|
186
|
+
deque->head = deque->tail = page;
|
187
|
+
deque->capacity += page->capacity;
|
188
|
+
} else if (page->size == page->capacity) {
|
189
|
+
if (page->tail) {
|
190
|
+
page = page->tail;
|
191
|
+
deque->tail = page;
|
192
|
+
} else {
|
193
|
+
page = Fiber_Profiler_Deque_Page_allocate(deque->element_size, Fiber_Profiler_Deque_default_capacity(deque));
|
194
|
+
|
195
|
+
if (page == NULL) {
|
196
|
+
return NULL;
|
197
|
+
}
|
198
|
+
|
199
|
+
page->head = deque->tail;
|
200
|
+
deque->tail->tail = page;
|
201
|
+
deque->tail = page;
|
202
|
+
deque->capacity += page->capacity;
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
// Push a new element:
|
207
|
+
page->size += 1;
|
208
|
+
void *element = Fiber_Profiler_Deque_Page_get(page, page->size - 1, deque->element_size);
|
209
|
+
|
210
|
+
if (deque->element_initialize) {
|
211
|
+
deque->element_initialize(element);
|
212
|
+
}
|
213
|
+
|
214
|
+
if (Fiber_Profiler_Deque_DEBUG) Fiber_Profiler_Deque_debug(deque, __FUNCTION__);
|
215
|
+
|
216
|
+
return element;
|
217
|
+
}
|
218
|
+
|
219
|
+
inline static void *Fiber_Profiler_Deque_pop(struct Fiber_Profiler_Deque *deque)
|
220
|
+
{
|
221
|
+
struct Fiber_Profiler_Deque_Page *page = deque->tail;
|
222
|
+
|
223
|
+
if (page == NULL) {
|
224
|
+
return NULL;
|
225
|
+
}
|
226
|
+
|
227
|
+
while (page->size == 0) {
|
228
|
+
if (page->head) {
|
229
|
+
page = page->head;
|
230
|
+
} else {
|
231
|
+
return NULL;
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
deque->tail = page;
|
236
|
+
|
237
|
+
// Pop the last element:
|
238
|
+
void *element = Fiber_Profiler_Deque_Page_get(page, page->size - 1, deque->element_size);
|
239
|
+
page->size -= 1;
|
240
|
+
|
241
|
+
if (deque->element_free) {
|
242
|
+
deque->element_free(element);
|
243
|
+
}
|
244
|
+
|
245
|
+
if (Fiber_Profiler_Deque_DEBUG) Fiber_Profiler_Deque_debug(deque, __FUNCTION__);
|
246
|
+
|
247
|
+
return element;
|
248
|
+
}
|
249
|
+
|
250
|
+
inline static void *Fiber_Profiler_Deque_last(struct Fiber_Profiler_Deque *deque)
|
251
|
+
{
|
252
|
+
struct Fiber_Profiler_Deque_Page *page = deque->tail;
|
253
|
+
|
254
|
+
if (page == NULL) {
|
255
|
+
return NULL;
|
256
|
+
}
|
257
|
+
|
258
|
+
while (page->size == 0) {
|
259
|
+
if (page->head) {
|
260
|
+
page = page->head;
|
261
|
+
} else {
|
262
|
+
return NULL;
|
263
|
+
}
|
264
|
+
}
|
265
|
+
|
266
|
+
return Fiber_Profiler_Deque_Page_get(page, page->size - 1, deque->element_size);
|
267
|
+
}
|
268
|
+
|
269
|
+
#define Fiber_Profiler_Deque_each(deque, type, element) \
|
270
|
+
for (struct Fiber_Profiler_Deque_Page *page = (deque)->head; page != NULL && page->size; page = page->tail) \
|
271
|
+
for (size_t i = 0; i < page->size; i++) \
|
272
|
+
for (type *element = Fiber_Profiler_Deque_Page_get(page, i, sizeof(type)); element != NULL; element = NULL)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
// Released under the MIT License.
|
2
|
+
// Copyright, 2025, by Samuel Williams.
|
3
|
+
|
4
|
+
#include "fiber.h"
|
5
|
+
|
6
|
+
#ifndef HAVE_RB_FIBER_CURRENT
|
7
|
+
static ID id_current;
|
8
|
+
|
9
|
+
static VALUE Fiber_Profiler_Fiber_current(void) {
|
10
|
+
return rb_funcall(rb_cFiber, id_current, 0);
|
11
|
+
}
|
12
|
+
#endif
|
13
|
+
|
14
|
+
// There is no public interface for this... yet.
|
15
|
+
static ID id_blocking_p;
|
16
|
+
|
17
|
+
int Fiber_Profiler_Fiber_blocking(VALUE fiber) {
|
18
|
+
return RTEST(rb_funcall(fiber, id_blocking_p, 0));
|
19
|
+
}
|
20
|
+
|
21
|
+
void Init_Fiber_Profiler_Fiber(VALUE Fiber_Profiler) {
|
22
|
+
#ifndef HAVE_RB_FIBER_CURRENT
|
23
|
+
id_current = rb_intern("current");
|
24
|
+
#endif
|
25
|
+
|
26
|
+
id_blocking_p = rb_intern("blocking?");
|
27
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
// Released under the MIT License.
|
2
|
+
// Copyright, 2025, by Samuel Williams.
|
3
|
+
|
4
|
+
#pragma once
|
5
|
+
|
6
|
+
#include <ruby.h>
|
7
|
+
|
8
|
+
#ifdef HAVE_RB_FIBER_CURRENT
|
9
|
+
#define Fiber_Profiler_Fiber_current() rb_fiber_current()
|
10
|
+
#else
|
11
|
+
VALUE Fiber_Profiler_Fiber_current(void);
|
12
|
+
#endif
|
13
|
+
|
14
|
+
int Fiber_Profiler_Fiber_blocking(VALUE fiber);
|
15
|
+
|
16
|
+
void Init_Fiber_Profiler_Fiber(VALUE Fiber_Profiler);
|
@@ -0,0 +1,20 @@
|
|
1
|
+
// Released under the MIT License.
|
2
|
+
// Copyright, 2021-2025, by Samuel Williams.
|
3
|
+
|
4
|
+
#include "profiler.h"
|
5
|
+
|
6
|
+
#include "fiber.h"
|
7
|
+
#include "capture.h"
|
8
|
+
|
9
|
+
void Init_Fiber_Profiler(void)
|
10
|
+
{
|
11
|
+
#ifdef HAVE_RB_EXT_RACTOR_SAFE
|
12
|
+
rb_ext_ractor_safe(true);
|
13
|
+
#endif
|
14
|
+
|
15
|
+
VALUE Fiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
|
16
|
+
VALUE Fiber_Profiler = rb_define_module_under(Fiber, "Profiler");
|
17
|
+
|
18
|
+
Init_Fiber_Profiler_Fiber(Fiber_Profiler);
|
19
|
+
Init_Fiber_Profiler_Capture(Fiber_Profiler);
|
20
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
// Released under the MIT License.
|
2
|
+
// Copyright, 2025, by Samuel Williams.
|
3
|
+
|
4
|
+
#include "time.h"
|
5
|
+
|
6
|
+
void Fiber_Profiler_Time_elapsed(const struct timespec* start, const struct timespec* stop, struct timespec *duration)
|
7
|
+
{
|
8
|
+
if ((stop->tv_nsec - start->tv_nsec) < 0) {
|
9
|
+
duration->tv_sec = stop->tv_sec - start->tv_sec - 1;
|
10
|
+
duration->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
|
11
|
+
} else {
|
12
|
+
duration->tv_sec = stop->tv_sec - start->tv_sec;
|
13
|
+
duration->tv_nsec = stop->tv_nsec - start->tv_nsec;
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
double Fiber_Profiler_Time_duration(const struct timespec *duration)
|
18
|
+
{
|
19
|
+
return duration->tv_sec + duration->tv_nsec / 1000000000.0;
|
20
|
+
}
|
21
|
+
|
22
|
+
void Fiber_Profiler_Time_current(struct timespec *time) {
|
23
|
+
clock_gettime(CLOCK_MONOTONIC, time);
|
24
|
+
}
|
25
|
+
|
26
|
+
double Fiber_Profiler_Time_proportion(const struct timespec *duration, const struct timespec *total_duration) {
|
27
|
+
return Fiber_Profiler_Time_duration(duration) / Fiber_Profiler_Time_duration(total_duration);
|
28
|
+
}
|
29
|
+
|
30
|
+
double Fiber_Profiler_Time_delta(const struct timespec *start, const struct timespec *stop) {
|
31
|
+
struct timespec duration;
|
32
|
+
Fiber_Profiler_Time_elapsed(start, stop, &duration);
|
33
|
+
|
34
|
+
return Fiber_Profiler_Time_duration(&duration);
|
35
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
// Released under the MIT License.
|
2
|
+
// Copyright, 2025, by Samuel Williams.
|
3
|
+
|
4
|
+
#pragma once
|
5
|
+
|
6
|
+
#include <ruby.h>
|
7
|
+
#include <time.h>
|
8
|
+
|
9
|
+
void Fiber_Profiler_Time_elapsed(const struct timespec* start, const struct timespec* stop, struct timespec *duration);
|
10
|
+
double Fiber_Profiler_Time_duration(const struct timespec *duration);
|
11
|
+
void Fiber_Profiler_Time_current(struct timespec *time);
|
12
|
+
|
13
|
+
double Fiber_Profiler_Time_delta(const struct timespec *start, const struct timespec *stop);
|
14
|
+
double Fiber_Profiler_Time_proportion(const struct timespec *duration, const struct timespec *total_duration);
|
15
|
+
|
16
|
+
#define Fiber_Profiler_TIME_PRINTF_TIMESPEC "%.3g"
|
17
|
+
#define Fiber_Profiler_TIME_PRINTF_TIMESPEC_ARGUMENTS(ts) ((double)(ts).tv_sec + (ts).tv_nsec / 1e9)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "profiler/version"
|
7
|
+
require_relative "profiler/native"
|
8
|
+
|
9
|
+
module Fiber::Profiler
|
10
|
+
# The default profiler to use, if any.
|
11
|
+
#
|
12
|
+
# Use the `FIBER_PROFILER_CAPTURE=true` environment variable to enable profiling.
|
13
|
+
#
|
14
|
+
# @returns [Capture | Nil]
|
15
|
+
def self.default
|
16
|
+
Capture.default
|
17
|
+
end
|
18
|
+
|
19
|
+
# Execute the given block with the {default} profiler, if any.
|
20
|
+
#
|
21
|
+
# @yields {...} The block to execute.
|
22
|
+
def self.capture
|
23
|
+
if capture = self.default
|
24
|
+
begin
|
25
|
+
capture.start
|
26
|
+
|
27
|
+
yield
|
28
|
+
ensure
|
29
|
+
capture.stop
|
30
|
+
end
|
31
|
+
else
|
32
|
+
yield
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/license.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# MIT License
|
2
|
+
|
3
|
+
Copyright, 2025, by Samuel Williams.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/readme.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Fiber::Profiler
|
2
|
+
|
3
|
+
Detects fibers that are stalling the event loop.
|
4
|
+
|
5
|
+
[](https://github.com/socketry/fiber-profiler/actions?workflow=Test)
|
6
|
+
|
7
|
+
## Motivation
|
8
|
+
|
9
|
+
Migrating existing applications to the event loop can be tricky. One of the most common issues is when a fiber is blocking the event loop. This can happen when a fiber is waiting on a blocking operation, such as a database query, or a network request, that could not take advantage of the event loop. This can cause the event loop to stall, and prevent other fibers from running. This gem provides a way to detect these fibers, and help you identify the cause of the stall.
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Please see the [project documentation](https://socketry.github.io/fiber-profiler/) for more details.
|
14
|
+
|
15
|
+
- [Getting Started](https://socketry.github.io/fiber-profiler/guides/getting-started/index) - This guide explains how to detect stalls using the fiber profiler.
|
16
|
+
|
17
|
+
## Releases
|
18
|
+
|
19
|
+
Please see the [project releases](https://socketry.github.io/fiber-profiler/releases/index) for all releases.
|
20
|
+
|
21
|
+
### v0.1.0
|
22
|
+
|
23
|
+
- Initial implementation extracted from `io-event` gem.
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
We welcome contributions to this project.
|
28
|
+
|
29
|
+
1. Fork it.
|
30
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
31
|
+
3. Commit your changes (`git commit -am 'Add some feature'`).
|
32
|
+
4. Push to the branch (`git push origin my-new-feature`).
|
33
|
+
5. Create new Pull Request.
|
34
|
+
|
35
|
+
### Developer Certificate of Origin
|
36
|
+
|
37
|
+
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
38
|
+
|
39
|
+
### Community Guidelines
|
40
|
+
|
41
|
+
This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
|
data/releases.md
ADDED
data.tar.gz.sig
ADDED
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fiber-profiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Samuel Williams
|
8
|
+
bindir: bin
|
9
|
+
cert_chain:
|
10
|
+
- |
|
11
|
+
-----BEGIN CERTIFICATE-----
|
12
|
+
MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
|
13
|
+
ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
|
14
|
+
CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
|
15
|
+
MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
|
16
|
+
MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
|
17
|
+
bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
|
18
|
+
igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
|
19
|
+
9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
|
20
|
+
sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
|
21
|
+
e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
|
22
|
+
XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
|
23
|
+
RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
|
24
|
+
tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
|
25
|
+
zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
|
26
|
+
xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
|
27
|
+
BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
|
28
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
|
29
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
|
30
|
+
cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
|
31
|
+
xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
|
32
|
+
c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
|
33
|
+
8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
|
34
|
+
JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
|
35
|
+
eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
|
36
|
+
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
37
|
+
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
38
|
+
-----END CERTIFICATE-----
|
39
|
+
date: 2025-02-13 00:00:00.000000000 Z
|
40
|
+
dependencies: []
|
41
|
+
executables: []
|
42
|
+
extensions:
|
43
|
+
- ext/extconf.rb
|
44
|
+
extra_rdoc_files: []
|
45
|
+
files:
|
46
|
+
- ext/extconf.rb
|
47
|
+
- ext/fiber/profiler/capture.c
|
48
|
+
- ext/fiber/profiler/capture.h
|
49
|
+
- ext/fiber/profiler/deque.h
|
50
|
+
- ext/fiber/profiler/fiber.c
|
51
|
+
- ext/fiber/profiler/fiber.h
|
52
|
+
- ext/fiber/profiler/profiler.c
|
53
|
+
- ext/fiber/profiler/profiler.h
|
54
|
+
- ext/fiber/profiler/time.c
|
55
|
+
- ext/fiber/profiler/time.h
|
56
|
+
- lib/fiber/profiler.rb
|
57
|
+
- lib/fiber/profiler/capture.rb
|
58
|
+
- lib/fiber/profiler/native.rb
|
59
|
+
- lib/fiber/profiler/version.rb
|
60
|
+
- license.md
|
61
|
+
- readme.md
|
62
|
+
- releases.md
|
63
|
+
homepage: https://github.com/socketry/fiber-profiler
|
64
|
+
licenses:
|
65
|
+
- MIT
|
66
|
+
metadata:
|
67
|
+
documentation_uri: https://socketry.github.io/fiber-profiler/
|
68
|
+
source_code_uri: https://github.com/socketry/fiber-profiler.git
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '3.1'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubygems_version: 3.6.2
|
84
|
+
specification_version: 4
|
85
|
+
summary: A fiber stall profiler.
|
86
|
+
test_files: []
|
metadata.gz.sig
ADDED