benchforge 0.1.11 → 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 -294
  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-BzyUfiyB.d.mts → BenchRunner-DglX1NOn.d.mts} +119 -66
  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 +711 -558
  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 +77 -105
  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 -27
  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 -51
  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 +132 -866
  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 +64 -99
  78. package/src/export/SpeedscopeTypes.ts +98 -0
  79. package/src/export/TimeExport.ts +115 -0
  80. package/src/index.ts +86 -67
  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 +49 -47
  85. package/src/matrix/MatrixInlineRunner.ts +50 -0
  86. package/src/matrix/MatrixReport.ts +90 -250
  87. package/src/matrix/VariantLoader.ts +5 -5
  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 +1 -2
  101. package/src/{heap-sample → profiling/node}/ResolvedProfile.ts +18 -9
  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 +116 -236
  115. package/src/runners/BenchRunner.ts +20 -15
  116. package/src/{Benchmark.ts → runners/BenchmarkSpec.ts} +5 -6
  117. package/src/runners/CreateRunner.ts +5 -7
  118. package/src/runners/GcStats.ts +47 -50
  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 +127 -243
  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 +135 -151
  128. package/src/stats/BootstrapDifference.ts +282 -0
  129. package/src/{PermutationTest.ts → stats/PermutationTest.ts} +8 -17
  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 +2 -2
  135. package/src/{tests → test}/BenchMatrix.test.ts +19 -16
  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 +14 -14
  144. package/src/{tests → test}/MatrixFilter.test.ts +1 -1
  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 +39 -38
  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 +12 -7
  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 +33 -38
  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/BrowserHeapSampler-B6asLKWQ.mjs +0 -202
  205. package/dist/BrowserHeapSampler-B6asLKWQ.mjs.map +0 -1
  206. package/dist/GcStats-wX7Xyblu.mjs +0 -77
  207. package/dist/GcStats-wX7Xyblu.mjs.map +0 -1
  208. package/dist/HeapSampler-B8dtKHn1.mjs.map +0 -1
  209. package/dist/TimingUtils-DwOwkc8G.mjs +0 -597
  210. package/dist/TimingUtils-DwOwkc8G.mjs.map +0 -1
  211. package/dist/browser/index.js +0 -914
  212. package/dist/src-B-DDaCa9.mjs +0 -3108
  213. package/dist/src-B-DDaCa9.mjs.map +0 -1
  214. package/src/BenchMatrix.ts +0 -380
  215. package/src/BenchmarkReport.ts +0 -161
  216. package/src/HtmlDataPrep.ts +0 -148
  217. package/src/StandardSections.ts +0 -261
  218. package/src/StatisticalUtils.ts +0 -175
  219. package/src/TypeUtil.ts +0 -8
  220. package/src/browser/BrowserGcStats.ts +0 -44
  221. package/src/browser/BrowserHeapSampler.ts +0 -271
  222. package/src/export/JsonExport.ts +0 -103
  223. package/src/export/JsonFormat.ts +0 -91
  224. package/src/export/SpeedscopeExport.ts +0 -202
  225. package/src/heap-sample/HeapSampleReport.ts +0 -269
  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 -157
  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 +0 -0
@@ -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,57 +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
 
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;
52
+
46
53
  return { type, pauseMs, allocated, collected, promoted, survived };
47
54
  }
48
55
 
49
- /** Aggregate GC events into summary stats */
56
+ /** Aggregate a list of GC events into summary statistics. */
50
57
  export function aggregateGcStats(events: GcEvent[]): GcStats {
51
58
  let scavenges = 0;
52
59
  let markCompacts = 0;
53
60
  let gcPauseTime = 0;
54
61
  let totalCollected = 0;
55
- let hasNodeFields = false;
62
+ let hasNode = false;
56
63
  let totalAllocated = 0;
57
64
  let totalPromoted = 0;
58
65
  let totalSurvived = 0;
59
66
 
60
- for (const e of events) {
61
- if (e.type === "scavenge" || e.type === "minor-ms") scavenges++;
62
- else if (e.type === "mark-compact") markCompacts++;
63
- gcPauseTime += e.pauseMs;
64
- totalCollected += e.collected;
65
- if (e.allocated != null) {
66
- hasNodeFields = true;
67
- totalAllocated += e.allocated;
68
- totalPromoted += e.promoted ?? 0;
69
- 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;
70
77
  }
71
78
  }
72
79
 
@@ -75,27 +82,17 @@ export function aggregateGcStats(events: GcEvent[]): GcStats {
75
82
  markCompacts,
76
83
  totalCollected,
77
84
  gcPauseTime,
78
- ...(hasNodeFields && { totalAllocated, totalPromoted, totalSurvived }),
85
+ ...(hasNode && { totalAllocated, totalPromoted, totalSurvived }),
79
86
  };
80
87
  }
81
88
 
82
- /** @return GcStats with all counters zeroed */
83
- export function emptyGcStats(): GcStats {
84
- return { scavenges: 0, markCompacts: 0, totalCollected: 0, gcPauseTime: 0 };
85
- }
86
-
87
- /** Parse name=value pairs from trace-gc-nvp line */
89
+ /** Parse name=value pairs from a trace-gc-nvp line. */
88
90
  function parseNvpFields(line: string): Record<string, string> {
89
- const fields: Record<string, string> = {};
90
- // Format: "key=value, key=value, ..." or "key=value key=value"
91
- const matches = line.matchAll(/(\w+)=([^\s,]+)/g);
92
- for (const [, key, value] of matches) {
93
- fields[key] = value;
94
- }
95
- return fields;
91
+ const pairs = [...line.matchAll(/(\w+)=([^\s,]+)/g)];
92
+ return Object.fromEntries(pairs.map(([, key, value]) => [key, value]));
96
93
  }
97
94
 
98
- /** Map V8 gc type codes to our types */
95
+ /** Map V8 gc type codes to normalized event types. */
99
96
  function parseGcType(gcField: string): GcEvent["type"] {
100
97
  // V8 uses: s=scavenge, mc=mark-compact, mmc=minor-mc (young gen mark-compact)
101
98
  if (gcField === "s" || gcField === "scavenge") return "scavenge";
@@ -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
  }