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 +4 -4
- data/ext/stackprof/stackprof.c +168 -61
- data/lib/stackprof/report.rb +21 -52
- data/lib/stackprof.rb +1 -1
- data/stackprof.gemspec +1 -1
- data/test/test_stackprof.rb +22 -4
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c802ba8d0dfa813e939679507d70376e7dc33924369bb9d767523cfa5faed31
|
4
|
+
data.tar.gz: 5defbe29a222a82e3b6901ed10b0c73d962ed6a650b14b0c3cd3c231c27c0e39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e585cf066f4008907975726f22185184210e375ca6fe28e51fe44fca7fe53e2cdc414482dcba3e413da2238a879cb5fa58530106ad5f8cb217fc6cee1bf4a2b
|
7
|
+
data.tar.gz: 7b2d08fbd2d70c803f06f42c15e7978871f01c343f3d149f2a63b781d4e074542f445e9a809bdaab1921e779ec9d868159fedc752103faf86c45ddfe7239b74a
|
data/ext/stackprof/stackprof.c
CHANGED
@@ -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
|
66
|
-
|
67
|
-
size_t
|
68
|
-
size_t
|
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
|
-
|
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,
|
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,
|
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,
|
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
|
-
|
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.
|
340
|
-
rb_ary_push(
|
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.
|
344
|
-
_stackprof.
|
345
|
-
_stackprof.
|
346
|
-
_stackprof.
|
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,
|
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.
|
487
|
-
_stackprof.
|
488
|
-
_stackprof.
|
489
|
-
_stackprof.
|
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.
|
494
|
-
_stackprof.
|
495
|
-
_stackprof.
|
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
|
499
|
-
_stackprof.
|
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
|
-
|
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
|
-
|
604
|
+
stackprof_buffer_sample(void)
|
538
605
|
{
|
539
|
-
|
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
|
543
|
-
|
544
|
-
|
545
|
-
|
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
|
-
|
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
|
-
|
556
|
-
|
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
|
559
|
-
|
560
|
-
|
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 = (
|
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
|
-
|
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
|
-
|
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
|
-
|
715
|
+
stackprof_job_record_buffer(void *data)
|
614
716
|
{
|
615
717
|
if (!_stackprof.running) return;
|
616
718
|
|
617
|
-
|
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,
|
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,
|
744
|
+
rb_postponed_job_register_one(0, stackprof_job_sample_and_record, (void*)0);
|
643
745
|
#else
|
644
|
-
|
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
|
-
|
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
|
-
|
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.
|
768
|
-
_stackprof.
|
769
|
-
_stackprof.
|
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);
|
data/lib/stackprof/report.rb
CHANGED
@@ -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.
|
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
data/stackprof.gemspec
CHANGED
data/test/test_stackprof.rb
CHANGED
@@ -78,9 +78,14 @@ class StackProfTest < MiniTest::Test
|
|
78
78
|
end
|
79
79
|
|
80
80
|
assert_operator profile[:samples], :>=, 1
|
81
|
-
|
82
|
-
|
83
|
-
|
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.
|
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:
|
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.
|
98
|
-
documentation_uri: https://www.rubydoc.info/gems/stackprof/0.2.
|
99
|
-
source_code_uri: https://github.com/tmm1/stackprof/tree/v0.2.
|
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
|
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: []
|