stackprof 0.2.13 → 0.2.14
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|