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
package/index.d.ts
CHANGED
|
@@ -1,119 +1,24 @@
|
|
|
1
1
|
export declare function defineConfig(config: OstiaConfigInput): OstiaConfigInput;
|
|
2
2
|
|
|
3
|
-
export interface CommandSpec {
|
|
4
|
-
command: string | string[];
|
|
5
|
-
label?: string;
|
|
6
|
-
/** Overrides `TimeOptions.prepare` for this command. */
|
|
7
|
-
prepare?: PrepareHook;
|
|
8
|
-
/** Overrides `TimeOptions.timeSource` for this command. */
|
|
9
|
-
timeSource?: TimeSource;
|
|
10
|
-
/** Overrides `TimeOptions.timeoutMs` for this command. */
|
|
11
|
-
timeoutMs?: number;
|
|
12
|
-
/** Overrides `TimeOptions.ignoreExitCodes` for this command. */
|
|
13
|
-
ignoreExitCodes?: number[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface TimeOptions {
|
|
17
|
-
commands: (string | string[] | CommandSpec)[];
|
|
18
|
-
/** Runs before every trial of every command (warmup and instrumented
|
|
19
|
-
* trials included), unmeasured, in `cwd`/`env`: a command string / argv
|
|
20
|
-
* array spawned and awaited, or a function. hyperfine's `--prepare`. A
|
|
21
|
-
* `CommandSpec.prepare` overrides it per command. */
|
|
22
|
-
prepare?: PrepareHook;
|
|
23
|
-
/** Take every command's timing from a number in its own output instead of
|
|
24
|
-
* its wall clock (e.g. a build tool's `built in 342ms` line, which
|
|
25
|
-
* excludes runtime startup). A `CommandSpec.timeSource` overrides it per
|
|
26
|
-
* command. The parsed value becomes `timing.samples`; each trial keeps
|
|
27
|
-
* `wallNs` too. */
|
|
28
|
-
timeSource?: TimeSource;
|
|
29
|
-
/** Exact trial count. When set, `budgetMs` is ignored. */
|
|
30
|
-
samples?: number;
|
|
31
|
-
/** Wall-clock time budget for the sampling loop, ms (default: a
|
|
32
|
-
* hyperfine-style ~3s min-total-time loop when neither `samples` nor
|
|
33
|
-
* `budgetMs` is given). */
|
|
34
|
-
budgetMs?: number;
|
|
35
|
-
/** Hard floor on trials when no exact `samples` count is given. */
|
|
36
|
-
minSamples?: number;
|
|
37
|
-
warmup?: number;
|
|
38
|
-
/** Round-robin trials across commands (one trial per command, repeated)
|
|
39
|
-
* instead of running each command's whole trial loop to completion before
|
|
40
|
-
* the next command starts. Default: true when 2+ commands are given (a
|
|
41
|
-
* single command has nothing to interleave against). Spreads any drift
|
|
42
|
-
* over the run's wall-clock span (thermal throttling, a noisy neighbor
|
|
43
|
-
* process) evenly across every command instead of favoring whichever ran
|
|
44
|
-
* first or last. */
|
|
45
|
-
interleave?: boolean;
|
|
46
|
-
cwd?: string;
|
|
47
|
-
env?: Record<string, string>;
|
|
48
|
-
cpu?: boolean;
|
|
49
|
-
heap?: boolean;
|
|
50
|
-
cpuIntervalUs?: number;
|
|
51
|
-
outDir?: string;
|
|
52
|
-
/** Measure this machine's noise floor before the first command (default:
|
|
53
|
-
* true) and stamp it on the document as `environment`. Set false to skip
|
|
54
|
-
* the ~200ms reference measurement. */
|
|
55
|
-
noiseCheck?: boolean;
|
|
56
|
-
/** Kills a trial (or prepare hook) that hasn't finished after this many
|
|
57
|
-
* ms, with SIGKILL. No default: an unset `timeoutMs` never times out. A
|
|
58
|
-
* timed-out trial contributes no sample; if every trial of a command times
|
|
59
|
-
* out, that command has no timing stats. `CommandSpec.timeoutMs` overrides
|
|
60
|
-
* it per command. */
|
|
61
|
-
timeoutMs?: number;
|
|
62
|
-
/** Aborting cancels the run: in-flight trials are killed with SIGKILL, no
|
|
63
|
-
* further trials are scheduled, and `time()` resolves (never rejects)
|
|
64
|
-
* with the document built from whatever measurements had already
|
|
65
|
-
* completed, plus an `aborted` warning on the document's last
|
|
66
|
-
* measurement. */
|
|
67
|
-
signal?: AbortSignal;
|
|
68
|
-
/** Exit codes to treat as success (hyperfine's `--ignore-failure`): a
|
|
69
|
-
* trial exiting with one of these still contributes its sample and gets
|
|
70
|
-
* no `nonzero-exit` warning, as if it had exited 0. `CommandSpec.ignoreExitCodes`
|
|
71
|
-
* overrides it per command. */
|
|
72
|
-
ignoreExitCodes?: number[];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export declare function time(opts: TimeOptions): Promise<ProfileDocument>;
|
|
76
|
-
|
|
77
|
-
interface ProfileOptions {
|
|
78
|
-
intervalUs?: number;
|
|
79
|
-
origin?: "inspector" | "jsc";
|
|
80
|
-
/** `profile()` runs `fn` in this process, so there's no child to kill: an
|
|
81
|
-
* already-aborted signal skips the profiler instrumentation entirely and
|
|
82
|
-
* just calls `fn` plain (still returning its `result`), with an `aborted`
|
|
83
|
-
* warning in place of CPU evidence. A signal that fires mid-capture can't
|
|
84
|
-
* interrupt `fn` once it's running. */
|
|
85
|
-
signal?: AbortSignal;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
interface ProfileResult<T> {
|
|
89
|
-
result: T;
|
|
90
|
-
measurement: Measurement;
|
|
91
|
-
document: ProfileDocument;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export declare function profile<T>(fn: () => T | Promise<T>, opts?: ProfileOptions): Promise<ProfileResult<T>>;
|
|
95
|
-
|
|
96
3
|
export interface WorkloadConfig {
|
|
97
4
|
label?: string;
|
|
98
5
|
command?: string[];
|
|
99
6
|
suites?: string[];
|
|
100
|
-
/** `command` only. Globs of the files
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* nothing and caches until the command or config changes. */
|
|
7
|
+
/** `command` only. Globs of the files the timing depends on: `ostia ci`
|
|
8
|
+
* reuses a cached run while their contents are unchanged. Omitted, the
|
|
9
|
+
* workload always reruns; `[]` caches until the command or config changes. */
|
|
104
10
|
inputs?: string[];
|
|
105
11
|
/** `command` only. Runs before every trial (warmup included), unmeasured:
|
|
106
|
-
* a command string / argv array in
|
|
107
|
-
* function
|
|
108
|
-
* uncacheable for `ostia ci` (its effect can't be fingerprinted), so it
|
|
109
|
-
* always executes. */
|
|
12
|
+
* a command string / argv array, or in `ostia.config.ts` a function. A
|
|
13
|
+
* function makes the workload uncacheable for `ostia ci`. */
|
|
110
14
|
prepare?: PrepareHook;
|
|
111
15
|
/** `command` only. Take timing from a number in the command's own output
|
|
112
16
|
* instead of its wall clock; see `TimeSource`. */
|
|
113
17
|
timeSource?: TimeSource;
|
|
114
|
-
/** `command
|
|
115
|
-
*
|
|
116
|
-
*
|
|
18
|
+
/** `command`: kills a trial (or prepare hook) after this many ms.
|
|
19
|
+
* `suites`: kills a suite file's (or isolated task's) process after this
|
|
20
|
+
* many ms, overriding `bench.timeoutMs`. Overrides `ostia ci`'s 10-minute
|
|
21
|
+
* default. */
|
|
117
22
|
timeoutMs?: number;
|
|
118
23
|
/** `command` only. Exit codes to treat as success; see
|
|
119
24
|
* `TimeOptions.ignoreExitCodes`. */
|
|
@@ -121,10 +26,9 @@ export interface WorkloadConfig {
|
|
|
121
26
|
}
|
|
122
27
|
|
|
123
28
|
interface BenchConfig {
|
|
124
|
-
/** Suite file globs, resolved
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* with it. */
|
|
29
|
+
/** Suite file globs, resolved against the config's directory (e.g.
|
|
30
|
+
* "bench/**\/*.bench.ts"). Suite files given on the command line replace
|
|
31
|
+
* this list. */
|
|
128
32
|
suites?: string[];
|
|
129
33
|
preload?: string[];
|
|
130
34
|
/** Extra flags for the `bun` process that runs each suite file; see
|
|
@@ -142,18 +46,37 @@ interface BenchConfig {
|
|
|
142
46
|
filter?: string;
|
|
143
47
|
isolate?: boolean;
|
|
144
48
|
outDir?: string;
|
|
145
|
-
/** Kills a suite file's (or isolated task's) subprocess
|
|
146
|
-
*
|
|
49
|
+
/** Kills a suite file's (or isolated task's) subprocess after this many
|
|
50
|
+
* ms; overrides `ostia ci`'s 10-minute default. */
|
|
147
51
|
timeoutMs?: number;
|
|
148
52
|
}
|
|
149
53
|
|
|
54
|
+
export interface AbConfig {
|
|
55
|
+
/** Shell command(s) run once in a freshly extracted base tree, e.g. to
|
|
56
|
+
* build gitignored files the suites import; see `AbOptions.baseSetup`.
|
|
57
|
+
* `--base-setup` replaces this list. */
|
|
58
|
+
setup?: string | string[];
|
|
59
|
+
/** Base trees to keep cached; see `AbOptions.keepTrees`. `--keep-trees`
|
|
60
|
+
* overrides. Default: 5. */
|
|
61
|
+
keepTrees?: number;
|
|
62
|
+
/** Compare retained heap per call; see `AbOptions.alloc`. `--alloc` /
|
|
63
|
+
* `--no-alloc` override. `bench.alloc` doesn't apply to `ab`. */
|
|
64
|
+
alloc?: boolean;
|
|
65
|
+
/** Compare peak RSS of the first call; see `AbOptions.peakMem`.
|
|
66
|
+
* `--peak-mem` / `--no-peak-mem` override. `bench.peakMem` doesn't apply
|
|
67
|
+
* to `ab`. */
|
|
68
|
+
peakMem?: boolean;
|
|
69
|
+
/** Memory verdict threshold, percent; see `AbOptions.memThresholdPct`.
|
|
70
|
+
* `--mem-threshold` overrides. Default: 10. */
|
|
71
|
+
memThresholdPct?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
150
74
|
export interface OstiaConfig {
|
|
151
|
-
/** `command` workloads: exact trial count
|
|
152
|
-
* Unset, the `budgetMs`/`minSamples` loop decides. */
|
|
75
|
+
/** `command` workloads: exact trial count; see `time()`. */
|
|
153
76
|
samples?: number;
|
|
154
|
-
/** `command` workloads:
|
|
77
|
+
/** `command` workloads: sampling budget in ms; see `time()`. */
|
|
155
78
|
budgetMs?: number;
|
|
156
|
-
/** `command` workloads:
|
|
79
|
+
/** `command` workloads: floor on trials; see `time()`. */
|
|
157
80
|
minSamples?: number;
|
|
158
81
|
warmup: number;
|
|
159
82
|
outDir: string;
|
|
@@ -162,20 +85,15 @@ export interface OstiaConfig {
|
|
|
162
85
|
thresholds: Thresholds;
|
|
163
86
|
workloads: WorkloadConfig[];
|
|
164
87
|
bench?: BenchConfig;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* error, a handful of new workloads next to an otherwise-matching
|
|
171
|
-
* baseline is not. */
|
|
88
|
+
ab?: AbConfig;
|
|
89
|
+
/** `ostia ci`'s policy for a workload with no matching baseline row (by
|
|
90
|
+
* workload id): `"fail"` exits 2 naming the baseline file, `"warn"` lists
|
|
91
|
+
* it in the report. Unset: `"fail"` when every workload is missing (a
|
|
92
|
+
* stale or wrong baseline), `"warn"` otherwise. */
|
|
172
93
|
onMissingBaseline?: "warn" | "fail";
|
|
173
|
-
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* so `compare`'s noise-floor threshold widening applies to `ci` too, not
|
|
177
|
-
* only to ad hoc `time`/`bench` runs. `--no-noise-check` overrides this
|
|
178
|
-
* to false per invocation. */
|
|
94
|
+
/** Measure the machine's noise floor once per `ostia ci` run (default
|
|
95
|
+
* true) and stamp it on the candidate document, so `compare` widens its
|
|
96
|
+
* thresholds accordingly. `--no-noise-check` overrides this per run. */
|
|
179
97
|
noiseCheck?: boolean;
|
|
180
98
|
}
|
|
181
99
|
|
|
@@ -188,11 +106,9 @@ export interface Thresholds {
|
|
|
188
106
|
frameSelfPct: number;
|
|
189
107
|
heapTypePct: number;
|
|
190
108
|
minFrameSelfUs: number;
|
|
191
|
-
/**
|
|
192
|
-
* `improved` verdict also requires `pValue < alpha`. */
|
|
109
|
+
/** A `regressed` / `improved` verdict also requires Mann-Whitney `pValue < alpha`. */
|
|
193
110
|
alpha: number;
|
|
194
|
-
/** Bootstrap resample rounds for the timing CI.
|
|
195
|
-
* see `bootstrapMedianDiffCi`. */
|
|
111
|
+
/** Bootstrap resample rounds for the timing CI. */
|
|
196
112
|
bootstrapIterations: number;
|
|
197
113
|
}
|
|
198
114
|
|
|
@@ -217,36 +133,27 @@ export interface ProfileDocument {
|
|
|
217
133
|
os: string;
|
|
218
134
|
arch: string;
|
|
219
135
|
};
|
|
136
|
+
/** ISO timestamp; metadata only, never part of an id. */
|
|
220
137
|
createdAt: string;
|
|
221
138
|
workloads: Workload[];
|
|
222
139
|
measurements: Measurement[];
|
|
223
140
|
comparisons?: Comparison[];
|
|
224
|
-
/** Aggregate across `comparisons
|
|
225
|
-
* `ostia ci`. Additive, no schema bump. Absent wherever `comparisons` is. */
|
|
141
|
+
/** Aggregate across `comparisons`; present wherever `comparisons` is. */
|
|
226
142
|
comparisonSummary?: ComparisonSummary;
|
|
227
|
-
/** Workload ids
|
|
228
|
-
*
|
|
229
|
-
* Additive, no schema bump; absent wherever `comparisons` is. Ids only
|
|
230
|
-
* (not full `Workload`s) to keep the document small; `ostia compare`'s
|
|
231
|
-
* printed report resolves labels from the two source documents directly. */
|
|
143
|
+
/** Workload ids on only one side of the comparison; present wherever
|
|
144
|
+
* `comparisons` is. */
|
|
232
145
|
unmatched?: {
|
|
233
146
|
baseOnly: string[];
|
|
234
147
|
candOnly: string[];
|
|
235
148
|
};
|
|
236
|
-
/** Machine conditions
|
|
237
|
-
*
|
|
238
|
-
* skipped the reference measurement. */
|
|
149
|
+
/** Machine conditions at measurement time; absent when the noise check was
|
|
150
|
+
* skipped. */
|
|
239
151
|
environment?: Environment;
|
|
240
|
-
/** Run-level result of
|
|
241
|
-
* candidate was paired against and the gate's verdict. Additive, no schema
|
|
242
|
-
* bump. Present only on documents from `ab()`, whose measurements are
|
|
152
|
+
/** Run-level result of `ostia ab` / `ab()`, whose measurements are
|
|
243
153
|
* `phase: "paired"`. */
|
|
244
154
|
ab?: AbSummary;
|
|
245
|
-
/** Repo state
|
|
246
|
-
*
|
|
247
|
-
* bump. Absent outside a git repo (or when `git` itself isn't
|
|
248
|
-
* available). Metadata only: never part of any fingerprint or id, so a
|
|
249
|
-
* commit or a dirty working tree never orphans a cached run or baseline. */
|
|
155
|
+
/** Repo state of the process's cwd; absent outside a git repo. Never part of
|
|
156
|
+
* an id or fingerprint, so a commit or dirty tree doesn't orphan a baseline. */
|
|
250
157
|
git?: GitMetadata;
|
|
251
158
|
}
|
|
252
159
|
|
|
@@ -257,9 +164,8 @@ interface GitMetadata {
|
|
|
257
164
|
}
|
|
258
165
|
|
|
259
166
|
interface NoiseFloor {
|
|
260
|
-
/** `mad / median` of
|
|
261
|
-
*
|
|
262
|
-
* measured. */
|
|
167
|
+
/** `mad / median` of a reference workload, percent: how noisy the machine is
|
|
168
|
+
* right now, independent of what's measured. */
|
|
263
169
|
floorPct: number;
|
|
264
170
|
referenceMedianNs: number;
|
|
265
171
|
samples: number;
|
|
@@ -274,69 +180,47 @@ interface Environment {
|
|
|
274
180
|
}
|
|
275
181
|
|
|
276
182
|
export interface Workload {
|
|
277
|
-
/** Identifies what is measured, not where or when
|
|
278
|
-
* `
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* measured from another. For an `inprocess` workload, a hash of the
|
|
283
|
-
* function source (or, for a registry entry, the file/task name plus
|
|
284
|
-
* `params`). `label`, `description`, and `baseline` are annotations and
|
|
285
|
-
* never affect it. */
|
|
183
|
+
/** Identifies what is measured, not where or when. Subprocess: a hash of
|
|
184
|
+
* argv, `prepare` and `timeSource`, excluding `process.cwd()`. In-process: a
|
|
185
|
+
* hash of the function source, or for a registry entry the file, task name
|
|
186
|
+
* and `params`. Annotations (`label`, `description`, `baseline`) never
|
|
187
|
+
* affect it. */
|
|
286
188
|
id: string;
|
|
287
189
|
kind: "subprocess" | "inprocess";
|
|
288
190
|
label?: string;
|
|
289
191
|
command?: string[];
|
|
290
|
-
/** Command
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* step measures different things. A function-form hook isn't
|
|
294
|
-
* serializable and is omitted here (its source text is still hashed into
|
|
295
|
-
* the id). */
|
|
192
|
+
/** Command-form `prepare` hook run before every trial; part of the id. A
|
|
193
|
+
* function-form hook isn't serializable and is omitted (its source is still
|
|
194
|
+
* hashed into the id). */
|
|
296
195
|
prepare?: string[];
|
|
297
|
-
/**
|
|
298
|
-
* wall clock
|
|
299
|
-
* number it captures. Part of the workload id, so the same command timed
|
|
300
|
-
* by wall clock and by its own report are two workloads. */
|
|
196
|
+
/** Takes samples from a number in the command's own output instead of the
|
|
197
|
+
* wall clock; part of the id. */
|
|
301
198
|
timeSource?: {
|
|
302
199
|
pattern: string;
|
|
303
200
|
group?: number;
|
|
304
201
|
unit?: "ns" | "us" | "ms" | "s";
|
|
305
202
|
};
|
|
306
|
-
/** `task` is the "group/name" id
|
|
307
|
-
*
|
|
308
|
-
* splitting `task` on "/", so task names may contain slashes. */
|
|
203
|
+
/** `task` is the registry's "group/name" id; `group` is the enclosing
|
|
204
|
+
* `group()` name. Renderers prefer `group` over splitting `task` on "/". */
|
|
309
205
|
entry?: {
|
|
310
206
|
file: string;
|
|
311
207
|
task: string;
|
|
312
208
|
group?: string;
|
|
313
209
|
};
|
|
314
|
-
/**
|
|
315
|
-
* `task(name, fn, { baseline: true })`). At most one per group is
|
|
316
|
-
* meaningful; renderers use the first they encounter. */
|
|
210
|
+
/** The Relative reference for its group; renderers use the first one found. */
|
|
317
211
|
baseline?: boolean;
|
|
318
|
-
/** What this task measures and why, from `task(
|
|
319
|
-
* Travels with the data so a reader of the document has intent, not just
|
|
320
|
-
* numbers. */
|
|
212
|
+
/** What this task measures and why, from `task(..., { description })`. */
|
|
321
213
|
description?: string;
|
|
322
|
-
/** The enclosing group's
|
|
323
|
-
* workload in the group so each record is self-contained. */
|
|
214
|
+
/** The enclosing group's description, repeated on every workload in it. */
|
|
324
215
|
groupDescription?: string;
|
|
325
|
-
/**
|
|
326
|
-
* on the task, its group, or the suite), vs. sharing its suite file's
|
|
327
|
-
* subprocess with other tasks. */
|
|
216
|
+
/** Ran in a subprocess of its own rather than sharing its suite file's. */
|
|
328
217
|
isolated?: boolean;
|
|
329
|
-
/**
|
|
330
|
-
*
|
|
331
|
-
* Lets renderers pivot and `compare` match on them instead of only on the
|
|
332
|
-
* task name. Part of the workload id when present, so two points that
|
|
333
|
-
* share a task name (a `sweep()`'s whole point) don't collide. */
|
|
218
|
+
/** From `task(..., { params })` or a `sweep()` point. Part of the id, so
|
|
219
|
+
* points sharing a task name don't collide. */
|
|
334
220
|
params?: Record<string, string | number | boolean>;
|
|
335
|
-
/** From `task.skip()`
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
* `compare` treats it as `unchanged` (with a `skipped` warning) rather
|
|
339
|
-
* than silently passing or failing to match it. */
|
|
221
|
+
/** From `task.skip()` / `group.skip()`: never measured, so no `Measurement`.
|
|
222
|
+
* Renderers print a "- skipped" row; `compare` treats it as `unchanged` with
|
|
223
|
+
* a `skipped` warning. */
|
|
340
224
|
skipped?: boolean;
|
|
341
225
|
}
|
|
342
226
|
|
|
@@ -355,38 +239,39 @@ interface Measurement {
|
|
|
355
239
|
heap?: HeapEvidence;
|
|
356
240
|
memory?: MemoryEvidence;
|
|
357
241
|
jit?: JitTierBreakdown;
|
|
358
|
-
/** `phase: "paired"` only
|
|
359
|
-
* measurement's `timing` is the candidate side. */
|
|
242
|
+
/** `phase: "paired"` only; `timing` is then the candidate side. */
|
|
360
243
|
paired?: PairedEvidence;
|
|
244
|
+
/** `phase: "paired"` only: the task threw on one side or both
|
|
245
|
+
* (`"both"`). Usually instead of `timing`/`paired`; when it threw in a
|
|
246
|
+
* confirmation repeat, `repeat` (from 1) says which, and `timing`/`paired`
|
|
247
|
+
* stay as the first process measured them. Either way the task isn't in
|
|
248
|
+
* `matched` or the geomean. */
|
|
249
|
+
threw?: {
|
|
250
|
+
side: "base" | "cand" | "both";
|
|
251
|
+
message: string;
|
|
252
|
+
repeat?: number;
|
|
253
|
+
};
|
|
361
254
|
warnings: Warning[];
|
|
362
255
|
artifacts: ArtifactRef[];
|
|
363
|
-
/**
|
|
364
|
-
*
|
|
365
|
-
* for 2+ commands) rather than run to completion before the next command
|
|
366
|
-
* started, so drift over the run's wall-clock span (thermal throttling, a
|
|
367
|
-
* noisy neighbor process) lands on every command equally instead of
|
|
368
|
-
* favoring whichever ran first or last. */
|
|
256
|
+
/** Trials ran round-robin with the other commands of the same `time()` call,
|
|
257
|
+
* so drift over the run hits every command equally. */
|
|
369
258
|
interleaved?: boolean;
|
|
370
259
|
}
|
|
371
260
|
|
|
372
261
|
interface Trial {
|
|
373
262
|
i: number;
|
|
374
263
|
wallNs: number;
|
|
375
|
-
/** The command's self-reported time (ns)
|
|
376
|
-
* `
|
|
377
|
-
* `wallNs` stays alongside so a document keeps both. */
|
|
264
|
+
/** The command's self-reported time (ns) under a `timeSource`; these, not
|
|
265
|
+
* `wallNs`, are then the `timing.samples`. */
|
|
378
266
|
reportedNs?: number;
|
|
379
267
|
exitCode?: number;
|
|
380
268
|
userNs?: number;
|
|
381
269
|
systemNs?: number;
|
|
382
270
|
maxRssBytes?: number;
|
|
383
|
-
/**
|
|
384
|
-
* own. `exitCode` is absent in that case: the kill signal, not the
|
|
385
|
-
* command, decided how the process ended. Contributes no sample. */
|
|
271
|
+
/** Killed by `timeoutMs`; `exitCode` is absent. Contributes no sample. */
|
|
386
272
|
timedOut?: true;
|
|
387
|
-
/**
|
|
388
|
-
*
|
|
389
|
-
* fallback to `wallNs`). Contributes no sample. */
|
|
273
|
+
/** Output didn't match the `timeSource` pattern; `reportedNs` is absent
|
|
274
|
+
* (never a `wallNs` fallback). Contributes no sample. */
|
|
390
275
|
timeSourceNoMatch?: true;
|
|
391
276
|
}
|
|
392
277
|
|
|
@@ -408,15 +293,11 @@ interface TimingStats {
|
|
|
408
293
|
p75: number;
|
|
409
294
|
/** 99th percentile, ns. */
|
|
410
295
|
p99: number;
|
|
411
|
-
/** Median absolute deviation, ns:
|
|
412
|
-
*
|
|
413
|
-
* by the long right tail typical of wall-clock timings. */
|
|
296
|
+
/** Median absolute deviation, ns: a spread measure that, unlike stddev, the
|
|
297
|
+
* long right tail of wall-clock timings doesn't skew. */
|
|
414
298
|
mad: number;
|
|
415
|
-
/**
|
|
416
|
-
*
|
|
417
|
-
* Absent for every subprocess timing measurement, and for an in-process
|
|
418
|
-
* one whose single call already cleared the batching threshold - a
|
|
419
|
-
* renderer treats an absent value the same as `1`. */
|
|
299
|
+
/** Calls batched into one timed block; absent (read as 1) unless batching
|
|
300
|
+
* occurred, and always absent for subprocess timing. */
|
|
420
301
|
batch?: number;
|
|
421
302
|
}
|
|
422
303
|
|
|
@@ -466,25 +347,16 @@ interface HeapEvidence {
|
|
|
466
347
|
|
|
467
348
|
interface MemoryEvidence {
|
|
468
349
|
origin: "resourceUsage" | "heapStats";
|
|
469
|
-
/**
|
|
470
|
-
* `--
|
|
471
|
-
* Absent on subprocess timing's `maxRssBytes`, and on `memstats`
|
|
472
|
-
* measurements written before this field existed, which are always
|
|
473
|
-
* `"retained"`. */
|
|
350
|
+
/** `memstats` only: `"retained"` (`--alloc`, `bytesPerOp`) or `"peak"`
|
|
351
|
+
* (`--peak-mem`, `peakBytes`). Absent means `"retained"` on older documents. */
|
|
474
352
|
kind?: "retained" | "peak";
|
|
475
353
|
/** Largest `Trial.maxRssBytes` across a timing measurement's trials. */
|
|
476
354
|
maxRssBytes?: number;
|
|
477
|
-
/**
|
|
478
|
-
*
|
|
479
|
-
* `process.memoryUsage().heapUsed`) around one batch bracketed by
|
|
480
|
-
* `Bun.gc(true)` on both sides, divided by the batch size. Garbage the
|
|
481
|
-
* calls create and drop is collected before the second reading, so this
|
|
482
|
-
* is what the calls keep alive (a leak check), not what they allocate. */
|
|
355
|
+
/** `--alloc`: heap growth per call across one batch bracketed by full GCs,
|
|
356
|
+
* so what the calls keep alive (a leak check), not what they allocate. */
|
|
483
357
|
bytesPerOp?: number;
|
|
484
|
-
/**
|
|
485
|
-
*
|
|
486
|
-
* processes, each making the task's first call after its `before` hooks
|
|
487
|
-
* and a full GC. A `peak-hidden` warning says when it may read low. */
|
|
358
|
+
/** `--peak-mem`: how far one call raised RSS, garbage included; median over
|
|
359
|
+
* 3 fresh processes. A `peak-hidden` warning says when it may read low. */
|
|
488
360
|
peakBytes?: number;
|
|
489
361
|
}
|
|
490
362
|
|
|
@@ -504,8 +376,7 @@ interface JitTierBreakdown {
|
|
|
504
376
|
}
|
|
505
377
|
|
|
506
378
|
interface PairedEvidence {
|
|
507
|
-
/**
|
|
508
|
-
* which goes first. */
|
|
379
|
+
/** One base batch and one candidate batch per round, alternating order. */
|
|
509
380
|
rounds: number;
|
|
510
381
|
/** Calls per side per round. */
|
|
511
382
|
batch: number;
|
|
@@ -517,27 +388,48 @@ interface PairedEvidence {
|
|
|
517
388
|
ratios: number[];
|
|
518
389
|
medianRatio: number;
|
|
519
390
|
/** 25th/75th percentile of `ratios`. */
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
*
|
|
524
|
-
* quarters of rounds. */
|
|
391
|
+
ratioP25: number;
|
|
392
|
+
ratioP75: number;
|
|
393
|
+
/** Median ratio past `1 ± threshold` and on the same side of 1 in at least
|
|
394
|
+
* three quarters of rounds. */
|
|
525
395
|
flagged?: "regressed" | "improved";
|
|
526
396
|
/** Each fresh-process re-measurement of a flagged workload. */
|
|
527
397
|
repeats?: {
|
|
528
398
|
medianRatio: number;
|
|
529
|
-
|
|
530
|
-
|
|
399
|
+
ratioP25: number;
|
|
400
|
+
ratioP75: number;
|
|
531
401
|
flagged?: "regressed" | "improved";
|
|
532
402
|
}[];
|
|
533
403
|
/** Set when `flagged` is: whether every repeat flagged the same way. */
|
|
534
404
|
confirmed?: boolean;
|
|
535
405
|
/** `flagged` when confirmed, else `"unchanged"`. */
|
|
536
406
|
verdict: "regressed" | "improved" | "unchanged";
|
|
537
|
-
/**
|
|
538
|
-
*
|
|
539
|
-
* `false` for a change in behavior, never a failure. */
|
|
407
|
+
/** First call's return values were deep-equal on both sides; informational,
|
|
408
|
+
* never a failure. */
|
|
540
409
|
sameOutput: boolean;
|
|
410
|
+
/** Set when the suite file's text differs from the base ref's copy, so
|
|
411
|
+
* each side may run a different benchmark (a fixed fixture, a changed
|
|
412
|
+
* input). With `sameOutput: false` too, the task is "not comparable": its
|
|
413
|
+
* verdict is `"unchanged"` and it's left out of the geomean. */
|
|
414
|
+
suiteChanged?: true;
|
|
415
|
+
/** `--alloc`: heap each call keeps alive after a full GC, per side, after
|
|
416
|
+
* a warmup batch; each the median of 3 fresh processes. */
|
|
417
|
+
retained?: MemoryChange;
|
|
418
|
+
/** `--peak-mem`: how far the task's first call raises RSS, per side,
|
|
419
|
+
* each the median of 3 fresh processes. */
|
|
420
|
+
peak?: MemoryChange;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
interface MemoryChange {
|
|
424
|
+
baseBytes: number;
|
|
425
|
+
candBytes: number;
|
|
426
|
+
/** Past both `AbSummary.memory.thresholdPct` of the base and
|
|
427
|
+
* `floorBytes`; always `"unchanged"` on a not-comparable task. */
|
|
428
|
+
verdict: "regressed" | "improved" | "unchanged";
|
|
429
|
+
/** The smallest change that counts, bytes: 16 KiB over the batch's calls
|
|
430
|
+
* for `retained`; for `peak`, 1 MiB, or the freed memory a `peak-hidden`
|
|
431
|
+
* warning reports. */
|
|
432
|
+
floorBytes: number;
|
|
541
433
|
}
|
|
542
434
|
|
|
543
435
|
interface AbSummary {
|
|
@@ -545,6 +437,9 @@ interface AbSummary {
|
|
|
545
437
|
ref: string;
|
|
546
438
|
sha: string;
|
|
547
439
|
};
|
|
440
|
+
/** Suite files, as given, that don't exist at the base ref: their tasks
|
|
441
|
+
* are all candidate-only. */
|
|
442
|
+
newSuites?: string[];
|
|
548
443
|
rounds: number;
|
|
549
444
|
thresholdPct: number;
|
|
550
445
|
geomeanThresholdPct: number;
|
|
@@ -559,17 +454,31 @@ interface AbSummary {
|
|
|
559
454
|
unconfirmed: number;
|
|
560
455
|
/** Workloads whose first call returned different values on each side. */
|
|
561
456
|
outputDiffers: number;
|
|
562
|
-
/**
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
|
|
457
|
+
/** Workloads whose suite file and output both changed (see
|
|
458
|
+
* `PairedEvidence.suiteChanged`); counted in `unchanged`, left out of
|
|
459
|
+
* `geomeanPct`. */
|
|
460
|
+
notComparable: number;
|
|
461
|
+
/** Workloads that threw on either side or both, and so weren't timed;
|
|
462
|
+
* not counted in `matched`. */
|
|
463
|
+
threw: number;
|
|
464
|
+
/** Present when any task had a memory reading (`--alloc`, `--peak-mem`):
|
|
465
|
+
* how many tasks' memory regressed or improved, on either reading. */
|
|
466
|
+
memory?: {
|
|
467
|
+
thresholdPct: number;
|
|
468
|
+
regressed: number;
|
|
469
|
+
improved: number;
|
|
470
|
+
};
|
|
471
|
+
/** Geometric mean of the workloads' median ratios, signed percent (negative:
|
|
472
|
+
* candidate faster); a flagged workload contributes its median over its main
|
|
473
|
+
* run and repeats. `null` when nothing was paired. */
|
|
566
474
|
geomeanPct: number | null;
|
|
567
|
-
/** `"fail"` when any workload regressed (confirmed)
|
|
568
|
-
* exceeds `geomeanThresholdPct
|
|
475
|
+
/** `"fail"` when any workload regressed (confirmed), threw on the
|
|
476
|
+
* candidate side only, or `geomeanPct` exceeds `geomeanThresholdPct`, or
|
|
477
|
+
* any task's memory regressed. */
|
|
569
478
|
verdict: "pass" | "fail";
|
|
570
479
|
}
|
|
571
480
|
|
|
572
|
-
declare const WARNING_CODES: readonly ["slow-first-run", "outliers-detected", "fast-command", "nonzero-exit", "artifact-missing", "empty-profile", "low-sample-count", "thin-comparison", "noisy-machine", "skipped", "jit-cold", "timeout", "aborted", "time-source-no-match", "environment-mismatch", "peak-hidden"];
|
|
481
|
+
declare const WARNING_CODES: readonly ["slow-first-run", "outliers-detected", "fast-command", "nonzero-exit", "artifact-missing", "empty-profile", "low-sample-count", "thin-comparison", "noisy-machine", "skipped", "jit-cold", "timeout", "aborted", "time-source-no-match", "environment-mismatch", "peak-hidden", "suite-changed"];
|
|
573
482
|
|
|
574
483
|
export type WarningCode = (typeof WARNING_CODES)[number];
|
|
575
484
|
|
|
@@ -594,19 +503,16 @@ export interface Comparison {
|
|
|
594
503
|
timing?: {
|
|
595
504
|
medianDeltaPct: number;
|
|
596
505
|
meanDeltaPct: number;
|
|
597
|
-
/** 95% bootstrap
|
|
598
|
-
*
|
|
599
|
-
* than 5 samples (see the `thin-comparison` warning). */
|
|
506
|
+
/** 95% bootstrap CI on the difference of medians, percent of the baseline
|
|
507
|
+
* median. Absent below 5 samples a side (`thin-comparison`). */
|
|
600
508
|
ci95?: [number, number];
|
|
601
|
-
/** Two-sided Mann-Whitney U p-value
|
|
602
|
-
* Same absence condition as `ci95`. */
|
|
509
|
+
/** Two-sided Mann-Whitney U p-value; absent whenever `ci95` is. */
|
|
603
510
|
pValue?: number;
|
|
604
|
-
/**
|
|
511
|
+
/** Bootstrap PRNG seed, so `ci95` is reproducible. */
|
|
605
512
|
seed?: number;
|
|
606
513
|
verdict: "improved" | "regressed" | "unchanged";
|
|
607
514
|
};
|
|
608
|
-
/**
|
|
609
|
-
* samples) or otherwise carries a caveat about this comparison. */
|
|
515
|
+
/** Caveats about this comparison, e.g. a thin-sample point-estimate verdict. */
|
|
610
516
|
warnings?: Warning[];
|
|
611
517
|
frames?: {
|
|
612
518
|
frameKey: string;
|
|
@@ -630,27 +536,23 @@ export interface Comparison {
|
|
|
630
536
|
minFrameSelfUs: number;
|
|
631
537
|
alpha: number;
|
|
632
538
|
bootstrapIterations: number;
|
|
633
|
-
/** `max(timingPct,
|
|
634
|
-
* cand.environment.noise.floorPct)` - the threshold timing was actually
|
|
635
|
-
* tested against, once machine noise widens it past `timingPct`. */
|
|
539
|
+
/** `max(timingPct, both documents' noise floors)`: what timing was tested against. */
|
|
636
540
|
effectiveTimingPct: number;
|
|
637
541
|
};
|
|
638
542
|
verdict: "pass" | "fail";
|
|
639
543
|
}
|
|
640
544
|
|
|
641
545
|
interface ComparisonSummary {
|
|
642
|
-
/** `comparisons.length
|
|
546
|
+
/** `comparisons.length`. */
|
|
643
547
|
matched: number;
|
|
644
548
|
regressed: number;
|
|
645
549
|
improved: number;
|
|
646
550
|
unchanged: number;
|
|
647
|
-
/** Geometric mean of `cand/base` median ratios
|
|
648
|
-
*
|
|
649
|
-
* average). `null` when no comparison had a finite timing ratio. */
|
|
551
|
+
/** Geometric mean of `cand/base` median ratios, signed percent (negative:
|
|
552
|
+
* candidate faster). `null` when no comparison had a finite ratio. */
|
|
650
553
|
geomeanPct: number | null;
|
|
651
|
-
/**
|
|
652
|
-
*
|
|
653
|
-
* the two documents' `environment.noise.floorPct`, never per-workload. */
|
|
554
|
+
/** `Comparison.thresholds.effectiveTimingPct`, which is the same for the
|
|
555
|
+
* whole document pair. */
|
|
654
556
|
effectiveTimingPct: number;
|
|
655
557
|
/** `"fail"` when any comparison's verdict is `"fail"`. */
|
|
656
558
|
verdict: "pass" | "fail";
|
|
@@ -673,36 +575,36 @@ export interface TimeSource {
|
|
|
673
575
|
unit?: TimeUnit;
|
|
674
576
|
}
|
|
675
577
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
578
|
+
export type AbProgress = {
|
|
579
|
+
phase: "setup";
|
|
580
|
+
command: string;
|
|
581
|
+
} | {
|
|
582
|
+
phase: "measure";
|
|
583
|
+
suite: number;
|
|
584
|
+
suites: number;
|
|
585
|
+
file: string;
|
|
586
|
+
/** Each suite runs twice, once with each side first. */
|
|
587
|
+
pass: number;
|
|
588
|
+
passes: number;
|
|
589
|
+
task: number;
|
|
590
|
+
tasks: number;
|
|
591
|
+
label: string;
|
|
592
|
+
} | {
|
|
593
|
+
phase: "confirm";
|
|
594
|
+
repeat: number;
|
|
595
|
+
repeats: number;
|
|
596
|
+
label: string;
|
|
597
|
+
} | {
|
|
598
|
+
phase: "memory";
|
|
599
|
+
run: number;
|
|
600
|
+
runs: number;
|
|
601
|
+
side: Side;
|
|
602
|
+
label: string;
|
|
603
|
+
};
|
|
702
604
|
|
|
703
|
-
interface AbOptions {
|
|
605
|
+
export interface AbOptions {
|
|
704
606
|
suites: string[];
|
|
705
|
-
/** Git ref whose committed tree is the base side (default: `"HEAD"`)
|
|
607
|
+
/** Git ref whose committed tree is the base side (default: `"HEAD"`); the
|
|
706
608
|
* candidate is the working tree, uncommitted changes included. */
|
|
707
609
|
base?: string;
|
|
708
610
|
/** Rounds per workload, each one base batch and one candidate batch of
|
|
@@ -715,9 +617,8 @@ interface AbOptions {
|
|
|
715
617
|
* this many percent (default: 1.5): a broad slowdown too small to flag any
|
|
716
618
|
* one workload. */
|
|
717
619
|
geomeanThresholdPct?: number;
|
|
718
|
-
/** Fresh-process repeats of each flagged workload
|
|
719
|
-
* every repeat flags it the same way (default: 2; 0 trusts the first
|
|
720
|
-
* process). */
|
|
620
|
+
/** Fresh-process repeats of each flagged workload, which only counts when
|
|
621
|
+
* every repeat flags it the same way (default: 2; 0 trusts the first). */
|
|
721
622
|
confirm?: number;
|
|
722
623
|
filter?: string;
|
|
723
624
|
preload?: string[];
|
|
@@ -725,98 +626,112 @@ interface AbOptions {
|
|
|
725
626
|
outDir?: string;
|
|
726
627
|
cwd?: string;
|
|
727
628
|
noiseCheck?: boolean;
|
|
728
|
-
/**
|
|
729
|
-
*
|
|
629
|
+
/** Also compare each side's retained heap per call (see
|
|
630
|
+
* `bench({ alloc })`) and how far its first call raises RSS (see
|
|
631
|
+
* `bench({ peakMem })`), each the median of 3 fresh processes per side.
|
|
632
|
+
* Task/group options override these. */
|
|
633
|
+
alloc?: boolean;
|
|
634
|
+
peakMem?: boolean;
|
|
635
|
+
/** Flag a memory reading that moves past this many percent of the base's
|
|
636
|
+
* and past its noise floor (default: 10). A memory regression fails the
|
|
637
|
+
* run. */
|
|
638
|
+
memThresholdPct?: number;
|
|
639
|
+
/** Shell commands run once, in order, in a freshly extracted base tree
|
|
640
|
+
* before it's used: to build what the suites import but git doesn't hold
|
|
641
|
+
* (generated or gitignored files). Each runs with its cwd at the tree's
|
|
642
|
+
* counterpart of `cwd`, the project's `node_modules` linked in, and
|
|
643
|
+
* `OSTIA_AB_SHA` / `OSTIA_AB_CANDIDATE_DIR` (the working tree's `cwd`) in
|
|
644
|
+
* its environment. The tree is cached per commit and command list. */
|
|
645
|
+
baseSetup?: string | string[];
|
|
646
|
+
/** Base trees to keep under `<outDir>/ab`, counting this run's: after a
|
|
647
|
+
* run, older ones go, least recently used first, unless one was used in
|
|
648
|
+
* the last hour (another run may still need it). Default: 5. */
|
|
649
|
+
keepTrees?: number;
|
|
650
|
+
/** SIGKILLs a runner process (one suite file or repeat), or a `baseSetup`
|
|
651
|
+
* command, after this many ms. */
|
|
730
652
|
timeoutMs?: number;
|
|
731
653
|
/** Aborting kills the running process and resolves with the suites that
|
|
732
|
-
* had finished, plus an `aborted` warning; repeats
|
|
654
|
+
* had finished, plus an `aborted` warning; unrun repeats leave their
|
|
733
655
|
* workloads unconfirmed. */
|
|
734
656
|
signal?: AbortSignal;
|
|
657
|
+
/** Called as each setup command, task, confirmation repeat and memory
|
|
658
|
+
* process starts. */
|
|
659
|
+
onProgress?: (progress: AbProgress) => void;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export declare class AbBaseError extends OstiaUsageError {
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export declare class AbSuiteError extends Error {
|
|
666
|
+
readonly side: Side;
|
|
667
|
+
constructor(message: string, side: Side);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
export declare class AbSetupError extends Error {
|
|
735
671
|
}
|
|
736
672
|
|
|
737
673
|
export declare function ab(opts: AbOptions): Promise<ProfileDocument>;
|
|
738
674
|
|
|
739
|
-
|
|
675
|
+
declare class OstiaUsageError extends Error {
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
type Side = "base" | "cand";
|
|
679
|
+
|
|
680
|
+
export interface BenchOptions {
|
|
740
681
|
suites: string[];
|
|
741
682
|
/** Wall-clock sampling budget per task, ms (default 500). */
|
|
742
683
|
budgetMs?: number;
|
|
743
|
-
/** Exact trial count per task
|
|
744
|
-
* in-process equivalent of `time()`'s `samples`. */
|
|
684
|
+
/** Exact trial count per task; the budget is ignored when set. */
|
|
745
685
|
samples?: number;
|
|
746
686
|
minSamples?: number;
|
|
747
687
|
gc?: boolean;
|
|
748
688
|
/** Capture one extra `phase: "cpu"` measurement per task (the task looped
|
|
749
|
-
* under the JSC sampling profiler for
|
|
750
|
-
*
|
|
751
|
-
* `TaskOptions.cpu` / `GroupOptions.cpu` override this per task or group. */
|
|
689
|
+
* under the JSC sampling profiler for ~2,000 samples, JIT tiers included),
|
|
690
|
+
* never mixed into the timing numbers. Task/group `cpu` overrides this. */
|
|
752
691
|
cpu?: boolean;
|
|
753
692
|
/** `cpu`'s sampling interval, µs (default: 100). A coarser interval
|
|
754
693
|
* lengthens the capture to keep the sample count, up to 10s per task. */
|
|
755
694
|
cpuIntervalUs?: number;
|
|
756
695
|
/** Capture one extra `phase: "memstats"` measurement per task: retained
|
|
757
696
|
* heap growth per call, from a batch with `Bun.gc(true)` on both sides.
|
|
758
|
-
* What the calls keep alive, not what they allocate
|
|
759
|
-
*
|
|
760
|
-
* override this per task or group. */
|
|
697
|
+
* What the calls keep alive, not what they allocate. Task/group `alloc`
|
|
698
|
+
* overrides this. */
|
|
761
699
|
alloc?: boolean;
|
|
762
700
|
/** Capture one extra `phase: "memstats"` measurement per task: how far the
|
|
763
|
-
* task's first call raises RSS, garbage included
|
|
764
|
-
*
|
|
765
|
-
* `
|
|
766
|
-
* `TaskOptions.peakMem` / `GroupOptions.peakMem` override this per task or
|
|
767
|
-
* group. */
|
|
701
|
+
* task's first call raises RSS, garbage included. Median of 3 fresh
|
|
702
|
+
* processes, each run with `OSTIA_PEAK_MEM=1` in its environment (see
|
|
703
|
+
* `measurePeakMem`). Task/group `peakMem` overrides this. */
|
|
768
704
|
peakMem?: boolean;
|
|
769
705
|
filter?: string;
|
|
770
|
-
/** Suite files to run at once, each
|
|
771
|
-
* 1).
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
* are noisier and not like-for-like with a baseline measured at 1. When
|
|
775
|
-
* `isolate` puts some tasks in their own subprocess, `jobs` pools across
|
|
776
|
-
* those per-task processes the same way - so the same noise/wall-clock
|
|
777
|
-
* tradeoff now scales with task count, not just file count. */
|
|
706
|
+
/** Suite files (or isolated tasks) to run at once, each in its own child
|
|
707
|
+
* process (default: 1). Concurrent CPU-bound processes contend for cores,
|
|
708
|
+
* caches and turbo headroom, so timings under `jobs > 1` are noisier and
|
|
709
|
+
* not like-for-like with a baseline measured at 1. */
|
|
778
710
|
jobs?: number;
|
|
779
711
|
outDir?: string;
|
|
780
712
|
cwd?: string;
|
|
781
713
|
/** Give every task its own subprocess instead of sharing its suite file's,
|
|
782
|
-
* isolating
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
* task or group for mixed suites (e.g. a few outlier-prone tasks isolated,
|
|
786
|
-
* many cheap ones sharing a process). Multiplies process-spawn overhead by
|
|
787
|
-
* task count instead of file count. */
|
|
714
|
+
* isolating JIT tier state, inline caches and heap shape between tasks.
|
|
715
|
+
* Task/group `isolate` overrides this. Multiplies spawn overhead by task
|
|
716
|
+
* count instead of file count. */
|
|
788
717
|
isolate?: boolean;
|
|
789
|
-
/** Scripts run, in order, before each suite file loads
|
|
790
|
-
* subprocess
|
|
791
|
-
*
|
|
792
|
-
* of the suite's own top-level code. Consumer-authored; ostia ships no
|
|
793
|
-
* preload scripts itself. */
|
|
718
|
+
/** Scripts run, in order, before each suite file loads, in the same
|
|
719
|
+
* subprocess: install globals (jsdom) or a `Bun.plugin()` loader ahead of
|
|
720
|
+
* the suite's top-level code. */
|
|
794
721
|
preload?: string[];
|
|
795
|
-
/** Extra flags
|
|
796
|
-
*
|
|
797
|
-
* script path so `bun` itself parses them rather than the runner. Useful
|
|
798
|
-
* for suites that import packages whose `exports` map branches on a
|
|
799
|
-
* resolution condition Bun doesn't set by default (e.g. Svelte/Vue's
|
|
800
|
-
* `browser` vs `default` builds). */
|
|
722
|
+
/** Extra flags for the `bun` invocation that runs each suite file (e.g.
|
|
723
|
+
* `["--conditions", "browser"]`), placed before the runner script path. */
|
|
801
724
|
bunFlags?: string[];
|
|
802
725
|
/** Measure this machine's noise floor once, before any suite runs
|
|
803
|
-
* (default: true), and stamp it on the document as
|
|
804
|
-
* `environment`. Set false to skip the ~200ms reference measurement. */
|
|
726
|
+
* (default: true), and stamp it on the document as `environment`. */
|
|
805
727
|
noiseCheck?: boolean;
|
|
806
|
-
/**
|
|
807
|
-
*
|
|
808
|
-
* ms. No default: an unset `timeoutMs` never times out. Applies to the
|
|
809
|
-
* whole subprocess, not per task - the same granularity `isolate` already
|
|
810
|
-
* runs at. */
|
|
728
|
+
/** SIGKILLs a suite file's subprocess (or, under `isolate`, one task's) that
|
|
729
|
+
* hasn't finished after this many ms. No default. */
|
|
811
730
|
timeoutMs?: number;
|
|
812
|
-
/** Aborting
|
|
813
|
-
*
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
* measurement. A suite subprocess killed mid-run contributes nothing (it
|
|
817
|
-
* only writes its result once, at the end), so a suite that was in flight
|
|
818
|
-
* when the signal fired is dropped entirely rather than partially
|
|
819
|
-
* represented. */
|
|
731
|
+
/** Aborting kills in-flight subprocesses, starts no new ones, and resolves
|
|
732
|
+
* (never rejects) with whatever suites/tasks had finished, plus an
|
|
733
|
+
* `aborted` warning on the last measurement. A suite killed mid-run is
|
|
734
|
+
* dropped entirely: it only writes its result once, at the end. */
|
|
820
735
|
signal?: AbortSignal;
|
|
821
736
|
}
|
|
822
737
|
|
|
@@ -907,38 +822,39 @@ export interface GroupOptions {
|
|
|
907
822
|
after?: Hook;
|
|
908
823
|
}
|
|
909
824
|
|
|
910
|
-
|
|
911
|
-
|
|
825
|
+
type GroupRegistrar = (name: string, fn: () => void, opts?: GroupOptions) => void;
|
|
826
|
+
|
|
827
|
+
interface GroupFn extends GroupRegistrar {
|
|
912
828
|
/** Registers every task inside as skipped: the runner never measures
|
|
913
829
|
* them, but the document still carries their workloads (marked
|
|
914
830
|
* `Workload.skipped`). */
|
|
915
|
-
skip:
|
|
831
|
+
skip: GroupRegistrar;
|
|
916
832
|
/** When any task or group in the suite uses `.only`, the runner restricts
|
|
917
833
|
* the whole suite file to only those tasks (before `--filter` narrows
|
|
918
834
|
* further) and prints a one-line notice to stderr. */
|
|
919
|
-
only:
|
|
835
|
+
only: GroupRegistrar;
|
|
920
836
|
}
|
|
921
837
|
|
|
922
838
|
export declare const group: GroupFn;
|
|
923
839
|
|
|
924
|
-
|
|
925
|
-
|
|
840
|
+
type TaskRegistrar = (name: string, fn: () => unknown | Promise<unknown>, opts?: TaskOptions) => void;
|
|
841
|
+
|
|
842
|
+
interface TaskFn extends TaskRegistrar {
|
|
926
843
|
/** Registers the task as skipped: the runner never measures it, but the
|
|
927
844
|
* document still carries its workload (marked `Workload.skipped`) so a
|
|
928
845
|
* renderer or `compare` can say so explicitly instead of the task simply
|
|
929
846
|
* being absent. */
|
|
930
|
-
skip:
|
|
847
|
+
skip: TaskRegistrar;
|
|
931
848
|
/** When any task or group in the suite uses `.only`, the runner restricts
|
|
932
849
|
* the whole suite file to only those tasks (before `--filter` narrows
|
|
933
850
|
* further) and prints a one-line notice to stderr. */
|
|
934
|
-
only:
|
|
851
|
+
only: TaskRegistrar;
|
|
935
852
|
}
|
|
936
853
|
|
|
937
854
|
export declare const task: TaskFn;
|
|
938
855
|
|
|
939
856
|
export interface RunOptions extends MeasureTasksOpts {
|
|
940
|
-
/** Regex
|
|
941
|
-
* `ostia bench --filter` / `bench({ filter })`. */
|
|
857
|
+
/** Regex matched against "group/name" task ids, as in `bench({ filter })`. */
|
|
942
858
|
filter?: string;
|
|
943
859
|
/** Skip printing the report to stdout; still returns the document. */
|
|
944
860
|
quiet?: boolean;
|
|
@@ -950,6 +866,11 @@ export declare function run(opts?: RunOptions): Promise<ProfileDocument>;
|
|
|
950
866
|
|
|
951
867
|
type FormatName = "table" | "json" | "markdown" | "jsonl" | "minimal" | "collapsed" | "mermaid" | "speedscope" | "cpuprofile";
|
|
952
868
|
|
|
869
|
+
export interface VizOptions {
|
|
870
|
+
/** Render this measurement only, instead of every CPU measurement. */
|
|
871
|
+
measurementId?: string;
|
|
872
|
+
}
|
|
873
|
+
|
|
953
874
|
interface RenderResult {
|
|
954
875
|
text?: string;
|
|
955
876
|
files?: {
|
|
@@ -964,32 +885,44 @@ interface Renderer<O = unknown> {
|
|
|
964
885
|
}
|
|
965
886
|
|
|
966
887
|
interface MeasureTasksOpts extends InprocessTimingOptions {
|
|
967
|
-
/** Suite-wide default for capturing an extra `phase: "cpu"` measurement
|
|
968
|
-
*
|
|
888
|
+
/** Suite-wide default for capturing an extra `phase: "cpu"` measurement per
|
|
889
|
+
* task (task/group `cpu` still wins). */
|
|
969
890
|
cpu?: boolean;
|
|
970
891
|
/** `cpu`'s sampling interval, µs (default: 100). */
|
|
971
892
|
cpuIntervalUs?: number;
|
|
972
893
|
/** Suite-wide default for capturing an extra `phase: "memstats"`
|
|
973
|
-
* measurement per task (task/group `
|
|
974
|
-
* still win). */
|
|
894
|
+
* measurement per task (task/group `alloc` still wins). */
|
|
975
895
|
alloc?: boolean;
|
|
976
|
-
/** Stamped onto every workload
|
|
977
|
-
* ran in a subprocess dedicated to it alone. */
|
|
896
|
+
/** Stamped onto every workload: it ran in a subprocess dedicated to it. */
|
|
978
897
|
markIsolated?: boolean;
|
|
979
898
|
/** Measure this machine's noise floor before the first task (default:
|
|
980
|
-
* true) and stamp it on the document as `environment`.
|
|
981
|
-
* the ~200ms reference measurement. */
|
|
899
|
+
* true) and stamp it on the document as `environment`. */
|
|
982
900
|
noiseCheck?: boolean;
|
|
983
901
|
}
|
|
984
902
|
|
|
903
|
+
interface InprocessTimingOptions {
|
|
904
|
+
/** Wall-clock budget for the sampling loop, per task (default: 500). */
|
|
905
|
+
budgetMs?: number;
|
|
906
|
+
/** Exact trial count; the budget is ignored. */
|
|
907
|
+
samples?: number;
|
|
908
|
+
/** Keep sampling past the budget until this many trials exist. When unset
|
|
909
|
+
* the floor is cost-aware (see `defaultSampleFloor`). */
|
|
910
|
+
minSamples?: number;
|
|
911
|
+
/** Warmup budget as a fraction of `budgetMs` (default: 0.1); unlike
|
|
912
|
+
* `time()`'s `warmup`, which is a trial count. Always runs at least one call. */
|
|
913
|
+
warmup?: number;
|
|
914
|
+
gc?: boolean;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
export declare function keep(value: unknown): void;
|
|
918
|
+
|
|
985
919
|
type SweepPoint<T extends Record<string, readonly unknown[]>> = {
|
|
986
920
|
[K in keyof T]: T[K][number];
|
|
987
921
|
};
|
|
988
922
|
|
|
989
923
|
export declare function sweep<T extends Record<string, readonly unknown[]>>(dims: T, fn: (point: SweepPoint<T>) => void): void;
|
|
990
924
|
|
|
991
|
-
export declare function
|
|
992
|
-
export { newDocument as createDocument };
|
|
925
|
+
export declare function createDocument(workloads: Workload[], measurements: Measurement[], environment?: Environment): ProfileDocument;
|
|
993
926
|
|
|
994
927
|
export declare function saveDocument(doc: ProfileDocument, path: string): Promise<void>;
|
|
995
928
|
|
|
@@ -1005,9 +938,45 @@ export declare class OstiaDocumentError extends Error {
|
|
|
1005
938
|
|
|
1006
939
|
export declare function loadDocument(path: string): Promise<ProfileDocument>;
|
|
1007
940
|
|
|
1008
|
-
export
|
|
941
|
+
export interface ProfileOptions {
|
|
942
|
+
/** Sampling interval in µs; default 1000. Named like `time()`'s and
|
|
943
|
+
* `bench()`'s option, the config key and `--cpu-interval`. */
|
|
944
|
+
cpuIntervalUs?: number;
|
|
945
|
+
/** Names the workload: it becomes the label and, instead of the function's
|
|
946
|
+
* name and source, what the id hashes, so closures that differ only in
|
|
947
|
+
* captured values get distinct ids and the id survives editing the function. */
|
|
948
|
+
name?: string;
|
|
949
|
+
/** `"inspector"` (default) captures through CDP and writes a portable
|
|
950
|
+
* .cpuprofile; `"jsc"` uses `bun:jsc.profile` and adds LLInt/Baseline/DFG/FTL
|
|
951
|
+
* tier data. */
|
|
952
|
+
origin?: "inspector" | "jsc";
|
|
953
|
+
/** There's no child to kill, so an already-aborted signal skips the profiler,
|
|
954
|
+
* calls `fn` plain (still returning its `result`) and records an `aborted`
|
|
955
|
+
* warning instead of CPU evidence. It can't interrupt a running `fn`. */
|
|
956
|
+
signal?: AbortSignal;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
export interface ProfileResult<T> {
|
|
960
|
+
result: T;
|
|
961
|
+
measurement: Measurement;
|
|
962
|
+
document: ProfileDocument;
|
|
963
|
+
}
|
|
1009
964
|
|
|
1010
|
-
export declare
|
|
965
|
+
export declare function profile<T>(fn: () => T | Promise<T>, opts?: ProfileOptions): Promise<ProfileResult<T>>;
|
|
966
|
+
|
|
967
|
+
export declare const renderers: {
|
|
968
|
+
table: Renderer<unknown>;
|
|
969
|
+
json: Renderer<unknown>;
|
|
970
|
+
markdown: Renderer<unknown>;
|
|
971
|
+
jsonl: Renderer<unknown>;
|
|
972
|
+
minimal: Renderer<MinimalRenderOptions>;
|
|
973
|
+
collapsed: Renderer<VizOptions>;
|
|
974
|
+
mermaid: Renderer<MermaidOptions>;
|
|
975
|
+
speedscope: Renderer<VizOptions>;
|
|
976
|
+
cpuprofile: Renderer<VizOptions>;
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
export declare const MINIMAL_PROTOCOL_VERSION: 2;
|
|
1011
980
|
|
|
1012
981
|
interface MinimalWarning {
|
|
1013
982
|
code: string;
|
|
@@ -1019,14 +988,11 @@ interface MinimalDelta {
|
|
|
1019
988
|
meanPct: number;
|
|
1020
989
|
verdict: "improved" | "regressed" | "unchanged";
|
|
1021
990
|
pass: boolean;
|
|
1022
|
-
/** 95% bootstrap CI on the difference of medians
|
|
1023
|
-
*
|
|
1024
|
-
* comparison, which falls back to a point-estimate threshold. */
|
|
991
|
+
/** 95% bootstrap CI on the difference of medians; absent (with `pValue`)
|
|
992
|
+
* on a thin (<5 samples/side) comparison. */
|
|
1025
993
|
ci95?: [number, number];
|
|
1026
994
|
pValue?: number;
|
|
1027
|
-
/** The threshold
|
|
1028
|
-
* noise widened it past `thresholds.timingPct` - see
|
|
1029
|
-
* `Comparison.thresholds.effectiveTimingPct`. */
|
|
995
|
+
/** The threshold actually tested against, after machine noise widened it. */
|
|
1030
996
|
effectiveTimingPct: number;
|
|
1031
997
|
matched: true;
|
|
1032
998
|
}
|
|
@@ -1035,44 +1001,51 @@ interface MinimalPaired {
|
|
|
1035
1001
|
/** Base side's median per-call time, ns. */
|
|
1036
1002
|
baseMedian: number;
|
|
1037
1003
|
medianRatio: number;
|
|
1038
|
-
|
|
1039
|
-
|
|
1004
|
+
ratioP25: number;
|
|
1005
|
+
ratioP75: number;
|
|
1040
1006
|
rounds: number;
|
|
1041
|
-
/**
|
|
1042
|
-
* `"unchanged"`. */
|
|
1007
|
+
/** `flagged` when fresh processes confirmed it, else `"unchanged"`. */
|
|
1043
1008
|
verdict: "regressed" | "improved" | "unchanged";
|
|
1044
|
-
/** What
|
|
1009
|
+
/** What the first process saw, before confirmation. */
|
|
1045
1010
|
flagged?: "regressed" | "improved";
|
|
1046
1011
|
confirmed?: boolean;
|
|
1047
1012
|
/** Median ratio of each fresh-process repeat. */
|
|
1048
1013
|
repeats?: number[];
|
|
1049
1014
|
sameOutput: boolean;
|
|
1015
|
+
/** The suite file differs from the base's copy. */
|
|
1016
|
+
suiteChanged?: true;
|
|
1017
|
+
/** `--alloc`: retained heap per call on each side. */
|
|
1018
|
+
retained?: MinimalMemoryChange;
|
|
1019
|
+
/** `--peak-mem`: first-call RSS rise on each side. */
|
|
1020
|
+
peak?: MinimalMemoryChange;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
interface MinimalMemoryChange {
|
|
1024
|
+
base: number;
|
|
1025
|
+
cand: number;
|
|
1026
|
+
verdict: "regressed" | "improved" | "unchanged";
|
|
1027
|
+
floor: number;
|
|
1050
1028
|
}
|
|
1051
1029
|
|
|
1052
1030
|
interface MinimalRunLine {
|
|
1053
1031
|
event: "run";
|
|
1054
1032
|
protocolVersion: typeof MINIMAL_PROTOCOL_VERSION;
|
|
1055
1033
|
schemaVersion: ProfileDocument["schemaVersion"];
|
|
1056
|
-
/** Join key
|
|
1057
|
-
* stable across
|
|
1058
|
-
* unlike `task` (a label, not an identity). */
|
|
1034
|
+
/** Join key to `Workload.id` / `Comparison.candidateMeasurementId`; unlike
|
|
1035
|
+
* `task`, stable across invocations. */
|
|
1059
1036
|
workloadId: string;
|
|
1060
1037
|
task: string;
|
|
1061
1038
|
group?: string;
|
|
1062
1039
|
description?: string;
|
|
1063
1040
|
groupDescription?: string;
|
|
1064
|
-
/** From `task(name, fn, { params })` or a `sweep()` point. */
|
|
1065
1041
|
params?: Record<string, string | number | boolean>;
|
|
1066
|
-
/**
|
|
1067
|
-
* stats field below is absent on this line. */
|
|
1042
|
+
/** `task.skip()` / `group.skip()`: no measurement, so every stats field is absent. */
|
|
1068
1043
|
skipped?: true;
|
|
1069
1044
|
unit?: "ns";
|
|
1045
|
+
/** 0, with every stats field absent, when no trial produced a sample (all
|
|
1046
|
+
* timed out or missed the `timeSource` pattern); see `warnings`. */
|
|
1070
1047
|
samples?: number;
|
|
1071
|
-
/** In-process trials batched into one timed block
|
|
1072
|
-
* `measure/inprocess.ts`'s `sizeBatch`) so the timer's own resolution
|
|
1073
|
-
* doesn't dominate a sub-microsecond task's reading. 1 when the timing
|
|
1074
|
-
* engine never batched (every subprocess run, and any in-process run
|
|
1075
|
-
* whose single call already clears the batching threshold). */
|
|
1048
|
+
/** In-process trials batched into one timed block; 1 when never batched. */
|
|
1076
1049
|
batch: number;
|
|
1077
1050
|
mean?: number;
|
|
1078
1051
|
median?: number;
|
|
@@ -1087,24 +1060,30 @@ interface MinimalRunLine {
|
|
|
1087
1060
|
/** Median user / system CPU time per trial, ns: subprocess commands only. */
|
|
1088
1061
|
userNs?: number;
|
|
1089
1062
|
systemNs?: number;
|
|
1090
|
-
/** `--alloc`: heap each call keeps alive after a full GC,
|
|
1063
|
+
/** `--alloc`: heap each call keeps alive after a full GC, bytes. */
|
|
1091
1064
|
retainedBytesPerOp?: number;
|
|
1092
1065
|
/** `--peak-mem`: how far the task's first call raised RSS, bytes. */
|
|
1093
1066
|
peakBytes?: number;
|
|
1094
1067
|
/** Median over the group's reference median (its baseline task, else its
|
|
1095
|
-
* fastest)
|
|
1068
|
+
* fastest); only when the document has more than one timing run. */
|
|
1096
1069
|
relative?: number;
|
|
1097
1070
|
baseline?: true;
|
|
1098
|
-
/**
|
|
1099
|
-
*
|
|
1100
|
-
* From `document.environment`; absent when `noiseCheck: false` skipped it. */
|
|
1071
|
+
/** The machine's noise floor from `document.environment`; absent when the
|
|
1072
|
+
* noise check was skipped. */
|
|
1101
1073
|
noiseFloorPct?: number;
|
|
1102
1074
|
warnings: MinimalWarning[];
|
|
1103
|
-
/**
|
|
1104
|
-
* the baseline document for this task. */
|
|
1075
|
+
/** The change against the baseline document (`compare` / `ci`). */
|
|
1105
1076
|
delta?: MinimalDelta;
|
|
1106
|
-
/** `ab` only; see `MinimalPaired`. */
|
|
1107
1077
|
paired?: MinimalPaired;
|
|
1078
|
+
/** `ab` only, instead of stats and `paired`: the task threw on one side
|
|
1079
|
+
* (`"base"`/`"cand"`) or both, so it isn't judged on time. `message` is
|
|
1080
|
+
* the error's first lines; `repeat` (from 1) is set when it threw in a
|
|
1081
|
+
* confirmation repeat. */
|
|
1082
|
+
threw?: {
|
|
1083
|
+
side: "base" | "cand" | "both";
|
|
1084
|
+
message: string;
|
|
1085
|
+
repeat?: number;
|
|
1086
|
+
};
|
|
1108
1087
|
}
|
|
1109
1088
|
|
|
1110
1089
|
interface MinimalUnmatchedLine {
|
|
@@ -1144,19 +1123,112 @@ interface MinimalSummaryLine {
|
|
|
1144
1123
|
};
|
|
1145
1124
|
/** `ab` only: the gate on `geomeanPct`. */
|
|
1146
1125
|
geomeanThresholdPct?: number;
|
|
1147
|
-
/** `ab` only: flagged in the first process but not reproduced
|
|
1148
|
-
* fresh-process repeats; counted in `unchanged`. */
|
|
1126
|
+
/** `ab` only: flagged in the first process but not reproduced; counted in `unchanged`. */
|
|
1149
1127
|
unconfirmed?: number;
|
|
1150
|
-
/** `ab` only: workloads whose first call returned different values on the
|
|
1151
|
-
* two sides. Informational. */
|
|
1128
|
+
/** `ab` only: workloads whose first call returned different values on the two sides. */
|
|
1152
1129
|
outputDiffers?: number;
|
|
1130
|
+
notComparable?: number;
|
|
1131
|
+
threw?: number;
|
|
1132
|
+
newSuites?: string[];
|
|
1133
|
+
/** `ab` only, with `--alloc` or `--peak-mem`: tasks whose memory
|
|
1134
|
+
* regressed or improved, and the threshold, percent. */
|
|
1135
|
+
memory?: {
|
|
1136
|
+
thresholdPct: number;
|
|
1137
|
+
regressed: number;
|
|
1138
|
+
improved: number;
|
|
1139
|
+
};
|
|
1153
1140
|
git?: {
|
|
1154
1141
|
base?: GitMetadata;
|
|
1155
1142
|
cand?: GitMetadata;
|
|
1156
1143
|
};
|
|
1157
1144
|
exportedTo?: string;
|
|
1158
|
-
|
|
1145
|
+
/** `pass` (exit 0), `fail` (exit 1: a regression), else `error` (exit 2, or
|
|
1146
|
+
* 130 when cancelled): no verdict was reached. */
|
|
1147
|
+
verdict: "pass" | "fail" | "error";
|
|
1159
1148
|
exitCode: number;
|
|
1160
1149
|
}
|
|
1161
1150
|
|
|
1162
1151
|
export type MinimalEvent = MinimalRunLine | MinimalUnmatchedLine | MinimalSummaryLine;
|
|
1152
|
+
|
|
1153
|
+
export interface MinimalProtocolContext {
|
|
1154
|
+
command: "compare" | "ci" | "ab";
|
|
1155
|
+
exitCode: number;
|
|
1156
|
+
unmatched?: {
|
|
1157
|
+
baseOnly: Workload[];
|
|
1158
|
+
candOnly: Workload[];
|
|
1159
|
+
};
|
|
1160
|
+
baseGit?: GitMetadata;
|
|
1161
|
+
candGit?: GitMetadata;
|
|
1162
|
+
baseline?: {
|
|
1163
|
+
name: string;
|
|
1164
|
+
path: string;
|
|
1165
|
+
};
|
|
1166
|
+
cached?: number;
|
|
1167
|
+
executed?: number;
|
|
1168
|
+
failed?: number;
|
|
1169
|
+
missingBaseline?: number;
|
|
1170
|
+
exportedTo?: string;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
export interface MinimalRenderOptions {
|
|
1174
|
+
protocol?: MinimalProtocolContext;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
export interface MermaidOptions extends VizOptions {
|
|
1178
|
+
topN?: number;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
export interface CommandSpec {
|
|
1182
|
+
command: string | string[];
|
|
1183
|
+
label?: string;
|
|
1184
|
+
/** Overrides `TimeOptions.prepare` for this command. */
|
|
1185
|
+
prepare?: PrepareHook;
|
|
1186
|
+
/** Overrides `TimeOptions.timeSource` for this command. */
|
|
1187
|
+
timeSource?: TimeSource;
|
|
1188
|
+
/** Overrides `TimeOptions.timeoutMs` for this command. */
|
|
1189
|
+
timeoutMs?: number;
|
|
1190
|
+
/** Overrides `TimeOptions.ignoreExitCodes` for this command. */
|
|
1191
|
+
ignoreExitCodes?: number[];
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
export interface TimeOptions {
|
|
1195
|
+
commands: (string | string[] | CommandSpec)[];
|
|
1196
|
+
/** Runs unmeasured before every trial (warmup and instrumented included), in
|
|
1197
|
+
* `cwd`/`env`: a command spawned and awaited, or a function. */
|
|
1198
|
+
prepare?: PrepareHook;
|
|
1199
|
+
/** Take timing from a number in each command's own output (e.g. a build
|
|
1200
|
+
* tool's `built in 342ms`) instead of its wall clock. */
|
|
1201
|
+
timeSource?: TimeSource;
|
|
1202
|
+
/** Exact trial count; when set, `budgetMs` is ignored. */
|
|
1203
|
+
samples?: number;
|
|
1204
|
+
/** Wall-clock budget for the sampling loop, ms. Default: ~3s when neither
|
|
1205
|
+
* `samples` nor `budgetMs` is given. */
|
|
1206
|
+
budgetMs?: number;
|
|
1207
|
+
/** Floor on trials when no exact `samples` is given. */
|
|
1208
|
+
minSamples?: number;
|
|
1209
|
+
warmup?: number;
|
|
1210
|
+
/** Round-robin trials across commands instead of finishing each command
|
|
1211
|
+
* first, so drift hits every command equally. Default: true with 2+ commands. */
|
|
1212
|
+
interleave?: boolean;
|
|
1213
|
+
cwd?: string;
|
|
1214
|
+
env?: Record<string, string>;
|
|
1215
|
+
cpu?: boolean;
|
|
1216
|
+
heap?: boolean;
|
|
1217
|
+
cpuIntervalUs?: number;
|
|
1218
|
+
outDir?: string;
|
|
1219
|
+
/** Measure the machine's noise floor first (default true, ~200ms) and stamp
|
|
1220
|
+
* it on the document as `environment`. */
|
|
1221
|
+
noiseCheck?: boolean;
|
|
1222
|
+
/** SIGKILLs a trial (or prepare hook) still running after this many ms. No
|
|
1223
|
+
* default. A timed-out trial contributes no sample. */
|
|
1224
|
+
timeoutMs?: number;
|
|
1225
|
+
/** Abort kills in-flight trials, schedules no more, and resolves (never
|
|
1226
|
+
* rejects) with the completed measurements plus an `aborted` warning on the
|
|
1227
|
+
* last one. */
|
|
1228
|
+
signal?: AbortSignal;
|
|
1229
|
+
/** Exit codes to treat as success (hyperfine's `--ignore-failure`): the
|
|
1230
|
+
* trial still contributes its sample and gets no `nonzero-exit` warning. */
|
|
1231
|
+
ignoreExitCodes?: number[];
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
export declare function time(opts: TimeOptions): Promise<ProfileDocument>;
|