stackprof 0.2.11 → 0.2.12
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/.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
|

|
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
|

|
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+
|