sperf 0.2.1 → 0.3.0
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/exe/sperf +1 -475
- data/lib/sperf/version.rb +2 -2
- data/lib/sperf.rb +1 -607
- metadata +49 -56
- data/ext/sperf/extconf.rb +0 -6
- data/ext/sperf/sperf.c +0 -796
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 70bf807f20a737cfb74344fb33a3f0e984d6bdd369ea3ff25f528467df676bb8
|
|
4
|
+
data.tar.gz: 5fb3a228a2d0a600dcbe648688c2e068c2634c575900aa058e256b55ae14f176
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd24d9e9c959baf5623a2601f3f46dce83d340d6a1b721dce2da5ddc3066c1d8e1f8cfd3af2aaa72cd85bb230be703dd1823021976e7fbbb1f9f36fd50aac0f8
|
|
7
|
+
data.tar.gz: df95dbacd3a6eef0794a54503856607becd5028fb0e917d9d2e4b957f68d993ea05c590f57f4e1fa21d51737dea052ddbebb116616babbc5c650e643c0eeba56
|
data/exe/sperf
CHANGED
|
@@ -1,476 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require "socket"
|
|
4
|
-
|
|
5
|
-
def find_available_port
|
|
6
|
-
server = TCPServer.new("localhost", 0)
|
|
7
|
-
port = server.addr[1]
|
|
8
|
-
server.close
|
|
9
|
-
port
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
HELP_TEXT = <<'HELP'
|
|
13
|
-
sperf - safepoint-based sampling performance profiler for Ruby
|
|
14
|
-
|
|
15
|
-
OVERVIEW
|
|
16
|
-
|
|
17
|
-
sperf profiles Ruby programs by sampling at safepoints and using actual
|
|
18
|
-
time deltas (nanoseconds) as weights to correct safepoint bias.
|
|
19
|
-
POSIX systems (Linux, macOS). Requires Ruby >= 3.4.0.
|
|
20
|
-
|
|
21
|
-
CLI USAGE
|
|
22
|
-
|
|
23
|
-
sperf record [options] command [args...]
|
|
24
|
-
sperf stat [options] command [args...]
|
|
25
|
-
sperf report [options] [file]
|
|
26
|
-
sperf help
|
|
27
|
-
|
|
28
|
-
record: Profile and save to file.
|
|
29
|
-
-o, --output PATH Output file (default: sperf.data)
|
|
30
|
-
-f, --frequency HZ Sampling frequency in Hz (default: 1000)
|
|
31
|
-
-m, --mode MODE cpu or wall (default: cpu)
|
|
32
|
-
--format FORMAT pprof, collapsed, or text (default: auto from extension)
|
|
33
|
-
--signal VALUE Timer signal (Linux only): signal number, or 'false'
|
|
34
|
-
for nanosleep thread (default: auto)
|
|
35
|
-
-v, --verbose Print sampling statistics to stderr
|
|
36
|
-
|
|
37
|
-
stat: Run command and print performance summary to stderr.
|
|
38
|
-
Always uses wall mode. No file output by default.
|
|
39
|
-
-o, --output PATH Also save profile to file (default: none)
|
|
40
|
-
-f, --frequency HZ Sampling frequency in Hz (default: 1000)
|
|
41
|
-
--signal VALUE Timer signal (Linux only): signal number, or 'false'
|
|
42
|
-
for nanosleep thread (default: auto)
|
|
43
|
-
-v, --verbose Print additional sampling statistics
|
|
44
|
-
|
|
45
|
-
Shows: user/sys/real time, time breakdown (CPU execution, GVL blocked,
|
|
46
|
-
GVL wait, GC marking, GC sweeping), and top 5 hot functions.
|
|
47
|
-
|
|
48
|
-
report: Open pprof profile with go tool pprof. Requires Go.
|
|
49
|
-
--top Print top functions by flat time
|
|
50
|
-
--text Print text report
|
|
51
|
-
Default (no flag): opens interactive web UI in browser.
|
|
52
|
-
Default file: sperf.data
|
|
53
|
-
|
|
54
|
-
diff: Compare two pprof profiles (target - base). Requires Go.
|
|
55
|
-
--top Print top functions by diff
|
|
56
|
-
--text Print text diff report
|
|
57
|
-
Default (no flag): opens diff in browser.
|
|
58
|
-
|
|
59
|
-
Examples:
|
|
60
|
-
sperf record ruby app.rb
|
|
61
|
-
sperf record -o profile.pb.gz ruby app.rb
|
|
62
|
-
sperf record -m wall -f 500 -o profile.pb.gz ruby server.rb
|
|
63
|
-
sperf record -o profile.collapsed ruby app.rb
|
|
64
|
-
sperf record -o profile.txt ruby app.rb
|
|
65
|
-
sperf stat ruby app.rb
|
|
66
|
-
sperf stat -o profile.pb.gz ruby app.rb
|
|
67
|
-
sperf report
|
|
68
|
-
sperf report --top profile.pb.gz
|
|
69
|
-
sperf diff before.pb.gz after.pb.gz
|
|
70
|
-
sperf diff --top before.pb.gz after.pb.gz
|
|
71
|
-
|
|
72
|
-
RUBY API
|
|
73
|
-
|
|
74
|
-
require "sperf"
|
|
75
|
-
|
|
76
|
-
# Block form (recommended) — profiles the block and writes to file
|
|
77
|
-
Sperf.start(output: "profile.pb.gz", frequency: 500, mode: :cpu) do
|
|
78
|
-
# code to profile
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Manual start/stop — returns data hash for programmatic use
|
|
82
|
-
Sperf.start(frequency: 1000, mode: :wall)
|
|
83
|
-
# ... code to profile ...
|
|
84
|
-
data = Sperf.stop
|
|
85
|
-
|
|
86
|
-
# Save data to file later
|
|
87
|
-
Sperf.save("profile.pb.gz", data)
|
|
88
|
-
Sperf.save("profile.collapsed", data)
|
|
89
|
-
Sperf.save("profile.txt", data)
|
|
90
|
-
|
|
91
|
-
Sperf.start parameters:
|
|
92
|
-
frequency: Sampling frequency in Hz (Integer, default: 1000)
|
|
93
|
-
mode: :cpu or :wall (Symbol, default: :cpu)
|
|
94
|
-
output: File path to write on stop (String or nil)
|
|
95
|
-
verbose: Print statistics to stderr (true/false, default: false)
|
|
96
|
-
format: :pprof, :collapsed, :text, or nil for auto-detect (Symbol or nil)
|
|
97
|
-
|
|
98
|
-
Sperf.stop return value:
|
|
99
|
-
nil if profiler was not running; otherwise a Hash:
|
|
100
|
-
{ mode: :cpu, # or :wall
|
|
101
|
-
frequency: 500,
|
|
102
|
-
sampling_count: 1234,
|
|
103
|
-
sampling_time_ns: 56789,
|
|
104
|
-
samples: [ # Array of [frames, weight]
|
|
105
|
-
[frames, weight], # frames: [[path, label], ...] deepest-first
|
|
106
|
-
... # weight: Integer (nanoseconds)
|
|
107
|
-
] }
|
|
108
|
-
|
|
109
|
-
Sperf.save(path, data, format: nil)
|
|
110
|
-
Writes data to path. format: :pprof, :collapsed, or :text.
|
|
111
|
-
nil auto-detects from extension.
|
|
112
|
-
|
|
113
|
-
PROFILING MODES
|
|
114
|
-
|
|
115
|
-
cpu Measures per-thread CPU time via Linux thread clock.
|
|
116
|
-
Use for: finding functions that consume CPU cycles.
|
|
117
|
-
Ignores time spent sleeping, in I/O, or waiting for GVL.
|
|
118
|
-
|
|
119
|
-
wall Measures wall-clock time (CLOCK_MONOTONIC).
|
|
120
|
-
Use for: finding where wall time goes, including I/O, sleep, GVL
|
|
121
|
-
contention, and off-CPU waits.
|
|
122
|
-
Includes synthetic frames (see below).
|
|
123
|
-
|
|
124
|
-
OUTPUT FORMATS
|
|
125
|
-
|
|
126
|
-
pprof (default)
|
|
127
|
-
Gzip-compressed protobuf. Standard pprof format.
|
|
128
|
-
Extension convention: .pb.gz
|
|
129
|
-
View with: go tool pprof, pprof-rs, or speedscope (via import).
|
|
130
|
-
|
|
131
|
-
collapsed
|
|
132
|
-
Plain text. One line per unique stack: "frame1;frame2;...;leaf weight\n"
|
|
133
|
-
Frames are semicolon-separated, bottom-to-top. Weight in nanoseconds.
|
|
134
|
-
Extension convention: .collapsed
|
|
135
|
-
Compatible with: FlameGraph (flamegraph.pl), speedscope.
|
|
136
|
-
|
|
137
|
-
text
|
|
138
|
-
Human/AI-readable report. Shows total time, then flat and cumulative
|
|
139
|
-
top-N tables sorted by weight descending. No parsing needed.
|
|
140
|
-
Extension convention: .txt
|
|
141
|
-
Example output:
|
|
142
|
-
Total: 1523.4ms (cpu)
|
|
143
|
-
Samples: 4820, Frequency: 500Hz
|
|
144
|
-
|
|
145
|
-
Flat:
|
|
146
|
-
820.3ms 53.8% Array#each (app/models/user.rb)
|
|
147
|
-
312.1ms 20.5% JSON.parse (lib/json/parser.rb)
|
|
148
|
-
...
|
|
149
|
-
|
|
150
|
-
Cumulative:
|
|
151
|
-
1401.2ms 92.0% UsersController#index (app/controllers/users_controller.rb)
|
|
152
|
-
...
|
|
153
|
-
|
|
154
|
-
Format is auto-detected from the output file extension:
|
|
155
|
-
.collapsed → collapsed
|
|
156
|
-
.txt → text
|
|
157
|
-
anything else → pprof
|
|
158
|
-
The --format flag (CLI) or format: parameter (API) overrides auto-detect.
|
|
159
|
-
|
|
160
|
-
SYNTHETIC FRAMES
|
|
161
|
-
|
|
162
|
-
In wall mode, sperf adds synthetic frames that represent non-CPU time:
|
|
163
|
-
|
|
164
|
-
[GVL blocked] Time the thread spent off-GVL (I/O, sleep, C extension
|
|
165
|
-
releasing GVL). Attributed to the stack at SUSPENDED.
|
|
166
|
-
[GVL wait] Time the thread spent waiting to reacquire the GVL after
|
|
167
|
-
becoming ready. Indicates GVL contention. Same stack.
|
|
168
|
-
|
|
169
|
-
In both modes, GC time is tracked:
|
|
170
|
-
|
|
171
|
-
[GC marking] Time spent in GC marking phase (wall time).
|
|
172
|
-
[GC sweeping] Time spent in GC sweeping phase (wall time).
|
|
173
|
-
|
|
174
|
-
These always appear as the leaf (deepest) frame in a sample.
|
|
175
|
-
|
|
176
|
-
INTERPRETING RESULTS
|
|
177
|
-
|
|
178
|
-
Weight unit is always nanoseconds regardless of mode.
|
|
179
|
-
|
|
180
|
-
Flat time: weight attributed directly to a function (it was the leaf).
|
|
181
|
-
Cumulative time: weight for all samples where the function appears
|
|
182
|
-
anywhere in the stack.
|
|
183
|
-
|
|
184
|
-
High flat time → the function itself is expensive.
|
|
185
|
-
High cum but low flat → the function calls expensive children.
|
|
186
|
-
|
|
187
|
-
To convert: 1_000_000 ns = 1 ms, 1_000_000_000 ns = 1 s.
|
|
188
|
-
|
|
189
|
-
DIAGNOSING COMMON PERFORMANCE PROBLEMS
|
|
190
|
-
|
|
191
|
-
Problem: high CPU usage
|
|
192
|
-
Mode: cpu
|
|
193
|
-
Look for: functions with high flat cpu time.
|
|
194
|
-
Action: optimize the hot function or call it less.
|
|
195
|
-
|
|
196
|
-
Problem: slow request / high latency
|
|
197
|
-
Mode: wall
|
|
198
|
-
Look for: functions with high cum wall time.
|
|
199
|
-
If [GVL blocked] is dominant → I/O or sleep is the bottleneck.
|
|
200
|
-
If [GVL wait] is dominant → GVL contention; reduce GVL-holding work
|
|
201
|
-
or move work to Ractors / child processes.
|
|
202
|
-
|
|
203
|
-
Problem: GC pauses
|
|
204
|
-
Mode: cpu or wall
|
|
205
|
-
Look for: [GC marking] and [GC sweeping] samples.
|
|
206
|
-
High [GC marking] → too many live objects; reduce allocations.
|
|
207
|
-
High [GC sweeping] → too many short-lived objects; reuse or pool.
|
|
208
|
-
|
|
209
|
-
Problem: multithreaded app slower than expected
|
|
210
|
-
Mode: wall
|
|
211
|
-
Look for: [GVL wait] time across threads.
|
|
212
|
-
High [GVL wait] means threads are serialized on the GVL.
|
|
213
|
-
|
|
214
|
-
READING COLLAPSED STACKS PROGRAMMATICALLY
|
|
215
|
-
|
|
216
|
-
Each line: "bottom_frame;...;top_frame weight_ns"
|
|
217
|
-
Parse with:
|
|
218
|
-
File.readlines("profile.collapsed").each do |line|
|
|
219
|
-
stack, weight = line.rpartition(" ").then { |s, _, w| [s, w.to_i] }
|
|
220
|
-
frames = stack.split(";")
|
|
221
|
-
# frames[0] is bottom (main), frames[-1] is leaf (hot)
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
READING PPROF PROGRAMMATICALLY
|
|
225
|
-
|
|
226
|
-
Decompress + parse protobuf:
|
|
227
|
-
require "zlib"; require "stringio"
|
|
228
|
-
raw = Zlib::GzipReader.new(StringIO.new(File.binread("profile.pb.gz"))).read
|
|
229
|
-
# raw is a protobuf binary; use google-protobuf gem or pprof tooling.
|
|
230
|
-
|
|
231
|
-
Or convert to text with pprof CLI:
|
|
232
|
-
go tool pprof -text profile.pb.gz
|
|
233
|
-
go tool pprof -top profile.pb.gz
|
|
234
|
-
go tool pprof -flame profile.pb.gz
|
|
235
|
-
|
|
236
|
-
ENVIRONMENT VARIABLES
|
|
237
|
-
|
|
238
|
-
Used internally by the CLI to pass options to the auto-started profiler:
|
|
239
|
-
SPERF_ENABLED=1 Enable auto-start on require
|
|
240
|
-
SPERF_OUTPUT=path Output file path
|
|
241
|
-
SPERF_FREQUENCY=hz Sampling frequency
|
|
242
|
-
SPERF_MODE=cpu|wall Profiling mode
|
|
243
|
-
SPERF_FORMAT=fmt pprof, collapsed, or text
|
|
244
|
-
SPERF_VERBOSE=1 Print statistics
|
|
245
|
-
SPERF_SIGNAL=N|false Timer signal number or 'false' for nanosleep (Linux only)
|
|
246
|
-
|
|
247
|
-
TIPS
|
|
248
|
-
|
|
249
|
-
- Default frequency (1000 Hz) works well for most cases; overhead is < 0.2%.
|
|
250
|
-
- For long-running production profiling, lower frequency (100-500) reduces overhead further.
|
|
251
|
-
- Profile representative workloads, not micro-benchmarks.
|
|
252
|
-
- Compare cpu and wall profiles to distinguish CPU-bound from I/O-bound.
|
|
253
|
-
- The verbose flag (-v) shows sampling overhead and top functions on stderr.
|
|
254
|
-
HELP
|
|
255
|
-
|
|
256
|
-
USAGE = "Usage: sperf record [options] command [args...]\n" \
|
|
257
|
-
" sperf stat [options] command [args...]\n" \
|
|
258
|
-
" sperf report [options] [file]\n" \
|
|
259
|
-
" sperf diff [options] base.pb.gz target.pb.gz\n" \
|
|
260
|
-
" sperf help\n"
|
|
261
|
-
|
|
262
|
-
# Handle top-level flags before subcommand parsing
|
|
263
|
-
case ARGV.first
|
|
264
|
-
when "-v", "--version"
|
|
265
|
-
require "sperf"
|
|
266
|
-
puts "sperf #{Sperf::VERSION}"
|
|
267
|
-
exit
|
|
268
|
-
when "-h", "--help"
|
|
269
|
-
puts USAGE
|
|
270
|
-
puts
|
|
271
|
-
puts "Run 'sperf help' for full documentation"
|
|
272
|
-
exit
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
subcommand = ARGV.shift
|
|
276
|
-
|
|
277
|
-
case subcommand
|
|
278
|
-
when "help"
|
|
279
|
-
puts HELP_TEXT
|
|
280
|
-
exit
|
|
281
|
-
when "report"
|
|
282
|
-
# sperf report: wrapper around go tool pprof
|
|
283
|
-
report_mode = :http # default: open in browser
|
|
284
|
-
report_file = nil
|
|
285
|
-
|
|
286
|
-
report_parser = OptionParser.new do |opts|
|
|
287
|
-
opts.banner = "Usage: sperf report [options] [file]\n" \
|
|
288
|
-
" Opens pprof profile in browser (default) or prints summary.\n" \
|
|
289
|
-
" Default file: sperf.data"
|
|
290
|
-
|
|
291
|
-
opts.on("--top", "Print top functions by flat time") do
|
|
292
|
-
report_mode = :top
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
opts.on("--text", "Print text report") do
|
|
296
|
-
report_mode = :text
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
opts.on("-h", "--help", "Show this help") do
|
|
300
|
-
puts opts
|
|
301
|
-
exit
|
|
302
|
-
end
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
begin
|
|
306
|
-
report_parser.order!(ARGV)
|
|
307
|
-
rescue OptionParser::InvalidOption => e
|
|
308
|
-
$stderr.puts e.message
|
|
309
|
-
$stderr.puts report_parser
|
|
310
|
-
exit 1
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
report_file = ARGV.shift || "sperf.data"
|
|
314
|
-
|
|
315
|
-
unless File.exist?(report_file)
|
|
316
|
-
$stderr.puts "File not found: #{report_file}"
|
|
317
|
-
exit 1
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
unless system("go", "version", out: File::NULL, err: File::NULL)
|
|
321
|
-
$stderr.puts "'go' command not found. Install Go to use 'sperf report'."
|
|
322
|
-
$stderr.puts " https://go.dev/dl/"
|
|
323
|
-
exit 1
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
case report_mode
|
|
327
|
-
when :top
|
|
328
|
-
exec("go", "tool", "pprof", "-top", report_file)
|
|
329
|
-
when :text
|
|
330
|
-
exec("go", "tool", "pprof", "-text", report_file)
|
|
331
|
-
else
|
|
332
|
-
exec("go", "tool", "pprof", "-http=localhost:#{find_available_port}", report_file)
|
|
333
|
-
end
|
|
334
|
-
when "diff"
|
|
335
|
-
# sperf diff: compare two pprof profiles via go tool pprof -diff_base
|
|
336
|
-
diff_mode = :http
|
|
337
|
-
diff_parser = OptionParser.new do |opts|
|
|
338
|
-
opts.banner = "Usage: sperf diff [options] base.pb.gz target.pb.gz\n" \
|
|
339
|
-
" Compare two pprof profiles (shows target - base)."
|
|
340
|
-
|
|
341
|
-
opts.on("--top", "Print top functions by diff") do
|
|
342
|
-
diff_mode = :top
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
opts.on("--text", "Print text diff report") do
|
|
346
|
-
diff_mode = :text
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
opts.on("-h", "--help", "Show this help") do
|
|
350
|
-
puts opts
|
|
351
|
-
exit
|
|
352
|
-
end
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
begin
|
|
356
|
-
diff_parser.order!(ARGV)
|
|
357
|
-
rescue OptionParser::InvalidOption => e
|
|
358
|
-
$stderr.puts e.message
|
|
359
|
-
$stderr.puts diff_parser
|
|
360
|
-
exit 1
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
if ARGV.size < 2
|
|
364
|
-
$stderr.puts "Two profile files required."
|
|
365
|
-
$stderr.puts diff_parser
|
|
366
|
-
exit 1
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
base_file, target_file = ARGV.shift(2)
|
|
370
|
-
|
|
371
|
-
[base_file, target_file].each do |f|
|
|
372
|
-
unless File.exist?(f)
|
|
373
|
-
$stderr.puts "File not found: #{f}"
|
|
374
|
-
exit 1
|
|
375
|
-
end
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
unless system("go", "version", out: File::NULL, err: File::NULL)
|
|
379
|
-
$stderr.puts "'go' command not found. Install Go to use 'sperf diff'."
|
|
380
|
-
$stderr.puts " https://go.dev/dl/"
|
|
381
|
-
exit 1
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
case diff_mode
|
|
385
|
-
when :top
|
|
386
|
-
exec("go", "tool", "pprof", "-top", "-diff_base=#{base_file}", target_file)
|
|
387
|
-
when :text
|
|
388
|
-
exec("go", "tool", "pprof", "-text", "-diff_base=#{base_file}", target_file)
|
|
389
|
-
else
|
|
390
|
-
exec("go", "tool", "pprof", "-http=localhost:#{find_available_port}", "-diff_base=#{base_file}", target_file)
|
|
391
|
-
end
|
|
392
|
-
when "record", "stat"
|
|
393
|
-
# continue below
|
|
394
|
-
else
|
|
395
|
-
$stderr.puts "Unknown subcommand: #{subcommand.inspect}" if subcommand
|
|
396
|
-
$stderr.puts USAGE
|
|
397
|
-
exit 1
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
output = (subcommand == "stat") ? nil : "sperf.data"
|
|
401
|
-
frequency = 1000
|
|
402
|
-
mode = (subcommand == "stat") ? "wall" : "cpu"
|
|
403
|
-
format = nil
|
|
404
|
-
signal = nil
|
|
405
|
-
verbose = false
|
|
406
|
-
|
|
407
|
-
parser = OptionParser.new do |opts|
|
|
408
|
-
opts.banner = USAGE
|
|
409
|
-
|
|
410
|
-
opts.on("-o", "--output PATH", "Output file#{subcommand == 'stat' ? ' (default: none)' : ' (default: sperf.data)'}") do |v|
|
|
411
|
-
output = v
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
opts.on("-f", "--frequency HZ", Integer, "Sampling frequency in Hz (default: 1000)") do |v|
|
|
415
|
-
frequency = v
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
if subcommand == "record"
|
|
419
|
-
opts.on("-m", "--mode MODE", %w[cpu wall], "Profiling mode: cpu or wall (default: cpu)") do |v|
|
|
420
|
-
mode = v
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
opts.on("--format FORMAT", %w[pprof collapsed text],
|
|
424
|
-
"Output format: pprof, collapsed, or text (default: auto from extension)") do |v|
|
|
425
|
-
format = v
|
|
426
|
-
end
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
opts.on("--signal VALUE", "Timer signal (Linux only): signal number, or 'false' for nanosleep thread") do |v|
|
|
430
|
-
signal = (v == "false") ? "false" : v
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
opts.on("-v", "--verbose", "Print sampling statistics to stderr") do
|
|
434
|
-
verbose = true
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
opts.on("-h", "--help", "Show this help") do
|
|
438
|
-
puts opts
|
|
439
|
-
puts
|
|
440
|
-
puts "Run 'sperf help' for full documentation (modes, formats, diagnostics guide, etc.)"
|
|
441
|
-
exit
|
|
442
|
-
end
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
begin
|
|
446
|
-
parser.order!(ARGV)
|
|
447
|
-
rescue OptionParser::InvalidOption => e
|
|
448
|
-
$stderr.puts e.message
|
|
449
|
-
$stderr.puts parser
|
|
450
|
-
exit 1
|
|
451
|
-
end
|
|
452
|
-
|
|
453
|
-
if ARGV.empty?
|
|
454
|
-
$stderr.puts "No command specified."
|
|
455
|
-
$stderr.puts parser
|
|
456
|
-
exit 1
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
# Add lib dir to RUBYLIB so -rsperf can find the extension
|
|
460
|
-
lib_dir = File.expand_path("../lib", __dir__)
|
|
461
|
-
ENV["RUBYLIB"] = [lib_dir, ENV["RUBYLIB"]].compact.join(File::PATH_SEPARATOR)
|
|
462
|
-
ENV["RUBYOPT"] = "-rsperf #{ENV['RUBYOPT']}".strip
|
|
463
|
-
ENV["SPERF_ENABLED"] = "1"
|
|
464
|
-
ENV["SPERF_OUTPUT"] = output if output
|
|
465
|
-
ENV["SPERF_FREQUENCY"] = frequency.to_s
|
|
466
|
-
ENV["SPERF_MODE"] = mode
|
|
467
|
-
ENV["SPERF_FORMAT"] = format if format
|
|
468
|
-
ENV["SPERF_VERBOSE"] = "1" if verbose
|
|
469
|
-
ENV["SPERF_SIGNAL"] = signal if signal
|
|
470
|
-
|
|
471
|
-
if subcommand == "stat"
|
|
472
|
-
ENV["SPERF_STAT"] = "1"
|
|
473
|
-
ENV["SPERF_STAT_COMMAND"] = ARGV.join(" ")
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
exec(*ARGV)
|
|
2
|
+
require_relative '../lib/sperf'
|
data/lib/sperf/version.rb
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
module Sperf
|
|
2
|
-
VERSION = "0.
|
|
3
|
-
end
|
|
2
|
+
VERSION = "0.3.0"
|
|
3
|
+
end
|