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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare function defineConfig(config: Partial<OstiaConfig>): Partial<OstiaConfig>;
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
- interface Thresholds {
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 function compareDocuments(base: ProfileDocument, cand: ProfileDocument, thresholds?: Thresholds): Comparison[];
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
- export type WarningCode = "slow-first-run" | "outliers-detected" | "fast-command" | "nonzero-exit" | "instrumented-timing" | "artifact-missing" | "empty-profile" | "below-timer-resolution" | "cache-fallback-rerun" | "low-sample-count" | "thin-comparison" | "noisy-machine" | "skipped" | "jit-cold";
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
- type FormatName = "table" | "json" | "markdown" | "jsonl" | "minimal" | "collapsed" | "mermaid" | "speedscope" | "cpuprofile";
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 Renderer<O = unknown> {
657
- name: FormatName;
658
- render(doc: ProfileDocument, options: O): Promise<RenderResult>;
872
+ interface MinimalWarning {
873
+ code: string;
874
+ data?: Record<string, unknown>;
659
875
  }
660
876
 
661
- export interface MinimalLine {
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
- warnings: {
689
- code: string;
690
- data?: Record<string, unknown>;
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
- medianPct: number;
696
- meanPct: number;
697
- verdict: "improved" | "regressed" | "unchanged";
698
- pass: boolean;
699
- /** 95% bootstrap CI on the difference of medians and the Mann-Whitney
700
- * p-value behind the verdict. Absent on a thin (<5 samples/side)
701
- * comparison, which falls back to a point-estimate threshold. */
702
- ci95?: [number, number];
703
- pValue?: number;
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{c,b,J,V,r,G,C,z}from"./chunk-h6579788.js";import{n,s,t,T,q,K}from"./chunk-9dqf5cxf.js";export{c as bench,b as compareDocuments,n as createDocument,G as defineConfig,q as group,T as keep,t as loadDocument,z as profile,J as range,r as renderers,s as saveDocument,V as sweep,K as task,C as time};
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
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "ostia",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Fast profiling and benchmarking for Bun.",
5
5
  "type": "module",
6
+ "engines": {
7
+ "bun": ">=1.4.0"
8
+ },
6
9
  "exports": {
7
10
  ".": {
8
11
  "types": "./index.d.ts",
package/runner.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- import{n,O,i,a,o,s,d,f,g,U,B,A,h,I,j,_,L,H}from"./chunk-9dqf5cxf.js";import{heapStats as X}from"bun:jsc";var Y=100;function z(){try{return X().heapSize}catch{return process.memoryUsage().heapUsed}}async function Z(l,m=Y){let w=Bun.nanoseconds();Bun.gc(!0);let t=z();for(let u=0;u<m;u++){let p=l();if(p instanceof Promise)await p}Bun.gc(!0);let k=z(),c=Bun.nanoseconds()-w;return{memory:{origin:"heapStats",bytesPerOp:Math.max(0,(k-t)/m)},diagnosticWallNs:c}}var ee=200,te=20;async function q(l,m=ee){let w=m*1e6,t=async()=>{let p=Bun.nanoseconds();while(Bun.nanoseconds()-p<w){let b=l();if(b instanceof Promise)await b}},{cpu:k,jit:c,diagnosticWallNs:u}=await d(t);return{cpu:k,jit:c,diagnosticWallNs:u}}function K(l){let{llint:m,baseline:w,dfg:t,ftl:k}=l.tiers,c=m+w+t+k;if(c===0)return;let u=m/c*100,p=w/c*100,b=t/c*100,S=k/c*100;if(u+p<=te)return;return{code:"jit-cold",message:`${(u+p).toFixed(1)}% of CPU samples were in the llint/baseline tiers: the JIT never warmed this task up.`,data:{llintPct:u,baselinePct:p,dfgPct:b,ftlPct:S}}}async function ne(){let[l,m,w]=process.argv.slice(2);if(!l||!m)return process.stderr.write(`bench runner: usage: runner.ts <suiteFile> <outputPath> [optsJson]
4
- `),2;let t=w?JSON.parse(w):{};for(let r of t.preload??[])await import(r);A(),await import(l);let k=B();if(k.length===0)return process.stderr.write(`bench runner: ${l} registered no tasks (no task() calls found).
5
- `),2;let c=k.filter((r)=>r.only),u=c.length>0?c:k;if(c.length>0)process.stderr.write(`bench: ${c.length} task(s) selected by .only
6
- `);let p=H(u,t.filter);if(t.taskIds){let r=new Set(t.taskIds);p=p.filter((e)=>r.has(h(e)))}if(p.length===0)return process.stderr.write(`bench runner: --filter ${JSON.stringify(t.filter)} matched zero of ${u.length} registered task(s) in ${l}.
7
- `),2;if(t.planPath){let r=p.map((e)=>({id:h(e),isolate:I(e,t.isolate??!1)}));await Bun.write(t.planPath,JSON.stringify({tasks:r}))}let b=t.taskIds?p:p.filter((r)=>!I(r,t.isolate??!1)),M=b.some((r)=>!r.skipped)&&t.noiseCheck!==!1?f():void 0,v=M?g(M):void 0,W=new Map,x=new Map;b.forEach((r,e)=>{if(r.groupName===void 0||r.skipped)return;if(!W.has(r.groupName))W.set(r.groupName,e);x.set(r.groupName,e)});let F=[],P=[];for(let r=0;r<b.length;r++){let e=b[r],R=h(e),T=O(l,R,{label:R,baseline:e.baseline,group:e.groupName,description:e.opts?.description,groupDescription:e.groupDescription,isolated:t.markIsolated,params:e.params,skipped:e.skipped});if(F.push(T),e.skipped)continue;let Q=e.groupName!==void 0&&W.get(e.groupName)===r,V=e.groupName!==void 0&&x.get(e.groupName)===r;if(Q&&e.groupBefore)await e.groupBefore();if(e.opts?.before)await e.opts.before();let E=e.opts?.budgetMs??t.budgetMs,J=e.opts?.samples??t.samples,D=e.opts?.minSamples??t.minSamples,N={...t,...E!==void 0&&{budgetMs:E},...J!==void 0&&{samples:J},...D!==void 0&&{minSamples:D},gc:j(e,t.gc??!1)},C=await U(e.fn,N);if(P.push(i({workload:T,configFingerprint:o({budgetMs:N.budgetMs??null,samples:N.samples??null,minSamples:N.minSamples??null,gc:N.gc??!1}),trials:C.trials,timing:C.timing,warnings:v&&P.length===0?[...C.warnings,v]:C.warnings})),_(e,t.cpu??!1)){let y=await q(e.fn),G=K(y.jit);P.push(a({workload:T,phase:"cpu",configFingerprint:o({cpu:!0}),diagnosticWallNs:y.diagnosticWallNs,cpu:y.cpu,jit:y.jit,warnings:G?[G]:[],artifacts:[]}))}if(L(e,t.alloc??!1)){let y=await Z(e.fn);P.push(a({workload:T,phase:"memstats",configFingerprint:o({alloc:!0}),diagnosticWallNs:y.diagnosticWallNs,memory:y.memory,warnings:[],artifacts:[]}))}if(e.opts?.after)await e.opts.after();if(V&&e.groupAfter)await e.groupAfter()}return await s(n(F,P,M),m),0}ne().then((l)=>process.exit(l));
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};