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.
- package/LICENSE +20 -0
- package/README.md +99 -260
- package/bin/benchforge +1 -2
- package/dist/AnalyzeArchive-8NCJhmhS.mjs +145 -0
- package/dist/AnalyzeArchive-8NCJhmhS.mjs.map +1 -0
- package/dist/BenchMatrix-BZVrBB_h.mjs +1050 -0
- package/dist/BenchMatrix-BZVrBB_h.mjs.map +1 -0
- package/dist/BenchRunner-DglX1NOn.d.mts +302 -0
- package/dist/CoverageSampler-D5T9DRqe.mjs +27 -0
- package/dist/CoverageSampler-D5T9DRqe.mjs.map +1 -0
- package/dist/Formatters-BWj3d4sv.mjs +95 -0
- package/dist/Formatters-BWj3d4sv.mjs.map +1 -0
- package/dist/{HeapSampler-B8dtKHn1.mjs → HeapSampler-Dq-hpXem.mjs} +4 -4
- package/dist/HeapSampler-Dq-hpXem.mjs.map +1 -0
- package/dist/RunBenchCLI-C17DrJz8.mjs +3075 -0
- package/dist/RunBenchCLI-C17DrJz8.mjs.map +1 -0
- package/dist/StatisticalUtils-BD92crgM.mjs +255 -0
- package/dist/StatisticalUtils-BD92crgM.mjs.map +1 -0
- package/dist/TimeSampler-Ds8n7l2B.mjs +29 -0
- package/dist/TimeSampler-Ds8n7l2B.mjs.map +1 -0
- package/dist/ViewerServer-BJhdnxlN.mjs +639 -0
- package/dist/ViewerServer-BJhdnxlN.mjs.map +1 -0
- package/dist/ViewerServer-CuMNdNBz.mjs +2 -0
- package/dist/bin/benchforge.mjs +4 -5
- package/dist/bin/benchforge.mjs.map +1 -1
- package/dist/index.d.mts +731 -522
- package/dist/index.mjs +98 -3
- package/dist/index.mjs.map +1 -0
- package/dist/runners/WorkerScript.d.mts +12 -4
- package/dist/runners/WorkerScript.mjs +92 -120
- package/dist/runners/WorkerScript.mjs.map +1 -1
- package/dist/viewer/assets/CIPlot-BkOvMoMa.js +1 -0
- package/dist/viewer/assets/HistogramKde-CmSyUFY0.js +1 -0
- package/dist/viewer/assets/LegendUtils-BJpbn_jr.js +55 -0
- package/dist/viewer/assets/SampleTimeSeries-C4VBhXr3.js +1 -0
- package/dist/viewer/assets/index-Br9bp_cX.js +153 -0
- package/dist/viewer/assets/index-NzXXe_CC.css +1 -0
- package/dist/viewer/index.html +19 -0
- package/dist/viewer/speedscope/LICENSE +21 -0
- package/dist/viewer/speedscope/SourceCodePro-Regular.ttf-ILST5JV6.woff2 +0 -0
- package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js +2 -0
- package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js.map +7 -0
- package/dist/viewer/speedscope/favicon-16x16-VSI62OPJ.png +0 -0
- package/dist/viewer/speedscope/favicon-32x32-3EB2YCUY.png +0 -0
- package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js +2 -0
- package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js.map +7 -0
- package/dist/viewer/speedscope/favicon-FOKUP5Y5.ico +0 -0
- package/dist/viewer/speedscope/favicon-M34RF7BI.js +2 -0
- package/dist/viewer/speedscope/favicon-M34RF7BI.js.map +7 -0
- package/dist/viewer/speedscope/file-format-schema.json +274 -0
- package/dist/viewer/speedscope/index.html +19 -0
- package/dist/viewer/speedscope/jfrview_bg-BLJXNNQB.wasm +0 -0
- package/dist/viewer/speedscope/perf-vertx-stacks-01-collapsed-all-ZNUIGAJL.txt +199 -0
- package/dist/viewer/speedscope/release.txt +3 -0
- package/dist/viewer/speedscope/source-code-pro.LICENSE.md +93 -0
- package/dist/viewer/speedscope/speedscope-GHPHNKXC.css +2 -0
- package/dist/viewer/speedscope/speedscope-GHPHNKXC.css.map +7 -0
- package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js +212 -0
- package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js.map +7 -0
- package/package.json +52 -26
- package/src/bin/benchforge.ts +2 -2
- package/src/cli/AnalyzeArchive.ts +232 -0
- package/src/cli/BrowserBench.ts +322 -0
- package/src/cli/CliArgs.ts +164 -48
- package/src/cli/CliExport.ts +179 -0
- package/src/cli/CliOptions.ts +147 -0
- package/src/cli/CliReport.ts +197 -0
- package/src/cli/FilterBenchmarks.ts +18 -30
- package/src/cli/RunBenchCLI.ts +138 -844
- package/src/cli/SuiteRunner.ts +160 -0
- package/src/cli/ViewerServer.ts +282 -0
- package/src/export/AllocExport.ts +121 -0
- package/src/export/ArchiveExport.ts +146 -0
- package/src/export/ArchiveFormat.ts +50 -0
- package/src/export/CoverageExport.ts +148 -0
- package/src/export/EditorUri.ts +10 -0
- package/src/export/PerfettoExport.ts +91 -126
- package/src/export/SpeedscopeTypes.ts +98 -0
- package/src/export/TimeExport.ts +115 -0
- package/src/index.ts +87 -62
- package/src/matrix/BenchMatrix.ts +230 -0
- package/src/matrix/CaseLoader.ts +8 -6
- package/src/matrix/MatrixDirRunner.ts +153 -0
- package/src/matrix/MatrixFilter.ts +55 -53
- package/src/matrix/MatrixInlineRunner.ts +50 -0
- package/src/matrix/MatrixReport.ts +94 -254
- package/src/matrix/VariantLoader.ts +9 -9
- package/src/profiling/browser/BenchLoop.ts +51 -0
- package/src/profiling/browser/BrowserCDP.ts +133 -0
- package/src/profiling/browser/BrowserGcStats.ts +33 -0
- package/src/profiling/browser/BrowserProfiler.ts +160 -0
- package/src/profiling/browser/CdpClient.ts +82 -0
- package/src/profiling/browser/CdpPage.ts +138 -0
- package/src/profiling/browser/ChromeLauncher.ts +158 -0
- package/src/profiling/browser/ChromeTraceEvent.ts +28 -0
- package/src/profiling/browser/PageLoadMode.ts +61 -0
- package/src/profiling/node/CoverageSampler.ts +27 -0
- package/src/profiling/node/CoverageTypes.ts +23 -0
- package/src/profiling/node/HeapSampleReport.ts +261 -0
- package/src/{heap-sample → profiling/node}/HeapSampler.ts +55 -13
- package/src/profiling/node/ResolvedProfile.ts +98 -0
- package/src/profiling/node/TimeSampler.ts +57 -0
- package/src/report/BenchmarkReport.ts +146 -0
- package/src/report/Colors.ts +9 -0
- package/src/report/Formatters.ts +110 -0
- package/src/report/GcSections.ts +151 -0
- package/src/{GitUtils.ts → report/GitUtils.ts} +18 -19
- package/src/report/HtmlReport.ts +223 -0
- package/src/report/ParseStats.ts +73 -0
- package/src/report/StandardSections.ts +147 -0
- package/src/report/ViewerSections.ts +286 -0
- package/src/report/text/TableReport.ts +253 -0
- package/src/report/text/TextReport.ts +123 -0
- package/src/runners/AdaptiveWrapper.ts +167 -287
- package/src/runners/BenchRunner.ts +27 -22
- package/src/{Benchmark.ts → runners/BenchmarkSpec.ts} +5 -6
- package/src/runners/CreateRunner.ts +5 -7
- package/src/runners/GcStats.ts +58 -61
- package/src/{MeasuredResults.ts → runners/MeasuredResults.ts} +43 -37
- package/src/runners/MergeBatches.ts +123 -0
- package/src/{NodeGC.ts → runners/NodeGC.ts} +2 -3
- package/src/runners/RunnerOrchestrator.ts +180 -296
- package/src/runners/RunnerUtils.ts +75 -1
- package/src/runners/SampleStats.ts +100 -0
- package/src/runners/TimingRunner.ts +244 -0
- package/src/runners/TimingUtils.ts +3 -2
- package/src/runners/WorkerScript.ts +162 -178
- package/src/stats/BootstrapDifference.ts +282 -0
- package/src/{PermutationTest.ts → stats/PermutationTest.ts} +31 -40
- package/src/stats/StatisticalUtils.ts +445 -0
- package/src/{tests → test}/AdaptiveConvergence.test.ts +10 -10
- package/src/test/AdaptiveRunner.test.ts +39 -41
- package/src/{tests → test}/AdaptiveSampling.test.ts +9 -9
- package/src/test/AdaptiveStatistics.integration.ts +9 -41
- package/src/{tests → test}/BenchMatrix.test.ts +31 -28
- package/src/test/BenchmarkReport.test.ts +63 -13
- package/src/test/BrowserBench.e2e.test.ts +186 -17
- package/src/test/BrowserBench.test.ts +10 -5
- package/src/test/BuildTimeSection.test.ts +130 -0
- package/src/test/CapSamples.test.ts +82 -0
- package/src/test/CoverageExport.test.ts +115 -0
- package/src/test/CoverageSampler.test.ts +33 -0
- package/src/test/HeapAttribution.test.ts +51 -0
- package/src/{tests → test}/MatrixFilter.test.ts +16 -16
- package/src/{tests → test}/MatrixReport.test.ts +1 -1
- package/src/test/PermutationTest.test.ts +1 -1
- package/src/{tests → test}/RealDataValidation.test.ts +6 -6
- package/src/test/RunBenchCLI.test.ts +57 -56
- package/src/test/RunnerOrchestrator.test.ts +12 -12
- package/src/test/StatisticalUtils.test.ts +48 -12
- package/src/{table-util/test → test}/TableReport.test.ts +2 -2
- package/src/test/TestUtils.ts +35 -30
- package/src/test/TimeExport.test.ts +139 -0
- package/src/test/TimeSampler.test.ts +37 -0
- package/src/test/ViewerLive.e2e.test.ts +159 -0
- package/src/test/ViewerStatic.static.e2e.test.ts +137 -0
- package/src/{tests → test}/fixtures/baseline/impl.ts +1 -1
- package/src/{tests → test}/fixtures/bevy30-samples.ts +3 -1
- package/src/test/fixtures/cases/asyncCases.ts +9 -0
- package/src/{tests → test}/fixtures/cases/cases.ts +5 -2
- package/src/test/fixtures/cases/variants/product.ts +2 -0
- package/src/test/fixtures/cases/variants/sum.ts +2 -0
- package/src/test/fixtures/discover/fast.ts +1 -0
- package/src/{tests → test}/fixtures/discover/slow.ts +1 -1
- package/src/test/fixtures/invalid/bad.ts +1 -0
- package/src/test/fixtures/loader/fast.ts +1 -0
- package/src/{tests → test}/fixtures/loader/slow.ts +1 -1
- package/src/test/fixtures/loader/stateful.ts +2 -0
- package/src/test/fixtures/stateful/stateful.ts +2 -0
- package/src/test/fixtures/variants/extra.ts +1 -0
- package/src/test/fixtures/variants/impl.ts +1 -0
- package/src/test/fixtures/worker/fast.ts +1 -0
- package/src/{tests → test}/fixtures/worker/slow.ts +1 -1
- package/src/viewer/DateFormat.ts +30 -0
- package/src/viewer/Helpers.ts +23 -0
- package/src/viewer/LineData.ts +120 -0
- package/src/viewer/Providers.ts +191 -0
- package/src/viewer/ReportData.ts +123 -0
- package/src/viewer/State.ts +49 -0
- package/src/viewer/Theme.ts +15 -0
- package/src/viewer/components/App.tsx +73 -0
- package/src/viewer/components/DropZone.tsx +71 -0
- package/src/viewer/components/LazyPlot.ts +33 -0
- package/src/viewer/components/SamplesPanel.tsx +214 -0
- package/src/viewer/components/Shell.tsx +26 -0
- package/src/viewer/components/SourcePanel.tsx +216 -0
- package/src/viewer/components/SummaryPanel.tsx +332 -0
- package/src/viewer/components/TabBar.tsx +131 -0
- package/src/viewer/components/TabContent.tsx +46 -0
- package/src/viewer/components/ThemeToggle.tsx +50 -0
- package/src/viewer/index.html +20 -0
- package/src/viewer/main.tsx +4 -0
- package/src/viewer/plots/CIPlot.ts +313 -0
- package/src/{html/browser → viewer/plots}/HistogramKde.ts +42 -47
- package/src/viewer/plots/LegendUtils.ts +134 -0
- package/src/viewer/plots/PlotTypes.ts +85 -0
- package/src/viewer/plots/RenderPlots.ts +230 -0
- package/src/viewer/plots/SampleTimeSeries.ts +306 -0
- package/src/viewer/plots/SvgHelpers.ts +136 -0
- package/src/viewer/plots/TimeSeriesMarks.ts +319 -0
- package/src/viewer/report.css +427 -0
- package/src/viewer/shell.css +357 -0
- package/src/viewer/tsconfig.json +11 -0
- package/dist/BenchRunner-CSKN9zPy.d.mts +0 -225
- package/dist/BrowserHeapSampler-DCeL42RE.mjs +0 -202
- package/dist/BrowserHeapSampler-DCeL42RE.mjs.map +0 -1
- package/dist/GcStats-ByEovUi1.mjs +0 -77
- package/dist/GcStats-ByEovUi1.mjs.map +0 -1
- package/dist/HeapSampler-B8dtKHn1.mjs.map +0 -1
- package/dist/TimingUtils-ClclVQ7E.mjs +0 -597
- package/dist/TimingUtils-ClclVQ7E.mjs.map +0 -1
- package/dist/browser/index.js +0 -914
- package/dist/src-Cf_LXwlp.mjs +0 -2873
- package/dist/src-Cf_LXwlp.mjs.map +0 -1
- package/src/BenchMatrix.ts +0 -380
- package/src/BenchmarkReport.ts +0 -156
- package/src/HtmlDataPrep.ts +0 -148
- package/src/StandardSections.ts +0 -261
- package/src/StatisticalUtils.ts +0 -176
- package/src/TypeUtil.ts +0 -8
- package/src/browser/BrowserGcStats.ts +0 -44
- package/src/browser/BrowserHeapSampler.ts +0 -271
- package/src/export/JsonExport.ts +0 -103
- package/src/export/JsonFormat.ts +0 -91
- package/src/heap-sample/HeapSampleReport.ts +0 -196
- package/src/html/HtmlReport.ts +0 -131
- package/src/html/HtmlTemplate.ts +0 -284
- package/src/html/Types.ts +0 -88
- package/src/html/browser/CIPlot.ts +0 -287
- package/src/html/browser/LegendUtils.ts +0 -163
- package/src/html/browser/RenderPlots.ts +0 -263
- package/src/html/browser/SampleTimeSeries.ts +0 -389
- package/src/html/browser/Types.ts +0 -96
- package/src/html/browser/index.ts +0 -1
- package/src/html/index.ts +0 -17
- package/src/runners/BasicRunner.ts +0 -364
- package/src/table-util/ConvergenceFormatters.ts +0 -19
- package/src/table-util/Formatters.ts +0 -152
- package/src/table-util/README.md +0 -70
- package/src/table-util/TableReport.ts +0 -293
- package/src/tests/fixtures/cases/asyncCases.ts +0 -7
- package/src/tests/fixtures/cases/variants/product.ts +0 -2
- package/src/tests/fixtures/cases/variants/sum.ts +0 -2
- package/src/tests/fixtures/discover/fast.ts +0 -1
- package/src/tests/fixtures/invalid/bad.ts +0 -1
- package/src/tests/fixtures/loader/fast.ts +0 -1
- package/src/tests/fixtures/loader/stateful.ts +0 -2
- package/src/tests/fixtures/stateful/stateful.ts +0 -2
- package/src/tests/fixtures/variants/extra.ts +0 -1
- package/src/tests/fixtures/variants/impl.ts +0 -1
- package/src/tests/fixtures/worker/fast.ts +0 -1
- package/src/{table-util/test → test}/TableValueExtractor.test.ts +0 -0
- package/src/{table-util/test → test}/TableValueExtractor.ts +9 -9
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
import { getHeapStatistics } from "node:v8";
|
|
2
|
-
import type { BenchmarkSpec } from "../Benchmark.ts";
|
|
3
|
-
import type {
|
|
4
|
-
MeasuredResults,
|
|
5
|
-
OptStatusInfo,
|
|
6
|
-
PausePoint,
|
|
7
|
-
} from "../MeasuredResults.ts";
|
|
8
|
-
import type { BenchRunner, RunnerOptions } from "./BenchRunner.ts";
|
|
9
|
-
import { executeBenchmark } from "./BenchRunner.ts";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Wait time after gc() for V8 to stabilize (ms).
|
|
13
|
-
*
|
|
14
|
-
* V8 has 4 compilation tiers: Ignition (interpreter) -> Sparkplug (baseline) ->
|
|
15
|
-
* Maglev (mid-tier optimizer) -> TurboFan (full optimizer). Tiering thresholds:
|
|
16
|
-
* - Ignition -> Sparkplug: 8 invocations
|
|
17
|
-
* - Sparkplug -> Maglev: 500 invocations
|
|
18
|
-
* - Maglev -> TurboFan: 6000 invocations
|
|
19
|
-
*
|
|
20
|
-
* Optimization compilation happens on background threads and requires idle time
|
|
21
|
-
* on the main thread to complete. Without sufficient warmup + settle time,
|
|
22
|
-
* benchmarks exhibit bimodal timing: slow Sparkplug samples (~30% slower) mixed
|
|
23
|
-
* with fast optimized samples.
|
|
24
|
-
*
|
|
25
|
-
* The warmup iterations trigger the optimization decision, then gcSettleTime
|
|
26
|
-
* provides idle time for background compilation to finish before measurement.
|
|
27
|
-
*
|
|
28
|
-
* @see https://v8.dev/blog/sparkplug
|
|
29
|
-
* @see https://v8.dev/blog/maglev
|
|
30
|
-
* @see https://v8.dev/blog/background-compilation
|
|
31
|
-
*/
|
|
32
|
-
const gcSettleTime = 1000;
|
|
33
|
-
|
|
34
|
-
type CollectParams<T = unknown> = {
|
|
35
|
-
benchmark: BenchmarkSpec<T>;
|
|
36
|
-
maxTime: number;
|
|
37
|
-
maxIterations: number;
|
|
38
|
-
warmup: number;
|
|
39
|
-
params?: T;
|
|
40
|
-
skipWarmup?: boolean;
|
|
41
|
-
traceOpt?: boolean;
|
|
42
|
-
noSettle?: boolean;
|
|
43
|
-
pauseFirst?: number;
|
|
44
|
-
pauseInterval?: number;
|
|
45
|
-
pauseDuration?: number;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
type CollectResult = {
|
|
49
|
-
samples: number[];
|
|
50
|
-
warmupSamples: number[]; // timing of warmup iterations
|
|
51
|
-
heapGrowth: number; // amortized KB per sample
|
|
52
|
-
heapSamples?: number[]; // heap size per sample (bytes)
|
|
53
|
-
timestamps?: number[]; // wall-clock μs per sample for Perfetto
|
|
54
|
-
optStatus?: OptStatusInfo;
|
|
55
|
-
optSamples?: number[]; // per-sample V8 opt status codes
|
|
56
|
-
pausePoints: PausePoint[]; // where pauses occurred
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export type SampleTimeStats = {
|
|
60
|
-
min: number;
|
|
61
|
-
max: number;
|
|
62
|
-
avg: number;
|
|
63
|
-
p50: number;
|
|
64
|
-
p75: number;
|
|
65
|
-
p99: number;
|
|
66
|
-
p999: number;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/** @return runner with time and iteration limits */
|
|
70
|
-
export class BasicRunner implements BenchRunner {
|
|
71
|
-
async runBench<T = unknown>(
|
|
72
|
-
benchmark: BenchmarkSpec<T>,
|
|
73
|
-
options: RunnerOptions,
|
|
74
|
-
params?: T,
|
|
75
|
-
): Promise<MeasuredResults[]> {
|
|
76
|
-
const opts = { ...defaultCollectOptions, ...(options as any) };
|
|
77
|
-
const collected = await collectSamples({ benchmark, params, ...opts });
|
|
78
|
-
return [buildMeasuredResults(benchmark.name, collected)];
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const defaultCollectOptions = {
|
|
83
|
-
maxTime: 5000,
|
|
84
|
-
maxIterations: 1000000,
|
|
85
|
-
warmup: 0,
|
|
86
|
-
traceOpt: false,
|
|
87
|
-
noSettle: false,
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
function buildMeasuredResults(name: string, c: CollectResult): MeasuredResults {
|
|
91
|
-
const time = computeStats(c.samples);
|
|
92
|
-
return {
|
|
93
|
-
name,
|
|
94
|
-
samples: c.samples,
|
|
95
|
-
warmupSamples: c.warmupSamples,
|
|
96
|
-
heapSamples: c.heapSamples,
|
|
97
|
-
timestamps: c.timestamps,
|
|
98
|
-
time,
|
|
99
|
-
heapSize: { avg: c.heapGrowth, min: c.heapGrowth, max: c.heapGrowth },
|
|
100
|
-
optStatus: c.optStatus,
|
|
101
|
-
optSamples: c.optSamples,
|
|
102
|
-
pausePoints: c.pausePoints,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** @return timing samples and amortized allocation from benchmark execution */
|
|
107
|
-
async function collectSamples<T>(p: CollectParams<T>): Promise<CollectResult> {
|
|
108
|
-
if (!p.maxIterations && !p.maxTime) {
|
|
109
|
-
throw new Error(`At least one of maxIterations or maxTime must be set`);
|
|
110
|
-
}
|
|
111
|
-
const warmupSamples = p.skipWarmup ? [] : await runWarmup(p);
|
|
112
|
-
const heapBefore = process.memoryUsage().heapUsed;
|
|
113
|
-
const { samples, heapSamples, timestamps, optStatuses, pausePoints } =
|
|
114
|
-
await runSampleLoop(p);
|
|
115
|
-
const heapGrowth =
|
|
116
|
-
Math.max(0, process.memoryUsage().heapUsed - heapBefore) /
|
|
117
|
-
1024 /
|
|
118
|
-
samples.length;
|
|
119
|
-
if (samples.length === 0) {
|
|
120
|
-
throw new Error(`No samples collected for benchmark: ${p.benchmark.name}`);
|
|
121
|
-
}
|
|
122
|
-
const optStatus = p.traceOpt
|
|
123
|
-
? analyzeOptStatus(samples, optStatuses)
|
|
124
|
-
: undefined;
|
|
125
|
-
const optSamples =
|
|
126
|
-
p.traceOpt && optStatuses.length > 0 ? optStatuses : undefined;
|
|
127
|
-
return {
|
|
128
|
-
samples,
|
|
129
|
-
warmupSamples,
|
|
130
|
-
heapGrowth,
|
|
131
|
-
heapSamples,
|
|
132
|
-
timestamps,
|
|
133
|
-
optStatus,
|
|
134
|
-
optSamples,
|
|
135
|
-
pausePoints,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** Run warmup iterations with gc + settle time for V8 optimization */
|
|
140
|
-
async function runWarmup<T>(p: CollectParams<T>): Promise<number[]> {
|
|
141
|
-
const gc = gcFunction();
|
|
142
|
-
const samples = new Array<number>(p.warmup);
|
|
143
|
-
for (let i = 0; i < p.warmup; i++) {
|
|
144
|
-
const start = performance.now();
|
|
145
|
-
executeBenchmark(p.benchmark, p.params);
|
|
146
|
-
samples[i] = performance.now() - start;
|
|
147
|
-
}
|
|
148
|
-
gc();
|
|
149
|
-
if (!p.noSettle) {
|
|
150
|
-
await new Promise(r => setTimeout(r, gcSettleTime));
|
|
151
|
-
gc();
|
|
152
|
-
}
|
|
153
|
-
return samples;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
type SampleLoopResult = {
|
|
157
|
-
samples: number[];
|
|
158
|
-
heapSamples?: number[];
|
|
159
|
-
timestamps?: number[];
|
|
160
|
-
optStatuses: number[];
|
|
161
|
-
pausePoints: PausePoint[];
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
/** Estimate sample count for pre-allocation */
|
|
165
|
-
function estimateSampleCount(maxTime: number, maxIterations: number): number {
|
|
166
|
-
return maxIterations || Math.ceil(maxTime / 0.1); // assume 0.1ms per iteration minimum
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
type SampleArrays = {
|
|
170
|
-
samples: number[];
|
|
171
|
-
timestamps: number[];
|
|
172
|
-
heapSamples: number[];
|
|
173
|
-
optStatuses: number[];
|
|
174
|
-
pausePoints: PausePoint[];
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
/** Pre-allocate arrays to reduce GC pressure during measurement */
|
|
178
|
-
function createSampleArrays(
|
|
179
|
-
n: number,
|
|
180
|
-
trackHeap: boolean,
|
|
181
|
-
trackOpt: boolean,
|
|
182
|
-
): SampleArrays {
|
|
183
|
-
const arr = (track: boolean) => (track ? new Array<number>(n) : []);
|
|
184
|
-
return {
|
|
185
|
-
samples: new Array<number>(n),
|
|
186
|
-
timestamps: new Array<number>(n),
|
|
187
|
-
heapSamples: arr(trackHeap),
|
|
188
|
-
optStatuses: arr(trackOpt),
|
|
189
|
-
pausePoints: [],
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** Trim arrays to actual sample count */
|
|
194
|
-
function trimArrays(
|
|
195
|
-
a: SampleArrays,
|
|
196
|
-
count: number,
|
|
197
|
-
trackHeap: boolean,
|
|
198
|
-
trackOpt: boolean,
|
|
199
|
-
): void {
|
|
200
|
-
a.samples.length = a.timestamps.length = count;
|
|
201
|
-
if (trackHeap) a.heapSamples.length = count;
|
|
202
|
-
if (trackOpt) a.optStatuses.length = count;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/** Collect timing samples with periodic pauses for V8 optimization */
|
|
206
|
-
async function runSampleLoop<T>(
|
|
207
|
-
p: CollectParams<T>,
|
|
208
|
-
): Promise<SampleLoopResult> {
|
|
209
|
-
const {
|
|
210
|
-
maxTime,
|
|
211
|
-
maxIterations,
|
|
212
|
-
pauseFirst,
|
|
213
|
-
pauseInterval = 0,
|
|
214
|
-
pauseDuration = 100,
|
|
215
|
-
} = p;
|
|
216
|
-
const trackHeap = true; // Always track heap for charts
|
|
217
|
-
const getOptStatus = p.traceOpt ? createOptStatusGetter() : undefined;
|
|
218
|
-
const estimated = estimateSampleCount(maxTime, maxIterations);
|
|
219
|
-
const a = createSampleArrays(estimated, trackHeap, !!getOptStatus);
|
|
220
|
-
|
|
221
|
-
let count = 0;
|
|
222
|
-
let elapsed = 0;
|
|
223
|
-
let totalPauseTime = 0;
|
|
224
|
-
const loopStart = performance.now();
|
|
225
|
-
|
|
226
|
-
while (
|
|
227
|
-
(!maxIterations || count < maxIterations) &&
|
|
228
|
-
(!maxTime || elapsed < maxTime)
|
|
229
|
-
) {
|
|
230
|
-
const start = performance.now();
|
|
231
|
-
executeBenchmark(p.benchmark, p.params);
|
|
232
|
-
const end = performance.now();
|
|
233
|
-
a.samples[count] = end - start;
|
|
234
|
-
a.timestamps[count] = Number(process.hrtime.bigint() / 1000n);
|
|
235
|
-
if (trackHeap) a.heapSamples[count] = getHeapStatistics().used_heap_size;
|
|
236
|
-
if (getOptStatus) a.optStatuses[count] = getOptStatus(p.benchmark.fn);
|
|
237
|
-
count++;
|
|
238
|
-
|
|
239
|
-
if (shouldPause(count, pauseFirst, pauseInterval)) {
|
|
240
|
-
a.pausePoints.push({ sampleIndex: count - 1, durationMs: pauseDuration });
|
|
241
|
-
const pauseStart = performance.now();
|
|
242
|
-
await new Promise(r => setTimeout(r, pauseDuration));
|
|
243
|
-
totalPauseTime += performance.now() - pauseStart;
|
|
244
|
-
}
|
|
245
|
-
elapsed = performance.now() - loopStart - totalPauseTime;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
trimArrays(a, count, trackHeap, !!getOptStatus);
|
|
249
|
-
return {
|
|
250
|
-
samples: a.samples,
|
|
251
|
-
heapSamples: trackHeap ? a.heapSamples : undefined,
|
|
252
|
-
timestamps: a.timestamps,
|
|
253
|
-
optStatuses: a.optStatuses,
|
|
254
|
-
pausePoints: a.pausePoints,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/** Check if we should pause at this iteration for V8 optimization */
|
|
259
|
-
function shouldPause(
|
|
260
|
-
iter: number,
|
|
261
|
-
first: number | undefined,
|
|
262
|
-
interval: number,
|
|
263
|
-
): boolean {
|
|
264
|
-
if (first !== undefined && iter === first) return true;
|
|
265
|
-
if (interval <= 0) return false;
|
|
266
|
-
if (first === undefined) return iter % interval === 0;
|
|
267
|
-
return (iter - first) % interval === 0;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/** @return percentiles and basic statistics */
|
|
271
|
-
export function computeStats(samples: number[]): SampleTimeStats {
|
|
272
|
-
const sorted = [...samples].sort((a, b) => a - b);
|
|
273
|
-
const avg = samples.reduce((sum, s) => sum + s, 0) / samples.length;
|
|
274
|
-
return {
|
|
275
|
-
min: sorted[0],
|
|
276
|
-
max: sorted[sorted.length - 1],
|
|
277
|
-
avg,
|
|
278
|
-
p50: percentile(sorted, 0.5),
|
|
279
|
-
p75: percentile(sorted, 0.75),
|
|
280
|
-
p99: percentile(sorted, 0.99),
|
|
281
|
-
p999: percentile(sorted, 0.999),
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/** @return percentile value with linear interpolation */
|
|
286
|
-
function percentile(sortedArray: number[], p: number): number {
|
|
287
|
-
const index = (sortedArray.length - 1) * p;
|
|
288
|
-
const lower = Math.floor(index);
|
|
289
|
-
const upper = Math.ceil(index);
|
|
290
|
-
const weight = index % 1;
|
|
291
|
-
|
|
292
|
-
if (upper >= sortedArray.length) return sortedArray[sortedArray.length - 1];
|
|
293
|
-
|
|
294
|
-
return sortedArray[lower] * (1 - weight) + sortedArray[upper] * weight;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/** @return runtime gc() function, or no-op if unavailable */
|
|
298
|
-
function gcFunction(): () => void {
|
|
299
|
-
const gc = globalThis.gc || (globalThis as any).__gc;
|
|
300
|
-
if (gc) return gc;
|
|
301
|
-
console.warn("gc() not available, run node/bun with --expose-gc");
|
|
302
|
-
return () => {};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/** @return function to get V8 optimization status (requires --allow-natives-syntax) */
|
|
306
|
-
function createOptStatusGetter(): ((fn: unknown) => number) | undefined {
|
|
307
|
-
try {
|
|
308
|
-
// %GetOptimizationStatus returns a bitmask
|
|
309
|
-
const getter = new Function("f", "return %GetOptimizationStatus(f)");
|
|
310
|
-
getter(() => {});
|
|
311
|
-
return getter as (fn: unknown) => number;
|
|
312
|
-
} catch {
|
|
313
|
-
return undefined;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* V8 optimization status bit meanings:
|
|
319
|
-
* Bit 0 (1): is_function
|
|
320
|
-
* Bit 4 (16): is_optimized (TurboFan)
|
|
321
|
-
* Bit 5 (32): is_optimized (Maglev)
|
|
322
|
-
* Bit 7 (128): is_baseline (Sparkplug)
|
|
323
|
-
* Bit 3 (8): maybe_deoptimized
|
|
324
|
-
*/
|
|
325
|
-
const statusNames: Record<number, string> = {
|
|
326
|
-
1: "interpreted",
|
|
327
|
-
129: "sparkplug", // 1 + 128
|
|
328
|
-
17: "turbofan", // 1 + 16
|
|
329
|
-
33: "maglev", // 1 + 32
|
|
330
|
-
49: "turbofan+maglev", // 1 + 16 + 32
|
|
331
|
-
32769: "optimized", // common optimized status
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
/** @return analysis of V8 optimization status per sample */
|
|
335
|
-
function analyzeOptStatus(
|
|
336
|
-
samples: number[],
|
|
337
|
-
statuses: number[],
|
|
338
|
-
): OptStatusInfo | undefined {
|
|
339
|
-
if (statuses.length === 0 || statuses[0] === undefined) return undefined;
|
|
340
|
-
|
|
341
|
-
const byStatusCode = new Map<number, number[]>();
|
|
342
|
-
let deoptCount = 0;
|
|
343
|
-
|
|
344
|
-
for (let i = 0; i < samples.length; i++) {
|
|
345
|
-
const status = statuses[i];
|
|
346
|
-
if (status === undefined) continue;
|
|
347
|
-
|
|
348
|
-
// Check deopt flag (bit 3)
|
|
349
|
-
if (status & 8) deoptCount++;
|
|
350
|
-
|
|
351
|
-
if (!byStatusCode.has(status)) byStatusCode.set(status, []);
|
|
352
|
-
byStatusCode.get(status)!.push(samples[i]);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const byTier: Record<string, { count: number; medianMs: number }> = {};
|
|
356
|
-
for (const [status, times] of byStatusCode) {
|
|
357
|
-
const name = statusNames[status] || `status=${status}`;
|
|
358
|
-
const sorted = [...times].sort((a, b) => a - b);
|
|
359
|
-
const median = sorted[Math.floor(sorted.length / 2)];
|
|
360
|
-
byTier[name] = { count: times.length, medianMs: median };
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return { byTier, deoptCount };
|
|
364
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import pico from "picocolors";
|
|
2
|
-
|
|
3
|
-
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
|
|
4
|
-
const { red } = isTest ? { red: (str: string) => str } : pico;
|
|
5
|
-
|
|
6
|
-
const lowConfidence = 80;
|
|
7
|
-
|
|
8
|
-
/** @return convergence percentage with color for low values */
|
|
9
|
-
export function formatConvergence(v: unknown): string {
|
|
10
|
-
if (typeof v !== "number") return "—";
|
|
11
|
-
const pct = `${Math.round(v)}%`;
|
|
12
|
-
return v < lowConfidence ? red(pct) : pct;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** @return coefficient of variation as ±percentage */
|
|
16
|
-
export function formatCV(v: unknown): string {
|
|
17
|
-
if (typeof v !== "number") return "";
|
|
18
|
-
return `±${(v * 100).toFixed(1)}%`;
|
|
19
|
-
}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import pico from "picocolors";
|
|
2
|
-
import type { CIDirection, DifferenceCI } from "../StatisticalUtils.ts";
|
|
3
|
-
|
|
4
|
-
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
|
|
5
|
-
const { red, green } = isTest
|
|
6
|
-
? { red: (str: string) => str, green: (str: string) => str }
|
|
7
|
-
: pico;
|
|
8
|
-
|
|
9
|
-
/** Format floats with custom precision */
|
|
10
|
-
export function floatPrecision(precision: number) {
|
|
11
|
-
return (x: unknown): string | null => {
|
|
12
|
-
if (typeof x !== "number") return null;
|
|
13
|
-
return x.toFixed(precision).replace(/\.?0+$/, "");
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Format percentages with custom precision */
|
|
18
|
-
export function percentPrecision(precision: number) {
|
|
19
|
-
return (x: unknown): string | null => {
|
|
20
|
-
if (typeof x !== "number") return null;
|
|
21
|
-
return percent(x, precision);
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Format duration in milliseconds with appropriate units */
|
|
26
|
-
export function duration(ms: unknown): string | null {
|
|
27
|
-
if (typeof ms !== "number") return null;
|
|
28
|
-
if (ms < 0.001) return `${(ms * 1000000).toFixed(0)}ns`;
|
|
29
|
-
if (ms < 1) return `${(ms * 1000).toFixed(1)}μs`;
|
|
30
|
-
if (ms < 1000) return `${ms.toFixed(2)}ms`;
|
|
31
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Format time in milliseconds, showing very small values with units */
|
|
35
|
-
export function timeMs(ms: unknown): string | null {
|
|
36
|
-
if (typeof ms !== "number") return null;
|
|
37
|
-
if (ms < 0.001) return `${(ms * 1000000).toFixed(0)}ns`;
|
|
38
|
-
if (ms < 0.01) return `${(ms * 1000).toFixed(1)}μs`;
|
|
39
|
-
if (ms >= 10) return ms.toFixed(0);
|
|
40
|
-
return ms.toFixed(2);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Format as rate (value per unit) */
|
|
44
|
-
export function rate(unit: string): (value: unknown) => string | null {
|
|
45
|
-
return (value: unknown) => {
|
|
46
|
-
if (typeof value !== "number") return null;
|
|
47
|
-
return `${integer(value)}/${unit}`;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Format integer with thousand separators */
|
|
52
|
-
export function integer(x: unknown): string | null {
|
|
53
|
-
if (typeof x !== "number") return null;
|
|
54
|
-
return new Intl.NumberFormat("en-US").format(Math.round(x));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Format fraction as percentage (0.473 → 47.3%) */
|
|
58
|
-
export function percent(fraction: unknown, precision = 1): string | null {
|
|
59
|
-
if (typeof fraction !== "number") return null;
|
|
60
|
-
return `${Math.abs(fraction * 100).toFixed(precision)}%`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Format percentage difference between two values */
|
|
64
|
-
export function diffPercent(main: unknown, base: unknown): string {
|
|
65
|
-
if (typeof main !== "number" || typeof base !== "number") return " ";
|
|
66
|
-
const diff = main - base;
|
|
67
|
-
return coloredPercent(diff, base);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Format percentage difference for benchmarks (lower is better) */
|
|
71
|
-
export function diffPercentBenchmark(main: unknown, base: unknown): string {
|
|
72
|
-
if (typeof main !== "number" || typeof base !== "number") return " ";
|
|
73
|
-
const diff = main - base;
|
|
74
|
-
return coloredPercent(diff, base, false); // negative is good for benchmarks
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Format fraction as colored +/- percentage */
|
|
78
|
-
function coloredPercent(
|
|
79
|
-
numerator: number,
|
|
80
|
-
denominator: number,
|
|
81
|
-
positiveIsGreen = true,
|
|
82
|
-
): string {
|
|
83
|
-
const fraction = numerator / denominator;
|
|
84
|
-
if (Number.isNaN(fraction) || !Number.isFinite(fraction)) {
|
|
85
|
-
return " ";
|
|
86
|
-
}
|
|
87
|
-
const positive = fraction >= 0;
|
|
88
|
-
const sign = positive ? "+" : "-";
|
|
89
|
-
const percentStr = `${sign}${percent(fraction)}`;
|
|
90
|
-
const isGood = positive === positiveIsGreen;
|
|
91
|
-
return isGood ? green(percentStr) : red(percentStr);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Format memory size in KB with appropriate units */
|
|
95
|
-
export function memoryKB(kb: unknown): string | null {
|
|
96
|
-
if (typeof kb !== "number") return null;
|
|
97
|
-
if (kb < 1024) return `${kb.toFixed(0)}KB`;
|
|
98
|
-
return `${(kb / 1024).toFixed(1)}MB`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Format bytes with appropriate units (B, KB, MB, GB) */
|
|
102
|
-
export function formatBytes(bytes: unknown): string | null {
|
|
103
|
-
if (typeof bytes !== "number") return null;
|
|
104
|
-
if (bytes < 1024) return `${bytes.toFixed(0)}B`;
|
|
105
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
106
|
-
if (bytes < 1024 * 1024 * 1024)
|
|
107
|
-
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
108
|
-
return `${(bytes / 1024 / 1024 / 1024).toFixed(1)}GB`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Format percentage difference with confidence interval */
|
|
112
|
-
export function formatDiffWithCI(value: unknown): string | null {
|
|
113
|
-
if (!isDifferenceCI(value)) return null;
|
|
114
|
-
const { percent, ci, direction } = value;
|
|
115
|
-
return colorByDirection(diffCIText(percent, ci), direction);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** Format percentage difference with CI for throughput metrics (higher is better) */
|
|
119
|
-
export function formatDiffWithCIHigherIsBetter(value: unknown): string | null {
|
|
120
|
-
if (!isDifferenceCI(value)) return null;
|
|
121
|
-
const { percent, ci, direction } = value;
|
|
122
|
-
// Flip percent sign for "higher is better" metrics (direction stays same)
|
|
123
|
-
return colorByDirection(diffCIText(-percent, [-ci[1], -ci[0]]), direction);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** @return formatted "pct [lo, hi]" text for a diff with CI */
|
|
127
|
-
function diffCIText(pct: number, ci: [number, number]): string {
|
|
128
|
-
return `${formatBound(pct)} [${formatBound(ci[0])}, ${formatBound(ci[1])}]`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** @return text colored green for faster, red for slower */
|
|
132
|
-
function colorByDirection(text: string, direction: CIDirection): string {
|
|
133
|
-
if (direction === "faster") return green(text);
|
|
134
|
-
if (direction === "slower") return red(text);
|
|
135
|
-
return text;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** @return signed percentage string (e.g. "+1.2%", "-3.4%") */
|
|
139
|
-
function formatBound(v: number): string {
|
|
140
|
-
const sign = v >= 0 ? "+" : "";
|
|
141
|
-
return `${sign}${v.toFixed(1)}%`;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** @return true if value is a DifferenceCI object */
|
|
145
|
-
function isDifferenceCI(x: unknown): x is DifferenceCI {
|
|
146
|
-
return typeof x === "object" && x !== null && "ci" in x && "direction" in x;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** @return truncated string with ellipsis if over maxLen */
|
|
150
|
-
export function truncate(str: string, maxLen = 30): string {
|
|
151
|
-
return str.length > maxLen ? str.slice(0, maxLen - 3) + "..." : str;
|
|
152
|
-
}
|
package/src/table-util/README.md
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# TableReport
|
|
2
|
-
|
|
3
|
-
Utilities for creating formatted text-based tables.
|
|
4
|
-
Under the hood, TableReport uses the npm [table](https://www.npmjs.com/package/table) library.
|
|
5
|
-
|
|
6
|
-
### Features
|
|
7
|
-
|
|
8
|
-
* **Column Grouping:** Group related columns under a common header.
|
|
9
|
-
* **Difference Columns:** Automatically generate columns that show the percentage difference between a value and a baseline value.
|
|
10
|
-
* **Custom Formatting:** Declaratively provide custom formatters for columns.
|
|
11
|
-
* **Simplified Configuration:** A higher-level API for the `table` library.
|
|
12
|
-
|
|
13
|
-
## `TableReport.ts`
|
|
14
|
-
|
|
15
|
-
The `buildTable` function in `TableReport.ts` is the main entry point for creating a table.
|
|
16
|
-
It takes a configuration object for columns and an array of data records for rows.
|
|
17
|
-
|
|
18
|
-
### Example
|
|
19
|
-
|
|
20
|
-
Here's a simplified example of how to use `buildTable`:
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import { buildTable, ColumnGroup } from './TableReport';
|
|
24
|
-
import { integer, floatPrecision } from './Formatters';
|
|
25
|
-
|
|
26
|
-
interface MyData {
|
|
27
|
-
name: string;
|
|
28
|
-
value: number;
|
|
29
|
-
score: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const data: MyData[] = [
|
|
33
|
-
{ name: 'test A', value: 123, score: 45.6 },
|
|
34
|
-
{ name: 'test B', value: 456, score: 78.9 },
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const baselineData: MyData[] = [
|
|
38
|
-
{ name: 'test A', value: 100, score: 50.0 },
|
|
39
|
-
{ name: 'test B', value: 500, score: 75.0 },
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
const tableConfig: ColumnGroup<MyData>[] = [
|
|
43
|
-
{
|
|
44
|
-
columns: [{ key: 'name', title: 'Name' }],
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
groupTitle: 'Metrics',
|
|
48
|
-
columns: [
|
|
49
|
-
{ key: 'value', title: 'Value', formatter: integer },
|
|
50
|
-
{ key: 'value_diff', title: 'Δ%', diffKey: 'value' },
|
|
51
|
-
{ key: 'score', title: 'Score', formatter: floatPrecision(1) },
|
|
52
|
-
{ key: 'score_diff', title: 'Δ%', diffKey: 'score' },
|
|
53
|
-
],
|
|
54
|
-
},
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
const table = buildTable(tableConfig, data, baselineData);
|
|
58
|
-
console.log(table);
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
For a more complex example, see `BenchmarkReport.ts`, specifically the `mostlyFullRow` and `tableConfig` variables.
|
|
62
|
-
|
|
63
|
-
## `Formatters.ts`
|
|
64
|
-
|
|
65
|
-
This file contains various utility functions for formatting numbers and strings, such as:
|
|
66
|
-
|
|
67
|
-
* `float`, `integer`: Format numbers to a specific precision.
|
|
68
|
-
* `percent`: Format a number as a percentage.
|
|
69
|
-
* `diffPercent`, `diffPercentNegative`: Format the percentage difference between two numbers, with color-coding for positive/negative changes.
|
|
70
|
-
* `bytes`, `duration`, `rate`: Format numbers with appropriate units.
|