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
@@ -1,15 +1,7 @@
1
- import type { BenchmarkSpec } from "../Benchmark.ts";
2
- import type { MeasuredResults } from "../MeasuredResults.ts";
1
+ import type { BenchmarkSpec } from "./BenchmarkSpec.ts";
2
+ import type { MeasuredResults } from "./MeasuredResults.ts";
3
3
 
4
- /** Execute benchmark with optional parameters */
5
- export function executeBenchmark<T>(
6
- benchmark: BenchmarkSpec<T>,
7
- params?: T,
8
- ): void {
9
- (benchmark.fn as (params?: T) => void)(params);
10
- }
11
-
12
- /** Interface for benchmark execution libraries */
4
+ /** Benchmark execution strategy that collects timing samples. */
13
5
  export interface BenchRunner {
14
6
  runBench<T = unknown>(
15
7
  benchmark: BenchmarkSpec<T>,
@@ -18,6 +10,7 @@ export interface BenchRunner {
18
10
  ): Promise<MeasuredResults[]>;
19
11
  }
20
12
 
13
+ /** Configuration for benchmark execution: timing limits, warmup, profiling, and V8 options. */
21
14
  export interface RunnerOptions {
22
15
  /** Minimum time to run each benchmark (milliseconds) */
23
16
  minTime?: number;
@@ -36,13 +29,11 @@ export interface RunnerOptions {
36
29
  /** Minimum samples required - mitata only */
37
30
  minSamples?: number;
38
31
  /** Force GC after each iteration (requires --expose-gc) */
39
- collect?: boolean;
40
- /** Enable CPU performance counters (requires root access) */
41
- cpuCounters?: boolean;
32
+ gcForce?: boolean;
42
33
  /** Trace V8 optimization tiers (requires --allow-natives-syntax) */
43
34
  traceOpt?: boolean;
44
- /** Skip post-warmup settle time (default: false) */
45
- noSettle?: boolean;
35
+ /** Post-warmup settle time in ms for V8 background compilation (0 to skip) */
36
+ pauseWarmup?: number;
46
37
  /** Iterations before first pause (then pauseInterval applies) */
47
38
  pauseFirst?: number;
48
39
  /** Iterations between pauses for V8 optimization (0 to disable) */
@@ -51,10 +42,24 @@ export interface RunnerOptions {
51
42
  pauseDuration?: number;
52
43
  /** Collect GC stats via --trace-gc-nvp (requires worker mode) */
53
44
  gcStats?: boolean;
54
- /** Heap sampling allocation attribution */
55
- heapSample?: boolean;
56
- /** Heap sampling interval in bytes */
57
- heapInterval?: number;
58
- /** Heap sampling stack depth */
59
- heapDepth?: number;
45
+ /** Allocation sampling attribution */
46
+ alloc?: boolean;
47
+ /** Allocation sampling interval in bytes */
48
+ allocInterval?: number;
49
+ /** Allocation sampling stack depth */
50
+ allocDepth?: number;
51
+ /** V8 CPU time sampling */
52
+ profile?: boolean;
53
+ /** CPU sampling interval in microseconds (default 1000) */
54
+ profileInterval?: number;
55
+ /** Collect per-function execution counts via V8 precise coverage */
56
+ callCounts?: boolean;
57
+ }
58
+
59
+ /** Invoke the benchmark function, forwarding setup params. */
60
+ export function executeBenchmark<T>(
61
+ benchmark: BenchmarkSpec<T>,
62
+ params?: T,
63
+ ): void {
64
+ (benchmark.fn as (params?: T) => void)(params);
60
65
  }
@@ -1,4 +1,4 @@
1
- /** Single benchmark function specification */
1
+ /** Benchmark function with optional module path for worker-mode serialization. */
2
2
  export interface BenchmarkSpec<T = unknown> {
3
3
  name: string;
4
4
  fn: BenchmarkFunction<T>;
@@ -10,23 +10,22 @@ export interface BenchmarkSpec<T = unknown> {
10
10
  setupExportName?: string;
11
11
  }
12
12
 
13
+ /** Benchmark function, optionally receiving setup parameters from the group. */
13
14
  export type BenchmarkFunction<T = unknown> =
14
15
  | ((params: T) => void)
15
16
  | (() => void);
16
17
 
17
- /** Group of benchmarks with shared setup */
18
+ /** Group of benchmarks with shared setup and optional baseline. */
18
19
  export interface BenchGroup<T = unknown> {
19
20
  name: string;
20
- /** Prepare parameters for all benchmarks in this group */
21
21
  setup?: () => T | Promise<T>;
22
22
  benchmarks: BenchmarkSpec<T>[];
23
- /** Baseline benchmark for comparison */
24
23
  baseline?: BenchmarkSpec<T>;
25
- /** Metadata for reporting (e.g., lines of code) */
24
+ /** Metadata for reporting (e.g. lines of code). */
26
25
  metadata?: Record<string, any>;
27
26
  }
28
27
 
29
- /** Collection of benchmark groups */
28
+ /** Named collection of benchmark groups. */
30
29
  export interface BenchSuite {
31
30
  name: string;
32
31
  groups: BenchGroup<any>[];
@@ -1,11 +1,9 @@
1
- import { BasicRunner } from "./BasicRunner.ts";
2
1
  import type { BenchRunner } from "./BenchRunner.ts";
2
+ import { TimingRunner } from "./TimingRunner.ts";
3
3
 
4
- export type KnownRunner = "basic";
4
+ export type KnownRunner = "timing";
5
5
 
6
- /** @return benchmark runner */
7
- export async function createRunner(
8
- _runnerName: KnownRunner,
9
- ): Promise<BenchRunner> {
10
- return new BasicRunner();
6
+ /** Create a benchmark runner by name. */
7
+ export async function createRunner(_name: KnownRunner): Promise<BenchRunner> {
8
+ return new TimingRunner();
11
9
  }
@@ -1,14 +1,20 @@
1
- /** GC statistics aggregated from V8 trace events.
2
- * Node (--trace-gc-nvp) provides all fields.
3
- * Browser (CDP Tracing) provides counts, collected, and pause only. */
1
+ /**
2
+ * Aggregated GC statistics from V8 trace events.
3
+ * Node (--trace-gc-nvp) provides all fields; browser (CDP) provides counts, collected, and pause only.
4
+ */
4
5
  export interface GcStats {
5
6
  scavenges: number;
6
7
  markCompacts: number;
7
- totalCollected: number; // bytes freed
8
- gcPauseTime: number; // total pause time (ms)
9
- totalAllocated?: number; // bytes allocated (Node only)
10
- totalPromoted?: number; // bytes promoted to old gen (Node only)
11
- totalSurvived?: number; // bytes survived in young gen (Node only)
8
+ /** Bytes freed by GC. */
9
+ totalCollected: number;
10
+ /** Total GC pause time in milliseconds. */
11
+ gcPauseTime: number;
12
+ /** Bytes allocated (Node only). */
13
+ totalAllocated?: number;
14
+ /** Bytes promoted to old generation (Node only). */
15
+ totalPromoted?: number;
16
+ /** Bytes survived in young generation (Node only). */
17
+ totalSurvived?: number;
12
18
  }
13
19
 
14
20
  /** Single GC event. Node provides all fields; browser provides type, pauseMs, collected. */
@@ -16,79 +22,58 @@ export interface GcEvent {
16
22
  type: "scavenge" | "mark-compact" | "minor-ms" | "unknown";
17
23
  pauseMs: number;
18
24
  collected: number;
19
- allocated?: number; // Node only
20
- promoted?: number; // Node only
21
- survived?: number; // Node only
25
+ /** Node only. */
26
+ allocated?: number;
27
+ /** Node only. */
28
+ promoted?: number;
29
+ /** Node only. */
30
+ survived?: number;
22
31
  }
23
32
 
24
- /** Parse a single --trace-gc-nvp stderr line */
33
+ /** Parse a single --trace-gc-nvp stderr line into a GcEvent. */
25
34
  export function parseGcLine(line: string): GcEvent | undefined {
26
- // V8 format: [pid:addr:gen] N ms: pause=X gc=s ... allocated=N promoted=N ...
27
35
  if (!line.includes("pause=")) return undefined;
28
36
 
29
37
  const fields = parseNvpFields(line);
30
38
  if (!fields.gc) return undefined;
31
39
 
32
- const int = (k: string) => Number.parseInt(fields[k] || "0", 10);
40
+ const intField = (name: string) => Number.parseInt(fields[name] || "0", 10);
33
41
  const type = parseGcType(fields.gc);
34
42
  const pauseMs = Number.parseFloat(fields.pause || "0");
35
- const allocated = int("allocated");
36
- const promoted = int("promoted");
37
- // V8 uses "new_space_survived" not "survived"
38
- const survived = int("new_space_survived") || int("survived");
39
- // Calculate collected from start/end object size if available
40
- const startSize = int("start_object_size");
41
- const endSize = int("end_object_size");
42
- const collected = startSize > endSize ? startSize - endSize : 0;
43
-
44
43
  if (Number.isNaN(pauseMs)) return undefined;
45
44
 
46
- return { type, pauseMs, allocated, collected, promoted, survived };
47
- }
48
-
49
- /** Parse name=value pairs from trace-gc-nvp line */
50
- function parseNvpFields(line: string): Record<string, string> {
51
- const fields: Record<string, string> = {};
52
- // Format: "key=value, key=value, ..." or "key=value key=value"
53
- const matches = line.matchAll(/(\w+)=([^\s,]+)/g);
54
- for (const [, key, value] of matches) {
55
- fields[key] = value;
56
- }
57
- return fields;
58
- }
45
+ const allocated = intField("allocated");
46
+ const promoted = intField("promoted");
47
+ // V8 uses "new_space_survived" not "survived"
48
+ const survived = intField("new_space_survived") || intField("survived");
49
+ const start = intField("start_object_size");
50
+ const end = intField("end_object_size");
51
+ const collected = start > end ? start - end : 0;
59
52
 
60
- /** Map V8 gc type codes to our types */
61
- function parseGcType(gcField: string): GcEvent["type"] {
62
- // V8 uses: s=scavenge, mc=mark-compact, mmc=minor-mc (young gen mark-compact)
63
- if (gcField === "s" || gcField === "scavenge") return "scavenge";
64
- if (gcField === "mc" || gcField === "ms" || gcField === "mark-compact")
65
- return "mark-compact";
66
- if (gcField === "mmc" || gcField === "minor-mc" || gcField === "minor-ms")
67
- return "minor-ms";
68
- return "unknown";
53
+ return { type, pauseMs, allocated, collected, promoted, survived };
69
54
  }
70
55
 
71
- /** Aggregate GC events into summary stats */
56
+ /** Aggregate a list of GC events into summary statistics. */
72
57
  export function aggregateGcStats(events: GcEvent[]): GcStats {
73
58
  let scavenges = 0;
74
59
  let markCompacts = 0;
75
60
  let gcPauseTime = 0;
76
61
  let totalCollected = 0;
77
- let hasNodeFields = false;
62
+ let hasNode = false;
78
63
  let totalAllocated = 0;
79
64
  let totalPromoted = 0;
80
65
  let totalSurvived = 0;
81
66
 
82
- for (const e of events) {
83
- if (e.type === "scavenge" || e.type === "minor-ms") scavenges++;
84
- else if (e.type === "mark-compact") markCompacts++;
85
- gcPauseTime += e.pauseMs;
86
- totalCollected += e.collected;
87
- if (e.allocated != null) {
88
- hasNodeFields = true;
89
- totalAllocated += e.allocated;
90
- totalPromoted += e.promoted ?? 0;
91
- totalSurvived += e.survived ?? 0;
67
+ for (const event of events) {
68
+ if (event.type === "scavenge" || event.type === "minor-ms") scavenges++;
69
+ else if (event.type === "mark-compact") markCompacts++;
70
+ gcPauseTime += event.pauseMs;
71
+ totalCollected += event.collected;
72
+ if (event.allocated != null) {
73
+ hasNode = true;
74
+ totalAllocated += event.allocated;
75
+ totalPromoted += event.promoted ?? 0;
76
+ totalSurvived += event.survived ?? 0;
92
77
  }
93
78
  }
94
79
 
@@ -97,11 +82,23 @@ export function aggregateGcStats(events: GcEvent[]): GcStats {
97
82
  markCompacts,
98
83
  totalCollected,
99
84
  gcPauseTime,
100
- ...(hasNodeFields && { totalAllocated, totalPromoted, totalSurvived }),
85
+ ...(hasNode && { totalAllocated, totalPromoted, totalSurvived }),
101
86
  };
102
87
  }
103
88
 
104
- /** @return GcStats with all counters zeroed */
105
- export function emptyGcStats(): GcStats {
106
- return { scavenges: 0, markCompacts: 0, totalCollected: 0, gcPauseTime: 0 };
89
+ /** Parse name=value pairs from a trace-gc-nvp line. */
90
+ function parseNvpFields(line: string): Record<string, string> {
91
+ const pairs = [...line.matchAll(/(\w+)=([^\s,]+)/g)];
92
+ return Object.fromEntries(pairs.map(([, key, value]) => [key, value]));
93
+ }
94
+
95
+ /** Map V8 gc type codes to normalized event types. */
96
+ function parseGcType(gcField: string): GcEvent["type"] {
97
+ // V8 uses: s=scavenge, mc=mark-compact, mmc=minor-mc (young gen mark-compact)
98
+ if (gcField === "s" || gcField === "scavenge") return "scavenge";
99
+ if (gcField === "mc" || gcField === "ms" || gcField === "mark-compact")
100
+ return "mark-compact";
101
+ if (gcField === "mmc" || gcField === "minor-mc" || gcField === "minor-ms")
102
+ return "minor-ms";
103
+ return "unknown";
107
104
  }
@@ -1,13 +1,9 @@
1
- import type { HeapProfile } from "./heap-sample/HeapSampler.ts";
2
- import type { NodeGCTime } from "./NodeGC.ts";
3
- import type { GcStats } from "./runners/GcStats.ts";
4
-
5
- /** CPU performance counter stats */
6
- export interface CpuCounts {
7
- instructions?: number;
8
- cycles?: number;
9
- branchMisses?: number;
10
- }
1
+ import type { NavTiming } from "../profiling/browser/BrowserProfiler.ts";
2
+ import type { CoverageData } from "../profiling/node/CoverageTypes.ts";
3
+ import type { HeapProfile } from "../profiling/node/HeapSampler.ts";
4
+ import type { TimeProfile } from "../profiling/node/TimeSampler.ts";
5
+ import type { GcStats } from "../runners/GcStats.ts";
6
+ import type { NodeGCTime } from "../runners/NodeGC.ts";
11
7
 
12
8
  /** Benchmark results: times in milliseconds, sizes in kilobytes */
13
9
  export interface MeasuredResults {
@@ -16,7 +12,7 @@ export interface MeasuredResults {
16
12
  /** Raw execution time samples for custom statistics */
17
13
  samples: number[];
18
14
 
19
- /** Warmup iteration timings (ms) - captured before gc/settle */
15
+ /** Warmup iteration timings (ms) - captured before gc/pause-warmup */
20
16
  warmupSamples?: number[];
21
17
 
22
18
  /** Raw allocation samples per iteration (KB) */
@@ -25,10 +21,7 @@ export interface MeasuredResults {
25
21
  /** Heap size per sample (bytes) - used for charts */
26
22
  heapSamples?: number[];
27
23
 
28
- /** Wall-clock timestamps per sample (μs since process start) - for Perfetto export */
29
- timestamps?: number[];
30
-
31
- /** Execution time in milliseconds (measurement overhead excluded by mitata) */
24
+ /** Execution time in milliseconds */
32
25
  time: {
33
26
  min: number;
34
27
  max: number;
@@ -51,36 +44,22 @@ export interface MeasuredResults {
51
44
  max: number;
52
45
  };
53
46
 
54
- /**
55
- * Time for explicit gc() call after test execution (milliseconds).
56
- * Does not include GC time during test execution.
57
- * Only reported by mitata runner.
58
- */
47
+ /** Time for explicit gc() call after test execution (ms), excludes in-run GC. */
59
48
  gcTime?: {
60
49
  avg: number;
61
50
  min: number;
62
51
  max: number;
63
52
  };
64
53
 
65
- /** CPU counter stats from @mitata/counters (requires root access) */
66
- cpu?: CpuCounts;
67
-
68
- /** L1 cache miss rate */
69
- cpuCacheMiss?: number;
70
-
71
- /** CPU stall rate (macOS only) */
72
- cpuStall?: number;
73
-
74
- /**
75
- * Stop-the-world GC time blocking main thread (milliseconds).
76
- * Measured via Node's performance hooks when nodeObserveGC is true.
77
- * Excludes parallel thread collection time and indirect slowdowns.
78
- */
54
+ /** Stop-the-world GC pause time (ms) via Node perf hooks (nodeObserveGC). */
79
55
  nodeGcTime?: NodeGCTime;
80
56
 
81
57
  /** Total time spent collecting samples (seconds) */
82
58
  totalTime?: number;
83
59
 
60
+ /** Monotonic start time (μs, hrtime-based) for Perfetto trace alignment. */
61
+ startTime?: number;
62
+
84
63
  /** Convergence information for adaptive mode */
85
64
  convergence?: {
86
65
  converged: boolean;
@@ -97,18 +76,28 @@ export interface MeasuredResults {
97
76
  /** Points where pauses occurred for V8 optimization */
98
77
  pausePoints?: PausePoint[];
99
78
 
79
+ /** Batch boundaries for block bootstrap (indices into samples where each batch starts) */
80
+ batchOffsets?: number[];
81
+
100
82
  /** GC stats from V8's --trace-gc-nvp (requires --gc-stats and worker mode) */
101
83
  gcStats?: GcStats;
102
84
 
103
85
  /** Heap sampling allocation profile (requires --heap-sample and worker mode) */
104
86
  heapProfile?: HeapProfile;
87
+
88
+ /** V8 CPU time sampling profile (requires --profile and worker mode) */
89
+ timeProfile?: TimeProfile;
90
+
91
+ /** Per-function execution counts (requires --call-counts) */
92
+ coverage?: CoverageData;
93
+
94
+ /** Navigation timings from page-load mode (one per iteration) */
95
+ navTimings?: NavTiming[];
105
96
  }
106
97
 
107
- /** A pause point during sample collection for V8 optimization */
98
+ /** Pause inserted during sample collection for V8 optimization settling. */
108
99
  export interface PausePoint {
109
- /** Sample index where pause occurred (after this iteration) */
110
100
  sampleIndex: number;
111
- /** Pause duration in milliseconds */
112
101
  durationMs: number;
113
102
  }
114
103
 
@@ -125,3 +114,20 @@ export interface OptStatusInfo {
125
114
  /** Number of samples with deopt flag set */
126
115
  deoptCount: number;
127
116
  }
117
+
118
+ /**
119
+ * V8 GetOptimizationStatus() return codes ==> human-readable tier names.
120
+ * Bit 0 (1): is_function
121
+ * Bit 4 (16): is_optimized (TurboFan)
122
+ * Bit 5 (32): is_optimized (Maglev)
123
+ * Bit 7 (128): is_baseline (Sparkplug)
124
+ * Bit 3 (8): maybe_deoptimized
125
+ */
126
+ export const optStatusNames: Record<number, string> = {
127
+ 1: "interpreted",
128
+ 129: "sparkplug", // 1 + 128
129
+ 17: "turbofan", // 1 + 16
130
+ 33: "maglev", // 1 + 32
131
+ 49: "turbofan+maglev", // 1 + 16 + 32
132
+ 32769: "optimized", // common optimized status
133
+ };
@@ -0,0 +1,123 @@
1
+ import type { GcStats } from "./GcStats.ts";
2
+ import type { MeasuredResults } from "./MeasuredResults.ts";
3
+ import { computeStats } from "./SampleStats.ts";
4
+
5
+ /** Progress update emitted after each batch run. */
6
+ export interface BatchProgress {
7
+ batch: number;
8
+ batches: number;
9
+ label: "baseline" | "current";
10
+ elapsed: number;
11
+ }
12
+
13
+ type SamplesFn = (r: MeasuredResults) => number[] | undefined;
14
+
15
+ /** Merge multiple batch results, concatenating samples and tracking batch boundaries. */
16
+ export function mergeBatchResults(results: MeasuredResults[]): MeasuredResults {
17
+ if (results.length === 0) {
18
+ throw new Error("Cannot merge empty results array");
19
+ }
20
+ if (results.length === 1) return { ...results[0], batchOffsets: [0] };
21
+
22
+ const allSamples = results.flatMap(r => r.samples);
23
+ const time = computeStats(allSamples);
24
+
25
+ const batchOffsets: number[] = [];
26
+ const offsetPauses: MeasuredResults["pausePoints"] = [];
27
+ let offset = 0;
28
+ for (const r of results) {
29
+ batchOffsets.push(offset);
30
+ for (const p of r.pausePoints ?? []) {
31
+ const sampleIndex = p.sampleIndex + offset;
32
+ offsetPauses.push({ sampleIndex, durationMs: p.durationMs });
33
+ }
34
+ offset += r.samples.length;
35
+ }
36
+
37
+ // last batch as base ==> new MeasuredResults fields get "take last"
38
+ // semantics by default instead of silently disappearing
39
+ return {
40
+ ...results[results.length - 1],
41
+ name: results[0].name,
42
+ samples: allSamples,
43
+ warmupSamples: concatOptional(results, r => r.warmupSamples),
44
+ allocationSamples: concatOptional(results, r => r.allocationSamples),
45
+ heapSamples: concatOptional(results, r => r.heapSamples),
46
+ optSamples: concatOptional(results, r => r.optSamples),
47
+ time,
48
+ startTime: results[0].startTime,
49
+ totalTime: results.reduce((sum, r) => sum + (r.totalTime || 0), 0),
50
+ pausePoints: offsetPauses.length ? offsetPauses : undefined,
51
+ batchOffsets,
52
+ gcStats: mergeGcStats(results),
53
+ };
54
+ }
55
+
56
+ /** Sum GcStats across batches, or undefined if none collected. */
57
+ export function mergeGcStats(
58
+ results: { gcStats?: GcStats }[],
59
+ ): GcStats | undefined {
60
+ const stats = results.map(r => r.gcStats).filter(Boolean) as GcStats[];
61
+ if (!stats.length) return undefined;
62
+ const sum = (fn: (s: GcStats) => number | undefined) =>
63
+ stats.reduce((acc, s) => acc + (fn(s) ?? 0), 0);
64
+ return {
65
+ scavenges: sum(s => s.scavenges),
66
+ markCompacts: sum(s => s.markCompacts),
67
+ totalCollected: sum(s => s.totalCollected),
68
+ gcPauseTime: sum(s => s.gcPauseTime),
69
+ totalAllocated: sum(s => s.totalAllocated) || undefined,
70
+ totalPromoted: sum(s => s.totalPromoted) || undefined,
71
+ totalSurvived: sum(s => s.totalSurvived) || undefined,
72
+ };
73
+ }
74
+
75
+ /** Run N benchmarks + optional baseline in batched alternation, merge results. */
76
+ export async function runBatched(
77
+ runners: (() => Promise<MeasuredResults>)[],
78
+ baseline: (() => Promise<MeasuredResults>) | undefined,
79
+ batches: number,
80
+ warmupBatch = false,
81
+ onProgress?: (p: BatchProgress) => void,
82
+ ): Promise<{ results: MeasuredResults[]; baseline?: MeasuredResults }> {
83
+ const runnerBatches: MeasuredResults[][] = runners.map(() => []);
84
+ const baselineBatches: MeasuredResults[] = [];
85
+ const start = performance.now();
86
+
87
+ const report = (batch: number, label: BatchProgress["label"]) =>
88
+ onProgress?.({ batch, batches, label, elapsed: performance.now() - start });
89
+
90
+ for (let i = 0; i < batches; i++) {
91
+ const reverse = i % 2 === 1;
92
+ // baseline runs before benchmarks on even batches, after on odd (alternation)
93
+ if (!reverse && baseline) {
94
+ baselineBatches.push(await baseline());
95
+ report(i, "baseline");
96
+ }
97
+ for (let j = 0; j < runners.length; j++) {
98
+ runnerBatches[j].push(await runners[j]());
99
+ report(i, "current");
100
+ }
101
+ if (reverse && baseline) {
102
+ baselineBatches.push(await baseline());
103
+ report(i, "baseline");
104
+ }
105
+ }
106
+
107
+ if (!warmupBatch && batches > 1) {
108
+ for (const b of runnerBatches) b.shift();
109
+ baselineBatches.shift();
110
+ }
111
+
112
+ const results = runnerBatches.map(b => mergeBatchResults(b));
113
+ const mergedBaseline = baselineBatches.length
114
+ ? mergeBatchResults(baselineBatches)
115
+ : undefined;
116
+ return { results, baseline: mergedBaseline };
117
+ }
118
+
119
+ /** Concat optional number arrays across batches. */
120
+ function concatOptional(results: MeasuredResults[], fn: SamplesFn) {
121
+ const all = results.flatMap(r => fn(r) || []);
122
+ return all.length ? all : undefined;
123
+ }
@@ -31,8 +31,7 @@ export function analyzeGCEntries(
31
31
  let collects = 0;
32
32
  const events: GcEvent[] = [];
33
33
 
34
- gcRecords.forEach(record => {
35
- const { duration, startTime } = record;
34
+ for (const { duration, startTime } of gcRecords) {
36
35
  if (startTime < start) {
37
36
  before += duration;
38
37
  } else if (startTime > end) {
@@ -42,7 +41,7 @@ export function analyzeGCEntries(
42
41
  collects++;
43
42
  events.push({ offset: startTime - start, duration });
44
43
  }
45
- });
44
+ }
46
45
  const total = inRun + before + after;
47
46
  return { inRun, before, after, total, collects, events };
48
47
  }