stackprof 0.2.11 → 0.2.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +3 -0
- data/README.md +21 -17
- data/ext/stackprof/stackprof.c +13 -16
- data/lib/stackprof/middleware.rb +15 -5
- data/lib/stackprof/report.rb +2 -2
- data/stackprof.gemspec +1 -1
- data/test/test_middleware.rb +7 -7
- data/test/test_stackprof.rb +21 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25c29d953e534a2d31065493cbffa762c5f92ee1
|
4
|
+
data.tar.gz: f9533f32cd7d5d648f3b9e16fd9d43469181521a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69d486b03120ed37b76b16e65825b98e00fa6fbfb3aa3c484870ebead42cdbfed204e1e4e4362e3d5bdadee9c81ccf4169bcdaf109191c8b0fa580f97a8dfc62
|
7
|
+
data.tar.gz: b735b0457b8ee0bbf217d0a891abd81c88ecbfd63a435001d1aaaec9707ac01607e14bea0b82a6c0c6ba827b64b3885d0cbffffdabe0742a3cc51bb402a59fc2
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -1,13 +1,17 @@
|
|
1
|
-
|
1
|
+
# Stackprof
|
2
2
|
|
3
|
-
|
3
|
+
A sampling call-stack profiler for Ruby.
|
4
4
|
|
5
|
-
|
6
|
-
and written as a replacement for [perftools.rb](https://github.com/tmm1/perftools.rb)
|
5
|
+
Inspired heavily by [gperftools](https://code.google.com/p/gperftools/), and written as a replacement for [perftools.rb](https://github.com/tmm1/perftools.rb).
|
7
6
|
|
8
|
-
|
7
|
+
## Requirements
|
9
8
|
|
10
|
-
|
9
|
+
* Ruby 2.1+
|
10
|
+
* Linux-based OS
|
11
|
+
|
12
|
+
## Getting Started
|
13
|
+
|
14
|
+
### Install
|
11
15
|
|
12
16
|
In your Gemfile add:
|
13
17
|
|
@@ -18,7 +22,7 @@ gem 'stackprof'
|
|
18
22
|
Then run `$ bundle install`. Alternatively you can run `$ gem install stackprof`.
|
19
23
|
|
20
24
|
|
21
|
-
|
25
|
+
### Run
|
22
26
|
|
23
27
|
in ruby:
|
24
28
|
|
@@ -93,7 +97,7 @@ The `--flamegraph-viewer` command will output the exact shell command you need t
|
|
93
97
|
|
94
98
|
![](http://i.imgur.com/EwndrgD.png)
|
95
99
|
|
96
|
-
|
100
|
+
## Sampling
|
97
101
|
|
98
102
|
four sampling modes are supported:
|
99
103
|
|
@@ -142,7 +146,7 @@ samples are taken using a combination of three new C-APIs in ruby 2.1:
|
|
142
146
|
- in allocation mode, samples are taken via `rb_tracepoint_new(RUBY_INTERNAL_EVENT_NEWOBJ)`,
|
143
147
|
which provides a notification every time the VM allocates a new object.
|
144
148
|
|
145
|
-
|
149
|
+
## Aggregation
|
146
150
|
|
147
151
|
each sample consists of N stack frames, where a frame looks something like `MyClass#method` or `block in MySingleton.method`.
|
148
152
|
for each of these frames in the sample, the profiler collects a few pieces of metadata:
|
@@ -175,14 +179,14 @@ this technique builds up an incremental callgraph from the samples. on any given
|
|
175
179
|
the sum of the outbound edge weights is equal to total samples collected on that frame
|
176
180
|
(`frame.total_samples == frame.edges.values.sum`).
|
177
181
|
|
178
|
-
|
182
|
+
## Reporting
|
179
183
|
|
180
184
|
multiple reporting modes are supported:
|
181
185
|
- text
|
182
186
|
- dotgraph
|
183
187
|
- source annotation
|
184
188
|
|
185
|
-
|
189
|
+
### `StackProf::Report.new(data).print_text`
|
186
190
|
|
187
191
|
```
|
188
192
|
TOTAL (pct) SAMPLES (pct) FRAME
|
@@ -197,7 +201,7 @@ multiple reporting modes are supported:
|
|
197
201
|
188 (100.0%) 0 (0.0%) <main>
|
198
202
|
```
|
199
203
|
|
200
|
-
|
204
|
+
### `StackProf::Report.new(data).print_graphviz`
|
201
205
|
|
202
206
|
![](http://cl.ly/image/2t3l2q0l0B0A/content)
|
203
207
|
|
@@ -223,7 +227,7 @@ digraph profile {
|
|
223
227
|
}
|
224
228
|
```
|
225
229
|
|
226
|
-
|
230
|
+
### `StackProf::Report.new(data).print_method(/pow|newobj|math/)`
|
227
231
|
|
228
232
|
```
|
229
233
|
A#pow (/Users/tmm1/code/stackprof/sample.rb:11)
|
@@ -245,7 +249,7 @@ block in A#math (/Users/tmm1/code/stackprof/sample.rb:21)
|
|
245
249
|
| 23 | end
|
246
250
|
```
|
247
251
|
|
248
|
-
|
252
|
+
## Usage
|
249
253
|
|
250
254
|
the profiler is compiled as a C-extension and exposes a simple api: `StackProf.run(mode: [:cpu|:wall|:object])`.
|
251
255
|
the `run` method takes a block of code and returns a profile as a simple hash.
|
@@ -295,7 +299,7 @@ above, `A#pow` was involved in 91 samples, and in all cases it was at the top of
|
|
295
299
|
divided up between its callee edges. all 91 calls to `A#pow` came from `A#initialize`, as seen by the edge numbered
|
296
300
|
`70346498324780`.
|
297
301
|
|
298
|
-
|
302
|
+
## Advanced usage
|
299
303
|
|
300
304
|
the profiler can be started and stopped manually. results are accumulated until retrieval, across
|
301
305
|
multiple start/stop invocations.
|
@@ -307,7 +311,7 @@ StackProf.stop
|
|
307
311
|
StackProf.results('/tmp/some.file')
|
308
312
|
```
|
309
313
|
|
310
|
-
|
314
|
+
## All options
|
311
315
|
|
312
316
|
`StackProf.run` accepts an options hash. Currently, the following options are recognized:
|
313
317
|
|
@@ -320,7 +324,7 @@ Option | Meaning
|
|
320
324
|
`raw` | defaults `false` - if `true` collects the extra data required by the `--flamegraph` and `--stackcollapse` report types
|
321
325
|
`save_every`| (rack middleware only) write the target file after this many requests
|
322
326
|
|
323
|
-
|
327
|
+
## Todo
|
324
328
|
|
325
329
|
* file/iseq blacklist
|
326
330
|
* restore signal handlers on stop
|
data/ext/stackprof/stackprof.c
CHANGED
@@ -20,7 +20,7 @@
|
|
20
20
|
typedef struct {
|
21
21
|
size_t total_samples;
|
22
22
|
size_t caller_samples;
|
23
|
-
|
23
|
+
size_t seen_at_sample_number;
|
24
24
|
st_table *edges;
|
25
25
|
st_table *lines;
|
26
26
|
} frame_data_t;
|
@@ -268,6 +268,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
|
|
268
268
|
|
269
269
|
if (_stackprof.raw && _stackprof.raw_samples_len) {
|
270
270
|
size_t len, n, o;
|
271
|
+
VALUE raw_timestamp_deltas;
|
271
272
|
VALUE raw_samples = rb_ary_new_capa(_stackprof.raw_samples_len);
|
272
273
|
|
273
274
|
for (n = 0; n < _stackprof.raw_samples_len; n++) {
|
@@ -287,7 +288,7 @@ stackprof_results(int argc, VALUE *argv, VALUE self)
|
|
287
288
|
|
288
289
|
rb_hash_aset(results, sym_raw, raw_samples);
|
289
290
|
|
290
|
-
|
291
|
+
raw_timestamp_deltas = rb_ary_new_capa(_stackprof.raw_timestamp_deltas_len);
|
291
292
|
|
292
293
|
for (n = 0; n < _stackprof.raw_timestamp_deltas_len; n++) {
|
293
294
|
rb_ary_push(raw_timestamp_deltas, INT2FIX(_stackprof.raw_timestamp_deltas[n]));
|
@@ -432,19 +433,15 @@ stackprof_record_sample_for_stack(int num, int timestamp_delta)
|
|
432
433
|
_stackprof.raw_timestamp_deltas[_stackprof.raw_timestamp_deltas_len++] = timestamp_delta;
|
433
434
|
}
|
434
435
|
|
435
|
-
for (i = 0; i < num; i++) {
|
436
|
-
VALUE frame = _stackprof.frames_buffer[i];
|
437
|
-
sample_for(frame)->already_accounted_in_total = 0;
|
438
|
-
}
|
439
|
-
|
440
436
|
for (i = 0; i < num; i++) {
|
441
437
|
int line = _stackprof.lines_buffer[i];
|
442
438
|
VALUE frame = _stackprof.frames_buffer[i];
|
443
439
|
frame_data_t *frame_data = sample_for(frame);
|
444
440
|
|
445
|
-
if (
|
441
|
+
if (frame_data->seen_at_sample_number != _stackprof.overall_samples) {
|
446
442
|
frame_data->total_samples++;
|
447
|
-
|
443
|
+
}
|
444
|
+
frame_data->seen_at_sample_number = _stackprof.overall_samples;
|
448
445
|
|
449
446
|
if (i == 0) {
|
450
447
|
frame_data->caller_samples++;
|
@@ -455,10 +452,10 @@ stackprof_record_sample_for_stack(int num, int timestamp_delta)
|
|
455
452
|
}
|
456
453
|
|
457
454
|
if (_stackprof.aggregate && line > 0) {
|
458
|
-
if (!frame_data->lines)
|
459
|
-
frame_data->lines = st_init_numtable();
|
460
455
|
size_t half = (size_t)1<<(8*SIZEOF_SIZE_T/2);
|
461
456
|
size_t increment = i == 0 ? half + 1 : half;
|
457
|
+
if (!frame_data->lines)
|
458
|
+
frame_data->lines = st_init_numtable();
|
462
459
|
st_numtable_increment(frame_data->lines, (st_data_t)line, increment);
|
463
460
|
}
|
464
461
|
|
@@ -474,14 +471,15 @@ void
|
|
474
471
|
stackprof_record_sample()
|
475
472
|
{
|
476
473
|
int timestamp_delta = 0;
|
474
|
+
int num;
|
477
475
|
if (_stackprof.raw) {
|
478
476
|
struct timeval t;
|
479
|
-
gettimeofday(&t, NULL);
|
480
477
|
struct timeval diff;
|
478
|
+
gettimeofday(&t, NULL);
|
481
479
|
timersub(&t, &_stackprof.last_sample_at, &diff);
|
482
480
|
timestamp_delta = (1000 * diff.tv_sec) + diff.tv_usec;
|
483
481
|
}
|
484
|
-
|
482
|
+
num = rb_profile_frames(0, sizeof(_stackprof.frames_buffer) / sizeof(VALUE), _stackprof.frames_buffer, _stackprof.lines_buffer);
|
485
483
|
stackprof_record_sample_for_stack(num, timestamp_delta);
|
486
484
|
}
|
487
485
|
|
@@ -489,10 +487,11 @@ void
|
|
489
487
|
stackprof_record_gc_samples()
|
490
488
|
{
|
491
489
|
int delta_to_first_unrecorded_gc_sample = 0;
|
490
|
+
int i;
|
492
491
|
if (_stackprof.raw) {
|
493
492
|
struct timeval t;
|
494
|
-
gettimeofday(&t, NULL);
|
495
493
|
struct timeval diff;
|
494
|
+
gettimeofday(&t, NULL);
|
496
495
|
timersub(&t, &_stackprof.last_sample_at, &diff);
|
497
496
|
|
498
497
|
// We don't know when the GC samples were actually marked, so let's
|
@@ -503,8 +502,6 @@ stackprof_record_gc_samples()
|
|
503
502
|
}
|
504
503
|
}
|
505
504
|
|
506
|
-
int i;
|
507
|
-
|
508
505
|
_stackprof.frames_buffer[0] = _stackprof.fake_gc_frame;
|
509
506
|
_stackprof.lines_buffer[0] = 0;
|
510
507
|
|
data/lib/stackprof/middleware.rb
CHANGED
@@ -11,7 +11,8 @@ module StackProf
|
|
11
11
|
Middleware.interval = options[:interval] || 1000
|
12
12
|
Middleware.raw = options[:raw] || false
|
13
13
|
Middleware.enabled = options[:enabled]
|
14
|
-
|
14
|
+
options[:path] = 'tmp/' if options[:path].to_s.empty?
|
15
|
+
Middleware.path = options[:path]
|
15
16
|
at_exit{ Middleware.save } if options[:save_at_exit]
|
16
17
|
end
|
17
18
|
|
@@ -40,11 +41,20 @@ module StackProf
|
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
43
|
-
def save
|
44
|
+
def save
|
44
45
|
if results = StackProf.results
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
path = Middleware.path
|
47
|
+
is_directory = path != path.chomp('/')
|
48
|
+
|
49
|
+
if is_directory
|
50
|
+
filename = "stackprof-#{results[:mode]}-#{Process.pid}-#{Time.now.to_i}.dump"
|
51
|
+
else
|
52
|
+
filename = File.basename(path)
|
53
|
+
path = File.dirname(path)
|
54
|
+
end
|
55
|
+
|
56
|
+
FileUtils.mkdir_p(path)
|
57
|
+
File.open(File.join(path, filename), 'wb') do |f|
|
48
58
|
f.write Marshal.dump(results)
|
49
59
|
end
|
50
60
|
filename
|
data/lib/stackprof/report.rb
CHANGED
@@ -55,8 +55,8 @@ module StackProf
|
|
55
55
|
|
56
56
|
def add_lines(a, b)
|
57
57
|
return b if a.nil?
|
58
|
-
return a+b if a.is_a?
|
59
|
-
return [ a[0], a[1]+b ] if b.is_a?
|
58
|
+
return a+b if a.is_a? Integer
|
59
|
+
return [ a[0], a[1]+b ] if b.is_a? Integer
|
60
60
|
[ a[0]+b[0], a[1]+b[1] ]
|
61
61
|
end
|
62
62
|
|
data/stackprof.gemspec
CHANGED
data/test/test_middleware.rb
CHANGED
@@ -9,31 +9,31 @@ class StackProf::MiddlewareTest < MiniTest::Test
|
|
9
9
|
def test_path_default
|
10
10
|
StackProf::Middleware.new(Object.new)
|
11
11
|
|
12
|
-
assert_equal 'tmp', StackProf::Middleware.path
|
12
|
+
assert_equal 'tmp/', StackProf::Middleware.path
|
13
13
|
end
|
14
14
|
|
15
15
|
def test_path_custom
|
16
|
-
StackProf::Middleware.new(Object.new, { path: '/
|
16
|
+
StackProf::Middleware.new(Object.new, { path: 'foo/' })
|
17
17
|
|
18
|
-
assert_equal '/
|
18
|
+
assert_equal 'foo/', StackProf::Middleware.path
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_save_default
|
22
22
|
StackProf::Middleware.new(Object.new)
|
23
23
|
|
24
24
|
StackProf.stubs(:results).returns({ mode: 'foo' })
|
25
|
-
FileUtils.expects(:mkdir_p).with('tmp')
|
25
|
+
FileUtils.expects(:mkdir_p).with('tmp/')
|
26
26
|
File.expects(:open).with(regexp_matches(/^tmp\/stackprof-foo/), 'wb')
|
27
27
|
|
28
28
|
StackProf::Middleware.save
|
29
29
|
end
|
30
30
|
|
31
31
|
def test_save_custom
|
32
|
-
StackProf::Middleware.new(Object.new, { path: '/
|
32
|
+
StackProf::Middleware.new(Object.new, { path: 'foo/' })
|
33
33
|
|
34
34
|
StackProf.stubs(:results).returns({ mode: 'foo' })
|
35
|
-
FileUtils.expects(:mkdir_p).with('/
|
36
|
-
File.expects(:open).with(regexp_matches(
|
35
|
+
FileUtils.expects(:mkdir_p).with('foo/')
|
36
|
+
File.expects(:open).with(regexp_matches(/^foo\/stackprof-foo/), 'wb')
|
37
37
|
|
38
38
|
StackProf::Middleware.save
|
39
39
|
end
|
data/test/test_stackprof.rb
CHANGED
@@ -119,6 +119,27 @@ class StackProfTest < MiniTest::Test
|
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
|
+
def foo(n = 10)
|
123
|
+
if n == 0
|
124
|
+
StackProf.sample
|
125
|
+
return
|
126
|
+
end
|
127
|
+
foo(n - 1)
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_recursive_total_samples
|
131
|
+
profile = StackProf.run(mode: :cpu, raw: true) do
|
132
|
+
10.times do
|
133
|
+
foo
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
frame = profile[:frames].values.find do |frame|
|
138
|
+
frame[:name] == "StackProfTest#foo"
|
139
|
+
end
|
140
|
+
assert_equal 10, frame[:total_samples]
|
141
|
+
end
|
142
|
+
|
122
143
|
def test_gc
|
123
144
|
profile = StackProf.run(interval: 100, raw: true) do
|
124
145
|
5.times do
|
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.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aman Gupta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -65,6 +65,7 @@ extra_rdoc_files: []
|
|
65
65
|
files:
|
66
66
|
- ".gitignore"
|
67
67
|
- ".travis.yml"
|
68
|
+
- CHANGELOG.md
|
68
69
|
- Gemfile
|
69
70
|
- Gemfile.lock
|
70
71
|
- LICENSE
|
@@ -109,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
110
|
version: '0'
|
110
111
|
requirements: []
|
111
112
|
rubyforge_project:
|
112
|
-
rubygems_version: 2.6
|
113
|
+
rubygems_version: 2.4.6
|
113
114
|
signing_key:
|
114
115
|
specification_version: 4
|
115
116
|
summary: sampling callstack-profiler for ruby 2.1+
|