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,255 @@
1
+ //#region src/stats/StatisticalUtils.ts
2
+ const defaultConfidence = .95;
3
+ const bootstrapSamples = 1e4;
4
+ const maxBootstrapInput = 1e4;
5
+ /** Swap direction labels for higher-is-better metrics (positive = faster) */
6
+ function swapDirection(ci) {
7
+ const swap = {
8
+ faster: "slower",
9
+ slower: "faster",
10
+ uncertain: "uncertain",
11
+ equivalent: "equivalent"
12
+ };
13
+ return {
14
+ ...ci,
15
+ direction: swap[ci.direction]
16
+ };
17
+ }
18
+ /** Negate percent and CI for "higher is better" metrics (e.g., throughput) */
19
+ function flipCI(ci) {
20
+ return {
21
+ ...ci,
22
+ percent: -ci.percent,
23
+ ci: [-ci.ci[1], -ci.ci[0]],
24
+ histogram: ci.histogram?.map((bin) => ({
25
+ x: -bin.x,
26
+ count: bin.count
27
+ }))
28
+ };
29
+ }
30
+ /** Compute a statistic from samples by kind */
31
+ function computeStat(samples, kind) {
32
+ if (kind === "mean") return average(samples);
33
+ if (kind === "min") return minOf(samples);
34
+ if (kind === "max") return maxOf(samples);
35
+ return percentile(samples, kind.percentile);
36
+ }
37
+ /** @return true if the stat kind supports bootstrap CI (min/max don't) */
38
+ function isBootstrappable(kind) {
39
+ return kind !== "min" && kind !== "max";
40
+ }
41
+ /** @return smallest value in samples (loop to avoid spread-arg limits) */
42
+ function minOf(samples) {
43
+ let min = samples[0];
44
+ for (let i = 1; i < samples.length; i++) if (samples[i] < min) min = samples[i];
45
+ return min;
46
+ }
47
+ /** @return largest value in samples (loop to avoid spread-arg limits) */
48
+ function maxOf(samples) {
49
+ let max = samples[0];
50
+ for (let i = 1; i < samples.length; i++) if (samples[i] > max) max = samples[i];
51
+ return max;
52
+ }
53
+ /** @return relative standard deviation (coefficient of variation) */
54
+ function coefficientOfVariation(samples) {
55
+ const mean = average(samples);
56
+ if (mean === 0) return 0;
57
+ return standardDeviation(samples) / mean;
58
+ }
59
+ /** @return median absolute deviation for robust variability measure */
60
+ function medianAbsoluteDeviation(samples) {
61
+ const med = median(samples);
62
+ return median(samples.map((x) => Math.abs(x - med)));
63
+ }
64
+ /** Shared-resample bootstrap: one resample per iteration, all stats computed on it.
65
+ * Mean is computed first (non-destructive), then percentiles via in-place quickSelect. */
66
+ function multiSampleBootstrap(samples, stats, options = {}) {
67
+ const { resamples = bootstrapSamples, confidence: conf = defaultConfidence } = options;
68
+ const sub = subsample(samples, maxBootstrapInput);
69
+ const n = sub.length;
70
+ const buf = new Array(n);
71
+ const ops = buildStatOps(stats, n);
72
+ const allStats = ops.map(() => new Array(resamples));
73
+ for (let i = 0; i < resamples; i++) {
74
+ resampleInto(sub, buf);
75
+ for (let j = 0; j < ops.length; j++) allStats[j][i] = ops[j].compute(buf);
76
+ }
77
+ const capped = sub !== samples;
78
+ const results = new Array(stats.length);
79
+ for (let j = 0; j < ops.length; j++) results[ops[j].origIndex] = {
80
+ estimate: ops[j].pointEstimate(samples),
81
+ ci: computeInterval(allStats[j], conf),
82
+ samples: allStats[j],
83
+ ciLevel: "sample",
84
+ ...capped && { subsampled: samples.length }
85
+ };
86
+ return results;
87
+ }
88
+ /** Bootstrap CIs for multiple stats, dispatching block vs sample automatically.
89
+ * Returns undefined for non-bootstrappable stats (min/max). */
90
+ function bootstrapCIs(samples, batchOffsets, stats, options) {
91
+ const bsStats = stats.filter(isBootstrappable);
92
+ if (bsStats.length === 0) return stats.map(() => void 0);
93
+ const bsResults = (batchOffsets?.length ?? 0) >= 2 ? bsStats.map((s) => blockBootstrap(samples, batchOffsets, statKindToFn(s), options)) : multiSampleBootstrap(samples, bsStats, options);
94
+ const results = new Array(stats.length);
95
+ let bi = 0;
96
+ for (let i = 0; i < stats.length; i++) results[i] = isBootstrappable(stats[i]) ? bsResults[bi++] : void 0;
97
+ return results;
98
+ }
99
+ /** Convert StatKind to a stat function */
100
+ function statKindToFn(kind) {
101
+ if (kind === "mean") return average;
102
+ if (kind === "min") return minOf;
103
+ if (kind === "max") return maxOf;
104
+ const p = kind.percentile;
105
+ return (s) => percentile(s, p);
106
+ }
107
+ /** Block bootstrap CI: Tukey-trim outlier batches, then resample per-block
108
+ * statFn values as independent observations. Requires 2+ blocks. */
109
+ function blockBootstrap(samples, blocks, statFn, options = {}) {
110
+ const { resamples = bootstrapSamples, confidence: conf = defaultConfidence } = options;
111
+ const side = prepareBlocks(samples, blocks, statFn);
112
+ const stats = Array.from({ length: resamples }, () => average(createResample(side.blockVals)));
113
+ return {
114
+ estimate: statFn(side.filtered),
115
+ ci: computeInterval(stats, conf),
116
+ samples: stats,
117
+ ciLevel: "block"
118
+ };
119
+ }
120
+ /** @return mean of values */
121
+ function average(values) {
122
+ return values.reduce((a, b) => a + b, 0) / values.length;
123
+ }
124
+ /** @return median (50th percentile) of values */
125
+ function median(values) {
126
+ return percentile(values, .5);
127
+ }
128
+ /** @return standard deviation with Bessel's correction */
129
+ function standardDeviation(samples) {
130
+ if (samples.length <= 1) return 0;
131
+ const mean = average(samples);
132
+ const variance = samples.reduce((sum, x) => sum + (x - mean) ** 2, 0) / (samples.length - 1);
133
+ return Math.sqrt(variance);
134
+ }
135
+ /** @return value at percentile p (0-1), using O(N) quickselect */
136
+ function percentile(values, p) {
137
+ const copy = values.slice();
138
+ return quickSelect(copy, Math.max(0, Math.ceil(copy.length * p) - 1));
139
+ }
140
+ /** Hoare's selection: O(N) average k-th smallest element. Mutates arr. */
141
+ function quickSelect(arr, k) {
142
+ let lo = 0;
143
+ let hi = arr.length - 1;
144
+ while (lo < hi) {
145
+ const [i, j] = partition(arr, lo, hi);
146
+ if (k <= j) hi = j;
147
+ else if (k >= i) lo = i;
148
+ else break;
149
+ }
150
+ return arr[k];
151
+ }
152
+ /** Fill buf in-place with bootstrap resample (with replacement) from source */
153
+ function resampleInto(source, buf) {
154
+ const n = source.length;
155
+ for (let i = 0; i < n; i++) buf[i] = source[Math.floor(Math.random() * n)];
156
+ }
157
+ /** @return bootstrap resample with replacement */
158
+ function createResample(samples) {
159
+ const n = samples.length;
160
+ return Array.from({ length: n }, () => samples[Math.floor(Math.random() * n)]);
161
+ }
162
+ /** @return Tukey fence bounds [lo, hi] for the given IQR multiplier.
163
+ * minIqr prevents degenerate fences when values are tightly clustered. */
164
+ function tukeyFences(values, multiplier = 3, minIqr = 0) {
165
+ const q1 = percentile(values, .25);
166
+ const q3 = percentile(values, .75);
167
+ const iqr = Math.max(q3 - q1, minIqr);
168
+ return [q1 - multiplier * iqr, q3 + multiplier * iqr];
169
+ }
170
+ /** @return indices of values below the upper 3x IQR Tukey fence.
171
+ * Only trims slow outliers — fast batches reflect less environmental noise, not errors.
172
+ * Floors IQR at 2% of median to avoid over-trimming tightly clustered batch means. */
173
+ function tukeyKeep(values) {
174
+ if (values.length < 4) return values.map((_, i) => i);
175
+ const [, hi] = tukeyFences(values, 3, median(values) * .02);
176
+ return values.flatMap((v, i) => v <= hi ? [i] : []);
177
+ }
178
+ /** @return samples split into blocks by offset boundaries */
179
+ function splitByOffsets(samples, offsets) {
180
+ return offsets.map((start, i) => {
181
+ const end = i + 1 < offsets.length ? offsets[i + 1] : samples.length;
182
+ return samples.slice(start, end);
183
+ });
184
+ }
185
+ /** Tukey-trim outlier blocks and compute per-block statistic for one side */
186
+ function prepareBlocks(samples, offsets, fn, noTrim) {
187
+ const splits = splitByOffsets(samples, offsets);
188
+ const means = splits.map(average);
189
+ const keep = noTrim ? means.map((_, i) => i) : tukeyKeep(means);
190
+ return {
191
+ blockVals: keep.map((i) => fn(splits[i])),
192
+ filtered: keep.flatMap((i) => splits[i]),
193
+ trimCount: means.length - keep.length
194
+ };
195
+ }
196
+ /** Random subsample without replacement via partial Fisher-Yates. Returns original if n <= max. */
197
+ function subsample(samples, max) {
198
+ if (samples.length <= max) return samples;
199
+ const copy = samples.slice();
200
+ for (let i = 0; i < max; i++) {
201
+ const j = i + Math.floor(Math.random() * (copy.length - i));
202
+ [copy[i], copy[j]] = [copy[j], copy[i]];
203
+ }
204
+ return copy.slice(0, max);
205
+ }
206
+ /** @return confidence interval [lower, upper] */
207
+ function computeInterval(values, conf) {
208
+ const alpha = (1 - conf) / 2;
209
+ return [percentile(values, alpha), percentile(values, 1 - alpha)];
210
+ }
211
+ /** Build stat operations in safe order: mean/min/max first (non-destructive),
212
+ * then percentiles ascending (use quickSelect which mutates buf) */
213
+ function buildStatOps(stats, n) {
214
+ const simple = (order, i, fn) => ({
215
+ order,
216
+ compute: fn,
217
+ pointEstimate: fn,
218
+ origIndex: i
219
+ });
220
+ const ops = stats.map((s, i) => {
221
+ if (s === "mean") return simple(-3, i, average);
222
+ if (s === "min") return simple(-2, i, minOf);
223
+ if (s === "max") return simple(-1, i, maxOf);
224
+ const p = s.percentile;
225
+ const k = Math.max(0, Math.ceil(n * p) - 1);
226
+ return {
227
+ order: p,
228
+ origIndex: i,
229
+ compute: (buf) => quickSelect(buf, k),
230
+ pointEstimate: (v) => percentile(v, p)
231
+ };
232
+ });
233
+ ops.sort((a, b) => a.order - b.order);
234
+ return ops;
235
+ }
236
+ /** Hoare partition around the midpoint pivot. @return [i, j] boundary indices. */
237
+ function partition(arr, lo, hi) {
238
+ const pivot = arr[lo + (hi - lo >> 1)];
239
+ let i = lo;
240
+ let j = hi;
241
+ while (i <= j) {
242
+ while (arr[i] < pivot) i++;
243
+ while (arr[j] > pivot) j--;
244
+ if (i <= j) {
245
+ [arr[i], arr[j]] = [arr[j], arr[i]];
246
+ i++;
247
+ j--;
248
+ }
249
+ }
250
+ return [i, j];
251
+ }
252
+ //#endregion
253
+ export { swapDirection as C, subsample as S, prepareBlocks as _, computeInterval as a, splitByOffsets as b, defaultConfidence as c, maxBootstrapInput as d, maxOf as f, percentile as g, minOf as h, coefficientOfVariation as i, flipCI as l, medianAbsoluteDeviation as m, bootstrapCIs as n, computeStat as o, median as p, bootstrapSamples as r, createResample as s, average as t, isBootstrappable as u, quickSelect as v, tukeyFences as w, statKindToFn as x, resampleInto as y };
254
+
255
+ //# sourceMappingURL=StatisticalUtils-BD92crgM.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatisticalUtils-BD92crgM.mjs","names":[],"sources":["../src/stats/StatisticalUtils.ts"],"sourcesContent":["/** Whether CI was computed from block-level or sample-level resampling */\nexport type CILevel = \"block\" | \"sample\";\n\n/** Stat descriptor for multi-bootstrap: known stat kinds enable zero-alloc inner loops */\nexport type StatKind = \"mean\" | \"min\" | \"max\" | { percentile: number };\n\n/** Bootstrap estimate with confidence interval and raw resample distribution */\nexport interface BootstrapResult {\n /** Point estimate from the original sample */\n estimate: number;\n /** Confidence interval [lower, upper] from bootstrap resampling */\n ci: [number, number];\n /** Bootstrap resample distribution (for visualization) */\n samples: number[];\n /** Block-level (between-run) or sample-level (within-run) resampling */\n ciLevel: CILevel;\n /** Original sample count before subsampling (set only when cap applied) */\n subsampled?: number;\n}\n\nexport type CIDirection = \"faster\" | \"slower\" | \"uncertain\" | \"equivalent\";\n\n/** Binned histogram for efficient transfer to browser */\nexport interface HistogramBin {\n /** Bin center value */\n x: number;\n count: number;\n}\n\n/**\n * Bootstrap confidence interval for percentage difference between two sample medians.\n * Used for baseline comparisons: negative percent means current is faster.\n */\nexport interface DifferenceCI {\n /** Observed percentage difference (current - baseline) / baseline */\n percent: number;\n /** Confidence interval [lower, upper] in percent */\n ci: [number, number];\n /** Whether the CI excludes zero: \"faster\", \"slower\", or \"uncertain\" */\n direction: CIDirection;\n /** Bootstrap distribution histogram for visualization */\n histogram?: HistogramBin[];\n /** Label for the CI plot title (e.g. \"mean Δ%\") */\n label?: string;\n /** Blocks trimmed per side [baseline, current] via Tukey fences */\n trimmed?: [number, number];\n /** Block-level (between-run) or sample-level (within-run) resampling */\n ciLevel?: CILevel;\n /** false when batch count is too low for reliable CI */\n ciReliable?: boolean;\n /** Original sample count before subsampling (set only when cap applied) */\n subsampled?: number;\n}\n\n/** Options for bootstrap resampling */\ntype BootstrapOptions = {\n /** Number of bootstrap resamples (default: 10000) */\n resamples?: number;\n /** Confidence level 0-1 (default: 0.95) */\n confidence?: number;\n};\n\ninterface StatOp {\n origIndex: number;\n compute: (buf: number[]) => number;\n pointEstimate: (s: number[]) => number;\n}\n\nexport const defaultConfidence = 0.95;\nexport const bootstrapSamples = 10000;\nexport const maxBootstrapInput = 10_000;\nconst outlierMultiplier = 1.5;\n\n/** Swap direction labels for higher-is-better metrics (positive = faster) */\nexport function swapDirection(ci: DifferenceCI): DifferenceCI {\n const swap: Record<CIDirection, CIDirection> = {\n faster: \"slower\",\n slower: \"faster\",\n uncertain: \"uncertain\",\n equivalent: \"equivalent\",\n };\n return { ...ci, direction: swap[ci.direction] };\n}\n\n/** Negate percent and CI for \"higher is better\" metrics (e.g., throughput) */\nexport function flipCI(ci: DifferenceCI): DifferenceCI {\n return {\n ...ci,\n percent: -ci.percent,\n ci: [-ci.ci[1], -ci.ci[0]],\n histogram: ci.histogram?.map(bin => ({ x: -bin.x, count: bin.count })),\n };\n}\n\n/** Compute a statistic from samples by kind */\nexport function computeStat(samples: number[], kind: StatKind): number {\n if (kind === \"mean\") return average(samples);\n if (kind === \"min\") return minOf(samples);\n if (kind === \"max\") return maxOf(samples);\n return percentile(samples, kind.percentile);\n}\n\n/** @return true if the stat kind supports bootstrap CI (min/max don't) */\nexport function isBootstrappable(kind: StatKind): boolean {\n return kind !== \"min\" && kind !== \"max\";\n}\n\n/** @return smallest value in samples (loop to avoid spread-arg limits) */\nexport function minOf(samples: number[]): number {\n let min = samples[0];\n for (let i = 1; i < samples.length; i++) {\n if (samples[i] < min) min = samples[i];\n }\n return min;\n}\n\n/** @return largest value in samples (loop to avoid spread-arg limits) */\nexport function maxOf(samples: number[]): number {\n let max = samples[0];\n for (let i = 1; i < samples.length; i++) {\n if (samples[i] > max) max = samples[i];\n }\n return max;\n}\n\n/** @return relative standard deviation (coefficient of variation) */\nexport function coefficientOfVariation(samples: number[]): number {\n const mean = average(samples);\n if (mean === 0) return 0;\n const stdDev = standardDeviation(samples);\n return stdDev / mean;\n}\n\n/** @return median absolute deviation for robust variability measure */\nexport function medianAbsoluteDeviation(samples: number[]): number {\n const med = median(samples);\n const deviations = samples.map(x => Math.abs(x - med));\n return median(deviations);\n}\n\n/** @return outliers detected via Tukey's interquartile range method */\nexport function findOutliers(samples: number[]): {\n rate: number;\n indices: number[];\n} {\n const [lo, hi] = tukeyFences(samples, outlierMultiplier);\n const indices = samples.flatMap((v, i) => (v < lo || v > hi ? [i] : []));\n return { rate: indices.length / samples.length, indices };\n}\n\n/** Sample-level bootstrap CI: resample individual samples with replacement. */\nexport function sampleBootstrap(\n samples: number[],\n statFn: (s: number[]) => number,\n options: BootstrapOptions = {},\n): BootstrapResult {\n const { resamples = bootstrapSamples, confidence: conf = defaultConfidence } =\n options;\n const sub = subsample(samples, maxBootstrapInput);\n const buf = new Array(sub.length);\n const stats = Array.from({ length: resamples }, () => {\n resampleInto(sub, buf);\n return statFn(buf);\n });\n return {\n estimate: statFn(samples),\n ci: computeInterval(stats, conf),\n samples: stats,\n ciLevel: \"sample\",\n ...(sub !== samples && { subsampled: samples.length }),\n };\n}\n\n/** Shared-resample bootstrap: one resample per iteration, all stats computed on it.\n * Mean is computed first (non-destructive), then percentiles via in-place quickSelect. */\nexport function multiSampleBootstrap(\n samples: number[],\n stats: StatKind[],\n options: BootstrapOptions = {},\n): BootstrapResult[] {\n const { resamples = bootstrapSamples, confidence: conf = defaultConfidence } =\n options;\n const sub = subsample(samples, maxBootstrapInput);\n const n = sub.length;\n const buf = new Array(n);\n const ops = buildStatOps(stats, n);\n const allStats = ops.map(() => new Array<number>(resamples));\n\n for (let i = 0; i < resamples; i++) {\n resampleInto(sub, buf);\n for (let j = 0; j < ops.length; j++) {\n allStats[j][i] = ops[j].compute(buf);\n }\n }\n\n const capped = sub !== samples;\n const results = new Array<BootstrapResult>(stats.length);\n for (let j = 0; j < ops.length; j++) {\n results[ops[j].origIndex] = {\n estimate: ops[j].pointEstimate(samples),\n ci: computeInterval(allStats[j], conf),\n samples: allStats[j],\n ciLevel: \"sample\",\n ...(capped && { subsampled: samples.length }),\n };\n }\n return results;\n}\n\n/** Bootstrap CIs for multiple stats, dispatching block vs sample automatically.\n * Returns undefined for non-bootstrappable stats (min/max). */\nexport function bootstrapCIs(\n samples: number[],\n batchOffsets: number[] | undefined,\n stats: StatKind[],\n options?: BootstrapOptions,\n): (BootstrapResult | undefined)[] {\n const bsStats = stats.filter(isBootstrappable);\n if (bsStats.length === 0) return stats.map(() => undefined);\n\n const hasBlocks = (batchOffsets?.length ?? 0) >= 2;\n const bsResults = hasBlocks\n ? bsStats.map(s =>\n blockBootstrap(samples, batchOffsets!, statKindToFn(s), options),\n )\n : multiSampleBootstrap(samples, bsStats, options);\n\n const results: (BootstrapResult | undefined)[] = new Array(stats.length);\n let bi = 0;\n for (let i = 0; i < stats.length; i++) {\n results[i] = isBootstrappable(stats[i]) ? bsResults[bi++] : undefined;\n }\n return results;\n}\n\n/** Convert StatKind to a stat function */\nexport function statKindToFn(kind: StatKind): (s: number[]) => number {\n if (kind === \"mean\") return average;\n if (kind === \"min\") return minOf;\n if (kind === \"max\") return maxOf;\n const p = kind.percentile;\n return (s: number[]) => percentile(s, p);\n}\n\n/** Block bootstrap CI: Tukey-trim outlier batches, then resample per-block\n * statFn values as independent observations. Requires 2+ blocks. */\nexport function blockBootstrap(\n samples: number[],\n blocks: number[],\n statFn: (s: number[]) => number,\n options: BootstrapOptions = {},\n): BootstrapResult {\n const { resamples = bootstrapSamples, confidence: conf = defaultConfidence } =\n options;\n const side = prepareBlocks(samples, blocks, statFn);\n const stats = Array.from({ length: resamples }, () =>\n average(createResample(side.blockVals)),\n );\n return {\n estimate: statFn(side.filtered),\n ci: computeInterval(stats, conf),\n samples: stats,\n ciLevel: \"block\",\n };\n}\n\n/** @return mean of values */\nexport function average(values: number[]): number {\n const sum = values.reduce((a, b) => a + b, 0);\n return sum / values.length;\n}\n\n/** @return median (50th percentile) of values */\nexport function median(values: number[]): number {\n return percentile(values, 0.5);\n}\n\n/** @return standard deviation with Bessel's correction */\nexport function standardDeviation(samples: number[]): number {\n if (samples.length <= 1) return 0;\n const mean = average(samples);\n const variance =\n samples.reduce((sum, x) => sum + (x - mean) ** 2, 0) / (samples.length - 1);\n return Math.sqrt(variance);\n}\n\n/** @return value at percentile p (0-1), using O(N) quickselect */\nexport function percentile(values: number[], p: number): number {\n const copy = values.slice();\n const k = Math.max(0, Math.ceil(copy.length * p) - 1);\n return quickSelect(copy, k);\n}\n\n/** Hoare's selection: O(N) average k-th smallest element. Mutates arr. */\nexport function quickSelect(arr: number[], k: number): number {\n let lo = 0;\n let hi = arr.length - 1;\n while (lo < hi) {\n const [i, j] = partition(arr, lo, hi);\n if (k <= j) hi = j;\n else if (k >= i) lo = i;\n else break;\n }\n return arr[k];\n}\n\n/** Fill buf in-place with bootstrap resample (with replacement) from source */\nexport function resampleInto(source: number[], buf: number[]): void {\n const n = source.length;\n for (let i = 0; i < n; i++) {\n buf[i] = source[Math.floor(Math.random() * n)];\n }\n}\n\n/** @return bootstrap resample with replacement */\nexport function createResample(samples: number[]): number[] {\n const n = samples.length;\n return Array.from(\n { length: n },\n () => samples[Math.floor(Math.random() * n)],\n );\n}\n\n/** @return Tukey fence bounds [lo, hi] for the given IQR multiplier.\n * minIqr prevents degenerate fences when values are tightly clustered. */\nexport function tukeyFences(\n values: number[],\n multiplier = 3,\n minIqr = 0,\n): [lo: number, hi: number] {\n const q1 = percentile(values, 0.25);\n const q3 = percentile(values, 0.75);\n const iqr = Math.max(q3 - q1, minIqr);\n return [q1 - multiplier * iqr, q3 + multiplier * iqr];\n}\n\n/** @return indices of values below the upper 3x IQR Tukey fence.\n * Only trims slow outliers — fast batches reflect less environmental noise, not errors.\n * Floors IQR at 2% of median to avoid over-trimming tightly clustered batch means. */\nexport function tukeyKeep(values: number[]): number[] {\n if (values.length < 4) return values.map((_, i) => i);\n const minIqr = median(values) * 0.02;\n const [, hi] = tukeyFences(values, 3, minIqr);\n return values.flatMap((v, i) => (v <= hi ? [i] : []));\n}\n\n/** @return samples split into blocks by offset boundaries */\nexport function splitByOffsets(\n samples: number[],\n offsets: number[],\n): number[][] {\n return offsets.map((start, i) => {\n const end = i + 1 < offsets.length ? offsets[i + 1] : samples.length;\n return samples.slice(start, end);\n });\n}\n\n/** @return per-block statistic values from sample data split by offsets */\nexport function blockValues(\n samples: number[],\n offsets: number[],\n fn: (s: number[]) => number,\n): number[] {\n return splitByOffsets(samples, offsets).map(fn);\n}\n\n/** Tukey-trim outlier blocks and compute per-block statistic for one side */\nexport function prepareBlocks(\n samples: number[],\n offsets: number[],\n fn: (s: number[]) => number,\n noTrim?: boolean,\n): { blockVals: number[]; filtered: number[]; trimCount: number } {\n const splits = splitByOffsets(samples, offsets);\n const means = splits.map(average);\n const keep = noTrim ? means.map((_, i) => i) : tukeyKeep(means);\n return {\n blockVals: keep.map(i => fn(splits[i])),\n filtered: keep.flatMap(i => splits[i]),\n trimCount: means.length - keep.length,\n };\n}\n\n/** Random subsample without replacement via partial Fisher-Yates. Returns original if n <= max. */\nexport function subsample(samples: number[], max: number): number[] {\n if (samples.length <= max) return samples;\n const copy = samples.slice();\n for (let i = 0; i < max; i++) {\n const j = i + Math.floor(Math.random() * (copy.length - i));\n [copy[i], copy[j]] = [copy[j], copy[i]];\n }\n return copy.slice(0, max);\n}\n\n/** @return confidence interval [lower, upper] */\nexport function computeInterval(\n values: number[],\n conf: number,\n): [number, number] {\n const alpha = (1 - conf) / 2;\n return [percentile(values, alpha), percentile(values, 1 - alpha)];\n}\n\n/** Build stat operations in safe order: mean/min/max first (non-destructive),\n * then percentiles ascending (use quickSelect which mutates buf) */\nfunction buildStatOps(stats: StatKind[], n: number): StatOp[] {\n const simple = (order: number, i: number, fn: (s: number[]) => number) => ({\n order,\n compute: fn,\n pointEstimate: fn,\n origIndex: i,\n });\n const ops = stats.map((s, i): StatOp & { order: number } => {\n if (s === \"mean\") return simple(-3, i, average);\n if (s === \"min\") return simple(-2, i, minOf);\n if (s === \"max\") return simple(-1, i, maxOf);\n const p = s.percentile;\n const k = Math.max(0, Math.ceil(n * p) - 1);\n return {\n order: p,\n origIndex: i,\n compute: (buf: number[]) => quickSelect(buf, k),\n pointEstimate: (v: number[]) => percentile(v, p),\n };\n });\n ops.sort((a, b) => a.order - b.order);\n return ops;\n}\n\n/** Hoare partition around the midpoint pivot. @return [i, j] boundary indices. */\nfunction partition(arr: number[], lo: number, hi: number): [number, number] {\n const pivot = arr[lo + ((hi - lo) >> 1)];\n let i = lo;\n let j = hi;\n while (i <= j) {\n while (arr[i] < pivot) i++;\n while (arr[j] > pivot) j--;\n if (i <= j) {\n [arr[i], arr[j]] = [arr[j], arr[i]];\n i++;\n j--;\n }\n }\n return [i, j];\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { Session } from "node:inspector/promises";
2
+ //#region src/profiling/node/TimeSampler.ts
3
+ /** Run a function while sampling CPU time, return profile */
4
+ async function withTimeProfiling(options, fn) {
5
+ const { interval } = options;
6
+ const ownSession = !options.session;
7
+ const session = options.session ?? new Session();
8
+ if (ownSession) session.connect();
9
+ try {
10
+ if (ownSession) await session.post("Profiler.enable");
11
+ if (interval) await session.post("Profiler.setSamplingInterval", { interval });
12
+ await session.post("Profiler.start");
13
+ const result = await fn();
14
+ const { profile } = await session.post("Profiler.stop");
15
+ return {
16
+ result,
17
+ profile
18
+ };
19
+ } finally {
20
+ if (ownSession) {
21
+ await session.post("Profiler.disable");
22
+ session.disconnect();
23
+ }
24
+ }
25
+ }
26
+ //#endregion
27
+ export { withTimeProfiling };
28
+
29
+ //# sourceMappingURL=TimeSampler-Ds8n7l2B.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeSampler-Ds8n7l2B.mjs","names":[],"sources":["../src/profiling/node/TimeSampler.ts"],"sourcesContent":["import { Session } from \"node:inspector/promises\";\nimport type { CallFrame } from \"./HeapSampler.ts\";\n\n/** V8 CPU profile node (flat array element, not tree) */\nexport interface TimeProfileNode {\n id: number;\n callFrame: CallFrame;\n hitCount?: number;\n /** Child node IDs */\n children?: number[];\n}\n\n/** V8 CPU profile returned by Profiler.stop */\nexport interface TimeProfile {\n nodes: TimeProfileNode[];\n /** Microseconds */\n startTime: number;\n /** Microseconds */\n endTime: number;\n /** Node IDs sampled at each tick */\n samples?: number[];\n /** Microseconds between samples */\n timeDeltas?: number[];\n}\n\nexport interface TimeProfileOptions {\n /** Sampling interval in microseconds (default 1000 = 1ms) */\n interval?: number;\n /** External session to use (shares Profiler domain, caller manages enable/disable) */\n session?: Session;\n}\n\n/** Run a function while sampling CPU time, return profile */\nexport async function withTimeProfiling<T>(\n options: TimeProfileOptions,\n fn: () => Promise<T> | T,\n): Promise<{ result: T; profile: TimeProfile }> {\n const { interval } = options;\n const ownSession = !options.session;\n const session = options.session ?? new Session();\n if (ownSession) session.connect();\n\n try {\n if (ownSession) await session.post(\"Profiler.enable\");\n if (interval)\n await session.post(\"Profiler.setSamplingInterval\", { interval });\n await session.post(\"Profiler.start\");\n const result = await fn();\n const { profile } = await session.post(\"Profiler.stop\");\n return { result, profile: profile as unknown as TimeProfile };\n } finally {\n if (ownSession) {\n await session.post(\"Profiler.disable\");\n session.disconnect();\n }\n }\n}\n"],"mappings":";;;AAiCA,eAAsB,kBACpB,SACA,IAC8C;CAC9C,MAAM,EAAE,aAAa;CACrB,MAAM,aAAa,CAAC,QAAQ;CAC5B,MAAM,UAAU,QAAQ,WAAW,IAAI,SAAS;AAChD,KAAI,WAAY,SAAQ,SAAS;AAEjC,KAAI;AACF,MAAI,WAAY,OAAM,QAAQ,KAAK,kBAAkB;AACrD,MAAI,SACF,OAAM,QAAQ,KAAK,gCAAgC,EAAE,UAAU,CAAC;AAClE,QAAM,QAAQ,KAAK,iBAAiB;EACpC,MAAM,SAAS,MAAM,IAAI;EACzB,MAAM,EAAE,YAAY,MAAM,QAAQ,KAAK,gBAAgB;AACvD,SAAO;GAAE;GAAiB;GAAmC;WACrD;AACR,MAAI,YAAY;AACd,SAAM,QAAQ,KAAK,mBAAmB;AACtC,WAAQ,YAAY"}