ostia 0.2.2 → 0.2.4
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 +301 -40
- package/chunk-1wydyvyj.js +12 -0
- package/chunk-cf9hya3b.js +22 -0
- package/cli.js +124 -62
- package/index.d.ts +334 -39
- package/index.js +1 -1
- package/package.json +4 -1
- package/runner.ts +5 -5
- package/chunk-pesqame2.js +0 -8
- package/chunk-ym89ds9x.js +0 -21
package/index.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export interface CommandSpec {
|
|
|
7
7
|
prepare?: PrepareHook;
|
|
8
8
|
/** Overrides `TimeOptions.timeSource` for this command. */
|
|
9
9
|
timeSource?: TimeSource;
|
|
10
|
+
/** Overrides `TimeOptions.timeoutMs` for this command. */
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
/** Overrides `TimeOptions.ignoreExitCodes` for this command. */
|
|
13
|
+
ignoreExitCodes?: number[];
|
|
14
|
+
/** Overrides `TimeOptions.failOnNonzero` for this command. */
|
|
15
|
+
failOnNonzero?: boolean;
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
export interface TimeOptions {
|
|
@@ -49,6 +55,28 @@ export interface TimeOptions {
|
|
|
49
55
|
* true) and stamp it on the document as `environment`. Set false to skip
|
|
50
56
|
* the ~200ms reference measurement. */
|
|
51
57
|
noiseCheck?: boolean;
|
|
58
|
+
/** Kills a trial (or prepare hook) that hasn't finished after this many
|
|
59
|
+
* ms, with SIGKILL. No default: an unset `timeoutMs` never times out. A
|
|
60
|
+
* timed-out trial contributes no sample; if every trial of a command times
|
|
61
|
+
* out, that command has no timing stats. `CommandSpec.timeoutMs` overrides
|
|
62
|
+
* it per command. */
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
/** Aborting cancels the run: in-flight trials are killed with SIGKILL, no
|
|
65
|
+
* further trials are scheduled, and `time()` resolves (never rejects)
|
|
66
|
+
* with the document built from whatever measurements had already
|
|
67
|
+
* completed, plus an `aborted` warning on the document's last
|
|
68
|
+
* measurement. */
|
|
69
|
+
signal?: AbortSignal;
|
|
70
|
+
/** Exit codes to treat as success (hyperfine's `--ignore-failure`): a
|
|
71
|
+
* trial exiting with one of these still contributes its sample and gets
|
|
72
|
+
* no `nonzero-exit` warning, as if it had exited 0. `CommandSpec.ignoreExitCodes`
|
|
73
|
+
* overrides it per command. */
|
|
74
|
+
ignoreExitCodes?: number[];
|
|
75
|
+
/** Stops a command's trial loop after its first non-zero, non-ignored
|
|
76
|
+
* exit (that trial's sample is still recorded) instead of running its
|
|
77
|
+
* full sample count regardless of exit code. `CommandSpec.failOnNonzero`
|
|
78
|
+
* overrides it per command. */
|
|
79
|
+
failOnNonzero?: boolean;
|
|
52
80
|
}
|
|
53
81
|
|
|
54
82
|
export declare function time(opts: TimeOptions): Promise<ProfileDocument>;
|
|
@@ -56,6 +84,12 @@ export declare function time(opts: TimeOptions): Promise<ProfileDocument>;
|
|
|
56
84
|
interface ProfileOptions {
|
|
57
85
|
intervalUs?: number;
|
|
58
86
|
origin?: "inspector" | "jsc";
|
|
87
|
+
/** `profile()` runs `fn` in this process, so there's no child to kill: an
|
|
88
|
+
* already-aborted signal skips the profiler instrumentation entirely and
|
|
89
|
+
* just calls `fn` plain (still returning its `result`), with an `aborted`
|
|
90
|
+
* warning in place of CPU evidence. A signal that fires mid-capture can't
|
|
91
|
+
* interrupt `fn` once it's running. */
|
|
92
|
+
signal?: AbortSignal;
|
|
59
93
|
}
|
|
60
94
|
|
|
61
95
|
interface ProfileResult<T> {
|
|
@@ -80,6 +114,16 @@ export interface WorkloadConfig {
|
|
|
80
114
|
/** `command` only. Take timing from a number in the command's own output
|
|
81
115
|
* instead of its wall clock; see `TimeSource`. */
|
|
82
116
|
timeSource?: TimeSource;
|
|
117
|
+
/** `command` only. Kills a trial (or prepare hook) that hasn't finished
|
|
118
|
+
* after this many ms. Overrides `ostia ci`'s 10-minute default per
|
|
119
|
+
* workload. */
|
|
120
|
+
timeoutMs?: number;
|
|
121
|
+
/** `command` only. Exit codes to treat as success; see
|
|
122
|
+
* `TimeOptions.ignoreExitCodes`. */
|
|
123
|
+
ignoreExitCodes?: number[];
|
|
124
|
+
/** `command` only. Stops this workload's trial loop after its first
|
|
125
|
+
* non-zero, non-ignored exit; see `TimeOptions.failOnNonzero`. */
|
|
126
|
+
failOnNonzero?: boolean;
|
|
83
127
|
}
|
|
84
128
|
|
|
85
129
|
interface BenchConfig {
|
|
@@ -99,6 +143,9 @@ interface BenchConfig {
|
|
|
99
143
|
filter?: string;
|
|
100
144
|
isolate?: boolean;
|
|
101
145
|
outDir?: string;
|
|
146
|
+
/** Kills a suite file's (or isolated task's) subprocess if it hasn't
|
|
147
|
+
* finished after this many ms. Overrides `ostia ci`'s 10-minute default. */
|
|
148
|
+
timeoutMs?: number;
|
|
102
149
|
}
|
|
103
150
|
|
|
104
151
|
export interface OstiaConfig {
|
|
@@ -111,13 +158,28 @@ export interface OstiaConfig {
|
|
|
111
158
|
thresholds: Thresholds;
|
|
112
159
|
workloads: WorkloadConfig[];
|
|
113
160
|
bench?: BenchConfig;
|
|
161
|
+
/** `ostia ci`'s policy when a configured workload has no matching row in
|
|
162
|
+
* the baseline (by workload id): `"fail"` exits 2 naming the baseline
|
|
163
|
+
* file, `"warn"` lists it in the report without affecting the exit code.
|
|
164
|
+
* Unset (the default): `"fail"` when *every* configured workload is
|
|
165
|
+
* missing, `"warn"` otherwise - a totally stale/wrong baseline is a hard
|
|
166
|
+
* error, a handful of new workloads next to an otherwise-matching
|
|
167
|
+
* baseline is not. `--on-missing-baseline` overrides this per invocation. */
|
|
168
|
+
onMissingBaseline?: "warn" | "fail";
|
|
169
|
+
/** Measures this machine's noise floor once per `ostia ci` invocation
|
|
170
|
+
* (default true) and stamps it on the candidate document as
|
|
171
|
+
* `environment`, the same reference measurement `time()`/`bench()` run -
|
|
172
|
+
* so `compare`'s noise-floor threshold widening applies to `ci` too, not
|
|
173
|
+
* only to ad hoc `time`/`bench` runs. `--no-noise-check` overrides this
|
|
174
|
+
* to false per invocation. */
|
|
175
|
+
noiseCheck?: boolean;
|
|
114
176
|
}
|
|
115
177
|
|
|
116
178
|
export type OstiaConfigInput = Omit<Partial<OstiaConfig>, "thresholds"> & {
|
|
117
179
|
thresholds?: Partial<Thresholds>;
|
|
118
180
|
};
|
|
119
181
|
|
|
120
|
-
interface Thresholds {
|
|
182
|
+
export interface Thresholds {
|
|
121
183
|
timingPct: number;
|
|
122
184
|
frameSelfPct: number;
|
|
123
185
|
heapTypePct: number;
|
|
@@ -130,7 +192,18 @@ interface Thresholds {
|
|
|
130
192
|
bootstrapIterations: number;
|
|
131
193
|
}
|
|
132
194
|
|
|
133
|
-
export declare
|
|
195
|
+
export declare const DEFAULT_THRESHOLDS: Thresholds;
|
|
196
|
+
|
|
197
|
+
export interface CompareResult {
|
|
198
|
+
comparisons: Comparison[];
|
|
199
|
+
unmatched: {
|
|
200
|
+
baseOnly: Workload[];
|
|
201
|
+
candOnly: Workload[];
|
|
202
|
+
};
|
|
203
|
+
summary: ComparisonSummary;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export declare function compareDocuments(base: ProfileDocument, cand: ProfileDocument, thresholds?: Thresholds): CompareResult;
|
|
134
207
|
|
|
135
208
|
export interface ProfileDocument {
|
|
136
209
|
schemaVersion: 2;
|
|
@@ -144,6 +217,18 @@ export interface ProfileDocument {
|
|
|
144
217
|
workloads: Workload[];
|
|
145
218
|
measurements: Measurement[];
|
|
146
219
|
comparisons?: Comparison[];
|
|
220
|
+
/** Aggregate across `comparisons`, from `compareDocuments`/`ostia compare`/
|
|
221
|
+
* `ostia ci`. Additive, no schema bump. Absent wherever `comparisons` is. */
|
|
222
|
+
comparisonSummary?: ComparisonSummary;
|
|
223
|
+
/** Workload ids present on only one side of a `compareDocuments` call -
|
|
224
|
+
* candidates matched by id gone missing from the baseline, or vice versa.
|
|
225
|
+
* Additive, no schema bump; absent wherever `comparisons` is. Ids only
|
|
226
|
+
* (not full `Workload`s) to keep the document small; `ostia compare`'s
|
|
227
|
+
* printed report resolves labels from the two source documents directly. */
|
|
228
|
+
unmatched?: {
|
|
229
|
+
baseOnly: string[];
|
|
230
|
+
candOnly: string[];
|
|
231
|
+
};
|
|
147
232
|
/** Machine conditions when this document was measured. Additive, no
|
|
148
233
|
* schema bump. Absent when `noiseCheck: false` (or `--no-noise-check`)
|
|
149
234
|
* skipped the reference measurement. */
|
|
@@ -180,6 +265,15 @@ interface Environment {
|
|
|
180
265
|
}
|
|
181
266
|
|
|
182
267
|
export interface Workload {
|
|
268
|
+
/** Identifies what is measured, not where or when it ran: for a
|
|
269
|
+
* `subprocess` workload, a hash of the command argv, `prepare`
|
|
270
|
+
* (command form or function source), and `timeSource` spec -
|
|
271
|
+
* deliberately excluding `process.cwd()`, so a baseline saved from one
|
|
272
|
+
* checkout (a CI runner, a different worktree) still matches a candidate
|
|
273
|
+
* measured from another. For an `inprocess` workload, a hash of the
|
|
274
|
+
* function source (or, for a registry entry, the file/task name plus
|
|
275
|
+
* `params`). `label`, `description`, and `baseline` are annotations and
|
|
276
|
+
* never affect it. */
|
|
183
277
|
id: string;
|
|
184
278
|
kind: "subprocess" | "inprocess";
|
|
185
279
|
label?: string;
|
|
@@ -255,7 +349,6 @@ interface Measurement {
|
|
|
255
349
|
jit?: JitTierBreakdown;
|
|
256
350
|
warnings: Warning[];
|
|
257
351
|
artifacts: ArtifactRef[];
|
|
258
|
-
baselineMeasurementId?: string;
|
|
259
352
|
/** True when this timing measurement's trials were run round-robin against
|
|
260
353
|
* the other commands in the same `time()` call (`--interleave`, default on
|
|
261
354
|
* for 2+ commands) rather than run to completion before the next command
|
|
@@ -276,6 +369,14 @@ interface Trial {
|
|
|
276
369
|
userNs?: number;
|
|
277
370
|
systemNs?: number;
|
|
278
371
|
maxRssBytes?: number;
|
|
372
|
+
/** Set when the trial was killed by `timeoutMs` before it exited on its
|
|
373
|
+
* own. `exitCode` is absent in that case: the kill signal, not the
|
|
374
|
+
* command, decided how the process ended. Contributes no sample. */
|
|
375
|
+
timedOut?: true;
|
|
376
|
+
/** Set when the workload has a `timeSource` and this trial's output didn't
|
|
377
|
+
* match its pattern. `reportedNs` is absent in that case (never a
|
|
378
|
+
* fallback to `wallNs`). Contributes no sample. */
|
|
379
|
+
timeSourceNoMatch?: true;
|
|
279
380
|
}
|
|
280
381
|
|
|
281
382
|
interface TimingStats {
|
|
@@ -290,8 +391,10 @@ interface TimingStats {
|
|
|
290
391
|
mild: number;
|
|
291
392
|
severe: number;
|
|
292
393
|
};
|
|
293
|
-
/**
|
|
394
|
+
/** 25th percentile, ns. Optional: absent on documents saved before this
|
|
294
395
|
* field existed (`loadDocument` never backfills it). */
|
|
396
|
+
p25?: number;
|
|
397
|
+
/** 75th percentile, ns. Same caveat as `p25`. */
|
|
295
398
|
p75?: number;
|
|
296
399
|
/** 99th percentile, ns. Same caveat as `p75`. */
|
|
297
400
|
p99?: number;
|
|
@@ -299,6 +402,12 @@ interface TimingStats {
|
|
|
299
402
|
* all samples. A robust spread measure that (unlike stddev) isn't skewed
|
|
300
403
|
* by the long right tail typical of wall-clock timings. */
|
|
301
404
|
mad?: number;
|
|
405
|
+
/** In-process trials batched into one timed block (see
|
|
406
|
+
* `measure/inprocess.ts`'s `sizeBatch`), set only when batching occurred.
|
|
407
|
+
* Absent for every subprocess timing measurement, and for an in-process
|
|
408
|
+
* one whose single call already cleared the batching threshold - a
|
|
409
|
+
* renderer treats an absent value the same as `1`. */
|
|
410
|
+
batch?: number;
|
|
302
411
|
}
|
|
303
412
|
|
|
304
413
|
interface Frame {
|
|
@@ -343,7 +452,6 @@ interface HeapEvidence {
|
|
|
343
452
|
count: number;
|
|
344
453
|
retainedBytes?: number;
|
|
345
454
|
}[];
|
|
346
|
-
snapshotArtifactId?: string;
|
|
347
455
|
}
|
|
348
456
|
|
|
349
457
|
interface MemoryEvidence {
|
|
@@ -353,8 +461,6 @@ interface MemoryEvidence {
|
|
|
353
461
|
heapSizeBytes?: number;
|
|
354
462
|
}[];
|
|
355
463
|
maxRssBytes?: number;
|
|
356
|
-
peakCommitBytes?: number;
|
|
357
|
-
pageFaults?: number;
|
|
358
464
|
/** Bytes allocated per call, from `ostia bench --alloc`: heap size delta
|
|
359
465
|
* (`bun:jsc`'s `heapStats().heapSize`, falling back to
|
|
360
466
|
* `process.memoryUsage().heapUsed`) around one `Bun.gc(true)`-bracketed
|
|
@@ -377,7 +483,9 @@ interface JitTierBreakdown {
|
|
|
377
483
|
}[];
|
|
378
484
|
}
|
|
379
485
|
|
|
380
|
-
|
|
486
|
+
declare const WARNING_CODES: readonly ["slow-first-run", "outliers-detected", "fast-command", "nonzero-exit", "artifact-missing", "empty-profile", "below-timer-resolution", "low-sample-count", "thin-comparison", "noisy-machine", "skipped", "jit-cold", "timeout", "aborted", "time-source-no-match", "environment-mismatch"];
|
|
487
|
+
|
|
488
|
+
export type WarningCode = (typeof WARNING_CODES)[number];
|
|
381
489
|
|
|
382
490
|
export interface Warning {
|
|
383
491
|
code: WarningCode;
|
|
@@ -393,16 +501,13 @@ interface ArtifactRef {
|
|
|
393
501
|
bytes: number;
|
|
394
502
|
}
|
|
395
503
|
|
|
396
|
-
interface Comparison {
|
|
504
|
+
export interface Comparison {
|
|
397
505
|
id: string;
|
|
398
506
|
baselineMeasurementId: string;
|
|
399
507
|
candidateMeasurementId: string;
|
|
400
508
|
timing?: {
|
|
401
509
|
medianDeltaPct: number;
|
|
402
510
|
meanDeltaPct: number;
|
|
403
|
-
/** Same value as `medianDeltaPct`, named for what it is used for: the
|
|
404
|
-
* effect size the verdict rule tests against `thresholds.timingPct`. */
|
|
405
|
-
effectPct: number;
|
|
406
511
|
/** 95% bootstrap confidence interval on the difference of medians,
|
|
407
512
|
* percent of the baseline median. Absent when either side had fewer
|
|
408
513
|
* than 5 samples (see the `thin-comparison` warning). */
|
|
@@ -447,6 +552,24 @@ interface Comparison {
|
|
|
447
552
|
verdict: "pass" | "fail";
|
|
448
553
|
}
|
|
449
554
|
|
|
555
|
+
interface ComparisonSummary {
|
|
556
|
+
/** `comparisons.length`: workloads present (and comparable) on both sides. */
|
|
557
|
+
matched: number;
|
|
558
|
+
regressed: number;
|
|
559
|
+
improved: number;
|
|
560
|
+
unchanged: number;
|
|
561
|
+
/** Geometric mean of `cand/base` median ratios over matched timing
|
|
562
|
+
* comparisons, as a signed percent (negative: candidate faster on
|
|
563
|
+
* average). `null` when no comparison had a finite timing ratio. */
|
|
564
|
+
geomeanPct: number | null;
|
|
565
|
+
/** Same value as `Comparison.thresholds.effectiveTimingPct` - one number
|
|
566
|
+
* for the whole document pair, since it depends only on `thresholds` and
|
|
567
|
+
* the two documents' `environment.noise.floorPct`, never per-workload. */
|
|
568
|
+
effectiveTimingPct: number;
|
|
569
|
+
/** `"fail"` when any comparison's verdict is `"fail"`. */
|
|
570
|
+
verdict: "pass" | "fail";
|
|
571
|
+
}
|
|
572
|
+
|
|
450
573
|
export interface PrepareRun {
|
|
451
574
|
phase: "warmup" | "timing" | "cpu" | "heap";
|
|
452
575
|
index: number;
|
|
@@ -464,6 +587,31 @@ export interface TimeSource {
|
|
|
464
587
|
unit?: TimeUnit;
|
|
465
588
|
}
|
|
466
589
|
|
|
590
|
+
interface InprocessTimingOptions {
|
|
591
|
+
/** Wall-clock budget for the sampling loop, per task (default: 500). The loop
|
|
592
|
+
* always runs for at least this long, unless `samples` is set. */
|
|
593
|
+
budgetMs?: number;
|
|
594
|
+
/** Exact trial count. When set, the budget is ignored and the loop runs
|
|
595
|
+
* exactly this many trials, however slow each one is - the in-process
|
|
596
|
+
* equivalent of `time()`'s `samples`. */
|
|
597
|
+
samples?: number;
|
|
598
|
+
/** Hard floor on the number of trials when no exact `samples` count is
|
|
599
|
+
* given. When set, the loop keeps sampling past the time budget until this
|
|
600
|
+
* many trials exist, however slow each one is. When unset, the floor is
|
|
601
|
+
* cost-aware (see `defaultSampleFloor`): as many trials as fit in the
|
|
602
|
+
* budget, capped at 20, but never below the rigor floor the task's per-trial cost
|
|
603
|
+
* earns it (3 at ≤1ms, rising to 10 for multi-second calls). */
|
|
604
|
+
minSamples?: number;
|
|
605
|
+
/** Warmup budget as a fraction of `budgetMs` (default: 0.1) - a fraction,
|
|
606
|
+
* not a call count, named `warmup` for cross-surface consistency with
|
|
607
|
+
* `time()`'s trial-count `warmup`; the two are genuinely different units (a
|
|
608
|
+
* fraction here, a count there), not papered over. Warmup always runs at
|
|
609
|
+
* least one call; for a task slower than the warmup budget that single call
|
|
610
|
+
* is the whole warmup. */
|
|
611
|
+
warmup?: number;
|
|
612
|
+
gc?: boolean;
|
|
613
|
+
}
|
|
614
|
+
|
|
467
615
|
export declare function keep(value: unknown): void;
|
|
468
616
|
|
|
469
617
|
interface BenchOptions {
|
|
@@ -522,6 +670,21 @@ interface BenchOptions {
|
|
|
522
670
|
* subprocess (default: true) and stamp it on the document as
|
|
523
671
|
* `environment`. Set false to skip the ~200ms reference measurement. */
|
|
524
672
|
noiseCheck?: boolean;
|
|
673
|
+
/** Kills a suite file's subprocess (or, under `isolate`, one task's
|
|
674
|
+
* dedicated subprocess) with SIGKILL if it hasn't finished after this many
|
|
675
|
+
* ms. No default: an unset `timeoutMs` never times out. Applies to the
|
|
676
|
+
* whole subprocess, not per task - the same granularity `isolate` already
|
|
677
|
+
* runs at. */
|
|
678
|
+
timeoutMs?: number;
|
|
679
|
+
/** Aborting cancels the run: in-flight suite/isolated-task subprocesses
|
|
680
|
+
* are killed with SIGKILL, no new ones are started, and `bench()` resolves
|
|
681
|
+
* (never rejects) with whatever suites/tasks had already finished when the
|
|
682
|
+
* signal fired, plus an `aborted` warning on the document's last
|
|
683
|
+
* measurement. A suite subprocess killed mid-run contributes nothing (it
|
|
684
|
+
* only writes its result once, at the end), so a suite that was in flight
|
|
685
|
+
* when the signal fired is dropped entirely rather than partially
|
|
686
|
+
* represented. */
|
|
687
|
+
signal?: AbortSignal;
|
|
525
688
|
}
|
|
526
689
|
|
|
527
690
|
export declare function bench(opts: BenchOptions): Promise<ProfileDocument>;
|
|
@@ -632,6 +795,50 @@ interface TaskFn {
|
|
|
632
795
|
|
|
633
796
|
export declare const task: TaskFn;
|
|
634
797
|
|
|
798
|
+
export interface RunOptions extends MeasureTasksOpts {
|
|
799
|
+
/** Regex, matched against "group/name" task ids - same semantics as
|
|
800
|
+
* `ostia bench --filter` / `bench({ filter })`. */
|
|
801
|
+
filter?: string;
|
|
802
|
+
/** Skip printing the report to stdout; still returns the document. */
|
|
803
|
+
quiet?: boolean;
|
|
804
|
+
/** Renderer used for the printed report (default: `"table"`). */
|
|
805
|
+
format?: FormatName;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
export declare function run(opts?: RunOptions): Promise<ProfileDocument>;
|
|
809
|
+
|
|
810
|
+
type FormatName = "table" | "json" | "markdown" | "jsonl" | "minimal" | "collapsed" | "mermaid" | "speedscope" | "cpuprofile";
|
|
811
|
+
|
|
812
|
+
interface RenderResult {
|
|
813
|
+
text?: string;
|
|
814
|
+
files?: {
|
|
815
|
+
path?: string;
|
|
816
|
+
content: string;
|
|
817
|
+
}[];
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
interface Renderer<O = unknown> {
|
|
821
|
+
name: FormatName;
|
|
822
|
+
render(doc: ProfileDocument, options: O): Promise<RenderResult>;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
interface MeasureTasksOpts extends InprocessTimingOptions {
|
|
826
|
+
/** Suite-wide default for capturing an extra `phase: "cpu"` measurement
|
|
827
|
+
* per task (task/group `TaskOptions.cpu`/`GroupOptions.cpu` still win). */
|
|
828
|
+
cpu?: boolean;
|
|
829
|
+
/** Suite-wide default for capturing an extra `phase: "memstats"`
|
|
830
|
+
* measurement per task (task/group `TaskOptions.alloc`/`GroupOptions.alloc`
|
|
831
|
+
* still win). */
|
|
832
|
+
alloc?: boolean;
|
|
833
|
+
/** Stamped onto every workload this call produces, recording whether it
|
|
834
|
+
* ran in a subprocess dedicated to it alone. */
|
|
835
|
+
markIsolated?: boolean;
|
|
836
|
+
/** Measure this machine's noise floor before the first task (default:
|
|
837
|
+
* true) and stamp it on the document as `environment`. Set false to skip
|
|
838
|
+
* the ~200ms reference measurement. */
|
|
839
|
+
noiseCheck?: boolean;
|
|
840
|
+
}
|
|
841
|
+
|
|
635
842
|
type SweepPoint<T extends Record<string, readonly unknown[]>> = {
|
|
636
843
|
[K in keyof T]: T[K][number];
|
|
637
844
|
};
|
|
@@ -643,26 +850,52 @@ export { newDocument as createDocument };
|
|
|
643
850
|
|
|
644
851
|
export declare function saveDocument(doc: ProfileDocument, path: string): Promise<void>;
|
|
645
852
|
|
|
853
|
+
export declare class OstiaDocumentError extends Error {
|
|
854
|
+
readonly code: "invalid-json" | "not-a-document" | "unsupported-schema";
|
|
855
|
+
readonly path?: string;
|
|
856
|
+
readonly schemaVersion?: unknown;
|
|
857
|
+
constructor(code: OstiaDocumentError["code"], message: string, opts?: {
|
|
858
|
+
path?: string;
|
|
859
|
+
schemaVersion?: unknown;
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
646
863
|
export declare function loadDocument(path: string): Promise<ProfileDocument>;
|
|
647
864
|
|
|
648
865
|
export declare const renderers: Record<FormatName, Renderer<any>>;
|
|
649
866
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
interface RenderResult {
|
|
653
|
-
text?: string;
|
|
654
|
-
files?: {
|
|
655
|
-
path?: string;
|
|
656
|
-
content: string;
|
|
657
|
-
}[];
|
|
658
|
-
}
|
|
867
|
+
export declare const MINIMAL_PROTOCOL_VERSION: 1;
|
|
659
868
|
|
|
660
|
-
interface
|
|
661
|
-
|
|
662
|
-
|
|
869
|
+
interface MinimalWarning {
|
|
870
|
+
code: string;
|
|
871
|
+
data?: Record<string, unknown>;
|
|
663
872
|
}
|
|
664
873
|
|
|
665
|
-
export interface
|
|
874
|
+
export interface MinimalDelta {
|
|
875
|
+
medianPct: number;
|
|
876
|
+
meanPct: number;
|
|
877
|
+
verdict: "improved" | "regressed" | "unchanged";
|
|
878
|
+
pass: boolean;
|
|
879
|
+
/** 95% bootstrap CI on the difference of medians and the Mann-Whitney
|
|
880
|
+
* p-value behind the verdict. Absent on a thin (<5 samples/side)
|
|
881
|
+
* comparison, which falls back to a point-estimate threshold. */
|
|
882
|
+
ci95?: [number, number];
|
|
883
|
+
pValue?: number;
|
|
884
|
+
/** The threshold this delta was actually tested against, once machine
|
|
885
|
+
* noise widened it past `thresholds.timingPct` - see
|
|
886
|
+
* `Comparison.thresholds.effectiveTimingPct`. */
|
|
887
|
+
effectiveTimingPct: number;
|
|
888
|
+
matched: true;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
export interface MinimalRunLine {
|
|
892
|
+
event: "run";
|
|
893
|
+
protocolVersion: typeof MINIMAL_PROTOCOL_VERSION;
|
|
894
|
+
schemaVersion: ProfileDocument["schemaVersion"];
|
|
895
|
+
/** Join key back to `Workload.id` / `Comparison.candidateMeasurementId` -
|
|
896
|
+
* stable across a `time`/`compare`/`ci` invocation of the same command,
|
|
897
|
+
* unlike `task` (a label, not an identity). */
|
|
898
|
+
workloadId: string;
|
|
666
899
|
task: string;
|
|
667
900
|
group?: string;
|
|
668
901
|
description?: string;
|
|
@@ -674,6 +907,12 @@ export interface MinimalLine {
|
|
|
674
907
|
skipped?: true;
|
|
675
908
|
unit?: "ns";
|
|
676
909
|
samples?: number;
|
|
910
|
+
/** In-process trials batched into one timed block (see
|
|
911
|
+
* `measure/inprocess.ts`'s `sizeBatch`) so the timer's own resolution
|
|
912
|
+
* doesn't dominate a sub-microsecond task's reading. 1 when the timing
|
|
913
|
+
* engine never batched (every subprocess run, and any in-process run
|
|
914
|
+
* whose single call already clears the batching threshold). */
|
|
915
|
+
batch: number;
|
|
677
916
|
mean?: number;
|
|
678
917
|
median?: number;
|
|
679
918
|
stddev?: number;
|
|
@@ -689,21 +928,77 @@ export interface MinimalLine {
|
|
|
689
928
|
* fastest). Only present when the document has more than one timing run. */
|
|
690
929
|
relative?: number;
|
|
691
930
|
baseline?: true;
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
931
|
+
/** `mad / median` of the machine's ~200ms reference measurement - how
|
|
932
|
+
* noisy this machine is right now, independent of what's being measured.
|
|
933
|
+
* From `document.environment`; absent when `noiseCheck: false` skipped it. */
|
|
934
|
+
noiseFloorPct?: number;
|
|
935
|
+
warnings: MinimalWarning[];
|
|
696
936
|
/** From `comparisons` when present (ostia compare / ci): the change against
|
|
697
937
|
* the baseline document for this task. */
|
|
698
|
-
delta?:
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
938
|
+
delta?: MinimalDelta;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
export interface MinimalUnmatchedLine {
|
|
942
|
+
event: "unmatched";
|
|
943
|
+
protocolVersion: typeof MINIMAL_PROTOCOL_VERSION;
|
|
944
|
+
workloadId: string;
|
|
945
|
+
task: string;
|
|
946
|
+
side: "base" | "cand";
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
export interface MinimalSummaryLine {
|
|
950
|
+
event: "summary";
|
|
951
|
+
protocolVersion: typeof MINIMAL_PROTOCOL_VERSION;
|
|
952
|
+
command: "compare" | "ci";
|
|
953
|
+
matched: number;
|
|
954
|
+
regressed: number;
|
|
955
|
+
improved: number;
|
|
956
|
+
unchanged: number;
|
|
957
|
+
unmatched: number;
|
|
958
|
+
/** `ci` only. */
|
|
959
|
+
cached?: number;
|
|
960
|
+
executed?: number;
|
|
961
|
+
failed?: number;
|
|
962
|
+
missingBaseline?: number;
|
|
963
|
+
geomeanPct: number | null;
|
|
964
|
+
effectiveTimingPct: number;
|
|
965
|
+
noiseFloorPct?: number;
|
|
966
|
+
/** `ci` only. */
|
|
967
|
+
baseline?: {
|
|
968
|
+
name: string;
|
|
969
|
+
path: string;
|
|
970
|
+
};
|
|
971
|
+
git?: {
|
|
972
|
+
base?: GitMetadata;
|
|
973
|
+
cand?: GitMetadata;
|
|
708
974
|
};
|
|
975
|
+
exportedTo?: string;
|
|
976
|
+
verdict: "pass" | "fail";
|
|
977
|
+
exitCode: number;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
export type MinimalEvent = MinimalRunLine | MinimalUnmatchedLine | MinimalSummaryLine;
|
|
981
|
+
|
|
982
|
+
export interface MinimalProtocolContext {
|
|
983
|
+
command: "compare" | "ci";
|
|
984
|
+
exitCode: number;
|
|
985
|
+
unmatched?: {
|
|
986
|
+
baseOnly: Workload[];
|
|
987
|
+
candOnly: Workload[];
|
|
988
|
+
};
|
|
989
|
+
baseGit?: GitMetadata;
|
|
990
|
+
candGit?: GitMetadata;
|
|
991
|
+
baseline?: {
|
|
992
|
+
name: string;
|
|
993
|
+
path: string;
|
|
994
|
+
};
|
|
995
|
+
cached?: number;
|
|
996
|
+
executed?: number;
|
|
997
|
+
failed?: number;
|
|
998
|
+
missingBaseline?: number;
|
|
999
|
+
exportedTo?: string;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export interface MinimalRenderOptions {
|
|
1003
|
+
protocol?: MinimalProtocolContext;
|
|
709
1004
|
}
|
package/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{
|
|
2
|
+
import{c,C,b,Q,u,i,ee,ne,te,V,re}from"./chunk-cf9hya3b.js";import{t,a,A,r,U,se,ie}from"./chunk-1wydyvyj.js";export{c as DEFAULT_THRESHOLDS,u as MINIMAL_PROTOCOL_VERSION,A as OstiaDocumentError,b as bench,C as compareDocuments,t as createDocument,te as defineConfig,se as group,U as keep,r as loadDocument,re as profile,Q as range,i as renderers,ee as run,a as saveDocument,ne as sweep,ie as task,V as time};
|
package/package.json
CHANGED
package/runner.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
import{
|
|
4
|
-
`),2;let
|
|
5
|
-
`),2;let
|
|
6
|
-
`);let
|
|
7
|
-
`),2;if(
|
|
3
|
+
import{a,M,X,v,G,T,R}from"./chunk-1wydyvyj.js";async function d(){let[s,l,c]=process.argv.slice(2);if(!s||!l)return process.stderr.write(`bench runner: usage: runner.ts <suiteFile> <outputPath> [optsJson]
|
|
4
|
+
`),2;let e=c?JSON.parse(c):{};for(let t of e.preload??[])await import(t);X(),await import(s);let i=M();if(i.length===0)return process.stderr.write(`bench runner: ${s} registered no tasks (no task() calls found).
|
|
5
|
+
`),2;let r=i.filter((t)=>t.only),p=r.length>0?r:i;if(r.length>0)process.stderr.write(`bench: ${r.length} task(s) selected by .only
|
|
6
|
+
`);let n=T(p,e.filter);if(e.taskIds){let t=new Set(e.taskIds);n=n.filter((o)=>t.has(v(o)))}if(n.length===0)return process.stderr.write(`bench runner: --filter ${JSON.stringify(e.filter)} matched zero of ${p.length} registered task(s) in ${s}.
|
|
7
|
+
`),2;if(e.planPath){let t=n.map((o)=>({id:v(o),isolate:G(o,e.isolate??!1)}));await Bun.write(e.planPath,JSON.stringify({tasks:t}))}let f=e.taskIds?n:n.filter((t)=>!G(t,e.isolate??!1)),u=await R(s,f,e);return await a(u,l),0}d().then((s)=>process.exit(s));
|
package/chunk-pesqame2.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
var De={ns:1,us:1000,ms:1e6,s:1e9};async function E(r){let a=r.timeSource!==void 0,u=Bun.nanoseconds(),m=Bun.spawn(r.argv,{cwd:r.cwd,env:r.env,stdout:a?"pipe":"ignore",stderr:a?"pipe":"ignore",stdin:"ignore"}),x=a?Promise.all([new Response(m.stdout).text(),new Response(m.stderr).text()]):void 0,M=await m.exited,I=Bun.nanoseconds(),N=m.resourceUsage?.(),B={wallNs:I-u,exitCode:M,userNs:N?Number(N.cpuTime.user)*1000:void 0,systemNs:N?Number(N.cpuTime.system)*1000:void 0,maxRssBytes:N?.maxRSS};if(x&&r.timeSource){let[U,ne]=await x;B.reportedNs=_e(r.timeSource,U,ne,r.argv)}return B}function _e(r,a,u,m=[]){let x=typeof r.pattern==="string"?new RegExp(r.pattern):r.pattern,M=r.group??1,I=x.exec(a)??x.exec(u),N=m.length>0?` for "${m.join(" ")}"`:"";if(!I)throw Error(`timeSource pattern ${x} did not match the output${N}. Output was:
|
|
3
|
-
${$e(a,u)}`);let B=I[M];if(B===void 0)throw Error(`timeSource pattern ${x} matched${N} but has no capture group ${M} (matched text: "${I[0]}").`);let U=Number(B);if(!Number.isFinite(U))throw Error(`timeSource pattern ${x} group ${M} captured "${B}"${N}, which is not a number.`);return U*De[r.unit??"ms"]}function $e(r,a){let m=(M)=>M.length>800?`${M.slice(0,800)}\u2026(${M.length-800} more)`:M,x=[];if(r.trim())x.push(`--- stdout ---
|
|
4
|
-
${m(r.trimEnd())}`);if(a.trim())x.push(`--- stderr ---
|
|
5
|
-
${m(a.trimEnd())}`);return x.length>0?x.join(`
|
|
6
|
-
`):"(empty)"}async function v(r,a,u){if(typeof r==="function"){await r(a);return}let m=ke(r),M=await Bun.spawn(m,{cwd:u.cwd,env:u.env,stdout:"ignore",stderr:"inherit",stdin:"ignore"}).exited;if(M!==0)throw Error(`prepare command "${m.join(" ")}" exited with code ${M} before ${a.phase} trial ${a.index}.`)}function ke(r){if(r===void 0||typeof r==="function")return;return Array.isArray(r)?r:f(r)}function Me(r){if(r===void 0)return;return{pattern:typeof r.pattern==="string"?r.pattern:r.pattern.source,...r.group!==void 0&&{group:r.group},...r.unit!==void 0&&{unit:r.unit}}}function f(r){return r.trim().split(/\s+/).filter(Boolean)}function T(r){return JSON.stringify(ge(r))}function ge(r){if(Array.isArray(r))return r.map(ge);if(r!==null&&typeof r==="object"){let a={};for(let u of Object.keys(r).sort())a[u]=ge(r[u]);return a}return r}function e(r,...a){let u=Bun.CryptoHasher.hash("sha256",T(a),"hex");return`${r}_${u.slice(0,16)}`}var be,Se=!1;function Te(r){let a=Bun.spawnSync(["git",...r],{timeout:200,stdout:"pipe",stderr:"ignore"});return a.success?a.stdout.toString().trim():void 0}function ve(){if(Se)return be;Se=!0;try{let r=Te(["rev-parse","--short","HEAD"]);if(r===void 0)be=void 0;else{let a=Te(["rev-parse","--abbrev-ref","HEAD"]),u=Te(["status","--porcelain"]);be={sha:r,branch:a??"HEAD",dirty:(u?.length??0)>0}}}catch{be=void 0}return be}var g="0.1.0";function n(r,a,u){let m=ve();return{schemaVersion:2,toolVersion:g,bunVersion:Bun.version,platform:{os:process.platform,arch:process.arch},createdAt:new Date().toISOString(),workloads:r,measurements:a,...u!==void 0&&{environment:u},...m!==void 0&&{git:m}}}function h(r,a,u={}){let m=ke(u.prepare),x=Me(u.timeSource),M=typeof u.prepare==="function"?u.prepare.toString():m;return{id:e("wl","subprocess",r,process.cwd(),...M!==void 0||x!==void 0?[M??null,x??null]:[]),kind:"subprocess",command:r,label:a,...m!==void 0&&{prepare:m},...x!==void 0&&{timeSource:x}}}function A(r,a){return{id:e("wl","inprocess",r.name,r.toString()),kind:"inprocess",label:a}}function W(r,a,u={}){return{id:u.params!==void 0?e("wl","inprocess-entry",r,a,u.params):e("wl","inprocess-entry",r,a),kind:"inprocess",entry:{file:r,task:a,...u.group!==void 0&&{group:u.group}},...u.label!==void 0&&{label:u.label},...u.baseline!==void 0&&{baseline:u.baseline},...u.description!==void 0&&{description:u.description},...u.groupDescription!==void 0&&{groupDescription:u.groupDescription},...u.isolated!==void 0&&{isolated:u.isolated},...u.params!==void 0&&{params:u.params},...u.skipped!==void 0&&{skipped:u.skipped}}}function c(r){return{id:e("run",r.workload.id,"timing",r.configFingerprint,Bun.version,g),workloadId:r.workload.id,phase:"timing",instrumented:!1,configFingerprint:r.configFingerprint,trials:r.trials,timing:r.timing,warnings:r.warnings,artifacts:[],memory:Ce(r.trials),...r.interleaved!==void 0&&{interleaved:r.interleaved}}}function Ce(r){let a=r.map((u)=>u.maxRssBytes).filter((u)=>u!==void 0);if(a.length===0)return;return{origin:"resourceUsage",perTrial:r.map((u)=>({rssBytes:u.maxRssBytes})),maxRssBytes:Math.max(...a)}}function l(r){return{id:e("run",r.workload.id,r.phase,r.configFingerprint,Bun.version,g),workloadId:r.workload.id,phase:r.phase,instrumented:!0,configFingerprint:r.configFingerprint,trials:[{i:0,wallNs:r.diagnosticWallNs,exitCode:r.exitCode}],diagnosticWallNs:r.diagnosticWallNs,cpu:r.cpu,heap:r.heap,memory:r.memory,jit:r.jit,warnings:r.warnings,artifacts:r.artifacts}}async function j(r,a,u){let x=await Bun.file(u).arrayBuffer(),M=new Bun.CryptoHasher("sha256");return M.update(x),{id:e("art",r,a,u),kind:a,path:u,sha256:M.digest("hex"),bytes:x.byteLength}}function s(r){return e("cfg",r)}function F(r){return`${JSON.stringify(ge(r),null,2)}
|
|
7
|
-
`}async function i(r,a){await Bun.write(a,F(r))}function We(r){if(r.schemaVersion===2)return r;let{runs:a,comparisons:u,...m}=r;return{...m,schemaVersion:2,measurements:a.map(({baselineRunId:x,...M})=>({...M,...x!==void 0&&{baselineMeasurementId:x}})),...u!==void 0&&{comparisons:u.map(({baselineRunId:x,candidateRunId:M,...I})=>({...I,baselineMeasurementId:x,candidateMeasurementId:M}))}}}async function t(r){let a=await Bun.file(r).text(),u=JSON.parse(a);return We(u)}import{profile as Le}from"bun:jsc";var Ge=new Map([["LLInt","llint"],["Baseline","baseline"],["DFG","dfg"],["FTL","ftl"]]),Ie=4294967295;function Ne(r,a){let u=a??r.interval*1e6,m=new Map,x=[];function M(p,S,P,re){let te=m.get(p);if(te===void 0)te=new Map,m.set(p,te);let Y=S??"",ae=te.get(Y);if(ae===void 0)ae=x.length,te.set(Y,ae),x.push({key:e("fr",p,Y),name:p,url:S,line:P,col:re});return ae}function I(p){let S=p.line===Ie,P=S?void 0:p.line-1,re=S||p.column===Ie?void 0:p.column-1;return M(p.name,p.sourceURL,P,re)}let N=M("(root)",void 0,void 0,void 0),B=1,U={id:0,frameIx:N,children:new Map,selfUs:0,samples:0,totalUs:0},ne=new Map([[0,U]]),oe={llint:0,baseline:0,dfg:0,ftl:0},K=new Map,me=[],ue=[];for(let p of r.traces){let S=p.frames,P=U;for(let Y=S.length-1;Y>=0;Y--){let ae=I(S[Y]),pe=P.children.get(ae);if(!pe)pe={id:B++,frameIx:ae,children:new Map,selfUs:0,samples:0,totalUs:0},P.children.set(ae,pe),ne.set(pe.id,pe);P=pe}P.selfUs+=u,P.samples+=1,me.push(P.id),ue.push(u);let re=S[0],te=re&&Ge.get(re.category);if(te){oe[te]++;let Y=K.get(te)??new Map;Y.set(P.frameIx,(Y.get(P.frameIx)??0)+1),K.set(te,Y)}}function ie(p){let S=p.selfUs;for(let P of p.children.values())S+=ie(P);return p.totalUs=S,S}ie(U);let ce=new Map;function se(p){let S=ce.get(p.frameIx);if(S)S.selfUs+=p.selfUs,S.totalUs+=p.totalUs,S.samples+=p.samples;else ce.set(p.frameIx,{frameIx:p.frameIx,selfUs:p.selfUs,totalUs:p.totalUs,samples:p.samples});for(let P of p.children.values())se(P)}se(U);let le=[...ne.values()].map((p)=>({id:p.id,frameIx:p.frameIx,children:[...p.children.values()].map((S)=>S.id)})),de={origin:"jsc-profile",samplingIntervalUs:u,frames:x,nodes:le,totals:[...ce.values()].sort((p,S)=>S.selfUs-p.selfUs),samples:{nodeIds:me,timeDeltasUs:ue}},D=[...K.entries()].flatMap(([p,S])=>[...S.entries()].sort((P,re)=>re[1]-P[1]).slice(0,3).map(([P,re])=>({tier:p,frameKey:x[P].key,samples:re})));return{cpu:de,jit:{origin:"jsc-profile",tiers:oe,topFramesByTier:D}}}var He=1000;async function b(r,a={}){let u=a.intervalUs??He,m,x=Bun.nanoseconds(),M=await Le(async()=>(m=await r(),m),u),I=Bun.nanoseconds()-x,{cpu:N,jit:B}=Ne(M.stackTraces,u);return{result:m,cpu:N,jit:B,diagnosticWallNs:I}}import xe from"os";function d(r){if(r.length===0)throw Error("computeTimingStats: samples must be non-empty");let a=r.length,u=Ee(r),m=0;for(let p=0;p<a;p++)m+=r[p];let x=m/a,M=o(u,0.5),I=0;for(let p=0;p<a;p++){let S=r[p]-x;I+=S*S}let N=Math.sqrt(I/a),B=u[0],U=u[a-1],ne=o(u,0.25),oe=o(u,0.75),K=oe-ne,me=ne-1.5*K,ue=oe+1.5*K,ie=ne-3*K,ce=oe+3*K,se=0,le=0;for(let p=0;p<a;p++){let S=r[p];if(S<ie||S>ce)le++;else if(S<me||S>ue)se++}let de=o(u,0.99),D=new Float64Array(a);for(let p=0;p<a;p++)D[p]=Math.abs(r[p]-M);D.sort();let Z=o(D,0.5);return{unit:"ns",samples:r,mean:x,median:M,stddev:N,min:B,max:U,outliers:{mild:se,severe:le},p75:oe,p99:de,mad:Z}}function Ee(r){let a=new Float64Array(r.length);return a.set(r),a.sort(),a}function o(r,a){let u=r.length;if(u===1)return r[0];let m=a*(u-1),x=Math.floor(m),M=Math.ceil(m);if(x===M)return r[x];let I=m-x;return r[x]*(1-I)+r[M]*I}var Je=5000000,je=200;function R(r,a,u="subprocess"){let m=[],x=r.samples[0];if(x!==void 0){let I=Ee(r.samples),N=o(I,0.25),U=o(I,0.75)-N;if(x>r.median+3*U&&U>0)m.push({code:"slow-first-run",message:`First run took ${(x/1e6).toFixed(2)}ms, much slower than the median ${(r.median/1e6).toFixed(2)}ms. Consider more warmup.`,data:{firstNs:x,medianNs:r.median}})}if(r.outliers.mild+r.outliers.severe>0)m.push({code:"outliers-detected",message:`${r.outliers.mild+r.outliers.severe} outlier(s) detected (${r.outliers.severe} severe, ${r.outliers.mild} mild).`,data:r.outliers});if(u==="subprocess"&&r.median<Je)m.push({code:"fast-command",message:`Median run time (${(r.median/1e6).toFixed(3)}ms) is very fast; results may be dominated by spawn overhead.`,data:{medianNs:r.median}});if(u==="inprocess"&&r.median<je)m.push({code:"below-timer-resolution",message:`Median run time (${r.median.toFixed(0)}ns) is close to timer resolution; consider a larger batch size or a coarser operation.`,data:{medianNs:r.median}});let M=a.filter((I)=>I!==void 0&&I!==0);if(M.length>0)m.push({code:"nonzero-exit",message:`${M.length} of ${a.length} trial(s) exited non-zero.`,data:{exitCodes:M}});return m}var Ve=200,Fe=64,qe=(()=>{let r=new Uint8Array(4096);for(let a=0;a<r.length;a++)r[a]=a*2654435761&255;return r})();function ze(r,a){let u=a;for(let m=0;m<r.length;m++)u^=r[m],u=Math.imul(u,16777619);return u>>>0}function Ke(r){let a=d(r),u=a.mad??0;return{floorPct:a.median===0?0:u/a.median*100,referenceMedianNs:a.median,samples:r.length}}function Be(r=Ve){let a=r*1e6,u=[],m=0,x=Bun.nanoseconds(),M=0;while(M<a){let I=Bun.nanoseconds();for(let B=0;B<Fe;B++)m^=ze(qe,B);let N=Bun.nanoseconds();u.push((N-I)/Fe),M=Bun.nanoseconds()-x}return Ke(u)}var Xe=0.75;function w(){let[r=0,a=0]=xe.loadavg();return{cpuModel:xe.cpus()[0]?.model??"unknown",cores:xe.availableParallelism(),loadAvg1:r,loadAvg5:a,noise:Be()}}function k(r){if(r.loadAvg1<=r.cores*Xe)return;return{code:"noisy-machine",message:`Load average ${r.loadAvg1.toFixed(2)} exceeds 75% of ${r.cores} available core(s); timing noise may be elevated.`,data:{loadAvg1:r.loadAvg1,cores:r.cores}}}var Ze=500,Qe=20,Oe=3,Ye=10,en=2,nn=0.1,rn=1000,tn=1e4;function Ue(r){let a=Math.log10(Math.max(1,r)/1e6),u=Math.round(Oe+en*a);return Math.min(Ye,Math.max(Oe,u))}function on(r,a){let u=Math.floor(a/r);return Math.min(Qe,Math.max(u,Ue(r)))}var Pe=0;function C(r){if(typeof r==="number")Pe+=r;else if(r!==void 0&&r!==null)Pe+=1}function we(r){return r!==null&&typeof r==="object"&&typeof r.then==="function"}function Ae(r,a){return Math.max(1,Math.ceil(rn/r),Math.ceil(a/(r*tn)))}async function _(r,a={}){let u=(a.budgetMs??Ze)*1e6,m=u*(a.warmup??nn),x=Bun.nanoseconds(),M=0,I=0;while(I<m){let D=r();C(we(D)?await D:D),M++,I=Bun.nanoseconds()-x}let N;if(M>0)N=Math.max(1,I/M);else{let D=Bun.nanoseconds(),Z=r();C(we(Z)?await Z:Z),N=Math.max(1,Bun.nanoseconds()-D)}let B=Ae(N,u);if(B>1){let D=Bun.nanoseconds();for(let Z=0;Z<B;Z++){let p=r();C(we(p)?await p:p)}N=Math.max(1,(Bun.nanoseconds()-D)/B),B=Ae(N,u)}let U=N*B,ne=a.samples??a.minSamples??on(U,u),oe=a.samples!==void 0?0:u,K=[],me=Bun.nanoseconds(),ue=0,ie=0;while(ie<ne||ue<oe){let D=Bun.nanoseconds();for(let p=0;p<B;p++){let S=r();C(we(S)?await S:S)}let Z=Bun.nanoseconds();if(K.push({i:ie,wallNs:(Z-D)/B}),ie++,ue=Bun.nanoseconds()-me,a.gc)Bun.gc(!0);if(a.samples!==void 0&&ie>=a.samples)break}let ce=K.map((D)=>D.wallNs),se=d(ce),le=R(se,[],"inprocess"),de=Ue(U);if(K.length<de)le.push({code:"low-sample-count",message:`Only ${K.length} sample(s) at ~${sn(U)} per trial; ${de} is the floor for this cost class. Raise minSamples or the time budget for a steadier number.`,data:{samples:K.length,target:de,trialCostNs:U}});return{trials:K,timing:se,warnings:le}}function sn(r){if(r>=1e9)return`${(r/1e9).toFixed(2)}s`;if(r>=1e6)return`${(r/1e6).toFixed(1)}ms`;if(r>=1000)return`${(r/1000).toFixed(1)}\xB5s`;return`${r.toFixed(0)}ns`}var Re=[],X,fe;function he(r,a,u,m){let x=X;X={name:r,description:u?.description,isolate:u?.isolate,gc:u?.gc,cpu:u?.cpu,alloc:u?.alloc,before:u?.before,after:u?.after,skip:m.skip,only:m.only};try{a()}finally{X=x}}var Q=Object.assign((r,a,u)=>he(r,a,u,{}),{skip:(r,a,u)=>he(r,a,u,{skip:!0}),only:(r,a,u)=>he(r,a,u,{only:!0})});function ye(r,a,u,m){let x=fe!==void 0||u?.params!==void 0?{...fe,...u?.params}:void 0;Re.push({groupName:X?.name,groupDescription:X?.description,groupIsolate:X?.isolate,groupGc:X?.gc,groupCpu:X?.cpu,groupAlloc:X?.alloc,groupBefore:X?.before,groupAfter:X?.after,name:r,fn:a,baseline:u?.baseline,params:x,skipped:m.skip||X?.skip,only:m.only||X?.only,opts:u})}var ee=Object.assign((r,a,u)=>ye(r,a,u,{}),{skip:(r,a,u)=>ye(r,a,u,{skip:!0}),only:(r,a,u)=>ye(r,a,u,{only:!0})});function L(){return Re}function H(){Re.length=0,X=void 0,fe=void 0}function J(r,a){let u=fe;fe=r;try{return a()}finally{fe=u}}function y(r){return r.groupName?`${r.groupName}/${r.name}`:r.name}function O(r,a){return r.opts?.isolate??r.groupIsolate??a}function G(r,a){return r.opts?.gc??r.groupGc??a}function V(r,a){return r.opts?.cpu??r.groupCpu??a}function z(r,a){return r.opts?.alloc??r.groupAlloc??a}function q(r,a){if(!a)return[...r];let u=new RegExp(a);return r.filter((m)=>u.test(y(m)))}
|
|
8
|
-
export{T,e,d,o,R,E,v,f,g,n,h,A,W,c,l,j,s,F,i,t,b,w,k,C,_,Q,ee,L,H,J,y,O,G,V,z,q};
|