stackprof 0.2.11 → 0.2.25
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 +5 -5
- data/.github/workflows/ci.yml +43 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +18 -0
- data/README.md +87 -68
- data/Rakefile +21 -25
- data/bin/stackprof +115 -71
- data/ext/stackprof/extconf.rb +6 -0
- data/ext/stackprof/stackprof.c +352 -90
- data/lib/stackprof/autorun.rb +19 -0
- data/lib/stackprof/middleware.rb +23 -7
- data/lib/stackprof/report.rb +282 -18
- data/lib/stackprof/truffleruby.rb +37 -0
- data/lib/stackprof.rb +18 -1
- 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 +163 -14
- data/test/test_truffleruby.rb +18 -0
- data/vendor/FlameGraph/flamegraph.pl +751 -85
- metadata +17 -9
- data/.travis.yml +0 -8
- data/Gemfile.lock +0 -27
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
|
+

|
93
99
|
|
94
|
-
|
100
|
+
Alternatively, you can generate a flamegraph that uses [d3-flame-graph](https://github.com/spiermar/d3-flame-graph):
|
95
101
|
|
96
|
-
|
102
|
+
```
|
103
|
+
$ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump > flamegraph.html
|
104
|
+
```
|
97
105
|
|
98
|
-
|
106
|
+
And just open the result by your browser.
|
99
107
|
|
100
|
-
|
101
|
-
- :cpu (using `ITIMER_PROF` and `SIGPROF`) [default mode]
|
102
|
-
- :object (using `RUBY_INTERNAL_EVENT_NEWOBJ`)
|
103
|
-
- :custom (user-defined via `StackProf.sample`)
|
108
|
+
## Sampling
|
104
109
|
|
105
|
-
|
110
|
+
Four sampling modes are supported:
|
106
111
|
|
107
|
-
- wall
|
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`)
|
116
|
+
|
117
|
+
Samplers have a tuneable interval which can be used to reduce overhead or increase granularity:
|
118
|
+
|
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.
|
133
149
|
|
134
|
-
|
150
|
+
Samples are taken using a combination of three new C-APIs in ruby 2.1:
|
151
|
+
|
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
|
-

|
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,38 +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
|
-
|
323
|
-
|
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
|
324
343
|
|
325
344
|
* file/iseq blacklist
|
326
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,84 +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"){ |f| options[:format] = :file; options[:filter] = f }
|
16
|
-
o.on('--walk', "Walk the stacktrace interactively\n\n"){ |f| options[:walk] = true }
|
17
|
-
o.on('--callgrind', 'Callgrind output (use with kcachegrind, stackprof-gprof2dot.py)'){ options[:format] = :callgrind }
|
18
|
-
o.on('--graphviz', "Graphviz output (use with dot)"){ options[:format] = :graphviz }
|
19
|
-
o.on('--node-fraction [frac]', OptionParser::DecimalNumeric, 'Drop nodes representing less than [frac] fraction of samples'){ |n| options[:node_fraction] = n }
|
20
|
-
o.on('--stackcollapse', 'stackcollapse.pl compatible output (use with stackprof-flamegraph.pl)'){ options[:format] = :stackcollapse }
|
21
|
-
o.on('--flamegraph', "timeline-flamegraph output (js)"){ options[:format] = :flamegraph }
|
22
|
-
o.on('--flamegraph-viewer [f.js]', String, "open html viewer for flamegraph output\n\n"){ |file|
|
23
|
-
puts("open file://#{File.expand_path('../../lib/stackprof/flamegraph/viewer.html', __FILE__)}?data=#{File.expand_path(file)}")
|
24
|
-
exit
|
25
|
-
}
|
26
|
-
o.on('--select-files []', String, 'Show results of matching files'){ |path| (options[:select_files] ||= []) << File.expand_path(path) }
|
27
|
-
o.on('--reject-files []', String, 'Exclude results of matching files'){ |path| (options[:reject_files] ||= []) << File.expand_path(path) }
|
28
|
-
o.on('--select-names []', Regexp, 'Show results of matching method names'){ |regexp| (options[:select_names] ||= []) << regexp }
|
29
|
-
o.on('--reject-names []', Regexp, 'Exclude results of matching method names'){ |regexp| (options[:reject_names] ||= []) << regexp }
|
30
|
-
o.on('--dump', 'Print marshaled profile dump (combine multiple profiles)'){ options[:format] = :dump }
|
31
|
-
o.on('--debug', 'Pretty print raw profile data'){ options[:format] = :debug }
|
32
|
-
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
|
33
21
|
|
34
|
-
|
35
|
-
|
22
|
+
o.on('--interval [MILLISECONDS]', Integer, 'Mode-relative sample rate') do |interval|
|
23
|
+
env['STACKPROF_INTERVAL'] = interval.to_s
|
24
|
+
end
|
36
25
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
44
33
|
end
|
45
|
-
|
46
|
-
|
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 = {}
|
47
41
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
}
|
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
|
53
70
|
|
54
|
-
|
55
|
-
|
56
|
-
default_options[:node_fraction] = 0.005
|
57
|
-
end
|
71
|
+
parser.parse!
|
72
|
+
parser.abort(parser.help) if ARGV.empty?
|
58
73
|
|
59
|
-
|
60
|
-
|
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(:+)
|
61
84
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
when :
|
78
|
-
|
79
|
-
when :
|
80
|
-
|
81
|
-
when :
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
85
129
|
end
|
data/ext/stackprof/extconf.rb
CHANGED