ostia 0.2.1 → 0.2.3
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-2s4xk1mg.js +12 -0
- package/chunk-dpxy38mq.js +22 -0
- package/cli.js +124 -67
- package/index.d.ts +336 -34
- package/index.js +1 -1
- package/package.json +4 -1
- package/runner.ts +5 -5
- package/chunk-9dqf5cxf.js +0 -8
- package/chunk-h6579788.js +0 -21
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare function defineConfig(config:
|
|
1
|
+
export declare function defineConfig(config: OstiaConfigInput): OstiaConfigInput;
|
|
2
2
|
|
|
3
3
|
export interface CommandSpec {
|
|
4
4
|
command: string | string[];
|
|
@@ -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,9 +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"> & {
|
|
179
|
+
thresholds?: Partial<Thresholds>;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export interface Thresholds {
|
|
117
183
|
timingPct: number;
|
|
118
184
|
frameSelfPct: number;
|
|
119
185
|
heapTypePct: number;
|
|
@@ -126,7 +192,18 @@ interface Thresholds {
|
|
|
126
192
|
bootstrapIterations: number;
|
|
127
193
|
}
|
|
128
194
|
|
|
129
|
-
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;
|
|
130
207
|
|
|
131
208
|
export interface ProfileDocument {
|
|
132
209
|
schemaVersion: 2;
|
|
@@ -140,6 +217,18 @@ export interface ProfileDocument {
|
|
|
140
217
|
workloads: Workload[];
|
|
141
218
|
measurements: Measurement[];
|
|
142
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
|
+
};
|
|
143
232
|
/** Machine conditions when this document was measured. Additive, no
|
|
144
233
|
* schema bump. Absent when `noiseCheck: false` (or `--no-noise-check`)
|
|
145
234
|
* skipped the reference measurement. */
|
|
@@ -176,6 +265,15 @@ interface Environment {
|
|
|
176
265
|
}
|
|
177
266
|
|
|
178
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. */
|
|
179
277
|
id: string;
|
|
180
278
|
kind: "subprocess" | "inprocess";
|
|
181
279
|
label?: string;
|
|
@@ -251,7 +349,6 @@ interface Measurement {
|
|
|
251
349
|
jit?: JitTierBreakdown;
|
|
252
350
|
warnings: Warning[];
|
|
253
351
|
artifacts: ArtifactRef[];
|
|
254
|
-
baselineMeasurementId?: string;
|
|
255
352
|
/** True when this timing measurement's trials were run round-robin against
|
|
256
353
|
* the other commands in the same `time()` call (`--interleave`, default on
|
|
257
354
|
* for 2+ commands) rather than run to completion before the next command
|
|
@@ -272,6 +369,14 @@ interface Trial {
|
|
|
272
369
|
userNs?: number;
|
|
273
370
|
systemNs?: number;
|
|
274
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;
|
|
275
380
|
}
|
|
276
381
|
|
|
277
382
|
interface TimingStats {
|
|
@@ -295,6 +400,12 @@ interface TimingStats {
|
|
|
295
400
|
* all samples. A robust spread measure that (unlike stddev) isn't skewed
|
|
296
401
|
* by the long right tail typical of wall-clock timings. */
|
|
297
402
|
mad?: number;
|
|
403
|
+
/** In-process trials batched into one timed block (see
|
|
404
|
+
* `measure/inprocess.ts`'s `sizeBatch`), set only when batching occurred.
|
|
405
|
+
* Absent for every subprocess timing measurement, and for an in-process
|
|
406
|
+
* one whose single call already cleared the batching threshold - a
|
|
407
|
+
* renderer treats an absent value the same as `1`. */
|
|
408
|
+
batch?: number;
|
|
298
409
|
}
|
|
299
410
|
|
|
300
411
|
interface Frame {
|
|
@@ -339,7 +450,6 @@ interface HeapEvidence {
|
|
|
339
450
|
count: number;
|
|
340
451
|
retainedBytes?: number;
|
|
341
452
|
}[];
|
|
342
|
-
snapshotArtifactId?: string;
|
|
343
453
|
}
|
|
344
454
|
|
|
345
455
|
interface MemoryEvidence {
|
|
@@ -373,7 +483,9 @@ interface JitTierBreakdown {
|
|
|
373
483
|
}[];
|
|
374
484
|
}
|
|
375
485
|
|
|
376
|
-
|
|
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];
|
|
377
489
|
|
|
378
490
|
export interface Warning {
|
|
379
491
|
code: WarningCode;
|
|
@@ -389,7 +501,7 @@ interface ArtifactRef {
|
|
|
389
501
|
bytes: number;
|
|
390
502
|
}
|
|
391
503
|
|
|
392
|
-
interface Comparison {
|
|
504
|
+
export interface Comparison {
|
|
393
505
|
id: string;
|
|
394
506
|
baselineMeasurementId: string;
|
|
395
507
|
candidateMeasurementId: string;
|
|
@@ -443,6 +555,24 @@ interface Comparison {
|
|
|
443
555
|
verdict: "pass" | "fail";
|
|
444
556
|
}
|
|
445
557
|
|
|
558
|
+
interface ComparisonSummary {
|
|
559
|
+
/** `comparisons.length`: workloads present (and comparable) on both sides. */
|
|
560
|
+
matched: number;
|
|
561
|
+
regressed: number;
|
|
562
|
+
improved: number;
|
|
563
|
+
unchanged: number;
|
|
564
|
+
/** Geometric mean of `cand/base` median ratios over matched timing
|
|
565
|
+
* comparisons, as a signed percent (negative: candidate faster on
|
|
566
|
+
* average). `null` when no comparison had a finite timing ratio. */
|
|
567
|
+
geomeanPct: number | null;
|
|
568
|
+
/** Same value as `Comparison.thresholds.effectiveTimingPct` - one number
|
|
569
|
+
* for the whole document pair, since it depends only on `thresholds` and
|
|
570
|
+
* the two documents' `environment.noise.floorPct`, never per-workload. */
|
|
571
|
+
effectiveTimingPct: number;
|
|
572
|
+
/** `"fail"` when any comparison's verdict is `"fail"`. */
|
|
573
|
+
verdict: "pass" | "fail";
|
|
574
|
+
}
|
|
575
|
+
|
|
446
576
|
export interface PrepareRun {
|
|
447
577
|
phase: "warmup" | "timing" | "cpu" | "heap";
|
|
448
578
|
index: number;
|
|
@@ -460,6 +590,31 @@ export interface TimeSource {
|
|
|
460
590
|
unit?: TimeUnit;
|
|
461
591
|
}
|
|
462
592
|
|
|
593
|
+
interface InprocessTimingOptions {
|
|
594
|
+
/** Wall-clock budget for the sampling loop, per task (default: 500). The loop
|
|
595
|
+
* always runs for at least this long, unless `samples` is set. */
|
|
596
|
+
budgetMs?: number;
|
|
597
|
+
/** Exact trial count. When set, the budget is ignored and the loop runs
|
|
598
|
+
* exactly this many trials, however slow each one is - the in-process
|
|
599
|
+
* equivalent of `time()`'s `samples`. */
|
|
600
|
+
samples?: number;
|
|
601
|
+
/** Hard floor on the number of trials when no exact `samples` count is
|
|
602
|
+
* given. When set, the loop keeps sampling past the time budget until this
|
|
603
|
+
* many trials exist, however slow each one is. When unset, the floor is
|
|
604
|
+
* cost-aware (see `defaultSampleFloor`): as many trials as fit in the
|
|
605
|
+
* budget, capped at 20, but never below the rigor floor the task's per-trial cost
|
|
606
|
+
* earns it (3 at ≤1ms, rising to 10 for multi-second calls). */
|
|
607
|
+
minSamples?: number;
|
|
608
|
+
/** Warmup budget as a fraction of `budgetMs` (default: 0.1) - a fraction,
|
|
609
|
+
* not a call count, named `warmup` for cross-surface consistency with
|
|
610
|
+
* `time()`'s trial-count `warmup`; the two are genuinely different units (a
|
|
611
|
+
* fraction here, a count there), not papered over. Warmup always runs at
|
|
612
|
+
* least one call; for a task slower than the warmup budget that single call
|
|
613
|
+
* is the whole warmup. */
|
|
614
|
+
warmup?: number;
|
|
615
|
+
gc?: boolean;
|
|
616
|
+
}
|
|
617
|
+
|
|
463
618
|
export declare function keep(value: unknown): void;
|
|
464
619
|
|
|
465
620
|
interface BenchOptions {
|
|
@@ -518,6 +673,21 @@ interface BenchOptions {
|
|
|
518
673
|
* subprocess (default: true) and stamp it on the document as
|
|
519
674
|
* `environment`. Set false to skip the ~200ms reference measurement. */
|
|
520
675
|
noiseCheck?: boolean;
|
|
676
|
+
/** Kills a suite file's subprocess (or, under `isolate`, one task's
|
|
677
|
+
* dedicated subprocess) with SIGKILL if it hasn't finished after this many
|
|
678
|
+
* ms. No default: an unset `timeoutMs` never times out. Applies to the
|
|
679
|
+
* whole subprocess, not per task - the same granularity `isolate` already
|
|
680
|
+
* runs at. */
|
|
681
|
+
timeoutMs?: number;
|
|
682
|
+
/** Aborting cancels the run: in-flight suite/isolated-task subprocesses
|
|
683
|
+
* are killed with SIGKILL, no new ones are started, and `bench()` resolves
|
|
684
|
+
* (never rejects) with whatever suites/tasks had already finished when the
|
|
685
|
+
* signal fired, plus an `aborted` warning on the document's last
|
|
686
|
+
* measurement. A suite subprocess killed mid-run contributes nothing (it
|
|
687
|
+
* only writes its result once, at the end), so a suite that was in flight
|
|
688
|
+
* when the signal fired is dropped entirely rather than partially
|
|
689
|
+
* represented. */
|
|
690
|
+
signal?: AbortSignal;
|
|
521
691
|
}
|
|
522
692
|
|
|
523
693
|
export declare function bench(opts: BenchOptions): Promise<ProfileDocument>;
|
|
@@ -628,6 +798,50 @@ interface TaskFn {
|
|
|
628
798
|
|
|
629
799
|
export declare const task: TaskFn;
|
|
630
800
|
|
|
801
|
+
export interface RunOptions extends MeasureTasksOpts {
|
|
802
|
+
/** Regex, matched against "group/name" task ids - same semantics as
|
|
803
|
+
* `ostia bench --filter` / `bench({ filter })`. */
|
|
804
|
+
filter?: string;
|
|
805
|
+
/** Skip printing the report to stdout; still returns the document. */
|
|
806
|
+
quiet?: boolean;
|
|
807
|
+
/** Renderer used for the printed report (default: `"table"`). */
|
|
808
|
+
format?: FormatName;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
export declare function run(opts?: RunOptions): Promise<ProfileDocument>;
|
|
812
|
+
|
|
813
|
+
type FormatName = "table" | "json" | "markdown" | "jsonl" | "minimal" | "collapsed" | "mermaid" | "speedscope" | "cpuprofile";
|
|
814
|
+
|
|
815
|
+
interface RenderResult {
|
|
816
|
+
text?: string;
|
|
817
|
+
files?: {
|
|
818
|
+
path?: string;
|
|
819
|
+
content: string;
|
|
820
|
+
}[];
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
interface Renderer<O = unknown> {
|
|
824
|
+
name: FormatName;
|
|
825
|
+
render(doc: ProfileDocument, options: O): Promise<RenderResult>;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
interface MeasureTasksOpts extends InprocessTimingOptions {
|
|
829
|
+
/** Suite-wide default for capturing an extra `phase: "cpu"` measurement
|
|
830
|
+
* per task (task/group `TaskOptions.cpu`/`GroupOptions.cpu` still win). */
|
|
831
|
+
cpu?: boolean;
|
|
832
|
+
/** Suite-wide default for capturing an extra `phase: "memstats"`
|
|
833
|
+
* measurement per task (task/group `TaskOptions.alloc`/`GroupOptions.alloc`
|
|
834
|
+
* still win). */
|
|
835
|
+
alloc?: boolean;
|
|
836
|
+
/** Stamped onto every workload this call produces, recording whether it
|
|
837
|
+
* ran in a subprocess dedicated to it alone. */
|
|
838
|
+
markIsolated?: boolean;
|
|
839
|
+
/** Measure this machine's noise floor before the first task (default:
|
|
840
|
+
* true) and stamp it on the document as `environment`. Set false to skip
|
|
841
|
+
* the ~200ms reference measurement. */
|
|
842
|
+
noiseCheck?: boolean;
|
|
843
|
+
}
|
|
844
|
+
|
|
631
845
|
type SweepPoint<T extends Record<string, readonly unknown[]>> = {
|
|
632
846
|
[K in keyof T]: T[K][number];
|
|
633
847
|
};
|
|
@@ -639,26 +853,52 @@ export { newDocument as createDocument };
|
|
|
639
853
|
|
|
640
854
|
export declare function saveDocument(doc: ProfileDocument, path: string): Promise<void>;
|
|
641
855
|
|
|
856
|
+
export declare class OstiaDocumentError extends Error {
|
|
857
|
+
readonly code: "invalid-json" | "not-a-document" | "unsupported-schema";
|
|
858
|
+
readonly path?: string;
|
|
859
|
+
readonly schemaVersion?: unknown;
|
|
860
|
+
constructor(code: OstiaDocumentError["code"], message: string, opts?: {
|
|
861
|
+
path?: string;
|
|
862
|
+
schemaVersion?: unknown;
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
|
|
642
866
|
export declare function loadDocument(path: string): Promise<ProfileDocument>;
|
|
643
867
|
|
|
644
868
|
export declare const renderers: Record<FormatName, Renderer<any>>;
|
|
645
869
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
interface RenderResult {
|
|
649
|
-
text?: string;
|
|
650
|
-
files?: {
|
|
651
|
-
path?: string;
|
|
652
|
-
content: string;
|
|
653
|
-
}[];
|
|
654
|
-
}
|
|
870
|
+
export declare const MINIMAL_PROTOCOL_VERSION: 1;
|
|
655
871
|
|
|
656
|
-
interface
|
|
657
|
-
|
|
658
|
-
|
|
872
|
+
interface MinimalWarning {
|
|
873
|
+
code: string;
|
|
874
|
+
data?: Record<string, unknown>;
|
|
659
875
|
}
|
|
660
876
|
|
|
661
|
-
export interface
|
|
877
|
+
export interface MinimalDelta {
|
|
878
|
+
medianPct: number;
|
|
879
|
+
meanPct: number;
|
|
880
|
+
verdict: "improved" | "regressed" | "unchanged";
|
|
881
|
+
pass: boolean;
|
|
882
|
+
/** 95% bootstrap CI on the difference of medians and the Mann-Whitney
|
|
883
|
+
* p-value behind the verdict. Absent on a thin (<5 samples/side)
|
|
884
|
+
* comparison, which falls back to a point-estimate threshold. */
|
|
885
|
+
ci95?: [number, number];
|
|
886
|
+
pValue?: number;
|
|
887
|
+
/** The threshold this delta was actually tested against, once machine
|
|
888
|
+
* noise widened it past `thresholds.timingPct` - see
|
|
889
|
+
* `Comparison.thresholds.effectiveTimingPct`. */
|
|
890
|
+
effectiveTimingPct: number;
|
|
891
|
+
matched: true;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
export interface MinimalRunLine {
|
|
895
|
+
event: "run";
|
|
896
|
+
protocolVersion: typeof MINIMAL_PROTOCOL_VERSION;
|
|
897
|
+
schemaVersion: ProfileDocument["schemaVersion"];
|
|
898
|
+
/** Join key back to `Workload.id` / `Comparison.candidateMeasurementId` -
|
|
899
|
+
* stable across a `time`/`compare`/`ci` invocation of the same command,
|
|
900
|
+
* unlike `task` (a label, not an identity). */
|
|
901
|
+
workloadId: string;
|
|
662
902
|
task: string;
|
|
663
903
|
group?: string;
|
|
664
904
|
description?: string;
|
|
@@ -670,6 +910,12 @@ export interface MinimalLine {
|
|
|
670
910
|
skipped?: true;
|
|
671
911
|
unit?: "ns";
|
|
672
912
|
samples?: number;
|
|
913
|
+
/** In-process trials batched into one timed block (see
|
|
914
|
+
* `measure/inprocess.ts`'s `sizeBatch`) so the timer's own resolution
|
|
915
|
+
* doesn't dominate a sub-microsecond task's reading. 1 when the timing
|
|
916
|
+
* engine never batched (every subprocess run, and any in-process run
|
|
917
|
+
* whose single call already clears the batching threshold). */
|
|
918
|
+
batch: number;
|
|
673
919
|
mean?: number;
|
|
674
920
|
median?: number;
|
|
675
921
|
stddev?: number;
|
|
@@ -685,21 +931,77 @@ export interface MinimalLine {
|
|
|
685
931
|
* fastest). Only present when the document has more than one timing run. */
|
|
686
932
|
relative?: number;
|
|
687
933
|
baseline?: true;
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
934
|
+
/** `mad / median` of the machine's ~200ms reference measurement - how
|
|
935
|
+
* noisy this machine is right now, independent of what's being measured.
|
|
936
|
+
* From `document.environment`; absent when `noiseCheck: false` skipped it. */
|
|
937
|
+
noiseFloorPct?: number;
|
|
938
|
+
warnings: MinimalWarning[];
|
|
692
939
|
/** From `comparisons` when present (ostia compare / ci): the change against
|
|
693
940
|
* the baseline document for this task. */
|
|
694
|
-
delta?:
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
941
|
+
delta?: MinimalDelta;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
export interface MinimalUnmatchedLine {
|
|
945
|
+
event: "unmatched";
|
|
946
|
+
protocolVersion: typeof MINIMAL_PROTOCOL_VERSION;
|
|
947
|
+
workloadId: string;
|
|
948
|
+
task: string;
|
|
949
|
+
side: "base" | "cand";
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
export interface MinimalSummaryLine {
|
|
953
|
+
event: "summary";
|
|
954
|
+
protocolVersion: typeof MINIMAL_PROTOCOL_VERSION;
|
|
955
|
+
command: "compare" | "ci";
|
|
956
|
+
matched: number;
|
|
957
|
+
regressed: number;
|
|
958
|
+
improved: number;
|
|
959
|
+
unchanged: number;
|
|
960
|
+
unmatched: number;
|
|
961
|
+
/** `ci` only. */
|
|
962
|
+
cached?: number;
|
|
963
|
+
executed?: number;
|
|
964
|
+
failed?: number;
|
|
965
|
+
missingBaseline?: number;
|
|
966
|
+
geomeanPct: number | null;
|
|
967
|
+
effectiveTimingPct: number;
|
|
968
|
+
noiseFloorPct?: number;
|
|
969
|
+
/** `ci` only. */
|
|
970
|
+
baseline?: {
|
|
971
|
+
name: string;
|
|
972
|
+
path: string;
|
|
704
973
|
};
|
|
974
|
+
git?: {
|
|
975
|
+
base?: GitMetadata;
|
|
976
|
+
cand?: GitMetadata;
|
|
977
|
+
};
|
|
978
|
+
exportedTo?: string;
|
|
979
|
+
verdict: "pass" | "fail";
|
|
980
|
+
exitCode: number;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
export type MinimalEvent = MinimalRunLine | MinimalUnmatchedLine | MinimalSummaryLine;
|
|
984
|
+
|
|
985
|
+
export interface MinimalProtocolContext {
|
|
986
|
+
command: "compare" | "ci";
|
|
987
|
+
exitCode: number;
|
|
988
|
+
unmatched?: {
|
|
989
|
+
baseOnly: Workload[];
|
|
990
|
+
candOnly: Workload[];
|
|
991
|
+
};
|
|
992
|
+
baseGit?: GitMetadata;
|
|
993
|
+
candGit?: GitMetadata;
|
|
994
|
+
baseline?: {
|
|
995
|
+
name: string;
|
|
996
|
+
path: string;
|
|
997
|
+
};
|
|
998
|
+
cached?: number;
|
|
999
|
+
executed?: number;
|
|
1000
|
+
failed?: number;
|
|
1001
|
+
missingBaseline?: number;
|
|
1002
|
+
exportedTo?: string;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
export interface MinimalRenderOptions {
|
|
1006
|
+
protocol?: MinimalProtocolContext;
|
|
705
1007
|
}
|
package/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{
|
|
2
|
+
import{u,M,f,Z,l,o,Q,ee,ne,_,te}from"./chunk-dpxy38mq.js";import{t,c,D,r,A,re,se}from"./chunk-2s4xk1mg.js";export{u as DEFAULT_THRESHOLDS,l as MINIMAL_PROTOCOL_VERSION,D as OstiaDocumentError,f as bench,M as compareDocuments,t as createDocument,ne as defineConfig,re as group,A as keep,r as loadDocument,te as profile,Z as range,o as renderers,Q as run,c as saveDocument,ee as sweep,se as task,_ 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{c,x,Y,y,H,v,P}from"./chunk-2s4xk1mg.js";async function d(){let[s,i,l]=process.argv.slice(2);if(!s||!i)return process.stderr.write(`bench runner: usage: runner.ts <suiteFile> <outputPath> [optsJson]
|
|
4
|
+
`),2;let e=l?JSON.parse(l):{};for(let t of e.preload??[])await import(t);Y(),await import(s);let a=x();if(a.length===0)return process.stderr.write(`bench runner: ${s} registered no tasks (no task() calls found).
|
|
5
|
+
`),2;let r=a.filter((t)=>t.only),p=r.length>0?r:a;if(r.length>0)process.stderr.write(`bench: ${r.length} task(s) selected by .only
|
|
6
|
+
`);let n=v(p,e.filter);if(e.taskIds){let t=new Set(e.taskIds);n=n.filter((o)=>t.has(y(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:y(o),isolate:H(o,e.isolate??!1)}));await Bun.write(e.planPath,JSON.stringify({tasks:t}))}let f=e.taskIds?n:n.filter((t)=>!H(t,e.isolate??!1)),u=await P(s,f,e);return await c(u,i),0}d().then((s)=>process.exit(s));
|
package/chunk-9dqf5cxf.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
var De={ns:1,us:1000,ms:1e6,s:1e9};async function D(r){let c=r.timeSource!==void 0,l=Bun.nanoseconds(),b=Bun.spawn(r.argv,{cwd:r.cwd,env:r.env,stdout:c?"pipe":"ignore",stderr:c?"pipe":"ignore",stdin:"ignore"}),R=c?Promise.all([new Response(b.stdout).text(),new Response(b.stderr).text()]):void 0,M=await b.exited,N=Bun.nanoseconds(),C=b.resourceUsage?.(),G={wallNs:N-l,exitCode:M,userNs:C?Number(C.cpuTime.user)*1000:void 0,systemNs:C?Number(C.cpuTime.system)*1000:void 0,maxRssBytes:C?.maxRSS};if(R&&r.timeSource){let[V,ee]=await R;G.reportedNs=_e(r.timeSource,V,ee,r.argv)}return G}function _e(r,c,l,b=[]){let R=typeof r.pattern==="string"?new RegExp(r.pattern):r.pattern,M=r.group??1,N=R.exec(c)??R.exec(l),C=b.length>0?` for "${b.join(" ")}"`:"";if(!N)throw Error(`timeSource pattern ${R} did not match the output${C}. Output was:
|
|
3
|
-
${$e(c,l)}`);let G=N[M];if(G===void 0)throw Error(`timeSource pattern ${R} matched${C} but has no capture group ${M} (matched text: "${N[0]}").`);let V=Number(G);if(!Number.isFinite(V))throw Error(`timeSource pattern ${R} group ${M} captured "${G}"${C}, which is not a number.`);return V*De[r.unit??"ms"]}function $e(r,c){let b=(M)=>M.length>800?`${M.slice(0,800)}\u2026(${M.length-800} more)`:M,R=[];if(r.trim())R.push(`--- stdout ---
|
|
4
|
-
${b(r.trimEnd())}`);if(c.trim())R.push(`--- stderr ---
|
|
5
|
-
${b(c.trimEnd())}`);return R.length>0?R.join(`
|
|
6
|
-
`):"(empty)"}async function k(r,c,l){if(typeof r==="function"){await r(c);return}let b=Array.isArray(r)?r:y(r),M=await Bun.spawn(b,{cwd:l.cwd,env:l.env,stdout:"ignore",stderr:"inherit",stdin:"ignore"}).exited;if(M!==0)throw Error(`prepare command "${b.join(" ")}" exited with code ${M} before ${c.phase} trial ${c.index}.`)}function Re(r){if(r===void 0||typeof r==="function")return;return Array.isArray(r)?r:y(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 y(r){return r.trim().split(/\s+/).filter(Boolean)}function x(r){return JSON.stringify(ge(r))}function ge(r){if(Array.isArray(r))return r.map(ge);if(r!==null&&typeof r==="object"){let c={};for(let l of Object.keys(r).sort())c[l]=ge(r[l]);return c}return r}function e(r,...c){let l=Bun.CryptoHasher.hash("sha256",x(c),"hex");return`${r}_${l.slice(0,16)}`}var be,Se=!1;function ke(r){let c=Bun.spawnSync(["git",...r],{timeout:200,stdout:"pipe",stderr:"ignore"});return c.success?c.stdout.toString().trim():void 0}function ve(){if(Se)return be;Se=!0;try{let r=ke(["rev-parse","--short","HEAD"]);if(r===void 0)be=void 0;else{let c=ke(["rev-parse","--abbrev-ref","HEAD"]),l=ke(["status","--porcelain"]);be={sha:r,branch:c??"HEAD",dirty:(l?.length??0)>0}}}catch{be=void 0}return be}var m="0.1.0";function n(r,c,l){let b=ve();return{schemaVersion:2,toolVersion:m,bunVersion:Bun.version,platform:{os:process.platform,arch:process.arch},createdAt:new Date().toISOString(),workloads:r,measurements:c,...l!==void 0&&{environment:l},...b!==void 0&&{git:b}}}function p(r,c,l={}){let b=Re(l.prepare),R=Me(l.timeSource),M=typeof l.prepare==="function"?l.prepare.toString():b;return{id:e("wl","subprocess",r,process.cwd(),...M!==void 0||R!==void 0?[M??null,R??null]:[]),kind:"subprocess",command:r,label:c,...b!==void 0&&{prepare:b},...R!==void 0&&{timeSource:R}}}function F(r,c){return{id:e("wl","inprocess",r.name,r.toString()),kind:"inprocess",label:c}}function O(r,c,l={}){return{id:l.params!==void 0?e("wl","inprocess-entry",r,c,l.params):e("wl","inprocess-entry",r,c),kind:"inprocess",entry:{file:r,task:c,...l.group!==void 0&&{group:l.group}},...l.label!==void 0&&{label:l.label},...l.baseline!==void 0&&{baseline:l.baseline},...l.description!==void 0&&{description:l.description},...l.groupDescription!==void 0&&{groupDescription:l.groupDescription},...l.isolated!==void 0&&{isolated:l.isolated},...l.params!==void 0&&{params:l.params},...l.skipped!==void 0&&{skipped:l.skipped}}}function i(r){return{id:e("run",r.workload.id,"timing",r.configFingerprint,Bun.version,m),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 c=r.map((l)=>l.maxRssBytes).filter((l)=>l!==void 0);if(c.length===0)return;return{origin:"resourceUsage",perTrial:r.map((l)=>({rssBytes:l.maxRssBytes})),maxRssBytes:Math.max(...c)}}function a(r){return{id:e("run",r.workload.id,r.phase,r.configFingerprint,Bun.version,m),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 E(r,c,l){let R=await Bun.file(l).arrayBuffer(),M=new Bun.CryptoHasher("sha256");return M.update(R),{id:e("art",r,c,l),kind:c,path:l,sha256:M.digest("hex"),bytes:R.byteLength}}function o(r){return e("cfg",r)}function S(r){return`${JSON.stringify(ge(r),null,2)}
|
|
7
|
-
`}async function s(r,c){await Bun.write(c,S(r))}function We(r){if(r.schemaVersion===2)return r;let{runs:c,comparisons:l,...b}=r;return{...b,schemaVersion:2,measurements:c.map(({baselineRunId:R,...M})=>({...M,...R!==void 0&&{baselineMeasurementId:R}})),...l!==void 0&&{comparisons:l.map(({baselineRunId:R,candidateRunId:M,...N})=>({...N,baselineMeasurementId:R,candidateMeasurementId:M}))}}}async function t(r){let c=await Bun.file(r).text(),l=JSON.parse(c);return We(l)}import{profile as Le}from"bun:jsc";var Ge=new Map([["LLInt","llint"],["Baseline","baseline"],["DFG","dfg"],["FTL","ftl"]]),Ie=4294967295;function Ne(r,c){let l=c??r.interval*1e6,b=new Map,R=[];function M(w,v,J,ne){let re=b.get(w);if(re===void 0)re=new Map,b.set(w,re);let Y=v??"",se=re.get(Y);if(se===void 0)se=R.length,re.set(Y,se),R.push({key:e("fr",w,Y),name:w,url:v,line:J,col:ne});return se}function N(w){let v=w.line===Ie,J=v?void 0:w.line-1,ne=v||w.column===Ie?void 0:w.column-1;return M(w.name,w.sourceURL,J,ne)}let C=M("(root)",void 0,void 0,void 0),G=1,V={id:0,frameIx:C,children:new Map,selfUs:0,samples:0,totalUs:0},ee=new Map([[0,V]]),te={llint:0,baseline:0,dfg:0,ftl:0},X=new Map,me=[],ae=[];for(let w of r.traces){let v=w.frames,J=V;for(let Y=v.length-1;Y>=0;Y--){let se=N(v[Y]),pe=J.children.get(se);if(!pe)pe={id:G++,frameIx:se,children:new Map,selfUs:0,samples:0,totalUs:0},J.children.set(se,pe),ee.set(pe.id,pe);J=pe}J.selfUs+=l,J.samples+=1,me.push(J.id),ae.push(l);let ne=v[0],re=ne&&Ge.get(ne.category);if(re){te[re]++;let Y=X.get(re)??new Map;Y.set(J.frameIx,(Y.get(J.frameIx)??0)+1),X.set(re,Y)}}function oe(w){let v=w.selfUs;for(let J of w.children.values())v+=oe(J);return w.totalUs=v,v}oe(V);let ue=new Map;function ie(w){let v=ue.get(w.frameIx);if(v)v.selfUs+=w.selfUs,v.totalUs+=w.totalUs,v.samples+=w.samples;else ue.set(w.frameIx,{frameIx:w.frameIx,selfUs:w.selfUs,totalUs:w.totalUs,samples:w.samples});for(let J of w.children.values())ie(J)}ie(V);let ce=[...ee.values()].map((w)=>({id:w.id,frameIx:w.frameIx,children:[...w.children.values()].map((v)=>v.id)})),le={origin:"jsc-profile",samplingIntervalUs:l,frames:R,nodes:ce,totals:[...ue.values()].sort((w,v)=>v.selfUs-w.selfUs),samples:{nodeIds:me,timeDeltasUs:ae}},z=[...X.entries()].flatMap(([w,v])=>[...v.entries()].sort((J,ne)=>ne[1]-J[1]).slice(0,3).map(([J,ne])=>({tier:w,frameKey:R[J].key,samples:ne})));return{cpu:le,jit:{origin:"jsc-profile",tiers:te,topFramesByTier:z}}}var He=1000;async function d(r,c={}){let l=c.intervalUs??He,b,R=Bun.nanoseconds(),M=await Le(async()=>(b=await r(),b),l),N=Bun.nanoseconds()-R,{cpu:C,jit:G}=Ne(M.stackTraces,l);return{result:b,cpu:C,jit:G,diagnosticWallNs:N}}import Te from"os";function u(r){if(r.length===0)throw Error("computeTimingStats: samples must be non-empty");let c=r.length,l=Ee(r),b=0;for(let w=0;w<c;w++)b+=r[w];let R=b/c,M=de(l,0.5),N=0;for(let w=0;w<c;w++){let v=r[w]-R;N+=v*v}let C=Math.sqrt(N/c),G=l[0],V=l[c-1],ee=de(l,0.25),te=de(l,0.75),X=te-ee,me=ee-1.5*X,ae=te+1.5*X,oe=ee-3*X,ue=te+3*X,ie=0,ce=0;for(let w=0;w<c;w++){let v=r[w];if(v<oe||v>ue)ce++;else if(v<me||v>ae)ie++}let le=de(l,0.99),z=new Float64Array(c);for(let w=0;w<c;w++)z[w]=Math.abs(r[w]-M);z.sort();let Q=de(z,0.5);return{unit:"ns",samples:r,mean:R,median:M,stddev:C,min:G,max:V,outliers:{mild:ie,severe:ce},p75:te,p99:le,mad:Q}}function Ee(r){let c=new Float64Array(r.length);return c.set(r),c.sort(),c}function de(r,c){let l=r.length;if(l===1)return r[0];let b=c*(l-1),R=Math.floor(b),M=Math.ceil(b);if(R===M)return r[R];let N=b-R;return r[R]*(1-N)+r[M]*N}var Je=5000000,je=200;function P(r,c,l="subprocess"){let b=[],R=r.samples[0];if(R!==void 0){let N=Ee(r.samples),C=de(N,0.25),V=de(N,0.75)-C;if(R>r.median+3*V&&V>0)b.push({code:"slow-first-run",message:`First run took ${(R/1e6).toFixed(2)}ms, much slower than the median ${(r.median/1e6).toFixed(2)}ms. Consider more warmup.`,data:{firstNs:R,medianNs:r.median}})}if(r.outliers.mild+r.outliers.severe>0)b.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(l==="subprocess"&&r.median<Je)b.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(l==="inprocess"&&r.median<je)b.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=c.filter((N)=>N!==void 0&&N!==0);if(M.length>0)b.push({code:"nonzero-exit",message:`${M.length} of ${c.length} trial(s) exited non-zero.`,data:{exitCodes:M}});return b}var Ve=200,Fe=64,qe=(()=>{let r=new Uint8Array(4096);for(let c=0;c<r.length;c++)r[c]=c*2654435761&255;return r})();function ze(r,c){let l=c;for(let b=0;b<r.length;b++)l^=r[b],l=Math.imul(l,16777619);return l>>>0}function Ke(r){let c=u(r),l=c.mad??0;return{floorPct:c.median===0?0:l/c.median*100,referenceMedianNs:c.median,samples:r.length}}function Be(r=Ve){let c=r*1e6,l=[],b=0,R=Bun.nanoseconds(),M=0;while(M<c){let N=Bun.nanoseconds();for(let G=0;G<Fe;G++)b^=ze(qe,G);let C=Bun.nanoseconds();l.push((C-N)/Fe),M=Bun.nanoseconds()-R}return Ke(l)}var Xe=0.75;function f(){let[r=0,c=0]=Te.loadavg();return{cpuModel:Te.cpus()[0]?.model??"unknown",cores:Te.availableParallelism(),loadAvg1:r,loadAvg5:c,noise:Be()}}function g(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 c=Math.log10(Math.max(1,r)/1e6),l=Math.round(Oe+en*c);return Math.min(Ye,Math.max(Oe,l))}function on(r,c){let l=Math.floor(c/r);return Math.min(Qe,Math.max(l,Ue(r)))}var Pe=0;function T(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,c){return Math.max(1,Math.ceil(rn/r),Math.ceil(c/(r*tn)))}async function U(r,c={}){let l=(c.budgetMs??Ze)*1e6,b=l*(c.warmup??nn),R=Bun.nanoseconds(),M=0,N=0;while(N<b){let z=r();T(we(z)?await z:z),M++,N=Bun.nanoseconds()-R}let C;if(M>0)C=Math.max(1,N/M);else{let z=Bun.nanoseconds(),Q=r();T(we(Q)?await Q:Q),C=Math.max(1,Bun.nanoseconds()-z)}let G=Ae(C,l);if(G>1){let z=Bun.nanoseconds();for(let Q=0;Q<G;Q++){let w=r();T(we(w)?await w:w)}C=Math.max(1,(Bun.nanoseconds()-z)/G),G=Ae(C,l)}let V=C*G,ee=c.samples??c.minSamples??on(V,l),te=c.samples!==void 0?0:l,X=[],me=Bun.nanoseconds(),ae=0,oe=0;while(oe<ee||ae<te){let z=Bun.nanoseconds();for(let w=0;w<G;w++){let v=r();T(we(v)?await v:v)}let Q=Bun.nanoseconds();if(X.push({i:oe,wallNs:(Q-z)/G}),oe++,ae=Bun.nanoseconds()-me,c.gc)Bun.gc(!0);if(c.samples!==void 0&&oe>=c.samples)break}let ue=X.map((z)=>z.wallNs),ie=u(ue),ce=P(ie,[],"inprocess"),le=Ue(V);if(X.length<le)ce.push({code:"low-sample-count",message:`Only ${X.length} sample(s) at ~${sn(V)} per trial; ${le} is the floor for this cost class. Raise minSamples or the time budget for a steadier number.`,data:{samples:X.length,target:le,trialCostNs:V}});return{trials:X,timing:ie,warnings:ce}}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 ye=[],Z,fe;function xe(r,c,l,b){let R=Z;Z={name:r,description:l?.description,isolate:l?.isolate,gc:l?.gc,cpu:l?.cpu,alloc:l?.alloc,before:l?.before,after:l?.after,skip:b.skip,only:b.only};try{c()}finally{Z=R}}var q=Object.assign((r,c,l)=>xe(r,c,l,{}),{skip:(r,c,l)=>xe(r,c,l,{skip:!0}),only:(r,c,l)=>xe(r,c,l,{only:!0})});function he(r,c,l,b){let R=fe!==void 0||l?.params!==void 0?{...fe,...l?.params}:void 0;ye.push({groupName:Z?.name,groupDescription:Z?.description,groupIsolate:Z?.isolate,groupGc:Z?.gc,groupCpu:Z?.cpu,groupAlloc:Z?.alloc,groupBefore:Z?.before,groupAfter:Z?.after,name:r,fn:c,baseline:l?.baseline,params:R,skipped:b.skip||Z?.skip,only:b.only||Z?.only,opts:l})}var K=Object.assign((r,c,l)=>he(r,c,l,{}),{skip:(r,c,l)=>he(r,c,l,{skip:!0}),only:(r,c,l)=>he(r,c,l,{only:!0})});function B(){return ye}function A(){ye.length=0,Z=void 0,fe=void 0}function W(r,c){let l=fe;fe=r;try{return c()}finally{fe=l}}function h(r){return r.groupName?`${r.groupName}/${r.name}`:r.name}function I(r,c){return r.opts?.isolate??r.groupIsolate??c}function j(r,c){return r.opts?.gc??r.groupGc??c}function _(r,c){return r.opts?.cpu??r.groupCpu??c}function L(r,c){return r.opts?.alloc??r.groupAlloc??c}function H(r,c){if(!c)return[...r];let l=new RegExp(c);return r.filter((b)=>l.test(h(b)))}
|
|
8
|
-
export{D,k,y,x,e,m,n,p,F,O,i,a,E,o,S,s,t,u,P,d,f,g,T,U,q,K,B,A,W,h,I,j,_,L,H};
|