benchforge 0.1.9 → 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.
Files changed (253) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +99 -260
  3. package/bin/benchforge +1 -2
  4. package/dist/AnalyzeArchive-8NCJhmhS.mjs +145 -0
  5. package/dist/AnalyzeArchive-8NCJhmhS.mjs.map +1 -0
  6. package/dist/BenchMatrix-BZVrBB_h.mjs +1050 -0
  7. package/dist/BenchMatrix-BZVrBB_h.mjs.map +1 -0
  8. package/dist/BenchRunner-DglX1NOn.d.mts +302 -0
  9. package/dist/CoverageSampler-D5T9DRqe.mjs +27 -0
  10. package/dist/CoverageSampler-D5T9DRqe.mjs.map +1 -0
  11. package/dist/Formatters-BWj3d4sv.mjs +95 -0
  12. package/dist/Formatters-BWj3d4sv.mjs.map +1 -0
  13. package/dist/{HeapSampler-B8dtKHn1.mjs → HeapSampler-Dq-hpXem.mjs} +4 -4
  14. package/dist/HeapSampler-Dq-hpXem.mjs.map +1 -0
  15. package/dist/RunBenchCLI-C17DrJz8.mjs +3075 -0
  16. package/dist/RunBenchCLI-C17DrJz8.mjs.map +1 -0
  17. package/dist/StatisticalUtils-BD92crgM.mjs +255 -0
  18. package/dist/StatisticalUtils-BD92crgM.mjs.map +1 -0
  19. package/dist/TimeSampler-Ds8n7l2B.mjs +29 -0
  20. package/dist/TimeSampler-Ds8n7l2B.mjs.map +1 -0
  21. package/dist/ViewerServer-BJhdnxlN.mjs +639 -0
  22. package/dist/ViewerServer-BJhdnxlN.mjs.map +1 -0
  23. package/dist/ViewerServer-CuMNdNBz.mjs +2 -0
  24. package/dist/bin/benchforge.mjs +4 -5
  25. package/dist/bin/benchforge.mjs.map +1 -1
  26. package/dist/index.d.mts +731 -522
  27. package/dist/index.mjs +98 -3
  28. package/dist/index.mjs.map +1 -0
  29. package/dist/runners/WorkerScript.d.mts +12 -4
  30. package/dist/runners/WorkerScript.mjs +92 -120
  31. package/dist/runners/WorkerScript.mjs.map +1 -1
  32. package/dist/viewer/assets/CIPlot-BkOvMoMa.js +1 -0
  33. package/dist/viewer/assets/HistogramKde-CmSyUFY0.js +1 -0
  34. package/dist/viewer/assets/LegendUtils-BJpbn_jr.js +55 -0
  35. package/dist/viewer/assets/SampleTimeSeries-C4VBhXr3.js +1 -0
  36. package/dist/viewer/assets/index-Br9bp_cX.js +153 -0
  37. package/dist/viewer/assets/index-NzXXe_CC.css +1 -0
  38. package/dist/viewer/index.html +19 -0
  39. package/dist/viewer/speedscope/LICENSE +21 -0
  40. package/dist/viewer/speedscope/SourceCodePro-Regular.ttf-ILST5JV6.woff2 +0 -0
  41. package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js +2 -0
  42. package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js.map +7 -0
  43. package/dist/viewer/speedscope/favicon-16x16-VSI62OPJ.png +0 -0
  44. package/dist/viewer/speedscope/favicon-32x32-3EB2YCUY.png +0 -0
  45. package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js +2 -0
  46. package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js.map +7 -0
  47. package/dist/viewer/speedscope/favicon-FOKUP5Y5.ico +0 -0
  48. package/dist/viewer/speedscope/favicon-M34RF7BI.js +2 -0
  49. package/dist/viewer/speedscope/favicon-M34RF7BI.js.map +7 -0
  50. package/dist/viewer/speedscope/file-format-schema.json +274 -0
  51. package/dist/viewer/speedscope/index.html +19 -0
  52. package/dist/viewer/speedscope/jfrview_bg-BLJXNNQB.wasm +0 -0
  53. package/dist/viewer/speedscope/perf-vertx-stacks-01-collapsed-all-ZNUIGAJL.txt +199 -0
  54. package/dist/viewer/speedscope/release.txt +3 -0
  55. package/dist/viewer/speedscope/source-code-pro.LICENSE.md +93 -0
  56. package/dist/viewer/speedscope/speedscope-GHPHNKXC.css +2 -0
  57. package/dist/viewer/speedscope/speedscope-GHPHNKXC.css.map +7 -0
  58. package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js +212 -0
  59. package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js.map +7 -0
  60. package/package.json +52 -26
  61. package/src/bin/benchforge.ts +2 -2
  62. package/src/cli/AnalyzeArchive.ts +232 -0
  63. package/src/cli/BrowserBench.ts +322 -0
  64. package/src/cli/CliArgs.ts +164 -48
  65. package/src/cli/CliExport.ts +179 -0
  66. package/src/cli/CliOptions.ts +147 -0
  67. package/src/cli/CliReport.ts +197 -0
  68. package/src/cli/FilterBenchmarks.ts +18 -30
  69. package/src/cli/RunBenchCLI.ts +138 -844
  70. package/src/cli/SuiteRunner.ts +160 -0
  71. package/src/cli/ViewerServer.ts +282 -0
  72. package/src/export/AllocExport.ts +121 -0
  73. package/src/export/ArchiveExport.ts +146 -0
  74. package/src/export/ArchiveFormat.ts +50 -0
  75. package/src/export/CoverageExport.ts +148 -0
  76. package/src/export/EditorUri.ts +10 -0
  77. package/src/export/PerfettoExport.ts +91 -126
  78. package/src/export/SpeedscopeTypes.ts +98 -0
  79. package/src/export/TimeExport.ts +115 -0
  80. package/src/index.ts +87 -62
  81. package/src/matrix/BenchMatrix.ts +230 -0
  82. package/src/matrix/CaseLoader.ts +8 -6
  83. package/src/matrix/MatrixDirRunner.ts +153 -0
  84. package/src/matrix/MatrixFilter.ts +55 -53
  85. package/src/matrix/MatrixInlineRunner.ts +50 -0
  86. package/src/matrix/MatrixReport.ts +94 -254
  87. package/src/matrix/VariantLoader.ts +9 -9
  88. package/src/profiling/browser/BenchLoop.ts +51 -0
  89. package/src/profiling/browser/BrowserCDP.ts +133 -0
  90. package/src/profiling/browser/BrowserGcStats.ts +33 -0
  91. package/src/profiling/browser/BrowserProfiler.ts +160 -0
  92. package/src/profiling/browser/CdpClient.ts +82 -0
  93. package/src/profiling/browser/CdpPage.ts +138 -0
  94. package/src/profiling/browser/ChromeLauncher.ts +158 -0
  95. package/src/profiling/browser/ChromeTraceEvent.ts +28 -0
  96. package/src/profiling/browser/PageLoadMode.ts +61 -0
  97. package/src/profiling/node/CoverageSampler.ts +27 -0
  98. package/src/profiling/node/CoverageTypes.ts +23 -0
  99. package/src/profiling/node/HeapSampleReport.ts +261 -0
  100. package/src/{heap-sample → profiling/node}/HeapSampler.ts +55 -13
  101. package/src/profiling/node/ResolvedProfile.ts +98 -0
  102. package/src/profiling/node/TimeSampler.ts +57 -0
  103. package/src/report/BenchmarkReport.ts +146 -0
  104. package/src/report/Colors.ts +9 -0
  105. package/src/report/Formatters.ts +110 -0
  106. package/src/report/GcSections.ts +151 -0
  107. package/src/{GitUtils.ts → report/GitUtils.ts} +18 -19
  108. package/src/report/HtmlReport.ts +223 -0
  109. package/src/report/ParseStats.ts +73 -0
  110. package/src/report/StandardSections.ts +147 -0
  111. package/src/report/ViewerSections.ts +286 -0
  112. package/src/report/text/TableReport.ts +253 -0
  113. package/src/report/text/TextReport.ts +123 -0
  114. package/src/runners/AdaptiveWrapper.ts +167 -287
  115. package/src/runners/BenchRunner.ts +27 -22
  116. package/src/{Benchmark.ts → runners/BenchmarkSpec.ts} +5 -6
  117. package/src/runners/CreateRunner.ts +5 -7
  118. package/src/runners/GcStats.ts +58 -61
  119. package/src/{MeasuredResults.ts → runners/MeasuredResults.ts} +43 -37
  120. package/src/runners/MergeBatches.ts +123 -0
  121. package/src/{NodeGC.ts → runners/NodeGC.ts} +2 -3
  122. package/src/runners/RunnerOrchestrator.ts +180 -296
  123. package/src/runners/RunnerUtils.ts +75 -1
  124. package/src/runners/SampleStats.ts +100 -0
  125. package/src/runners/TimingRunner.ts +244 -0
  126. package/src/runners/TimingUtils.ts +3 -2
  127. package/src/runners/WorkerScript.ts +162 -178
  128. package/src/stats/BootstrapDifference.ts +282 -0
  129. package/src/{PermutationTest.ts → stats/PermutationTest.ts} +31 -40
  130. package/src/stats/StatisticalUtils.ts +445 -0
  131. package/src/{tests → test}/AdaptiveConvergence.test.ts +10 -10
  132. package/src/test/AdaptiveRunner.test.ts +39 -41
  133. package/src/{tests → test}/AdaptiveSampling.test.ts +9 -9
  134. package/src/test/AdaptiveStatistics.integration.ts +9 -41
  135. package/src/{tests → test}/BenchMatrix.test.ts +31 -28
  136. package/src/test/BenchmarkReport.test.ts +63 -13
  137. package/src/test/BrowserBench.e2e.test.ts +186 -17
  138. package/src/test/BrowserBench.test.ts +10 -5
  139. package/src/test/BuildTimeSection.test.ts +130 -0
  140. package/src/test/CapSamples.test.ts +82 -0
  141. package/src/test/CoverageExport.test.ts +115 -0
  142. package/src/test/CoverageSampler.test.ts +33 -0
  143. package/src/test/HeapAttribution.test.ts +51 -0
  144. package/src/{tests → test}/MatrixFilter.test.ts +16 -16
  145. package/src/{tests → test}/MatrixReport.test.ts +1 -1
  146. package/src/test/PermutationTest.test.ts +1 -1
  147. package/src/{tests → test}/RealDataValidation.test.ts +6 -6
  148. package/src/test/RunBenchCLI.test.ts +57 -56
  149. package/src/test/RunnerOrchestrator.test.ts +12 -12
  150. package/src/test/StatisticalUtils.test.ts +48 -12
  151. package/src/{table-util/test → test}/TableReport.test.ts +2 -2
  152. package/src/test/TestUtils.ts +35 -30
  153. package/src/test/TimeExport.test.ts +139 -0
  154. package/src/test/TimeSampler.test.ts +37 -0
  155. package/src/test/ViewerLive.e2e.test.ts +159 -0
  156. package/src/test/ViewerStatic.static.e2e.test.ts +137 -0
  157. package/src/{tests → test}/fixtures/baseline/impl.ts +1 -1
  158. package/src/{tests → test}/fixtures/bevy30-samples.ts +3 -1
  159. package/src/test/fixtures/cases/asyncCases.ts +9 -0
  160. package/src/{tests → test}/fixtures/cases/cases.ts +5 -2
  161. package/src/test/fixtures/cases/variants/product.ts +2 -0
  162. package/src/test/fixtures/cases/variants/sum.ts +2 -0
  163. package/src/test/fixtures/discover/fast.ts +1 -0
  164. package/src/{tests → test}/fixtures/discover/slow.ts +1 -1
  165. package/src/test/fixtures/invalid/bad.ts +1 -0
  166. package/src/test/fixtures/loader/fast.ts +1 -0
  167. package/src/{tests → test}/fixtures/loader/slow.ts +1 -1
  168. package/src/test/fixtures/loader/stateful.ts +2 -0
  169. package/src/test/fixtures/stateful/stateful.ts +2 -0
  170. package/src/test/fixtures/variants/extra.ts +1 -0
  171. package/src/test/fixtures/variants/impl.ts +1 -0
  172. package/src/test/fixtures/worker/fast.ts +1 -0
  173. package/src/{tests → test}/fixtures/worker/slow.ts +1 -1
  174. package/src/viewer/DateFormat.ts +30 -0
  175. package/src/viewer/Helpers.ts +23 -0
  176. package/src/viewer/LineData.ts +120 -0
  177. package/src/viewer/Providers.ts +191 -0
  178. package/src/viewer/ReportData.ts +123 -0
  179. package/src/viewer/State.ts +49 -0
  180. package/src/viewer/Theme.ts +15 -0
  181. package/src/viewer/components/App.tsx +73 -0
  182. package/src/viewer/components/DropZone.tsx +71 -0
  183. package/src/viewer/components/LazyPlot.ts +33 -0
  184. package/src/viewer/components/SamplesPanel.tsx +214 -0
  185. package/src/viewer/components/Shell.tsx +26 -0
  186. package/src/viewer/components/SourcePanel.tsx +216 -0
  187. package/src/viewer/components/SummaryPanel.tsx +332 -0
  188. package/src/viewer/components/TabBar.tsx +131 -0
  189. package/src/viewer/components/TabContent.tsx +46 -0
  190. package/src/viewer/components/ThemeToggle.tsx +50 -0
  191. package/src/viewer/index.html +20 -0
  192. package/src/viewer/main.tsx +4 -0
  193. package/src/viewer/plots/CIPlot.ts +313 -0
  194. package/src/{html/browser → viewer/plots}/HistogramKde.ts +42 -47
  195. package/src/viewer/plots/LegendUtils.ts +134 -0
  196. package/src/viewer/plots/PlotTypes.ts +85 -0
  197. package/src/viewer/plots/RenderPlots.ts +230 -0
  198. package/src/viewer/plots/SampleTimeSeries.ts +306 -0
  199. package/src/viewer/plots/SvgHelpers.ts +136 -0
  200. package/src/viewer/plots/TimeSeriesMarks.ts +319 -0
  201. package/src/viewer/report.css +427 -0
  202. package/src/viewer/shell.css +357 -0
  203. package/src/viewer/tsconfig.json +11 -0
  204. package/dist/BenchRunner-CSKN9zPy.d.mts +0 -225
  205. package/dist/BrowserHeapSampler-DCeL42RE.mjs +0 -202
  206. package/dist/BrowserHeapSampler-DCeL42RE.mjs.map +0 -1
  207. package/dist/GcStats-ByEovUi1.mjs +0 -77
  208. package/dist/GcStats-ByEovUi1.mjs.map +0 -1
  209. package/dist/HeapSampler-B8dtKHn1.mjs.map +0 -1
  210. package/dist/TimingUtils-ClclVQ7E.mjs +0 -597
  211. package/dist/TimingUtils-ClclVQ7E.mjs.map +0 -1
  212. package/dist/browser/index.js +0 -914
  213. package/dist/src-Cf_LXwlp.mjs +0 -2873
  214. package/dist/src-Cf_LXwlp.mjs.map +0 -1
  215. package/src/BenchMatrix.ts +0 -380
  216. package/src/BenchmarkReport.ts +0 -156
  217. package/src/HtmlDataPrep.ts +0 -148
  218. package/src/StandardSections.ts +0 -261
  219. package/src/StatisticalUtils.ts +0 -176
  220. package/src/TypeUtil.ts +0 -8
  221. package/src/browser/BrowserGcStats.ts +0 -44
  222. package/src/browser/BrowserHeapSampler.ts +0 -271
  223. package/src/export/JsonExport.ts +0 -103
  224. package/src/export/JsonFormat.ts +0 -91
  225. package/src/heap-sample/HeapSampleReport.ts +0 -196
  226. package/src/html/HtmlReport.ts +0 -131
  227. package/src/html/HtmlTemplate.ts +0 -284
  228. package/src/html/Types.ts +0 -88
  229. package/src/html/browser/CIPlot.ts +0 -287
  230. package/src/html/browser/LegendUtils.ts +0 -163
  231. package/src/html/browser/RenderPlots.ts +0 -263
  232. package/src/html/browser/SampleTimeSeries.ts +0 -389
  233. package/src/html/browser/Types.ts +0 -96
  234. package/src/html/browser/index.ts +0 -1
  235. package/src/html/index.ts +0 -17
  236. package/src/runners/BasicRunner.ts +0 -364
  237. package/src/table-util/ConvergenceFormatters.ts +0 -19
  238. package/src/table-util/Formatters.ts +0 -152
  239. package/src/table-util/README.md +0 -70
  240. package/src/table-util/TableReport.ts +0 -293
  241. package/src/tests/fixtures/cases/asyncCases.ts +0 -7
  242. package/src/tests/fixtures/cases/variants/product.ts +0 -2
  243. package/src/tests/fixtures/cases/variants/sum.ts +0 -2
  244. package/src/tests/fixtures/discover/fast.ts +0 -1
  245. package/src/tests/fixtures/invalid/bad.ts +0 -1
  246. package/src/tests/fixtures/loader/fast.ts +0 -1
  247. package/src/tests/fixtures/loader/stateful.ts +0 -2
  248. package/src/tests/fixtures/stateful/stateful.ts +0 -2
  249. package/src/tests/fixtures/variants/extra.ts +0 -1
  250. package/src/tests/fixtures/variants/impl.ts +0 -1
  251. package/src/tests/fixtures/worker/fast.ts +0 -1
  252. package/src/{table-util/test → test}/TableValueExtractor.test.ts +0 -0
  253. package/src/{table-util/test → test}/TableValueExtractor.ts +9 -9
@@ -0,0 +1,153 @@
1
+ import type { RunnerOptions } from "../runners/BenchRunner.ts";
2
+ import type { MeasuredResults } from "../runners/MeasuredResults.ts";
3
+ import { runBatched } from "../runners/MergeBatches.ts";
4
+ import { runMatrixVariant } from "../runners/RunnerOrchestrator.ts";
5
+ import type {
6
+ BenchMatrix,
7
+ CaseResult,
8
+ RunMatrixOptions,
9
+ VariantResult,
10
+ } from "./BenchMatrix.ts";
11
+ import {
12
+ buildRunnerOptions,
13
+ computeDeltaPercent,
14
+ resolveCases,
15
+ } from "./BenchMatrix.ts";
16
+ import type { CasesModule } from "./CaseLoader.ts";
17
+ import { loadCaseData } from "./CaseLoader.ts";
18
+ import { discoverVariants } from "./VariantLoader.ts";
19
+
20
+ type VariantArgs = Parameters<typeof runMatrixVariant>[0];
21
+
22
+ /** Shared state for directory-based matrix execution */
23
+ interface DirMatrixContext<T> {
24
+ matrix: BenchMatrix<T>;
25
+ casesModule?: CasesModule<T>;
26
+ baselineIds: string[];
27
+ caseIds: string[];
28
+ runnerOpts: RunnerOptions;
29
+ batches: number;
30
+ warmupBatch: boolean;
31
+ useWorker: boolean;
32
+ }
33
+
34
+ /** Run matrix using variant files from a directory, each in a worker process */
35
+ export async function runMatrixWithDir<T>(
36
+ matrix: BenchMatrix<T>,
37
+ options: RunMatrixOptions,
38
+ ): Promise<{ name: string; variants: VariantResult[] }> {
39
+ const allVariantIds = await discoverVariants(matrix.variantDir!);
40
+ if (allVariantIds.length === 0) {
41
+ throw new Error(`No variants found in ${matrix.variantDir}`);
42
+ }
43
+ const variantIds = options.filteredVariants ?? allVariantIds;
44
+
45
+ const ctx = await createDirContext(matrix, options);
46
+ const variants = await runDirVariants(variantIds, ctx);
47
+ return { name: matrix.name, variants };
48
+ }
49
+
50
+ /** Create context for directory-based matrix execution */
51
+ async function createDirContext<T>(
52
+ matrix: BenchMatrix<T>,
53
+ options: RunMatrixOptions,
54
+ ): Promise<DirMatrixContext<T>> {
55
+ const baselineIds = matrix.baselineDir
56
+ ? await discoverVariants(matrix.baselineDir)
57
+ : [];
58
+ const { casesModule, caseIds } = await resolveCases(matrix, options);
59
+ const runnerOpts = buildRunnerOptions(options);
60
+ const { batches = 1, warmupBatch = false, useWorker = true } = options;
61
+ return {
62
+ matrix,
63
+ casesModule,
64
+ baselineIds,
65
+ caseIds,
66
+ runnerOpts,
67
+ batches,
68
+ warmupBatch,
69
+ useWorker,
70
+ };
71
+ }
72
+
73
+ /** Run all variants sequentially, collecting per-case results */
74
+ async function runDirVariants<T>(
75
+ variantIds: string[],
76
+ ctx: DirMatrixContext<T>,
77
+ ): Promise<VariantResult[]> {
78
+ const variants: VariantResult[] = [];
79
+ for (const id of variantIds) {
80
+ const cases = await runDirVariantCases(id, ctx);
81
+ variants.push({ id, cases });
82
+ }
83
+ return variants;
84
+ }
85
+
86
+ /** Run all cases for a single variant */
87
+ async function runDirVariantCases<T>(
88
+ variantId: string,
89
+ ctx: DirMatrixContext<T>,
90
+ ): Promise<CaseResult[]> {
91
+ const { matrix, casesModule, caseIds, runnerOpts, batches } = ctx;
92
+ const cases: CaseResult[] = [];
93
+
94
+ for (const caseId of caseIds) {
95
+ const caseData = matrix.cases && !matrix.casesModule ? caseId : undefined;
96
+ const variantArgs: VariantArgs = {
97
+ variantDir: matrix.variantDir!,
98
+ variantId,
99
+ caseId,
100
+ caseData,
101
+ casesModule: matrix.casesModule,
102
+ runner: "timing" as const,
103
+ options: runnerOpts,
104
+ useWorker: ctx.useWorker,
105
+ };
106
+ const baselineArgs =
107
+ matrix.baselineDir && ctx.baselineIds.includes(variantId)
108
+ ? { ...variantArgs, variantDir: matrix.baselineDir! }
109
+ : undefined;
110
+
111
+ const { metadata } = await loadCaseData(casesModule, caseId);
112
+ const { measured, baseline } =
113
+ batches > 1
114
+ ? await runCaseBatched(variantArgs, baselineArgs, ctx)
115
+ : await runCaseSingle(variantArgs, baselineArgs);
116
+ const deltaPercent = baseline
117
+ ? computeDeltaPercent(baseline, measured)
118
+ : undefined;
119
+ cases.push({ caseId, measured, metadata, baseline, deltaPercent });
120
+ }
121
+ return cases;
122
+ }
123
+
124
+ /** Run a batched measurement for a case, alternating current/baseline order. */
125
+ async function runCaseBatched<T>(
126
+ variantArgs: VariantArgs,
127
+ baselineArgs: VariantArgs | undefined,
128
+ ctx: DirMatrixContext<T>,
129
+ ): Promise<{ measured: MeasuredResults; baseline?: MeasuredResults }> {
130
+ const runCurrent = async () => (await runMatrixVariant(variantArgs))[0];
131
+ const runBase = baselineArgs
132
+ ? async () => (await runMatrixVariant(baselineArgs))[0]
133
+ : undefined;
134
+ const { results, baseline } = await runBatched(
135
+ [runCurrent],
136
+ runBase,
137
+ ctx.batches,
138
+ ctx.warmupBatch,
139
+ );
140
+ return { measured: results[0], baseline };
141
+ }
142
+
143
+ /** Run a single unbatched measurement for a case. */
144
+ async function runCaseSingle(
145
+ variantArgs: VariantArgs,
146
+ baselineArgs: VariantArgs | undefined,
147
+ ): Promise<{ measured: MeasuredResults; baseline?: MeasuredResults }> {
148
+ const [measured] = await runMatrixVariant(variantArgs);
149
+ const baseline = baselineArgs
150
+ ? (await runMatrixVariant(baselineArgs))[0]
151
+ : undefined;
152
+ return { measured, baseline };
153
+ }
@@ -1,4 +1,4 @@
1
- import type { BenchMatrix } from "../BenchMatrix.ts";
1
+ import type { BenchMatrix } from "./BenchMatrix.ts";
2
2
  import { loadCasesModule } from "./CaseLoader.ts";
3
3
  import { discoverVariants } from "./VariantLoader.ts";
4
4
 
@@ -8,24 +8,21 @@ export interface MatrixFilter {
8
8
  variant?: string;
9
9
  }
10
10
 
11
+ /** Filtered matrix with explicit case and variant lists */
12
+ export interface FilteredMatrix<T = unknown> extends BenchMatrix<T> {
13
+ filteredCases?: string[];
14
+ filteredVariants?: string[];
15
+ }
16
+
11
17
  /** Parse filter string: "case/variant", "case/", "/variant", or "case" */
12
18
  export function parseMatrixFilter(filter: string): MatrixFilter {
13
19
  if (filter.includes("/")) {
14
- const [casePart, variantPart] = filter.split("/", 2);
15
- return {
16
- case: casePart || undefined,
17
- variant: variantPart || undefined,
18
- };
20
+ const [casePart, varPart] = filter.split("/", 2);
21
+ return { case: casePart || undefined, variant: varPart || undefined };
19
22
  }
20
23
  return { case: filter };
21
24
  }
22
25
 
23
- /** Filtered matrix with explicit case and variant lists */
24
- export interface FilteredMatrix<T = unknown> extends BenchMatrix<T> {
25
- filteredCases?: string[];
26
- filteredVariants?: string[];
27
- }
28
-
29
26
  /** Apply filter to a matrix, merging with existing filters via intersection */
30
27
  export async function filterMatrix<T>(
31
28
  matrix: FilteredMatrix<T>,
@@ -36,68 +33,73 @@ export async function filterMatrix<T>(
36
33
  const caseList = await getFilteredCases(matrix, filter.case);
37
34
  const variantList = await getFilteredVariants(matrix, filter.variant);
38
35
 
39
- const filteredCases =
40
- caseList && matrix.filteredCases
41
- ? caseList.filter(c => matrix.filteredCases!.includes(c))
42
- : (caseList ?? matrix.filteredCases);
43
-
44
- const filteredVariants =
45
- variantList && matrix.filteredVariants
46
- ? variantList.filter(v => matrix.filteredVariants!.includes(v))
47
- : (variantList ?? matrix.filteredVariants);
36
+ const filteredCases = intersectFilters(caseList, matrix.filteredCases);
37
+ const filteredVariants = intersectFilters(
38
+ variantList,
39
+ matrix.filteredVariants,
40
+ );
48
41
 
49
42
  return { ...matrix, filteredCases, filteredVariants };
50
43
  }
51
44
 
52
- /** Get case IDs matching filter pattern */
45
+ /** Collect all case IDs from either casesModule or inline cases */
46
+ export async function resolveCaseIds<T>(
47
+ matrix: BenchMatrix<T>,
48
+ ): Promise<string[] | undefined> {
49
+ if (matrix.casesModule)
50
+ return (await loadCasesModule(matrix.casesModule)).cases;
51
+ return matrix.cases;
52
+ }
53
+
54
+ /** Collect all variant IDs from either inline variants or variantDir */
55
+ export async function resolveVariantIds<T>(
56
+ matrix: BenchMatrix<T>,
57
+ ): Promise<string[]> {
58
+ if (matrix.variants) return Object.keys(matrix.variants);
59
+ if (matrix.variantDir) return discoverVariants(matrix.variantDir);
60
+ throw new Error("BenchMatrix requires 'variants' or 'variantDir'");
61
+ }
62
+
63
+ /** Return case IDs matching a substring pattern, or all if no pattern */
53
64
  async function getFilteredCases<T>(
54
65
  matrix: BenchMatrix<T>,
55
66
  casePattern?: string,
56
67
  ): Promise<string[] | undefined> {
57
68
  if (!casePattern) return undefined;
58
-
59
- const caseIds = matrix.casesModule
60
- ? (await loadCasesModule(matrix.casesModule)).cases
61
- : matrix.cases;
69
+ const caseIds = await resolveCaseIds(matrix);
62
70
  if (!caseIds) return ["default"]; // implicit single case
63
-
64
- const filtered = caseIds.filter(id => matchPattern(id, casePattern));
65
- if (filtered.length === 0) {
66
- throw new Error(`No cases match filter: "${casePattern}"`);
67
- }
68
- return filtered;
71
+ return filterByPattern(caseIds, casePattern, "cases");
69
72
  }
70
73
 
71
- /** Get variant IDs matching filter pattern */
74
+ /** Return variant IDs matching a substring pattern, or all if no pattern */
72
75
  async function getFilteredVariants<T>(
73
76
  matrix: BenchMatrix<T>,
74
77
  variantPattern?: string,
75
78
  ): Promise<string[] | undefined> {
76
79
  if (!variantPattern) return undefined;
80
+ const allIds = await resolveVariantIds(matrix);
81
+ return filterByPattern(allIds, variantPattern, "variants");
82
+ }
77
83
 
78
- if (matrix.variants) {
79
- const ids = Object.keys(matrix.variants).filter(id =>
80
- matchPattern(id, variantPattern),
81
- );
82
- if (ids.length === 0) {
83
- throw new Error(`No variants match filter: "${variantPattern}"`);
84
- }
85
- return ids;
86
- }
87
-
88
- if (matrix.variantDir) {
89
- const allIds = await discoverVariants(matrix.variantDir);
90
- const filtered = allIds.filter(id => matchPattern(id, variantPattern));
91
- if (filtered.length === 0) {
92
- throw new Error(`No variants match filter: "${variantPattern}"`);
93
- }
94
- return filtered;
95
- }
84
+ /** Intersect two optional filter lists: both present ==> intersection, otherwise the one that exists */
85
+ function intersectFilters(a?: string[], b?: string[]): string[] | undefined {
86
+ if (a && b) return a.filter(v => b.includes(v));
87
+ return a ?? b;
88
+ }
96
89
 
97
- throw new Error("BenchMatrix requires 'variants' or 'variantDir'");
90
+ /** Filter IDs by substring pattern, throwing if no matches */
91
+ function filterByPattern(
92
+ ids: string[],
93
+ pattern: string,
94
+ label: string,
95
+ ): string[] {
96
+ const filtered = ids.filter(id => matchPattern(id, pattern));
97
+ if (filtered.length === 0)
98
+ throw new Error(`No ${label} match filter: "${pattern}"`);
99
+ return filtered;
98
100
  }
99
101
 
100
- /** Match id against pattern (case-insensitive substring) */
102
+ /** Case-insensitive substring match */
101
103
  function matchPattern(id: string, pattern: string): boolean {
102
104
  return id.toLowerCase().includes(pattern.toLowerCase());
103
105
  }
@@ -0,0 +1,50 @@
1
+ import { TimingRunner } from "../runners/TimingRunner.ts";
2
+ import type {
3
+ BenchMatrix,
4
+ CaseResult,
5
+ RunMatrixOptions,
6
+ VariantResult,
7
+ } from "./BenchMatrix.ts";
8
+ import {
9
+ buildRunnerOptions,
10
+ prepareBenchFn,
11
+ resolveCases,
12
+ } from "./BenchMatrix.ts";
13
+ import { loadCaseData } from "./CaseLoader.ts";
14
+
15
+ /** Run matrix with in-memory variant functions (no worker isolation) */
16
+ export async function runMatrixInline<T>(
17
+ matrix: BenchMatrix<T>,
18
+ options: RunMatrixOptions,
19
+ ): Promise<{ name: string; variants: VariantResult[] }> {
20
+ if (matrix.baselineDir)
21
+ throw new Error(
22
+ "BenchMatrix with inline 'variants' cannot use 'baselineDir'. Use 'variantDir' instead.",
23
+ );
24
+
25
+ const { casesModule, caseIds } = await resolveCases(matrix, options);
26
+ const runner = new TimingRunner();
27
+ const runnerOpts = buildRunnerOptions(options);
28
+
29
+ const allEntries = Object.entries(matrix.variants!);
30
+ const { filteredVariants } = options;
31
+ const variantEntries = filteredVariants
32
+ ? allEntries.filter(([id]) => filteredVariants.includes(id))
33
+ : allEntries;
34
+
35
+ const variants: VariantResult[] = [];
36
+ for (const [variantId, variant] of variantEntries) {
37
+ const cases: CaseResult[] = [];
38
+ for (const caseId of caseIds) {
39
+ const loaded = await loadCaseData(casesModule, caseId);
40
+ const data = casesModule || matrix.cases ? loaded.data : (undefined as T);
41
+ const fn = await prepareBenchFn(variant, data);
42
+ const spec = { name: variantId, fn };
43
+ const [measured] = await runner.runBench(spec, runnerOpts);
44
+ cases.push({ caseId, measured, metadata: loaded.metadata });
45
+ }
46
+ variants.push({ id: variantId, cases });
47
+ }
48
+
49
+ return { name: matrix.name, variants };
50
+ }