rperf 0.9.0 → 0.11.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/README.md +21 -6
- data/docs/help.md +199 -10
- data/exe/rperf +275 -55
- data/ext/rperf/rperf.c +96 -43
- data/lib/rperf/meta.rb +343 -0
- data/lib/rperf/rack.rb +7 -2
- data/lib/rperf/table.rb +156 -0
- data/lib/rperf/version.rb +1 -1
- data/lib/rperf/viewer/viewer.html +1148 -0
- data/lib/rperf/viewer.rb +101 -653
- data/lib/rperf.rb +208 -69
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: db5be965ca0511ea90e7482309f7dca4dc545a1c83a73f7b3fc08fc4029f9d34
|
|
4
|
+
data.tar.gz: 4ae2c1fe82a6d754a1ef0b4759ce2b1a2a22370788ce0197b1d269ba0a1e98e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d99ff20c55b9d359b0dfced5f13cdfaa5f8d4fe1debb5375f2a2de2c7d2ed8a219d2bd4b89c05dbb6ee7fe257b5cf51fa74ad8a85b017db608d6f30d9a5eadf1
|
|
7
|
+
data.tar.gz: fefb4b168fc733da575e758933083fd169ee6513ac38624f7d89ba8e66b202b4166bf2cb6d8f6e513fe85fb5fec9e31c48231d036ba19439a4e24e09b077460d
|
data/README.md
CHANGED
|
@@ -75,9 +75,22 @@ rperf report --top profile.json.gz # print top functions to terminal
|
|
|
75
75
|
|
|
76
76
|
# Compare two profiles (requires Go)
|
|
77
77
|
rperf diff before.json.gz after.json.gz # open diff in browser
|
|
78
|
+
|
|
79
|
+
# Track performance across commits (time-travel viewer)
|
|
80
|
+
rperf record --snapshot-dir ./profiles ruby app.rb # → profiles/rperf-<sha7>-<ts>.json.gz
|
|
81
|
+
rperf report ./profiles/ # sidebar: per-commit list, diff, sparkline
|
|
82
|
+
|
|
83
|
+
# Flat tables for AI analysis (no Go required)
|
|
84
|
+
rperf diff --format table base.json.gz head.json.gz | claude -p "analyze the regression"
|
|
85
|
+
|
|
86
|
+
# Drive rperf without wrapping the command (CI/tooling): source the env, then
|
|
87
|
+
# run your command unchanged — `bundle exec …` stays bundler-managed, plain
|
|
88
|
+
# `ruby …` stays plain. (Run `rperf help` for details.)
|
|
89
|
+
eval "$(rperf record --snapshot-dir ./profiles --print-env)"
|
|
90
|
+
export RPERF_ROOT_PROCESS=$$ && exec ruby app.rb
|
|
78
91
|
```
|
|
79
92
|
|
|
80
|
-
On `rperf report`, you can see the profile result like this page: [
|
|
93
|
+
On `rperf report`, you can see the profile result like this page: [rperf viewer](https://ko1.github.io/rperf/examples/cpu_intensive_profile.html)
|
|
81
94
|
|
|
82
95
|
### Ruby API
|
|
83
96
|
|
|
@@ -119,10 +132,12 @@ Thread.new { loop { sleep 3600; Rperf::Viewer.instance&.take_snapshot! } }
|
|
|
119
132
|
Profile without code changes (e.g., Rails):
|
|
120
133
|
|
|
121
134
|
```bash
|
|
122
|
-
RPERF_ENABLED=1 RPERF_MODE=wall ruby app.rb # → rperf.json.gz
|
|
123
|
-
rperf report
|
|
135
|
+
RPERF_ENABLED=1 RPERF_MODE=wall RUBYOPT=-rrperf ruby app.rb # → rperf.json.gz
|
|
136
|
+
rperf report # open in viewer
|
|
124
137
|
```
|
|
125
138
|
|
|
139
|
+
`RPERF_ENABLED` takes effect when rperf is loaded — if rperf is already in your Gemfile (e.g., Rails), `RUBYOPT=-rrperf` is unnecessary.
|
|
140
|
+
|
|
126
141
|
Run `rperf help` for full documentation, or see the [online manual](https://ko1.github.io/rperf/docs/manual/).
|
|
127
142
|
|
|
128
143
|
## Subcommands
|
|
@@ -134,8 +149,8 @@ Inspired by Linux `perf` — familiar subcommand interface for profiling workflo
|
|
|
134
149
|
| `rperf record` | Profile a command and save to file (default: `.json.gz`) |
|
|
135
150
|
| `rperf stat` | Profile a command and print summary to stderr |
|
|
136
151
|
| `rperf exec` | Profile a command and print full report to stderr |
|
|
137
|
-
| `rperf report` | Open viewer for `.json.gz
|
|
138
|
-
| `rperf diff` | Compare two profiles (requires Go) |
|
|
152
|
+
| `rperf report` | Open viewer for `.json.gz` (or a directory for time-travel mode); wraps `go tool pprof` for `.pb.gz` (requires Go) |
|
|
153
|
+
| `rperf diff` | Compare two profiles (requires Go, except `--format table`) |
|
|
139
154
|
| `rperf help` | Show full reference documentation |
|
|
140
155
|
|
|
141
156
|
## How It Works
|
|
@@ -188,7 +203,7 @@ rperf hooks GVL and GC events to attribute non-CPU time. These are recorded as l
|
|
|
188
203
|
- **Accurate despite safepoints** — Safepoint sampling is *safer* (no async-signal-safety issues), but normally *inaccurate*. rperf compensates with real time-delta weights, so profiles faithfully reflect where time is actually spent.
|
|
189
204
|
- **See the whole picture** (wall mode) — GVL contention, off-GVL I/O, GC marking/sweeping — all attributed to the call stacks responsible, via sample labels.
|
|
190
205
|
- **Built-in viewer** — Flamegraph, Top, Tags tabs with interactive tag filtering. No external tools needed to analyze profiles.
|
|
191
|
-
- **Low overhead** — Signal-based timer on Linux (
|
|
206
|
+
- **Low overhead** — Signal-based timer on Linux (signals delivered to a dedicated worker thread — Ruby threads are never interrupted). ~1–5 us per sample.
|
|
192
207
|
- **Zero code changes** — Profile any Ruby program via CLI or environment variables. Drop-in for Rails, too.
|
|
193
208
|
- **`perf`-like CLI** — `record`, `stat`, `report`, `diff` — if you know Linux perf, you already know rperf.
|
|
194
209
|
- **Multi-process** — automatically profiles forked/spawned Ruby child processes (e.g., Unicorn/Puma workers). Use `--no-inherit` to disable.
|
data/docs/help.md
CHANGED
|
@@ -26,10 +26,37 @@ POSIX systems (Linux, macOS). Requires Ruby >= 3.4.0.
|
|
|
26
26
|
(same as --format=text --output=/dev/stdout)
|
|
27
27
|
--signal VALUE Timer signal (Linux only): signal number, or 'false'
|
|
28
28
|
for nanosleep thread (default: auto)
|
|
29
|
+
--snapshot-dir DIR Save as rperf-<sha7>-<timestamp>.json.gz in DIR
|
|
30
|
+
(rperf-nogit-<timestamp>-<pid>.json.gz outside git)
|
|
31
|
+
--label KEY=VALUE Add a label to profile metadata (repeatable)
|
|
29
32
|
--no-inherit Do not profile forked/spawned child processes
|
|
30
33
|
--no-aggregate Disable C-level sample aggregation (raw per-sample data)
|
|
34
|
+
--print-env Print the env rperf would set (export KEY=VALUE lines)
|
|
35
|
+
and exit WITHOUT running a command — see below
|
|
31
36
|
-v, --verbose Print sampling statistics to stderr
|
|
32
37
|
|
|
38
|
+
#### --print-env: drive rperf without wrapping the command
|
|
39
|
+
|
|
40
|
+
Instead of `rperf record [options] -- command`, `--print-env` prints the
|
|
41
|
+
environment a profiled process auto-starts from and exits without running
|
|
42
|
+
anything. A wrapper (e.g. a CI script) sources it, makes the process that runs
|
|
43
|
+
the command the session root, and runs the command unchanged — so a
|
|
44
|
+
`bundle exec …` command stays bundler-managed and a plain `ruby …` stays plain;
|
|
45
|
+
rperf is never inserted into the command line.
|
|
46
|
+
|
|
47
|
+
eval "$(rperf record --snapshot-dir ./profiles --print-env)"
|
|
48
|
+
export RPERF_ROOT_PROCESS=$$ # the pid that will exec the command is the root
|
|
49
|
+
exec ruby app.rb # runs verbatim; rperf auto-starts via RUBYOPT
|
|
50
|
+
|
|
51
|
+
`RPERF_ROOT_PROCESS` is not emitted (the caller sets it to the pid that execs
|
|
52
|
+
the command); `RPERF_META_LABELS` is not emitted (set it yourself if you want
|
|
53
|
+
per-run labels). Everything else (`RUBYOPT=-rrperf`, output path, mode,
|
|
54
|
+
frequency, fork session dir, …) is included.
|
|
55
|
+
|
|
56
|
+
JSON output embeds `meta` (git commit, host, Ruby/rperf versions, labels)
|
|
57
|
+
and `summary` (time, GC, allocation, top methods) — see "Profile metadata"
|
|
58
|
+
under OUTPUT FORMATS.
|
|
59
|
+
|
|
33
60
|
### stat: Run command and print performance summary to stderr.
|
|
34
61
|
|
|
35
62
|
Uses wall mode by default. No file output by default.
|
|
@@ -37,6 +64,7 @@ Uses wall mode by default. No file output by default.
|
|
|
37
64
|
-o, --output PATH Also save profile to file (default: none)
|
|
38
65
|
-f, --frequency HZ Sampling frequency in Hz (default: 1000)
|
|
39
66
|
-m, --mode MODE cpu or wall (default: wall)
|
|
67
|
+
--label KEY=VALUE Add a label to profile metadata (repeatable)
|
|
40
68
|
--report Include flat/cumulative profile tables in output
|
|
41
69
|
--signal VALUE Timer signal (Linux only): signal number, or 'false'
|
|
42
70
|
for nanosleep thread (default: auto)
|
|
@@ -61,6 +89,7 @@ Like `stat --report`. Uses wall mode by default. No file output by default.
|
|
|
61
89
|
-o, --output PATH Also save profile to file (default: none)
|
|
62
90
|
-f, --frequency HZ Sampling frequency in Hz (default: 1000)
|
|
63
91
|
-m, --mode MODE cpu or wall (default: wall)
|
|
92
|
+
--label KEY=VALUE Add a label to profile metadata (repeatable)
|
|
64
93
|
--signal VALUE Timer signal (Linux only): signal number, or 'false'
|
|
65
94
|
for nanosleep thread (default: auto)
|
|
66
95
|
--no-inherit Do not profile forked/spawned child processes
|
|
@@ -74,11 +103,44 @@ and flat/cumulative top-50 function tables.
|
|
|
74
103
|
|
|
75
104
|
--top Print top functions by flat time
|
|
76
105
|
--text Print text report
|
|
106
|
+
--format FORMAT Flat table for AI/machine consumption:
|
|
107
|
+
'table' (TSV) or 'table-json' (JSON array)
|
|
77
108
|
--html Output static HTML viewer to stdout
|
|
109
|
+
--port PORT Port for the web UI (default: auto).
|
|
110
|
+
Useful for SSH port forwarding
|
|
111
|
+
--host HOST Bind address for the web UI (default: localhost).
|
|
112
|
+
0.0.0.0 allows external access — the viewer has
|
|
113
|
+
NO authentication; prefer SSH port forwarding
|
|
114
|
+
|
|
115
|
+
The browser is auto-opened only when a GUI is available (DISPLAY /
|
|
116
|
+
WAYLAND_DISPLAY on Linux) and the bind address is local; otherwise the URL
|
|
117
|
+
is printed for manual opening (no terminal-browser fallback).
|
|
78
118
|
|
|
79
119
|
Default (no flag): opens interactive web UI in browser.
|
|
80
120
|
Default file: rperf.json.gz
|
|
81
121
|
|
|
122
|
+
#### Time-travel mode (directory input)
|
|
123
|
+
|
|
124
|
+
rperf report ./profiles/
|
|
125
|
+
|
|
126
|
+
Passing a directory lists all `*.json(.gz)` profiles in a sidebar — one row
|
|
127
|
+
per snapshot with commit SHA (a `*` marks a dirty working tree), commit
|
|
128
|
+
subject, date, and alloc/GC badges versus the previous snapshot (⚠️ when
|
|
129
|
+
allocation changed more than ±15%). Rows are grouped by git branch
|
|
130
|
+
(main/master expanded by default). Only meta/summary heads are read for the
|
|
131
|
+
listing; profile bodies are lazy-loaded on selection, so directories with
|
|
132
|
+
100+ snapshots open instantly. Works well with `rperf record --snapshot-dir`.
|
|
133
|
+
|
|
134
|
+
- Click a row to view that snapshot; j / k move to the newer / older one.
|
|
135
|
+
- ⇄ on a row diffs the current snapshot against it: the flamegraph is
|
|
136
|
+
recolored by share change (red = increased, blue = decreased, neutral
|
|
137
|
+
below ±0.4pt; direction is base (older) → current).
|
|
138
|
+
- Shift+click a frame to pin that method: a sparkline of its share across
|
|
139
|
+
all snapshots appears at the top of the sidebar (points are filled in as
|
|
140
|
+
bodies load; click a point to jump). The pinned frame is highlighted and
|
|
141
|
+
others are dimmed.
|
|
142
|
+
- Files without meta (saved by older rperf) appear as unknown snapshots.
|
|
143
|
+
|
|
82
144
|
`--html` generates an HTML file with profile data embedded inline.
|
|
83
145
|
No server is needed — open it directly in a browser. d3 and
|
|
84
146
|
d3-flamegraph are loaded from CDN, so an internet connection is
|
|
@@ -87,15 +149,48 @@ sites (e.g., GitHub Pages).
|
|
|
87
149
|
|
|
88
150
|
rperf report --html profile.json.gz > report.html
|
|
89
151
|
|
|
90
|
-
### diff: Compare two profiles (target - base). Requires Go.
|
|
152
|
+
### diff: Compare two profiles (target - base). Requires Go (except --format table).
|
|
91
153
|
|
|
92
154
|
Accepts `.json.gz` (auto-converted to pprof) or `.pb.gz` files.
|
|
93
155
|
|
|
94
156
|
--top Print top functions by diff
|
|
95
157
|
--text Print text diff report
|
|
158
|
+
--format FORMAT Flat diff table for AI/machine consumption:
|
|
159
|
+
'table' (TSV) or 'table-json' (JSON array).
|
|
160
|
+
Computed in Ruby — no Go required.
|
|
161
|
+
.json.gz / .json files only.
|
|
162
|
+
--port PORT Port for the web UI (default: auto)
|
|
163
|
+
--host HOST Bind address for the web UI (default: localhost)
|
|
96
164
|
|
|
97
165
|
Default (no flag): opens diff in browser.
|
|
98
166
|
|
|
167
|
+
### Table output for AI analysis (--format table / table-json)
|
|
168
|
+
|
|
169
|
+
Aggregation, diffing, and cutoff happen on the rperf side; the output is a
|
|
170
|
+
flat table that an LLM can analyze directly — no tree walking required.
|
|
171
|
+
|
|
172
|
+
`rperf report --format table FILE` columns (self_pct descending, top 50
|
|
173
|
+
plus an `(other)` aggregate row):
|
|
174
|
+
|
|
175
|
+
method self_pct total_pct self_ms
|
|
176
|
+
|
|
177
|
+
`rperf diff --format table BASE HEAD` columns (|delta_pt| descending,
|
|
178
|
+
top 50; delta_pt = self_pct_head - self_pct_base in percentage points):
|
|
179
|
+
|
|
180
|
+
method self_pct_base self_pct_head delta_pt
|
|
181
|
+
|
|
182
|
+
Per-method allocation data does not exist in sampling profiles, so
|
|
183
|
+
allocation counts appear only in the summary (whole-profile delta).
|
|
184
|
+
|
|
185
|
+
The last TSV line is `# summary` with tab-separated key=value pairs
|
|
186
|
+
(total_ms / allocated_objects / GC counts; base/head/delta for diff).
|
|
187
|
+
With `table-json`, the output is a JSON array of row objects whose last
|
|
188
|
+
element is `{"summary": {...}}`.
|
|
189
|
+
|
|
190
|
+
Feed the result to an LLM:
|
|
191
|
+
|
|
192
|
+
rperf diff --format table base.json.gz head.json.gz | claude -p "回帰の原因を分析して"
|
|
193
|
+
|
|
99
194
|
### Multi-process profiling
|
|
100
195
|
|
|
101
196
|
By default, rperf profiles forked and spawned Ruby child processes.
|
|
@@ -146,6 +241,8 @@ Limitations:
|
|
|
146
241
|
rperf record -o profile.collapsed ruby app.rb
|
|
147
242
|
rperf record -o profile.txt ruby app.rb
|
|
148
243
|
rperf record -p ruby app.rb
|
|
244
|
+
rperf record --snapshot-dir ./profiles ruby app.rb
|
|
245
|
+
rperf record --label ci=github-actions --label pr=123 ruby app.rb
|
|
149
246
|
rperf stat ruby app.rb
|
|
150
247
|
rperf stat --report ruby app.rb
|
|
151
248
|
rperf stat -o profile.pb.gz ruby app.rb
|
|
@@ -155,8 +252,11 @@ Limitations:
|
|
|
155
252
|
rperf exec -m cpu ruby app.rb
|
|
156
253
|
rperf report
|
|
157
254
|
rperf report --top profile.pb.gz
|
|
255
|
+
rperf report --format table profile.json.gz
|
|
256
|
+
rperf report ./profiles/
|
|
158
257
|
rperf diff before.pb.gz after.pb.gz
|
|
159
258
|
rperf diff --top before.pb.gz after.pb.gz
|
|
259
|
+
rperf diff --format table before.json.gz after.json.gz
|
|
160
260
|
|
|
161
261
|
## RUBY API
|
|
162
262
|
|
|
@@ -206,19 +306,24 @@ nil if profiler was not running; otherwise a Hash:
|
|
|
206
306
|
detected_thread_count: 4, # threads seen during profiling
|
|
207
307
|
start_time_ns: 17740..., # CLOCK_REALTIME epoch nanos
|
|
208
308
|
duration_ns: 10000000, # profiling duration in nanos
|
|
209
|
-
aggregated_samples: [ #
|
|
309
|
+
aggregated_samples: [ # always present
|
|
210
310
|
[frames, weight, seq, label_set_id], # frames: [[path, label], ...] deepest-first
|
|
211
311
|
... # weight: Integer (nanoseconds, merged per unique stack)
|
|
212
312
|
], # seq: Integer (thread sequence, 1-based)
|
|
213
313
|
# label_set_id: Integer (0 = no labels)
|
|
214
314
|
label_sets: [{}, {request: "abc"}, ...], # label set table (index = label_set_id)
|
|
215
|
-
#
|
|
216
|
-
raw_samples: [
|
|
217
|
-
[frames, weight, seq, label_set_id],
|
|
218
|
-
...
|
|
219
|
-
] }
|
|
315
|
+
# additionally, when aggregate: false:
|
|
316
|
+
raw_samples: [ # one entry per timer sample (not merged)
|
|
317
|
+
[frames, weight, seq, label_set_id, vm_state],
|
|
318
|
+
... # vm_state: Integer (raw VM state; NOT
|
|
319
|
+
] } # converted to %GVL/%GC labels — only
|
|
320
|
+
# aggregated_samples gets that conversion)
|
|
220
321
|
```
|
|
221
322
|
|
|
323
|
+
With `aggregate: false`, BOTH keys are present: `aggregated_samples` is built
|
|
324
|
+
in Ruby from the raw samples (so encoders always work), and `raw_samples`
|
|
325
|
+
preserves the unmerged per-sample data.
|
|
326
|
+
|
|
222
327
|
### Rperf.snapshot(clear: false)
|
|
223
328
|
|
|
224
329
|
Returns a snapshot of the current profiling data without stopping.
|
|
@@ -325,6 +430,10 @@ running). Raises `RuntimeError` if not started, `ArgumentError` without block.
|
|
|
325
430
|
|
|
326
431
|
Returns the current thread's labels as a Hash. Empty hash if none set.
|
|
327
432
|
|
|
433
|
+
### Rperf.running?
|
|
434
|
+
|
|
435
|
+
Returns true while a profiling session is active (between start and stop).
|
|
436
|
+
|
|
328
437
|
### Rperf.load(path)
|
|
329
438
|
|
|
330
439
|
Loads a `.json.gz` or `.json` profile file (saved by `rperf record` or `Rperf.save`)
|
|
@@ -356,6 +465,8 @@ use Rperf::RackMiddleware
|
|
|
356
465
|
|
|
357
466
|
The middleware uses `Rperf.profile` to activate timer and set labels.
|
|
358
467
|
Start profiling separately. Option: `label_key:` (default: `:endpoint`).
|
|
468
|
+
When the profiler is not running, the middleware is a no-op (passes the
|
|
469
|
+
request straight through).
|
|
359
470
|
|
|
360
471
|
### Rperf::ActiveJobMiddleware
|
|
361
472
|
|
|
@@ -399,7 +510,19 @@ use Rperf::Viewer, max_snapshots: 12 # keep fewer snapshots (default: 24)
|
|
|
399
510
|
```
|
|
400
511
|
|
|
401
512
|
Take snapshots via `Rperf::Viewer.instance.take_snapshot!` or
|
|
402
|
-
`Rperf::Viewer.instance.add_snapshot(data)`.
|
|
513
|
+
`Rperf::Viewer.instance.add_snapshot(data)`. Snapshots carry the same
|
|
514
|
+
meta/summary as saved profiles, so when more than one snapshot exists the
|
|
515
|
+
UI shows the time-travel sidebar (list, diff, pin/sparkline, j/k) — see
|
|
516
|
+
"Time-travel mode" under the report subcommand.
|
|
517
|
+
`add_snapshot_dir(dir)` loads a directory of saved profiles (lazy-loaded;
|
|
518
|
+
`max_snapshots` does not apply to directory entries).
|
|
519
|
+
|
|
520
|
+
The UI fetches data from `<path>/snapshots` (list) and
|
|
521
|
+
`<path>/snapshots/<id>` (body). The URLs are replaceable at runtime:
|
|
522
|
+
define `window.RPERF_DATA_SOURCE` (with `listUrl()` / `snapshotUrl(id)`,
|
|
523
|
+
and optionally an async `onAuthError(url)` hook that returns a fresh URL
|
|
524
|
+
when a fetch hits HTTP 403, e.g. an expired signed URL) before the viewer
|
|
525
|
+
script runs to read snapshots from another source.
|
|
403
526
|
|
|
404
527
|
#### Typical setup with RackMiddleware and periodic snapshots
|
|
405
528
|
|
|
@@ -445,7 +568,8 @@ end
|
|
|
445
568
|
|
|
446
569
|
#### UI tabs
|
|
447
570
|
|
|
448
|
-
- **Flamegraph** — Interactive flamegraph (d3-flame-graph). Click to zoom
|
|
571
|
+
- **Flamegraph** — Interactive flamegraph (d3-flame-graph). Click to zoom;
|
|
572
|
+
Shift+click to pin a method (sparkline across snapshots in the sidebar).
|
|
449
573
|
- **Top** — Flat/cumulative weight table. Click column headers to sort.
|
|
450
574
|
- **Tags** — Label key/value breakdown with weight bars. Click a row to
|
|
451
575
|
set tagfocus and switch to Flamegraph.
|
|
@@ -485,6 +609,71 @@ Extension convention: `.json.gz` (gzip-compressed, default) or `.json` (plain te
|
|
|
485
609
|
View with: `rperf report` (opens rperf viewer in browser, no Go required).
|
|
486
610
|
Load programmatically: `data = Rperf.load("rperf.json.gz")`
|
|
487
611
|
|
|
612
|
+
#### Profile metadata (meta / summary)
|
|
613
|
+
|
|
614
|
+
JSON profiles embed two extra top-level keys, written FIRST in the file so
|
|
615
|
+
tools can list profiles by decompressing only the head (`Rperf.read_meta`):
|
|
616
|
+
|
|
617
|
+
```json
|
|
618
|
+
{
|
|
619
|
+
"meta": {
|
|
620
|
+
"format_version": 1,
|
|
621
|
+
"created_at": "2026-06-12T10:00:00Z",
|
|
622
|
+
"ruby_version": "3.5.0",
|
|
623
|
+
"rperf_version": "0.10.0",
|
|
624
|
+
"mode": "cpu",
|
|
625
|
+
"hostname": "...",
|
|
626
|
+
"git": {
|
|
627
|
+
"sha": "88e1a40...",
|
|
628
|
+
"branch": "main",
|
|
629
|
+
"subject": "Add nested includes support",
|
|
630
|
+
"committed_at": "2026-06-09T...",
|
|
631
|
+
"dirty": false
|
|
632
|
+
},
|
|
633
|
+
"labels": { "ci": "github-actions", "pr": "123" }
|
|
634
|
+
},
|
|
635
|
+
"summary": {
|
|
636
|
+
"total_ms": 2001.8,
|
|
637
|
+
"cpu_ms": 2023.3,
|
|
638
|
+
"gc_count_minor": 2,
|
|
639
|
+
"gc_count_major": 2,
|
|
640
|
+
"gc_ms": 3.0,
|
|
641
|
+
"allocated_objects": 48741,
|
|
642
|
+
"freed_objects": 27034,
|
|
643
|
+
"maxrss_mb": 16,
|
|
644
|
+
"samples": 1999,
|
|
645
|
+
"top_methods": [
|
|
646
|
+
{ "name": "Object#fibonacci", "self_pct": 99.9, "total_pct": 99.9 }
|
|
647
|
+
]
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Notes:
|
|
653
|
+
|
|
654
|
+
- `git` is omitted outside a git repository (never an error). In GitHub
|
|
655
|
+
Actions, `GITHUB_SHA` / `GITHUB_REF_NAME` take priority over git commands.
|
|
656
|
+
The CLI collects git info before launching the profiled command, so a
|
|
657
|
+
`chdir` in the app cannot point git at the wrong repository.
|
|
658
|
+
- `git.dirty` is true when the working tree has uncommitted changes.
|
|
659
|
+
- GC counts and allocation counts are deltas over the profiled period
|
|
660
|
+
(`GC.stat` baselines are captured at `Rperf.start`). `maxrss_mb` is the
|
|
661
|
+
process-lifetime peak (no period delta is possible; on macOS the value
|
|
662
|
+
comes from `ps -o rss=` and is the current RSS, not the peak).
|
|
663
|
+
- `summary.top_methods` lists up to 50 methods by self time.
|
|
664
|
+
- In multi-process profiling, GC/memory stats come from the root process
|
|
665
|
+
only (same policy as `rperf stat`); time and sample stats are aggregated.
|
|
666
|
+
- Files saved by older rperf versions (no `meta`) remain loadable; viewers
|
|
667
|
+
treat them as unknown snapshots.
|
|
668
|
+
- pprof / collapsed / text exports do not contain meta.
|
|
669
|
+
|
|
670
|
+
### Rperf.read_meta(path)
|
|
671
|
+
|
|
672
|
+
Reads only `meta` / `summary` from a `.json(.gz)` profile without parsing
|
|
673
|
+
the sample body (fast even for large files). Returns
|
|
674
|
+
`{ meta: Hash|nil, summary: Hash|nil }`, or nil for pre-meta files and
|
|
675
|
+
unreadable files.
|
|
676
|
+
|
|
488
677
|
### pprof
|
|
489
678
|
|
|
490
679
|
Gzip-compressed protobuf. Standard pprof format.
|
|
@@ -654,7 +843,7 @@ Or convert to text with pprof CLI:
|
|
|
654
843
|
|
|
655
844
|
go tool pprof -text profile.pb.gz
|
|
656
845
|
go tool pprof -top profile.pb.gz
|
|
657
|
-
go tool pprof -
|
|
846
|
+
go tool pprof -http=:8080 profile.pb.gz # web UI (includes flame graph view)
|
|
658
847
|
|
|
659
848
|
## ENVIRONMENT VARIABLES
|
|
660
849
|
|