stackprof 0.2.10 → 0.2.25
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +43 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +18 -0
- data/README.md +87 -67
- data/Rakefile +21 -25
- data/bin/stackprof +115 -70
- data/ext/stackprof/extconf.rb +6 -0
- data/ext/stackprof/stackprof.c +434 -37
- data/lib/stackprof/autorun.rb +19 -0
- data/lib/stackprof/flamegraph/flamegraph.js +926 -300
- data/lib/stackprof/flamegraph/viewer.html +29 -23
- data/lib/stackprof/middleware.rb +23 -7
- data/lib/stackprof/report.rb +323 -18
- data/lib/stackprof/truffleruby.rb +37 -0
- data/lib/stackprof.rb +18 -1
- data/sample.rb +3 -3
- data/stackprof.gemspec +11 -2
- data/test/fixtures/profile.dump +1 -0
- data/test/fixtures/profile.json +1 -0
- data/test/test_middleware.rb +13 -7
- data/test/test_report.rb +24 -0
- data/test/test_stackprof.rb +177 -25
- data/test/test_truffleruby.rb +18 -0
- data/vendor/FlameGraph/flamegraph.pl +751 -85
- metadata +17 -10
- data/.travis.yml +0 -8
- data/Gemfile.lock +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b299eb696cf0c2748e931532afab3f90bab1c94447ff1c844bce0eda878a93c6
|
4
|
+
data.tar.gz: bd2c389baa8253fc06beda425abc87762b9bca425c6bc5046655610fb9852e79
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 322e506fc77bd964f39e7b3f0c5584e57efdec8e1dcc432cdea924536e334234d916a1d5e1ff085e2e5210a49751897ca36a4b1bd007468a5b16184151129be3
|
7
|
+
data.tar.gz: 3234d4159c78119a9238200d1f4516280cbd1e0cfe3affca1c4fa4827b805ecf154e38857c917fafe7aed042d67525bb7e6c5fce85a039e30cde4672ae08f97e
|
@@ -0,0 +1,43 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
rubies:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
strategy:
|
9
|
+
fail-fast: false
|
10
|
+
matrix:
|
11
|
+
ruby: [ ruby-head, '3.1', '3.0', '2.7', '2.6', '2.5', '2.4', '2.3', '2.2', truffleruby ]
|
12
|
+
steps:
|
13
|
+
- name: Checkout
|
14
|
+
uses: actions/checkout@v2
|
15
|
+
- name: Set up Ruby
|
16
|
+
uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
19
|
+
- name: Install dependencies
|
20
|
+
run: bundle install
|
21
|
+
- name: Run test
|
22
|
+
run: rake
|
23
|
+
- name: Install gem
|
24
|
+
run: rake install
|
25
|
+
platforms:
|
26
|
+
strategy:
|
27
|
+
matrix:
|
28
|
+
os: [macos]
|
29
|
+
ruby: ['3.0']
|
30
|
+
runs-on: ${{ matrix.os }}-latest
|
31
|
+
steps:
|
32
|
+
- name: Checkout
|
33
|
+
uses: actions/checkout@v2
|
34
|
+
- name: Set up Ruby
|
35
|
+
uses: ruby/setup-ruby@v1
|
36
|
+
with:
|
37
|
+
ruby-version: ${{ matrix.ruby }}
|
38
|
+
- name: Install dependencies
|
39
|
+
run: bundle install
|
40
|
+
- name: Run test
|
41
|
+
run: rake
|
42
|
+
- name: Install gem
|
43
|
+
run: rake install
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# 0.2.25
|
2
|
+
|
3
|
+
* Fix GC marking
|
4
|
+
|
5
|
+
# 0.2.16
|
6
|
+
|
7
|
+
* [flamegraph.pl] Update to latest version
|
8
|
+
* Add option to ignore GC frames
|
9
|
+
* Handle source code not being available
|
10
|
+
* Freeze strings in report.rb
|
11
|
+
* Use a cursor object instead of array slicing
|
12
|
+
* ArgumentError on interval <1 or >1m
|
13
|
+
* fix variable name.
|
14
|
+
* Fix default mode comment in readme
|
15
|
+
|
16
|
+
# 0.2.15
|
17
|
+
|
18
|
+
* Mark the metadata object before the GC is invoked to prevent it from being garbage collected.
|
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.2+
|
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
|
|
@@ -77,34 +81,42 @@ $ stackprof tmp/stackprof-cpu-*.dump --method 'Object#present?'
|
|
77
81
|
|
78
82
|
For an experimental version of WebUI reporting of stackprof, see [stackprof-webnav](https://github.com/alisnic/stackprof-webnav)
|
79
83
|
|
80
|
-
|
84
|
+
To generate flamegraphs with Stackprof, additional data must be collected using the `raw: true` flag. Once you've collected results with this flag enabled, generate a flamegraph with:
|
81
85
|
|
82
86
|
```
|
83
87
|
$ stackprof --flamegraph tmp/stackprof-cpu-myapp.dump > tmp/flamegraph
|
84
88
|
```
|
85
89
|
|
86
|
-
|
90
|
+
After the flamegraph has been generated, you can generate a viewer command with:
|
87
91
|
|
88
92
|
```
|
89
93
|
$ stackprof --flamegraph-viewer=tmp/flamegraph
|
90
94
|
```
|
91
95
|
|
92
|
-
The `--flamegraph-viewer` command will output the exact shell command you need to run to open the `tmp/flamegraph` you generated with the built
|
96
|
+
The `--flamegraph-viewer` command will output the exact shell command you need to run in order to open the `tmp/flamegraph` you generated with the built-in stackprof flamegraph viewer:
|
97
|
+
|
98
|
+
![Flamegraph Viewer](http://i.imgur.com/EwndrgD.png)
|
99
|
+
|
100
|
+
Alternatively, you can generate a flamegraph that uses [d3-flame-graph](https://github.com/spiermar/d3-flame-graph):
|
101
|
+
|
102
|
+
```
|
103
|
+
$ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump > flamegraph.html
|
104
|
+
```
|
93
105
|
|
94
|
-
|
106
|
+
And just open the result by your browser.
|
95
107
|
|
96
|
-
|
108
|
+
## Sampling
|
97
109
|
|
98
|
-
|
110
|
+
Four sampling modes are supported:
|
99
111
|
|
100
|
-
-
|
101
|
-
-
|
102
|
-
-
|
103
|
-
-
|
112
|
+
- `:wall` (using `ITIMER_REAL` and `SIGALRM`) [default mode]
|
113
|
+
- `:cpu` (using `ITIMER_PROF` and `SIGPROF`)
|
114
|
+
- `:object` (using `RUBY_INTERNAL_EVENT_NEWOBJ`)
|
115
|
+
- `:custom` (user-defined via `StackProf.sample`)
|
104
116
|
|
105
|
-
|
117
|
+
Samplers have a tuneable interval which can be used to reduce overhead or increase granularity:
|
106
118
|
|
107
|
-
-
|
119
|
+
- Wall time: sample every _interval_ microseconds of wallclock time (default: 1000)
|
108
120
|
|
109
121
|
```ruby
|
110
122
|
StackProf.run(mode: :wall, out: 'tmp/stackprof.dump', interval: 1000) do
|
@@ -112,7 +124,7 @@ StackProf.run(mode: :wall, out: 'tmp/stackprof.dump', interval: 1000) do
|
|
112
124
|
end
|
113
125
|
```
|
114
126
|
|
115
|
-
-
|
127
|
+
- CPU time: sample every _interval_ microseconds of CPU activity (default: 1000 = 1 millisecond)
|
116
128
|
|
117
129
|
```ruby
|
118
130
|
StackProf.run(mode: :cpu, out: 'tmp/stackprof.dump', interval: 1000) do
|
@@ -120,7 +132,7 @@ StackProf.run(mode: :cpu, out: 'tmp/stackprof.dump', interval: 1000) do
|
|
120
132
|
end
|
121
133
|
```
|
122
134
|
|
123
|
-
-
|
135
|
+
- Object allocation: sample every _interval_ allocations (default: 1)
|
124
136
|
|
125
137
|
|
126
138
|
```ruby
|
@@ -129,30 +141,36 @@ StackProf.run(mode: :object, out: 'tmp/stackprof.dump', interval: 1) do
|
|
129
141
|
end
|
130
142
|
```
|
131
143
|
|
132
|
-
samples
|
144
|
+
By default, samples taken during garbage collection will show as garbage collection frames
|
145
|
+
including both mark and sweep phases. For longer traces, these can leave gaps in a flamegraph
|
146
|
+
that are hard to follow. They can be disabled by setting the `ignore_gc` option to true.
|
147
|
+
Garbage collection time will still be present in the profile but not explicitly marked with
|
148
|
+
its own frame.
|
149
|
+
|
150
|
+
Samples are taken using a combination of three new C-APIs in ruby 2.1:
|
133
151
|
|
134
|
-
-
|
152
|
+
- Signal handlers enqueue a sampling job using `rb_postponed_job_register_one`.
|
135
153
|
this ensures callstack samples can be taken safely, in case the VM is garbage collecting
|
136
154
|
or in some other inconsistent state during the interruption.
|
137
155
|
|
138
|
-
-
|
139
|
-
to the VM's call stack.
|
156
|
+
- Stack frames are collected via `rb_profile_frames`, which provides low-overhead C-API access
|
157
|
+
to the VM's call stack. No object allocations occur in this path, allowing stackprof to collect
|
140
158
|
callstacks in allocation mode.
|
141
159
|
|
142
|
-
-
|
160
|
+
- In allocation mode, samples are taken via `rb_tracepoint_new(RUBY_INTERNAL_EVENT_NEWOBJ)`,
|
143
161
|
which provides a notification every time the VM allocates a new object.
|
144
162
|
|
145
|
-
|
163
|
+
## Aggregation
|
146
164
|
|
147
|
-
|
148
|
-
|
165
|
+
Each sample consists of N stack frames, where a frame looks something like `MyClass#method` or `block in MySingleton.method`.
|
166
|
+
For each of these frames in the sample, the profiler collects a few pieces of metadata:
|
149
167
|
|
150
|
-
- samples
|
151
|
-
- total_samples
|
152
|
-
- lines
|
153
|
-
- edges
|
168
|
+
- `samples`: Number of samples where this was the topmost frame
|
169
|
+
- `total_samples`: Samples where this frame was in the stack
|
170
|
+
- `lines`: Samples per line number in this frame
|
171
|
+
- `edges`: Samples per callee frame (methods invoked by this frame)
|
154
172
|
|
155
|
-
|
173
|
+
The aggregation algorithm is roughly equivalent to the following pseudo code:
|
156
174
|
|
157
175
|
``` ruby
|
158
176
|
trap('PROF') do
|
@@ -171,18 +189,18 @@ trap('PROF') do
|
|
171
189
|
end
|
172
190
|
```
|
173
191
|
|
174
|
-
|
192
|
+
This technique builds up an incremental call graph from the samples. On any given frame,
|
175
193
|
the sum of the outbound edge weights is equal to total samples collected on that frame
|
176
194
|
(`frame.total_samples == frame.edges.values.sum`).
|
177
195
|
|
178
|
-
|
196
|
+
## Reporting
|
179
197
|
|
180
|
-
|
181
|
-
-
|
182
|
-
-
|
183
|
-
-
|
198
|
+
Multiple reporting modes are supported:
|
199
|
+
- Text
|
200
|
+
- Dotgraph
|
201
|
+
- Source annotation
|
184
202
|
|
185
|
-
|
203
|
+
### `StackProf::Report.new(data).print_text`
|
186
204
|
|
187
205
|
```
|
188
206
|
TOTAL (pct) SAMPLES (pct) FRAME
|
@@ -197,9 +215,7 @@ multiple reporting modes are supported:
|
|
197
215
|
188 (100.0%) 0 (0.0%) <main>
|
198
216
|
```
|
199
217
|
|
200
|
-
|
201
|
-
|
202
|
-
![](http://cl.ly/image/2t3l2q0l0B0A/content)
|
218
|
+
### `StackProf::Report.new(data).print_graphviz`
|
203
219
|
|
204
220
|
```
|
205
221
|
digraph profile {
|
@@ -223,7 +239,7 @@ digraph profile {
|
|
223
239
|
}
|
224
240
|
```
|
225
241
|
|
226
|
-
|
242
|
+
### `StackProf::Report.new(data).print_method(/pow|newobj|math/)`
|
227
243
|
|
228
244
|
```
|
229
245
|
A#pow (/Users/tmm1/code/stackprof/sample.rb:11)
|
@@ -245,10 +261,10 @@ block in A#math (/Users/tmm1/code/stackprof/sample.rb:21)
|
|
245
261
|
| 23 | end
|
246
262
|
```
|
247
263
|
|
248
|
-
|
264
|
+
## Usage
|
249
265
|
|
250
|
-
|
251
|
-
|
266
|
+
The profiler is compiled as a C-extension and exposes a simple api: `StackProf.run(mode: [:cpu|:wall|:object])`.
|
267
|
+
The `run` method takes a block of code and returns a profile as a simple hash.
|
252
268
|
|
253
269
|
``` ruby
|
254
270
|
# sample after every 1ms of cpu activity
|
@@ -257,12 +273,12 @@ profile = StackProf.run(mode: :cpu, interval: 1000) do
|
|
257
273
|
end
|
258
274
|
```
|
259
275
|
|
260
|
-
|
261
|
-
(as json/marshal for example) for later processing.
|
276
|
+
This profile data structure is part of the public API, and is intended to be saved
|
277
|
+
(as json/marshal for example) for later processing. The reports above can be generated
|
262
278
|
by passing this structure into `StackProf::Report.new`.
|
263
279
|
|
264
|
-
|
265
|
-
identifying information such as its name, file and line.
|
280
|
+
The format itself is very simple. It contains a header and a list of frames. Each frame has a unique ID and
|
281
|
+
identifying information such as its name, file, and line. The frame also contains sampling data, including per-line
|
266
282
|
samples, and a list of relationships to other frames represented as weighted edges.
|
267
283
|
|
268
284
|
``` ruby
|
@@ -289,37 +305,41 @@ samples, and a list of relationships to other frames represented as weighted edg
|
|
289
305
|
:lines=>{8=>1}},
|
290
306
|
```
|
291
307
|
|
292
|
-
|
308
|
+
Above, `A#pow` was involved in 91 samples, and in all cases it was at the top of the stack on line 12.
|
293
309
|
|
294
|
-
`A#initialize` was in 185 samples, but it was at the top of the stack in only 1 sample.
|
295
|
-
divided up between its callee edges.
|
310
|
+
`A#initialize` was in 185 samples, but it was at the top of the stack in only 1 sample. The rest of the samples are
|
311
|
+
divided up between its callee edges. All 91 calls to `A#pow` came from `A#initialize`, as seen by the edge numbered
|
296
312
|
`70346498324780`.
|
297
313
|
|
298
|
-
|
314
|
+
## Advanced usage
|
299
315
|
|
300
|
-
|
301
|
-
multiple start
|
316
|
+
The profiler can be started and stopped manually. Results are accumulated until retrieval, across
|
317
|
+
multiple `start`/`stop` invocations.
|
302
318
|
|
303
319
|
``` ruby
|
304
|
-
StackProf.running?
|
320
|
+
StackProf.running? # => false
|
305
321
|
StackProf.start(mode: :cpu)
|
322
|
+
StackProf.running? # => true
|
306
323
|
StackProf.stop
|
307
324
|
StackProf.results('/tmp/some.file')
|
308
325
|
```
|
309
326
|
|
310
|
-
|
327
|
+
## All options
|
311
328
|
|
312
329
|
`StackProf.run` accepts an options hash. Currently, the following options are recognized:
|
313
330
|
|
314
331
|
Option | Meaning
|
315
332
|
------- | ---------
|
316
|
-
`mode` |
|
317
|
-
`out` |
|
318
|
-
`interval` |
|
319
|
-
`
|
320
|
-
`
|
321
|
-
|
322
|
-
|
333
|
+
`mode` | Mode of sampling: `:cpu`, `:wall`, `:object`, or `:custom` [c.f.](#sampling)
|
334
|
+
`out` | The target file, which will be overwritten
|
335
|
+
`interval` | Mode-relative sample rate [c.f.](#sampling)
|
336
|
+
`ignore_gc` | Ignore garbage collection frames
|
337
|
+
`aggregate` | Defaults: `true` - if `false` disables [aggregation](#aggregation)
|
338
|
+
`raw` | Defaults `false` - if `true` collects the extra data required by the `--flamegraph` and `--stackcollapse` report types
|
339
|
+
`metadata` | Defaults to `{}`. Must be a `Hash`. metadata associated with this profile
|
340
|
+
`save_every`| (Rack middleware only) write the target file after this many requests
|
341
|
+
|
342
|
+
## Todo
|
323
343
|
|
324
344
|
* file/iseq blacklist
|
325
345
|
* restore signal handlers on stop
|
data/Rakefile
CHANGED
@@ -1,31 +1,27 @@
|
|
1
|
-
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
GEMSPEC = Gem::Specification::load('stackprof.gemspec')
|
8
|
-
|
9
|
-
require 'rubygems/package_task'
|
10
|
-
Gem::PackageTask.new(GEMSPEC) do |pkg|
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.libs << "lib"
|
7
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
11
8
|
end
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
#
|
10
|
+
if RUBY_ENGINE == "truffleruby"
|
11
|
+
task :compile do
|
12
|
+
# noop
|
13
|
+
end
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
task :clean do
|
16
|
+
# noop
|
17
|
+
end
|
18
|
+
else
|
19
|
+
require "rake/extensiontask"
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
require 'rake/testtask'
|
28
|
-
Rake::TestTask.new 'test' do |t|
|
29
|
-
t.test_files = FileList['test/test_*.rb']
|
21
|
+
Rake::ExtensionTask.new("stackprof") do |ext|
|
22
|
+
ext.ext_dir = "ext/stackprof"
|
23
|
+
ext.lib_dir = "lib/stackprof"
|
24
|
+
end
|
30
25
|
end
|
31
|
-
|
26
|
+
|
27
|
+
task default: %i(compile test)
|
data/bin/stackprof
CHANGED
@@ -2,83 +2,128 @@
|
|
2
2
|
require 'optparse'
|
3
3
|
require 'stackprof'
|
4
4
|
|
5
|
-
|
5
|
+
banner = <<-END
|
6
|
+
Usage: stackprof run [--mode=MODE|--out=FILE|--interval=INTERVAL|--format=FORMAT] -- COMMAND
|
7
|
+
Usage: stackprof [file.dump]+ [--text|--method=NAME|--callgrind|--graphviz]
|
8
|
+
END
|
6
9
|
|
7
|
-
|
8
|
-
|
10
|
+
if ARGV.first == "run"
|
11
|
+
ARGV.shift
|
12
|
+
env = {}
|
13
|
+
parser = OptionParser.new(banner) do |o|
|
14
|
+
o.on('--mode [MODE]', String, 'Mode of sampling: cpu, wall, object, default to wall') do |mode|
|
15
|
+
env["STACKPROF_MODE"] = mode
|
16
|
+
end
|
9
17
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
o.on('--sort-total', "Sort --text or --files output on total samples\n\n"){ options[:sort] = true }
|
14
|
-
o.on('--method [grep]', 'Zoom into specified method'){ |f| options[:format] = :method; options[:filter] = f }
|
15
|
-
o.on('--file [grep]', "Show annotated code for specified file\n\n"){ |f| options[:format] = :file; options[:filter] = f }
|
16
|
-
o.on('--callgrind', 'Callgrind output (use with kcachegrind, stackprof-gprof2dot.py)'){ options[:format] = :callgrind }
|
17
|
-
o.on('--graphviz', "Graphviz output (use with dot)"){ options[:format] = :graphviz }
|
18
|
-
o.on('--node-fraction [frac]', OptionParser::DecimalNumeric, 'Drop nodes representing less than [frac] fraction of samples'){ |n| options[:node_fraction] = n }
|
19
|
-
o.on('--stackcollapse', 'stackcollapse.pl compatible output (use with stackprof-flamegraph.pl)'){ options[:format] = :stackcollapse }
|
20
|
-
o.on('--flamegraph', "timeline-flamegraph output (js)"){ options[:format] = :flamegraph }
|
21
|
-
o.on('--flamegraph-viewer [f.js]', String, "open html viewer for flamegraph output\n\n"){ |file|
|
22
|
-
puts("open file://#{File.expand_path('../../lib/stackprof/flamegraph/viewer.html', __FILE__)}?data=#{File.expand_path(file)}")
|
23
|
-
exit
|
24
|
-
}
|
25
|
-
o.on('--select-files []', String, 'Show results of matching files'){ |path| (options[:select_files] ||= []) << File.expand_path(path) }
|
26
|
-
o.on('--reject-files []', String, 'Exclude results of matching files'){ |path| (options[:reject_files] ||= []) << File.expand_path(path) }
|
27
|
-
o.on('--select-names []', Regexp, 'Show results of matching method names'){ |regexp| (options[:select_names] ||= []) << regexp }
|
28
|
-
o.on('--reject-names []', Regexp, 'Exclude results of matching method names'){ |regexp| (options[:reject_names] ||= []) << regexp }
|
29
|
-
o.on('--dump', 'Print marshaled profile dump (combine multiple profiles)'){ options[:format] = :dump }
|
30
|
-
o.on('--debug', 'Pretty print raw profile data'){ options[:format] = :debug }
|
31
|
-
end
|
18
|
+
o.on('--out [FILENAME]', String, 'The target file, which will be overwritten. Defaults to a random temporary file') do |out|
|
19
|
+
env['STACKPROF_OUT'] = out
|
20
|
+
end
|
32
21
|
|
33
|
-
|
34
|
-
|
22
|
+
o.on('--interval [MILLISECONDS]', Integer, 'Mode-relative sample rate') do |interval|
|
23
|
+
env['STACKPROF_INTERVAL'] = interval.to_s
|
24
|
+
end
|
35
25
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
26
|
+
o.on('--raw', 'collects the extra data required by the --flamegraph and --stackcollapse report types') do |raw|
|
27
|
+
env['STACKPROF_RAW'] = raw.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
o.on('--ignore-gc', 'Ignore garbage collection frames') do |gc|
|
31
|
+
env['STACKPROF_IGNORE_GC'] = gc.to_s
|
32
|
+
end
|
43
33
|
end
|
44
|
-
|
45
|
-
|
34
|
+
parser.parse!
|
35
|
+
parser.abort(parser.help) if ARGV.empty?
|
36
|
+
stackprof_path = File.expand_path('../lib', __dir__)
|
37
|
+
env['RUBYOPT'] = "-I #{stackprof_path} -r stackprof/autorun #{ENV['RUBYOPT']}"
|
38
|
+
Kernel.exec(env, *ARGV)
|
39
|
+
else
|
40
|
+
options = {}
|
46
41
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
}
|
42
|
+
parser = OptionParser.new(banner) do |o|
|
43
|
+
o.on('--text', 'Text summary per method (default)'){ options[:format] = :text }
|
44
|
+
o.on('--json', 'JSON output (use with web viewers)'){ options[:format] = :json }
|
45
|
+
o.on('--files', 'List of files'){ |f| options[:format] = :files }
|
46
|
+
o.on('--limit [num]', Integer, 'Limit --text, --files, or --graphviz output to N entries'){ |n| options[:limit] = n }
|
47
|
+
o.on('--sort-total', "Sort --text or --files output on total samples\n\n"){ options[:sort] = true }
|
48
|
+
o.on('--method [grep]', 'Zoom into specified method'){ |f| options[:format] = :method; options[:filter] = f }
|
49
|
+
o.on('--file [grep]', "Show annotated code for specified file"){ |f| options[:format] = :file; options[:filter] = f }
|
50
|
+
o.on('--walk', "Walk the stacktrace interactively\n\n"){ |f| options[:walk] = true }
|
51
|
+
o.on('--callgrind', 'Callgrind output (use with kcachegrind, stackprof-gprof2dot.py)'){ options[:format] = :callgrind }
|
52
|
+
o.on('--graphviz', "Graphviz output (use with dot)"){ options[:format] = :graphviz }
|
53
|
+
o.on('--node-fraction [frac]', OptionParser::DecimalNumeric, 'Drop nodes representing less than [frac] fraction of samples'){ |n| options[:node_fraction] = n }
|
54
|
+
o.on('--stackcollapse', 'stackcollapse.pl compatible output (use with stackprof-flamegraph.pl)'){ options[:format] = :stackcollapse }
|
55
|
+
o.on('--timeline-flamegraph', "timeline-flamegraph output (js)"){ options[:format] = :timeline_flamegraph }
|
56
|
+
o.on('--alphabetical-flamegraph', "alphabetical-flamegraph output (js)"){ options[:format] = :alphabetical_flamegraph }
|
57
|
+
o.on('--flamegraph', "alias to --timeline-flamegraph"){ options[:format] = :timeline_flamegraph }
|
58
|
+
o.on('--flamegraph-viewer [f.js]', String, "open html viewer for flamegraph output"){ |file|
|
59
|
+
puts("open file://#{File.expand_path('../../lib/stackprof/flamegraph/viewer.html', __FILE__)}?data=#{File.expand_path(file)}")
|
60
|
+
exit
|
61
|
+
}
|
62
|
+
o.on('--d3-flamegraph', "flamegraph output (html using d3-flame-graph)\n\n"){ options[:format] = :d3_flamegraph }
|
63
|
+
o.on('--select-files []', String, 'Show results of matching files'){ |path| (options[:select_files] ||= []) << File.expand_path(path) }
|
64
|
+
o.on('--reject-files []', String, 'Exclude results of matching files'){ |path| (options[:reject_files] ||= []) << File.expand_path(path) }
|
65
|
+
o.on('--select-names []', Regexp, 'Show results of matching method names'){ |regexp| (options[:select_names] ||= []) << regexp }
|
66
|
+
o.on('--reject-names []', Regexp, 'Exclude results of matching method names'){ |regexp| (options[:reject_names] ||= []) << regexp }
|
67
|
+
o.on('--dump', 'Print marshaled profile dump (combine multiple profiles)'){ options[:format] = :dump }
|
68
|
+
o.on('--debug', 'Pretty print raw profile data'){ options[:format] = :debug }
|
69
|
+
end
|
52
70
|
|
53
|
-
|
54
|
-
|
55
|
-
default_options[:node_fraction] = 0.005
|
56
|
-
end
|
71
|
+
parser.parse!
|
72
|
+
parser.abort(parser.help) if ARGV.empty?
|
57
73
|
|
58
|
-
|
59
|
-
|
74
|
+
reports = []
|
75
|
+
while ARGV.size > 0
|
76
|
+
begin
|
77
|
+
file = ARGV.pop
|
78
|
+
reports << StackProf::Report.from_file(file)
|
79
|
+
rescue TypeError => e
|
80
|
+
STDERR.puts "** error parsing #{file}: #{e.inspect}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
report = reports.inject(:+)
|
60
84
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
when :
|
77
|
-
|
78
|
-
when :
|
79
|
-
|
80
|
-
when :
|
81
|
-
|
82
|
-
|
83
|
-
|
85
|
+
default_options = {
|
86
|
+
:format => :text,
|
87
|
+
:sort => false,
|
88
|
+
:limit => 30
|
89
|
+
}
|
90
|
+
|
91
|
+
if options[:format] == :graphviz
|
92
|
+
default_options[:limit] = 120
|
93
|
+
default_options[:node_fraction] = 0.005
|
94
|
+
end
|
95
|
+
|
96
|
+
options = default_options.merge(options)
|
97
|
+
options.delete(:limit) if options[:limit] == 0
|
98
|
+
|
99
|
+
case options[:format]
|
100
|
+
when :text
|
101
|
+
report.print_text(options[:sort], options[:limit], options[:select_files], options[:reject_files], options[:select_names], options[:reject_names])
|
102
|
+
when :json
|
103
|
+
report.print_json
|
104
|
+
when :debug
|
105
|
+
report.print_debug
|
106
|
+
when :dump
|
107
|
+
report.print_dump
|
108
|
+
when :callgrind
|
109
|
+
report.print_callgrind
|
110
|
+
when :graphviz
|
111
|
+
report.print_graphviz(options)
|
112
|
+
when :stackcollapse
|
113
|
+
report.print_stackcollapse
|
114
|
+
when :timeline_flamegraph
|
115
|
+
report.print_timeline_flamegraph
|
116
|
+
when :alphabetical_flamegraph
|
117
|
+
report.print_alphabetical_flamegraph
|
118
|
+
when :d3_flamegraph
|
119
|
+
report.print_d3_flamegraph
|
120
|
+
when :method
|
121
|
+
options[:walk] ? report.walk_method(options[:filter]) : report.print_method(options[:filter])
|
122
|
+
when :file
|
123
|
+
report.print_file(options[:filter])
|
124
|
+
when :files
|
125
|
+
report.print_files(options[:sort], options[:limit])
|
126
|
+
else
|
127
|
+
raise ArgumentError, "unknown format: #{options[:format]}"
|
128
|
+
end
|
84
129
|
end
|
data/ext/stackprof/extconf.rb
CHANGED