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/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 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;
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
- /** 75th percentile, ns. Optional: absent on documents saved before this
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
- 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];
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
- type FormatName = "table" | "json" | "markdown" | "jsonl" | "minimal" | "collapsed" | "mermaid" | "speedscope" | "cpuprofile";
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 Renderer<O = unknown> {
661
- name: FormatName;
662
- render(doc: ProfileDocument, options: O): Promise<RenderResult>;
869
+ interface MinimalWarning {
870
+ code: string;
871
+ data?: Record<string, unknown>;
663
872
  }
664
873
 
665
- export interface MinimalLine {
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
- warnings: {
693
- code: string;
694
- data?: Record<string, unknown>;
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
- medianPct: number;
700
- meanPct: number;
701
- verdict: "improved" | "regressed" | "unchanged";
702
- pass: boolean;
703
- /** 95% bootstrap CI on the difference of medians and the Mann-Whitney
704
- * p-value behind the verdict. Absent on a thin (<5 samples/side)
705
- * comparison, which falls back to a point-estimate threshold. */
706
- ci95?: [number, number];
707
- pValue?: number;
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{x,m,K,X,u,Z,D,Y}from"./chunk-ym89ds9x.js";import{n,i,t,C,Q,ee}from"./chunk-pesqame2.js";export{m as bench,x as compareDocuments,n as createDocument,Z as defineConfig,Q as group,C as keep,t as loadDocument,Y as profile,K as range,u as renderers,i as saveDocument,X as sweep,ee as task,D as time};
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
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "ostia",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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,W,c,l,s,i,b,w,k,_,L,H,y,O,G,V,z,q}from"./chunk-pesqame2.js";import{heapStats as Z}from"bun:jsc";var K=100;function E(){try{return Z().heapSize}catch{return process.memoryUsage().heapUsed}}async function J(p,m=K){let d=Bun.nanoseconds();Bun.gc(!0);let t=E();for(let u=0;u<m;u++){let o=p();if(o instanceof Promise)await o}Bun.gc(!0);let f=E(),a=Bun.nanoseconds()-d;return{memory:{origin:"heapStats",bytesPerOp:Math.max(0,(f-t)/m)},diagnosticWallNs:a}}var Q=200,X=20;async function A(p,m=Q){let d=m*1e6,t=async()=>{let o=Bun.nanoseconds();while(Bun.nanoseconds()-o<d){let g=p();if(g instanceof Promise)await g}},{cpu:f,jit:a,diagnosticWallNs:u}=await b(t);return{cpu:f,jit:a,diagnosticWallNs:u}}function j(p){let{llint:m,baseline:d,dfg:t,ftl:f}=p.tiers,a=m+d+t+f;if(a===0)return;let u=m/a*100,o=d/a*100,g=t/a*100,S=f/a*100;if(u+o<=X)return;return{code:"jit-cold",message:`${(u+o).toFixed(1)}% of CPU samples were in the llint/baseline tiers: the JIT never warmed this task up.`,data:{llintPct:u,baselinePct:o,dfgPct:g,ftlPct:S}}}async function Y(){let[p,m,d]=process.argv.slice(2);if(!p||!m)return process.stderr.write(`bench runner: usage: runner.ts <suiteFile> <outputPath> [optsJson]
4
- `),2;let t=d?JSON.parse(d):{};for(let r of t.preload??[])await import(r);H(),await import(p);let f=L();if(f.length===0)return process.stderr.write(`bench runner: ${p} registered no tasks (no task() calls found).
5
- `),2;let a=f.filter((r)=>r.only),u=a.length>0?a:f;if(a.length>0)process.stderr.write(`bench: ${a.length} task(s) selected by .only
6
- `);let o=q(u,t.filter);if(t.taskIds){let r=new Set(t.taskIds);o=o.filter((e)=>r.has(y(e)))}if(o.length===0)return process.stderr.write(`bench runner: --filter ${JSON.stringify(t.filter)} matched zero of ${u.length} registered task(s) in ${p}.
7
- `),2;if(t.planPath){let r=o.map((e)=>({id:y(e),isolate:O(e,t.isolate??!1)}));await Bun.write(t.planPath,JSON.stringify({tasks:r}))}let g=t.taskIds?o:o.filter((r)=>!O(r,t.isolate??!1)),I=g.some((r)=>!r.skipped)&&t.noiseCheck!==!1?w():void 0,B=I?k(I):void 0,M=new Map,x=new Map;g.forEach((r,e)=>{if(r.groupName===void 0||r.skipped)return;if(!M.has(r.groupName))M.set(r.groupName,e);x.set(r.groupName,e)});let F=[],P=[];for(let r=0;r<g.length;r++){let e=g[r],R=y(e),T=W(p,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 D=e.groupName!==void 0&&M.get(e.groupName)===r,U=e.groupName!==void 0&&x.get(e.groupName)===r;if(D&&e.groupBefore)await e.groupBefore();if(e.opts?.before)await e.opts.before();let N={budgetMs:e.opts?.budgetMs??t.budgetMs,samples:e.opts?.samples??t.samples,minSamples:e.opts?.minSamples??t.minSamples,warmup:t.warmup,gc:G(e,t.gc??!1)},C=await _(e.fn,N);if(P.push(c({workload:T,configFingerprint:s({budgetMs:N.budgetMs??null,samples:N.samples??null,minSamples:N.minSamples??null,gc:N.gc??!1}),trials:C.trials,timing:C.timing,warnings:B&&P.length===0?[...C.warnings,B]:C.warnings})),V(e,t.cpu??!1)){let h=await A(e.fn),v=j(h.jit);P.push(l({workload:T,phase:"cpu",configFingerprint:s({cpu:!0}),diagnosticWallNs:h.diagnosticWallNs,cpu:h.cpu,jit:h.jit,warnings:v?[v]:[],artifacts:[]}))}if(z(e,t.alloc??!1)){let h=await J(e.fn);P.push(l({workload:T,phase:"memstats",configFingerprint:s({alloc:!0}),diagnosticWallNs:h.diagnosticWallNs,memory:h.memory,warnings:[],artifacts:[]}))}if(e.opts?.after)await e.opts.after();if(U&&e.groupAfter)await e.groupAfter()}return await i(n(F,P,I),m),0}Y().then((p)=>process.exit(p));
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};