stackprof 0.2.17 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b55691b8d1966ba4b2b2458a4908b2a2d5b65f2074dfe3b3b1b6350f752704ec
4
- data.tar.gz: 79e2a0508a1c722f39cc61d39b0577cfb5520669a7a2db4cadac6c49dcb1267a
3
+ metadata.gz: 2c802ba8d0dfa813e939679507d70376e7dc33924369bb9d767523cfa5faed31
4
+ data.tar.gz: 5defbe29a222a82e3b6901ed10b0c73d962ed6a650b14b0c3cd3c231c27c0e39
5
5
  SHA512:
6
- metadata.gz: 2fa22779f03c332a3680f526bf1df29553588773fabeb00da327af3525018e535e973bafd990254c6ad50516faf5e8b1d087bb7c208c99d0b512d99ccdef53bb
7
- data.tar.gz: 73ba1328c793b0c0c4657e7826f4bf2cd52102c61a2ca2e3e0b1c5240ffe96ee0ec328ea831b1592c10b4e13c6aec2bb9d28fd05e93ddef999d5131e55124362
6
+ metadata.gz: 0e585cf066f4008907975726f22185184210e375ca6fe28e51fe44fca7fe53e2cdc414482dcba3e413da2238a879cb5fa58530106ad5f8cb217fc6cee1bf4a2b
7
+ data.tar.gz: 7b2d08fbd2d70c803f06f42c15e7978871f01c343f3d149f2a63b781d4e074542f445e9a809bdaab1921e779ec9d868159fedc752103faf86c45ddfe7239b74a
@@ -7,16 +7,19 @@
7
7
  **********************************************************************/
8
8
 
9
9
  #include <ruby/ruby.h>
10
+ #include <ruby/version.h>
10
11
  #include <ruby/debug.h>
11
12
  #include <ruby/st.h>
12
13
  #include <ruby/io.h>
13
14
  #include <ruby/intern.h>
14
15
  #include <signal.h>
15
16
  #include <sys/time.h>
17
+ #include <time.h>
16
18
  #include <pthread.h>
17
19
 
18
20
  #define BUF_SIZE 2048
19
21
  #define MICROSECONDS_IN_SECOND 1000000
22
+ #define NANOSECONDS_IN_SECOND 1000000000
20
23
 
21
24
  #define FAKE_FRAME_GC INT2FIX(0)
22
25
  #define FAKE_FRAME_MARK INT2FIX(1)
@@ -38,6 +41,47 @@ static const char *fake_frame_cstrs[] = {
38
41
 
39
42
  #define TOTAL_FAKE_FRAMES (sizeof(fake_frame_cstrs) / sizeof(char *))
40
43
 
44
+ #ifdef _POSIX_MONOTONIC_CLOCK
45
+ #define timestamp_t timespec
46
+ typedef struct timestamp_t timestamp_t;
47
+
48
+ static void capture_timestamp(timestamp_t *ts) {
49
+ clock_gettime(CLOCK_MONOTONIC, ts);
50
+ }
51
+
52
+ static int64_t delta_usec(timestamp_t *start, timestamp_t *end) {
53
+ int64_t result = MICROSECONDS_IN_SECOND * (end->tv_sec - start->tv_sec);
54
+ if (end->tv_nsec < start->tv_nsec) {
55
+ result -= MICROSECONDS_IN_SECOND;
56
+ result += (NANOSECONDS_IN_SECOND + end->tv_nsec - start->tv_nsec) / 1000;
57
+ } else {
58
+ result += (end->tv_nsec - start->tv_nsec) / 1000;
59
+ }
60
+ return result;
61
+ }
62
+
63
+ static uint64_t timestamp_usec(timestamp_t *ts) {
64
+ return (MICROSECONDS_IN_SECOND * ts->tv_sec) + (ts->tv_nsec / 1000);
65
+ }
66
+ #else
67
+ #define timestamp_t timeval
68
+ typedef struct timestamp_t timestamp_t;
69
+
70
+ static void capture_timestamp(timestamp_t *ts) {
71
+ gettimeofday(ts, NULL);
72
+ }
73
+
74
+ static int64_t delta_usec(timestamp_t *start, timestamp_t *end) {
75
+ struct timeval diff;
76
+ timersub(end, start, &diff);
77
+ return (MICROSECONDS_IN_SECOND * diff.tv_sec) + diff.tv_usec;
78
+ }
79
+
80
+ static uint64_t timestamp_usec(timestamp_t *ts) {
81
+ return (MICROSECONDS_IN_SECOND * ts.tv_sec) + diff.tv_usec
82
+ }
83
+ #endif
84
+
41
85
  typedef struct {
42
86
  size_t total_samples;
43
87
  size_t caller_samples;
@@ -46,6 +90,11 @@ typedef struct {
46
90
  st_table *lines;
47
91
  } frame_data_t;
48
92
 
93
+ typedef struct {
94
+ uint64_t timestamp_usec;
95
+ int64_t delta_usec;
96
+ } sample_time_t;
97
+
49
98
  static struct {
50
99
  int running;
51
100
  int raw;
@@ -62,10 +111,10 @@ static struct {
62
111
  size_t raw_samples_capa;
63
112
  size_t raw_sample_index;
64
113
 
65
- struct timeval last_sample_at;
66
- int *raw_timestamp_deltas;
67
- size_t raw_timestamp_deltas_len;
68
- size_t raw_timestamp_deltas_capa;
114
+ struct timestamp_t last_sample_at;
115
+ sample_time_t *raw_sample_times;
116
+ size_t raw_sample_times_len;
117
+ size_t raw_sample_times_capa;
69
118
 
70
119
  size_t overall_signals;
71
120
  size_t overall_samples;
@@ -77,6 +126,9 @@ static struct {
77
126
 
78
127
  VALUE fake_frame_names[TOTAL_FAKE_FRAMES];
79
128
  VALUE empty_string;
129
+
130
+ int buffer_count;
131
+ sample_time_t buffer_time;
80
132
  VALUE frames_buffer[BUF_SIZE];
81
133
  int lines_buffer[BUF_SIZE];
82
134
  } _stackprof;
@@ -84,7 +136,7 @@ static struct {
84
136
  static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
85
137
  static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
86
138
  static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_metadata, sym_frames, sym_ignore_gc, sym_out;
87
- static VALUE sym_aggregate, sym_raw_timestamp_deltas, sym_state, sym_marking, sym_sweeping;
139
+ static VALUE sym_aggregate, sym_raw_sample_timestamps, sym_raw_timestamp_deltas, sym_state, sym_marking, sym_sweeping;
88
140
  static VALUE sym_gc_samples, objtracer;
89
141
  static VALUE gc_hook;
90
142
  static VALUE rb_mStackProf;
@@ -174,7 +226,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
174
226
  _stackprof.out = out;
175
227
 
176
228
  if (raw) {
177
- gettimeofday(&_stackprof.last_sample_at, NULL);
229
+ capture_timestamp(&_stackprof.last_sample_at);
178
230
  }
179
231
 
180
232
  return Qtrue;
@@ -209,13 +261,19 @@ stackprof_stop(VALUE self)
209
261
  return Qtrue;
210
262
  }
211
263
 
264
+ #if SIZEOF_VOIDP == SIZEOF_LONG
265
+ # define PTR2NUM(x) (LONG2NUM((long)(x)))
266
+ #else
267
+ # define PTR2NUM(x) (LL2NUM((LONG_LONG)(x)))
268
+ #endif
269
+
212
270
  static int
213
271
  frame_edges_i(st_data_t key, st_data_t val, st_data_t arg)
214
272
  {
215
273
  VALUE edges = (VALUE)arg;
216
274
 
217
275
  intptr_t weight = (intptr_t)val;
218
- rb_hash_aset(edges, rb_obj_id((VALUE)key), INT2FIX(weight));
276
+ rb_hash_aset(edges, PTR2NUM(key), INT2FIX(weight));
219
277
  return ST_CONTINUE;
220
278
  }
221
279
 
@@ -242,7 +300,7 @@ frame_i(st_data_t key, st_data_t val, st_data_t arg)
242
300
  VALUE name, file, edges, lines;
243
301
  VALUE line;
244
302
 
245
- rb_hash_aset(results, rb_obj_id(frame), details);
303
+ rb_hash_aset(results, PTR2NUM(frame), details);
246
304
 
247
305
  if (FIXNUM_P(frame)) {
248
306
  name = _stackprof.fake_frame_names[FIX2INT(frame)];
@@ -314,7 +372,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
314
372
 
315
373
  if (_stackprof.raw && _stackprof.raw_samples_len) {
316
374
  size_t len, n, o;
317
- VALUE raw_timestamp_deltas;
375
+ VALUE raw_sample_timestamps, raw_timestamp_deltas;
318
376
  VALUE raw_samples = rb_ary_new_capa(_stackprof.raw_samples_len);
319
377
 
320
378
  for (n = 0; n < _stackprof.raw_samples_len; n++) {
@@ -322,7 +380,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
322
380
  rb_ary_push(raw_samples, SIZET2NUM(len));
323
381
 
324
382
  for (o = 0, n++; o < len; n++, o++)
325
- rb_ary_push(raw_samples, rb_obj_id(_stackprof.raw_samples[n]));
383
+ rb_ary_push(raw_samples, PTR2NUM(_stackprof.raw_samples[n]));
326
384
  rb_ary_push(raw_samples, SIZET2NUM((size_t)_stackprof.raw_samples[n]));
327
385
  }
328
386
 
@@ -334,17 +392,20 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
334
392
 
335
393
  rb_hash_aset(results, sym_raw, raw_samples);
336
394
 
337
- raw_timestamp_deltas = rb_ary_new_capa(_stackprof.raw_timestamp_deltas_len);
395
+ raw_sample_timestamps = rb_ary_new_capa(_stackprof.raw_sample_times_len);
396
+ raw_timestamp_deltas = rb_ary_new_capa(_stackprof.raw_sample_times_len);
338
397
 
339
- for (n = 0; n < _stackprof.raw_timestamp_deltas_len; n++) {
340
- rb_ary_push(raw_timestamp_deltas, INT2FIX(_stackprof.raw_timestamp_deltas[n]));
398
+ for (n = 0; n < _stackprof.raw_sample_times_len; n++) {
399
+ rb_ary_push(raw_sample_timestamps, ULL2NUM(_stackprof.raw_sample_times[n].timestamp_usec));
400
+ rb_ary_push(raw_timestamp_deltas, LL2NUM(_stackprof.raw_sample_times[n].delta_usec));
341
401
  }
342
402
 
343
- free(_stackprof.raw_timestamp_deltas);
344
- _stackprof.raw_timestamp_deltas = NULL;
345
- _stackprof.raw_timestamp_deltas_len = 0;
346
- _stackprof.raw_timestamp_deltas_capa = 0;
403
+ free(_stackprof.raw_sample_times);
404
+ _stackprof.raw_sample_times = NULL;
405
+ _stackprof.raw_sample_times_len = 0;
406
+ _stackprof.raw_sample_times_capa = 0;
347
407
 
408
+ rb_hash_aset(results, sym_raw_sample_timestamps, raw_sample_timestamps);
348
409
  rb_hash_aset(results, sym_raw_timestamp_deltas, raw_timestamp_deltas);
349
410
 
350
411
  _stackprof.raw = 0;
@@ -424,14 +485,14 @@ st_numtable_increment(st_table *table, st_data_t key, size_t increment)
424
485
  }
425
486
 
426
487
  void
427
- stackprof_record_sample_for_stack(int num, int timestamp_delta)
488
+ stackprof_record_sample_for_stack(int num, uint64_t sample_timestamp, int64_t timestamp_delta)
428
489
  {
429
490
  int i, n;
430
491
  VALUE prev_frame = Qnil;
431
492
 
432
493
  _stackprof.overall_samples++;
433
494
 
434
- if (_stackprof.raw) {
495
+ if (_stackprof.raw && num > 0) {
435
496
  int found = 0;
436
497
 
437
498
  /* If there's no sample buffer allocated, then allocate one. The buffer
@@ -483,20 +544,23 @@ stackprof_record_sample_for_stack(int num, int timestamp_delta)
483
544
  }
484
545
 
485
546
  /* If there's no timestamp delta buffer, allocate one */
486
- if (!_stackprof.raw_timestamp_deltas) {
487
- _stackprof.raw_timestamp_deltas_capa = 100;
488
- _stackprof.raw_timestamp_deltas = malloc(sizeof(int) * _stackprof.raw_timestamp_deltas_capa);
489
- _stackprof.raw_timestamp_deltas_len = 0;
547
+ if (!_stackprof.raw_sample_times) {
548
+ _stackprof.raw_sample_times_capa = 100;
549
+ _stackprof.raw_sample_times = malloc(sizeof(sample_time_t) * _stackprof.raw_sample_times_capa);
550
+ _stackprof.raw_sample_times_len = 0;
490
551
  }
491
552
 
492
553
  /* Double the buffer size if it's too small */
493
- while (_stackprof.raw_timestamp_deltas_capa <= _stackprof.raw_timestamp_deltas_len + 1) {
494
- _stackprof.raw_timestamp_deltas_capa *= 2;
495
- _stackprof.raw_timestamp_deltas = realloc(_stackprof.raw_timestamp_deltas, sizeof(int) * _stackprof.raw_timestamp_deltas_capa);
554
+ while (_stackprof.raw_sample_times_capa <= _stackprof.raw_sample_times_len + 1) {
555
+ _stackprof.raw_sample_times_capa *= 2;
556
+ _stackprof.raw_sample_times = realloc(_stackprof.raw_sample_times, sizeof(sample_time_t) * _stackprof.raw_sample_times_capa);
496
557
  }
497
558
 
498
- /* Store the time delta (which is the amount of time between samples) */
499
- _stackprof.raw_timestamp_deltas[_stackprof.raw_timestamp_deltas_len++] = timestamp_delta;
559
+ /* Store the time delta (which is the amount of microseconds between samples). */
560
+ _stackprof.raw_sample_times[_stackprof.raw_sample_times_len++] = (sample_time_t) {
561
+ .timestamp_usec = sample_timestamp,
562
+ .delta_usec = timestamp_delta,
563
+ };
500
564
  }
501
565
 
502
566
  for (i = 0; i < num; i++) {
@@ -529,48 +593,59 @@ stackprof_record_sample_for_stack(int num, int timestamp_delta)
529
593
  }
530
594
 
531
595
  if (_stackprof.raw) {
532
- gettimeofday(&_stackprof.last_sample_at, NULL);
596
+ capture_timestamp(&_stackprof.last_sample_at);
533
597
  }
534
598
  }
535
599
 
600
+ // buffer the current profile frames
601
+ // This must be async-signal-safe
602
+ // Returns immediately if another set of frames are already in the buffer
536
603
  void
537
- stackprof_record_sample()
604
+ stackprof_buffer_sample(void)
538
605
  {
539
- int timestamp_delta = 0;
606
+ if (_stackprof.buffer_count > 0) {
607
+ // Another sample is already pending
608
+ return;
609
+ }
610
+
611
+ uint64_t start_timestamp = 0;
612
+ int64_t timestamp_delta = 0;
540
613
  int num;
541
614
  if (_stackprof.raw) {
542
- struct timeval t;
543
- struct timeval diff;
544
- gettimeofday(&t, NULL);
545
- timersub(&t, &_stackprof.last_sample_at, &diff);
546
- timestamp_delta = (1000 * diff.tv_sec) + diff.tv_usec;
615
+ struct timestamp_t t;
616
+ capture_timestamp(&t);
617
+ start_timestamp = timestamp_usec(&t);
618
+ timestamp_delta = delta_usec(&t, &_stackprof.last_sample_at);
547
619
  }
620
+
548
621
  num = rb_profile_frames(0, sizeof(_stackprof.frames_buffer) / sizeof(VALUE), _stackprof.frames_buffer, _stackprof.lines_buffer);
549
- stackprof_record_sample_for_stack(num, timestamp_delta);
622
+
623
+ _stackprof.buffer_count = num;
624
+ _stackprof.buffer_time.timestamp_usec = start_timestamp;
625
+ _stackprof.buffer_time.delta_usec = timestamp_delta;
550
626
  }
551
627
 
552
628
  void
553
- stackprof_record_gc_samples()
629
+ stackprof_record_gc_samples(void)
554
630
  {
555
- int delta_to_first_unrecorded_gc_sample = 0;
556
- int i;
631
+ int64_t delta_to_first_unrecorded_gc_sample = 0;
632
+ uint64_t start_timestamp = 0;
633
+ size_t i;
557
634
  if (_stackprof.raw) {
558
- struct timeval t;
559
- struct timeval diff;
560
- gettimeofday(&t, NULL);
561
- timersub(&t, &_stackprof.last_sample_at, &diff);
635
+ struct timestamp_t t;
636
+ capture_timestamp(&t);
637
+ start_timestamp = timestamp_usec(&t);
562
638
 
563
639
  // We don't know when the GC samples were actually marked, so let's
564
640
  // assume that they were marked at a perfectly regular interval.
565
- delta_to_first_unrecorded_gc_sample = (1000 * diff.tv_sec + diff.tv_usec) - (_stackprof.unrecorded_gc_samples - 1) * NUM2LONG(_stackprof.interval);
641
+ delta_to_first_unrecorded_gc_sample = delta_usec(&t, &_stackprof.last_sample_at) - (_stackprof.unrecorded_gc_samples - 1) * NUM2LONG(_stackprof.interval);
566
642
  if (delta_to_first_unrecorded_gc_sample < 0) {
567
643
  delta_to_first_unrecorded_gc_sample = 0;
568
644
  }
569
645
  }
570
646
 
571
-
572
647
  for (i = 0; i < _stackprof.unrecorded_gc_samples; i++) {
573
- int timestamp_delta = i == 0 ? delta_to_first_unrecorded_gc_sample : NUM2LONG(_stackprof.interval);
648
+ int64_t timestamp_delta = i == 0 ? delta_to_first_unrecorded_gc_sample : NUM2LONG(_stackprof.interval);
574
649
 
575
650
  if (_stackprof.unrecorded_gc_marking_samples) {
576
651
  _stackprof.frames_buffer[0] = FAKE_FRAME_MARK;
@@ -579,7 +654,7 @@ stackprof_record_gc_samples()
579
654
  _stackprof.lines_buffer[1] = 0;
580
655
  _stackprof.unrecorded_gc_marking_samples--;
581
656
 
582
- stackprof_record_sample_for_stack(2, timestamp_delta);
657
+ stackprof_record_sample_for_stack(2, start_timestamp, timestamp_delta);
583
658
  } else if (_stackprof.unrecorded_gc_sweeping_samples) {
584
659
  _stackprof.frames_buffer[0] = FAKE_FRAME_SWEEP;
585
660
  _stackprof.lines_buffer[0] = 0;
@@ -588,11 +663,11 @@ stackprof_record_gc_samples()
588
663
 
589
664
  _stackprof.unrecorded_gc_sweeping_samples--;
590
665
 
591
- stackprof_record_sample_for_stack(2, timestamp_delta);
666
+ stackprof_record_sample_for_stack(2, start_timestamp, timestamp_delta);
592
667
  } else {
593
668
  _stackprof.frames_buffer[0] = FAKE_FRAME_GC;
594
669
  _stackprof.lines_buffer[0] = 0;
595
- stackprof_record_sample_for_stack(1, timestamp_delta);
670
+ stackprof_record_sample_for_stack(1, start_timestamp, timestamp_delta);
596
671
  }
597
672
  }
598
673
  _stackprof.during_gc += _stackprof.unrecorded_gc_samples;
@@ -601,20 +676,47 @@ stackprof_record_gc_samples()
601
676
  _stackprof.unrecorded_gc_sweeping_samples = 0;
602
677
  }
603
678
 
679
+ // record the sample previously buffered by stackprof_buffer_sample
680
+ static void
681
+ stackprof_record_buffer(void)
682
+ {
683
+ stackprof_record_sample_for_stack(_stackprof.buffer_count, _stackprof.buffer_time.timestamp_usec, _stackprof.buffer_time.delta_usec);
684
+
685
+ // reset the buffer
686
+ _stackprof.buffer_count = 0;
687
+ }
688
+
604
689
  static void
605
- stackprof_gc_job_handler(void *data)
690
+ stackprof_sample_and_record(void)
691
+ {
692
+ stackprof_buffer_sample();
693
+ stackprof_record_buffer();
694
+ }
695
+
696
+ static void
697
+ stackprof_job_record_gc(void *data)
606
698
  {
607
699
  if (!_stackprof.running) return;
608
700
 
609
701
  stackprof_record_gc_samples();
610
702
  }
611
703
 
704
+ #ifdef USE_POSTPONED_JOB
705
+ static void
706
+ stackprof_job_sample_and_record(void *data)
707
+ {
708
+ if (!_stackprof.running) return;
709
+
710
+ stackprof_sample_and_record();
711
+ }
712
+ #endif
713
+
612
714
  static void
613
- stackprof_job_handler(void *data)
715
+ stackprof_job_record_buffer(void *data)
614
716
  {
615
717
  if (!_stackprof.running) return;
616
718
 
617
- stackprof_record_sample();
719
+ stackprof_record_buffer();
618
720
  }
619
721
 
620
722
  static void
@@ -636,12 +738,16 @@ stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
636
738
  _stackprof.unrecorded_gc_sweeping_samples++;
637
739
  }
638
740
  _stackprof.unrecorded_gc_samples++;
639
- rb_postponed_job_register_one(0, stackprof_gc_job_handler, (void*)0);
741
+ rb_postponed_job_register_one(0, stackprof_job_record_gc, (void*)0);
640
742
  } else {
641
743
  #ifdef USE_POSTPONED_JOB
642
- rb_postponed_job_register_one(0, stackprof_job_handler, (void*)0);
744
+ rb_postponed_job_register_one(0, stackprof_job_sample_and_record, (void*)0);
643
745
  #else
644
- stackprof_job_handler(0);
746
+ // Buffer a sample immediately, if an existing sample exists this will
747
+ // return immediately
748
+ stackprof_buffer_sample();
749
+ // Enqueue a job to record the sample
750
+ rb_postponed_job_register_one(0, stackprof_job_record_buffer, (void*)0);
645
751
  #endif
646
752
  }
647
753
  pthread_mutex_unlock(&lock);
@@ -653,7 +759,7 @@ stackprof_newobj_handler(VALUE tpval, void *data)
653
759
  _stackprof.overall_signals++;
654
760
  if (RTEST(_stackprof.interval) && _stackprof.overall_signals % NUM2LONG(_stackprof.interval))
655
761
  return;
656
- stackprof_job_handler(0);
762
+ stackprof_sample_and_record();
657
763
  }
658
764
 
659
765
  static VALUE
@@ -663,7 +769,7 @@ stackprof_sample(VALUE self)
663
769
  return Qfalse;
664
770
 
665
771
  _stackprof.overall_signals++;
666
- stackprof_job_handler(0);
772
+ stackprof_sample_and_record();
667
773
  return Qtrue;
668
774
  }
669
775
 
@@ -742,6 +848,7 @@ Init_stackprof(void)
742
848
  S(mode);
743
849
  S(interval);
744
850
  S(raw);
851
+ S(raw_sample_timestamps);
745
852
  S(raw_timestamp_deltas);
746
853
  S(out);
747
854
  S(metadata);
@@ -764,9 +871,9 @@ Init_stackprof(void)
764
871
  _stackprof.raw_samples_capa = 0;
765
872
  _stackprof.raw_sample_index = 0;
766
873
 
767
- _stackprof.raw_timestamp_deltas = NULL;
768
- _stackprof.raw_timestamp_deltas_len = 0;
769
- _stackprof.raw_timestamp_deltas_capa = 0;
874
+ _stackprof.raw_sample_times = NULL;
875
+ _stackprof.raw_sample_times_len = 0;
876
+ _stackprof.raw_sample_times_capa = 0;
770
877
 
771
878
  _stackprof.empty_string = rb_str_new_cstr("");
772
879
  rb_global_variable(&_stackprof.empty_string);
@@ -95,51 +95,10 @@ module StackProf
95
95
  print_flamegraph(f, skip_common, true)
96
96
  end
97
97
 
98
- StackCursor = Struct.new(:raw, :idx, :length) do
99
- def weight
100
- @weight ||= raw[1 + idx + length]
101
- end
102
-
103
- def [](i)
104
- if i >= length
105
- nil
106
- else
107
- raw[1 + idx + i]
108
- end
109
- end
110
-
111
- def <=>(other)
112
- i = 0
113
- while i < length && i < other.length
114
- if self[i] != other[i]
115
- return self[i] <=> other[i]
116
- end
117
- i += 1
118
- end
119
-
120
- return length <=> other.length
121
- end
122
- end
123
-
124
98
  def print_flamegraph(f, skip_common, alphabetical=false)
125
99
  raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]
126
100
 
127
- stacks = []
128
- max_x = 0
129
- max_y = 0
130
-
131
- idx = 0
132
- loop do
133
- len = raw[idx]
134
- break unless len
135
- max_y = len if len > max_y
136
-
137
- stack = StackCursor.new(raw, idx, len)
138
- stacks << stack
139
- max_x += stack.weight
140
-
141
- idx += len + 2
142
- end
101
+ stacks, max_x, max_y = flamegraph_stacks(raw)
143
102
 
144
103
  stacks.sort! if alphabetical
145
104
 
@@ -150,7 +109,7 @@ module StackProf
150
109
  x = 0
151
110
 
152
111
  stacks.each do |stack|
153
- weight = stack.weight
112
+ weight = stack.last
154
113
  cell = stack[y] unless y == stack.length-1
155
114
 
156
115
  if cell.nil?
@@ -191,6 +150,24 @@ module StackProf
191
150
  f.puts '])'
192
151
  end
193
152
 
153
+ def flamegraph_stacks(raw)
154
+ stacks = []
155
+ max_x = 0
156
+ max_y = 0
157
+ idx = 0
158
+
159
+ while len = raw[idx]
160
+ idx += 1
161
+ max_y = len if len > max_y
162
+ stack = raw.slice(idx, len+1)
163
+ idx += len+1
164
+ stacks << stack
165
+ max_x += stack.last
166
+ end
167
+
168
+ return stacks, max_x, max_y
169
+ end
170
+
194
171
  def flamegraph_row(f, x, y, weight, addr)
195
172
  frame = @data[:frames][addr]
196
173
  f.print ',' if @rows_started
@@ -231,15 +208,7 @@ module StackProf
231
208
  def print_d3_flamegraph(f=STDOUT, skip_common=true)
232
209
  raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]
233
210
 
234
- stacks = []
235
- max_x = 0
236
- max_y = 0
237
- while len = raw.shift
238
- max_y = len if len > max_y
239
- stack = raw.slice!(0, len+1)
240
- stacks << stack
241
- max_x += stack.last
242
- end
211
+ stacks, * = flamegraph_stacks(raw)
243
212
 
244
213
  # d3-flame-grpah supports only alphabetical flamegraph
245
214
  stacks.sort!
data/lib/stackprof.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require "stackprof/stackprof"
2
2
 
3
3
  module StackProf
4
- VERSION = '0.2.17'
4
+ VERSION = '0.2.18'
5
5
  end
6
6
 
7
7
  StackProf.autoload :Report, "stackprof/report.rb"
data/stackprof.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'stackprof'
3
- s.version = '0.2.17'
3
+ s.version = '0.2.18'
4
4
  s.homepage = 'http://github.com/tmm1/stackprof'
5
5
 
6
6
  s.authors = 'Aman Gupta'
@@ -78,9 +78,14 @@ class StackProfTest < MiniTest::Test
78
78
  end
79
79
 
80
80
  assert_operator profile[:samples], :>=, 1
81
- offset = RUBY_VERSION >= '3' ? 1 : 0
82
- frame = profile[:frames].values[offset]
83
- assert_includes frame[:name], "StackProfTest#math"
81
+ if RUBY_VERSION >= '3'
82
+ assert profile[:frames].values.take(2).map { |f|
83
+ f[:name].include? "StackProfTest#math"
84
+ }.any?
85
+ else
86
+ frame = profile[:frames].values.first
87
+ assert_includes frame[:name], "StackProfTest#math"
88
+ end
84
89
  end
85
90
 
86
91
  def test_walltime
@@ -121,19 +126,33 @@ class StackProfTest < MiniTest::Test
121
126
  end
122
127
 
123
128
  def test_raw
129
+ before_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
130
+
124
131
  profile = StackProf.run(mode: :custom, raw: true) do
125
132
  10.times do
126
133
  StackProf.sample
127
134
  end
128
135
  end
129
136
 
137
+ after_monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
138
+
130
139
  raw = profile[:raw]
131
140
  assert_equal 10, raw[-1]
132
141
  assert_equal raw[0] + 2, raw.size
133
142
 
134
143
  offset = RUBY_VERSION >= '3' ? -3 : -2
135
144
  assert_includes profile[:frames][raw[offset]][:name], 'StackProfTest#test_raw'
145
+
146
+ assert_equal 10, profile[:raw_sample_timestamps].size
147
+ profile[:raw_sample_timestamps].each_cons(2) do |t1, t2|
148
+ assert_operator t1, :>, before_monotonic
149
+ assert_operator t2, :>=, t1
150
+ assert_operator t2, :<, after_monotonic
151
+ end
152
+
136
153
  assert_equal 10, profile[:raw_timestamp_deltas].size
154
+ total_duration = after_monotonic - before_monotonic
155
+ assert_operator profile[:raw_timestamp_deltas].inject(&:+), :<, total_duration
137
156
  end
138
157
 
139
158
  def test_metadata
@@ -205,7 +224,6 @@ class StackProfTest < MiniTest::Test
205
224
  end
206
225
  end
207
226
 
208
- raw = profile[:raw]
209
227
  gc_frame = profile[:frames].values.find{ |f| f[:name] == "(garbage collection)" }
210
228
  marking_frame = profile[:frames].values.find{ |f| f[:name] == "(marking)" }
211
229
  sweeping_frame = profile[:frames].values.find{ |f| f[:name] == "(sweeping)" }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stackprof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.17
4
+ version: 0.2.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aman Gupta
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-03 00:00:00.000000000 Z
11
+ date: 2022-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -94,10 +94,10 @@ licenses:
94
94
  - MIT
95
95
  metadata:
96
96
  bug_tracker_uri: https://github.com/tmm1/stackprof/issues
97
- changelog_uri: https://github.com/tmm1/stackprof/blob/v0.2.17/CHANGELOG.md
98
- documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.17
99
- source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.17
100
- post_install_message:
97
+ changelog_uri: https://github.com/tmm1/stackprof/blob/v0.2.18/CHANGELOG.md
98
+ documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.18
99
+ source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.18
100
+ post_install_message:
101
101
  rdoc_options: []
102
102
  require_paths:
103
103
  - lib
@@ -112,8 +112,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
112
  - !ruby/object:Gem::Version
113
113
  version: '0'
114
114
  requirements: []
115
- rubygems_version: 3.1.2
116
- signing_key:
115
+ rubygems_version: 3.0.3.1
116
+ signing_key:
117
117
  specification_version: 4
118
118
  summary: sampling callstack-profiler for ruby 2.2+
119
119
  test_files: []