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