stackprof 0.2.17 → 0.2.18

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: 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: []