stackprof 0.2.11 → 0.2.25

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 88f62f89ebff2c249b7eaabcb330f12997f09120
4
- data.tar.gz: 629071b6584701d830b827e5d4b9b0951eaa0282
2
+ SHA256:
3
+ metadata.gz: b299eb696cf0c2748e931532afab3f90bab1c94447ff1c844bce0eda878a93c6
4
+ data.tar.gz: bd2c389baa8253fc06beda425abc87762b9bca425c6bc5046655610fb9852e79
5
5
  SHA512:
6
- metadata.gz: d02684e9bd77e2b561f626a69a897e1ec2c89b870b30ef54709165cc1debb966a56127f3053d7a27e81cc7c3c62fef50865ebd5bcbfd6d2b28630add6280e479
7
- data.tar.gz: 2d5d70aaa53080112d8f794d1d5412cd8225f451f43431f30209e42854b5de8246ff77347ef39496c2b55b447d6cf114116b81dc2eb458adea16a5d9ce03606e
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
@@ -2,3 +2,5 @@
2
2
  /lib/stackprof/stackprof.bundle
3
3
  /lib/stackprof/stackprof.so
4
4
  *.sw?
5
+ /pkg
6
+ /Gemfile.lock
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
- ## stackprof
1
+ # Stackprof
2
2
 
3
- a sampling call-stack profiler for ruby 2.1+
3
+ A sampling call-stack profiler for Ruby.
4
4
 
5
- inspired heavily by [gperftools](https://code.google.com/p/gperftools/),
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
- ### getting started
7
+ ## Requirements
9
8
 
10
- #### Install
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
- #### Run
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
- You can generate a flamegraph however additional data must be collected using the `raw: true` flag. Once you've collected results with this flag enabled you can generate a flamegraph:
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
- Once the flamegraph has been generated you can generate a viewer command with:
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 in stackprof flamegraph viewer:
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)
93
99
 
94
- ![](http://i.imgur.com/EwndrgD.png)
100
+ Alternatively, you can generate a flamegraph that uses [d3-flame-graph](https://github.com/spiermar/d3-flame-graph):
95
101
 
96
- ### sampling
102
+ ```
103
+ $ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump > flamegraph.html
104
+ ```
97
105
 
98
- four sampling modes are supported:
106
+ And just open the result by your browser.
99
107
 
100
- - :wall (using `ITIMER_REAL` and `SIGALRM`)
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
- samplers have a tuneable interval which can be used to reduce overhead or increase granularity:
110
+ Four sampling modes are supported:
106
111
 
107
- - wall time: sample every _interval_ microseconds of wallclock time (default: 1000)
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
- - cpu time: sample every _interval_ microseconds of cpu activity (default: 1000 = 1 millisecond)
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
- - object allocation: sample every _interval_ allocations (default: 1)
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 are taken using a combination of three new C-APIs in ruby 2.1:
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
- - signal handlers enqueue a sampling job using `rb_postponed_job_register_one`.
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
- - stack frames are collected via `rb_profile_frames`, which provides low-overhead C-API access
139
- to the VM's call stack. no object allocations occur in this path, allowing stackprof to collect
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
- - in allocation mode, samples are taken via `rb_tracepoint_new(RUBY_INTERNAL_EVENT_NEWOBJ)`,
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
- ### Aggregation
163
+ ## Aggregation
146
164
 
147
- each sample consists of N stack frames, where a frame looks something like `MyClass#method` or `block in MySingleton.method`.
148
- for each of these frames in the sample, the profiler collects a few pieces of metadata:
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: number of samples where this was the topmost frame
151
- - total_samples: samples where this frame was in the stack
152
- - lines: samples per line number in this frame
153
- - edges: samples per callee frame (methods invoked by this frame)
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
- the aggregation algorithm is roughly equivalent to the following pseudo code:
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
- this technique builds up an incremental callgraph from the samples. on any given frame,
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
- ### reporting
196
+ ## Reporting
179
197
 
180
- multiple reporting modes are supported:
181
- - text
182
- - dotgraph
183
- - source annotation
198
+ Multiple reporting modes are supported:
199
+ - Text
200
+ - Dotgraph
201
+ - Source annotation
184
202
 
185
- #### `StackProf::Report.new(data).print_text`
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
- #### `StackProf::Report.new(data).print_graphviz`
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
- #### `StackProf::Report.new(data).print_method(/pow|newobj|math/)`
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
- ### usage
264
+ ## Usage
249
265
 
250
- the profiler is compiled as a C-extension and exposes a simple api: `StackProf.run(mode: [:cpu|:wall|:object])`.
251
- the `run` method takes a block of code and returns a profile as a simple hash.
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
- this profile data structure is part of the public API, and is intended to be saved
261
- (as json/marshal for example) for later processing. the reports above can be generated
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
- the format itself is very simple. it contains a header and a list of frames. each frame has a unique id and
265
- identifying information such as its name, file and line. the frame also contains sampling data, including per-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
- above, `A#pow` was involved in 91 samples, and in all cases it was at the top of the stack on line 12.
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. the rest of the samples are
295
- divided up between its callee edges. all 91 calls to `A#pow` came from `A#initialize`, as seen by the edge numbered
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
- ### advanced usage
314
+ ## Advanced usage
299
315
 
300
- the profiler can be started and stopped manually. results are accumulated until retrieval, across
301
- multiple start/stop invocations.
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
- ### all options
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` | mode of sampling: `:cpu`, `:wall`, `:object`, or `:custom` [c.f.](#sampling)
317
- `out` | the target file, which will be overwritten
318
- `interval` | mode-relative sample rate [c.f.](#sampling)
319
- `aggregate` | defaults: `true` - if `false` disables [aggregation](#aggregation)
320
- `raw` | defaults `false` - if `true` collects the extra data required by the `--flamegraph` and `--stackcollapse` report types
321
- `save_every`| (rack middleware only) write the target file after this many requests
322
-
323
- ### todo
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
- task :default => :test
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
2
3
 
3
- # ==========================================================
4
- # Packaging
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
- # Ruby Extension
15
- # ==========================================================
10
+ if RUBY_ENGINE == "truffleruby"
11
+ task :compile do
12
+ # noop
13
+ end
16
14
 
17
- require 'rake/extensiontask'
18
- Rake::ExtensionTask.new('stackprof', GEMSPEC) do |ext|
19
- ext.lib_dir = 'lib/stackprof'
20
- end
21
- task :build => :compile
15
+ task :clean do
16
+ # noop
17
+ end
18
+ else
19
+ require "rake/extensiontask"
22
20
 
23
- # ==========================================================
24
- # Testing
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
- task :test => :build
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
- options = {}
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
- parser = OptionParser.new(ARGV) do |o|
8
- o.banner = "Usage: stackprof [file.dump]+ [--text|--method=NAME|--callgrind|--graphviz]"
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
- o.on('--text', 'Text summary per method (default)'){ options[:format] = :text }
11
- o.on('--files', 'List of files'){ |f| options[:format] = :files }
12
- o.on('--limit [num]', Integer, 'Limit --text, --files, or --graphviz output to N entries'){ |n| options[:limit] = n }
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
- parser.parse!
35
- parser.abort(parser.help) if ARGV.empty?
22
+ o.on('--interval [MILLISECONDS]', Integer, 'Mode-relative sample rate') do |interval|
23
+ env['STACKPROF_INTERVAL'] = interval.to_s
24
+ end
36
25
 
37
- reports = []
38
- while ARGV.size > 0
39
- begin
40
- file = ARGV.pop
41
- reports << StackProf::Report.new(Marshal.load(IO.binread(file)))
42
- rescue TypeError => e
43
- STDERR.puts "** error parsing #{file}: #{e.inspect}"
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
- end
46
- report = reports.inject(:+)
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
- default_options = {
49
- :format => :text,
50
- :sort => false,
51
- :limit => 30
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
- if options[:format] == :graphviz
55
- default_options[:limit] = 120
56
- default_options[:node_fraction] = 0.005
57
- end
71
+ parser.parse!
72
+ parser.abort(parser.help) if ARGV.empty?
58
73
 
59
- options = default_options.merge(options)
60
- options.delete(:limit) if options[:limit] == 0
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
- case options[:format]
63
- when :text
64
- report.print_text(options[:sort], options[:limit], options[:select_files], options[:reject_files], options[:select_names], options[:reject_names])
65
- when :debug
66
- report.print_debug
67
- when :dump
68
- report.print_dump
69
- when :callgrind
70
- report.print_callgrind
71
- when :graphviz
72
- report.print_graphviz(options)
73
- when :stackcollapse
74
- report.print_stackcollapse
75
- when :flamegraph
76
- report.print_flamegraph
77
- when :method
78
- options[:walk] ? report.walk_method(options[:filter]) : report.print_method(options[:filter])
79
- when :file
80
- report.print_file(options[:filter])
81
- when :files
82
- report.print_files(options[:sort], options[:limit])
83
- else
84
- raise ArgumentError, "unknown format: #{options[:format]}"
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
@@ -1,4 +1,10 @@
1
1
  require 'mkmf'
2
+
3
+ if RUBY_ENGINE == 'truffleruby'
4
+ File.write('Makefile', dummy_makefile($srcdir).join(""))
5
+ return
6
+ end
7
+
2
8
  if have_func('rb_postponed_job_register_one') &&
3
9
  have_func('rb_profile_frames') &&
4
10
  have_func('rb_tracepoint_new') &&