ostia 0.1.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.
- package/README.md +439 -0
- package/cli.js +151 -0
- package/index.d.ts +259 -0
- package/index.js +19 -0
- package/package.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# ostia
|
|
2
|
+
|
|
3
|
+
ostia is a profiling and benchmarking toolkit for Bun.
|
|
4
|
+
|
|
5
|
+
Use it when you want wall-clock timings, CPU hotspots, heap summaries, or JIT tier
|
|
6
|
+
data in one place, then diff those results or fail CI when something gets slower.
|
|
7
|
+
Everything lands in one schema-versioned JSON document (`ProfileDocument`), so the
|
|
8
|
+
CLI and the library speak the same language.
|
|
9
|
+
|
|
10
|
+
Zero runtime dependencies. Requires Bun ≥ 1.4.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
bun add ostia
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
Compare two commands:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
ostia run --runs 10 --warmup 2 "bun fixtures/fast.ts" "bun fixtures/slow.ts"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Command Mean [ms] Min…Max [ms] Relative
|
|
28
|
+
--------------------------------------------------------------------
|
|
29
|
+
bun fixtures/fast.ts 8.413 ± 1.340 7.623…12.365 1.00×
|
|
30
|
+
bun fixtures/slow.ts 21.712 ± 1.157 20.831…24.220 2.62× slower
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Find a CPU hotspot (profiler runs as a separate labeled trial, never mixed into the
|
|
34
|
+
timing numbers above):
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
ostia run --runs 5 --cpu --cpu-interval 200 --export-json .ostia/doc.json fixtures/work.ts
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Command Mean [ms] Min…Max [ms]
|
|
42
|
+
----------------------------------------------------
|
|
43
|
+
bun fixtures/work.ts 275.815 ± 2.853 273.334…281.384
|
|
44
|
+
|
|
45
|
+
CPU capture - bun fixtures/work.ts (instrumented, 200µs interval, diagnostic wall 297.578ms)
|
|
46
|
+
100.0% 284.19ms self hashLoop
|
|
47
|
+
0.0% 0.00ms self (root)
|
|
48
|
+
artifact: .ostia/artifacts/<run-id>-cpu.cpuprofile
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Gate a change against a local baseline (baselines are gitignored under `.ostia/`):
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
bun run baseline # on known-good: measure ostia.config.json -> .ostia/baselines/main.json
|
|
55
|
+
ostia ci # on your branch: rerun changed workloads, exit 1 on regression
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
2 workloads
|
|
60
|
+
0 affected by this change
|
|
61
|
+
2 cached
|
|
62
|
+
0 executed
|
|
63
|
+
2 passed 0 regressed
|
|
64
|
+
|
|
65
|
+
Profile CI: ✓
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## What ostia is for
|
|
69
|
+
|
|
70
|
+
- Time subprocesses or in-process functions without a profiler attached to the timing runs.
|
|
71
|
+
- Capture CPU (`--cpu`), heap (`--heap`), or JSC JIT tiers (`profile(..., { origin: "jsc" })`)
|
|
72
|
+
as separate evidence on the same document.
|
|
73
|
+
- Diff two documents (`ostia compare`) or fail CI (`ostia ci`) with exit codes `0` / `1` / `2`.
|
|
74
|
+
- Emit files other tools already understand: collapsed stacks, Mermaid, speedscope JSON,
|
|
75
|
+
raw `.cpuprofile`.
|
|
76
|
+
|
|
77
|
+
## CLI reference
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
ostia run <command...> time commands; optional --cpu / --heap capture
|
|
81
|
+
ostia bench <suite.ts...> in-process group()/task() suites (time-budgeted)
|
|
82
|
+
ostia compare <a> <b> diff two ProfileDocuments
|
|
83
|
+
ostia report <document.json> render a saved document
|
|
84
|
+
ostia viz <document.json> render CPU evidence to a file format
|
|
85
|
+
ostia ci run configured workloads vs a baseline, gate regressions
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Every subcommand takes `--help` for its full flag list.
|
|
89
|
+
|
|
90
|
+
### `ostia run`
|
|
91
|
+
|
|
92
|
+
Clean wall-clock timing by default. `--cpu` / `--heap` schedule one extra instrumented
|
|
93
|
+
trial each, labeled separately in the document.
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
ostia run "bun a.ts" "bun b.ts"
|
|
97
|
+
ostia run --runs 25 --warmup 3 --cpu --heap "bun src/server.ts"
|
|
98
|
+
ostia run --format json --export-json out.json "bun a.ts"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Timing table (two commands get a Relative column automatically):
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Command Mean [ms] Min…Max [ms] Relative
|
|
105
|
+
--------------------------------------------------------------------
|
|
106
|
+
bun fixtures/fast.ts 8.413 ± 1.340 7.623…12.365 1.00×
|
|
107
|
+
bun fixtures/slow.ts 21.712 ± 1.157 20.831…24.220 2.62× slower
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Heap summary (type counts from the snapshot trial):
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
Command Mean [ms] Min…Max [ms]
|
|
114
|
+
--------------------------------------------------------
|
|
115
|
+
bun fixtures/allocate.ts 27.079 ± 6.844 23.888…91.799
|
|
116
|
+
|
|
117
|
+
Heap snapshot - bun fixtures/allocate.ts (instrumented, 2518 objects, 0.12MB)
|
|
118
|
+
1369 string
|
|
119
|
+
426 code
|
|
120
|
+
321 closure
|
|
121
|
+
216 object shape
|
|
122
|
+
104 hidden
|
|
123
|
+
artifact: .ostia/artifacts/<run-id>-heap.heapsnapshot
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `ostia bench`
|
|
127
|
+
|
|
128
|
+
In-process microbenchmarks registered with `group()` / `task()`. Time budget + min
|
|
129
|
+
samples (mitata-shaped); batches when a call is too fast for `Bun.nanoseconds()`.
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
ostia bench bench/*.ts
|
|
133
|
+
ostia bench --time-budget 500 --min-samples 50 bench/stats.ts
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Command Mean [ms] Min…Max [ms] Relative
|
|
138
|
+
--------------------------------------------------------------------------------------
|
|
139
|
+
stats/computeTimingStats (1e3 samples) 0.014 ± 0.016 0.012…0.598 1.00×
|
|
140
|
+
stats/computeTimingStats (1e4 samples) 0.484 ± 0.052 0.434…0.680 38.22× slower
|
|
141
|
+
stats/timingWarnings (1e3 samples) 0.036 ± 0.020 0.032…0.541 2.82× slower
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `ostia compare`
|
|
145
|
+
|
|
146
|
+
Match workloads by id, rank timing / frame / heap deltas, print a verdict per workload.
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
ostia compare before.json after.json
|
|
150
|
+
ostia compare after.json --baseline .ostia/baselines/main.json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
✓ bun fixtures/fast.ts
|
|
155
|
+
timing: -2.9% median (unchanged)
|
|
156
|
+
|
|
157
|
+
✗ work
|
|
158
|
+
timing: +1249.2% median (regressed)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### `ostia report`
|
|
162
|
+
|
|
163
|
+
Render a saved `ProfileDocument` without re-running anything.
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
ostia report out.json # table (default)
|
|
167
|
+
ostia report out.json --format markdown
|
|
168
|
+
ostia report out.json --format json
|
|
169
|
+
ostia report out.json --format jsonl
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Markdown:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
# Profile Report
|
|
176
|
+
|
|
177
|
+
Bun 1.4.0 · ostia 0.1.0 · darwin/arm64 · 2026-09-04T03:46:02.961Z
|
|
178
|
+
|
|
179
|
+
## Timing
|
|
180
|
+
|
|
181
|
+
| Command | Mean ± SD (ms) | Min…Max (ms) | Median (ms) |
|
|
182
|
+
|---|---|---|---|
|
|
183
|
+
| bun -e 1 | 5.477 ± 0.303 | 5.153…5.882 | 5.396 |
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### `ostia viz`
|
|
187
|
+
|
|
188
|
+
Turn CPU evidence into files for other tools. Formats: `collapsed`, `mermaid`,
|
|
189
|
+
`speedscope`, `cpuprofile` (pass-through of a real CDP artifact when present).
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
ostia viz .ostia/doc.json --format collapsed
|
|
193
|
+
ostia viz .ostia/doc.json --format mermaid
|
|
194
|
+
ostia viz .ostia/doc.json --format speedscope > flame.json
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Collapsed stacks (one line per stack; feeds `flamegraph.pl` and friends):
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
(root);(module);hashLoop 1055
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Mermaid call tree (top N nodes by self time, never the whole profile):
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
graph TD
|
|
207
|
+
n1["(root) (self 0.00ms, total 284.19ms)"]
|
|
208
|
+
n2["(module) (self 0.00ms, total 284.19ms)"]
|
|
209
|
+
n3["hashLoop (self 284.19ms, total 284.19ms)"]
|
|
210
|
+
n1 --> n2
|
|
211
|
+
n2 --> n3
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `ostia ci`
|
|
215
|
+
|
|
216
|
+
Reads `ostia.config.json`, fingerprints each workload, reruns only what changed, compares
|
|
217
|
+
against a named baseline, exits `1` on regression.
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
ostia ci
|
|
221
|
+
ostia ci --full # ignore cache
|
|
222
|
+
ostia ci --baseline main
|
|
223
|
+
ostia ci --export-json out.json
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Pass:
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
1 workloads
|
|
230
|
+
1 affected by this change
|
|
231
|
+
0 cached
|
|
232
|
+
1 executed
|
|
233
|
+
1 passed 0 regressed
|
|
234
|
+
|
|
235
|
+
Profile CI: ✓
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Fail:
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
1 workloads
|
|
242
|
+
1 affected by this change
|
|
243
|
+
0 cached
|
|
244
|
+
1 executed
|
|
245
|
+
0 passed 1 regressed (+1249.2% median on work)
|
|
246
|
+
|
|
247
|
+
Profile CI: ✗
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Exit codes: `0` pass, `1` regression, `2` harness error (missing config/baseline, spawn failure).
|
|
251
|
+
|
|
252
|
+
#### `ostia.config.json`
|
|
253
|
+
|
|
254
|
+
```json
|
|
255
|
+
{
|
|
256
|
+
"baseline": "main",
|
|
257
|
+
"thresholds": { "timingPct": 5 },
|
|
258
|
+
"workloads": [
|
|
259
|
+
{
|
|
260
|
+
"label": "parse",
|
|
261
|
+
"command": ["bun", "bench/parse.ts"],
|
|
262
|
+
"inputs": ["src/**/*.ts"]
|
|
263
|
+
}
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`inputs` is optional. Workloads with no `inputs` always rerun (cache fails conservative).
|
|
269
|
+
|
|
270
|
+
#### Baselines (local and CI)
|
|
271
|
+
|
|
272
|
+
Baselines are JSON under `.ostia/baselines/` (gitignored). `ostia ci` only needs the file
|
|
273
|
+
on disk; it does not need to be committed.
|
|
274
|
+
|
|
275
|
+
Local branch workflow:
|
|
276
|
+
|
|
277
|
+
```sh
|
|
278
|
+
git checkout master # known-good tip
|
|
279
|
+
bun run baseline # -> .ostia/baselines/main.json
|
|
280
|
+
|
|
281
|
+
git checkout -b my-opt
|
|
282
|
+
# ... change code ...
|
|
283
|
+
ostia ci # or: bun run dogfood
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The baseline survives branch switches because it is not tracked. Re-seed only when you
|
|
287
|
+
intentionally accept a new floor. Seeding on the branch you are guarding compares that
|
|
288
|
+
branch to itself.
|
|
289
|
+
|
|
290
|
+
One-off outside this repo's config:
|
|
291
|
+
|
|
292
|
+
```sh
|
|
293
|
+
ostia run --export-json .ostia/baselines/main.json "bun bench.ts"
|
|
294
|
+
ostia ci
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
On pull requests, CI measures the base branch into that same path, checks out the PR,
|
|
298
|
+
and runs `ostia ci` against it. If the base has no `package.json` / `ostia.config.json`
|
|
299
|
+
yet (empty starter commit), CI seeds from the PR tip instead so dogfood still runs.
|
|
300
|
+
|
|
301
|
+
## Library API
|
|
302
|
+
|
|
303
|
+
The CLI is a thin wrapper around the library. Same `ProfileDocument` either way.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import {
|
|
307
|
+
run,
|
|
308
|
+
profile,
|
|
309
|
+
bench,
|
|
310
|
+
group,
|
|
311
|
+
task,
|
|
312
|
+
compareDocuments,
|
|
313
|
+
renderers,
|
|
314
|
+
saveDocument,
|
|
315
|
+
loadDocument,
|
|
316
|
+
} from "ostia"
|
|
317
|
+
import type { ProfileDocument } from "ostia"
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### `run(opts)` → `ProfileDocument`
|
|
321
|
+
|
|
322
|
+
Subprocess timing, optional CPU/heap capture. Same behavior as `ostia run`.
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
const doc = await run({
|
|
326
|
+
commands: ["bun a.ts", "bun b.ts"],
|
|
327
|
+
runs: 10,
|
|
328
|
+
warmup: 2,
|
|
329
|
+
cpu: true,
|
|
330
|
+
heap: false,
|
|
331
|
+
cpuIntervalUs: 200,
|
|
332
|
+
outDir: ".ostia",
|
|
333
|
+
})
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### `profile(fn, opts)` → `{ result, run }`
|
|
337
|
+
|
|
338
|
+
In-process capture. `origin: "jsc"` is the only path that reports JIT tiers
|
|
339
|
+
(LLInt / Baseline / DFG / FTL). Default `origin: "inspector"` writes portable CDP-shaped
|
|
340
|
+
evidence instead.
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
const { result, run } = await profile(() => hashLoop(8_000_000), {
|
|
344
|
+
origin: "jsc",
|
|
345
|
+
intervalUs: 100,
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
console.log(run.jit?.tiers)
|
|
349
|
+
// {
|
|
350
|
+
// llint: 0,
|
|
351
|
+
// baseline: 9,
|
|
352
|
+
// dfg: 37,
|
|
353
|
+
// ftl: 2825,
|
|
354
|
+
// }
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### `group` / `task` / `bench`
|
|
358
|
+
|
|
359
|
+
Register in-process suites, then run them (same as `ostia bench`):
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
// suite.ts
|
|
363
|
+
import { group, task } from "ostia"
|
|
364
|
+
|
|
365
|
+
group("parse", () => {
|
|
366
|
+
task("small input", () => parse(smallBuf))
|
|
367
|
+
task("large input", () => parse(largeBuf))
|
|
368
|
+
})
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
// demo.ts
|
|
373
|
+
import { bench } from "ostia"
|
|
374
|
+
|
|
375
|
+
const doc = await bench({
|
|
376
|
+
suites: ["suite.ts"],
|
|
377
|
+
timeBudgetMs: 500,
|
|
378
|
+
minSamples: 50,
|
|
379
|
+
})
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### `compareDocuments(base, cand, thresholds?)` → `Comparison[]`
|
|
383
|
+
|
|
384
|
+
Same matching and thresholds as `ostia compare` / `ostia ci`.
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
const diffs = compareDocuments(baselineDoc, candidateDoc, {
|
|
388
|
+
timingPct: 5,
|
|
389
|
+
frameSelfPct: 10,
|
|
390
|
+
heapTypePct: 10,
|
|
391
|
+
minFrameSelfUs: 1000,
|
|
392
|
+
})
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### `saveDocument` / `loadDocument`
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
await saveDocument(doc, ".ostia/doc.json")
|
|
399
|
+
const loaded: ProfileDocument = await loadDocument(".ostia/doc.json")
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### `renderers`
|
|
403
|
+
|
|
404
|
+
Pure functions of a `ProfileDocument`. Each returns `{ text? }` and/or `{ files? }`.
|
|
405
|
+
|
|
406
|
+
| Name | Output |
|
|
407
|
+
|---|---|
|
|
408
|
+
| `table` | terminal timing / CPU / heap / comparison text |
|
|
409
|
+
| `markdown` | agent- and human-readable report |
|
|
410
|
+
| `json` | pretty JSON document |
|
|
411
|
+
| `jsonl` | one metadata line, then one line per run |
|
|
412
|
+
| `collapsed` | folded stacks (`name;name;name count`) |
|
|
413
|
+
| `mermaid` | top-N call tree |
|
|
414
|
+
| `speedscope` | speedscope.app JSON |
|
|
415
|
+
| `cpuprofile` | verbatim `.cpuprofile` when a CDP artifact exists |
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
const { text } = await renderers.markdown.render(doc)
|
|
419
|
+
const { files } = await renderers.collapsed.render(doc, { runId: someCpuRunId })
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Units in the IR are fixed: ns (time), bytes (memory), µs (sampling interval).
|
|
423
|
+
|
|
424
|
+
## Examples
|
|
425
|
+
|
|
426
|
+
[`examples/`](examples/) has six runnable recipes (they spawn `../../src/cli/main.ts`
|
|
427
|
+
or import `../../src` directly; no install step):
|
|
428
|
+
|
|
429
|
+
- [`compare-two-commands`](examples/compare-two-commands/). Relative timing table.
|
|
430
|
+
- [`find-a-hotspot`](examples/find-a-hotspot/). `--cpu` plus collapsed / Mermaid viz.
|
|
431
|
+
- [`heap-usage`](examples/heap-usage/). `--heap` type breakdown.
|
|
432
|
+
- [`gate-a-regression`](examples/gate-a-regression/). Config, local baseline, and `ostia ci`.
|
|
433
|
+
- [`profile-in-process`](examples/profile-in-process/). `profile(fn, { origin: "jsc" })`.
|
|
434
|
+
- [`benchmark-a-function`](examples/benchmark-a-function/). `bench()` / `group()` / `task()`.
|
|
435
|
+
|
|
436
|
+
```sh
|
|
437
|
+
cd examples/find-a-hotspot && bun run demo
|
|
438
|
+
bun run examples # all of them from the repo root
|
|
439
|
+
```
|
package/cli.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
function q(e){return JSON.stringify(W(e))}function W(e){if(Array.isArray(e))return e.map(W);if(e!==null&&typeof e==="object"){let n={};for(let t of Object.keys(e).sort())n[t]=W(e[t]);return n}return e}function T(e,...n){let t=Bun.CryptoHasher.hash("sha256",q(n),"hex");return`${e}_${t.slice(0,16)}`}function Ye(e){return e.startsWith("file://")?e.slice(7):e}function K(e,n,t){let s=e.nodes,r=s.length,o=new Map,i=[],a=new Map,c=new Int32Array(r);for(let m=0;m<r;m++){let g=s[m],f=g.callFrame,y=Ye(f.url),C=o.get(f.functionName);if(C===void 0)C=new Map,o.set(f.functionName,C);let N=C.get(y);if(N===void 0)N=i.length,C.set(y,N),i.push({key:T("fr",f.functionName,y),name:f.functionName,url:y||void 0,line:f.lineNumber>=0?f.lineNumber:void 0,col:f.columnNumber>=0?f.columnNumber:void 0});c[m]=N,a.set(g.id,m)}let l=Array(r);for(let m=0;m<r;m++){let g=s[m];l[m]={id:g.id,frameIx:c[a.get(g.id)],children:g.children??[]}}let u=new Float64Array(r),p=new Float64Array(r),d=e.samples,b=e.timeDeltas;for(let m=0;m<d.length;m++){let g=a.get(d[m]);if(g===void 0)continue;u[g]+=b[m]??0,p[g]+=1}let x=new Int32Array(r).fill(-1);for(let m=0;m<r;m++){let g=s[m].children;if(!g)continue;for(let f of g){let y=a.get(f);if(y!==void 0)x[y]=m}}let w=[],R=[];for(let m=r-1;m>=0;m--)if(x[m]===-1)R.push(m);while(R.length>0){let m=R.pop();w.push(m);let g=s[m].children;if(!g)continue;for(let f of g){let y=a.get(f);if(y!==void 0&&x[y]===m)R.push(y)}}let k=new Float64Array(r);for(let m=w.length-1;m>=0;m--){let g=w[m];k[g]+=u[g];let f=x[g];if(f>=0)k[f]+=k[g]}let P=Array(i.length),h=[];for(let m=0;m<r;m++){let g=l[m].frameIx,f=P[g];if(f)f.selfUs+=u[m],f.totalUs+=k[m],f.samples+=p[m];else{let y={frameIx:g,selfUs:u[m],totalUs:k[m],samples:p[m]};P[g]=y,h.push(y)}}return{origin:n,samplingIntervalUs:t,frames:i,nodes:l,totals:h.sort((m,g)=>g.selfUs-m.selfUs),samples:{nodeIds:e.samples,timeDeltasUs:e.timeDeltas}}}function Qe(e,n,t,s){let r=["--cpu-prof","--cpu-prof-dir",n,"--cpu-prof-name",t,"--cpu-prof-interval",String(s)],o=e[0];if(o==="bun"||o?.endsWith("/bun"))return[o,...r,...e.slice(1)];return e}async function pe(e){let n=`${e.artifactDir}/${e.fileName}`,t=e.argv[0],s=t==="bun"||t?.endsWith("/bun"),r=Qe(e.argv,e.artifactDir,e.fileName,e.intervalUs),o=s?e.env:{...process.env,...e.env,BUN_OPTIONS:`--cpu-prof --cpu-prof-dir ${e.artifactDir} --cpu-prof-name ${e.fileName} --cpu-prof-interval ${e.intervalUs}`},i=Bun.nanoseconds(),c=await Bun.spawn(r,{cwd:e.cwd,env:o,stdout:"ignore",stderr:"ignore",stdin:"ignore"}).exited,l=Bun.nanoseconds()-i,u=Bun.file(n);if(!await u.exists())return{diagnosticWallNs:l,exitCode:c,warnings:[{code:"artifact-missing",message:`Expected a .cpuprofile at ${n} after exit ${c}, found nothing. The workload's argv[0] must be a \`bun\` binary for CPU capture.`,data:{artifactPath:n,argv:e.argv}}]};let p=await u.json(),d=K(p,"cpu-prof",e.intervalUs),b=p.samples.length===0?[{code:"empty-profile",message:"CPU capture produced zero samples."}]:[];return{diagnosticWallNs:l,exitCode:c,artifactPath:n,cpu:d,warnings:b}}function me(e,n="heap-prof"){let{node_fields:t,node_types:s}=e.snapshot.meta,r=t.indexOf("type"),o=t.indexOf("self_size"),i=t.length,a=s[0];if(r===-1||o===-1||!Array.isArray(a))return{origin:n,typeCounts:[],objectCount:e.snapshot.node_count};let c=a.length,l=Array(c),u=new Map,p=[],d=0,b=e.nodes,x=b.length;for(let h=0;h<x;h+=i){let m=b[h+r],g=b[h+o]??0;d+=g;let f;if(m>=0&&m<c){if(f=l[m],f===void 0)f={type:a[m],count:0,bytes:0},l[m]=f,p.push(f)}else{let y=`unknown(${m})`;if(f=u.get(y),f===void 0)f={type:y,count:0,bytes:0},u.set(y,f),p.push(f)}f.count++,f.bytes+=g}let w=p.sort((h,m)=>m.count-h.count),R=w.slice(0,20),k=w.slice(20),P=R.map(({type:h,count:m,bytes:g})=>({type:h,count:m,retainedBytes:g}));if(k.length>0){let h=0,m=0;for(let g of k)h+=g.count,m+=g.bytes;P.push({type:"other",count:h,retainedBytes:m})}return{origin:n,heapSizeBytes:d,objectCount:e.snapshot.node_count,typeCounts:P}}function Xe(e,n,t){let s=["--heap-prof","--heap-prof-dir",n,"--heap-prof-name",t],r=e[0];if(r==="bun"||r?.endsWith("/bun"))return[r,...s,...e.slice(1)];return e}async function de(e){let n=`${e.artifactDir}/${e.fileName}`,t=e.argv[0],s=t==="bun"||t?.endsWith("/bun"),r=Xe(e.argv,e.artifactDir,e.fileName),o=s?e.env:{...process.env,...e.env,BUN_OPTIONS:`--heap-prof --heap-prof-dir ${e.artifactDir} --heap-prof-name ${e.fileName}`},i=Bun.nanoseconds(),c=await Bun.spawn(r,{cwd:e.cwd,env:o,stdout:"ignore",stderr:"ignore",stdin:"ignore"}).exited,l=Bun.nanoseconds()-i,u=Bun.file(n);if(!await u.exists())return{diagnosticWallNs:l,exitCode:c,warnings:[{code:"artifact-missing",message:`Expected a heap snapshot at ${n} after exit ${c}, found nothing. The workload's argv[0] must be a \`bun\` binary for heap capture.`,data:{artifactPath:n,argv:e.argv}}]};let p=await u.json(),d=me(p,"heap-prof");return{diagnosticWallNs:l,exitCode:c,artifactPath:n,heap:d,warnings:[]}}import{Session as en}from"inspector/promises";var nn=1000;async function fe(e,n={}){let t=n.intervalUs??nn,s=new en;s.connect();let r=Bun.nanoseconds();try{await s.post("Profiler.enable"),await s.post("Profiler.setSamplingInterval",{interval:t}),await s.post("Profiler.start");let o=await e(),{profile:i}=await s.post("Profiler.stop"),a=Bun.nanoseconds()-r,c=K(i,"inspector",t);return{result:o,cpu:c,diagnosticWallNs:a}}finally{s.disconnect()}}import{profile as rn}from"bun:jsc";var tn=new Map([["LLInt","llint"],["Baseline","baseline"],["DFG","dfg"],["FTL","ftl"]]),ge=4294967295;function he(e,n){let t=n??e.interval*1e6,s=new Map,r=[];function o(f,y,C,N){let D=s.get(f);if(D===void 0)D=new Map,s.set(f,D);let F=y??"",U=D.get(F);if(U===void 0)U=r.length,D.set(F,U),r.push({key:T("fr",f,F),name:f,url:y,line:C,col:N});return U}function i(f){let y=f.line===ge,C=y?void 0:f.line-1,N=y||f.column===ge?void 0:f.column-1;return o(f.name,f.sourceURL,C,N)}let a=o("(root)",void 0,void 0,void 0),c=1,l={id:0,frameIx:a,children:new Map,selfUs:0,samples:0,totalUs:0},u=new Map([[0,l]]),p={llint:0,baseline:0,dfg:0,ftl:0},d=new Map,b=[],x=[];for(let f of e.traces){let y=f.frames,C=l;for(let F=y.length-1;F>=0;F--){let U=i(y[F]),A=C.children.get(U);if(!A)A={id:c++,frameIx:U,children:new Map,selfUs:0,samples:0,totalUs:0},C.children.set(U,A),u.set(A.id,A);C=A}C.selfUs+=t,C.samples+=1,b.push(C.id),x.push(t);let N=y[0],D=N&&tn.get(N.category);if(D){p[D]++;let F=d.get(D)??new Map;F.set(C.frameIx,(F.get(C.frameIx)??0)+1),d.set(D,F)}}function w(f){let y=f.selfUs;for(let C of f.children.values())y+=w(C);return f.totalUs=y,y}w(l);let R=new Map;function k(f){let y=R.get(f.frameIx);if(y)y.selfUs+=f.selfUs,y.totalUs+=f.totalUs,y.samples+=f.samples;else R.set(f.frameIx,{frameIx:f.frameIx,selfUs:f.selfUs,totalUs:f.totalUs,samples:f.samples});for(let C of f.children.values())k(C)}k(l);let P=[...u.values()].map((f)=>({id:f.id,frameIx:f.frameIx,children:[...f.children.values()].map((y)=>y.id)})),h={origin:"jsc-profile",samplingIntervalUs:t,frames:r,nodes:P,totals:[...R.values()].sort((f,y)=>y.selfUs-f.selfUs),samples:{nodeIds:b,timeDeltasUs:x}},m=[...d.entries()].flatMap(([f,y])=>[...y.entries()].sort((C,N)=>N[1]-C[1]).slice(0,3).map(([C,N])=>({tier:f,frameKey:r[C].key,samples:N})));return{cpu:h,jit:{origin:"jsc-profile",tiers:p,topFramesByTier:m}}}var sn=1000;async function be(e,n={}){let t=n.intervalUs??sn,s,r=Bun.nanoseconds(),o=await rn(async()=>(s=await e(),s),t),i=Bun.nanoseconds()-r,{cpu:a,jit:c}=he(o.stackTraces,t);return{result:s,cpu:a,jit:c,diagnosticWallNs:i}}var _="0.1.0";function M(e,n){return{schemaVersion:1,toolVersion:_,bunVersion:Bun.version,platform:{os:process.platform,arch:process.arch},createdAt:new Date().toISOString(),workloads:e,runs:n}}function G(e,n){return{id:T("wl","subprocess",e,process.cwd()),kind:"subprocess",command:e,label:n}}function we(e,n){return{id:T("wl","inprocess",e.name,e.toString()),kind:"inprocess",label:n}}function Z(e){return{id:T("run",e.workload.id,"timing",e.configFingerprint,Bun.version,_),workloadId:e.workload.id,phase:"timing",instrumented:!1,configFingerprint:e.configFingerprint,trials:e.trials,timing:e.timing,warnings:e.warnings,artifacts:[],memory:on(e.trials)}}function on(e){let n=e.map((t)=>t.maxRssBytes).filter((t)=>t!==void 0);if(n.length===0)return;return{origin:"resourceUsage",perTrial:e.map((t)=>({rssBytes:t.maxRssBytes})),maxRssBytes:Math.max(...n)}}function Y(e){return{id:T("run",e.workload.id,e.phase,e.configFingerprint,Bun.version,_),workloadId:e.workload.id,phase:e.phase,instrumented:!0,configFingerprint:e.configFingerprint,trials:[{i:0,wallNs:e.diagnosticWallNs,exitCode:e.exitCode}],diagnosticWallNs:e.diagnosticWallNs,cpu:e.cpu,heap:e.heap,jit:e.jit,warnings:e.warnings,artifacts:e.artifacts}}async function ye(e,n,t){let r=await Bun.file(t).arrayBuffer(),o=new Bun.CryptoHasher("sha256");return o.update(r),{id:T("art",e,n,t),kind:n,path:t,sha256:o.digest("hex"),bytes:r.byteLength}}function H(e){return T("cfg",e)}function re(e){return`${JSON.stringify(W(e),null,2)}
|
|
4
|
+
`}async function E(e,n){await Bun.write(n,re(e))}async function v(e){let n=await Bun.file(e).text();return JSON.parse(n)}async function se(e){let n=Bun.nanoseconds(),t=Bun.spawn(e.argv,{cwd:e.cwd,env:e.env,stdout:"ignore",stderr:"ignore",stdin:"ignore"}),s=await t.exited,r=Bun.nanoseconds(),o=t.resourceUsage?.();return{wallNs:r-n,exitCode:s,userNs:o?Number(o.cpuTime.user)*1000:void 0,systemNs:o?Number(o.cpuTime.system)*1000:void 0,maxRssBytes:o?.maxRSS}}function xe(e){return e.trim().split(/\s+/).filter(Boolean)}function Re(e){if(e.length===0)throw Error("computeTimingStats: samples must be non-empty");let n=e.length,t=ke(e),s=0;for(let h=0;h<n;h++)s+=e[h];let r=s/n,o=L(t,0.5),i=0;for(let h=0;h<n;h++){let m=e[h]-r;i+=m*m}let a=Math.sqrt(i/n),c=t[0],l=t[n-1],u=L(t,0.25),p=L(t,0.75),d=p-u,b=u-1.5*d,x=p+1.5*d,w=u-3*d,R=p+3*d,k=0,P=0;for(let h=0;h<n;h++){let m=e[h];if(m<w||m>R)P++;else if(m<b||m>x)k++}return{unit:"ns",samples:e,mean:r,median:o,stddev:a,min:c,max:l,outliers:{mild:k,severe:P}}}function ke(e){let n=new Float64Array(e.length);return n.set(e),n.sort(),n}function L(e,n){let t=e.length;if(t===1)return e[0];let s=n*(t-1),r=Math.floor(s),o=Math.ceil(s);if(r===o)return e[r];let i=s-r;return e[r]*(1-i)+e[o]*i}var an=5000000,cn=200;function Pe(e,n,t="subprocess"){let s=[],r=e.samples[0];if(r!==void 0){let i=ke(e.samples),a=L(i,0.25),l=L(i,0.75)-a;if(r>e.median+3*l&&l>0)s.push({code:"slow-first-run",message:`First run took ${(r/1e6).toFixed(2)}ms, much slower than the median ${(e.median/1e6).toFixed(2)}ms. Consider more warmup.`,data:{firstNs:r,medianNs:e.median}})}if(e.outliers.mild+e.outliers.severe>0)s.push({code:"outliers-detected",message:`${e.outliers.mild+e.outliers.severe} outlier(s) detected (${e.outliers.severe} severe, ${e.outliers.mild} mild).`,data:e.outliers});if(t==="subprocess"&&e.median<an)s.push({code:"fast-command",message:`Median run time (${(e.median/1e6).toFixed(3)}ms) is very fast; results may be dominated by spawn overhead.`,data:{medianNs:e.median}});if(t==="inprocess"&&e.median<cn)s.push({code:"below-timer-resolution",message:`Median run time (${e.median.toFixed(0)}ns) is close to timer resolution; consider a larger batch size or a coarser operation.`,data:{medianNs:e.median}});let o=n.filter((i)=>i!==void 0&&i!==0);if(o.length>0)s.push({code:"nonzero-exit",message:`${o.length} of ${n.length} trial(s) exited non-zero.`,data:{exitCodes:o}});return s}var un=10,ln=3000000000,pn=3;async function Q(e){let n=e.warmup??pn;for(let u=0;u<n;u++)await se(e);let t=[],s=e.runs??e.minRuns??un,r=e.runs!==void 0?0:e.minTotalNs??ln,o=0,i=0;while(i<s||o<r){let u=await se(e);if(t.push({i,wallNs:u.wallNs,exitCode:u.exitCode,userNs:u.userNs,systemNs:u.systemNs,maxRssBytes:u.maxRssBytes}),o+=u.wallNs,i++,e.runs!==void 0&&i>=e.runs)break}let a=t.map((u)=>u.wallNs),c=Re(a),l=Pe(c,t.map((u)=>u.exitCode));return{trials:t,timing:c,warnings:l}}var mn=new URL("./runner.ts",import.meta.url).pathname,dn=".ostia";async function oe(e){let t=`${e.outDir??dn}/bench-tmp`,s=e.cwd??process.cwd(),r={timeBudgetMs:e.timeBudgetMs,minSamples:e.minSamples,gc:e.gc},o=[],i=[];try{for(let a of e.suites){let c=a.startsWith("/")?a:`${s}/${a}`,l=`${t}/${T("bench-out",c)}.json`,p=await Bun.spawn(["bun",mn,c,l,JSON.stringify(r)],{cwd:s,stdout:"inherit",stderr:"inherit",stdin:"ignore"}).exited;if(p!==0)throw Error(`Bench suite failed: ${a} (runner exited ${p})`);let d=await v(l);o.push(...d.workloads),i.push(...d.runs)}}finally{await Bun.spawn(["rm","-rf",t]).exited}return M(o,i)}var fn=[],X;function gn(e,n){let t=X;X=e;try{n()}finally{X=t}}function hn(e,n){fn.push({groupName:X,name:e,fn:n})}var J={timingPct:5,frameSelfPct:10,heapTypePct:10,minFrameSelfUs:1000};function ee(e,n){if(e===0)return n===0?0:1/0;return(n-e)/e*100}function O(e,n,t){return e.runs.find((s)=>s.workloadId===n&&s.phase===t)}function ie(e,n,t=J){let s=new Set(n.workloads.map((o)=>o.id)),r=[];for(let o of e.workloads){if(!s.has(o.id))continue;let i=ae(e,n,o.id,t);if(i)r.push(i)}return r}function ae(e,n,t,s=J){let r=O(e,t,"timing"),o=O(n,t,"timing"),i=O(e,t,"cpu"),a=O(n,t,"cpu"),c=O(e,t,"heap"),l=O(n,t,"heap"),u=r?.id??i?.id??c?.id,p=o?.id??a?.id??l?.id;if(!u||!p)return;let d=!1,b;if(r?.timing&&o?.timing){let R=ee(r.timing.median,o.timing.median),k=ee(r.timing.mean,o.timing.mean),P=R>s.timingPct?"regressed":R<-s.timingPct?"improved":"unchanged";if(P==="regressed")d=!0;b={medianDeltaPct:R,meanDeltaPct:k,verdict:P}}let x;if(i?.cpu&&a?.cpu){let R=new Map(i.cpu.totals.map((g)=>[i.cpu.frames[g.frameIx].key,g])),k=new Map(a.cpu.totals.map((g)=>[a.cpu.frames[g.frameIx].key,g])),P=new Map(i.cpu.frames.map((g)=>[g.key,g.name])),h=new Map(a.cpu.frames.map((g)=>[g.key,g.name]));x=[...new Set([...R.keys(),...k.keys()])].map((g)=>{let f=R.get(g)?.selfUs??0,y=k.get(g)?.selfUs??0;return{frameKey:g,name:h.get(g)??P.get(g)??g,baseSelfUs:f,candSelfUs:y,deltaPct:ee(f,y)}}).sort((g,f)=>Math.abs(f.deltaPct)-Math.abs(g.deltaPct));for(let g of x)if((g.baseSelfUs>=s.minFrameSelfUs||g.candSelfUs>=s.minFrameSelfUs)&&g.deltaPct>s.frameSelfPct)d=!0}let w;if(c?.heap&&l?.heap){let R=new Map(c.heap.typeCounts.map((h)=>[h.type,h])),k=new Map(l.heap.typeCounts.map((h)=>[h.type,h]));w=[...new Set([...R.keys(),...k.keys()])].map((h)=>{let m=R.get(h),g=k.get(h);return{type:h,baseCount:m?.count??0,candCount:g?.count??0,baseBytes:m?.retainedBytes,candBytes:g?.retainedBytes,deltaPct:ee(m?.count??0,g?.count??0)}}).sort((h,m)=>Math.abs(m.deltaPct)-Math.abs(h.deltaPct));for(let h of w)if(h.deltaPct>s.heapTypePct)d=!0}return{id:T("cmp",u,p),baselineRunId:u,candidateRunId:p,timing:b,frames:x,heapTypes:w,thresholds:s,verdict:d?"fail":"pass"}}function S(e,n){if(n){let t=e.runs.find((s)=>s.id===n);return t?.cpu?[t]:[]}return e.runs.filter((t)=>t.phase==="cpu"&&t.cpu)}function j(e){let n=e.nodes,t=n.length,s=bn(e),r=new Int32Array(t).fill(-1);for(let c=0;c<t;c++)for(let l of n[c].children){let u=s(l);if(u!==-1)r[u]=c}let o=[];for(let c=0;c<t;c++)if(r[c]===-1)o.push(c);let i=[],a=[];for(let c=o.length-1;c>=0;c--)a.push(o[c]);while(a.length>0){let c=a.pop();i.push(c);for(let l of n[c].children){let u=s(l);if(u!==-1&&r[u]===c)a.push(u)}}return{count:t,indexOf:s,parentIx:r,roots:o,order:i}}function bn(e){let n=e.nodes,t=n.length,s=1/0,r=-1/0,o=!0;for(let a=0;a<t;a++){let c=n[a].id;if(!Number.isInteger(c)){o=!1;break}if(c<s)s=c;if(c>r)r=c}if(o&&t>0&&r-s<t*4+64){let a=r-s+1,c=new Int32Array(a).fill(-1);for(let l=0;l<t;l++)c[n[l].id-s]=l;return(l)=>{let u=l-s;return u>=0&&u<a?c[u]:-1}}let i=new Map;for(let a=0;a<t;a++)i.set(n[a].id,a);return(a)=>i.get(a)??-1}function Ce(e,n){let{count:t,indexOf:s,parentIx:r,order:o}=n,i=new Float64Array(t),a=new Float64Array(t),c=e.samples?.nodeIds??[],l=e.samples?.timeDeltasUs??[];for(let p=0;p<c.length;p++){let d=s(c[p]);if(d===-1)continue;i[d]+=l[p]??0,a[d]+=1}let u=new Float64Array(t);for(let p=o.length-1;p>=0;p--){let d=o[p];u[d]+=i[d];let b=r[d];if(b>=0)u[b]+=u[d]}return{selfUs:i,totalUs:u,samples:a}}var Te={name:"collapsed",async render(e,n={}){return{files:S(e,n.runId).map((r)=>{let o=r.cpu,{nodes:i,frames:a}=o,c=j(o),l=Array(c.count);for(let x of c.order){let w=a[i[x].frameIx].name||"(anonymous)",R=c.parentIx[x];l[x]=R===-1?w:`${l[R]};${w}`}let u=new Float64Array(c.count),p=[],d=o.samples?.nodeIds??[];for(let x=0;x<d.length;x++){let w=c.indexOf(d[x]);if(w===-1)continue;if(u[w]++===0)p.push(w)}let b=Array(p.length);for(let x=0;x<p.length;x++){let w=p[x];b[x]=`${l[w]} ${u[w]}`}return{path:`${r.id}.collapsed.txt`,content:b.join(`
|
|
5
|
+
`)+(b.length>0?`
|
|
6
|
+
`:"")}})}}};var $e={name:"cpuprofile",async render(e,n={}){let t=S(e,n.runId),s=[],r=[];for(let o of t){if(o.cpu?.origin!=="cpu-prof"&&o.cpu?.origin!=="inspector"){r.push(`${o.id} (origin ${o.cpu?.origin??"unknown"} has no .cpuprofile artifact to pass through)`);continue}let i=o.artifacts.find((c)=>c.kind==="cpuprofile");if(!i){r.push(`${o.id} (no cpuprofile artifact recorded on this run)`);continue}let a=Bun.file(i.path);if(!await a.exists()){r.push(`${o.id} (artifact missing on disk: ${i.path})`);continue}s.push({path:`${o.id}.cpuprofile`,content:await a.text()})}if(s.length===0&&r.length>0)return{text:`No .cpuprofile artifacts available:
|
|
7
|
+
${r.map((o)=>` - ${o}`).join(`
|
|
8
|
+
`)}
|
|
9
|
+
`};return{files:s}}};var Ne={name:"json",async render(e){return{text:re(e)}}};var Ie={name:"jsonl",async render(e){let{runs:n,...t}=e;return{text:`${[q(t),...n.map((r)=>q(r))].join(`
|
|
10
|
+
`)}
|
|
11
|
+
`}}};function B(e){return(e/1e6).toFixed(3)}function ne(e){return e?.label??e?.command?.join(" ")??e?.entry?.task??e?.id??"unknown"}var Fe=10,De=10,ve={name:"markdown",async render(e){let n=new Map(e.workloads.map((r)=>[r.id,r])),t=[];t.push("# Profile Report",""),t.push(`Bun ${e.bunVersion} \xB7 ostia ${e.toolVersion} \xB7 ${e.platform.os}/${e.platform.arch} \xB7 ${e.createdAt}`,"");let s=e.runs.filter((r)=>r.phase==="timing"&&r.timing!==void 0);if(s.length>0){t.push("## Timing",""),t.push("| Command | Mean \xB1 SD (ms) | Min\u2026Max (ms) | Median (ms) |","|---|---|---|---|");for(let o of s){let i=ne(n.get(o.workloadId)),a=o.timing;t.push(`| ${i} | ${B(a.mean)} \xB1 ${B(a.stddev)} | ${B(a.min)}\u2026${B(a.max)} | ${B(a.median)} |`)}t.push("");let r=s.filter((o)=>o.warnings.length>0);if(r.length>0){t.push("### Warnings","");for(let o of r){let i=ne(n.get(o.workloadId));for(let a of o.warnings)t.push(`- **${i}**: ${a.message} (\`${a.code}\`)`)}t.push("")}}for(let r of e.runs){if(r.phase!=="cpu"&&r.phase!=="heap")continue;let o=ne(n.get(r.workloadId));if(r.phase==="cpu"){if(t.push(`## CPU capture - ${o}`,""),t.push(`instrumented, diagnostic wall ${B(r.diagnosticWallNs??0)}ms`,""),r.cpu){t.push(`origin: \`${r.cpu.origin}\`, interval: ${r.cpu.samplingIntervalUs}\xB5s`,""),t.push("| Self % | Self (ms) | Total (ms) | Frame |","|---|---|---|---|");let i=r.cpu.totals.reduce((a,c)=>a+c.selfUs,0)||1;for(let a of r.cpu.totals.slice(0,Fe)){let c=r.cpu.frames[a.frameIx],l=(a.selfUs/i*100).toFixed(1);t.push(`| ${l}% | ${(a.selfUs/1000).toFixed(2)} | ${(a.totalUs/1000).toFixed(2)} | ${c?.name||"(anonymous)"} |`)}if(t.push(""),r.jit){let a=r.jit.tiers;t.push(`JIT tiers: LLInt ${a.llint} \xB7 Baseline ${a.baseline} \xB7 DFG ${a.dfg} \xB7 FTL ${a.ftl}`,"")}}}else if(t.push(`## Heap snapshot - ${o}`,""),t.push(`instrumented, diagnostic wall ${B(r.diagnosticWallNs??0)}ms`,""),r.heap){t.push(`${r.heap.objectCount??"?"} objects, ${((r.heap.heapSizeBytes??0)/1e6).toFixed(2)}MB`,""),t.push("| Count | Type |","|---|---|");for(let i of r.heap.typeCounts.slice(0,De))t.push(`| ${i.count} | ${i.type} |`);t.push("")}for(let i of r.artifacts)t.push(`- artifact: \`${i.path}\``);for(let i of r.warnings)t.push(`- ! ${i.message} (\`${i.code}\`)`);if(r.artifacts.length>0||r.warnings.length>0)t.push("")}if(e.comparisons&&e.comparisons.length>0){t.push("## Comparisons","");for(let r of e.comparisons){let o=e.runs.find((a)=>a.id===r.candidateRunId),i=ne(o?n.get(o.workloadId):void 0);if(t.push(`### ${r.verdict==="pass"?"\u2713":"\u2717"} ${i}`,""),r.timing){let a=r.timing.medianDeltaPct>0?"+":"";t.push(`- timing: ${a}${r.timing.medianDeltaPct.toFixed(1)}% median (**${r.timing.verdict}**)`)}for(let a of r.frames?.slice(0,Fe)??[]){if(Math.abs(a.deltaPct)<0.5)continue;let c=a.deltaPct>0?"+":"";t.push(`- frame \`${a.name}\`: ${c}${a.deltaPct.toFixed(1)}% self-time (${(a.baseSelfUs/1000).toFixed(2)}ms \u2192 ${(a.candSelfUs/1000).toFixed(2)}ms)`)}for(let a of r.heapTypes?.slice(0,De)??[]){if(Math.abs(a.deltaPct)<0.5)continue;let c=a.deltaPct>0?"+":"";t.push(`- heap \`${a.type}\`: ${c}${a.deltaPct.toFixed(1)}% count (${a.baseCount} \u2192 ${a.candCount})`)}t.push("")}}return{text:t.join(`
|
|
12
|
+
`)}}};var wn=15;function ce(e){return`n${e}`}function yn(e,n,t){return`${(e||"(anonymous)").replace(/"/g,"'")} (self ${(n/1000).toFixed(2)}ms, total ${(t/1000).toFixed(2)}ms)`}function xn(e,n,t,s){let r=[];if(s<=0)return r;for(let o=0;o<n;o++){if(o===t)continue;let i=e[o];if(r.length===s&&i<=e[r[s-1]])continue;let a=r.length;while(a>0&&e[r[a-1]]<i)a--;if(r.splice(a,0,o),r.length>s)r.pop()}return r}var Ue={name:"mermaid",async render(e,n={}){let t=n.topN??wn;return{files:S(e,n.runId).map((o)=>{let i=o.cpu,{nodes:a,frames:c}=i,l=j(i),{selfUs:u,totalUs:p}=Ce(i,l),{parentIx:d}=l,b=l.roots[0]??-1,x=xn(u,l.count,b,t),w=new Set(b!==-1?[b]:[]),R=[];for(let P of x){R.length=0;for(let h=P;h!==-1;h=d[h])R.push(h);for(let h=R.length-1;h>=0;h--)w.add(R[h])}let k=["graph TD"];for(let P of w){let h=a[P].id;k.push(` ${ce(h)}["${yn(c[a[P].frameIx].name,u[P],p[P])}"]`)}for(let P of w){let h=d[P];if(h!==-1&&w.has(h))k.push(` ${ce(a[h].id)} --> ${ce(a[P].id)}`)}return{path:`${o.id}.mermaid.md`,content:`${k.join(`
|
|
13
|
+
`)}
|
|
14
|
+
`}})}}};var Rn="https://www.speedscope.app/file-format-schema.json";function Se(e){return e?.label??e?.command?.join(" ")??e?.entry?.task??"profile"}var Be={name:"speedscope",async render(e,n={}){let t=S(e,n.runId),s=new Map(e.workloads.map((o)=>[o.id,o]));return{files:t.map((o)=>{let i=o.cpu,{nodes:a}=i,c=j(i),l=i.samples?.nodeIds??[],u=i.samples?.timeDeltasUs??[],p=Array(c.count);for(let w of c.order){let R=c.parentIx[w],k=a[w].frameIx;p[w]=R===-1?[k]:[...p[R],k]}let d=Array(l.length);for(let w=0;w<l.length;w++){let R=c.indexOf(l[w]);d[w]=R===-1?[]:p[R]}let b=0;for(let w=0;w<u.length;w++)b+=u[w];let x={$schema:Rn,exporter:"ostia",name:Se(s.get(o.workloadId)),activeProfileIndex:0,shared:{frames:i.frames.map((w)=>({name:w.name||"(anonymous)",file:w.url,line:w.line!==void 0?w.line+1:void 0}))},profiles:[{type:"sampled",name:Se(s.get(o.workloadId)),unit:"microseconds",startValue:0,endValue:b,samples:d,weights:u}]};return{path:`${o.id}.speedscope.json`,content:`${JSON.stringify(x,null,2)}
|
|
15
|
+
`}})}}};function z(e){return(e/1e6).toFixed(3)}function ue(e){return e.label??e.command?.join(" ")??e.entry?.task??e.id}var Me={name:"table",async render(e){let n=e.runs.filter((p)=>p.phase==="timing"&&p.timing!==void 0),t=new Map(e.workloads.map((p)=>[p.id,p]));if(n.length===0){let p=Ae(e,t);return{text:p.length>0?`${p.join(`
|
|
16
|
+
`)}
|
|
17
|
+
`:`(no timing runs)
|
|
18
|
+
`}}let s=n.map((p)=>{let d=t.get(p.workloadId);return{run:p,workload:d,label:d?ue(d):p.workloadId}}),r=Math.min(...s.map((p)=>p.run.timing.median)),o=s.length>1,i=[],a=Math.max(7,...s.map((p)=>p.label.length)),c=o?`${"Command".padEnd(a)} Mean [ms] Min\u2026Max [ms] Relative`:`${"Command".padEnd(a)} Mean [ms] Min\u2026Max [ms]`;i.push(c),i.push("-".repeat(c.length));for(let{run:p,label:d}of s){let b=p.timing,x=`${z(b.mean)} \xB1 ${z(b.stddev)}`,w=`${z(b.min)}\u2026${z(b.max)}`,R=`${d.padEnd(a)} ${x.padEnd(15)} ${w.padEnd(18)}`;if(o){let k=b.median/r;R+=k===1?" 1.00\xD7":` ${k.toFixed(2)}\xD7 slower`}i.push(R);for(let k of p.warnings)i.push(` ! ${k.message}`)}let l=Pn(e,t);if(l.length>0)i.push(""),i.push(...l);let u=Ae(e,t);if(u.length>0)i.push(""),i.push(...u);return{text:`${i.join(`
|
|
19
|
+
`)}
|
|
20
|
+
`}}};function kn(e,n,t){let s=e.runs.find((o)=>o.id===t),r=s?n.get(s.workloadId):void 0;return r?ue(r):t}function Ae(e,n){if(!e.comparisons||e.comparisons.length===0)return[];let t=[];for(let s of e.comparisons){let r=kn(e,n,s.candidateRunId),o=s.verdict==="pass"?"\u2713":"\u2717";if(t.push(`${o} ${r}`),s.timing){let i=s.timing.medianDeltaPct>0?"+":"";t.push(` timing: ${i}${s.timing.medianDeltaPct.toFixed(1)}% median (${s.timing.verdict})`)}if(s.frames)for(let i of s.frames.slice(0,Ee)){if(Math.abs(i.deltaPct)<0.5)continue;let a=i.deltaPct>0?"+":"";t.push(` frame ${i.name}: ${a}${i.deltaPct.toFixed(1)}% self-time (${(i.baseSelfUs/1000).toFixed(2)}ms -> ${(i.candSelfUs/1000).toFixed(2)}ms)`)}if(s.heapTypes)for(let i of s.heapTypes.slice(0,Oe)){if(Math.abs(i.deltaPct)<0.5)continue;let a=i.deltaPct>0?"+":"";t.push(` heap ${i.type}: ${a}${i.deltaPct.toFixed(1)}% count (${i.baseCount} -> ${i.candCount})`)}}return t}var Ee=5,Oe=5;function Pn(e,n){let t=[];for(let s of e.runs){if(s.phase!=="cpu"&&s.phase!=="heap")continue;let r=n.get(s.workloadId),o=r?ue(r):s.workloadId;if(s.phase==="cpu")if(s.cpu){t.push(`CPU capture - ${o} (instrumented, ${s.cpu.samplingIntervalUs}\xB5s interval, diagnostic wall ${z(s.diagnosticWallNs??0)}ms)`);let i=s.cpu.totals.reduce((a,c)=>a+c.selfUs,0)||1;for(let a of s.cpu.totals.slice(0,Ee)){let c=s.cpu.frames[a.frameIx],l=(a.selfUs/i*100).toFixed(1);t.push(` ${l.padStart(5)}% ${(a.selfUs/1000).toFixed(2).padStart(8)}ms self ${c?.name??"?"}`)}}else t.push(`CPU capture - ${o} (instrumented, no evidence captured)`);else if(s.heap){let i=((s.heap.heapSizeBytes??0)/1e6).toFixed(2);t.push(`Heap snapshot - ${o} (instrumented, ${s.heap.objectCount??"?"} objects, ${i}MB)`);for(let a of s.heap.typeCounts.slice(0,Oe))t.push(` ${String(a.count).padStart(6)} ${a.type}`)}else t.push(`Heap snapshot - ${o} (instrumented, no evidence captured)`);for(let i of s.artifacts)t.push(` artifact: ${i.path}`);for(let i of s.warnings)t.push(` ! ${i.message}`)}return t}var I={table:Me,json:Ne,markdown:ve,jsonl:Ie,collapsed:Te,mermaid:Ue,speedscope:Be,cpuprofile:$e};var Cn=".ostia",le=1000;async function We(e){let n=H({runs:e.runs??null,warmup:e.warmup??null,cpu:e.cpu??!1,heap:e.heap??!1,cpuIntervalUs:e.cpuIntervalUs??le}),s=`${e.outDir??Cn}/artifacts`,r=[],o=[];for(let i of e.commands){let a=Array.isArray(i)?i:xe(i),c=G(a,Array.isArray(i)?void 0:i);r.push(c);let l=await Q({argv:a,cwd:e.cwd,env:e.env,runs:e.runs,warmup:e.warmup}),u=Z({workload:c,configFingerprint:n,trials:l.trials,timing:l.timing,warnings:l.warnings});if(o.push(u),e.cpu){let p=`${u.id}-cpu.cpuprofile`,d=await pe({argv:a,cwd:e.cwd,env:e.env,artifactDir:s,fileName:p,intervalUs:e.cpuIntervalUs??le});o.push(await je({workload:c,phase:"cpu",configFingerprint:n,diagnosticWallNs:d.diagnosticWallNs,exitCode:d.exitCode,cpu:d.cpu,artifactPath:d.artifactPath,artifactKind:"cpuprofile",warnings:d.warnings}))}if(e.heap){let p=`${u.id}-heap.heapsnapshot`,d=await de({argv:a,cwd:e.cwd,env:e.env,artifactDir:s,fileName:p});o.push(await je({workload:c,phase:"heap",configFingerprint:n,diagnosticWallNs:d.diagnosticWallNs,exitCode:d.exitCode,heap:d.heap,artifactPath:d.artifactPath,artifactKind:"heapsnapshot",warnings:d.warnings}))}}return M(r,o)}async function je(e){let n=`${e.workload.id}-${e.phase}-${e.configFingerprint}`,t=e.artifactPath?[await ye(n,e.artifactKind,e.artifactPath)]:[];return Y({workload:e.workload,phase:e.phase,configFingerprint:e.configFingerprint,diagnosticWallNs:e.diagnosticWallNs,exitCode:e.exitCode,cpu:e.cpu,heap:e.heap,warnings:e.warnings,artifacts:t})}async function Yt(e,n={}){let t=we(e),s=H({intervalUs:n.intervalUs??le,origin:n.origin??"inspector"}),r=(l)=>l.samples?.nodeIds.length===0?[{code:"empty-profile",message:"In-process capture produced zero samples."}]:[];if(n.origin==="jsc"){let{result:l,cpu:u,jit:p,diagnosticWallNs:d}=await be(e,n),b=Y({workload:t,phase:"cpu",configFingerprint:s,diagnosticWallNs:d,cpu:u,jit:p,warnings:r(u),artifacts:[]});return{result:l,run:b}}let{result:o,cpu:i,diagnosticWallNs:a}=await fe(e,n),c=Y({workload:t,phase:"cpu",configFingerprint:s,diagnosticWallNs:a,cpu:i,warnings:r(i),artifacts:[]});return{result:o,run:c}}function _e(e){return T("cache",e.workloadId,e.phase,e.configFingerprint,e.bunVersion,e.toolVersion,e.instrumented,e.inputsDigest??null)}async function He(e,n=process.cwd()){if(e.length===0)return;let t=new Set;for(let o of e){let i=new Bun.Glob(o);for await(let a of i.scan({cwd:n,absolute:!1}))t.add(a)}let s=[...t].sort(),r=await Promise.all(s.map(async(o)=>{let i=await Bun.file(`${n}/${o}`).arrayBuffer();return{path:o,sha256:Bun.CryptoHasher.hash("sha256",i,"hex")}}));return T("inputs",r)}function Le(e,n){return`${e}/cache/${n}.json`}async function Je(e,n){let t=Bun.file(Le(e,n));if(!await t.exists())return;return await t.json()}async function ze(e,n,t){await Bun.write(Le(e,n),`${JSON.stringify(t,null,2)}
|
|
21
|
+
`)}var Tn={runs:null,warmup:3,outDir:".ostia",baseline:"main",cpuIntervalUs:1000,thresholds:J,workloads:[]};async function Ve(e="ostia.config.json"){let n=Bun.file(e);if(!await n.exists())return;let t=await n.json();return{...Tn,...t,thresholds:{...J,...t.thresholds??{}}}}function qe(e,n){return`${e.outDir}/baselines/${n??e.baseline}.json`}class te extends Error{path;constructor(e){super(`No baseline document at ${e}. Create one with: ostia run --export-json ${e} <command...>`);this.path=e}}async function Ke(e){let{config:n}=e,t=qe(n,e.baselineName);if(!await Bun.file(t).exists())throw new te(t);let r=await v(t),o=[],i=0,a=0,c=0;for(let b of n.workloads){let x=G(b.command,b.label),w=await He(b.inputs??[]),R=H({runs:n.runs,warmup:n.warmup}),k=_e({workloadId:x.id,phase:"timing",configFingerprint:R,bunVersion:Bun.version,toolVersion:_,instrumented:!1,inputsDigest:w}),P=e.full?void 0:await Je(n.outDir,k),h,m;if(P)h=P,m="cached",a++;else{i++;let g=await Q({argv:b.command,runs:n.runs??void 0,warmup:n.warmup});h=Z({workload:x,configFingerprint:R,trials:g.trials,timing:g.timing,warnings:g.warnings}),await ze(n.outDir,k,h),m="executed",c++}o.push({workload:x,status:m,run:h})}let l=M(o.map((b)=>b.workload),o.map((b)=>b.run)),u=0,p=0,d=0;for(let b of o){let x=ae(r,l,b.workload.id,n.thresholds);if(!x){d++;continue}if(b.comparison=x,x.verdict==="pass")u++;else p++}return l.comparisons=o.map((b)=>b.comparison).filter((b)=>b!==void 0),{document:l,summary:{total:n.workloads.length,affected:i,cached:a,executed:c,passed:u,regressed:p,missingBaseline:d,results:o}}}function Ge(e){let n=[];if(n.push(`${e.total} workloads`),n.push(`${e.affected} affected by this change`),n.push(`${e.cached} cached`),n.push(`${e.executed} executed`),e.missingBaseline>0)n.push(`${e.missingBaseline} skipped (no matching baseline workload)`);let t=e.results.filter((s)=>s.comparison?.verdict==="fail").map((s)=>{let r=s.comparison.timing,o=s.workload.label??s.workload.command?.join(" ")??s.workload.id;return r?`${r.medianDeltaPct>0?"+":""}${r.medianDeltaPct.toFixed(1)}% median on ${o}`:o});return n.push(`${e.passed} passed ${e.regressed} regressed${t.length>0?` (${t.join(", ")})`:""}`),n.push(""),n.push(`Profile CI: ${e.regressed>0?"\u2717":"\u2713"}`),`${n.join(`
|
|
22
|
+
`)}
|
|
23
|
+
`}async function V(e,n){if(e.text)process.stdout.write(e.text);if(!e.files||e.files.length===0)return;if(n)for(let t of e.files){let s=t.path?`${n}/${t.path}`:n;await Bun.write(s,t.content),process.stdout.write(`wrote ${s}
|
|
24
|
+
`)}else if(e.files.length===1)process.stdout.write(e.files[0].content);else for(let t of e.files)process.stdout.write(`--- ${t.path??"(unnamed)"} ---
|
|
25
|
+
${t.content}
|
|
26
|
+
`)}var $n=`ostia run [flags] <command...>
|
|
27
|
+
|
|
28
|
+
Run one or more commands N times with warmup and report timing statistics.
|
|
29
|
+
|
|
30
|
+
Flags:
|
|
31
|
+
--runs N exact number of timed trials (default: hyperfine-style auto)
|
|
32
|
+
--warmup N warmup trials, discarded (default: 3)
|
|
33
|
+
--cpu capture one instrumented CPU-profile trial (subprocess --cpu-prof)
|
|
34
|
+
--heap capture one instrumented heap-snapshot trial (subprocess --heap-prof)
|
|
35
|
+
--cpu-interval USEC CPU sampling interval in microseconds (default: 1000)
|
|
36
|
+
--out-dir PATH directory for captured artifacts (default: .ostia)
|
|
37
|
+
--export-json PATH write the full ProfileDocument to PATH
|
|
38
|
+
--format FORMAT table | json (default: table)
|
|
39
|
+
--quiet suppress the rendered report (still writes --export-json)
|
|
40
|
+
--help show this message
|
|
41
|
+
|
|
42
|
+
Instrumented runs (--cpu, --heap) are labeled separately from clean timing and never
|
|
43
|
+
mixed into the timing statistics.
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
ostia run "bun ./fixtures/work.ts"
|
|
47
|
+
ostia run --runs 25 --warmup 3 "bun a.ts" "bun b.ts"
|
|
48
|
+
ostia run --cpu --heap "bun src/server.ts"
|
|
49
|
+
ostia run --format json "bun a.ts"
|
|
50
|
+
`,Nn=`ostia bench [flags] <suite.ts...>
|
|
51
|
+
|
|
52
|
+
Run in-process benchmark suites (registered via group()/task()). Each suite file runs
|
|
53
|
+
in its own spawned child process (isolated from CLI startup state).
|
|
54
|
+
|
|
55
|
+
Flags:
|
|
56
|
+
--time-budget MS time budget per task, min-samples permitting (default: 500)
|
|
57
|
+
--min-samples N minimum samples per task (default: 20)
|
|
58
|
+
--gc Bun.gc(true) between trials (default: off - hides allocation cost)
|
|
59
|
+
--out-dir PATH directory for scratch IPC files (default: .ostia)
|
|
60
|
+
--export-json PATH write the full ProfileDocument to PATH
|
|
61
|
+
--format FORMAT table | json (default: table)
|
|
62
|
+
--quiet suppress the rendered report (still writes --export-json)
|
|
63
|
+
--help show this message
|
|
64
|
+
|
|
65
|
+
Suite files register tasks like:
|
|
66
|
+
import { group, task } from "<pkg>"
|
|
67
|
+
group("parse", () => {
|
|
68
|
+
task("small input", () => parse(smallBuf))
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
ostia bench benches/parse.ts
|
|
73
|
+
ostia bench --time-budget 1000 --min-samples 50 benches/*.ts
|
|
74
|
+
`,Ze=`ostia compare <base.json> <candidate.json>
|
|
75
|
+
ostia compare <candidate.json> --baseline <path.json>
|
|
76
|
+
|
|
77
|
+
Compare two ProfileDocuments (matched by workload id) and rank timing/frame/heap deltas.
|
|
78
|
+
|
|
79
|
+
Flags:
|
|
80
|
+
--export-json PATH write the resulting document (with comparisons) to PATH
|
|
81
|
+
--format FORMAT table | json (default: table)
|
|
82
|
+
--quiet suppress the rendered report (still writes --export-json)
|
|
83
|
+
--help show this message
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
ostia compare before.json after.json
|
|
87
|
+
ostia compare after.json --baseline .ostia/baselines/main.json
|
|
88
|
+
`,In=`ostia report <document.json> [--format table|json|markdown|jsonl]
|
|
89
|
+
|
|
90
|
+
Render a saved ProfileDocument.
|
|
91
|
+
`,Fn=`ostia viz <document.json> --format FORMAT [--run <id>] [--out-dir PATH]
|
|
92
|
+
|
|
93
|
+
Render CPU evidence from a saved ProfileDocument as a visualization artifact. Files,
|
|
94
|
+
not a GUI - hand the output to speedscope.app, flamegraph.pl, or
|
|
95
|
+
a Mermaid renderer.
|
|
96
|
+
|
|
97
|
+
Formats:
|
|
98
|
+
ascii ranked self-time table (alias for the table renderer)
|
|
99
|
+
collapsed folded stacks: "root;a;b 42" - flamegraph.pl and most flame tooling
|
|
100
|
+
mermaid call tree, top-15 frames by total time (never the whole profile)
|
|
101
|
+
speedscope sampled profile JSON for speedscope.app
|
|
102
|
+
cpuprofile verbatim .cpuprofile pass-through (cpu-prof/inspector origins only)
|
|
103
|
+
|
|
104
|
+
Flags:
|
|
105
|
+
--run <id> render only this run (default: every CPU run in the document)
|
|
106
|
+
--out-dir PATH write artifacts here instead of stdout
|
|
107
|
+
--help show this message
|
|
108
|
+
|
|
109
|
+
Examples:
|
|
110
|
+
ostia viz run.json --format speedscope --out-dir .ostia/viz
|
|
111
|
+
ostia viz run.json --format collapsed | flamegraph.pl > flame.svg
|
|
112
|
+
`,Dn=`ostia ci [--full] [--baseline NAME]
|
|
113
|
+
|
|
114
|
+
Load ostia.config.json, run configured workloads (reusing cached results when their
|
|
115
|
+
fingerprint is unchanged), compare against the named baseline, and gate on regressions.
|
|
116
|
+
|
|
117
|
+
Flags:
|
|
118
|
+
--full ignore the cache; rerun every configured workload
|
|
119
|
+
--baseline NAME baseline name (default: config's "baseline" field, or "main")
|
|
120
|
+
--export-json PATH write the resulting document (with comparisons) to PATH
|
|
121
|
+
--quiet suppress the rendered report (still writes --export-json)
|
|
122
|
+
--help show this message
|
|
123
|
+
|
|
124
|
+
Exit codes: 0 pass, 1 regression, 2 harness error (missing config/baseline, spawn failure).
|
|
125
|
+
`;function vn(e){let n=[],t,s,r=!1,o=!1,i,a,c,l="table",u=!1,p=!1;for(let d=0;d<e.length;d++){let b=e[d];switch(b){case"--runs":t=Number(e[++d]);break;case"--warmup":s=Number(e[++d]);break;case"--cpu":r=!0;break;case"--heap":o=!0;break;case"--cpu-interval":i=Number(e[++d]);break;case"--out-dir":a=e[++d];break;case"--export-json":c=e[++d];break;case"--format":l=e[++d];break;case"--quiet":u=!0;break;case"--help":case"-h":p=!0;break;default:n.push(b)}}return{commands:n,runs:t,warmup:s,cpu:r,heap:o,cpuIntervalUs:i,outDir:a,exportJson:c,format:l,quiet:u,help:p}}async function Un(e){let n=vn(e);if(n.help||n.commands.length===0)return process.stdout.write($n),n.help?0:2;if(!(n.format in I))return process.stderr.write(`Unknown --format "${n.format}". Expected one of: ${Object.keys(I).join(", ")}
|
|
126
|
+
`),2;let t;try{t=await We({commands:n.commands,runs:n.runs,warmup:n.warmup,cpu:n.cpu,heap:n.heap,cpuIntervalUs:n.cpuIntervalUs,outDir:n.outDir})}catch(r){return process.stderr.write(`Run failed: ${r instanceof Error?r.message:String(r)}
|
|
127
|
+
`),2}if(n.exportJson)await E(t,n.exportJson);if(!n.quiet){let o=await I[n.format].render(t,{});await V(o)}return t.runs.some((r)=>r.trials.some((o)=>o.exitCode!==void 0&&o.exitCode!==0))?1:0}function Sn(e){let n=[],t,s,r=!1,o,i,a="table",c=!1,l=!1;for(let u=0;u<e.length;u++){let p=e[u];switch(p){case"--time-budget":t=Number(e[++u]);break;case"--min-samples":s=Number(e[++u]);break;case"--gc":r=!0;break;case"--out-dir":o=e[++u];break;case"--export-json":i=e[++u];break;case"--format":a=e[++u];break;case"--quiet":c=!0;break;case"--help":case"-h":l=!0;break;default:n.push(p)}}return{suites:n,timeBudgetMs:t,minSamples:s,gc:r,outDir:o,exportJson:i,format:a,quiet:c,help:l}}async function Bn(e){let n=Sn(e);if(n.help||n.suites.length===0)return process.stdout.write(Nn),n.help?0:2;if(!(n.format in I))return process.stderr.write(`Unknown --format "${n.format}". Expected one of: ${Object.keys(I).join(", ")}
|
|
128
|
+
`),2;let t;try{t=await oe({suites:n.suites,timeBudgetMs:n.timeBudgetMs,minSamples:n.minSamples,gc:n.gc,outDir:n.outDir})}catch(s){return process.stderr.write(`Bench failed: ${s instanceof Error?s.message:String(s)}
|
|
129
|
+
`),2}if(n.exportJson)await E(t,n.exportJson);if(!n.quiet){let r=await I[n.format].render(t,{});await V(r)}return 0}function An(e){let n=[],t,s,r="table",o=!1,i=!1;for(let a=0;a<e.length;a++){let c=e[a];switch(c){case"--baseline":t=e[++a];break;case"--export-json":s=e[++a];break;case"--format":r=e[++a];break;case"--quiet":o=!0;break;case"--help":case"-h":i=!0;break;default:n.push(c)}}return{paths:n,baseline:t,exportJson:s,format:r,quiet:o,help:i}}async function Mn(e){let n=An(e);if(n.help)return process.stdout.write(Ze),0;let t,s;if(n.baseline)t=n.baseline,s=n.paths[0];else t=n.paths[0],s=n.paths[1];if(!t||!s)return process.stdout.write(Ze),2;let r,o;try{[r,o]=await Promise.all([v(t),v(s)])}catch(l){return process.stderr.write(`Failed to load documents: ${l instanceof Error?l.message:String(l)}
|
|
130
|
+
`),2}let i=ie(r,o),a={...o,comparisons:i};if(n.exportJson)await E(a,n.exportJson);if(!n.quiet){let u=await I[n.format].render(a,{});await V(u)}return i.some((l)=>l.verdict==="fail")?1:0}function En(e){let n,t="table",s=!1;for(let r=0;r<e.length;r++){let o=e[r];switch(o){case"--format":t=e[++r];break;case"--help":case"-h":s=!0;break;default:n=o}}return{path:n,format:t,help:s}}async function On(e){let n=En(e);if(n.help||!n.path)return process.stdout.write(In),n.help?0:2;let t;try{t=await v(n.path)}catch(o){return process.stderr.write(`Failed to load ${n.path}: ${o instanceof Error?o.message:String(o)}
|
|
131
|
+
`),2}let r=await I[n.format].render(t,{});return await V(r),0}var jn={ascii:"table"};function Wn(e){let n,t,s,r,o=!1;for(let i=0;i<e.length;i++){let a=e[i];switch(a){case"--format":{let c=e[++i]??"";t=jn[c]??c;break}case"--run":s=e[++i];break;case"--out-dir":r=e[++i];break;case"--help":case"-h":o=!0;break;default:n=a}}return{path:n,format:t,runId:s,outDir:r,help:o}}async function _n(e){let n=Wn(e);if(n.help||!n.path||!n.format)return process.stdout.write(Fn),n.help?0:2;if(!(n.format in I))return process.stderr.write(`Unknown --format "${n.format}". Expected one of: ${Object.keys(I).join(", ")}, ascii
|
|
132
|
+
`),2;let t;try{t=await v(n.path)}catch(o){return process.stderr.write(`Failed to load ${n.path}: ${o instanceof Error?o.message:String(o)}
|
|
133
|
+
`),2}let r=await I[n.format].render(t,{runId:n.runId});if(!r.text&&(!r.files||r.files.length===0))return process.stderr.write(n.runId?`No CPU evidence found for run "${n.runId}".
|
|
134
|
+
`:`No CPU evidence found in this document (no cpu-phase runs). Capture some with "ostia run --cpu ...".
|
|
135
|
+
`),2;return await V(r,n.outDir),0}function Hn(e){let n=!1,t,s,r=!1,o=!1;for(let i=0;i<e.length;i++)switch(e[i]){case"--full":n=!0;break;case"--baseline":t=e[++i];break;case"--export-json":s=e[++i];break;case"--quiet":r=!0;break;case"--help":case"-h":o=!0;break}return{full:n,baseline:t,exportJson:s,quiet:r,help:o}}async function Ln(e){let n=Hn(e);if(n.help)return process.stdout.write(Dn),0;let t=await Ve();if(!t)return process.stderr.write(`No ostia.config.json found. "ostia ci" needs configured workloads.
|
|
136
|
+
`),2;if(t.workloads.length===0)return process.stderr.write(`ostia.config.json has no "workloads" configured.
|
|
137
|
+
`),2;let s;try{s=await Ke({config:t,full:n.full,baselineName:n.baseline})}catch(r){if(r instanceof te)return process.stderr.write(`${r.message}
|
|
138
|
+
`),2;return process.stderr.write(`CI run failed: ${r instanceof Error?r.message:String(r)}
|
|
139
|
+
`),2}if(n.exportJson)await E(s.document,n.exportJson);if(!n.quiet)process.stdout.write(Ge(s.summary));return s.summary.regressed>0?1:0}async function Jn(){let[e,...n]=process.argv.slice(2);switch(e){case"run":return Un(n);case"bench":return Bn(n);case"compare":return Mn(n);case"report":return On(n);case"ci":return Ln(n);case"viz":return _n(n);case void 0:case"--help":case"-h":return process.stdout.write(`ostia - Bun-native profile IR engine
|
|
140
|
+
|
|
141
|
+
Commands:
|
|
142
|
+
run Run commands N times and report timing/CPU/heap
|
|
143
|
+
bench Run in-process benchmark suites (group()/task())
|
|
144
|
+
compare Compare two ProfileDocuments
|
|
145
|
+
report Render a saved ProfileDocument
|
|
146
|
+
ci Run configured workloads against a baseline, gate on regressions
|
|
147
|
+
viz Render CPU evidence as collapsed/mermaid/speedscope/cpuprofile
|
|
148
|
+
|
|
149
|
+
Run "ostia <command> --help" for details.
|
|
150
|
+
`),e===void 0?2:0;default:return process.stderr.write(`Unknown subcommand "${e}". Run "ostia --help".
|
|
151
|
+
`),2}}if(import.meta.main)Jn().then((e)=>process.exit(e));
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
interface RunOptions {
|
|
2
|
+
commands: (string | string[])[];
|
|
3
|
+
runs?: number;
|
|
4
|
+
warmup?: number;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
env?: Record<string, string>;
|
|
7
|
+
cpu?: boolean;
|
|
8
|
+
heap?: boolean;
|
|
9
|
+
cpuIntervalUs?: number;
|
|
10
|
+
outDir?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export declare function run(opts: RunOptions): Promise<ProfileDocument>;
|
|
14
|
+
|
|
15
|
+
interface ProfileOptions {
|
|
16
|
+
intervalUs?: number;
|
|
17
|
+
origin?: "inspector" | "jsc";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ProfileResult<T> {
|
|
21
|
+
result: T;
|
|
22
|
+
run: Run;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export declare function profile<T>(fn: () => T | Promise<T>, opts?: ProfileOptions): Promise<ProfileResult<T>>;
|
|
26
|
+
|
|
27
|
+
export interface ProfileDocument {
|
|
28
|
+
schemaVersion: 1;
|
|
29
|
+
toolVersion: string;
|
|
30
|
+
bunVersion: string;
|
|
31
|
+
platform: {
|
|
32
|
+
os: string;
|
|
33
|
+
arch: string;
|
|
34
|
+
};
|
|
35
|
+
createdAt: string;
|
|
36
|
+
workloads: Workload[];
|
|
37
|
+
runs: Run[];
|
|
38
|
+
comparisons?: Comparison[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface Workload {
|
|
42
|
+
id: string;
|
|
43
|
+
kind: "subprocess" | "inprocess";
|
|
44
|
+
label?: string;
|
|
45
|
+
command?: string[];
|
|
46
|
+
shell?: string;
|
|
47
|
+
entry?: {
|
|
48
|
+
file: string;
|
|
49
|
+
task: string;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type Phase = "timing" | "cpu" | "heap" | "memstats";
|
|
54
|
+
|
|
55
|
+
interface Run {
|
|
56
|
+
id: string;
|
|
57
|
+
workloadId: string;
|
|
58
|
+
phase: Phase;
|
|
59
|
+
instrumented: boolean;
|
|
60
|
+
configFingerprint: string;
|
|
61
|
+
trials: Trial[];
|
|
62
|
+
timing?: TimingStats;
|
|
63
|
+
diagnosticWallNs?: number;
|
|
64
|
+
cpu?: CpuEvidence;
|
|
65
|
+
heap?: HeapEvidence;
|
|
66
|
+
memory?: MemoryEvidence;
|
|
67
|
+
jit?: JitTierBreakdown;
|
|
68
|
+
warnings: Warning[];
|
|
69
|
+
artifacts: ArtifactRef[];
|
|
70
|
+
baselineRunId?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface Trial {
|
|
74
|
+
i: number;
|
|
75
|
+
wallNs: number;
|
|
76
|
+
exitCode?: number;
|
|
77
|
+
userNs?: number;
|
|
78
|
+
systemNs?: number;
|
|
79
|
+
maxRssBytes?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface TimingStats {
|
|
83
|
+
unit: "ns";
|
|
84
|
+
samples: number[];
|
|
85
|
+
mean: number;
|
|
86
|
+
median: number;
|
|
87
|
+
stddev: number;
|
|
88
|
+
min: number;
|
|
89
|
+
max: number;
|
|
90
|
+
outliers: {
|
|
91
|
+
mild: number;
|
|
92
|
+
severe: number;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface Frame {
|
|
97
|
+
key: string;
|
|
98
|
+
name: string;
|
|
99
|
+
url?: string;
|
|
100
|
+
line?: number;
|
|
101
|
+
col?: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface CallNode {
|
|
105
|
+
id: number;
|
|
106
|
+
frameIx: number;
|
|
107
|
+
children: number[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface FrameTotal {
|
|
111
|
+
frameIx: number;
|
|
112
|
+
selfUs: number;
|
|
113
|
+
totalUs: number;
|
|
114
|
+
samples: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface CpuEvidence {
|
|
118
|
+
origin: "cpu-prof" | "inspector" | "jsc-profile";
|
|
119
|
+
samplingIntervalUs: number;
|
|
120
|
+
frames: Frame[];
|
|
121
|
+
nodes: CallNode[];
|
|
122
|
+
totals: FrameTotal[];
|
|
123
|
+
samples?: {
|
|
124
|
+
nodeIds: number[];
|
|
125
|
+
timeDeltasUs: number[];
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface HeapEvidence {
|
|
130
|
+
origin: "heap-prof" | "generateHeapSnapshot" | "heapStats";
|
|
131
|
+
heapSizeBytes?: number;
|
|
132
|
+
objectCount?: number;
|
|
133
|
+
typeCounts: {
|
|
134
|
+
type: string;
|
|
135
|
+
count: number;
|
|
136
|
+
retainedBytes?: number;
|
|
137
|
+
}[];
|
|
138
|
+
snapshotArtifactId?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface MemoryEvidence {
|
|
142
|
+
origin: "resourceUsage" | "memoryUsage" | "heapStats";
|
|
143
|
+
perTrial?: {
|
|
144
|
+
rssBytes?: number;
|
|
145
|
+
heapSizeBytes?: number;
|
|
146
|
+
}[];
|
|
147
|
+
maxRssBytes?: number;
|
|
148
|
+
peakCommitBytes?: number;
|
|
149
|
+
pageFaults?: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface JitTierBreakdown {
|
|
153
|
+
origin: "jsc-profile";
|
|
154
|
+
tiers: {
|
|
155
|
+
llint: number;
|
|
156
|
+
baseline: number;
|
|
157
|
+
dfg: number;
|
|
158
|
+
ftl: number;
|
|
159
|
+
};
|
|
160
|
+
topFramesByTier?: {
|
|
161
|
+
tier: string;
|
|
162
|
+
frameKey: string;
|
|
163
|
+
samples: number;
|
|
164
|
+
}[];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
type WarningCode = "slow-first-run" | "outliers-detected" | "fast-command" | "nonzero-exit" | "instrumented-timing" | "artifact-missing" | "empty-profile" | "below-timer-resolution" | "cache-fallback-rerun";
|
|
168
|
+
|
|
169
|
+
interface Warning {
|
|
170
|
+
code: WarningCode;
|
|
171
|
+
message: string;
|
|
172
|
+
data?: Record<string, unknown>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
interface ArtifactRef {
|
|
176
|
+
id: string;
|
|
177
|
+
kind: "cpuprofile" | "cpu-md" | "heapsnapshot" | "heap-md" | "speedscope" | "collapsed" | "mermaid" | "other";
|
|
178
|
+
path: string;
|
|
179
|
+
sha256: string;
|
|
180
|
+
bytes: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface Comparison {
|
|
184
|
+
id: string;
|
|
185
|
+
baselineRunId: string;
|
|
186
|
+
candidateRunId: string;
|
|
187
|
+
timing?: {
|
|
188
|
+
medianDeltaPct: number;
|
|
189
|
+
meanDeltaPct: number;
|
|
190
|
+
verdict: "improved" | "regressed" | "unchanged";
|
|
191
|
+
};
|
|
192
|
+
frames?: {
|
|
193
|
+
frameKey: string;
|
|
194
|
+
name: string;
|
|
195
|
+
baseSelfUs: number;
|
|
196
|
+
candSelfUs: number;
|
|
197
|
+
deltaPct: number;
|
|
198
|
+
}[];
|
|
199
|
+
heapTypes?: {
|
|
200
|
+
type: string;
|
|
201
|
+
baseCount: number;
|
|
202
|
+
candCount: number;
|
|
203
|
+
baseBytes?: number;
|
|
204
|
+
candBytes?: number;
|
|
205
|
+
deltaPct: number;
|
|
206
|
+
}[];
|
|
207
|
+
thresholds: {
|
|
208
|
+
timingPct: number;
|
|
209
|
+
frameSelfPct: number;
|
|
210
|
+
heapTypePct: number;
|
|
211
|
+
minFrameSelfUs: number;
|
|
212
|
+
};
|
|
213
|
+
verdict: "pass" | "fail";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
interface BenchOptions {
|
|
217
|
+
suites: string[];
|
|
218
|
+
timeBudgetMs?: number;
|
|
219
|
+
minSamples?: number;
|
|
220
|
+
gc?: boolean;
|
|
221
|
+
outDir?: string;
|
|
222
|
+
cwd?: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export declare function bench(opts: BenchOptions): Promise<ProfileDocument>;
|
|
226
|
+
|
|
227
|
+
export declare function group(name: string, fn: () => void): void;
|
|
228
|
+
|
|
229
|
+
export declare function task(name: string, fn: () => unknown | Promise<unknown>): void;
|
|
230
|
+
|
|
231
|
+
interface Thresholds {
|
|
232
|
+
timingPct: number;
|
|
233
|
+
frameSelfPct: number;
|
|
234
|
+
heapTypePct: number;
|
|
235
|
+
minFrameSelfUs: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export declare function compareDocuments(base: ProfileDocument, cand: ProfileDocument, thresholds?: Thresholds): Comparison[];
|
|
239
|
+
|
|
240
|
+
export declare function saveDocument(doc: ProfileDocument, path: string): Promise<void>;
|
|
241
|
+
|
|
242
|
+
export declare function loadDocument(path: string): Promise<ProfileDocument>;
|
|
243
|
+
|
|
244
|
+
export declare const renderers: Record<FormatName, Renderer<any>>;
|
|
245
|
+
|
|
246
|
+
type FormatName = "table" | "json" | "markdown" | "jsonl" | "collapsed" | "mermaid" | "speedscope" | "cpuprofile";
|
|
247
|
+
|
|
248
|
+
interface RenderResult {
|
|
249
|
+
text?: string;
|
|
250
|
+
files?: {
|
|
251
|
+
path?: string;
|
|
252
|
+
content: string;
|
|
253
|
+
}[];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
interface Renderer<O = unknown> {
|
|
257
|
+
name: FormatName;
|
|
258
|
+
render(doc: ProfileDocument, options: O): Promise<RenderResult>;
|
|
259
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
function A(e){return JSON.stringify(W(e))}function W(e){if(Array.isArray(e))return e.map(W);if(e!==null&&typeof e==="object"){let s={};for(let n of Object.keys(e).sort())s[n]=W(e[n]);return s}return e}function C(e,...s){let n=Bun.CryptoHasher.hash("sha256",A(s),"hex");return`${e}_${n.slice(0,16)}`}function Se(e){return e.startsWith("file://")?e.slice(7):e}function _(e,s,n){let r=e.nodes,t=r.length,a=new Map,i=[],o=new Map,c=new Int32Array(t);for(let p=0;p<t;p++){let f=r[p],u=f.callFrame,w=Se(u.url),k=a.get(u.functionName);if(k===void 0)k=new Map,a.set(u.functionName,k);let N=k.get(w);if(N===void 0)N=i.length,k.set(w,N),i.push({key:C("fr",u.functionName,w),name:u.functionName,url:w||void 0,line:u.lineNumber>=0?u.lineNumber:void 0,col:u.columnNumber>=0?u.columnNumber:void 0});c[p]=N,o.set(f.id,p)}let m=Array(t);for(let p=0;p<t;p++){let f=r[p];m[p]={id:f.id,frameIx:c[o.get(f.id)],children:f.children??[]}}let l=new Float64Array(t),d=new Float64Array(t),g=e.samples,x=e.timeDeltas;for(let p=0;p<g.length;p++){let f=o.get(g[p]);if(f===void 0)continue;l[f]+=x[p]??0,d[f]+=1}let R=new Int32Array(t).fill(-1);for(let p=0;p<t;p++){let f=r[p].children;if(!f)continue;for(let u of f){let w=o.get(u);if(w!==void 0)R[w]=p}}let y=[],b=[];for(let p=t-1;p>=0;p--)if(R[p]===-1)b.push(p);while(b.length>0){let p=b.pop();y.push(p);let f=r[p].children;if(!f)continue;for(let u of f){let w=o.get(u);if(w!==void 0&&R[w]===p)b.push(w)}}let T=new Float64Array(t);for(let p=y.length-1;p>=0;p--){let f=y[p];T[f]+=l[f];let u=R[f];if(u>=0)T[u]+=T[f]}let P=Array(i.length),h=[];for(let p=0;p<t;p++){let f=m[p].frameIx,u=P[f];if(u)u.selfUs+=l[p],u.totalUs+=T[p],u.samples+=d[p];else{let w={frameIx:f,selfUs:l[p],totalUs:T[p],samples:d[p]};P[f]=w,h.push(w)}}return{origin:s,samplingIntervalUs:n,frames:i,nodes:m,totals:h.sort((p,f)=>f.selfUs-p.selfUs),samples:{nodeIds:e.samples,timeDeltasUs:e.timeDeltas}}}function Me(e,s,n,r){let t=["--cpu-prof","--cpu-prof-dir",s,"--cpu-prof-name",n,"--cpu-prof-interval",String(r)],a=e[0];if(a==="bun"||a?.endsWith("/bun"))return[a,...t,...e.slice(1)];return e}async function ee(e){let s=`${e.artifactDir}/${e.fileName}`,n=e.argv[0],r=n==="bun"||n?.endsWith("/bun"),t=Me(e.argv,e.artifactDir,e.fileName,e.intervalUs),a=r?e.env:{...process.env,...e.env,BUN_OPTIONS:`--cpu-prof --cpu-prof-dir ${e.artifactDir} --cpu-prof-name ${e.fileName} --cpu-prof-interval ${e.intervalUs}`},i=Bun.nanoseconds(),c=await Bun.spawn(t,{cwd:e.cwd,env:a,stdout:"ignore",stderr:"ignore",stdin:"ignore"}).exited,m=Bun.nanoseconds()-i,l=Bun.file(s);if(!await l.exists())return{diagnosticWallNs:m,exitCode:c,warnings:[{code:"artifact-missing",message:`Expected a .cpuprofile at ${s} after exit ${c}, found nothing. The workload's argv[0] must be a \`bun\` binary for CPU capture.`,data:{artifactPath:s,argv:e.argv}}]};let d=await l.json(),g=_(d,"cpu-prof",e.intervalUs),x=d.samples.length===0?[{code:"empty-profile",message:"CPU capture produced zero samples."}]:[];return{diagnosticWallNs:m,exitCode:c,artifactPath:s,cpu:g,warnings:x}}function ne(e,s="heap-prof"){let{node_fields:n,node_types:r}=e.snapshot.meta,t=n.indexOf("type"),a=n.indexOf("self_size"),i=n.length,o=r[0];if(t===-1||a===-1||!Array.isArray(o))return{origin:s,typeCounts:[],objectCount:e.snapshot.node_count};let c=o.length,m=Array(c),l=new Map,d=[],g=0,x=e.nodes,R=x.length;for(let h=0;h<R;h+=i){let p=x[h+t],f=x[h+a]??0;g+=f;let u;if(p>=0&&p<c){if(u=m[p],u===void 0)u={type:o[p],count:0,bytes:0},m[p]=u,d.push(u)}else{let w=`unknown(${p})`;if(u=l.get(w),u===void 0)u={type:w,count:0,bytes:0},l.set(w,u),d.push(u)}u.count++,u.bytes+=f}let y=d.sort((h,p)=>p.count-h.count),b=y.slice(0,20),T=y.slice(20),P=b.map(({type:h,count:p,bytes:f})=>({type:h,count:p,retainedBytes:f}));if(T.length>0){let h=0,p=0;for(let f of T)h+=f.count,p+=f.bytes;P.push({type:"other",count:h,retainedBytes:p})}return{origin:s,heapSizeBytes:g,objectCount:e.snapshot.node_count,typeCounts:P}}function Be(e,s,n){let r=["--heap-prof","--heap-prof-dir",s,"--heap-prof-name",n],t=e[0];if(t==="bun"||t?.endsWith("/bun"))return[t,...r,...e.slice(1)];return e}async function te(e){let s=`${e.artifactDir}/${e.fileName}`,n=e.argv[0],r=n==="bun"||n?.endsWith("/bun"),t=Be(e.argv,e.artifactDir,e.fileName),a=r?e.env:{...process.env,...e.env,BUN_OPTIONS:`--heap-prof --heap-prof-dir ${e.artifactDir} --heap-prof-name ${e.fileName}`},i=Bun.nanoseconds(),c=await Bun.spawn(t,{cwd:e.cwd,env:a,stdout:"ignore",stderr:"ignore",stdin:"ignore"}).exited,m=Bun.nanoseconds()-i,l=Bun.file(s);if(!await l.exists())return{diagnosticWallNs:m,exitCode:c,warnings:[{code:"artifact-missing",message:`Expected a heap snapshot at ${s} after exit ${c}, found nothing. The workload's argv[0] must be a \`bun\` binary for heap capture.`,data:{artifactPath:s,argv:e.argv}}]};let d=await l.json(),g=ne(d,"heap-prof");return{diagnosticWallNs:m,exitCode:c,artifactPath:s,heap:g,warnings:[]}}import{Session as We}from"inspector/promises";var Oe=1000;async function re(e,s={}){let n=s.intervalUs??Oe,r=new We;r.connect();let t=Bun.nanoseconds();try{await r.post("Profiler.enable"),await r.post("Profiler.setSamplingInterval",{interval:n}),await r.post("Profiler.start");let a=await e(),{profile:i}=await r.post("Profiler.stop"),o=Bun.nanoseconds()-t,c=_(i,"inspector",n);return{result:a,cpu:c,diagnosticWallNs:o}}finally{r.disconnect()}}import{profile as Ae}from"bun:jsc";var Ee=new Map([["LLInt","llint"],["Baseline","baseline"],["DFG","dfg"],["FTL","ftl"]]),se=4294967295;function oe(e,s){let n=s??e.interval*1e6,r=new Map,t=[];function a(u,w,k,N){let I=r.get(u);if(I===void 0)I=new Map,r.set(u,I);let v=w??"",U=I.get(v);if(U===void 0)U=t.length,I.set(v,U),t.push({key:C("fr",u,v),name:u,url:w,line:k,col:N});return U}function i(u){let w=u.line===se,k=w?void 0:u.line-1,N=w||u.column===se?void 0:u.column-1;return a(u.name,u.sourceURL,k,N)}let o=a("(root)",void 0,void 0,void 0),c=1,m={id:0,frameIx:o,children:new Map,selfUs:0,samples:0,totalUs:0},l=new Map([[0,m]]),d={llint:0,baseline:0,dfg:0,ftl:0},g=new Map,x=[],R=[];for(let u of e.traces){let w=u.frames,k=m;for(let v=w.length-1;v>=0;v--){let U=i(w[v]),S=k.children.get(U);if(!S)S={id:c++,frameIx:U,children:new Map,selfUs:0,samples:0,totalUs:0},k.children.set(U,S),l.set(S.id,S);k=S}k.selfUs+=n,k.samples+=1,x.push(k.id),R.push(n);let N=w[0],I=N&&Ee.get(N.category);if(I){d[I]++;let v=g.get(I)??new Map;v.set(k.frameIx,(v.get(k.frameIx)??0)+1),g.set(I,v)}}function y(u){let w=u.selfUs;for(let k of u.children.values())w+=y(k);return u.totalUs=w,w}y(m);let b=new Map;function T(u){let w=b.get(u.frameIx);if(w)w.selfUs+=u.selfUs,w.totalUs+=u.totalUs,w.samples+=u.samples;else b.set(u.frameIx,{frameIx:u.frameIx,selfUs:u.selfUs,totalUs:u.totalUs,samples:u.samples});for(let k of u.children.values())T(k)}T(m);let P=[...l.values()].map((u)=>({id:u.id,frameIx:u.frameIx,children:[...u.children.values()].map((w)=>w.id)})),h={origin:"jsc-profile",samplingIntervalUs:n,frames:t,nodes:P,totals:[...b.values()].sort((u,w)=>w.selfUs-u.selfUs),samples:{nodeIds:x,timeDeltasUs:R}},p=[...g.entries()].flatMap(([u,w])=>[...w.entries()].sort((k,N)=>N[1]-k[1]).slice(0,3).map(([k,N])=>({tier:u,frameKey:t[k].key,samples:N})));return{cpu:h,jit:{origin:"jsc-profile",tiers:d,topFramesByTier:p}}}var _e=1000;async function ie(e,s={}){let n=s.intervalUs??_e,r,t=Bun.nanoseconds(),a=await Ae(async()=>(r=await e(),r),n),i=Bun.nanoseconds()-t,{cpu:o,jit:c}=oe(a.stackTraces,n);return{result:r,cpu:o,jit:c,diagnosticWallNs:i}}var V="0.1.0";function j(e,s){return{schemaVersion:1,toolVersion:V,bunVersion:Bun.version,platform:{os:process.platform,arch:process.arch},createdAt:new Date().toISOString(),workloads:e,runs:s}}function ae(e,s){return{id:C("wl","subprocess",e,process.cwd()),kind:"subprocess",command:e,label:s}}function ce(e,s){return{id:C("wl","inprocess",e.name,e.toString()),kind:"inprocess",label:s}}function ue(e){return{id:C("run",e.workload.id,"timing",e.configFingerprint,Bun.version,V),workloadId:e.workload.id,phase:"timing",instrumented:!1,configFingerprint:e.configFingerprint,trials:e.trials,timing:e.timing,warnings:e.warnings,artifacts:[],memory:je(e.trials)}}function je(e){let s=e.map((n)=>n.maxRssBytes).filter((n)=>n!==void 0);if(s.length===0)return;return{origin:"resourceUsage",perTrial:e.map((n)=>({rssBytes:n.maxRssBytes})),maxRssBytes:Math.max(...s)}}function L(e){return{id:C("run",e.workload.id,e.phase,e.configFingerprint,Bun.version,V),workloadId:e.workload.id,phase:e.phase,instrumented:!0,configFingerprint:e.configFingerprint,trials:[{i:0,wallNs:e.diagnosticWallNs,exitCode:e.exitCode}],diagnosticWallNs:e.diagnosticWallNs,cpu:e.cpu,heap:e.heap,jit:e.jit,warnings:e.warnings,artifacts:e.artifacts}}async function pe(e,s,n){let t=await Bun.file(n).arrayBuffer(),a=new Bun.CryptoHasher("sha256");return a.update(t),{id:C("art",e,s,n),kind:s,path:n,sha256:a.digest("hex"),bytes:t.byteLength}}function K(e){return C("cfg",e)}function q(e){return`${JSON.stringify(W(e),null,2)}
|
|
3
|
+
`}async function Le(e,s){await Bun.write(s,q(e))}async function G(e){let s=await Bun.file(e).text();return JSON.parse(s)}async function Y(e){let s=Bun.nanoseconds(),n=Bun.spawn(e.argv,{cwd:e.cwd,env:e.env,stdout:"ignore",stderr:"ignore",stdin:"ignore"}),r=await n.exited,t=Bun.nanoseconds(),a=n.resourceUsage?.();return{wallNs:t-s,exitCode:r,userNs:a?Number(a.cpuTime.user)*1000:void 0,systemNs:a?Number(a.cpuTime.system)*1000:void 0,maxRssBytes:a?.maxRSS}}function le(e){return e.trim().split(/\s+/).filter(Boolean)}function me(e){if(e.length===0)throw Error("computeTimingStats: samples must be non-empty");let s=e.length,n=de(e),r=0;for(let h=0;h<s;h++)r+=e[h];let t=r/s,a=O(n,0.5),i=0;for(let h=0;h<s;h++){let p=e[h]-t;i+=p*p}let o=Math.sqrt(i/s),c=n[0],m=n[s-1],l=O(n,0.25),d=O(n,0.75),g=d-l,x=l-1.5*g,R=d+1.5*g,y=l-3*g,b=d+3*g,T=0,P=0;for(let h=0;h<s;h++){let p=e[h];if(p<y||p>b)P++;else if(p<x||p>R)T++}return{unit:"ns",samples:e,mean:t,median:a,stddev:o,min:c,max:m,outliers:{mild:T,severe:P}}}function de(e){let s=new Float64Array(e.length);return s.set(e),s.sort(),s}function O(e,s){let n=e.length;if(n===1)return e[0];let r=s*(n-1),t=Math.floor(r),a=Math.ceil(r);if(t===a)return e[t];let i=r-t;return e[t]*(1-i)+e[a]*i}var He=5000000,Je=200;function fe(e,s,n="subprocess"){let r=[],t=e.samples[0];if(t!==void 0){let i=de(e.samples),o=O(i,0.25),m=O(i,0.75)-o;if(t>e.median+3*m&&m>0)r.push({code:"slow-first-run",message:`First run took ${(t/1e6).toFixed(2)}ms, much slower than the median ${(e.median/1e6).toFixed(2)}ms. Consider more warmup.`,data:{firstNs:t,medianNs:e.median}})}if(e.outliers.mild+e.outliers.severe>0)r.push({code:"outliers-detected",message:`${e.outliers.mild+e.outliers.severe} outlier(s) detected (${e.outliers.severe} severe, ${e.outliers.mild} mild).`,data:e.outliers});if(n==="subprocess"&&e.median<He)r.push({code:"fast-command",message:`Median run time (${(e.median/1e6).toFixed(3)}ms) is very fast; results may be dominated by spawn overhead.`,data:{medianNs:e.median}});if(n==="inprocess"&&e.median<Je)r.push({code:"below-timer-resolution",message:`Median run time (${e.median.toFixed(0)}ns) is close to timer resolution; consider a larger batch size or a coarser operation.`,data:{medianNs:e.median}});let a=s.filter((i)=>i!==void 0&&i!==0);if(a.length>0)r.push({code:"nonzero-exit",message:`${a.length} of ${s.length} trial(s) exited non-zero.`,data:{exitCodes:a}});return r}var ze=10,Ve=3000000000,Ke=3;async function ge(e){let s=e.warmup??Ke;for(let l=0;l<s;l++)await Y(e);let n=[],r=e.runs??e.minRuns??ze,t=e.runs!==void 0?0:e.minTotalNs??Ve,a=0,i=0;while(i<r||a<t){let l=await Y(e);if(n.push({i,wallNs:l.wallNs,exitCode:l.exitCode,userNs:l.userNs,systemNs:l.systemNs,maxRssBytes:l.maxRssBytes}),a+=l.wallNs,i++,e.runs!==void 0&&i>=e.runs)break}let o=n.map((l)=>l.wallNs),c=me(o),m=fe(c,n.map((l)=>l.exitCode));return{trials:n,timing:c,warnings:m}}var qe=new URL("./runner.ts",import.meta.url).pathname,Ge=".ostia";async function Ye(e){let n=`${e.outDir??Ge}/bench-tmp`,r=e.cwd??process.cwd(),t={timeBudgetMs:e.timeBudgetMs,minSamples:e.minSamples,gc:e.gc},a=[],i=[];try{for(let o of e.suites){let c=o.startsWith("/")?o:`${r}/${o}`,m=`${n}/${C("bench-out",c)}.json`,d=await Bun.spawn(["bun",qe,c,m,JSON.stringify(t)],{cwd:r,stdout:"inherit",stderr:"inherit",stdin:"ignore"}).exited;if(d!==0)throw Error(`Bench suite failed: ${o} (runner exited ${d})`);let g=await G(m);a.push(...g.workloads),i.push(...g.runs)}}finally{await Bun.spawn(["rm","-rf",n]).exited}return j(a,i)}var Ze=[],H;function Qe(e,s){let n=H;H=e;try{s()}finally{H=n}}function Xe(e,s){Ze.push({groupName:H,name:e,fn:s})}var he={timingPct:5,frameSelfPct:10,heapTypePct:10,minFrameSelfUs:1000};function J(e,s){if(e===0)return s===0?0:1/0;return(s-e)/e*100}function M(e,s,n){return e.runs.find((r)=>r.workloadId===s&&r.phase===n)}function en(e,s,n=he){let r=new Set(s.workloads.map((a)=>a.id)),t=[];for(let a of e.workloads){if(!r.has(a.id))continue;let i=nn(e,s,a.id,n);if(i)t.push(i)}return t}function nn(e,s,n,r=he){let t=M(e,n,"timing"),a=M(s,n,"timing"),i=M(e,n,"cpu"),o=M(s,n,"cpu"),c=M(e,n,"heap"),m=M(s,n,"heap"),l=t?.id??i?.id??c?.id,d=a?.id??o?.id??m?.id;if(!l||!d)return;let g=!1,x;if(t?.timing&&a?.timing){let b=J(t.timing.median,a.timing.median),T=J(t.timing.mean,a.timing.mean),P=b>r.timingPct?"regressed":b<-r.timingPct?"improved":"unchanged";if(P==="regressed")g=!0;x={medianDeltaPct:b,meanDeltaPct:T,verdict:P}}let R;if(i?.cpu&&o?.cpu){let b=new Map(i.cpu.totals.map((f)=>[i.cpu.frames[f.frameIx].key,f])),T=new Map(o.cpu.totals.map((f)=>[o.cpu.frames[f.frameIx].key,f])),P=new Map(i.cpu.frames.map((f)=>[f.key,f.name])),h=new Map(o.cpu.frames.map((f)=>[f.key,f.name]));R=[...new Set([...b.keys(),...T.keys()])].map((f)=>{let u=b.get(f)?.selfUs??0,w=T.get(f)?.selfUs??0;return{frameKey:f,name:h.get(f)??P.get(f)??f,baseSelfUs:u,candSelfUs:w,deltaPct:J(u,w)}}).sort((f,u)=>Math.abs(u.deltaPct)-Math.abs(f.deltaPct));for(let f of R)if((f.baseSelfUs>=r.minFrameSelfUs||f.candSelfUs>=r.minFrameSelfUs)&&f.deltaPct>r.frameSelfPct)g=!0}let y;if(c?.heap&&m?.heap){let b=new Map(c.heap.typeCounts.map((h)=>[h.type,h])),T=new Map(m.heap.typeCounts.map((h)=>[h.type,h]));y=[...new Set([...b.keys(),...T.keys()])].map((h)=>{let p=b.get(h),f=T.get(h);return{type:h,baseCount:p?.count??0,candCount:f?.count??0,baseBytes:p?.retainedBytes,candBytes:f?.retainedBytes,deltaPct:J(p?.count??0,f?.count??0)}}).sort((h,p)=>Math.abs(p.deltaPct)-Math.abs(h.deltaPct));for(let h of y)if(h.deltaPct>r.heapTypePct)g=!0}return{id:C("cmp",l,d),baselineRunId:l,candidateRunId:d,timing:x,frames:R,heapTypes:y,thresholds:r,verdict:g?"fail":"pass"}}function F(e,s){if(s){let n=e.runs.find((r)=>r.id===s);return n?.cpu?[n]:[]}return e.runs.filter((n)=>n.phase==="cpu"&&n.cpu)}function B(e){let s=e.nodes,n=s.length,r=tn(e),t=new Int32Array(n).fill(-1);for(let c=0;c<n;c++)for(let m of s[c].children){let l=r(m);if(l!==-1)t[l]=c}let a=[];for(let c=0;c<n;c++)if(t[c]===-1)a.push(c);let i=[],o=[];for(let c=a.length-1;c>=0;c--)o.push(a[c]);while(o.length>0){let c=o.pop();i.push(c);for(let m of s[c].children){let l=r(m);if(l!==-1&&t[l]===c)o.push(l)}}return{count:n,indexOf:r,parentIx:t,roots:a,order:i}}function tn(e){let s=e.nodes,n=s.length,r=1/0,t=-1/0,a=!0;for(let o=0;o<n;o++){let c=s[o].id;if(!Number.isInteger(c)){a=!1;break}if(c<r)r=c;if(c>t)t=c}if(a&&n>0&&t-r<n*4+64){let o=t-r+1,c=new Int32Array(o).fill(-1);for(let m=0;m<n;m++)c[s[m].id-r]=m;return(m)=>{let l=m-r;return l>=0&&l<o?c[l]:-1}}let i=new Map;for(let o=0;o<n;o++)i.set(s[o].id,o);return(o)=>i.get(o)??-1}function ye(e,s){let{count:n,indexOf:r,parentIx:t,order:a}=s,i=new Float64Array(n),o=new Float64Array(n),c=e.samples?.nodeIds??[],m=e.samples?.timeDeltasUs??[];for(let d=0;d<c.length;d++){let g=r(c[d]);if(g===-1)continue;i[g]+=m[d]??0,o[g]+=1}let l=new Float64Array(n);for(let d=a.length-1;d>=0;d--){let g=a[d];l[g]+=i[g];let x=t[g];if(x>=0)l[x]+=l[g]}return{selfUs:i,totalUs:l,samples:o}}var we={name:"collapsed",async render(e,s={}){return{files:F(e,s.runId).map((t)=>{let a=t.cpu,{nodes:i,frames:o}=a,c=B(a),m=Array(c.count);for(let R of c.order){let y=o[i[R].frameIx].name||"(anonymous)",b=c.parentIx[R];m[R]=b===-1?y:`${m[b]};${y}`}let l=new Float64Array(c.count),d=[],g=a.samples?.nodeIds??[];for(let R=0;R<g.length;R++){let y=c.indexOf(g[R]);if(y===-1)continue;if(l[y]++===0)d.push(y)}let x=Array(d.length);for(let R=0;R<d.length;R++){let y=d[R];x[R]=`${m[y]} ${l[y]}`}return{path:`${t.id}.collapsed.txt`,content:x.join(`
|
|
4
|
+
`)+(x.length>0?`
|
|
5
|
+
`:"")}})}}};var be={name:"cpuprofile",async render(e,s={}){let n=F(e,s.runId),r=[],t=[];for(let a of n){if(a.cpu?.origin!=="cpu-prof"&&a.cpu?.origin!=="inspector"){t.push(`${a.id} (origin ${a.cpu?.origin??"unknown"} has no .cpuprofile artifact to pass through)`);continue}let i=a.artifacts.find((c)=>c.kind==="cpuprofile");if(!i){t.push(`${a.id} (no cpuprofile artifact recorded on this run)`);continue}let o=Bun.file(i.path);if(!await o.exists()){t.push(`${a.id} (artifact missing on disk: ${i.path})`);continue}r.push({path:`${a.id}.cpuprofile`,content:await o.text()})}if(r.length===0&&t.length>0)return{text:`No .cpuprofile artifacts available:
|
|
6
|
+
${t.map((a)=>` - ${a}`).join(`
|
|
7
|
+
`)}
|
|
8
|
+
`};return{files:r}}};var xe={name:"json",async render(e){return{text:q(e)}}};var Re={name:"jsonl",async render(e){let{runs:s,...n}=e;return{text:`${[A(n),...s.map((t)=>A(t))].join(`
|
|
9
|
+
`)}
|
|
10
|
+
`}}};function D(e){return(e/1e6).toFixed(3)}function z(e){return e?.label??e?.command?.join(" ")??e?.entry?.task??e?.id??"unknown"}var Te=10,Pe=10,ke={name:"markdown",async render(e){let s=new Map(e.workloads.map((t)=>[t.id,t])),n=[];n.push("# Profile Report",""),n.push(`Bun ${e.bunVersion} \xB7 ostia ${e.toolVersion} \xB7 ${e.platform.os}/${e.platform.arch} \xB7 ${e.createdAt}`,"");let r=e.runs.filter((t)=>t.phase==="timing"&&t.timing!==void 0);if(r.length>0){n.push("## Timing",""),n.push("| Command | Mean \xB1 SD (ms) | Min\u2026Max (ms) | Median (ms) |","|---|---|---|---|");for(let a of r){let i=z(s.get(a.workloadId)),o=a.timing;n.push(`| ${i} | ${D(o.mean)} \xB1 ${D(o.stddev)} | ${D(o.min)}\u2026${D(o.max)} | ${D(o.median)} |`)}n.push("");let t=r.filter((a)=>a.warnings.length>0);if(t.length>0){n.push("### Warnings","");for(let a of t){let i=z(s.get(a.workloadId));for(let o of a.warnings)n.push(`- **${i}**: ${o.message} (\`${o.code}\`)`)}n.push("")}}for(let t of e.runs){if(t.phase!=="cpu"&&t.phase!=="heap")continue;let a=z(s.get(t.workloadId));if(t.phase==="cpu"){if(n.push(`## CPU capture - ${a}`,""),n.push(`instrumented, diagnostic wall ${D(t.diagnosticWallNs??0)}ms`,""),t.cpu){n.push(`origin: \`${t.cpu.origin}\`, interval: ${t.cpu.samplingIntervalUs}\xB5s`,""),n.push("| Self % | Self (ms) | Total (ms) | Frame |","|---|---|---|---|");let i=t.cpu.totals.reduce((o,c)=>o+c.selfUs,0)||1;for(let o of t.cpu.totals.slice(0,Te)){let c=t.cpu.frames[o.frameIx],m=(o.selfUs/i*100).toFixed(1);n.push(`| ${m}% | ${(o.selfUs/1000).toFixed(2)} | ${(o.totalUs/1000).toFixed(2)} | ${c?.name||"(anonymous)"} |`)}if(n.push(""),t.jit){let o=t.jit.tiers;n.push(`JIT tiers: LLInt ${o.llint} \xB7 Baseline ${o.baseline} \xB7 DFG ${o.dfg} \xB7 FTL ${o.ftl}`,"")}}}else if(n.push(`## Heap snapshot - ${a}`,""),n.push(`instrumented, diagnostic wall ${D(t.diagnosticWallNs??0)}ms`,""),t.heap){n.push(`${t.heap.objectCount??"?"} objects, ${((t.heap.heapSizeBytes??0)/1e6).toFixed(2)}MB`,""),n.push("| Count | Type |","|---|---|");for(let i of t.heap.typeCounts.slice(0,Pe))n.push(`| ${i.count} | ${i.type} |`);n.push("")}for(let i of t.artifacts)n.push(`- artifact: \`${i.path}\``);for(let i of t.warnings)n.push(`- ! ${i.message} (\`${i.code}\`)`);if(t.artifacts.length>0||t.warnings.length>0)n.push("")}if(e.comparisons&&e.comparisons.length>0){n.push("## Comparisons","");for(let t of e.comparisons){let a=e.runs.find((o)=>o.id===t.candidateRunId),i=z(a?s.get(a.workloadId):void 0);if(n.push(`### ${t.verdict==="pass"?"\u2713":"\u2717"} ${i}`,""),t.timing){let o=t.timing.medianDeltaPct>0?"+":"";n.push(`- timing: ${o}${t.timing.medianDeltaPct.toFixed(1)}% median (**${t.timing.verdict}**)`)}for(let o of t.frames?.slice(0,Te)??[]){if(Math.abs(o.deltaPct)<0.5)continue;let c=o.deltaPct>0?"+":"";n.push(`- frame \`${o.name}\`: ${c}${o.deltaPct.toFixed(1)}% self-time (${(o.baseSelfUs/1000).toFixed(2)}ms \u2192 ${(o.candSelfUs/1000).toFixed(2)}ms)`)}for(let o of t.heapTypes?.slice(0,Pe)??[]){if(Math.abs(o.deltaPct)<0.5)continue;let c=o.deltaPct>0?"+":"";n.push(`- heap \`${o.type}\`: ${c}${o.deltaPct.toFixed(1)}% count (${o.baseCount} \u2192 ${o.candCount})`)}n.push("")}}return{text:n.join(`
|
|
11
|
+
`)}}};var rn=15;function Z(e){return`n${e}`}function sn(e,s,n){return`${(e||"(anonymous)").replace(/"/g,"'")} (self ${(s/1000).toFixed(2)}ms, total ${(n/1000).toFixed(2)}ms)`}function on(e,s,n,r){let t=[];if(r<=0)return t;for(let a=0;a<s;a++){if(a===n)continue;let i=e[a];if(t.length===r&&i<=e[t[r-1]])continue;let o=t.length;while(o>0&&e[t[o-1]]<i)o--;if(t.splice(o,0,a),t.length>r)t.pop()}return t}var $e={name:"mermaid",async render(e,s={}){let n=s.topN??rn;return{files:F(e,s.runId).map((a)=>{let i=a.cpu,{nodes:o,frames:c}=i,m=B(i),{selfUs:l,totalUs:d}=ye(i,m),{parentIx:g}=m,x=m.roots[0]??-1,R=on(l,m.count,x,n),y=new Set(x!==-1?[x]:[]),b=[];for(let P of R){b.length=0;for(let h=P;h!==-1;h=g[h])b.push(h);for(let h=b.length-1;h>=0;h--)y.add(b[h])}let T=["graph TD"];for(let P of y){let h=o[P].id;T.push(` ${Z(h)}["${sn(c[o[P].frameIx].name,l[P],d[P])}"]`)}for(let P of y){let h=g[P];if(h!==-1&&y.has(h))T.push(` ${Z(o[h].id)} --> ${Z(o[P].id)}`)}return{path:`${a.id}.mermaid.md`,content:`${T.join(`
|
|
12
|
+
`)}
|
|
13
|
+
`}})}}};var an="https://www.speedscope.app/file-format-schema.json";function Ne(e){return e?.label??e?.command?.join(" ")??e?.entry?.task??"profile"}var Ce={name:"speedscope",async render(e,s={}){let n=F(e,s.runId),r=new Map(e.workloads.map((a)=>[a.id,a]));return{files:n.map((a)=>{let i=a.cpu,{nodes:o}=i,c=B(i),m=i.samples?.nodeIds??[],l=i.samples?.timeDeltasUs??[],d=Array(c.count);for(let y of c.order){let b=c.parentIx[y],T=o[y].frameIx;d[y]=b===-1?[T]:[...d[b],T]}let g=Array(m.length);for(let y=0;y<m.length;y++){let b=c.indexOf(m[y]);g[y]=b===-1?[]:d[b]}let x=0;for(let y=0;y<l.length;y++)x+=l[y];let R={$schema:an,exporter:"ostia",name:Ne(r.get(a.workloadId)),activeProfileIndex:0,shared:{frames:i.frames.map((y)=>({name:y.name||"(anonymous)",file:y.url,line:y.line!==void 0?y.line+1:void 0}))},profiles:[{type:"sampled",name:Ne(r.get(a.workloadId)),unit:"microseconds",startValue:0,endValue:x,samples:g,weights:l}]};return{path:`${a.id}.speedscope.json`,content:`${JSON.stringify(R,null,2)}
|
|
14
|
+
`}})}}};function E(e){return(e/1e6).toFixed(3)}function Q(e){return e.label??e.command?.join(" ")??e.entry?.task??e.id}var Ie={name:"table",async render(e){let s=e.runs.filter((d)=>d.phase==="timing"&&d.timing!==void 0),n=new Map(e.workloads.map((d)=>[d.id,d]));if(s.length===0){let d=ve(e,n);return{text:d.length>0?`${d.join(`
|
|
15
|
+
`)}
|
|
16
|
+
`:`(no timing runs)
|
|
17
|
+
`}}let r=s.map((d)=>{let g=n.get(d.workloadId);return{run:d,workload:g,label:g?Q(g):d.workloadId}}),t=Math.min(...r.map((d)=>d.run.timing.median)),a=r.length>1,i=[],o=Math.max(7,...r.map((d)=>d.label.length)),c=a?`${"Command".padEnd(o)} Mean [ms] Min\u2026Max [ms] Relative`:`${"Command".padEnd(o)} Mean [ms] Min\u2026Max [ms]`;i.push(c),i.push("-".repeat(c.length));for(let{run:d,label:g}of r){let x=d.timing,R=`${E(x.mean)} \xB1 ${E(x.stddev)}`,y=`${E(x.min)}\u2026${E(x.max)}`,b=`${g.padEnd(o)} ${R.padEnd(15)} ${y.padEnd(18)}`;if(a){let T=x.median/t;b+=T===1?" 1.00\xD7":` ${T.toFixed(2)}\xD7 slower`}i.push(b);for(let T of d.warnings)i.push(` ! ${T.message}`)}let m=un(e,n);if(m.length>0)i.push(""),i.push(...m);let l=ve(e,n);if(l.length>0)i.push(""),i.push(...l);return{text:`${i.join(`
|
|
18
|
+
`)}
|
|
19
|
+
`}}};function cn(e,s,n){let r=e.runs.find((a)=>a.id===n),t=r?s.get(r.workloadId):void 0;return t?Q(t):n}function ve(e,s){if(!e.comparisons||e.comparisons.length===0)return[];let n=[];for(let r of e.comparisons){let t=cn(e,s,r.candidateRunId),a=r.verdict==="pass"?"\u2713":"\u2717";if(n.push(`${a} ${t}`),r.timing){let i=r.timing.medianDeltaPct>0?"+":"";n.push(` timing: ${i}${r.timing.medianDeltaPct.toFixed(1)}% median (${r.timing.verdict})`)}if(r.frames)for(let i of r.frames.slice(0,Ue)){if(Math.abs(i.deltaPct)<0.5)continue;let o=i.deltaPct>0?"+":"";n.push(` frame ${i.name}: ${o}${i.deltaPct.toFixed(1)}% self-time (${(i.baseSelfUs/1000).toFixed(2)}ms -> ${(i.candSelfUs/1000).toFixed(2)}ms)`)}if(r.heapTypes)for(let i of r.heapTypes.slice(0,Fe)){if(Math.abs(i.deltaPct)<0.5)continue;let o=i.deltaPct>0?"+":"";n.push(` heap ${i.type}: ${o}${i.deltaPct.toFixed(1)}% count (${i.baseCount} -> ${i.candCount})`)}}return n}var Ue=5,Fe=5;function un(e,s){let n=[];for(let r of e.runs){if(r.phase!=="cpu"&&r.phase!=="heap")continue;let t=s.get(r.workloadId),a=t?Q(t):r.workloadId;if(r.phase==="cpu")if(r.cpu){n.push(`CPU capture - ${a} (instrumented, ${r.cpu.samplingIntervalUs}\xB5s interval, diagnostic wall ${E(r.diagnosticWallNs??0)}ms)`);let i=r.cpu.totals.reduce((o,c)=>o+c.selfUs,0)||1;for(let o of r.cpu.totals.slice(0,Ue)){let c=r.cpu.frames[o.frameIx],m=(o.selfUs/i*100).toFixed(1);n.push(` ${m.padStart(5)}% ${(o.selfUs/1000).toFixed(2).padStart(8)}ms self ${c?.name??"?"}`)}}else n.push(`CPU capture - ${a} (instrumented, no evidence captured)`);else if(r.heap){let i=((r.heap.heapSizeBytes??0)/1e6).toFixed(2);n.push(`Heap snapshot - ${a} (instrumented, ${r.heap.objectCount??"?"} objects, ${i}MB)`);for(let o of r.heap.typeCounts.slice(0,Fe))n.push(` ${String(o.count).padStart(6)} ${o.type}`)}else n.push(`Heap snapshot - ${a} (instrumented, no evidence captured)`);for(let i of r.artifacts)n.push(` artifact: ${i.path}`);for(let i of r.warnings)n.push(` ! ${i.message}`)}return n}var pn={table:Ie,json:xe,markdown:ke,jsonl:Re,collapsed:we,mermaid:$e,speedscope:Ce,cpuprofile:be};var ln=".ostia",X=1000;async function wt(e){let s=K({runs:e.runs??null,warmup:e.warmup??null,cpu:e.cpu??!1,heap:e.heap??!1,cpuIntervalUs:e.cpuIntervalUs??X}),r=`${e.outDir??ln}/artifacts`,t=[],a=[];for(let i of e.commands){let o=Array.isArray(i)?i:le(i),c=ae(o,Array.isArray(i)?void 0:i);t.push(c);let m=await ge({argv:o,cwd:e.cwd,env:e.env,runs:e.runs,warmup:e.warmup}),l=ue({workload:c,configFingerprint:s,trials:m.trials,timing:m.timing,warnings:m.warnings});if(a.push(l),e.cpu){let d=`${l.id}-cpu.cpuprofile`,g=await ee({argv:o,cwd:e.cwd,env:e.env,artifactDir:r,fileName:d,intervalUs:e.cpuIntervalUs??X});a.push(await De({workload:c,phase:"cpu",configFingerprint:s,diagnosticWallNs:g.diagnosticWallNs,exitCode:g.exitCode,cpu:g.cpu,artifactPath:g.artifactPath,artifactKind:"cpuprofile",warnings:g.warnings}))}if(e.heap){let d=`${l.id}-heap.heapsnapshot`,g=await te({argv:o,cwd:e.cwd,env:e.env,artifactDir:r,fileName:d});a.push(await De({workload:c,phase:"heap",configFingerprint:s,diagnosticWallNs:g.diagnosticWallNs,exitCode:g.exitCode,heap:g.heap,artifactPath:g.artifactPath,artifactKind:"heapsnapshot",warnings:g.warnings}))}}return j(t,a)}async function De(e){let s=`${e.workload.id}-${e.phase}-${e.configFingerprint}`,n=e.artifactPath?[await pe(s,e.artifactKind,e.artifactPath)]:[];return L({workload:e.workload,phase:e.phase,configFingerprint:e.configFingerprint,diagnosticWallNs:e.diagnosticWallNs,exitCode:e.exitCode,cpu:e.cpu,heap:e.heap,warnings:e.warnings,artifacts:n})}async function bt(e,s={}){let n=ce(e),r=K({intervalUs:s.intervalUs??X,origin:s.origin??"inspector"}),t=(m)=>m.samples?.nodeIds.length===0?[{code:"empty-profile",message:"In-process capture produced zero samples."}]:[];if(s.origin==="jsc"){let{result:m,cpu:l,jit:d,diagnosticWallNs:g}=await ie(e,s),x=L({workload:n,phase:"cpu",configFingerprint:r,diagnosticWallNs:g,cpu:l,jit:d,warnings:t(l),artifacts:[]});return{result:m,run:x}}let{result:a,cpu:i,diagnosticWallNs:o}=await re(e,s),c=L({workload:n,phase:"cpu",configFingerprint:r,diagnosticWallNs:o,cpu:i,warnings:t(i),artifacts:[]});return{result:a,run:c}}export{Ye as bench,en as compareDocuments,Qe as group,G as loadDocument,bt as profile,pn as renderers,wt as run,Le as saveDocument,Xe as task};
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ostia",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Fast profiling and benchmarking for Bun.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"default": "./index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"ostia": "cli.js"
|
|
14
|
+
},
|
|
15
|
+
"main": "./index.js",
|
|
16
|
+
"module": "./index.js",
|
|
17
|
+
"types": "./index.d.ts"
|
|
18
|
+
}
|