stackprof 0.2.13 → 0.2.14
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/.travis.yml +0 -1
- data/CHANGELOG.md +6 -14
- data/README.md +2 -1
- data/ext/stackprof/stackprof.c +75 -12
- data/lib/stackprof.rb +1 -1
- data/lib/stackprof/middleware.rb +8 -2
- data/stackprof.gemspec +4 -2
- data/test/test_middleware.rb +6 -0
- data/test/test_stackprof.rb +40 -2
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50313d95bc2bb3257ea5d4bc6b4c1889f7d6c4c2612b35b7a289de1d8abddaf8
|
4
|
+
data.tar.gz: ac748642b2298f1d23a0c950eed379dd6670eaef7ad300bc5ab5341886d22b39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d790c7c34cc8f12e5e3d12cfc2476e94801e62f79a8ccb747b3d0743b9b8d0bb8ec8fd92757c5696814c028ddeaa4044bbaa0d242fe5796079f741d2fdbef2d6
|
7
|
+
data.tar.gz: b3e5d358816564a6a3a72bfd68c2d94f184a27430b9b75765cf733c4c5104e7c4020ce72bfdf6d3698bbf0456ca487314fb094a0faf6eed7040bce10726df017
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,15 +1,7 @@
|
|
1
|
-
# 0.2.
|
1
|
+
# 0.2.14
|
2
2
|
|
3
|
-
*
|
4
|
-
*
|
5
|
-
*
|
6
|
-
*
|
7
|
-
*
|
8
|
-
* Add test coverage around the string branch in result writing
|
9
|
-
* Flip conditional to use duck typing
|
10
|
-
* Allow Pathname objects for Stackprof :out
|
11
|
-
* Fix a compilation error and a compilation warning
|
12
|
-
* Add `--alphabetical-flamegraph` for population-based instead of timeline
|
13
|
-
* Add `--d3-flamegraph` to output html using d3-flame-graph
|
14
|
-
* Avoid JSON::NestingError when processing deep stacks
|
15
|
-
* Use docker for CI
|
3
|
+
* Relax threshold of missed_samples to <= 25 in GC test
|
4
|
+
* Support custom metadata option in stackprof
|
5
|
+
* Drop support for Ruby 2.1
|
6
|
+
* More generic handling of "fake" frame names
|
7
|
+
* Record marking and sweeping as fake gc calls
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ Inspired heavily by [gperftools](https://code.google.com/p/gperftools/), and wri
|
|
6
6
|
|
7
7
|
## Requirements
|
8
8
|
|
9
|
-
* Ruby 2.
|
9
|
+
* Ruby 2.2+
|
10
10
|
* Linux-based OS
|
11
11
|
|
12
12
|
## Getting Started
|
@@ -330,6 +330,7 @@ Option | Meaning
|
|
330
330
|
`interval` | mode-relative sample rate [c.f.](#sampling)
|
331
331
|
`aggregate` | defaults: `true` - if `false` disables [aggregation](#aggregation)
|
332
332
|
`raw` | defaults `false` - if `true` collects the extra data required by the `--flamegraph` and `--stackcollapse` report types
|
333
|
+
`metadata` | defaults to `{}`. Must be a `Hash`. metadata associated with this profile
|
333
334
|
`save_every`| (rack middleware only) write the target file after this many requests
|
334
335
|
|
335
336
|
## Todo
|
data/ext/stackprof/stackprof.c
CHANGED
@@ -17,6 +17,18 @@
|
|
17
17
|
|
18
18
|
#define BUF_SIZE 2048
|
19
19
|
|
20
|
+
#define FAKE_FRAME_GC INT2FIX(0)
|
21
|
+
#define FAKE_FRAME_MARK INT2FIX(1)
|
22
|
+
#define FAKE_FRAME_SWEEP INT2FIX(2)
|
23
|
+
|
24
|
+
static const char *fake_frame_cstrs[] = {
|
25
|
+
"(garbage collection)",
|
26
|
+
"(marking)",
|
27
|
+
"(sweeping)",
|
28
|
+
};
|
29
|
+
|
30
|
+
#define TOTAL_FAKE_FRAMES (sizeof(fake_frame_cstrs) / sizeof(char *))
|
31
|
+
|
20
32
|
typedef struct {
|
21
33
|
size_t total_samples;
|
22
34
|
size_t caller_samples;
|
@@ -33,6 +45,7 @@ static struct {
|
|
33
45
|
VALUE mode;
|
34
46
|
VALUE interval;
|
35
47
|
VALUE out;
|
48
|
+
VALUE metadata;
|
36
49
|
|
37
50
|
VALUE *raw_samples;
|
38
51
|
size_t raw_samples_len;
|
@@ -48,10 +61,11 @@ static struct {
|
|
48
61
|
size_t overall_samples;
|
49
62
|
size_t during_gc;
|
50
63
|
size_t unrecorded_gc_samples;
|
64
|
+
size_t unrecorded_gc_marking_samples;
|
65
|
+
size_t unrecorded_gc_sweeping_samples;
|
51
66
|
st_table *frames;
|
52
67
|
|
53
|
-
VALUE
|
54
|
-
VALUE fake_gc_frame_name;
|
68
|
+
VALUE fake_frame_names[TOTAL_FAKE_FRAMES];
|
55
69
|
VALUE empty_string;
|
56
70
|
VALUE frames_buffer[BUF_SIZE];
|
57
71
|
int lines_buffer[BUF_SIZE];
|
@@ -59,7 +73,8 @@ static struct {
|
|
59
73
|
|
60
74
|
static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
|
61
75
|
static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
|
62
|
-
static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_frames, sym_out, sym_aggregate, sym_raw_timestamp_deltas;
|
76
|
+
static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_metadata, sym_frames, sym_out, sym_aggregate, sym_raw_timestamp_deltas;
|
77
|
+
static VALUE sym_state, sym_marking, sym_sweeping;
|
63
78
|
static VALUE sym_gc_samples, objtracer;
|
64
79
|
static VALUE gc_hook;
|
65
80
|
static VALUE rb_mStackProf;
|
@@ -72,7 +87,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
|
|
72
87
|
{
|
73
88
|
struct sigaction sa;
|
74
89
|
struct itimerval timer;
|
75
|
-
VALUE opts = Qnil, mode = Qnil, interval = Qnil, out = Qfalse;
|
90
|
+
VALUE opts = Qnil, mode = Qnil, interval = Qnil, metadata = rb_hash_new(), out = Qfalse;
|
76
91
|
int raw = 0, aggregate = 1;
|
77
92
|
|
78
93
|
if (_stackprof.running)
|
@@ -85,6 +100,14 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
|
|
85
100
|
interval = rb_hash_aref(opts, sym_interval);
|
86
101
|
out = rb_hash_aref(opts, sym_out);
|
87
102
|
|
103
|
+
VALUE metadata_val = rb_hash_aref(opts, sym_metadata);
|
104
|
+
if (RTEST(metadata_val)) {
|
105
|
+
if (!RB_TYPE_P(metadata_val, T_HASH))
|
106
|
+
rb_raise(rb_eArgError, "metadata should be a hash");
|
107
|
+
|
108
|
+
metadata = metadata_val;
|
109
|
+
}
|
110
|
+
|
88
111
|
if (RTEST(rb_hash_aref(opts, sym_raw)))
|
89
112
|
raw = 1;
|
90
113
|
if (rb_hash_lookup2(opts, sym_aggregate, Qundef) == Qfalse)
|
@@ -128,6 +151,7 @@ stackprof_start(int argc, VALUE *argv, VALUE self)
|
|
128
151
|
_stackprof.aggregate = aggregate;
|
129
152
|
_stackprof.mode = mode;
|
130
153
|
_stackprof.interval = interval;
|
154
|
+
_stackprof.metadata = metadata;
|
131
155
|
_stackprof.out = out;
|
132
156
|
|
133
157
|
if (raw) {
|
@@ -201,8 +225,8 @@ frame_i(st_data_t key, st_data_t val, st_data_t arg)
|
|
201
225
|
|
202
226
|
rb_hash_aset(results, rb_obj_id(frame), details);
|
203
227
|
|
204
|
-
if (frame
|
205
|
-
name = _stackprof.
|
228
|
+
if (FIXNUM_P(frame)) {
|
229
|
+
name = _stackprof.fake_frame_names[FIX2INT(frame)];
|
206
230
|
file = _stackprof.empty_string;
|
207
231
|
line = INT2FIX(0);
|
208
232
|
} else {
|
@@ -258,6 +282,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
|
|
258
282
|
rb_hash_aset(results, sym_samples, SIZET2NUM(_stackprof.overall_samples));
|
259
283
|
rb_hash_aset(results, sym_gc_samples, SIZET2NUM(_stackprof.during_gc));
|
260
284
|
rb_hash_aset(results, sym_missed_samples, SIZET2NUM(_stackprof.overall_signals - _stackprof.overall_samples));
|
285
|
+
rb_hash_aset(results, sym_metadata, _stackprof.metadata);
|
261
286
|
|
262
287
|
frames = rb_hash_new();
|
263
288
|
rb_hash_aset(results, sym_frames, frames);
|
@@ -522,15 +547,37 @@ stackprof_record_gc_samples()
|
|
522
547
|
}
|
523
548
|
}
|
524
549
|
|
525
|
-
_stackprof.frames_buffer[0] = _stackprof.fake_gc_frame;
|
526
|
-
_stackprof.lines_buffer[0] = 0;
|
527
550
|
|
528
551
|
for (i = 0; i < _stackprof.unrecorded_gc_samples; i++) {
|
529
552
|
int timestamp_delta = i == 0 ? delta_to_first_unrecorded_gc_sample : NUM2LONG(_stackprof.interval);
|
530
|
-
|
553
|
+
|
554
|
+
if (_stackprof.unrecorded_gc_marking_samples) {
|
555
|
+
_stackprof.frames_buffer[0] = FAKE_FRAME_MARK;
|
556
|
+
_stackprof.lines_buffer[0] = 0;
|
557
|
+
_stackprof.frames_buffer[1] = FAKE_FRAME_GC;
|
558
|
+
_stackprof.lines_buffer[1] = 0;
|
559
|
+
_stackprof.unrecorded_gc_marking_samples--;
|
560
|
+
|
561
|
+
stackprof_record_sample_for_stack(2, timestamp_delta);
|
562
|
+
} else if (_stackprof.unrecorded_gc_sweeping_samples) {
|
563
|
+
_stackprof.frames_buffer[0] = FAKE_FRAME_SWEEP;
|
564
|
+
_stackprof.lines_buffer[0] = 0;
|
565
|
+
_stackprof.frames_buffer[1] = FAKE_FRAME_GC;
|
566
|
+
_stackprof.lines_buffer[1] = 0;
|
567
|
+
|
568
|
+
_stackprof.unrecorded_gc_sweeping_samples--;
|
569
|
+
|
570
|
+
stackprof_record_sample_for_stack(2, timestamp_delta);
|
571
|
+
} else {
|
572
|
+
_stackprof.frames_buffer[0] = FAKE_FRAME_GC;
|
573
|
+
_stackprof.lines_buffer[0] = 0;
|
574
|
+
stackprof_record_sample_for_stack(1, timestamp_delta);
|
575
|
+
}
|
531
576
|
}
|
532
577
|
_stackprof.during_gc += _stackprof.unrecorded_gc_samples;
|
533
578
|
_stackprof.unrecorded_gc_samples = 0;
|
579
|
+
_stackprof.unrecorded_gc_marking_samples = 0;
|
580
|
+
_stackprof.unrecorded_gc_sweeping_samples = 0;
|
534
581
|
}
|
535
582
|
|
536
583
|
static void
|
@@ -562,6 +609,12 @@ stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
|
|
562
609
|
{
|
563
610
|
_stackprof.overall_signals++;
|
564
611
|
if (rb_during_gc()) {
|
612
|
+
VALUE mode = rb_gc_latest_gc_info(sym_state);
|
613
|
+
if (mode == sym_marking) {
|
614
|
+
_stackprof.unrecorded_gc_marking_samples++;
|
615
|
+
} else if (mode == sym_sweeping) {
|
616
|
+
_stackprof.unrecorded_gc_sweeping_samples++;
|
617
|
+
}
|
565
618
|
_stackprof.unrecorded_gc_samples++;
|
566
619
|
rb_postponed_job_register_one(0, stackprof_gc_job_handler, (void*)0);
|
567
620
|
} else {
|
@@ -642,6 +695,7 @@ stackprof_atfork_child(void)
|
|
642
695
|
void
|
643
696
|
Init_stackprof(void)
|
644
697
|
{
|
698
|
+
size_t i;
|
645
699
|
#define S(name) sym_##name = ID2SYM(rb_intern(#name));
|
646
700
|
S(object);
|
647
701
|
S(custom);
|
@@ -662,10 +716,17 @@ Init_stackprof(void)
|
|
662
716
|
S(raw);
|
663
717
|
S(raw_timestamp_deltas);
|
664
718
|
S(out);
|
719
|
+
S(metadata);
|
665
720
|
S(frames);
|
666
721
|
S(aggregate);
|
722
|
+
S(state);
|
723
|
+
S(marking);
|
724
|
+
S(sweeping);
|
667
725
|
#undef S
|
668
726
|
|
727
|
+
/* Need to run this to warm the symbol table before we call this during GC */
|
728
|
+
rb_gc_latest_gc_info(sym_state);
|
729
|
+
|
669
730
|
gc_hook = Data_Wrap_Struct(rb_cObject, stackprof_gc_mark, NULL, &_stackprof);
|
670
731
|
rb_global_variable(&gc_hook);
|
671
732
|
|
@@ -678,12 +739,14 @@ Init_stackprof(void)
|
|
678
739
|
_stackprof.raw_timestamp_deltas_len = 0;
|
679
740
|
_stackprof.raw_timestamp_deltas_capa = 0;
|
680
741
|
|
681
|
-
_stackprof.fake_gc_frame = INT2FIX(0x9C);
|
682
742
|
_stackprof.empty_string = rb_str_new_cstr("");
|
683
|
-
_stackprof.fake_gc_frame_name = rb_str_new_cstr("(garbage collection)");
|
684
|
-
rb_global_variable(&_stackprof.fake_gc_frame_name);
|
685
743
|
rb_global_variable(&_stackprof.empty_string);
|
686
744
|
|
745
|
+
for (i = 0; i < TOTAL_FAKE_FRAMES; i++) {
|
746
|
+
_stackprof.fake_frame_names[i] = rb_str_new_cstr(fake_frame_cstrs[i]);
|
747
|
+
rb_global_variable(&_stackprof.fake_frame_names[i]);
|
748
|
+
}
|
749
|
+
|
687
750
|
rb_mStackProf = rb_define_module("StackProf");
|
688
751
|
rb_define_singleton_method(rb_mStackProf, "running?", stackprof_running_p, 0);
|
689
752
|
rb_define_singleton_method(rb_mStackProf, "run", stackprof_run, -1);
|
data/lib/stackprof.rb
CHANGED
data/lib/stackprof/middleware.rb
CHANGED
@@ -13,12 +13,18 @@ module StackProf
|
|
13
13
|
Middleware.enabled = options[:enabled]
|
14
14
|
options[:path] = 'tmp/' if options[:path].to_s.empty?
|
15
15
|
Middleware.path = options[:path]
|
16
|
+
Middleware.metadata = options[:metadata] || {}
|
16
17
|
at_exit{ Middleware.save } if options[:save_at_exit]
|
17
18
|
end
|
18
19
|
|
19
20
|
def call(env)
|
20
21
|
enabled = Middleware.enabled?(env)
|
21
|
-
StackProf.start(
|
22
|
+
StackProf.start(
|
23
|
+
mode: Middleware.mode,
|
24
|
+
interval: Middleware.interval,
|
25
|
+
raw: Middleware.raw,
|
26
|
+
metadata: Middleware.metadata,
|
27
|
+
) if enabled
|
22
28
|
@app.call(env)
|
23
29
|
ensure
|
24
30
|
if enabled
|
@@ -31,7 +37,7 @@ module StackProf
|
|
31
37
|
end
|
32
38
|
|
33
39
|
class << self
|
34
|
-
attr_accessor :enabled, :mode, :interval, :raw, :path
|
40
|
+
attr_accessor :enabled, :mode, :interval, :raw, :path, :metadata
|
35
41
|
|
36
42
|
def enabled?(env)
|
37
43
|
if enabled.respond_to?(:call)
|
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.
|
3
|
+
s.version = '0.2.14'
|
4
4
|
s.homepage = 'http://github.com/tmm1/stackprof'
|
5
5
|
|
6
6
|
s.authors = 'Aman Gupta'
|
@@ -14,9 +14,11 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.executables << 'stackprof-flamegraph.pl'
|
15
15
|
s.executables << 'stackprof-gprof2dot.py'
|
16
16
|
|
17
|
-
s.summary = 'sampling callstack-profiler for ruby 2.
|
17
|
+
s.summary = 'sampling callstack-profiler for ruby 2.2+'
|
18
18
|
s.description = 'stackprof is a fast sampling profiler for ruby code, with cpu, wallclock and object allocation samplers.'
|
19
19
|
|
20
|
+
s.required_ruby_version = '>= 2.2'
|
21
|
+
|
20
22
|
s.license = 'MIT'
|
21
23
|
|
22
24
|
s.add_development_dependency 'rake-compiler', '~> 0.9'
|
data/test/test_middleware.rb
CHANGED
@@ -64,4 +64,10 @@ class StackProf::MiddlewareTest < MiniTest::Test
|
|
64
64
|
StackProf::Middleware.new(Object.new, raw: true)
|
65
65
|
assert StackProf::Middleware.raw
|
66
66
|
end
|
67
|
+
|
68
|
+
def test_metadata
|
69
|
+
metadata = { key: 'value' }
|
70
|
+
StackProf::Middleware.new(Object.new, metadata: metadata)
|
71
|
+
assert_equal metadata, StackProf::Middleware.metadata
|
72
|
+
end
|
67
73
|
end
|
data/test/test_stackprof.rb
CHANGED
@@ -109,6 +109,36 @@ class StackProfTest < MiniTest::Test
|
|
109
109
|
assert_equal 10, profile[:raw_timestamp_deltas].size
|
110
110
|
end
|
111
111
|
|
112
|
+
def test_metadata
|
113
|
+
metadata = {
|
114
|
+
path: '/foo/bar',
|
115
|
+
revision: '5c0b01f1522ae8c194510977ae29377296dd236b',
|
116
|
+
}
|
117
|
+
profile = StackProf.run(mode: :cpu, metadata: metadata) do
|
118
|
+
math
|
119
|
+
end
|
120
|
+
|
121
|
+
assert_equal metadata, profile[:metadata]
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_empty_metadata
|
125
|
+
profile = StackProf.run(mode: :cpu) do
|
126
|
+
math
|
127
|
+
end
|
128
|
+
|
129
|
+
assert_equal({}, profile[:metadata])
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_raises_if_metadata_is_not_a_hash
|
133
|
+
exception = assert_raises ArgumentError do
|
134
|
+
StackProf.run(mode: :cpu, metadata: 'foobar') do
|
135
|
+
math
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
assert_equal 'metadata should be a hash', exception.message
|
140
|
+
end
|
141
|
+
|
112
142
|
def test_fork
|
113
143
|
StackProf.run do
|
114
144
|
pid = fork do
|
@@ -150,10 +180,18 @@ class StackProfTest < MiniTest::Test
|
|
150
180
|
|
151
181
|
raw = profile[:raw]
|
152
182
|
gc_frame = profile[:frames].values.find{ |f| f[:name] == "(garbage collection)" }
|
183
|
+
marking_frame = profile[:frames].values.find{ |f| f[:name] == "(marking)" }
|
184
|
+
sweeping_frame = profile[:frames].values.find{ |f| f[:name] == "(sweeping)" }
|
185
|
+
|
153
186
|
assert gc_frame
|
154
|
-
|
187
|
+
assert marking_frame
|
188
|
+
assert sweeping_frame
|
189
|
+
|
190
|
+
assert_equal gc_frame[:total_samples], profile[:gc_samples]
|
191
|
+
assert_equal profile[:gc_samples], [gc_frame, marking_frame, sweeping_frame].map{|x| x[:samples] }.inject(:+)
|
192
|
+
|
155
193
|
assert_operator profile[:gc_samples], :>, 0
|
156
|
-
assert_operator profile[:missed_samples], :<=,
|
194
|
+
assert_operator profile[:missed_samples], :<=, 25
|
157
195
|
end
|
158
196
|
|
159
197
|
def test_out
|
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.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aman Gupta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -103,15 +103,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
103
|
requirements:
|
104
104
|
- - ">="
|
105
105
|
- !ruby/object:Gem::Version
|
106
|
-
version: '
|
106
|
+
version: '2.2'
|
107
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
108
|
requirements:
|
109
109
|
- - ">="
|
110
110
|
- !ruby/object:Gem::Version
|
111
111
|
version: '0'
|
112
112
|
requirements: []
|
113
|
-
rubygems_version: 3.1.0.
|
113
|
+
rubygems_version: 3.1.0.pre3
|
114
114
|
signing_key:
|
115
115
|
specification_version: 4
|
116
|
-
summary: sampling callstack-profiler for ruby 2.
|
116
|
+
summary: sampling callstack-profiler for ruby 2.2+
|
117
117
|
test_files: []
|