ostia 0.2.8 → 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.
@@ -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
- import{e,w,t,y,N,r,B,x,i,P,Z,l,M,s,a,c}from"./chunk-17t12ta6.js";import{d,ne,te,L,T,E,u,re,R,j,H,ae,G,se,F,V,S,f,m,oe}from"./chunk-p3342c5c.js";import{resolve as Ue}from"path";function ve(o){return e("cache",o.workloadId,o.phase,o.configFingerprint,o.bunVersion,o.toolVersion,o.instrumented,o.inputsDigest??null)}async function ye(o,n=process.cwd()){if(o.length===0)return;let p=await H(o,n),h=await Promise.all(p.map(async(g)=>{let b=await Bun.file(Ue(n,g)).arrayBuffer();return{path:g,sha256:Bun.CryptoHasher.hash("sha256",b,"hex")}}));return e("inputs",h)}function Ce(o,n){return`${o}/cache/${n}.json`}async function xe(o,n){let p=Bun.file(Ce(o,n));if(!await p.exists())return;return await p.json()}async function Pe(o,n,p){await Bun.write(Ce(o,n),`${JSON.stringify(p,null,2)}
4
- `)}var $e=600000;class me extends Error{path;constructor(o){super(`No baseline document at ${o}. Create one with: ostia baseline save ${o.split("/").pop().replace(/\.json$/,"")}`);this.path=o}}class de extends Error{path;missing;total;constructor(o,n,p){super(`${n} of ${p} 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=p}}function qe(o,n,p){if(o)return o;return n===p?"fail":"warn"}async function ge(o,n){let p=o.noiseCheck===!1?void 0:a(),h=p?c(p):void 0,g=[];for(let b of o.workloads){if(b.suites){let I=await F(b.suites,process.cwd()),K=await V({suites:I,preload:[],bunFlags:[],noiseCheck:!1},o.bench),Q=await S({...K,outDir:K.outDir??o.outDir,timeoutMs:K.timeoutMs??$e});for(let X of Q.workloads){let W=Q.measurements.find((ie)=>ie.workloadId===X.id&&ie.phase==="timing");if(!W)continue;if(h)W.warnings.push(h);g.push({workload:X,status:"executed",run:W,harnessFailed:!1})}continue}let A=B(b.command,b.label,{prepare:b.prepare,timeSource:b.timeSource}),k=await ye(b.inputs??[]),v=i({samples:o.samples??null,budgetMs:o.budgetMs??null,minSamples:o.minSamples??null,warmup:o.warmup}),J=b.inputs!==void 0&&typeof b.prepare!=="function",U=ve({workloadId:A.id,phase:"timing",configFingerprint:v,bunVersion:Bun.version,toolVersion:N,instrumented:!1,inputsDigest:k}),O=n||!J?void 0:await xe(o.outDir,U),D,C;if(O)D=O,C="cached";else{let I=await ae({argv:b.command,samples:o.samples,budgetMs:o.budgetMs,minSamples:o.minSamples,warmup:o.warmup,prepare:b.prepare,timeSource:b.timeSource,timeoutMs:b.timeoutMs??$e,ignoreExitCodes:b.ignoreExitCodes});if(D=x({workload:A,configFingerprint:v,trials:I.trials,timing:I.timing,warnings:h?[...I.warnings,h]:I.warnings}),J)await Pe(o.outDir,U,D);C="executed"}g.push({workload:A,status:C,run:D,harnessFailed:G(D,b.ignoreExitCodes)})}return{results:g,environment:p}}async function Ae(o){let{config:n}=o,p=u(n,o.baselineName);if(!await Bun.file(p).exists())throw new me(p);let g=await s(p),{results:b,environment:A}=await ge(n,o.full),k=b.map((W)=>({...W})),v=k.filter((W)=>W.status==="executed").length,J=k.length-v,U=k.filter((W)=>W.harnessFailed).length,O=r(k.map((W)=>W.workload),k.map((W)=>W.run),A),D=0,C=0,I=0,K=ne(g,O,n.thresholds);for(let W of k){let ie=K.compare(W.workload.id);if(!ie){I++;continue}if(W.comparison=ie,ie.verdict==="pass")D++;else C++}if(I>0){if(qe(n.onMissingBaseline,I,k.length)==="fail")throw new de(p,I,k.length)}let Q=k.map((W)=>W.comparison).filter((W)=>W!==void 0);O.comparisons=Q;let X=K.unmatched();return O.unmatched={baseOnly:X.baseOnly.map((W)=>W.id),candOnly:X.candOnly.map((W)=>W.id)},O.comparisonSummary=te(Q,K.effectiveTimingPct),{document:O,summary:{total:k.length,cached:J,executed:v,passed:D,regressed:C,missingBaseline:I,failed:U,results:k,unmatched:X},baseline:g}}function Me(o){let n=[];if(n.push(`${o.total} workloads`),n.push(`${o.cached} cached`),n.push(`${o.executed} executed`),o.missingBaseline>0)n.push(`${o.missingBaseline} skipped (no matching baseline workload)`);if(o.failed>0){let h=o.results.filter((g)=>g.harnessFailed).map((g)=>t(g.workload));n.push(`${o.failed} failed (harness error, a command exited non-zero or produced no samples: ${h.join(", ")})`)}let p=o.results.filter((h)=>h.comparison?.verdict==="fail").map((h)=>{let g=h.comparison.timing,b=t(h.workload);return g?`${g.medianDeltaPct>0?"+":""}${g.medianDeltaPct.toFixed(1)}% median on ${b}`:b});return n.push(`${o.passed} passed ${o.regressed} regressed${p.length>0?` (${p.join(", ")})`:""}`),n.push(""),n.push(`Profile CI: ${o.regressed>0||o.failed>0?"\u2717":"\u2713"}`),`${n.join(`
5
- `)}
6
- `}async function Se(o,n){let{results:p,environment:h}=await ge(o,!1),g=r(p.map((A)=>A.workload),p.map((A)=>A.run),h),b=u(o,n);return await l(g,b),b}async function Fe(o){let n=[];try{let h=new Bun.Glob("*.json");for await(let g of h.scan({cwd:o.baselineDir,absolute:!1}))n.push(g.replace(/\.json$/,""))}catch{return[]}return n.sort(),(await Promise.all(n.map(async(h)=>{let g=u(o,h);try{let b=await s(g);return{name:h,path:g,createdAt:b.createdAt,toolVersion:b.toolVersion,bunVersion:b.bunVersion,workloads:b.workloads.length,...b.git!==void 0&&{git:b.git}}}catch{return}}))).filter((h)=>h!==void 0)}function ee(o){return o instanceof Error?o.message:String(o)}var q=(o)=>Bun.write(Bun.stdout,o),Te=(o)=>Bun.write(Bun.stderr,o);class Y extends Error{}var Ie=[],Ee=new Set(["minimal","json","jsonl"]);async function Le(o){let{isatty:n}=await import("tty");if(!n(2))return!0;return o.some((p,h)=>p==="--format"&&Ee.has(o[h+1]??"")||p.startsWith("--format=")&&Ee.has(p.slice(9)))}async function _(o,n,p){if(await Te(n.endsWith(`
7
- `)?n:`${n}
8
- `),!await Le(Ie))return;await Te(`${JSON.stringify({event:"error",protocolVersion:f,code:o,message:n.split(`
9
- `)[0],...p&&{data:p}})}
10
- `)}async function z(o,n){return await q(o),n?0:2}async function Je(o,n){return await _("invalid-flag",`${o.message}
11
- Run 'ostia ${n} --help'.`),2}function Ge(o,n,p={}){if(p.allowAuto&&n==="auto")return se();let h=p.min??1,g=Number(n);if(!Number.isInteger(g)||g<h)throw new Y(`Invalid ${o} "${n}": expected an integer \u2265 ${h}${p.allowAuto?' (or "auto")':""}`);return g}var Ve=["ns","us","ms","s"],le=["table","json","jsonl","markdown","minimal"],we=["collapsed","mermaid","speedscope","cpuprofile"],ze=[...le,...we],be=`No CPU evidence in this document; rerun with --cpu (ostia time) or --cpu (ostia bench)
12
- `;function Ne(o){return o.measurements.some((n)=>n.phase==="cpu")}async function pe(o,n,p={}){if(n.format==="json"&&n.exportJson){let h=P(o);if(await Z(h,n.exportJson),!n.quiet)await q(h);return 0}if(n.exportJson)await l(o,n.exportJson);if(n.quiet)return 0;if(we.includes(n.format)&&!Ne(o))return await _("no-cpu-evidence",be),2;return await ue(await m[n.format].render(o,p)),0}async function fe(o){let n=await E();if(!n){await _("config-missing",o?`No ostia.config.json found. "${o}" needs configured workloads.`:"No ostia.config.json found.");return}if(o&&n.workloads.length===0){let p=await re()??"ostia.config.json";await _("config-missing",`${p} has no "workloads" configured.`);return}return n}async function ue(o,n){if(o.text)await q(o.text);if(!o.files||o.files.length===0)return;if(n)for(let p of o.files){let h=p.path?`${n}/${p.path}`:n;await Bun.write(h,p.content),await q(`wrote ${h}
13
- `)}else if(o.files.length===1)await q(o.files[0].content);else for(let p of o.files)await q(`--- ${p.path??"(unnamed)"} ---
14
- ${p.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},(o,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(o,n,p,h,g,b){let A=h;for(let k=0;k<o.length;k++){let v=o[k],J=b?.(v,k,h);if(J!==void 0){k=J;continue}let U=v.startsWith("--")?v.indexOf("="):-1,O=U>0?v.slice(0,U):v,D=p[O];if(!D||U>0&&D.spec.kind==="bool"){if(v.startsWith("-"))throw new Y(`Unknown flag "${v}" for "ostia ${n}".`);g(v,h);continue}let C=()=>U>0?v.slice(U+1):o[++k];switch(D.spec.kind){case"string":A[D.dest]=C();break;case"int":A[D.dest]=Ge(O,C(),D.spec);break;case"number":{let I=C(),K=Number(I);if(I===void 0||I===""||!Number.isFinite(K)||K<D.spec.min)throw new Y(`Invalid ${O} "${I}": expected a number \u2265 ${D.spec.min}`);A[D.dest]=K;break}case"bool":A[D.dest]=D.spec.value;break;case"list":A[D.dest].push(C()??"");break;case"enum":{let I=C();if(!D.spec.values.includes(I))throw new Y(`Invalid ${O} "${I}": expected one of: ${D.spec.values.join(", ")}`);A[D.dest]=I;break}}}return h}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:le}},"--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(o){return ce(o,"time",Ye,{commands:[],prepare:[],interleave:!0,cpu:!1,heap:!1,ignoreExitCodes:[],noiseCheck:!0,format:"table",quiet:!1,help:!1},(p,h)=>h.commands.push(p),(p,h,g)=>{if(p==="--")return g.argvCommand=o.slice(h+1),o.length;if(p==="--ignore-failure"||p.startsWith("--ignore-failure=")){let b=p.startsWith("--ignore-failure=")?p.slice(17):void 0;return g.ignoreExitCodes.push(...b===void 0?Xe:b.split(",").map(Number)),h}return})}async function ke(o){let n=new AbortController,p=()=>n.abort();process.on("SIGINT",p);try{return{result:await o(n.signal),aborted:n.signal.aborted}}finally{process.off("SIGINT",p)}}async function tt(o){let n=et(o),p=(n.argvCommand?.length??0)>0;if(n.help||n.commands.length===0&&!p)return z(Ke,n.help);let h=n.commands.length+(p?1:0);if(n.prepare.length>1&&n.prepare.length!==h)return await _("invalid-flag",`--prepare given ${n.prepare.length} times for ${h} command(s): give it once (applies to all) or once per command.`),2;if(n.timeSource!==void 0)try{new RegExp(n.timeSource)}catch(C){return await _("invalid-flag",`Invalid --time-source regex: ${ee(C)}`),2}let g=n.timeSource!==void 0?{pattern:n.timeSource,unit:n.timeUnit}:void 0,b=(C)=>n.prepare.length===1?n.prepare[0]:n.prepare[C],A=n.commands.map((C,I)=>({command:C,prepare:b(I)}));if(p)A.push({command:n.argvCommand,prepare:b(n.commands.length)});let k,v;try{({result:k,aborted:v}=await ke((C)=>oe({commands:A,timeSource:g,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:C})))}catch(C){return await _("spawn-failed",`Run failed: ${ee(C)}`),2}let J=await pe(k,n);if(J!==0)return J;if(v)return 130;let U=k.workloads.map((C)=>k.measurements.find((I)=>I.workloadId===C.id&&I.phase==="timing")??{trials:[],timing:void 0}),O=U.some((C)=>!C.timing),D=U.some((C)=>C.timing&&G(C,n.ignoreExitCodes));if(D||O){let C=k.measurements.some((Q)=>Q.warnings.some((X)=>X.code==="timeout")),I=k.measurements.some((Q)=>Q.warnings.some((X)=>X.code==="time-source-no-match"));return await _(D?"command-failed":C?"timeout":I?"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:le}},"--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(o){return ce(o,"bench",nt,{suites:[],preload:[],bunFlags:[],noiseCheck:!0,format:"table",quiet:!1,help:!1},(p,h)=>h.suites.push(p),Be(o))}function Be(o){return(n,p,h)=>{if(n==="--bun-flags")return h.bunFlags.push(...y(o[p+1]??"")),p+1;if(n.startsWith("--bun-flags="))return h.bunFlags.push(...y(n.slice(12))),p;return}}async function ot(o){let n=st(o);if(n.help)return z(De,!0);let p=await E(),h=await V(n,p?.bench);if(h.suites.length===0)return z(De,!1);if(h.jobs!==void 0&&!(h.jobs>=1))return await _("invalid-flag",'--jobs expects a positive integer or "auto".'),2;let g,b;try{({result:g,aborted:b}=await ke((k)=>S({...h,signal:k})))}catch(k){return await _("spawn-failed",`Bench failed: ${ee(k)}`),2}let A=await pe(g,n);if(A!==0)return A;return b?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:le}},"--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(o){return ce(o,"ab",at,{suites:[],preload:[],bunFlags:[],noiseCheck:!0,format:"table",quiet:!1,help:!1},(p,h)=>h.suites.push(p),Be(o))}async function rt(o){let n=it(o);if(n.help)return z(Re,!0);let p=(await E())?.bench,h=n.suites.length>0?n.suites:p?.suites?await F(p.suites,process.cwd()):[];if(h.length===0)return z(Re,!1);let g,b;try{({result:g,aborted:b}=await ke((O)=>j({suites:h,base:n.base,rounds:n.rounds,thresholdPct:n.thresholdPct,geomeanThresholdPct:n.geomeanThresholdPct,confirm:n.confirm,filter:n.filter??p?.filter,preload:n.preload.length>0?n.preload:p?.preload??[],bunFlags:n.bunFlags.length>0?n.bunFlags:p?.bunFlags??[],timeoutMs:n.timeoutMs??p?.timeoutMs,outDir:n.outDir??p?.outDir,noiseCheck:n.noiseCheck,signal:O})))}catch(O){if(O instanceof R)return await _("invalid-flag",`--base: ${O.message}`),2;return await _("spawn-failed",`A/B run failed: ${ee(O)}`),2}let A=g.ab,k=b?130:A.matched===0?2:A.verdict==="fail"?1:0,v=new Map(g.workloads.map((O)=>[O.id,O])),J=(O=[])=>O.flatMap((D)=>v.get(D)??[]),U=await pe(g,n,{protocol:{command:"ab",exitCode:k,unmatched:{baseOnly:J(g.unmatched?.baseOnly),candOnly:J(g.unmatched?.candOnly)},...n.exportJson&&{exportedTo:n.exportJson}}});if(U!==0)return U;if(k===2)await _("no-matches",`No task exists both at ${A.base.ref} and in the working tree; nothing was paired.`);return k}var lt={"--baseline":{dest:"baseline",spec:{kind:"string"}},"--export-json":{dest:"exportJson",spec:{kind:"string"}},"--format":{dest:"format",spec:{kind:"enum",values:le}},"--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(o){return ce(o,"compare",lt,{paths:[],format:"table",quiet:!1,help:!1},(p,h)=>h.paths.push(p))}async function ut(){let o=await E();if(!o)return{thresholds:d,source:"defaults"};return{thresholds:o.thresholds,source:await re()??"ostia.config.json"}}async function mt(o){let n=ct(o);if(n.help)return z(Oe,!0);let p,h;if(n.baseline)p=n.baseline,h=n.paths[0];else p=n.paths[0],h=n.paths[1];if(!p||!h)return z(Oe,!1);let g,b;try{[g,b]=await Promise.all([s(p),s(h)])}catch(C){return await _("document-load-failed",`Failed to load documents: ${ee(C)}`),2}let{thresholds:A,source:k}=await ut(),v=L(g,b,A),J={...b,comparisons:v.comparisons,comparisonSummary:v.summary,unmatched:{baseOnly:v.unmatched.baseOnly.map((C)=>C.id),candOnly:v.unmatched.candOnly.map((C)=>C.id)}},U=n.format==="table"||n.format==="markdown";if(!n.quiet&&U){if(await q(`thresholds: ${k}
198
- `),g.git&&b.git)await q(`base ${w(g.git)} \u2192 cand ${w(b.git)}
199
- `);if(n.format==="table"&&v.summary.effectiveTimingPct>A.timingPct)await q(`threshold ${A.timingPct}% (widened to ${v.summary.effectiveTimingPct.toFixed(1)}% by noise floor)
200
- `)}let O=v.summary.matched===0?2:v.summary.verdict==="fail"?1:0,D=await pe(J,n,{protocol:{command:"compare",exitCode:O,unmatched:v.unmatched,baseGit:g.git,candGit:b.git,...n.exportJson&&{exportedTo:n.exportJson}}});if(D!==0)return D;if(!n.quiet&&(n.format==="table"||n.format==="markdown")&&(v.unmatched.baseOnly.length>0||v.unmatched.candOnly.length>0))await q(dt(v.unmatched,n.format));if(v.summary.matched===0)await _("no-matches","No workload matched between base and candidate; nothing was compared.");return O}function dt(o,n){let p=(g)=>g.map((b)=>t(b)).join(", ");if(n==="markdown"){let g=["### Unmatched",""];if(o.baseOnly.length>0)g.push(`- baseline only: ${p(o.baseOnly)}`);if(o.candOnly.length>0)g.push(`- candidate only: ${p(o.candOnly)}`);return g.push(""),`${g.join(`
201
- `)}
202
- `}let h=["","Unmatched:"];if(o.baseOnly.length>0)h.push(` baseline only: ${p(o.baseOnly)}`);if(o.candOnly.length>0)h.push(` candidate only: ${p(o.candOnly)}`);return`${h.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(o){return ce(o,"report",pt,{format:"table",help:!1},(p,h)=>{if(h.path!==void 0)throw new Y(`"ostia report" takes exactly one document path, got "${h.path}" and "${p}".`);h.path=p})}async function _e(o){let n=ft(o);if(n.help||!n.path)return z(Ze,n.help);let p;try{p=await s(n.path)}catch(b){return await _("document-load-failed",`Failed to load ${n.path}: ${ee(b)}`),2}if(we.includes(n.format)&&!n.measurementId&&!Ne(p))return await _("no-cpu-evidence",be),2;let g=await m[n.format].render(p,{measurementId:n.measurementId});if(!g.text&&(!g.files||g.files.length===0))return await _("no-cpu-evidence",n.measurementId?`No CPU evidence found for measurement "${n.measurementId}".`:be),2;return await ue(g,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:le}},"--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(o){return ce(o,"ci",ht,{full:!1,saveBaseline:!1,format:"table",quiet:!1,help:!1,noiseCheck:!0},(p)=>{throw new Y(`Unknown flag "${p}" for "ostia ci".`)})}async function bt(o){let n=gt(o);if(n.help)return z(Qe,!0);let p=await fe("ostia ci");if(!p)return 2;let h={...p,...!n.noiseCheck&&{noiseCheck:!1}},g;try{g=await Ae({config:h,full:n.full,baselineName:n.baseline})}catch(k){if(k instanceof me||k instanceof de)return await _("baseline-missing",k.message),2;if(k instanceof M)return await _("document-load-failed",k.message),2;return await _("spawn-failed",`CI run failed: ${ee(k)}`),2}let b=n.format==="json"&&!n.quiet?P(g.document):void 0;if(n.exportJson)if(b)await Z(b,n.exportJson);else await l(g.document,n.exportJson);if(n.saveBaseline&&g.summary.regressed===0&&g.summary.failed===0)await l(g.document,u(h,n.baseline));let A=g.summary.failed>0?2:g.summary.regressed>0?1:0;if(g.summary.failed>0){let k=g.summary.results.filter((v)=>v.harnessFailed).map((v)=>t(v.workload));await _("command-failed",`${g.summary.failed} workload(s) failed (harness error, a command exited non-zero or produced no samples): ${k.join(", ")}`)}if(!n.quiet){let k=n.baseline??h.baseline;if(n.format==="table"){if(await q(Me(g.summary)),g.document.comparisons&&g.document.comparisons.length>0)await ue(await m.table.render(g.document,{}))}else if(b!==void 0)await q(b);else if(n.format==="markdown")await q(`## ostia ci
205
-
206
- Baseline: \`${k}\` (\`${u(h,n.baseline)}\`) \xB7 ${g.summary.cached} cached, ${g.summary.executed} executed
207
-
208
- `),await ue(await m.markdown.render(g.document,{}));else await ue(await m[n.format].render(g.document,{protocol:{command:"ci",exitCode:A,unmatched:g.summary.unmatched,baseGit:g.baseline.git,candGit:g.document.git,baseline:{name:k,path:u(h,n.baseline)},cached:g.summary.cached,executed:g.summary.executed,failed:g.summary.failed,missingBaseline:g.summary.missingBaseline,...n.exportJson&&{exportedTo:n.exportJson}}}))}return A}var je=/^[A-Za-z0-9._-]+$/;function He(o){if(o.startsWith("-"))return`Invalid baseline name "${o}": names can't start with "-".`;if(!je.test(o))return`Invalid baseline name "${o}": expected to match ${je}.`;return}async function wt(o){if(o.includes("--help")||o.includes("-h"))return z(he,!0);if(o.length>1)return await _("invalid-flag",`"ostia baseline save" takes at most one name argument, got ${o.length}.
209
- Run 'ostia baseline --help'.`),2;let n=o[0];if(n!==void 0){let g=He(n);if(g)return await _("invalid-flag",`${g}
210
- Run 'ostia baseline --help'.`),2}let p=await fe("ostia baseline save");if(!p)return 2;let h=await Se(p,n);return await q(`Wrote ${h}
211
- `),0}async function kt(o){if(o.includes("--help")||o.includes("-h"))return z(he,!0);let n=await fe();if(!n)return 2;let p=await Fe(n);if(p.length===0)return await q(`No baselines found in ${n.baselineDir}.
212
- `),0;for(let h of p){let g=h.git?` ${w(h.git)}`:"";await q(`${h.name} ${h.workloads} workloads created ${h.createdAt} toolVersion ${h.toolVersion}${g}
213
- `)}return 0}async function vt(o){let[n,...p]=o;if(!n||n==="--help"||n==="-h")return z(he,!!n);let h=He(n);if(h)return await _("invalid-flag",`${h}
214
- Run 'ostia baseline --help'.`),2;let g=await fe();if(!g)return 2;return _e([u(g,n),...p])}async function We(o,n,p,h,g,b=""){if(o===void 0||o==="--help"||o==="-h")return z(h,o!==void 0);let A=p[o];if(!A)return await _("invalid-flag",g(o)),2;try{return await A(n)}catch(k){if(k instanceof T)return await _("config-invalid",k.message),2;if(k instanceof Y)return Je(k,`${b}${o}`);return await _("internal",`Internal error: ${k instanceof Error?k.stack??k.message:String(k)}`),2}}async function yt(o){let[n,...p]=o;return We(n,p,{save:wt,list:kt,show:vt},he,(h)=>`Unknown "ostia baseline ${h}". 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[o,...n]=process.argv.slice(2);return Ie=n,We(o,n,{time:tt,bench:ot,ab:rt,compare:mt,report:_e,ci:bt,baseline:yt},Ct,(p)=>`Unknown subcommand "${p}". Run "ostia --help".`)}if(import.meta.main)xt().then((o)=>process.exit(o));export{Y 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());