fiber-profiler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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(&current->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,8 @@
1
+ // Released under the MIT License.
2
+ // Copyright, 2021-2025, by Samuel Williams.
3
+
4
+ #pragma once
5
+
6
+ #include <ruby.h>
7
+
8
+ void Init_Fiber_Profiler(void);
@@ -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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require_relative "native"
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require "Fiber_Profiler"
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ # @namespace
7
+ class Fiber
8
+ # @namespace
9
+ module Profiler
10
+ VERSION = "0.1.0"
11
+ end
12
+ end
@@ -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
+ [![Development Status](https://github.com/socketry/fiber-profiler/workflows/Test/badge.svg)](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
@@ -0,0 +1,5 @@
1
+ # Releases
2
+
3
+ ## v0.1.0
4
+
5
+ - Initial implementation extracted from `io-event` gem.
data.tar.gz.sig ADDED
@@ -0,0 +1,3 @@
1
+ '��Jg��`��
2
+ �1m���Vk���Lm�z������g�R��~�z�P�F��ɮ֑�ɇ2=q;9˅��?�`��J`�/�
3
+ �C��ڄ(xx�
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
@@ -0,0 +1,3 @@
1
+ r�!H��
2
+ e�nz�X������L�񉿁栕|�=�~���*4,q�i)ݟ0�M�Q.���H����^
3
+ �s�� %��6"��8/�P.6~ 0��볪�89U���=rU�xX�5C ���Õ|�,���