fiber-profiler 0.1.2 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a39de10cbe411ddcec80b11ea0ac8204d589e15d54bc70b19e4bc979f49a16b
4
- data.tar.gz: 57e2fd24daee67fd4d7aa35fbfa82b12bb73f9b0f2c66610aab58c35baac4ded
3
+ metadata.gz: 90d32f14c57f33c6c247a942a45a2cc56e3cfd673a67c32cfacdf780b21b3473
4
+ data.tar.gz: 7b666f890ca18c55d5baba91961bae7033d25fa052fc8b618eac13e28992199d
5
5
  SHA512:
6
- metadata.gz: 91fc103cf78905421511a6c43523911a31fe5862b177024d410c9cace9870015d8d81c0f326b04be0c2329d9998bd5a900c71b74afb1eb276b2168bb398ab386
7
- data.tar.gz: bb5abc757b8cd79bcd2589ad66cc8693a049ed3a7ba6a81706e2ff1a8c4d6db27ae3b5eb6f51e43a157dbc37ab3e8e08e9926482066acd9d84c2235d51a59e2c
6
+ metadata.gz: f3a93685cad55774675df4db103cc3f2308cbe342a198eadd6ac83e216e409224faf65736b648f3849b1e3faa28b0ae9298faa6b4fb75e1ddcb5c05f6c86efef
7
+ data.tar.gz: 621ee83b498452ce5d3eab1501a115b6f086f62b04800b09829e9a85143bcbd8141ea6050ff34f3afe0c52a996ec6ce62893f9e4e998b63485522409a684415e
checksums.yaml.gz.sig CHANGED
Binary file
Binary file
data/ext/capture.o CHANGED
Binary file
@@ -14,6 +14,7 @@
14
14
 
15
15
  enum {
16
16
  DEBUG_SKIPPED = 0,
17
+ DEBUG_FILTERED = 0,
17
18
  };
18
19
 
19
20
  int Fiber_Profiler_capture_p = 0;
@@ -25,9 +26,11 @@ VALUE Fiber_Profiler_Capture = Qnil;
25
26
 
26
27
  struct Fiber_Profiler_Capture_Call {
27
28
  struct timespec enter_time;
28
- struct timespec exit_time;
29
+ double duration;
29
30
 
30
- size_t nesting;
31
+ int nesting;
32
+ size_t children;
33
+ size_t filtered;
31
34
 
32
35
  rb_event_flag_t event_flag;
33
36
  ID id;
@@ -65,41 +68,61 @@ struct Fiber_Profiler_Capture;
65
68
  typedef void(*Fiber_Profiler_Stream_Print)(struct Fiber_Profiler_Capture*, FILE* restrict);
66
69
 
67
70
  struct Fiber_Profiler_Capture {
68
- // Configuration:
71
+ // The threshold in seconds, which determines when a fiber is considered to have stalled the event loop.
69
72
  double stall_threshold;
73
+
74
+ // Whether or not to track calls.
70
75
  int track_calls;
76
+
77
+ // The sample rate of the profiler, as a fraction of 1.0, which controls how often the profiler will sample between fiber context switches.
71
78
  double sample_rate;
72
79
 
73
- // Calls that are shorter than this filter threshold will be ignored:
80
+ // Calls that are shorter than this filter threshold will be ignored.
74
81
  double filter_threshold;
75
82
 
83
+ // The output object to write to.
76
84
  VALUE output;
85
+
86
+ // The stream print function to use.
77
87
  Fiber_Profiler_Stream_Print print;
88
+
89
+ // The stream buffer used for printing.
78
90
  struct Fiber_Profiler_Stream stream;
79
91
 
80
- // Whether or not the profiler is currently running:
92
+ // Whether or not the profiler is currently running.
81
93
  int running;
82
94
 
83
- // Whether or not to capture call data:
95
+ // The thread being profiled.
96
+ VALUE thread;
97
+
98
+ // Whether or not to capture call data.
84
99
  int capture;
85
100
 
101
+ // The number of stalls encountered.
86
102
  size_t stalls;
87
103
 
88
- // From this point on, the state of any profile in progress:
104
+ // The start time of the profile.
89
105
  struct timespec start_time;
106
+
107
+ // The stop time of the profile.
90
108
  struct timespec stop_time;
91
109
 
92
- // The depth of the call stack:
93
- size_t nesting;
110
+ // The depth of the call stack (can be negative).
111
+ int nesting;
112
+
113
+ // The minimum nesting level encountered during the profiling session.
114
+ int nesting_minimum;
94
115
 
95
- // The current call frame:
116
+ // The current call frame.
96
117
  struct Fiber_Profiler_Capture_Call *current;
97
118
 
119
+ // The call recorded during the profiling session.
98
120
  struct Fiber_Profiler_Deque calls;
99
121
  };
100
122
 
101
123
  void Fiber_Profiler_Capture_reset(struct Fiber_Profiler_Capture *profiler) {
102
124
  profiler->nesting = 0;
125
+ profiler->nesting_minimum = 0;
103
126
  profiler->current = NULL;
104
127
  Fiber_Profiler_Deque_truncate(&profiler->calls);
105
128
  }
@@ -109,10 +132,11 @@ void Fiber_Profiler_Capture_Call_initialize(void *element) {
109
132
 
110
133
  call->enter_time.tv_sec = 0;
111
134
  call->enter_time.tv_nsec = 0;
112
- call->exit_time.tv_sec = 0;
113
- call->exit_time.tv_nsec = 0;
135
+ call->duration = 0;
114
136
 
115
137
  call->nesting = 0;
138
+ call->children = 0;
139
+ call->filtered = 0;
116
140
 
117
141
  call->event_flag = 0;
118
142
  call->id = 0;
@@ -133,6 +157,7 @@ void Fiber_Profiler_Capture_Call_free(void *element) {
133
157
  static void Fiber_Profiler_Capture_mark(void *ptr) {
134
158
  struct Fiber_Profiler_Capture *profiler = (struct Fiber_Profiler_Capture*)ptr;
135
159
 
160
+ rb_gc_mark_movable(profiler->thread);
136
161
  rb_gc_mark_movable(profiler->output);
137
162
 
138
163
  // If `klass` is stored as a VALUE in calls, we need to mark them here:
@@ -144,6 +169,7 @@ static void Fiber_Profiler_Capture_mark(void *ptr) {
144
169
  static void Fiber_Profiler_Capture_compact(void *ptr) {
145
170
  struct Fiber_Profiler_Capture *profiler = (struct Fiber_Profiler_Capture*)ptr;
146
171
 
172
+ profiler->thread = rb_gc_location(profiler->thread);
147
173
  profiler->output = rb_gc_location(profiler->output);
148
174
 
149
175
  // If `klass` is stored as a VALUE in calls, we need to update their locations here:
@@ -213,22 +239,28 @@ VALUE Fiber_Profiler_Capture_allocate(VALUE klass) {
213
239
  // Initialize the profiler state:
214
240
  Fiber_Profiler_Stream_initialize(&profiler->stream);
215
241
  profiler->output = Qnil;
242
+
216
243
  profiler->running = 0;
244
+ profiler->thread = Qnil;
245
+
217
246
  profiler->capture = 0;
218
247
  profiler->stalls = 0;
219
248
  profiler->nesting = 0;
249
+ profiler->nesting_minimum = 0;
220
250
  profiler->current = NULL;
221
251
 
222
252
  profiler->stall_threshold = Fiber_Profiler_Capture_stall_threshold;
223
253
  profiler->track_calls = Fiber_Profiler_Capture_track_calls;
224
254
  profiler->sample_rate = Fiber_Profiler_Capture_sample_rate;
225
255
 
226
- // Filter calls that are less than 1% of the stall threshold:
227
- profiler->filter_threshold = profiler->stall_threshold * 0.01;
256
+ // Filter calls that are less than 10% of the stall threshold:
257
+ profiler->filter_threshold = profiler->stall_threshold * 0.1;
228
258
 
229
259
  profiler->calls.element_initialize = (void (*)(void*))Fiber_Profiler_Capture_Call_initialize;
230
260
  profiler->calls.element_free = (void (*)(void*))Fiber_Profiler_Capture_Call_free;
261
+
231
262
  Fiber_Profiler_Deque_initialize(&profiler->calls, sizeof(struct Fiber_Profiler_Capture_Call));
263
+ Fiber_Profiler_Deque_reserve_default(&profiler->calls);
232
264
 
233
265
  return TypedData_Wrap_Struct(klass, &Fiber_Profiler_Capture_Type, profiler);
234
266
  }
@@ -295,20 +327,24 @@ const char *event_flag_name(rb_event_flag_t event_flag) {
295
327
  case RUBY_INTERNAL_EVENT_GC_START: return "gc-start";
296
328
  case RUBY_INTERNAL_EVENT_GC_END_MARK: return "gc-end-mark";
297
329
  case RUBY_INTERNAL_EVENT_GC_END_SWEEP: return "gc-end-sweep";
330
+ case RUBY_EVENT_LINE: return "line";
298
331
  default: return "unknown";
299
332
  }
300
333
  }
301
334
 
302
- 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) {
335
+ static struct Fiber_Profiler_Capture_Call* Fiber_Profiler_Capture_Call_new(struct Fiber_Profiler_Capture *profiler, rb_event_flag_t event_flag, ID id, VALUE klass) {
303
336
  struct Fiber_Profiler_Capture_Call *call = Fiber_Profiler_Deque_push(&profiler->calls);
304
337
 
305
338
  call->event_flag = event_flag;
306
339
 
307
340
  call->parent = profiler->current;
341
+ if (call->parent) {
342
+ call->parent->children += 1;
343
+ }
344
+
308
345
  profiler->current = call;
309
-
346
+
310
347
  call->nesting = profiler->nesting;
311
- profiler->nesting += 1;
312
348
 
313
349
  if (id) {
314
350
  call->id = id;
@@ -326,21 +362,54 @@ static struct Fiber_Profiler_Capture_Call* profiler_event_record_call(struct Fib
326
362
  return call;
327
363
  }
328
364
 
329
- void Fiber_Profiler_Capture_fiber_switch(struct Fiber_Profiler_Capture *profiler);
365
+ // Finish the call by calculating the duration and filtering it if necessary.
366
+ int Fiber_Profiler_Capture_Call_finish(struct Fiber_Profiler_Capture *profiler, struct Fiber_Profiler_Capture_Call *call) {
367
+ // Don't filter calls if we're not running:
368
+ if (DEBUG_FILTERED) return 0;
369
+
370
+ if (call->duration < profiler->filter_threshold) {
371
+ // We can only remove calls from the end of the deque, otherwise they might be referenced by other calls:
372
+ if (call == Fiber_Profiler_Deque_last(&profiler->calls)) {
373
+ if (profiler->current == call) {
374
+ profiler->current = call->parent;
375
+ }
376
+
377
+ if (call->parent) {
378
+ call->parent->children -= 1;
379
+ call->parent->filtered += 1;
380
+ call->parent = NULL;
381
+ }
382
+
383
+ Fiber_Profiler_Deque_pop(&profiler->calls);
384
+
385
+ return 1;
386
+ }
387
+ }
388
+
389
+ return 0;
390
+ }
391
+
392
+ static const double Fiber_Profiler_Capture_Call_EXPENSIVE_THRESHOLD = 0.2;
393
+
394
+ int Fiber_Profiler_Capture_Call_expensive_p(struct Fiber_Profiler_Capture_Call *call, double total_duration) {
395
+ if (call->duration > total_duration * Fiber_Profiler_Capture_Call_EXPENSIVE_THRESHOLD) {
396
+ return 1;
397
+ }
398
+
399
+ return 0;
400
+ }
330
401
 
331
402
  static void Fiber_Profiler_Capture_callback(rb_event_flag_t event_flag, VALUE data, VALUE self, ID id, VALUE klass) {
332
403
  struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(data);
333
404
 
334
- if (event_flag & RUBY_EVENT_FIBER_SWITCH) {
335
- Fiber_Profiler_Capture_fiber_switch(profiler);
336
- return;
337
- }
338
-
339
405
  // We don't want to capture data if we're not running:
340
406
  if (!profiler->capture) return;
341
407
 
342
408
  if (event_flag_call_p(event_flag)) {
343
- struct Fiber_Profiler_Capture_Call *call = profiler_event_record_call(profiler, event_flag, id, klass);
409
+ struct Fiber_Profiler_Capture_Call *call = Fiber_Profiler_Capture_Call_new(profiler, event_flag, id, klass);
410
+
411
+ profiler->nesting += 1;
412
+
344
413
  Fiber_Profiler_Time_current(&call->enter_time);
345
414
  }
346
415
 
@@ -350,7 +419,7 @@ static void Fiber_Profiler_Capture_callback(rb_event_flag_t event_flag, VALUE da
350
419
  // 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:
351
420
  if (call == NULL) {
352
421
  struct Fiber_Profiler_Capture_Call *last_call = Fiber_Profiler_Deque_last(&profiler->calls);
353
- call = profiler_event_record_call(profiler, event_flag, id, klass);
422
+ call = Fiber_Profiler_Capture_Call_new(profiler, event_flag, id, klass);
354
423
 
355
424
  if (last_call) {
356
425
  call->enter_time = last_call->enter_time;
@@ -359,46 +428,85 @@ static void Fiber_Profiler_Capture_callback(rb_event_flag_t event_flag, VALUE da
359
428
  }
360
429
  }
361
430
 
362
- Fiber_Profiler_Time_current(&call->exit_time);
431
+ call->duration = Fiber_Profiler_Time_delta_current(&call->enter_time);
363
432
 
364
433
  profiler->current = call->parent;
365
434
 
366
435
  // We may encounter returns without a preceeding call.
367
- if (profiler->nesting > 0)
368
- profiler->nesting -= 1;
436
+ profiler->nesting -= 1;
369
437
 
370
- // If the call was < 1% of the stall threshold, we can ignore it:
371
- double duration = Fiber_Profiler_Time_delta(&call->enter_time, &call->exit_time);
372
- if (duration < profiler->filter_threshold) {
373
- Fiber_Profiler_Deque_pop(&profiler->calls);
438
+ // We need to keep track of how deep the call stack goes:
439
+ if (profiler->nesting < profiler->nesting_minimum) {
440
+ profiler->nesting_minimum = profiler->nesting;
441
+ }
442
+
443
+ Fiber_Profiler_Capture_Call_finish(profiler, call);
444
+ }
445
+
446
+ else {
447
+ struct Fiber_Profiler_Capture_Call *last_call = Fiber_Profiler_Deque_last(&profiler->calls);
448
+ struct Fiber_Profiler_Capture_Call *call = Fiber_Profiler_Capture_Call_new(profiler, event_flag, id, klass);
449
+
450
+ if (last_call) {
451
+ call->enter_time = last_call->enter_time;
452
+ } else {
453
+ call->enter_time = profiler->start_time;
374
454
  }
455
+
456
+ call->duration = Fiber_Profiler_Time_delta_current(&call->enter_time);
375
457
  }
376
458
  }
377
459
 
378
- VALUE Fiber_Profiler_Capture_start(VALUE self) {
460
+ void Fiber_Profiler_Capture_pause(VALUE self) {
379
461
  struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
380
462
 
381
- if (profiler->running) return Qfalse;
463
+ if (!profiler->capture) return;
382
464
 
383
- profiler->running = 1;
465
+ profiler->capture = 0;
384
466
 
385
- Fiber_Profiler_Capture_reset(profiler);
386
- Fiber_Profiler_Time_current(&profiler->start_time);
467
+ rb_thread_remove_event_hook_with_data(profiler->thread, Fiber_Profiler_Capture_callback, self);
468
+ }
469
+
470
+ void Fiber_Profiler_Capture_resume(VALUE self) {
471
+ struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
472
+
473
+ if (profiler->capture) return;
474
+
475
+ profiler->capture = 1;
387
476
 
388
- rb_event_flag_t event_flags = RUBY_EVENT_FIBER_SWITCH;
477
+ rb_event_flag_t event_flags = 0;
389
478
 
390
479
  if (profiler->track_calls) {
480
+ // event_flags |= RUBY_EVENT_LINE;
481
+
391
482
  event_flags |= RUBY_EVENT_CALL | RUBY_EVENT_RETURN;
392
483
  event_flags |= RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN;
393
- // event_flags |= RUBY_EVENT_B_CALL | RUBY_EVENT_B_RETURN;
484
+ event_flags |= RUBY_EVENT_B_CALL | RUBY_EVENT_B_RETURN;
394
485
  }
395
486
 
396
- VALUE thread = rb_thread_current();
397
- rb_thread_add_event_hook(thread, Fiber_Profiler_Capture_callback, event_flags, self);
487
+ // CRuby will raise an exception if you try to add "INTERNAL_EVENT" hooks at the same time as other hooks, so we do it in two calls:
488
+ rb_thread_add_event_hook(profiler->thread, Fiber_Profiler_Capture_callback, event_flags, self);
489
+ rb_thread_add_event_hook(profiler->thread, Fiber_Profiler_Capture_callback, RUBY_INTERNAL_EVENT_GC_START | RUBY_INTERNAL_EVENT_GC_END_SWEEP, self);
490
+ }
491
+
492
+ void Fiber_Profiler_Capture_fiber_switch(VALUE self);
493
+
494
+ void Fiber_Profiler_Capture_fiber_switch_callback(rb_event_flag_t event_flag, VALUE data, VALUE self, ID id, VALUE klass) {
495
+ Fiber_Profiler_Capture_fiber_switch(data);
496
+ }
497
+
498
+ VALUE Fiber_Profiler_Capture_start(VALUE self) {
499
+ struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
500
+
501
+ if (profiler->running) return Qfalse;
502
+
503
+ profiler->running = 1;
504
+ profiler->thread = rb_thread_current();
505
+
506
+ Fiber_Profiler_Capture_reset(profiler);
507
+ Fiber_Profiler_Time_current(&profiler->start_time);
398
508
 
399
- // if (profiler->track_garbage_collection) {
400
- rb_thread_add_event_hook(thread, Fiber_Profiler_Capture_callback, RUBY_INTERNAL_EVENT_GC_START | RUBY_INTERNAL_EVENT_GC_END_SWEEP, self);
401
- // }
509
+ rb_thread_add_event_hook(profiler->thread, Fiber_Profiler_Capture_fiber_switch_callback, RUBY_EVENT_FIBER_SWITCH, self);
402
510
 
403
511
  return self;
404
512
  }
@@ -408,34 +516,34 @@ VALUE Fiber_Profiler_Capture_stop(VALUE self) {
408
516
 
409
517
  if (!profiler->running) return Qfalse;
410
518
 
411
- profiler->running = 0;
519
+ Fiber_Profiler_Capture_pause(self);
412
520
 
413
- VALUE thread = rb_thread_current();
414
- rb_thread_remove_event_hook_with_data(thread, Fiber_Profiler_Capture_callback, self);
521
+ rb_thread_remove_event_hook_with_data(profiler->thread, Fiber_Profiler_Capture_fiber_switch_callback, self);
415
522
 
523
+ profiler->running = 0;
524
+ profiler->thread = Qnil;
525
+
416
526
  Fiber_Profiler_Time_current(&profiler->stop_time);
417
527
  Fiber_Profiler_Capture_reset(profiler);
418
528
 
419
529
  return self;
420
530
  }
421
531
 
422
- static inline float Fiber_Profiler_Capture_duration(struct Fiber_Profiler_Capture *profiler) {
423
- struct timespec duration;
424
-
425
- Fiber_Profiler_Time_current(&profiler->stop_time);
426
- Fiber_Profiler_Time_elapsed(&profiler->start_time, &profiler->stop_time, &duration);
427
-
428
- return Fiber_Profiler_Time_duration(&duration);
429
- }
430
-
431
532
  void Fiber_Profiler_Capture_finish(struct Fiber_Profiler_Capture *profiler) {
432
533
  profiler->capture = 0;
433
534
 
535
+ struct timespec stop_time;
536
+ Fiber_Profiler_Time_current(&stop_time);
537
+
434
538
  struct Fiber_Profiler_Capture_Call *current = profiler->current;
435
539
  while (current) {
436
- Fiber_Profiler_Time_current(&current->exit_time);
540
+ struct Fiber_Profiler_Capture_Call *parent = current->parent;
437
541
 
438
- current = current->parent;
542
+ current->duration = Fiber_Profiler_Time_delta(&current->enter_time, &stop_time);
543
+
544
+ Fiber_Profiler_Capture_Call_finish(profiler, current);
545
+
546
+ current = parent;
439
547
  }
440
548
  }
441
549
 
@@ -454,80 +562,119 @@ int Fiber_Profiler_Capture_sample(struct Fiber_Profiler_Capture *profiler) {
454
562
  }
455
563
  }
456
564
 
457
- void Fiber_Profiler_Capture_fiber_switch(struct Fiber_Profiler_Capture *profiler)
565
+ void Fiber_Profiler_Capture_fiber_switch(VALUE self)
458
566
  {
459
- float duration = Fiber_Profiler_Capture_duration(profiler);
567
+ struct Fiber_Profiler_Capture *profiler = Fiber_Profiler_Capture_get(self);
568
+ Fiber_Profiler_Time_current(&profiler->stop_time);
569
+ double duration = Fiber_Profiler_Time_delta(&profiler->start_time, &profiler->stop_time);
460
570
 
461
571
  if (profiler->capture) {
572
+ Fiber_Profiler_Capture_pause(self);
573
+
462
574
  Fiber_Profiler_Capture_finish(profiler);
463
575
 
464
576
  if (duration > profiler->stall_threshold) {
465
577
  profiler->stalls += 1;
466
578
  Fiber_Profiler_Capture_print(profiler);
467
579
  }
580
+
581
+ Fiber_Profiler_Capture_reset(profiler);
468
582
  }
469
583
 
470
- Fiber_Profiler_Capture_reset(profiler);
471
-
472
584
  if (Fiber_Profiler_Capture_sample(profiler)) {
473
585
  // Reset the start time:
474
586
  Fiber_Profiler_Time_current(&profiler->start_time);
475
587
 
476
- profiler->capture = 1;
588
+ Fiber_Profiler_Capture_resume(self);
477
589
  }
478
590
  }
479
591
 
480
- static const float Fiber_Profiler_Capture_PRINT_MINIMUM_PROPORTION = 0.01;
592
+ // When sampling a fiber, we may encounter returns without a preceeding call. This isn't an error, and we should correctly visualize the call stack. We track both the relative nesting (which can be negative) and the minimum nesting level encountered during the profiling session, and use that to determine the absolute nesting level of each call when printing the call stack.
593
+ static size_t Fiber_Profiler_Capture_absolute_nesting(struct Fiber_Profiler_Capture *profiler, struct Fiber_Profiler_Capture_Call *call) {
594
+ return call->nesting - profiler->nesting_minimum;
595
+ }
596
+
597
+ // If a call is within this threshold of the parent call, it will be skipped when printing the call stack - it's considered inconsequential to the performance of the parent call.
598
+ static const double Fiber_Profiler_Capture_SKIP_THRESHOLD = 0.98;
481
599
 
482
600
  void Fiber_Profiler_Capture_print_tty(struct Fiber_Profiler_Capture *profiler, FILE *restrict stream) {
483
- struct timespec total_duration = {};
484
- Fiber_Profiler_Time_elapsed(&profiler->start_time, &profiler->stop_time, &total_duration);
601
+ double total_duration = Fiber_Profiler_Time_delta(&profiler->start_time, &profiler->stop_time);
485
602
 
486
- fprintf(stderr, "Fiber stalled for %.3f seconds\n", Fiber_Profiler_Time_duration(&total_duration));
603
+ fprintf(stderr, "## Fiber stalled for %.3f seconds ##\n", total_duration);
487
604
 
488
605
  size_t skipped = 0;
489
606
 
490
607
  Fiber_Profiler_Deque_each(&profiler->calls, struct Fiber_Profiler_Capture_Call, call) {
491
- struct timespec duration = {};
492
- Fiber_Profiler_Time_elapsed(&call->enter_time, &call->exit_time, &duration);
608
+ if (call->children) {
609
+ if (call->parent && call->parent->children == 1) {
610
+ if (call->duration > call->parent->duration * Fiber_Profiler_Capture_SKIP_THRESHOLD) {
611
+ if (!DEBUG_SKIPPED) {
612
+ // We remove the nesting level as we're skipping this call - and we use this to track the nesting of child calls which MAY be printed:
613
+ call->nesting = call->parent->nesting;
614
+ skipped += 1;
615
+ continue;
616
+ } else {
617
+ fprintf(stream, "\e[34m");
618
+ }
619
+ }
620
+ }
621
+ }
622
+
623
+ if (call->parent) {
624
+ call->nesting = call->parent->nesting + 1;
625
+ }
493
626
 
494
- // Skip calls that are too short to be meaningful:
495
- if (Fiber_Profiler_Time_proportion(&duration, &total_duration) < Fiber_Profiler_Capture_PRINT_MINIMUM_PROPORTION) {
627
+ if (skipped) {
628
+ fprintf(stream, "\e[2m");
496
629
 
497
- if (!DEBUG_SKIPPED) {
498
- skipped += 1;
499
- continue;
500
- } else {
501
- fprintf(stream, "\e[2m");
630
+ size_t nesting = Fiber_Profiler_Capture_absolute_nesting(profiler, call);
631
+ for (size_t i = 0; i < nesting; i += 1) {
632
+ fputc('\t', stream);
502
633
  }
634
+
635
+ fprintf(stream, "... skipped %zu nested calls ...\e[0m\n", skipped);
636
+
637
+ skipped = 0;
638
+ call->nesting += 1;
503
639
  }
504
640
 
505
- for (size_t i = 0; i < call->nesting; i += 1) {
641
+ size_t nesting = Fiber_Profiler_Capture_absolute_nesting(profiler, call);
642
+ for (size_t i = 0; i < nesting; i += 1) {
506
643
  fputc('\t', stream);
507
644
  }
508
645
 
646
+ if (Fiber_Profiler_Capture_Call_expensive_p(call, total_duration)) {
647
+ fprintf(stream, "\e[31m");
648
+ }
649
+
509
650
  VALUE class_inspect = rb_inspect(call->klass);
510
651
  const char *name = rb_id2name(call->id);
511
652
 
512
- 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));
653
+ struct timespec offset;
654
+ Fiber_Profiler_Time_elapsed(&profiler->start_time, &call->enter_time, &offset);
655
+
656
+ fprintf(stream, "%s:%d in %s '%s#%s' (%0.4fs, T+" Fiber_Profiler_TIME_PRINTF_TIMESPEC ")\n", call->path, call->line, event_flag_name(call->event_flag), RSTRING_PTR(class_inspect), name, call->duration, Fiber_Profiler_TIME_PRINTF_TIMESPEC_ARGUMENTS(offset));
657
+
658
+ fprintf(stream, "\e[0m");
513
659
 
514
- if (DEBUG_SKIPPED) {
515
- fprintf(stream, "\e[0m");
660
+ if (call->filtered) {
661
+ fprintf(stream, "\e[2m");
662
+
663
+ for (size_t i = 0; i < nesting + 1; i += 1) {
664
+ fputc('\t', stream);
665
+ }
666
+
667
+ fprintf(stream, "... filtered %zu direct calls ...\e[0m\n", call->filtered);
516
668
  }
517
669
  }
518
-
519
- if (skipped > 0) {
520
- fprintf(stream, "Skipped %zu calls that were too short to be meaningful.\n", skipped);
521
- }
522
670
  }
523
671
 
524
672
  void Fiber_Profiler_Capture_print_json(struct Fiber_Profiler_Capture *profiler, FILE *restrict stream) {
525
- struct timespec total_duration = {};
526
- Fiber_Profiler_Time_elapsed(&profiler->start_time, &profiler->stop_time, &total_duration);
673
+ double total_duration = Fiber_Profiler_Time_delta(&profiler->start_time, &profiler->stop_time);
527
674
 
528
675
  fputc('{', stream);
529
676
 
530
- fprintf(stream, "\"duration\":" Fiber_Profiler_TIME_PRINTF_TIMESPEC, Fiber_Profiler_TIME_PRINTF_TIMESPEC_ARGUMENTS(total_duration));
677
+ fprintf(stream, "\"duration\":%0.6f", total_duration);
531
678
 
532
679
  size_t skipped = 0;
533
680
 
@@ -535,20 +682,32 @@ void Fiber_Profiler_Capture_print_json(struct Fiber_Profiler_Capture *profiler,
535
682
  int first = 1;
536
683
 
537
684
  Fiber_Profiler_Deque_each(&profiler->calls, struct Fiber_Profiler_Capture_Call, call) {
538
- struct timespec duration = {};
539
- Fiber_Profiler_Time_elapsed(&call->enter_time, &call->exit_time, &duration);
685
+ if (call->children) {
686
+ if (call->parent && call->parent->children == 1) {
687
+ if (call->duration > call->parent->duration * Fiber_Profiler_Capture_SKIP_THRESHOLD) {
688
+ // We remove the nesting level as we're skipping this call - and we use this to track the nesting of child calls which MAY be printed:
689
+ call->nesting = call->parent->nesting;
690
+ skipped += 1;
691
+ continue;
692
+ }
693
+ }
694
+ }
540
695
 
541
- // Skip calls that are too short to be meaningful:
542
- if (Fiber_Profiler_Time_proportion(&duration, &total_duration) < Fiber_Profiler_Capture_PRINT_MINIMUM_PROPORTION) {
543
- skipped += 1;
544
- continue;
696
+ if (call->parent) {
697
+ call->nesting = call->parent->nesting + 1;
545
698
  }
546
699
 
547
700
  VALUE class_inspect = rb_inspect(call->klass);
548
701
  const char *name = rb_id2name(call->id);
549
702
 
550
- 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);
703
+ size_t nesting = Fiber_Profiler_Capture_absolute_nesting(profiler, call);
704
+
705
+ struct timespec offset;
706
+ Fiber_Profiler_Time_elapsed(&profiler->start_time, &call->enter_time, &offset);
707
+
708
+ fprintf(stream, "%s{\"path\":\"%s\",\"line\":%d,\"class\":\"%s\",\"method\":\"%s\",\"duration\":%0.6f,\"offset\":" Fiber_Profiler_TIME_PRINTF_TIMESPEC ",\"nesting\":%zu,\"skipped\":%zu,\"filtered\":%zu}", first ? "" : ",", call->path, call->line, RSTRING_PTR(class_inspect), name, call->duration, Fiber_Profiler_TIME_PRINTF_TIMESPEC_ARGUMENTS(offset), nesting, skipped, call->filtered);
551
709
 
710
+ skipped = 0;
552
711
  first = 0;
553
712
  }
554
713
 
@@ -11,7 +11,7 @@ enum {
11
11
  #ifdef RUBY_DEBUG
12
12
  Fiber_Profiler_Deque_DEBUG = 1,
13
13
  #else
14
- Fiber_Profiler_Deque_DEBUG = 0,
14
+ Fiber_Profiler_Deque_DEBUG = 1,
15
15
  #endif
16
16
  };
17
17
 
@@ -172,6 +172,53 @@ inline static size_t Fiber_Profiler_Deque_default_capacity(struct Fiber_Profiler
172
172
  return (target_size - sizeof(struct Fiber_Profiler_Deque_Page)) / deque->element_size;
173
173
  }
174
174
 
175
+ static inline void Fiber_Profiler_Deque_reserve(struct Fiber_Profiler_Deque *deque, size_t capacity)
176
+ {
177
+ struct Fiber_Profiler_Deque_Page *page = deque->head;
178
+
179
+ // Walk over all the pages and see if we have enough capacity:
180
+ while (page) {
181
+ size_t available = page->capacity - page->size;
182
+
183
+ if (available > capacity) {
184
+ return;
185
+ }
186
+
187
+ capacity -= available;
188
+
189
+ if (page->tail) {
190
+ page = page->tail;
191
+ } else {
192
+ break;
193
+ }
194
+ }
195
+
196
+ // We need to allocate a new page:
197
+ size_t minimum_capacity = Fiber_Profiler_Deque_default_capacity(deque);
198
+ if (capacity < minimum_capacity) {
199
+ capacity = minimum_capacity;
200
+ }
201
+
202
+ struct Fiber_Profiler_Deque_Page *reserved_page = Fiber_Profiler_Deque_Page_allocate(deque->element_size, capacity);
203
+ if (reserved_page == NULL) {
204
+ return;
205
+ }
206
+
207
+ if (page) {
208
+ page->tail = reserved_page;
209
+ reserved_page->head = page;
210
+ } else {
211
+ deque->head = deque->tail = reserved_page;
212
+ }
213
+
214
+ if (Fiber_Profiler_Deque_DEBUG) Fiber_Profiler_Deque_debug(deque, __FUNCTION__);
215
+ }
216
+
217
+ static inline void Fiber_Profiler_Deque_reserve_default(struct Fiber_Profiler_Deque *deque)
218
+ {
219
+ Fiber_Profiler_Deque_reserve(deque, Fiber_Profiler_Deque_default_capacity(deque));
220
+ }
221
+
175
222
  void *Fiber_Profiler_Deque_push(struct Fiber_Profiler_Deque *deque)
176
223
  {
177
224
  struct Fiber_Profiler_Deque_Page *page = deque->tail;
@@ -13,23 +13,3 @@ void Fiber_Profiler_Time_elapsed(const struct timespec* start, const struct time
13
13
  duration->tv_nsec = stop->tv_nsec - start->tv_nsec;
14
14
  }
15
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
- }
@@ -7,11 +7,34 @@
7
7
  #include <time.h>
8
8
 
9
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
10
 
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);
11
+ static inline double Fiber_Profiler_Time_duration(const struct timespec *duration)
12
+ {
13
+ return duration->tv_sec + duration->tv_nsec / 1000000000.0;
14
+ }
15
+
16
+ static inline double Fiber_Profiler_Time_proportion(const struct timespec *duration, const struct timespec *total_duration)
17
+ {
18
+ return Fiber_Profiler_Time_duration(duration) / Fiber_Profiler_Time_duration(total_duration);
19
+ }
20
+
21
+ static inline void Fiber_Profiler_Time_current(struct timespec *time)
22
+ {
23
+ clock_gettime(CLOCK_MONOTONIC, time);
24
+ }
25
+
26
+ static inline double Fiber_Profiler_Time_delta(const struct timespec *start, const struct timespec *stop)
27
+ {
28
+ return stop->tv_sec - start->tv_sec + (stop->tv_nsec - start->tv_nsec) / 1e9;
29
+ }
30
+
31
+ static inline double Fiber_Profiler_Time_delta_current(const struct timespec *start)
32
+ {
33
+ struct timespec stop;
34
+ Fiber_Profiler_Time_current(&stop);
35
+
36
+ return Fiber_Profiler_Time_delta(start, &stop);
37
+ }
15
38
 
16
39
  #define Fiber_Profiler_TIME_PRINTF_TIMESPEC "%.3g"
17
40
  #define Fiber_Profiler_TIME_PRINTF_TIMESPEC_ARGUMENTS(ts) ((double)(ts).tv_sec + (ts).tv_nsec / 1e9)
data/ext/time.o CHANGED
Binary file
@@ -7,6 +7,6 @@
7
7
  class Fiber
8
8
  # @namespace
9
9
  module Profiler
10
- VERSION = "0.1.2"
10
+ VERSION = "0.1.4"
11
11
  end
12
12
  end
data/readme.md CHANGED
@@ -18,6 +18,10 @@ Please see the [project documentation](https://socketry.github.io/fiber-profiler
18
18
 
19
19
  Please see the [project releases](https://socketry.github.io/fiber-profiler/releases/index) for all releases.
20
20
 
21
+ ### v0.1.3
22
+
23
+ - Improved performance when not profiling (when sampling is enabled).
24
+
21
25
  ### v0.1.0
22
26
 
23
27
  - Initial implementation extracted from `io-event` gem.
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v0.1.3
4
+
5
+ - Improved performance when not profiling (when sampling is enabled).
6
+
3
7
  ## v0.1.0
4
8
 
5
9
  - Initial implementation extracted from `io-event` gem.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fiber-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -36,7 +36,7 @@ cert_chain:
36
36
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
37
37
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
38
38
  -----END CERTIFICATE-----
39
- date: 2025-02-13 00:00:00.000000000 Z
39
+ date: 2025-02-14 00:00:00.000000000 Z
40
40
  dependencies: []
41
41
  executables: []
42
42
  extensions:
@@ -61,7 +61,6 @@ files:
61
61
  - ext/fiber/profiler/profiler.h
62
62
  - ext/fiber/profiler/time.c
63
63
  - ext/fiber/profiler/time.h
64
- - ext/mkmf.log
65
64
  - ext/profiler.o
66
65
  - ext/time.o
67
66
  - lib/fiber/profiler.rb
metadata.gz.sig CHANGED
Binary file
data/ext/mkmf.log DELETED
@@ -1,69 +0,0 @@
1
- have_func: checking for rb_fiber_current()... -------------------- yes
2
-
3
- DYLD_LIBRARY_PATH=.:/Users/samuel/.rubies/ruby-3.4.1/lib ASAN_OPTIONS=detect_leaks=0 "clang -o conftest -I/Users/samuel/.rubies/ruby-3.4.1/include/ruby-3.4.0/arm64-darwin24 -I/Users/samuel/.rubies/ruby-3.4.1/include/ruby-3.4.0/ruby/backward -I/Users/samuel/.rubies/ruby-3.4.1/include/ruby-3.4.0 -I. -I/opt/local/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -fstack-protector-strong -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fdeclspec -O3 -fno-fast-math -ggdb3 -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wmisleading-indentation -Wundef -pipe -Wall -Wno-unknown-pragmas -std=c99 conftest.c -L. -L/Users/samuel/.rubies/ruby-3.4.1/lib -L/opt/local/lib -L. -fstack-protector-strong -L/opt/local/lib -arch arm64 -lruby.3.4-static -framework CoreFoundation -lgmp -ldl -lobjc -lpthread -lpthread "
4
- ld: warning: ignoring duplicate libraries: '-lpthread'
5
- checked program was:
6
- /* begin */
7
- 1: #include "ruby.h"
8
- 2:
9
- 3: int main(int argc, char **argv)
10
- 4: {
11
- 5: return !!argv[argc];
12
- 6: }
13
- /* end */
14
-
15
- DYLD_LIBRARY_PATH=.:/Users/samuel/.rubies/ruby-3.4.1/lib ASAN_OPTIONS=detect_leaks=0 "clang -o conftest -I/Users/samuel/.rubies/ruby-3.4.1/include/ruby-3.4.0/arm64-darwin24 -I/Users/samuel/.rubies/ruby-3.4.1/include/ruby-3.4.0/ruby/backward -I/Users/samuel/.rubies/ruby-3.4.1/include/ruby-3.4.0 -I. -I/opt/local/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -fstack-protector-strong -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fdeclspec -O3 -fno-fast-math -ggdb3 -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wmisleading-indentation -Wundef -pipe -Wall -Wno-unknown-pragmas -std=c99 conftest.c -L. -L/Users/samuel/.rubies/ruby-3.4.1/lib -L/opt/local/lib -L. -fstack-protector-strong -L/opt/local/lib -arch arm64 -lruby.3.4-static -framework CoreFoundation -lgmp -ldl -lobjc -lpthread -lpthread "
16
- ld: warning: ignoring duplicate libraries: '-lpthread'
17
- checked program was:
18
- /* begin */
19
- 1: #include "ruby.h"
20
- 2:
21
- 3: /*top*/
22
- 4: extern int t(void);
23
- 5: int main(int argc, char **argv)
24
- 6: {
25
- 7: if (argc > 1000000) {
26
- 8: int (* volatile tp)(void)=(int (*)(void))&t;
27
- 9: printf("%d", (*tp)());
28
- 10: }
29
- 11:
30
- 12: return !!argv[argc];
31
- 13: }
32
- 14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_fiber_current; return !p; }
33
- /* end */
34
-
35
- --------------------
36
-
37
- have_func: checking for rb_ext_ractor_safe()... -------------------- yes
38
-
39
- DYLD_LIBRARY_PATH=.:/Users/samuel/.rubies/ruby-3.4.1/lib ASAN_OPTIONS=detect_leaks=0 "clang -o conftest -I/Users/samuel/.rubies/ruby-3.4.1/include/ruby-3.4.0/arm64-darwin24 -I/Users/samuel/.rubies/ruby-3.4.1/include/ruby-3.4.0/ruby/backward -I/Users/samuel/.rubies/ruby-3.4.1/include/ruby-3.4.0 -I. -I/opt/local/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -fstack-protector-strong -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fdeclspec -O3 -fno-fast-math -ggdb3 -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wmisleading-indentation -Wundef -pipe -Wall -Wno-unknown-pragmas -std=c99 conftest.c -L. -L/Users/samuel/.rubies/ruby-3.4.1/lib -L/opt/local/lib -L. -fstack-protector-strong -L/opt/local/lib -arch arm64 -lruby.3.4-static -framework CoreFoundation -lgmp -ldl -lobjc -lpthread -lpthread "
40
- ld: warning: ignoring duplicate libraries: '-lpthread'
41
- checked program was:
42
- /* begin */
43
- 1: #include "ruby.h"
44
- 2:
45
- 3: /*top*/
46
- 4: extern int t(void);
47
- 5: int main(int argc, char **argv)
48
- 6: {
49
- 7: if (argc > 1000000) {
50
- 8: int (* volatile tp)(void)=(int (*)(void))&t;
51
- 9: printf("%d", (*tp)());
52
- 10: }
53
- 11:
54
- 12: return !!argv[argc];
55
- 13: }
56
- 14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_ext_ractor_safe; return !p; }
57
- /* end */
58
-
59
- --------------------
60
-
61
- extconf.h is:
62
- /* begin */
63
- 1: #ifndef EXTCONF_H
64
- 2: #define EXTCONF_H
65
- 3: #define HAVE_RB_FIBER_CURRENT 1
66
- 4: #define HAVE_RB_EXT_RACTOR_SAFE 1
67
- 5: #endif
68
- /* end */
69
-