ostia 0.2.9 → 0.2.10
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 +35 -21
- package/ab-runner.ts +6 -4
- package/chunk-9cr8k2g1.js +2 -0
- package/chunk-jvjh4223.js +27 -0
- package/chunk-jzfv4c07.js +15 -0
- package/chunk-qwhvzj0v.js +227 -0
- package/chunk-rhk7gwaa.js +28 -0
- package/chunk-w9pvsd7y.js +14 -0
- package/chunk-zf3y6chk.js +3 -0
- package/cli.js +1 -224
- package/index.d.ts +489 -417
- package/index.js +1 -1
- package/package.json +1 -1
- package/runner.ts +5 -16
- package/chunk-cqy5fzae.js +0 -16
- package/chunk-t6q9jbb6.js +0 -25
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var nn=`ostia time [flags] <command...>
|
|
3
|
+
ostia time [flags] -- <argv...>
|
|
4
|
+
|
|
5
|
+
Time commands as subprocesses. Each <command> is whitespace-split into argv (no shell);
|
|
6
|
+
everything after -- is one more command's argv, verbatim.
|
|
7
|
+
|
|
8
|
+
Flags:
|
|
9
|
+
--samples N exact trials per command (default: ~3s budget, at least 10)
|
|
10
|
+
--budget MS sampling time budget per command
|
|
11
|
+
--min-samples N floor on trials when --samples isn't given
|
|
12
|
+
--warmup N discarded warmup trials (default: 3)
|
|
13
|
+
--no-interleave run commands one after another instead of round-robin
|
|
14
|
+
--prepare CMD run before every trial, unmeasured; once, or once per command
|
|
15
|
+
--time-source REGEX take each trial's time from capture group 1 in the command's output
|
|
16
|
+
--time-unit UNIT unit of --time-source: ns | us | ms | s (default: ms); needs --time-source
|
|
17
|
+
--cpu capture one extra CPU-profile trial
|
|
18
|
+
--heap capture one extra heap-snapshot trial
|
|
19
|
+
--cpu-interval US CPU sampling interval (default: 1000); needs --cpu
|
|
20
|
+
--timeout MS kill a trial or --prepare hook after MS
|
|
21
|
+
--ignore-failure[=CODE,...] treat these exit codes (bare: all) as success
|
|
22
|
+
--out-dir PATH artifact directory (default: node_modules/.cache/ostia)
|
|
23
|
+
--no-noise-check skip the ~200ms noise-floor measurement
|
|
24
|
+
--export-json PATH write the ProfileDocument to PATH
|
|
25
|
+
--format FORMAT table | json | jsonl | markdown | minimal (default: table)
|
|
26
|
+
--quiet don't print the report
|
|
27
|
+
--help show this message
|
|
28
|
+
|
|
29
|
+
A command stops at its first non-ignored non-zero exit.
|
|
30
|
+
Exit codes: 0 ok, 2 a command failed or produced no samples (or a bad flag), 130 Ctrl-C.
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
ostia time "bun a.ts" "bun b.ts"
|
|
34
|
+
ostia time --samples 25 --cpu --heap "bun src/server.ts"
|
|
35
|
+
ostia time --prepare "rm -rf dist" "bun build.ts"
|
|
36
|
+
ostia time --time-source "built in (\\d+)ms" "bun build.ts"
|
|
37
|
+
ostia time -- bun -e "console.log('a b')"
|
|
38
|
+
`,je=`ostia bench [flags] <suite.ts...>
|
|
39
|
+
|
|
40
|
+
Run in-process group()/task() suites, each file in its own child process. With no files,
|
|
41
|
+
uses ostia.config's "bench" section; each flag overrides its config field.
|
|
42
|
+
|
|
43
|
+
Flags:
|
|
44
|
+
--budget MS sampling budget per task (default: 500)
|
|
45
|
+
--samples N exact trials per task (ignores --budget)
|
|
46
|
+
--min-samples N floor on trials (default: cost-aware, 3-20)
|
|
47
|
+
--jobs N|auto suite files at once (default: 1; >1 adds noise)
|
|
48
|
+
--isolate one process per task, not per file (--no-isolate to undo config)
|
|
49
|
+
--gc Bun.gc(true) between trials (--no-gc)
|
|
50
|
+
--cpu extra per-task CPU profile with JIT tiers, ~2,000 samples (--no-cpu)
|
|
51
|
+
--cpu-interval US CPU sampling interval (default: 100)
|
|
52
|
+
--alloc extra per-task retained-heap-per-call measurement: what calls keep
|
|
53
|
+
alive after a full GC, not what they allocate (--no-alloc)
|
|
54
|
+
--peak-mem extra per-task RSS rise during the task's first call, garbage
|
|
55
|
+
included; median of 3 fresh processes (--no-peak-mem)
|
|
56
|
+
--filter REGEX only tasks whose "group/name" matches
|
|
57
|
+
--preload PATH import before each suite file (repeatable, in order)
|
|
58
|
+
--bun-flags FLAGS extra flags for the bun process running each suite (repeatable)
|
|
59
|
+
--timeout MS kill a suite (or isolated task) process after MS
|
|
60
|
+
--out-dir PATH scratch directory (default: node_modules/.cache/ostia)
|
|
61
|
+
--config PATH read this config file instead of ostia.config.ts/.json
|
|
62
|
+
--no-noise-check skip the ~200ms noise-floor measurement
|
|
63
|
+
--export-json PATH write the ProfileDocument to PATH
|
|
64
|
+
--format FORMAT table | json | jsonl | markdown | minimal (default: table)
|
|
65
|
+
--quiet don't print the report
|
|
66
|
+
--help show this message
|
|
67
|
+
|
|
68
|
+
Per-task/per-group options ({ budgetMs, minSamples, gc, isolate, cpu, alloc, peakMem }) override
|
|
69
|
+
the suite-wide defaults. Exit codes: 0 ok, 2 a suite failed (or a bad flag), 130 Ctrl-C.
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
ostia bench bench/*.ts
|
|
73
|
+
ostia bench bench/*.ts --filter parse --cpu --alloc
|
|
74
|
+
ostia bench --preload ./bench/dom-setup.ts --bun-flags="--conditions=browser" bench/*.ts
|
|
75
|
+
`,He=`ostia ab [flags] <suite.ts...>
|
|
76
|
+
|
|
77
|
+
Pair every task on a git ref's committed tree (base) against the working tree (candidate), in
|
|
78
|
+
one process, alternating ~10ms batches, and gate on the per-round time ratio. Drift between
|
|
79
|
+
the two sides cancels within a round. Each suite runs twice, once with each side first, and
|
|
80
|
+
the rounds are pooled. With no files, uses ostia.config's "bench" suites.
|
|
81
|
+
|
|
82
|
+
Flags:
|
|
83
|
+
--base REF git ref for the base side (default: HEAD)
|
|
84
|
+
--base-setup CMD shell command run once in a freshly extracted base tree, e.g. to build
|
|
85
|
+
gitignored files the suites import (repeatable, in order)
|
|
86
|
+
--rounds N base/candidate rounds per task (default: 15)
|
|
87
|
+
--threshold PCT flag a task whose median ratio moves past PCT (default: 10)
|
|
88
|
+
--geomean-threshold PCT fail when the geometric mean of all ratios is slower than PCT
|
|
89
|
+
(default: 1.5)
|
|
90
|
+
--confirm N re-measure each flagged task in N fresh processes (default: 2; 0 skips)
|
|
91
|
+
--alloc also compare retained heap per call on each side (--no-alloc)
|
|
92
|
+
--peak-mem also compare each side's first-call RSS rise (--no-peak-mem); both
|
|
93
|
+
run in 3 fresh processes per side, and the median counts
|
|
94
|
+
--mem-threshold PCT flag a memory reading that moves past PCT of the base's and past
|
|
95
|
+
its noise floor (default: 10)
|
|
96
|
+
--filter REGEX only tasks whose "group/name" matches
|
|
97
|
+
--preload PATH import before each suite file (repeatable, in order)
|
|
98
|
+
--bun-flags FLAGS extra flags for the bun process running each suite (repeatable)
|
|
99
|
+
--timeout MS kill a suite (or repeat) process after MS
|
|
100
|
+
--out-dir PATH scratch directory and base-tree cache (default: node_modules/.cache/ostia)
|
|
101
|
+
--keep-trees N base trees to keep cached, least recently used go first (default: 5)
|
|
102
|
+
--clean remove every cached base tree, then exit
|
|
103
|
+
--config PATH read this config file instead of ostia.config.ts/.json
|
|
104
|
+
--no-noise-check skip the ~200ms noise-floor measurement
|
|
105
|
+
--export-json PATH write the ProfileDocument to PATH
|
|
106
|
+
--format FORMAT table | json | jsonl | markdown | minimal (default: table)
|
|
107
|
+
--progress print progress to stderr (default: when stderr is a terminal and not
|
|
108
|
+
--quiet); --no-progress turns it off
|
|
109
|
+
--quiet don't print the report
|
|
110
|
+
--help show this message
|
|
111
|
+
|
|
112
|
+
The base tree is cached per commit (and --base-setup commands) under --out-dir. Setup runs
|
|
113
|
+
with the project's node_modules linked in, and OSTIA_AB_SHA and OSTIA_AB_CANDIDATE_DIR (the
|
|
114
|
+
working tree) set. A flagged task counts only when every fresh-process repeat flags it the
|
|
115
|
+
same way. A task that throws isn't timed; one that throws on the candidate side only fails
|
|
116
|
+
the run. A task whose suite file and output both changed reads "not comparable" and stays
|
|
117
|
+
out of the verdict. Exit codes: 0 pass, 1 a confirmed regression, a candidate-only throw,
|
|
118
|
+
the geomean over its threshold or a memory regression, 2 nothing paired or a harness error (not a git repo,
|
|
119
|
+
unknown ref, a failed setup or suite), 130 Ctrl-C.
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
ostia ab bench/parse.bench.ts
|
|
123
|
+
ostia ab bench/*.ts --base origin/main --rounds 21
|
|
124
|
+
ostia ab bench/*.ts --filter parse --format minimal
|
|
125
|
+
ostia ab bench/*.ts --base-setup "bun scripts/generate.ts"
|
|
126
|
+
ostia ab bench/paint.bench.ts --alloc --peak-mem
|
|
127
|
+
`,Ge=`ostia compare <base.json> <candidate.json>
|
|
128
|
+
ostia compare <candidate.json> --baseline <base.json>
|
|
129
|
+
|
|
130
|
+
Compare two ProfileDocuments by workload id. Gates on ostia.config's "thresholds" when
|
|
131
|
+
present (the same ones "ostia ci" uses), else the defaults.
|
|
132
|
+
|
|
133
|
+
Flags:
|
|
134
|
+
--baseline PATH the base document, when only the candidate is positional
|
|
135
|
+
--config PATH read thresholds from this config file instead of ostia.config.ts/.json
|
|
136
|
+
--export-json PATH write the candidate document with comparisons to PATH
|
|
137
|
+
--format FORMAT table | json | jsonl | markdown | minimal (default: table)
|
|
138
|
+
--quiet don't print the report
|
|
139
|
+
--help show this message
|
|
140
|
+
|
|
141
|
+
Exactly two document paths (one with --baseline); anything more is an error.
|
|
142
|
+
Exit codes: 0 pass, 1 a workload regressed, 2 nothing matched or a harness error.
|
|
143
|
+
`,Ve=`ostia report <document.json> [flags]
|
|
144
|
+
|
|
145
|
+
Render a saved ProfileDocument. Files, not a GUI for the visualization formats -
|
|
146
|
+
hand the output to speedscope.app, flamegraph.pl, or a Mermaid renderer.
|
|
147
|
+
|
|
148
|
+
Formats:
|
|
149
|
+
table terminal timing/CPU/heap/comparison text (default)
|
|
150
|
+
json pretty JSON document
|
|
151
|
+
jsonl one metadata line, then one line per measurement
|
|
152
|
+
markdown agent- and human-readable report
|
|
153
|
+
minimal one compact JSON object per timing measurement, for LLM/CI consumption
|
|
154
|
+
collapsed folded stacks: "root;a;b 42" - flamegraph.pl and most flame tooling
|
|
155
|
+
mermaid call tree, top-15 frames by total time (never the whole profile)
|
|
156
|
+
speedscope sampled profile JSON for speedscope.app
|
|
157
|
+
cpuprofile verbatim .cpuprofile pass-through (cpu-prof/inspector origins only)
|
|
158
|
+
|
|
159
|
+
Flags:
|
|
160
|
+
--format FORMAT one of the formats above (default: table)
|
|
161
|
+
--measurement <id> visualization formats only: render only this measurement
|
|
162
|
+
(default: every CPU measurement in the document)
|
|
163
|
+
--out-dir PATH visualization formats only: write files here instead of stdout
|
|
164
|
+
--help show this message
|
|
165
|
+
|
|
166
|
+
Examples:
|
|
167
|
+
ostia report doc.json
|
|
168
|
+
ostia report doc.json --format markdown
|
|
169
|
+
ostia report doc.json --format speedscope --out-dir node_modules/.cache/ostia/viz
|
|
170
|
+
ostia report doc.json --format collapsed | flamegraph.pl > flame.svg
|
|
171
|
+
`,tn=`ostia ci [flags]
|
|
172
|
+
|
|
173
|
+
Run ostia.config's workloads (reusing cached runs whose declared inputs are unchanged),
|
|
174
|
+
compare against a saved baseline, and gate on regressions.
|
|
175
|
+
|
|
176
|
+
Flags:
|
|
177
|
+
--full ignore the cache
|
|
178
|
+
--baseline NAME baseline to compare against (default: config "baseline", or "main")
|
|
179
|
+
--save-baseline after a pass, save this run as the new baseline (always measures
|
|
180
|
+
fresh: the cache is not used)
|
|
181
|
+
--config PATH read this config file instead of ostia.config.ts/.json
|
|
182
|
+
--no-noise-check skip the ~200ms noise-floor measurement
|
|
183
|
+
--export-json PATH write the candidate document with comparisons to PATH
|
|
184
|
+
--format FORMAT table | json | jsonl | markdown | minimal (default: table)
|
|
185
|
+
--quiet don't print the report
|
|
186
|
+
--help show this message
|
|
187
|
+
|
|
188
|
+
Exit codes: 0 pass, 1 regression, 2 harness error (missing config/baseline, a workload
|
|
189
|
+
failed or produced no samples, or an onMissingBaseline "fail" mismatch), 130 Ctrl-C (the
|
|
190
|
+
partial document is exported; nothing is compared or saved).
|
|
191
|
+
`,Q=`ostia baseline <save|list|show> [args]
|
|
192
|
+
|
|
193
|
+
Manage the baseline ProfileDocuments "ostia ci" gates against and "ostia compare --baseline"
|
|
194
|
+
reads.
|
|
195
|
+
|
|
196
|
+
Flags (every subcommand): --config PATH reads that config file instead of
|
|
197
|
+
ostia.config.ts/.json.
|
|
198
|
+
|
|
199
|
+
Subcommands:
|
|
200
|
+
save [name] measure every configured workload (same code path as "ostia ci",
|
|
201
|
+
no comparison) and write it to <baselineDir>/<name>.json
|
|
202
|
+
(default name: config's "baseline" field, or "main")
|
|
203
|
+
list list saved baselines: name, created date, workload count
|
|
204
|
+
(takes no arguments)
|
|
205
|
+
show <name> [flags] render a saved baseline, like "ostia report" (same
|
|
206
|
+
--format/--measurement/--out-dir flags)
|
|
207
|
+
|
|
208
|
+
Examples:
|
|
209
|
+
ostia baseline save
|
|
210
|
+
ostia baseline save my-feature
|
|
211
|
+
ostia baseline list
|
|
212
|
+
ostia baseline show main
|
|
213
|
+
ostia baseline show main --format markdown
|
|
214
|
+
`,rn=`ostia - profiling and benchmarking for Bun
|
|
215
|
+
|
|
216
|
+
Commands:
|
|
217
|
+
time Time commands N times and report timing/CPU/heap
|
|
218
|
+
bench Run in-process benchmark suites (group()/task())
|
|
219
|
+
ab Pair suites against a git ref in one process, gate on regressions
|
|
220
|
+
compare Compare two ProfileDocuments
|
|
221
|
+
report Render a saved ProfileDocument (table/json/markdown/collapsed/mermaid/speedscope/...)
|
|
222
|
+
ci Run configured workloads against a baseline, gate on regressions
|
|
223
|
+
baseline Manage baseline ProfileDocuments (save/list/show)
|
|
224
|
+
|
|
225
|
+
Run "ostia <command> --help" for details.
|
|
226
|
+
`;
|
|
227
|
+
export{nn,je,He,Ge,Ve,tn,Q,rn};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{T,l,K,ie,e,t,ae,U,i,q,Ze,u,A,a,_,k,r,v}from"./chunk-jzfv4c07.js";import{P,Je,ze,pe,m,ne,on,C,Ke,sn,fe,F,G,V,an,ge,J,qe,te,z,re,he,x,g}from"./chunk-jvjh4223.js";import{nn,je,He,Ge,Ve,tn,Q,rn}from"./chunk-qwhvzj0v.js";import{b}from"./chunk-zf3y6chk.js";import{writeSync as Ee}from"fs";import{resolve as kn}from"path";function Re(o){return e("cache",o.workloadId,o.phase,o.configFingerprint,o.bunVersion,o.toolVersion,o.instrumented,o.inputsDigest??null)}var yn=32;async function Ne(o,n=process.cwd()){if(o.length===0)return;let s=await J(o,n),c=Array(s.length),d=0,p=async()=>{while(d<s.length){let y=d++,f=s[y],w=await Bun.file(kn(n,f)).arrayBuffer();c[y]={path:f,sha256:Bun.CryptoHasher.hash("sha256",w,"hex")}}};return await Promise.all(Array.from({length:Math.min(yn,s.length)},p)),e("inputs",c)}function De(o,n){return`${o}/cache/${n}.json`}async function Ie(o,n){try{return await Bun.file(De(o,n)).json()}catch{return}}async function Be(o,n,s){await Bun.write(De(o,n),`${JSON.stringify(s,null,2)}
|
|
3
|
+
`)}var _e=600000;class me extends l{}class be extends me{path;constructor(o){super(`No baseline document at ${o}. Create one with: ostia baseline save ${o.split("/").pop().replace(/\.json$/,"")}`);this.path=o}}class Le extends me{path;missing;total;constructor(o,n,s){super(`${n} of ${s} configured workload(s) have no matching row in baseline ${o} (by workload id). Re-seed with: ostia baseline save`);this.path=o;this.missing=n;this.total=s}}async function xn(o,n,s,c){let d=await J(n.suites,process.cwd());if(d.length===0)throw new l(`Workload suites ${JSON.stringify(n.suites)} matched no files.`);let p=await te({suites:d,preload:[],bunFlags:[],noiseCheck:!1},o.bench),y=await z({...p,outDir:p.outDir??o.outDir,timeoutMs:n.timeoutMs??p.timeoutMs??_e,signal:c}),f=[];for(let w of y.workloads){let S=y.measurements.find((O)=>O.workloadId===w.id&&O.phase==="timing");if(!S)continue;if(s)S.warnings.push(s);f.push({workload:w,status:"executed",run:S,harnessFailed:!1})}return f}async function $n(o,n,s,c,d){let p=ae(n.command,n.label,{prepare:n.prepare,timeSource:n.timeSource}),y=i({samples:o.samples??null,budgetMs:o.budgetMs??null,minSamples:o.minSamples??null,warmup:o.warmup}),f=n.inputs!==void 0&&typeof n.prepare!=="function",w=Re({workloadId:p.id,phase:"timing",configFingerprint:y,bunVersion:Bun.version,toolVersion:ie,instrumented:!1,inputsDigest:await Ne(n.inputs??[])}),S=s||!f?void 0:await Ie(o.outDir,w),O;if(S)O=S;else{let R=await sn({argv:n.command,samples:o.samples,budgetMs:o.budgetMs,minSamples:o.minSamples,warmup:o.warmup,prepare:n.prepare,timeSource:n.timeSource,timeoutMs:n.timeoutMs??_e,ignoreExitCodes:n.ignoreExitCodes,signal:d});if(O=U({workload:p,configFingerprint:y,trials:R.trials,timing:R.timing,warnings:c?[...R.warnings,c]:R.warnings}),f&&!d?.aborted)await Be(o.outDir,w,O)}return{workload:p,status:S?"cached":"executed",run:O,harnessFailed:fe(O,n.ignoreExitCodes)}}async function Se(o,n,s){let{environment:c,noiseWarning:d}=v(o.noiseCheck),p=[];for(let w of o.workloads){if(s?.aborted)break;if(w.suites)p.push(...await xn(o,w,d,s));else p.push(await $n(o,w,n,d,s))}let y=s?.aborted??!1,f=p.at(-1)?.run;if(y&&f&&!f.warnings.some((w)=>w.code==="aborted"))f.warnings=[...f.warnings,re()];return{results:p,environment:c,aborted:y}}async function Ue(o){let{config:n}=o,s=C(n,o.baselineName);if(!await Bun.file(s).exists())throw new be(s);let c=await a(s),{results:d,environment:p,aborted:y}=await Se(n,o.full,o.signal),f=d.map((E)=>({...E})),w=f.filter((E)=>E.status==="executed").length,S=t(f.map((E)=>E.workload),f.map((E)=>E.run),p);if(y)return{document:S,summary:{total:f.length,cached:f.length-w,executed:w,passed:0,regressed:0,missingBaseline:0,failed:0,results:f,unmatched:{baseOnly:[],candOnly:[]}},baseline:c,aborted:y};let O=Je(c,S,n.thresholds),R=[];for(let E of f){let ce=O.compare(E.workload.id);if(ce)E.comparison=ce,R.push(ce)}let N=f.length-R.length,M=R.filter((E)=>E.verdict==="pass").length,I=n.onMissingBaseline??(N===f.length?"fail":"warn");if(N>0&&I==="fail")throw new Le(s,N,f.length);let B=O.unmatched();return S.comparisons=R,S.unmatched={baseOnly:B.baseOnly.map((E)=>E.id),candOnly:B.candOnly.map((E)=>E.id)},S.comparisonSummary=ze(R,O.effectiveTimingPct),{document:S,summary:{total:f.length,cached:f.length-w,executed:w,passed:M,regressed:R.length-M,missingBaseline:N,failed:f.filter((E)=>E.harnessFailed).length,results:f,unmatched:B},baseline:c,aborted:!1}}function We(o){let n=[`${o.total} workloads`,`${o.cached} cached`,`${o.executed} executed`];if(o.missingBaseline>0)n.push(`${o.missingBaseline} skipped (no matching baseline workload)`);if(o.failed>0){let c=o.results.filter((d)=>d.harnessFailed).map((d)=>k(d.workload));n.push(`${o.failed} failed (harness error, a command exited non-zero or produced no samples: ${c.join(", ")})`)}let s=o.results.filter((c)=>c.comparison?.verdict==="fail").map((c)=>{let d=c.comparison.timing,p=k(c.workload);return d?`${r(d.medianDeltaPct)} median on ${p}`:p});return n.push(`${o.passed} passed ${o.regressed} regressed${s.length>0?` (${s.join(", ")})`:""}`,"",`Profile CI: ${o.regressed>0||o.failed>0?"\u2717":"\u2713"}`),`${n.join(`
|
|
4
|
+
`)}
|
|
5
|
+
`}async function Ye(o,n){let{results:s,environment:c}=await Se(o,!0),d=t(s.map((y)=>y.workload),s.map((y)=>y.run),c),p=C(o,n);return await u(d,p),p}async function Qe(o){let n;try{n=(await Array.fromAsync(new Bun.Glob("*.json").scan({cwd:o.baselineDir}))).map((d)=>d.replace(/\.json$/,"")).sort()}catch{return[]}return(await Promise.all(n.map(async(c)=>{let d=C(o,c);try{let p=await a(d);return{name:c,path:d,createdAt:p.createdAt,toolVersion:p.toolVersion,bunVersion:p.bunVersion,workloads:p.workloads.length,...p.git!==void 0&&{git:p.git}}}catch{return}}))).filter((c)=>c!==void 0)}class j extends Error{}var Xe=["table","json","jsonl","markdown","minimal"],we=["collapsed","mermaid","speedscope","cpuprofile"],en=[...Xe,...we];function An(o,n,s={}){if(s.allowAuto&&n==="auto")return qe();let c=s.min??1,d=n?.trim()===""?Number.NaN:Number(n);if(!Number.isInteger(d)||d<c)throw new j(`Invalid ${o} "${n}": expected an integer \u2265 ${c}${s.allowAuto?' (or "auto")':""}`);return d}function Sn(o,n){return n.split(",").map((s)=>{let c=/^\d+$/.test(s)?Number(s):Number.NaN;if(!(c<=255))throw new j(`Invalid ${o} "${n}": expected exit codes 0-255, comma-separated`);return c})}var Y=(o,n)=>({dest:o,spec:n}),h={str:(o)=>Y(o,{kind:"string"}),int:(o,n=1,s=!1)=>Y(o,{kind:"int",min:n,allowAuto:s}),num:(o,n)=>Y(o,{kind:"number",min:n}),on:(o)=>Y(o,{kind:"bool",value:!0}),off:(o)=>Y(o,{kind:"bool",value:!1}),oneOf:(o,n)=>Y(o,{kind:"enum",values:n}),list:(o)=>Y(o,{kind:"list"}),words:(o)=>Y(o,{kind:"words"}),exitCodes:(o)=>Y(o,{kind:"exitCodes"})},Pe={format:"table",quiet:!1,help:!1},ue={...Pe,noiseCheck:!0},Ce={"--help":h.on("help"),"-h":h.on("help")},ke={...Ce,"--export-json":h.str("exportJson"),"--format":h.oneOf("format",Xe),"--quiet":h.on("quiet")},ve={"--no-noise-check":h.off("noiseCheck")},oe={"--config":h.str("config")},Oe={...ke,...ve,"--timeout":h.int("timeoutMs"),"--out-dir":h.str("outDir")},Te={...Oe,...oe,"--filter":h.str("filter"),"--preload":h.list("preload"),"--bun-flags":h.words("bunFlags")};function X(o,{command:n,flags:s,args:c,positional:d,rest:p}){let y=c;for(let f=0;f<o.length;f++){let w=o[f];if(w==="--"&&p){p(o.slice(f+1),c);break}let S=w.startsWith("--")?w.indexOf("="):-1,O=S>0?w.slice(0,S):w,R=Object.hasOwn(s,O)?s[O]:void 0;if(!R||S>0&&R.spec.kind==="bool"){if(w.startsWith("-")||!d)throw new j(`Unknown flag "${w}" for "ostia ${n}".`);d(w,c);continue}let N=()=>{if(S>0)return w.slice(S+1);if(f+1>=o.length)throw new j(`Missing value for ${O}.`);return o[++f]},{dest:M,spec:I}=R;switch(I.kind){case"string":y[M]=N();break;case"int":y[M]=An(O,N(),I);break;case"number":{let B=N(),E=Number(B);if(B===""||!Number.isFinite(E)||E<I.min)throw new j(`Invalid ${O} "${B}": expected a number \u2265 ${I.min}`);y[M]=E;break}case"bool":y[M]=I.value;break;case"list":y[M].push(N());break;case"words":y[M].push(...K(N()));break;case"exitCodes":y[M].push(...S>0?Sn(O,w.slice(S+1)):Array.from({length:255},(B,E)=>E+1));break;case"enum":{let B=N();if(!I.values.includes(B))throw new j(`Invalid ${O} "${B}": expected one of: ${I.values.join(", ")}`);y[M]=B;break}}}return c}var L=(o)=>Bun.write(Bun.stdout,o),ln=(o)=>Bun.write(Bun.stderr,o);class D extends Error{code;data;constructor(o,n,s){super(n);this.code=o;this.data=s}}async function ye(o,n,s){try{return await s()}catch(c){if(c instanceof D)throw c;if(c instanceof l)throw new D("invalid-flag",`${n}: ${c.message}`);throw new D(o,`${n}: ${T(c)}`)}}var cn=new Set(["minimal","json","jsonl"]);async function Pn(){let{isatty:o}=await import("tty");if(!o(2))return!0;let n=process.argv.slice(2);return n.some((s,c)=>s==="--format"&&cn.has(n[c+1]??"")||s.startsWith("--format=")&&cn.has(s.slice(9)))}async function ee(o,n,s){if(await ln(n.endsWith(`
|
|
6
|
+
`)?n:`${n}
|
|
7
|
+
`),!await Pn())return;await ln(`${JSON.stringify({event:"error",protocolVersion:x,code:o,message:n.split(`
|
|
8
|
+
`)[0],...s&&{data:s}})}
|
|
9
|
+
`)}async function W(o,n){return await L(o),n?0:2}async function se(o,n){if(o.text)await L(o.text);if(!o.files||o.files.length===0)return;if(n)for(let s of o.files){let c=s.path?`${n}/${s.path}`:n;await Bun.write(c,s.content),await L(`wrote ${c}
|
|
10
|
+
`)}else if(o.files.length===1)await L(o.files[0].content);else for(let s of o.files)await L(`--- ${s.path??"(unnamed)"} ---
|
|
11
|
+
${s.content}
|
|
12
|
+
`)}async function Fe(o,{exportJson:n,format:s,quiet:c}){let d=s==="json"&&!c?q(o):void 0;if(n)if(d!==void 0)await Ze(d,n);else await u(o,n);return d}async function le(o,n,s={}){let c=await Fe(o,n);if(n.quiet)return;if(c!==void 0)await L(c);else await se(await g[n.format].render(o,s))}var dn=(o)=>o==="--help"||o==="-h";async function xe(o,n){let s=new AbortController,c=()=>s.abort();process.on("SIGINT",c);try{return{result:await ye("spawn-failed",o,()=>n(s.signal)),aborted:s.signal.aborted}}finally{process.off("SIGINT",c)}}async function $e(o){let n=await on(o);if(!n&&o!==void 0)throw new D("config-missing",`Config file ${o} not found.`);return n}async function Ae(o,n){let s=await $e(n);if(!s){let c=ne.join(" or ");throw new D("config-missing",o?`No ${c} found. "${o}" needs configured workloads.`:`No ${c} found.`)}if(o&&s.workloads.length===0){let c=n??await Ke()??ne[1];throw new D("config-missing",`${c} has no "workloads" configured.`)}return s}function fn(o){return o.matched===0?2:o.verdict==="fail"?1:0}var vn=["ns","us","ms","s"],On={...Oe,"--samples":h.int("samples"),"--budget":h.int("budgetMs"),"--min-samples":h.int("minSamples"),"--warmup":h.int("warmup",0),"--no-interleave":h.off("interleave"),"--prepare":h.list("prepare"),"--time-source":h.str("timeSource"),"--time-unit":h.oneOf("timeUnit",vn),"--cpu":h.on("cpu"),"--heap":h.on("heap"),"--cpu-interval":h.int("cpuIntervalUs"),"--ignore-failure":h.exitCodes("ignoreExitCodes")};function Tn(o){let n={...ue,commands:[],argvCommand:[],prepare:[],interleave:!0,cpu:!1,heap:!1,ignoreExitCodes:[]};return X(o,{command:"time",flags:On,args:n,positional:(s,c)=>c.commands.push(s),rest:(s,c)=>{c.argvCommand=s}})}async function Fn(o){let n=Tn(o),s=[...n.commands,...n.argvCommand.length>0?[n.argvCommand]:[]];if(n.help||s.length===0)return W(nn,n.help);if(n.prepare.length>1&&n.prepare.length!==s.length)throw new D("invalid-flag",`--prepare given ${n.prepare.length} times for ${s.length} command(s): give it once (applies to all) or once per command.`);if(n.timeUnit!==void 0&&n.timeSource===void 0)throw new D("invalid-flag","--time-unit only applies with --time-source.");if(n.cpuIntervalUs!==void 0&&!n.cpu)throw new D("invalid-flag","--cpu-interval only applies with --cpu.");if(n.timeSource!==void 0)try{new RegExp(n.timeSource)}catch(S){throw new D("invalid-flag",`Invalid --time-source regex: ${T(S)}`)}let c=n.timeSource!==void 0?{pattern:n.timeSource,unit:n.timeUnit}:void 0,d=s.map((S,O)=>({command:S,prepare:n.prepare.length===1?n.prepare[0]:n.prepare[O]})),{result:p,aborted:y}=await xe("Run failed",(S)=>he({commands:d,timeSource:c,samples:n.samples,budgetMs:n.budgetMs,minSamples:n.minSamples,warmup:n.warmup,interleave:n.interleave,cpu:n.cpu,heap:n.heap,cpuIntervalUs:n.cpuIntervalUs,timeoutMs:n.timeoutMs,ignoreExitCodes:n.ignoreExitCodes,outDir:n.outDir,noiseCheck:n.noiseCheck,signal:S}));if(await le(p,n),y)return 130;let f=p.workloads.map((S)=>p.measurements.find((O)=>O.workloadId===S.id&&O.phase==="timing")),w=f.some((S)=>S?.timing&&fe(S,n.ignoreExitCodes));if(w||f.some((S)=>!S?.timing)){let S=(R)=>p.measurements.some((N)=>N.warnings.some((M)=>M.code===R)),O=w?"command-failed":S("timeout")?"timeout":S("time-source-no-match")?"time-source-no-match":"command-failed";throw new D(O,"One or more commands failed to produce a clean measurement; see the report above for details.")}return 0}var En={...Te,"--budget":h.int("budgetMs"),"--samples":h.int("samples"),"--min-samples":h.int("minSamples"),"--jobs":h.int("jobs",1,!0),"--gc":h.on("gc"),"--no-gc":h.off("gc"),"--cpu":h.on("cpu"),"--no-cpu":h.off("cpu"),"--cpu-interval":h.int("cpuIntervalUs"),"--alloc":h.on("alloc"),"--no-alloc":h.off("alloc"),"--peak-mem":h.on("peakMem"),"--no-peak-mem":h.off("peakMem"),"--isolate":h.on("isolate"),"--no-isolate":h.off("isolate")};function Mn(o){let n={...ue,suites:[],preload:[],bunFlags:[]};return X(o,{command:"bench",flags:En,args:n,positional:(s,c)=>c.suites.push(s)})}async function Rn(o){let n=Mn(o);if(n.help)return W(je,!0);let s=await $e(n.config),c=await te(n,s?.bench);if(c.suites.length===0)return W(je,!1);let{result:d,aborted:p}=await xe("Bench failed",(y)=>z({...c,signal:y}));return await le(d,n),p?130:0}var Nn={...Te,"--base":h.str("base"),"--base-setup":h.list("baseSetup"),"--rounds":h.int("rounds",3),"--threshold":h.num("thresholdPct",0),"--geomean-threshold":h.num("geomeanThresholdPct",0),"--confirm":h.int("confirm",0),"--alloc":h.on("alloc"),"--no-alloc":h.off("alloc"),"--peak-mem":h.on("peakMem"),"--no-peak-mem":h.off("peakMem"),"--mem-threshold":h.num("memThresholdPct",0),"--keep-trees":h.int("keepTrees"),"--clean":h.on("clean"),"--progress":h.on("progress"),"--no-progress":h.off("progress")};function Dn(o){switch(o.phase){case"setup":return`[ab] base setup: ${o.command}`;case"measure":return`[ab] suite ${o.suite}/${o.suites} ${o.file} \xB7 pass ${o.pass}/${o.passes} \xB7 task ${o.task}/${o.tasks} ${o.label}`;case"confirm":return`[ab] confirming flagged tasks \xB7 repeat ${o.repeat}/${o.repeats} ${o.label}`;case"memory":return`[ab] memory \xB7 process ${o.run}/${o.runs} ${b(o.side)} ${o.label}`}}function In(o){let n=!1;return{onProgress:(s)=>{let c=Dn(s);if(!o){Ee(2,`${c}
|
|
13
|
+
`);return}let d=process.stderr.columns||80;Ee(2,`\r\x1B[K${c.slice(0,d-1)}`),n=!0},done:()=>{if(n)Ee(2,"\r\x1B[K");n=!1}}}function Bn(o){let n={...ue,suites:[],baseSetup:[],preload:[],bunFlags:[],clean:!1};return X(o,{command:"ab",flags:Nn,args:n,positional:(s,c)=>c.suites.push(s)})}async function _n(o){let n=Bn(o);if(n.help)return W(He,!0);let s=await $e(n.config);if(n.clean){let H=await an({outDir:n.outDir??s?.bench?.outDir});if(!n.quiet)await Bun.write(Bun.stderr,`Removed ${H} cached base tree(s).
|
|
14
|
+
`);return 0}let{suites:c,filter:d,preload:p,bunFlags:y,timeoutMs:f,outDir:w,noiseCheck:S}=await te(n,s?.bench);if(c.length===0)return W(He,!1);let{isatty:O}=await import("tty"),R=O(2),N=n.progress??(R&&!n.quiet)?In(R):void 0,{result:M,aborted:I}=await xe("A/B run failed",async(H)=>{try{return await ge({suites:c,base:n.base,baseSetup:n.baseSetup.length>0?n.baseSetup:s?.ab?.setup,rounds:n.rounds,thresholdPct:n.thresholdPct,geomeanThresholdPct:n.geomeanThresholdPct,confirm:n.confirm,alloc:n.alloc??s?.ab?.alloc,peakMem:n.peakMem??s?.ab?.peakMem,memThresholdPct:n.memThresholdPct??s?.ab?.memThresholdPct,filter:d,preload:p,bunFlags:y,timeoutMs:f,outDir:w,keepTrees:n.keepTrees??s?.ab?.keepTrees,noiseCheck:S,signal:H,onProgress:N?.onProgress})}catch(Z){if(Z instanceof F)throw new D("invalid-flag",`--base: ${Z.message}`);if(Z instanceof V)throw new D("command-failed",Z.message);if(Z instanceof G)throw new D("suite-failed",Z.message,{side:Z.side});throw Z}finally{N?.done()}}),B=M.ab,E=I?130:fn({matched:B.matched+B.threw,verdict:B.verdict}),ce=new Map(M.workloads.map((H)=>[H.id,H])),Me=(H=[])=>H.flatMap((Z)=>ce.get(Z)??[]);if(await le(M,n,{protocol:{command:"ab",exitCode:E,unmatched:{baseOnly:Me(M.unmatched?.baseOnly),candOnly:Me(M.unmatched?.candOnly)},...n.exportJson&&{exportedTo:n.exportJson}}}),E===2){let H=B.newSuites?.length===c.length;await ee("no-matches",H?`No suite exists at ${B.base.ref}; nothing was paired. Use "ostia bench" to time new suites.`:`No task exists both at ${B.base.ref} and in the working tree; nothing was paired.`)}return E}var Ln={...ke,...oe,"--baseline":h.str("baseline")};function Un(o){let n={...Pe,paths:[]};return X(o,{command:"compare",flags:Ln,args:n,positional:(s,c)=>c.paths.push(s)})}async function Wn(o){let n=Un(o);if(n.help)return W(Ge,!0);let s=n.baseline?1:2;if(n.paths.length>s)throw new j(n.baseline?`"ostia compare --baseline" takes one candidate path, got ${n.paths.length}.`:`"ostia compare" takes two document paths, got ${n.paths.length}.`);let[c,d]=n.baseline?[n.baseline,n.paths[0]]:n.paths;if(!c||!d)return W(Ge,!1);let[p,y]=await ye("document-load-failed","Failed to load documents",()=>Promise.all([a(c),a(d)])),f=await $e(n.config),w=f?.thresholds??P,S=f?n.config??await Ke()??ne[1]:"defaults",O=pe(p,y,w),R={...y,comparisons:O.comparisons,comparisonSummary:O.summary,unmatched:{baseOnly:O.unmatched.baseOnly.map((I)=>I.id),candOnly:O.unmatched.candOnly.map((I)=>I.id)}},N=!n.quiet&&(n.format==="table"||n.format==="markdown");if(N){if(await L(`thresholds: ${S}
|
|
15
|
+
`),p.git&&y.git)await L(`base ${_(p.git)} \u2192 cand ${_(y.git)}
|
|
16
|
+
`);if(n.format==="table"&&O.summary.effectiveTimingPct>w.timingPct)await L(`threshold ${w.timingPct}% (widened to ${O.summary.effectiveTimingPct.toFixed(1)}% by noise floor)
|
|
17
|
+
`)}let M=fn(O.summary);if(await le(R,n,{protocol:{command:"compare",exitCode:M,unmatched:O.unmatched,baseGit:p.git,candGit:y.git,...n.exportJson&&{exportedTo:n.exportJson}}}),N&&(O.unmatched.baseOnly.length>0||O.unmatched.candOnly.length>0))await L(Gn(O.unmatched,n.format==="markdown"));if(O.summary.matched===0)await ee("no-matches","No workload matched between base and candidate; nothing was compared.");return M}function Gn(o,n){let s=(p)=>p.map((y)=>k(y)).join(", "),c=n?"- ":" ",d=n?["### Unmatched",""]:["","Unmatched:"];if(o.baseOnly.length>0)d.push(`${c}baseline only: ${s(o.baseOnly)}`);if(o.candOnly.length>0)d.push(`${c}candidate only: ${s(o.candOnly)}`);if(n)d.push("");return`${d.join(`
|
|
18
|
+
`)}
|
|
19
|
+
`}var pn={...Ce,"--format":h.oneOf("format",en),"--measurement":h.str("measurementId"),"--out-dir":h.str("outDir")};function jn(o){return X(o,{command:"report",flags:pn,args:{format:"table",help:!1},positional:(s,c)=>{if(c.path!==void 0)throw new j(`"ostia report" takes exactly one document path, got "${c.path}" and "${s}".`);c.path=s}})}var mn="No CPU evidence in this document; rerun with --cpu (ostia time) or --cpu (ostia bench)";async function gn(o,{format:n,measurementId:s,outDir:c}){let d=we.includes(n),p=[s!==void 0&&"--measurement",c!==void 0&&"--out-dir"].filter((w)=>w!==!1);if(!d&&p.length>0)throw new j(`${p.join(" and ")} only apply to the visualization formats (${we.join(", ")}), not --format ${n}.`);let y=await ye("document-load-failed",`Failed to load ${o}`,()=>a(o));if(d&&!s&&!y.measurements.some((w)=>w.phase==="cpu"))throw new D("no-cpu-evidence",mn);let f=await g[n].render(y,{measurementId:s});if(!f.text&&(!f.files||f.files.length===0))throw new D("no-cpu-evidence",s?`No CPU evidence found for measurement "${s}".`:mn);return await se(f,c),0}async function Hn(o){let n=jn(o);if(n.help||!n.path)return W(Ve,n.help);return gn(n.path,n)}var Vn={...ke,...ve,...oe,"--full":h.on("full"),"--baseline":h.str("baseline"),"--save-baseline":h.on("saveBaseline")};function qn(o){let n={...ue,full:!1,saveBaseline:!1};return X(o,{command:"ci",flags:Vn,args:n})}var hn=(o)=>new D("baseline-missing",o.message);function Jn(o){if(o instanceof me)throw hn(o);if(o instanceof A)throw new D("document-load-failed",o.message);throw o}async function Kn(o){let n=qn(o);if(n.help)return W(tn,!0);let s=await Ae("ostia ci",n.config),c=n.noiseCheck?s:{...s,noiseCheck:!1},d=n.full||n.saveBaseline,{result:p,aborted:y}=await xe("CI run failed",(M)=>Ue({config:c,full:d,baselineName:n.baseline,signal:M}).catch(Jn)),{document:f,summary:w}=p;if(y)return await le(f,n),130;let S=n.baseline??c.baseline,O=C(c,S),R=await Fe(f,n);if(n.saveBaseline&&w.regressed===0&&w.failed===0)await u(f,O);let N=w.failed>0?2:w.regressed>0?1:0;if(w.failed>0){let M=w.results.filter((I)=>I.harnessFailed).map((I)=>k(I.workload));await ee("command-failed",`${w.failed} workload(s) failed (harness error, a command exited non-zero or produced no samples): ${M.join(", ")}`)}if(n.quiet)return N;if(R!==void 0)await L(R);else if(n.format==="table"){if(await L(We(w)),f.comparisons&&f.comparisons.length>0)await se(await g.table.render(f,{}))}else if(n.format==="markdown")await L(`## ostia ci
|
|
20
|
+
|
|
21
|
+
Baseline: \`${S}\` (\`${O}\`) \xB7 ${w.cached} cached, ${w.executed} executed
|
|
22
|
+
|
|
23
|
+
`),await se(await g.markdown.render(f,{}));else await se(await g[n.format].render(f,{protocol:{command:"ci",exitCode:N,unmatched:w.unmatched,baseGit:p.baseline.git,candGit:f.git,baseline:{name:S,path:O},cached:w.cached,executed:w.executed,failed:w.failed,missingBaseline:w.missingBaseline,...n.exportJson&&{exportedTo:n.exportJson}}}));return N}var un=/^[A-Za-z0-9._-]+$/;function de(o){return new D("invalid-flag",`${o}
|
|
24
|
+
Run 'ostia baseline --help'.`)}function bn(o){if(o.startsWith("-"))throw de(`Invalid baseline name "${o}": names can't start with "-".`);if(!un.test(o))throw de(`Invalid baseline name "${o}": expected to match ${un}.`)}var zn={...Ce,...oe};function wn(o,n,s){return X(o,{command:n,flags:zn,args:{help:!1},positional:(c,d)=>{if(!s)throw de(`"ostia ${n}" takes no arguments, got "${c}".`);if(d.name!==void 0)throw de(`"ostia ${n}" takes at most one name argument, got "${d.name}" and "${c}".`);d.name=c}})}async function Zn(o){let{name:n,help:s,config:c}=wn(o,"baseline save",!0);if(s)return W(Q,!0);if(n!==void 0)bn(n);let d=await Ae("ostia baseline save",c);return await L(`Wrote ${await Ye(d,n)}
|
|
25
|
+
`),0}async function Yn(o){let{help:n,config:s}=wn(o,"baseline list",!1);if(n)return W(Q,!0);let c=await Ae(void 0,s),d=await Qe(c);if(d.length===0)return await L(`No baselines found in ${c.baselineDir}.
|
|
26
|
+
`),0;for(let p of d){let y=p.git?` ${_(p.git)}`:"";await L(`${p.name} ${p.workloads} workloads created ${p.createdAt} toolVersion ${p.toolVersion}${y}
|
|
27
|
+
`)}return 0}async function Qn(o){let[n,...s]=o;if(!n||dn(n))return W(Q,!!n);bn(n);let c=X(s,{command:"baseline show",flags:{...pn,...oe},args:{format:"table",help:!1},positional:(y)=>{throw de(`"ostia baseline show" takes one name, got "${n}" and "${y}".`)}});if(c.help)return W(Ve,!0);let d=await Ae(void 0,c.config),p=C(d,n);if(!await Bun.file(p).exists())throw hn(new be(p));return gn(p,c)}async function Cn(o,n,s,c,d,p=""){if(o===void 0||dn(o))return W(c,o!==void 0);let y=Object.hasOwn(s,o)?s[o]:void 0;try{if(!y)throw new D("invalid-flag",d(o));return await y(n)}catch(f){if(f instanceof D)await ee(f.code,f.message,f.data);else if(f instanceof m)await ee("config-invalid",f.message);else if(f instanceof j)await ee("invalid-flag",`${f.message}
|
|
28
|
+
Run 'ostia ${p}${o} --help'.`);else await ee("internal",`Internal error: ${f instanceof Error?f.stack??f.message:String(f)}`);return 2}}function Xn(o){let[n,...s]=o;return Cn(n,s,{save:Zn,list:Yn,show:Qn},Q,(c)=>`Unknown "ostia baseline ${c}". Run "ostia baseline --help".`,"baseline ")}function Kt(){let[o,...n]=process.argv.slice(2);return Cn(o,n,{time:Fn,bench:Rn,ab:_n,compare:Wn,report:Hn,ci:Kn,baseline:Xn},rn,(s)=>`Unknown subcommand "${s}". Run "ostia --help".`)}export{Kt as main};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{W}from"./chunk-jzfv4c07.js";import{heapStats as y}from"bun:jsc";function j(){let{heapSize:n,extraMemorySize:e}=y();return{rssBytes:process.memoryUsage.rss(),liveBytes:n+e}}var s={ready:1,go:2,sampling:3,stop:4},g=`self.onmessage = (event) => {
|
|
3
|
+
const slots = new Int32Array(event.data)
|
|
4
|
+
Atomics.store(slots, 0, ${s.ready})
|
|
5
|
+
Atomics.wait(slots, 0, ${s.ready})
|
|
6
|
+
if (Atomics.compareExchange(slots, 0, ${s.go}, ${s.sampling}) === ${s.go}) {
|
|
7
|
+
while (Atomics.load(slots, 0) === ${s.sampling}) {
|
|
8
|
+
const kib = (process.memoryUsage.rss() / 1024) | 0
|
|
9
|
+
if (kib > Atomics.load(slots, 1)) Atomics.store(slots, 1, kib)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
postMessage(0)
|
|
13
|
+
}`,d=1e4;async function H(n=g){let e=new Int32Array(new SharedArrayBuffer(8)),r=new Worker(URL.createObjectURL(new Blob([n],{type:"application/javascript"}))),a=new Promise((t,c)=>{let o=(m)=>c(Error(`RSS sampler worker failed: ${m}`));r.onerror=(m)=>o(m.message||"uncaught error"),r.addEventListener("close",()=>o("worker exited unexpectedly"))});a.catch(()=>{});let i=new Promise((t)=>{r.onmessage=t});try{r.postMessage(e.buffer);let t=Date.now()+d;while(Atomics.load(e,0)!==s.ready){if(Date.now()>t)throw Error("RSS sampler worker failed: did not start in time");await Promise.race([Bun.sleep(1),a])}}catch(t){throw r.terminate(),t}return{async during(t){try{Atomics.store(e,0,s.go),Atomics.notify(e,0);let c=Bun.nanoseconds()+50000000;while(Atomics.load(e,0)!==s.sampling&&Bun.nanoseconds()<c);let o=Date.now()+d;while(Atomics.load(e,0)!==s.sampling){if(Date.now()>o)throw Error("RSS sampler worker failed: did not start sampling");await Promise.race([Bun.sleep(1),a])}return await t(),Atomics.store(e,0,s.stop),await Promise.race([i,a]),Atomics.load(e,1)*1024}finally{r.terminate()}}}}async function ee(n,e,r){let a=r??await H();Bun.gc(!0);let i=j(),t=process.resourceUsage().maxRSS*1024,c=Bun.nanoseconds(),o=0,m=await a.during(async()=>{let p=n();if(W(p))await p;o=Bun.nanoseconds()-c}),l=process.resourceUsage().maxRSS*1024,w=Math.max(m,l>t?l:0),u=e?i.rssBytes-e.rssBytes-(i.liveBytes-e.liveBytes):0;return{peakBytes:Math.max(0,w-i.rssBytes),slackBytes:Math.max(0,u),wallNs:o}}
|
|
14
|
+
export{j,H,ee};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{w,o,Ee,Ne}from"./chunk-jzfv4c07.js";function b(n){return n==="cand"?"candidate":n}class oe extends Error{side;constructor(n,t){super(t instanceof Error?t.message:String(t),{cause:t});this.side=n}}async function M(n,t){try{return await t()}catch(r){throw r instanceof oe?r:new oe(n,r)}}var _=15,N=1e7,U=1e6,v=3;async function cn(n,t,r={}){let i=r.rounds??_,{signal:s}=r;s?.throwIfAborted();let m=Bun.nanoseconds(),c=r.candFirst?["cand","base"]:["base","cand"],p=c.toReversed(),g={base:n,cand:t},f={};for(let e of c)f[e]=await M(e,()=>Ne(g[e]));let A=Bun.deepEquals(f.base.result,f.cand.result),S=(e)=>{let a=Ee(g[e],f[e].isAsync);return(d)=>M(e,()=>a(d))},l={base:S("base"),cand:S("cand")},C=async(e)=>{for(let a=1;;a*=2){s?.throwIfAborted();let d=await e(a);if(d>=U)return Math.max(1,d/a)}},h={base:0,cand:0};for(let e of c)h[e]=await C(l[e]);let u=Math.max(1,Math.round(N/Math.max(h.base,h.cand)));for(let e=0;e<v;e++){s?.throwIfAborted();for(let a of c)await l[a](u)}let x=[],y=[],T=[];for(let e=0;e<i;e++){s?.throwIfAborted();let a={base:0,cand:0};for(let R of e%2===0?c:p)a[R]=await l[R](u);let d=Math.max(1,a.base),P=Math.max(1,a.cand);x.push(d/u),y.push(P/u),T.push(P/d)}return{rounds:i,batch:u,baseSamples:x,candSamples:y,ratios:T,sameOutput:A,diagnosticWallNs:Bun.nanoseconds()-m}}function O(n){return!(n.suiteChanged&&!n.sameOutput)}function un(n,t,r,i){let s=t-n,m=Math.abs(s)>Math.max(i,n*r/100);return{baseBytes:n,candBytes:t,verdict:!m?"unchanged":s>0?"regressed":"improved",floorBytes:i}}function se(n,t){let r=w(n),i=o(r,0.5),s=o(r,0.25),m=o(r,0.75),c=t/100,p=i>1+c&&s>1?"regressed":i<1-c&&m<1?"improved":void 0;return{medianRatio:i,ratioP25:s,ratioP75:m,...p&&{flagged:p}}}
|
|
3
|
+
export{b,oe,cn,O,un,se};
|
package/cli.js
CHANGED
|
@@ -1,226 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
|
|
4
|
-
`)}var $e=600000;class me extends Error{path;constructor(r){super(`No baseline document at ${r}. Create one with: ostia baseline save ${r.split("/").pop().replace(/\.json$/,"")}`);this.path=r}}class de extends Error{path;missing;total;constructor(r,n,c){super(`${n} of ${c} configured workload(s) have no matching row in baseline ${r} (by workload id). Re-seed with: ostia baseline save`);this.path=r;this.missing=n;this.total=c}}function qe(r,n,c){if(r)return r;return n===c?"fail":"warn"}async function ge(r,n){let c=r.noiseCheck===!1?void 0:u(),p=c?m(c):void 0,h=[];for(let g of r.workloads){if(g.suites){let F=await U(g.suites,process.cwd()),K=await z({suites:F,preload:[],bunFlags:[],noiseCheck:!1},r.bench),Z=await D({...K,outDir:K.outDir??r.outDir,timeoutMs:K.timeoutMs??$e});for(let X of Z.workloads){let E=Z.measurements.find((te)=>te.workloadId===X.id&&te.phase==="timing");if(!E)continue;if(p)E.warnings.push(p);h.push({workload:X,status:"executed",run:E,harnessFailed:!1})}continue}let x=_(g.command,g.label,{prepare:g.prepare,timeSource:g.timeSource}),b=await ye(g.inputs??[]),k=i({samples:r.samples??null,budgetMs:r.budgetMs??null,minSamples:r.minSamples??null,warmup:r.warmup}),q=g.inputs!==void 0&&typeof g.prepare!=="function",j=ve({workloadId:x.id,phase:"timing",configFingerprint:k,bunVersion:Bun.version,toolVersion:S,instrumented:!1,inputsDigest:b}),M=n||!q?void 0:await xe(r.outDir,j),P,y;if(M)P=M,y="cached";else{let F=await le({argv:g.command,samples:r.samples,budgetMs:r.budgetMs,minSamples:r.minSamples,warmup:r.warmup,prepare:g.prepare,timeSource:g.timeSource,timeoutMs:g.timeoutMs??$e,ignoreExitCodes:g.ignoreExitCodes});if(P=C({workload:x,configFingerprint:k,trials:F.trials,timing:F.timing,warnings:p?[...F.warnings,p]:F.warnings}),q)await Pe(r.outDir,j,P);y="executed"}h.push({workload:x,status:y,run:P,harnessFailed:J(P,g.ignoreExitCodes)})}return{results:h,environment:c}}async function Ae(r){let{config:n}=r,c=l(n,r.baselineName);if(!await Bun.file(c).exists())throw new me(c);let h=await o(c),{results:g,environment:x}=await ge(n,r.full),b=g.map((E)=>({...E})),k=b.filter((E)=>E.status==="executed").length,q=b.length-k,j=b.filter((E)=>E.harnessFailed).length,M=s(b.map((E)=>E.workload),b.map((E)=>E.run),x),P=0,y=0,F=0,K=re(h,M,n.thresholds);for(let E of b){let te=K.compare(E.workload.id);if(!te){F++;continue}if(E.comparison=te,te.verdict==="pass")P++;else y++}if(F>0){if(qe(n.onMissingBaseline,F,b.length)==="fail")throw new de(c,F,b.length)}let Z=b.map((E)=>E.comparison).filter((E)=>E!==void 0);M.comparisons=Z;let X=K.unmatched();return M.unmatched={baseOnly:X.baseOnly.map((E)=>E.id),candOnly:X.candOnly.map((E)=>E.id)},M.comparisonSummary=se(Z,K.effectiveTimingPct),{document:M,summary:{total:b.length,cached:q,executed:k,passed:P,regressed:y,missingBaseline:F,failed:j,results:b,unmatched:X},baseline:h}}function Me(r){let n=[];if(n.push(`${r.total} workloads`),n.push(`${r.cached} cached`),n.push(`${r.executed} executed`),r.missingBaseline>0)n.push(`${r.missingBaseline} skipped (no matching baseline workload)`);if(r.failed>0){let p=r.results.filter((h)=>h.harnessFailed).map((h)=>t(h.workload));n.push(`${r.failed} failed (harness error, a command exited non-zero or produced no samples: ${p.join(", ")})`)}let c=r.results.filter((p)=>p.comparison?.verdict==="fail").map((p)=>{let h=p.comparison.timing,g=t(p.workload);return h?`${h.medianDeltaPct>0?"+":""}${h.medianDeltaPct.toFixed(1)}% median on ${g}`:g});return n.push(`${r.passed} passed ${r.regressed} regressed${c.length>0?` (${c.join(", ")})`:""}`),n.push(""),n.push(`Profile CI: ${r.regressed>0||r.failed>0?"\u2717":"\u2713"}`),`${n.join(`
|
|
5
|
-
`)}
|
|
6
|
-
`}async function Se(r,n){let{results:c,environment:p}=await ge(r,!1),h=s(c.map((x)=>x.workload),c.map((x)=>x.run),p),g=l(r,n);return await a(h,g),g}async function Fe(r){let n=[];try{let p=new Bun.Glob("*.json");for await(let h of p.scan({cwd:r.baselineDir,absolute:!1}))n.push(h.replace(/\.json$/,""))}catch{return[]}return n.sort(),(await Promise.all(n.map(async(p)=>{let h=l(r,p);try{let g=await o(h);return{name:p,path:h,createdAt:g.createdAt,toolVersion:g.toolVersion,bunVersion:g.bunVersion,workloads:g.workloads.length,...g.git!==void 0&&{git:g.git}}}catch{return}}))).filter((p)=>p!==void 0)}function Y(r){return r instanceof Error?r.message:String(r)}var B=(r)=>Bun.write(Bun.stdout,r),Te=(r)=>Bun.write(Bun.stderr,r);class Q extends Error{}var Ie=[],Ee=new Set(["minimal","json","jsonl"]);async function Le(r){let{isatty:n}=await import("tty");if(!n(2))return!0;return r.some((c,p)=>c==="--format"&&Ee.has(r[p+1]??"")||c.startsWith("--format=")&&Ee.has(c.slice(9)))}async function T(r,n,c){if(await Te(n.endsWith(`
|
|
7
|
-
`)?n:`${n}
|
|
8
|
-
`),!await Le(Ie))return;await Te(`${JSON.stringify({event:"error",protocolVersion:w,code:r,message:n.split(`
|
|
9
|
-
`)[0],...c&&{data:c}})}
|
|
10
|
-
`)}async function L(r,n){return await B(r),n?0:2}async function Je(r,n){return await T("invalid-flag",`${r.message}
|
|
11
|
-
Run 'ostia ${n} --help'.`),2}function Ge(r,n,c={}){if(c.allowAuto&&n==="auto")return ie();let p=c.min??1,h=Number(n);if(!Number.isInteger(h)||h<p)throw new Q(`Invalid ${r} "${n}": expected an integer \u2265 ${p}${c.allowAuto?' (or "auto")':""}`);return h}var Ve=["ns","us","ms","s"],ne=["table","json","jsonl","markdown","minimal"],we=["collapsed","mermaid","speedscope","cpuprofile"],ze=[...ne,...we],be=`No CPU evidence in this document; rerun with --cpu (ostia time) or --cpu (ostia bench)
|
|
12
|
-
`;function Ne(r){return r.measurements.some((n)=>n.phase==="cpu")}async function pe(r,n,c={}){if(n.format==="json"&&n.exportJson){let p=I(r);if(await ee(p,n.exportJson),!n.quiet)await B(p);return 0}if(n.exportJson)await a(r,n.exportJson);if(n.quiet)return 0;if(we.includes(n.format)&&!Ne(r))return await T("no-cpu-evidence",be),2;return await ue(await d[n.format].render(r,c)),0}async function fe(r){let n=await W();if(!n){await T("config-missing",r?`No ostia.config.json found. "${r}" needs configured workloads.`:"No ostia.config.json found.");return}if(r&&n.workloads.length===0){let c=await oe()??"ostia.config.json";await T("config-missing",`${c} has no "workloads" configured.`);return}return n}async function ue(r,n){if(r.text)await B(r.text);if(!r.files||r.files.length===0)return;if(n)for(let c of r.files){let p=c.path?`${n}/${c.path}`:n;await Bun.write(p,c.content),await B(`wrote ${p}
|
|
13
|
-
`)}else if(r.files.length===1)await B(r.files[0].content);else for(let c of r.files)await B(`--- ${c.path??"(unnamed)"} ---
|
|
14
|
-
${c.content}
|
|
15
|
-
`)}var Ke=`ostia time [flags] <command...>
|
|
16
|
-
ostia time [flags] -- <argv...>
|
|
17
|
-
|
|
18
|
-
Time commands as subprocesses. Each <command> is whitespace-split into argv (no shell);
|
|
19
|
-
everything after -- is one more command's argv, verbatim.
|
|
20
|
-
|
|
21
|
-
Flags:
|
|
22
|
-
--samples N exact trials per command (default: ~3s budget, at least 10)
|
|
23
|
-
--budget MS sampling time budget per command
|
|
24
|
-
--min-samples N floor on trials when --samples isn't given
|
|
25
|
-
--warmup N discarded warmup trials (default: 3)
|
|
26
|
-
--no-interleave run commands one after another instead of round-robin
|
|
27
|
-
--prepare CMD run before every trial, unmeasured; once, or once per command
|
|
28
|
-
--time-source REGEX take each trial's time from capture group 1 in the command's output
|
|
29
|
-
--time-unit UNIT unit of --time-source: ns | us | ms | s (default: ms)
|
|
30
|
-
--cpu capture one extra CPU-profile trial
|
|
31
|
-
--heap capture one extra heap-snapshot trial
|
|
32
|
-
--cpu-interval US CPU sampling interval (default: 1000)
|
|
33
|
-
--timeout MS kill a trial or --prepare hook after MS
|
|
34
|
-
--ignore-failure[=CODE,...] treat these exit codes (bare: all) as success
|
|
35
|
-
--out-dir PATH artifact directory (default: node_modules/.cache/ostia)
|
|
36
|
-
--no-noise-check skip the ~200ms noise-floor measurement
|
|
37
|
-
--export-json PATH write the ProfileDocument to PATH
|
|
38
|
-
--format FORMAT table | json | jsonl | markdown | minimal (default: table)
|
|
39
|
-
--quiet don't print the report
|
|
40
|
-
--help show this message
|
|
41
|
-
|
|
42
|
-
A command stops at its first non-ignored non-zero exit.
|
|
43
|
-
Exit codes: 0 ok, 2 a command failed or produced no samples (or a bad flag), 130 Ctrl-C.
|
|
44
|
-
|
|
45
|
-
Examples:
|
|
46
|
-
ostia time "bun a.ts" "bun b.ts"
|
|
47
|
-
ostia time --samples 25 --cpu --heap "bun src/server.ts"
|
|
48
|
-
ostia time --prepare "rm -rf dist" "bun build.ts"
|
|
49
|
-
ostia time --time-source "built in (\\d+)ms" "bun build.ts"
|
|
50
|
-
ostia time -- bun -e "console.log('a b')"
|
|
51
|
-
`,Xe=Array.from({length:255},(r,n)=>n+1),De=`ostia bench [flags] <suite.ts...>
|
|
52
|
-
|
|
53
|
-
Run in-process group()/task() suites, each file in its own child process. With no files,
|
|
54
|
-
uses ostia.config's "bench" section; each flag overrides its config field.
|
|
55
|
-
|
|
56
|
-
Flags:
|
|
57
|
-
--budget MS sampling budget per task (default: 500)
|
|
58
|
-
--samples N exact trials per task (ignores --budget)
|
|
59
|
-
--min-samples N floor on trials (default: cost-aware, 3-20)
|
|
60
|
-
--jobs N|auto suite files at once (default: 1; >1 adds noise)
|
|
61
|
-
--isolate one process per task, not per file (--no-isolate to undo config)
|
|
62
|
-
--gc Bun.gc(true) between trials (--no-gc)
|
|
63
|
-
--cpu extra per-task CPU profile with JIT tiers, ~2,000 samples (--no-cpu)
|
|
64
|
-
--cpu-interval US CPU sampling interval (default: 100)
|
|
65
|
-
--alloc extra per-task retained-heap-per-call measurement: what calls keep
|
|
66
|
-
alive after a full GC, not what they allocate (--no-alloc)
|
|
67
|
-
--peak-mem extra per-task RSS rise during the task's first call, garbage
|
|
68
|
-
included; median of 3 fresh processes (--no-peak-mem)
|
|
69
|
-
--filter REGEX only tasks whose "group/name" matches
|
|
70
|
-
--preload PATH import before each suite file (repeatable, in order)
|
|
71
|
-
--bun-flags FLAGS extra flags for the bun process running each suite (repeatable)
|
|
72
|
-
--timeout MS kill a suite (or isolated task) process after MS
|
|
73
|
-
--out-dir PATH scratch directory (default: node_modules/.cache/ostia)
|
|
74
|
-
--no-noise-check skip the ~200ms noise-floor measurement
|
|
75
|
-
--export-json PATH write the ProfileDocument to PATH
|
|
76
|
-
--format FORMAT table | json | jsonl | markdown | minimal (default: table)
|
|
77
|
-
--quiet don't print the report
|
|
78
|
-
--help show this message
|
|
79
|
-
|
|
80
|
-
Per-task/per-group options ({ budgetMs, minSamples, gc, isolate, cpu, alloc, peakMem }) override
|
|
81
|
-
the suite-wide defaults. Exit codes: 0 ok, 2 a suite failed (or a bad flag), 130 Ctrl-C.
|
|
82
|
-
|
|
83
|
-
Examples:
|
|
84
|
-
ostia bench bench/*.ts
|
|
85
|
-
ostia bench bench/*.ts --filter parse --cpu --alloc
|
|
86
|
-
ostia bench --preload ./bench/dom-setup.ts --bun-flags="--conditions=browser" bench/*.ts
|
|
87
|
-
`,Re=`ostia ab [flags] <suite.ts...>
|
|
88
|
-
|
|
89
|
-
Pair every task on a git ref's committed tree (base) against the working tree (candidate), in
|
|
90
|
-
one process, alternating ~10ms batches, and gate on the per-round time ratio. Drift between
|
|
91
|
-
the two sides cancels within a round. With no files, uses ostia.config's "bench" suites.
|
|
92
|
-
|
|
93
|
-
Flags:
|
|
94
|
-
--base REF git ref for the base side (default: HEAD)
|
|
95
|
-
--rounds N base/candidate rounds per task (default: 15)
|
|
96
|
-
--threshold PCT flag a task whose median ratio moves past PCT (default: 10)
|
|
97
|
-
--geomean-threshold PCT fail when the geometric mean of all ratios is slower than PCT
|
|
98
|
-
(default: 1.5)
|
|
99
|
-
--confirm N re-measure each flagged task in N fresh processes (default: 2; 0 skips)
|
|
100
|
-
--filter REGEX only tasks whose "group/name" matches
|
|
101
|
-
--preload PATH import before each suite file (repeatable, in order)
|
|
102
|
-
--bun-flags FLAGS extra flags for the bun process running each suite (repeatable)
|
|
103
|
-
--timeout MS kill a suite (or repeat) process after MS
|
|
104
|
-
--out-dir PATH scratch directory and base-tree cache (default: node_modules/.cache/ostia)
|
|
105
|
-
--no-noise-check skip the ~200ms noise-floor measurement
|
|
106
|
-
--export-json PATH write the ProfileDocument to PATH
|
|
107
|
-
--format FORMAT table | json | jsonl | markdown | minimal (default: table)
|
|
108
|
-
--quiet don't print the report
|
|
109
|
-
--help show this message
|
|
110
|
-
|
|
111
|
-
A flagged task counts only when every fresh-process repeat flags it the same way. Exit codes:
|
|
112
|
-
0 pass, 1 a confirmed regression or the geomean over its threshold, 2 nothing paired or a
|
|
113
|
-
harness error (not a git repo, unknown ref, a suite failed), 130 Ctrl-C.
|
|
114
|
-
|
|
115
|
-
Examples:
|
|
116
|
-
ostia ab bench/parse.bench.ts
|
|
117
|
-
ostia ab bench/*.ts --base origin/main --rounds 21
|
|
118
|
-
ostia ab bench/*.ts --filter parse --format minimal
|
|
119
|
-
`,Oe=`ostia compare <base.json> <candidate.json>
|
|
120
|
-
ostia compare <candidate.json> --baseline <base.json>
|
|
121
|
-
|
|
122
|
-
Compare two ProfileDocuments by workload id. Gates on ostia.config's "thresholds" when
|
|
123
|
-
present (the same ones "ostia ci" uses), else the defaults.
|
|
124
|
-
|
|
125
|
-
Flags:
|
|
126
|
-
--baseline PATH the base document, when only the candidate is positional
|
|
127
|
-
--export-json PATH write the candidate document with comparisons to PATH
|
|
128
|
-
--format FORMAT table | json | jsonl | markdown | minimal (default: table)
|
|
129
|
-
--quiet don't print the report
|
|
130
|
-
--help show this message
|
|
131
|
-
|
|
132
|
-
Exit codes: 0 pass, 1 a workload regressed, 2 nothing matched or a harness error.
|
|
133
|
-
`,Ze=`ostia report <document.json> [flags]
|
|
134
|
-
|
|
135
|
-
Render a saved ProfileDocument. Files, not a GUI for the visualization formats -
|
|
136
|
-
hand the output to speedscope.app, flamegraph.pl, or a Mermaid renderer.
|
|
137
|
-
|
|
138
|
-
Formats:
|
|
139
|
-
table terminal timing/CPU/heap/comparison text (default)
|
|
140
|
-
json pretty JSON document
|
|
141
|
-
jsonl one metadata line, then one line per measurement
|
|
142
|
-
markdown agent- and human-readable report
|
|
143
|
-
minimal one compact JSON object per timing measurement, for LLM/CI consumption
|
|
144
|
-
collapsed folded stacks: "root;a;b 42" - flamegraph.pl and most flame tooling
|
|
145
|
-
mermaid call tree, top-15 frames by total time (never the whole profile)
|
|
146
|
-
speedscope sampled profile JSON for speedscope.app
|
|
147
|
-
cpuprofile verbatim .cpuprofile pass-through (cpu-prof/inspector origins only)
|
|
148
|
-
|
|
149
|
-
Flags:
|
|
150
|
-
--format FORMAT one of the formats above (default: table)
|
|
151
|
-
--measurement <id> for the visualization formats: render only this measurement
|
|
152
|
-
(default: every CPU measurement in the document)
|
|
153
|
-
--out-dir PATH write visualization files here instead of stdout
|
|
154
|
-
--help show this message
|
|
155
|
-
|
|
156
|
-
Examples:
|
|
157
|
-
ostia report doc.json
|
|
158
|
-
ostia report doc.json --format markdown
|
|
159
|
-
ostia report doc.json --format speedscope --out-dir node_modules/.cache/ostia/viz
|
|
160
|
-
ostia report doc.json --format collapsed | flamegraph.pl > flame.svg
|
|
161
|
-
`,Qe=`ostia ci [flags]
|
|
162
|
-
|
|
163
|
-
Run ostia.config's workloads (reusing cached runs whose declared inputs are unchanged),
|
|
164
|
-
compare against a saved baseline, and gate on regressions.
|
|
165
|
-
|
|
166
|
-
Flags:
|
|
167
|
-
--full ignore the cache
|
|
168
|
-
--baseline NAME baseline to compare against (default: config "baseline", or "main")
|
|
169
|
-
--save-baseline after a pass, save this run as the new baseline
|
|
170
|
-
--no-noise-check skip the ~200ms noise-floor measurement
|
|
171
|
-
--export-json PATH write the candidate document with comparisons to PATH
|
|
172
|
-
--format FORMAT table | json | jsonl | markdown | minimal (default: table)
|
|
173
|
-
--quiet don't print the report
|
|
174
|
-
--help show this message
|
|
175
|
-
|
|
176
|
-
Exit codes: 0 pass, 1 regression, 2 harness error (missing config/baseline, a workload
|
|
177
|
-
failed or produced no samples, or an onMissingBaseline "fail" mismatch).
|
|
178
|
-
`,he=`ostia baseline <save|list|show> [args]
|
|
179
|
-
|
|
180
|
-
Manage the baseline ProfileDocuments "ostia ci" gates against and "ostia compare --baseline"
|
|
181
|
-
reads.
|
|
182
|
-
|
|
183
|
-
Subcommands:
|
|
184
|
-
save [name] measure every configured workload (same code path as "ostia ci",
|
|
185
|
-
no comparison) and write it to <baselineDir>/<name>.json
|
|
186
|
-
(default name: config's "baseline" field, or "main")
|
|
187
|
-
list list saved baselines: name, created date, workload count
|
|
188
|
-
show <name> [flags] render a saved baseline; delegates to "ostia report" (same
|
|
189
|
-
--format/--measurement/--out-dir flags)
|
|
190
|
-
|
|
191
|
-
Examples:
|
|
192
|
-
ostia baseline save
|
|
193
|
-
ostia baseline save my-feature
|
|
194
|
-
ostia baseline list
|
|
195
|
-
ostia baseline show main
|
|
196
|
-
ostia baseline show main --format markdown
|
|
197
|
-
`;function ce(r,n,c,p,h,g){let x=p;for(let b=0;b<r.length;b++){let k=r[b],q=g?.(k,b,p);if(q!==void 0){b=q;continue}let j=k.startsWith("--")?k.indexOf("="):-1,M=j>0?k.slice(0,j):k,P=c[M];if(!P||j>0&&P.spec.kind==="bool"){if(k.startsWith("-"))throw new Q(`Unknown flag "${k}" for "ostia ${n}".`);h(k,p);continue}let y=()=>j>0?k.slice(j+1):r[++b];switch(P.spec.kind){case"string":x[P.dest]=y();break;case"int":x[P.dest]=Ge(M,y(),P.spec);break;case"number":{let F=y(),K=Number(F);if(F===void 0||F===""||!Number.isFinite(K)||K<P.spec.min)throw new Q(`Invalid ${M} "${F}": expected a number \u2265 ${P.spec.min}`);x[P.dest]=K;break}case"bool":x[P.dest]=P.spec.value;break;case"list":x[P.dest].push(y()??"");break;case"enum":{let F=y();if(!P.spec.values.includes(F))throw new Q(`Invalid ${M} "${F}": expected one of: ${P.spec.values.join(", ")}`);x[P.dest]=F;break}}}return p}var Ye={"--samples":{dest:"samples",spec:{kind:"int",min:1}},"--budget":{dest:"budgetMs",spec:{kind:"int",min:1}},"--min-samples":{dest:"minSamples",spec:{kind:"int",min:1}},"--warmup":{dest:"warmup",spec:{kind:"int",min:0}},"--no-interleave":{dest:"interleave",spec:{kind:"bool",value:!1}},"--prepare":{dest:"prepare",spec:{kind:"list"}},"--time-source":{dest:"timeSource",spec:{kind:"string"}},"--time-unit":{dest:"timeUnit",spec:{kind:"enum",values:Ve}},"--cpu":{dest:"cpu",spec:{kind:"bool",value:!0}},"--heap":{dest:"heap",spec:{kind:"bool",value:!0}},"--cpu-interval":{dest:"cpuIntervalUs",spec:{kind:"int",min:1}},"--timeout":{dest:"timeoutMs",spec:{kind:"int",min:1}},"--out-dir":{dest:"outDir",spec:{kind:"string"}},"--no-noise-check":{dest:"noiseCheck",spec:{kind:"bool",value:!1}},"--export-json":{dest:"exportJson",spec:{kind:"string"}},"--format":{dest:"format",spec:{kind:"enum",values:ne}},"--quiet":{dest:"quiet",spec:{kind:"bool",value:!0}},"--help":{dest:"help",spec:{kind:"bool",value:!0}},"-h":{dest:"help",spec:{kind:"bool",value:!0}}};function et(r){return ce(r,"time",Ye,{commands:[],prepare:[],interleave:!0,cpu:!1,heap:!1,ignoreExitCodes:[],noiseCheck:!0,format:"table",quiet:!1,help:!1},(c,p)=>p.commands.push(c),(c,p,h)=>{if(c==="--")return h.argvCommand=r.slice(p+1),r.length;if(c==="--ignore-failure"||c.startsWith("--ignore-failure=")){let g=c.startsWith("--ignore-failure=")?c.slice(17):void 0;return h.ignoreExitCodes.push(...g===void 0?Xe:g.split(",").map(Number)),p}return})}async function ke(r){let n=new AbortController,c=()=>n.abort();process.on("SIGINT",c);try{return{result:await r(n.signal),aborted:n.signal.aborted}}finally{process.off("SIGINT",c)}}async function tt(r){let n=et(r),c=(n.argvCommand?.length??0)>0;if(n.help||n.commands.length===0&&!c)return L(Ke,n.help);let p=n.commands.length+(c?1:0);if(n.prepare.length>1&&n.prepare.length!==p)return await T("invalid-flag",`--prepare given ${n.prepare.length} times for ${p} command(s): give it once (applies to all) or once per command.`),2;if(n.timeSource!==void 0)try{new RegExp(n.timeSource)}catch(y){return await T("invalid-flag",`Invalid --time-source regex: ${Y(y)}`),2}let h=n.timeSource!==void 0?{pattern:n.timeSource,unit:n.timeUnit}:void 0,g=(y)=>n.prepare.length===1?n.prepare[0]:n.prepare[y],x=n.commands.map((y,F)=>({command:y,prepare:g(F)}));if(c)x.push({command:n.argvCommand,prepare:g(n.commands.length)});let b,k;try{({result:b,aborted:k}=await ke((y)=>ae({commands:x,timeSource:h,samples:n.samples,budgetMs:n.budgetMs,minSamples:n.minSamples,warmup:n.warmup,interleave:n.interleave,cpu:n.cpu,heap:n.heap,cpuIntervalUs:n.cpuIntervalUs,timeoutMs:n.timeoutMs,ignoreExitCodes:n.ignoreExitCodes,outDir:n.outDir,noiseCheck:n.noiseCheck,signal:y})))}catch(y){return await T("spawn-failed",`Run failed: ${Y(y)}`),2}let q=await pe(b,n);if(q!==0)return q;if(k)return 130;let j=b.workloads.map((y)=>b.measurements.find((F)=>F.workloadId===y.id&&F.phase==="timing")??{trials:[],timing:void 0}),M=j.some((y)=>!y.timing),P=j.some((y)=>y.timing&&J(y,n.ignoreExitCodes));if(P||M){let y=b.measurements.some((Z)=>Z.warnings.some((X)=>X.code==="timeout")),F=b.measurements.some((Z)=>Z.warnings.some((X)=>X.code==="time-source-no-match"));return await T(P?"command-failed":y?"timeout":F?"time-source-no-match":"command-failed","One or more commands failed to produce a clean measurement; see the report above for details."),2}return 0}var nt={"--budget":{dest:"budgetMs",spec:{kind:"int",min:1}},"--samples":{dest:"samples",spec:{kind:"int",min:1}},"--min-samples":{dest:"minSamples",spec:{kind:"int",min:1}},"--jobs":{dest:"jobs",spec:{kind:"int",min:1,allowAuto:!0}},"--gc":{dest:"gc",spec:{kind:"bool",value:!0}},"--no-gc":{dest:"gc",spec:{kind:"bool",value:!1}},"--cpu":{dest:"cpu",spec:{kind:"bool",value:!0}},"--no-cpu":{dest:"cpu",spec:{kind:"bool",value:!1}},"--cpu-interval":{dest:"cpuIntervalUs",spec:{kind:"int",min:1}},"--alloc":{dest:"alloc",spec:{kind:"bool",value:!0}},"--no-alloc":{dest:"alloc",spec:{kind:"bool",value:!1}},"--peak-mem":{dest:"peakMem",spec:{kind:"bool",value:!0}},"--no-peak-mem":{dest:"peakMem",spec:{kind:"bool",value:!1}},"--filter":{dest:"filter",spec:{kind:"string"}},"--isolate":{dest:"isolate",spec:{kind:"bool",value:!0}},"--no-isolate":{dest:"isolate",spec:{kind:"bool",value:!1}},"--preload":{dest:"preload",spec:{kind:"list"}},"--timeout":{dest:"timeoutMs",spec:{kind:"int",min:1}},"--out-dir":{dest:"outDir",spec:{kind:"string"}},"--no-noise-check":{dest:"noiseCheck",spec:{kind:"bool",value:!1}},"--export-json":{dest:"exportJson",spec:{kind:"string"}},"--format":{dest:"format",spec:{kind:"enum",values:ne}},"--quiet":{dest:"quiet",spec:{kind:"bool",value:!0}},"--help":{dest:"help",spec:{kind:"bool",value:!0}},"-h":{dest:"help",spec:{kind:"bool",value:!0}}};function st(r){return ce(r,"bench",nt,{suites:[],preload:[],bunFlags:[],noiseCheck:!0,format:"table",quiet:!1,help:!1},(c,p)=>p.suites.push(c),Be(r))}function Be(r){return(n,c,p)=>{if(n==="--bun-flags")return p.bunFlags.push(...R(r[c+1]??"")),c+1;if(n.startsWith("--bun-flags="))return p.bunFlags.push(...R(n.slice(12))),c;return}}async function ot(r){let n=st(r);if(n.help)return L(De,!0);let c=await W(),p=await z(n,c?.bench);if(p.suites.length===0)return L(De,!1);if(p.jobs!==void 0&&!(p.jobs>=1))return await T("invalid-flag",'--jobs expects a positive integer or "auto".'),2;let h,g;try{({result:h,aborted:g}=await ke((b)=>D({...p,signal:b})))}catch(b){return await T("spawn-failed",`Bench failed: ${Y(b)}`),2}let x=await pe(h,n);if(x!==0)return x;return g?130:0}var at={"--base":{dest:"base",spec:{kind:"string"}},"--rounds":{dest:"rounds",spec:{kind:"int",min:3}},"--threshold":{dest:"thresholdPct",spec:{kind:"number",min:0}},"--geomean-threshold":{dest:"geomeanThresholdPct",spec:{kind:"number",min:0}},"--confirm":{dest:"confirm",spec:{kind:"int",min:0}},"--filter":{dest:"filter",spec:{kind:"string"}},"--preload":{dest:"preload",spec:{kind:"list"}},"--timeout":{dest:"timeoutMs",spec:{kind:"int",min:1}},"--out-dir":{dest:"outDir",spec:{kind:"string"}},"--no-noise-check":{dest:"noiseCheck",spec:{kind:"bool",value:!1}},"--export-json":{dest:"exportJson",spec:{kind:"string"}},"--format":{dest:"format",spec:{kind:"enum",values:ne}},"--quiet":{dest:"quiet",spec:{kind:"bool",value:!0}},"--help":{dest:"help",spec:{kind:"bool",value:!0}},"-h":{dest:"help",spec:{kind:"bool",value:!0}}};function it(r){return ce(r,"ab",at,{suites:[],preload:[],bunFlags:[],noiseCheck:!0,format:"table",quiet:!1,help:!1},(c,p)=>p.suites.push(c),Be(r))}async function rt(r){let n=it(r);if(n.help)return L(Re,!0);let c=(await W())?.bench,p=n.suites.length>0?n.suites:c?.suites?await U(c.suites,process.cwd()):[];if(p.length===0)return L(Re,!1);let h,g;try{({result:h,aborted:g}=await ke((M)=>G({suites:p,base:n.base,rounds:n.rounds,thresholdPct:n.thresholdPct,geomeanThresholdPct:n.geomeanThresholdPct,confirm:n.confirm,filter:n.filter??c?.filter,preload:n.preload.length>0?n.preload:c?.preload??[],bunFlags:n.bunFlags.length>0?n.bunFlags:c?.bunFlags??[],timeoutMs:n.timeoutMs??c?.timeoutMs,outDir:n.outDir??c?.outDir,noiseCheck:n.noiseCheck,signal:M})))}catch(M){if(M instanceof A)return await T("invalid-flag",`--base: ${M.message}`),2;return await T("spawn-failed",`A/B run failed: ${Y(M)}`),2}let x=h.ab,b=g?130:x.matched===0?2:x.verdict==="fail"?1:0,k=new Map(h.workloads.map((M)=>[M.id,M])),q=(M=[])=>M.flatMap((P)=>k.get(P)??[]),j=await pe(h,n,{protocol:{command:"ab",exitCode:b,unmatched:{baseOnly:q(h.unmatched?.baseOnly),candOnly:q(h.unmatched?.candOnly)},...n.exportJson&&{exportedTo:n.exportJson}}});if(j!==0)return j;if(b===2)await T("no-matches",`No task exists both at ${x.base.ref} and in the working tree; nothing was paired.`);return b}var lt={"--baseline":{dest:"baseline",spec:{kind:"string"}},"--export-json":{dest:"exportJson",spec:{kind:"string"}},"--format":{dest:"format",spec:{kind:"enum",values:ne}},"--quiet":{dest:"quiet",spec:{kind:"bool",value:!0}},"--help":{dest:"help",spec:{kind:"bool",value:!0}},"-h":{dest:"help",spec:{kind:"bool",value:!0}}};function ct(r){return ce(r,"compare",lt,{paths:[],format:"table",quiet:!1,help:!1},(c,p)=>p.paths.push(c))}async function ut(){let r=await W();if(!r)return{thresholds:f,source:"defaults"};return{thresholds:r.thresholds,source:await oe()??"ostia.config.json"}}async function mt(r){let n=ct(r);if(n.help)return L(Oe,!0);let c,p;if(n.baseline)c=n.baseline,p=n.paths[0];else c=n.paths[0],p=n.paths[1];if(!c||!p)return L(Oe,!1);let h,g;try{[h,g]=await Promise.all([o(c),o(p)])}catch(y){return await T("document-load-failed",`Failed to load documents: ${Y(y)}`),2}let{thresholds:x,source:b}=await ut(),k=H(h,g,x),q={...g,comparisons:k.comparisons,comparisonSummary:k.summary,unmatched:{baseOnly:k.unmatched.baseOnly.map((y)=>y.id),candOnly:k.unmatched.candOnly.map((y)=>y.id)}},j=n.format==="table"||n.format==="markdown";if(!n.quiet&&j){if(await B(`thresholds: ${b}
|
|
198
|
-
`),h.git&&g.git)await B(`base ${v(h.git)} \u2192 cand ${v(g.git)}
|
|
199
|
-
`);if(n.format==="table"&&k.summary.effectiveTimingPct>x.timingPct)await B(`threshold ${x.timingPct}% (widened to ${k.summary.effectiveTimingPct.toFixed(1)}% by noise floor)
|
|
200
|
-
`)}let M=k.summary.matched===0?2:k.summary.verdict==="fail"?1:0,P=await pe(q,n,{protocol:{command:"compare",exitCode:M,unmatched:k.unmatched,baseGit:h.git,candGit:g.git,...n.exportJson&&{exportedTo:n.exportJson}}});if(P!==0)return P;if(!n.quiet&&(n.format==="table"||n.format==="markdown")&&(k.unmatched.baseOnly.length>0||k.unmatched.candOnly.length>0))await B(dt(k.unmatched,n.format));if(k.summary.matched===0)await T("no-matches","No workload matched between base and candidate; nothing was compared.");return M}function dt(r,n){let c=(h)=>h.map((g)=>t(g)).join(", ");if(n==="markdown"){let h=["### Unmatched",""];if(r.baseOnly.length>0)h.push(`- baseline only: ${c(r.baseOnly)}`);if(r.candOnly.length>0)h.push(`- candidate only: ${c(r.candOnly)}`);return h.push(""),`${h.join(`
|
|
201
|
-
`)}
|
|
202
|
-
`}let p=["","Unmatched:"];if(r.baseOnly.length>0)p.push(` baseline only: ${c(r.baseOnly)}`);if(r.candOnly.length>0)p.push(` candidate only: ${c(r.candOnly)}`);return`${p.join(`
|
|
203
|
-
`)}
|
|
204
|
-
`}var pt={"--format":{dest:"format",spec:{kind:"enum",values:ze}},"--measurement":{dest:"measurementId",spec:{kind:"string"}},"--out-dir":{dest:"outDir",spec:{kind:"string"}},"--help":{dest:"help",spec:{kind:"bool",value:!0}},"-h":{dest:"help",spec:{kind:"bool",value:!0}}};function ft(r){return ce(r,"report",pt,{format:"table",help:!1},(c,p)=>{if(p.path!==void 0)throw new Q(`"ostia report" takes exactly one document path, got "${p.path}" and "${c}".`);p.path=c})}async function _e(r){let n=ft(r);if(n.help||!n.path)return L(Ze,n.help);let c;try{c=await o(n.path)}catch(g){return await T("document-load-failed",`Failed to load ${n.path}: ${Y(g)}`),2}if(we.includes(n.format)&&!n.measurementId&&!Ne(c))return await T("no-cpu-evidence",be),2;let h=await d[n.format].render(c,{measurementId:n.measurementId});if(!h.text&&(!h.files||h.files.length===0))return await T("no-cpu-evidence",n.measurementId?`No CPU evidence found for measurement "${n.measurementId}".`:be),2;return await ue(h,n.outDir),0}var ht={"--full":{dest:"full",spec:{kind:"bool",value:!0}},"--baseline":{dest:"baseline",spec:{kind:"string"}},"--save-baseline":{dest:"saveBaseline",spec:{kind:"bool",value:!0}},"--export-json":{dest:"exportJson",spec:{kind:"string"}},"--format":{dest:"format",spec:{kind:"enum",values:ne}},"--no-noise-check":{dest:"noiseCheck",spec:{kind:"bool",value:!1}},"--quiet":{dest:"quiet",spec:{kind:"bool",value:!0}},"--help":{dest:"help",spec:{kind:"bool",value:!0}},"-h":{dest:"help",spec:{kind:"bool",value:!0}}};function gt(r){return ce(r,"ci",ht,{full:!1,saveBaseline:!1,format:"table",quiet:!1,help:!1,noiseCheck:!0},(c)=>{throw new Q(`Unknown flag "${c}" for "ostia ci".`)})}async function bt(r){let n=gt(r);if(n.help)return L(Qe,!0);let c=await fe("ostia ci");if(!c)return 2;let p={...c,...!n.noiseCheck&&{noiseCheck:!1}},h;try{h=await Ae({config:p,full:n.full,baselineName:n.baseline})}catch(b){if(b instanceof me||b instanceof de)return await T("baseline-missing",b.message),2;if(b instanceof O)return await T("document-load-failed",b.message),2;return await T("spawn-failed",`CI run failed: ${Y(b)}`),2}let g=n.format==="json"&&!n.quiet?I(h.document):void 0;if(n.exportJson)if(g)await ee(g,n.exportJson);else await a(h.document,n.exportJson);if(n.saveBaseline&&h.summary.regressed===0&&h.summary.failed===0)await a(h.document,l(p,n.baseline));let x=h.summary.failed>0?2:h.summary.regressed>0?1:0;if(h.summary.failed>0){let b=h.summary.results.filter((k)=>k.harnessFailed).map((k)=>t(k.workload));await T("command-failed",`${h.summary.failed} workload(s) failed (harness error, a command exited non-zero or produced no samples): ${b.join(", ")}`)}if(!n.quiet){let b=n.baseline??p.baseline;if(n.format==="table"){if(await B(Me(h.summary)),h.document.comparisons&&h.document.comparisons.length>0)await ue(await d.table.render(h.document,{}))}else if(g!==void 0)await B(g);else if(n.format==="markdown")await B(`## ostia ci
|
|
205
|
-
|
|
206
|
-
Baseline: \`${b}\` (\`${l(p,n.baseline)}\`) \xB7 ${h.summary.cached} cached, ${h.summary.executed} executed
|
|
207
|
-
|
|
208
|
-
`),await ue(await d.markdown.render(h.document,{}));else await ue(await d[n.format].render(h.document,{protocol:{command:"ci",exitCode:x,unmatched:h.summary.unmatched,baseGit:h.baseline.git,candGit:h.document.git,baseline:{name:b,path:l(p,n.baseline)},cached:h.summary.cached,executed:h.summary.executed,failed:h.summary.failed,missingBaseline:h.summary.missingBaseline,...n.exportJson&&{exportedTo:n.exportJson}}}))}return x}var je=/^[A-Za-z0-9._-]+$/;function He(r){if(r.startsWith("-"))return`Invalid baseline name "${r}": names can't start with "-".`;if(!je.test(r))return`Invalid baseline name "${r}": expected to match ${je}.`;return}async function wt(r){if(r.includes("--help")||r.includes("-h"))return L(he,!0);if(r.length>1)return await T("invalid-flag",`"ostia baseline save" takes at most one name argument, got ${r.length}.
|
|
209
|
-
Run 'ostia baseline --help'.`),2;let n=r[0];if(n!==void 0){let h=He(n);if(h)return await T("invalid-flag",`${h}
|
|
210
|
-
Run 'ostia baseline --help'.`),2}let c=await fe("ostia baseline save");if(!c)return 2;let p=await Se(c,n);return await B(`Wrote ${p}
|
|
211
|
-
`),0}async function kt(r){if(r.includes("--help")||r.includes("-h"))return L(he,!0);let n=await fe();if(!n)return 2;let c=await Fe(n);if(c.length===0)return await B(`No baselines found in ${n.baselineDir}.
|
|
212
|
-
`),0;for(let p of c){let h=p.git?` ${v(p.git)}`:"";await B(`${p.name} ${p.workloads} workloads created ${p.createdAt} toolVersion ${p.toolVersion}${h}
|
|
213
|
-
`)}return 0}async function vt(r){let[n,...c]=r;if(!n||n==="--help"||n==="-h")return L(he,!!n);let p=He(n);if(p)return await T("invalid-flag",`${p}
|
|
214
|
-
Run 'ostia baseline --help'.`),2;let h=await fe();if(!h)return 2;return _e([l(h,n),...c])}async function We(r,n,c,p,h,g=""){if(r===void 0||r==="--help"||r==="-h")return L(p,r!==void 0);let x=c[r];if(!x)return await T("invalid-flag",h(r)),2;try{return await x(n)}catch(b){if(b instanceof N)return await T("config-invalid",b.message),2;if(b instanceof Q)return Je(b,`${g}${r}`);return await T("internal",`Internal error: ${b instanceof Error?b.stack??b.message:String(b)}`),2}}async function yt(r){let[n,...c]=r;return We(n,c,{save:wt,list:kt,show:vt},he,(p)=>`Unknown "ostia baseline ${p}". Run "ostia baseline --help".`,"baseline ")}var Ct=`ostia - profiling and benchmarking for Bun
|
|
215
|
-
|
|
216
|
-
Commands:
|
|
217
|
-
time Time commands N times and report timing/CPU/heap
|
|
218
|
-
bench Run in-process benchmark suites (group()/task())
|
|
219
|
-
ab Pair suites against a git ref in one process, gate on regressions
|
|
220
|
-
compare Compare two ProfileDocuments
|
|
221
|
-
report Render a saved ProfileDocument (table/json/markdown/collapsed/mermaid/speedscope/...)
|
|
222
|
-
ci Run configured workloads against a baseline, gate on regressions
|
|
223
|
-
baseline Manage baseline ProfileDocuments (save/list/show)
|
|
224
|
-
|
|
225
|
-
Run "ostia <command> --help" for details.
|
|
226
|
-
`;async function xt(){let[r,...n]=process.argv.slice(2);return Ie=n,We(r,n,{time:tt,bench:ot,ab:rt,compare:mt,report:_e,ci:bt,baseline:yt},Ct,(c)=>`Unknown subcommand "${c}". Run "ostia --help".`)}if(import.meta.main)xt().then((r)=>process.exit(r));export{Q as CliUsageError,Ge as parseIntFlag};
|
|
3
|
+
var t=process.argv[2];if(t===void 0||t==="--help"||t==="-h"){let{MAIN_HELP:i}=await import("./chunk-9cr8k2g1.js");await Bun.write(Bun.stdout,i),process.exit(t===void 0?2:0)}var{main:e}=await import("./chunk-rhk7gwaa.js");process.exit(await e());
|