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.
- package/LICENSE +20 -0
- package/README.md +99 -294
- 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-BzyUfiyB.d.mts → BenchRunner-DglX1NOn.d.mts} +119 -66
- 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 +711 -558
- 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 +77 -105
- 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 -27
- 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 -51
- 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 +132 -866
- 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 +64 -99
- package/src/export/SpeedscopeTypes.ts +98 -0
- package/src/export/TimeExport.ts +115 -0
- package/src/index.ts +86 -67
- 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 +49 -47
- package/src/matrix/MatrixInlineRunner.ts +50 -0
- package/src/matrix/MatrixReport.ts +90 -250
- package/src/matrix/VariantLoader.ts +5 -5
- 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 +1 -2
- package/src/{heap-sample → profiling/node}/ResolvedProfile.ts +18 -9
- 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 +116 -236
- package/src/runners/BenchRunner.ts +20 -15
- package/src/{Benchmark.ts → runners/BenchmarkSpec.ts} +5 -6
- package/src/runners/CreateRunner.ts +5 -7
- package/src/runners/GcStats.ts +47 -50
- 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 +127 -243
- 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 +135 -151
- package/src/stats/BootstrapDifference.ts +282 -0
- package/src/{PermutationTest.ts → stats/PermutationTest.ts} +8 -17
- 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 +2 -2
- package/src/{tests → test}/BenchMatrix.test.ts +19 -16
- 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 +14 -14
- package/src/{tests → test}/MatrixFilter.test.ts +1 -1
- 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 +39 -38
- 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 +12 -7
- 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 +33 -38
- 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/BrowserHeapSampler-B6asLKWQ.mjs +0 -202
- package/dist/BrowserHeapSampler-B6asLKWQ.mjs.map +0 -1
- package/dist/GcStats-wX7Xyblu.mjs +0 -77
- package/dist/GcStats-wX7Xyblu.mjs.map +0 -1
- package/dist/HeapSampler-B8dtKHn1.mjs.map +0 -1
- package/dist/TimingUtils-DwOwkc8G.mjs +0 -597
- package/dist/TimingUtils-DwOwkc8G.mjs.map +0 -1
- package/dist/browser/index.js +0 -914
- package/dist/src-B-DDaCa9.mjs +0 -3108
- package/dist/src-B-DDaCa9.mjs.map +0 -1
- package/src/BenchMatrix.ts +0 -380
- package/src/BenchmarkReport.ts +0 -161
- package/src/HtmlDataPrep.ts +0 -148
- package/src/StandardSections.ts +0 -261
- package/src/StatisticalUtils.ts +0 -175
- 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/export/SpeedscopeExport.ts +0 -202
- package/src/heap-sample/HeapSampleReport.ts +0 -269
- 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 -157
- 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 +0 -0
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { type ChildProcess, fork } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import type {
|
|
5
|
-
import type { HeapProfile } from "../
|
|
6
|
-
import type {
|
|
7
|
-
import {
|
|
8
|
-
type AdaptiveOptions,
|
|
9
|
-
createAdaptiveWrapper,
|
|
10
|
-
} from "./AdaptiveWrapper.ts";
|
|
4
|
+
import type { CoverageData } from "../profiling/node/CoverageTypes.ts";
|
|
5
|
+
import type { HeapProfile } from "../profiling/node/HeapSampler.ts";
|
|
6
|
+
import type { TimeProfile } from "../profiling/node/TimeSampler.ts";
|
|
7
|
+
import type { BenchmarkFunction, BenchmarkSpec } from "./BenchmarkSpec.ts";
|
|
11
8
|
import type { RunnerOptions } from "./BenchRunner.ts";
|
|
12
|
-
import {
|
|
9
|
+
import type { KnownRunner } from "./CreateRunner.ts";
|
|
13
10
|
import { aggregateGcStats, type GcEvent, parseGcLine } from "./GcStats.ts";
|
|
11
|
+
import type { MeasuredResults } from "./MeasuredResults.ts";
|
|
12
|
+
import {
|
|
13
|
+
createBenchRunner,
|
|
14
|
+
importBenchFn,
|
|
15
|
+
resolveVariantFn,
|
|
16
|
+
} from "./RunnerUtils.ts";
|
|
14
17
|
import { debugWorkerTiming, getElapsed, getPerfNow } from "./TimingUtils.ts";
|
|
15
18
|
import type {
|
|
16
19
|
ErrorMessage,
|
|
@@ -18,7 +21,7 @@ import type {
|
|
|
18
21
|
RunMessage,
|
|
19
22
|
} from "./WorkerScript.ts";
|
|
20
23
|
|
|
21
|
-
/** Parameters for running a matrix variant
|
|
24
|
+
/** Parameters for running a matrix variant */
|
|
22
25
|
export interface RunMatrixVariantParams {
|
|
23
26
|
variantDir: string;
|
|
24
27
|
variantId: string;
|
|
@@ -27,20 +30,9 @@ export interface RunMatrixVariantParams {
|
|
|
27
30
|
casesModule?: string;
|
|
28
31
|
runner: KnownRunner;
|
|
29
32
|
options: RunnerOptions;
|
|
33
|
+
useWorker?: boolean;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
type WorkerParams<T = unknown> = {
|
|
33
|
-
spec: BenchmarkSpec<T>;
|
|
34
|
-
runner: KnownRunner;
|
|
35
|
-
options: RunnerOptions;
|
|
36
|
-
params?: T;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
type WorkerHandlers = {
|
|
40
|
-
resolve: (results: MeasuredResults[], heapProfile?: HeapProfile) => void;
|
|
41
|
-
reject: (error: Error) => void;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
36
|
interface RunBenchmarkParams<T = unknown> {
|
|
45
37
|
spec: BenchmarkSpec<T>;
|
|
46
38
|
runner: KnownRunner;
|
|
@@ -53,7 +45,7 @@ const logTiming = debugWorkerTiming
|
|
|
53
45
|
? (message: string) => console.log(`[RunnerOrchestrator] ${message}`)
|
|
54
46
|
: () => {};
|
|
55
47
|
|
|
56
|
-
/**
|
|
48
|
+
/** Run a benchmark spec, optionally in an isolated worker process for profiling support. */
|
|
57
49
|
export async function runBenchmark<T = unknown>({
|
|
58
50
|
spec,
|
|
59
51
|
runner,
|
|
@@ -62,41 +54,30 @@ export async function runBenchmark<T = unknown>({
|
|
|
62
54
|
params,
|
|
63
55
|
}: RunBenchmarkParams<T>): Promise<MeasuredResults[]> {
|
|
64
56
|
if (!useWorker) {
|
|
65
|
-
const
|
|
57
|
+
const resolved = spec.modulePath
|
|
66
58
|
? await resolveModuleSpec(spec, params)
|
|
67
59
|
: { spec, params };
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const benchRunner = (options as any).adaptive
|
|
71
|
-
? createAdaptiveWrapper(base, options as AdaptiveOptions)
|
|
72
|
-
: base;
|
|
73
|
-
return benchRunner.runBench(
|
|
74
|
-
resolvedSpec.spec,
|
|
75
|
-
options,
|
|
76
|
-
resolvedSpec.params,
|
|
77
|
-
);
|
|
60
|
+
const benchRunner = await createBenchRunner(runner, options);
|
|
61
|
+
return benchRunner.runBench(resolved.spec, options, resolved.params);
|
|
78
62
|
}
|
|
79
63
|
|
|
80
|
-
|
|
64
|
+
const msg = createRunMessage(spec, runner, options, params);
|
|
65
|
+
return runWorkerWithMessage(spec.name, options, msg);
|
|
81
66
|
}
|
|
82
67
|
|
|
83
|
-
/** Run a matrix variant benchmark in
|
|
68
|
+
/** Run a matrix variant benchmark, directly or in a worker. */
|
|
84
69
|
export async function runMatrixVariant(
|
|
85
70
|
params: RunMatrixVariantParams,
|
|
86
71
|
): Promise<MeasuredResults[]> {
|
|
87
|
-
const {
|
|
88
|
-
variantDir,
|
|
89
|
-
variantId,
|
|
90
|
-
caseId,
|
|
91
|
-
caseData,
|
|
92
|
-
casesModule,
|
|
93
|
-
runner,
|
|
94
|
-
options,
|
|
95
|
-
} = params;
|
|
72
|
+
const { variantId, caseId, runner, options, useWorker = true } = params;
|
|
96
73
|
const name = `${variantId}/${caseId}`;
|
|
74
|
+
|
|
75
|
+
if (!useWorker) return runMatrixVariantDirect(params, name);
|
|
76
|
+
|
|
77
|
+
const { variantDir, caseData, casesModule } = params;
|
|
97
78
|
const message: RunMessage = {
|
|
98
79
|
type: "run",
|
|
99
|
-
spec: { name
|
|
80
|
+
spec: { name } as BenchmarkSpec,
|
|
100
81
|
runnerName: runner,
|
|
101
82
|
options,
|
|
102
83
|
variantDir,
|
|
@@ -113,67 +94,18 @@ async function resolveModuleSpec<T>(
|
|
|
113
94
|
spec: BenchmarkSpec<T>,
|
|
114
95
|
params: T | undefined,
|
|
115
96
|
): Promise<{ spec: BenchmarkSpec<T>; params: T | undefined }> {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
`Export '${name}' from ${spec.modulePath} is not a function`,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
let resolvedParams = params;
|
|
130
|
-
if (spec.setupExportName) {
|
|
131
|
-
const setupFn = module[spec.setupExportName];
|
|
132
|
-
if (typeof setupFn !== "function") {
|
|
133
|
-
const msg = `Setup export '${spec.setupExportName}' from ${spec.modulePath} is not a function`;
|
|
134
|
-
throw new Error(msg);
|
|
135
|
-
}
|
|
136
|
-
resolvedParams = await setupFn(params);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return { spec: { ...spec, fn }, params: resolvedParams };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** Run benchmark in isolated worker process */
|
|
143
|
-
async function runInWorker<T>(
|
|
144
|
-
workerParams: WorkerParams<T>,
|
|
145
|
-
): Promise<MeasuredResults[]> {
|
|
146
|
-
const { spec, runner, options, params } = workerParams;
|
|
147
|
-
const msg = createRunMessage(spec, runner, options, params);
|
|
148
|
-
return runWorkerWithMessage(spec.name, options, msg);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Spawn worker, wire handlers, send message, return results */
|
|
152
|
-
function runWorkerWithMessage(
|
|
153
|
-
name: string,
|
|
154
|
-
options: RunnerOptions,
|
|
155
|
-
message: RunMessage,
|
|
156
|
-
): Promise<MeasuredResults[]> {
|
|
157
|
-
const startTime = getPerfNow();
|
|
158
|
-
const collectGcStats = options.gcStats ?? false;
|
|
159
|
-
logTiming(`Starting worker for ${name}`);
|
|
160
|
-
|
|
161
|
-
return new Promise((resolve, reject) => {
|
|
162
|
-
const { worker, createTime, gcEvents } =
|
|
163
|
-
createWorkerWithTiming(collectGcStats);
|
|
164
|
-
const handlers = createWorkerHandlers(
|
|
165
|
-
name,
|
|
166
|
-
startTime,
|
|
167
|
-
gcEvents,
|
|
168
|
-
resolve,
|
|
169
|
-
reject,
|
|
170
|
-
);
|
|
171
|
-
setupWorkerHandlers(worker, name, handlers);
|
|
172
|
-
sendWorkerMessage(worker, message, createTime);
|
|
173
|
-
});
|
|
97
|
+
const { modulePath, exportName, setupExportName } = spec;
|
|
98
|
+
const imported = await importBenchFn(
|
|
99
|
+
modulePath!,
|
|
100
|
+
exportName,
|
|
101
|
+
setupExportName,
|
|
102
|
+
params,
|
|
103
|
+
);
|
|
104
|
+
const fn = imported.fn as BenchmarkFunction<T>;
|
|
105
|
+
return { spec: { ...spec, fn }, params: imported.params as T | undefined };
|
|
174
106
|
}
|
|
175
107
|
|
|
176
|
-
/**
|
|
108
|
+
/** Serialize a BenchmarkSpec into a worker-safe message (modulePath or fnCode) */
|
|
177
109
|
function createRunMessage<T>(
|
|
178
110
|
spec: BenchmarkSpec<T>,
|
|
179
111
|
runnerName: KnownRunner,
|
|
@@ -198,175 +130,127 @@ function createRunMessage<T>(
|
|
|
198
130
|
return message;
|
|
199
131
|
}
|
|
200
132
|
|
|
201
|
-
/**
|
|
202
|
-
function
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
);
|
|
211
|
-
return { worker, createTime, gcEvents };
|
|
212
|
-
}
|
|
133
|
+
/** Run a benchmark in an isolated worker process with timeout and GC capture. */
|
|
134
|
+
function runWorkerWithMessage(
|
|
135
|
+
name: string,
|
|
136
|
+
options: RunnerOptions,
|
|
137
|
+
message: RunMessage,
|
|
138
|
+
): Promise<MeasuredResults[]> {
|
|
139
|
+
const startTime = getPerfNow();
|
|
140
|
+
const collectGcStats = options.gcStats ?? false;
|
|
141
|
+
logTiming(`Starting worker for ${name}`);
|
|
213
142
|
|
|
214
|
-
|
|
215
|
-
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const gcEvents: GcEvent[] = [];
|
|
145
|
+
const worker = spawnWorkerProcess(collectGcStats);
|
|
146
|
+
if (collectGcStats && worker.stdout) setupGcCapture(worker, gcEvents);
|
|
216
147
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
148
|
+
const timeoutId = setTimeout(() => {
|
|
149
|
+
killWorker();
|
|
150
|
+
reject(new Error(`Benchmark "${name}" timed out after 60 seconds`));
|
|
151
|
+
}, 60000);
|
|
152
|
+
|
|
153
|
+
function killWorker() {
|
|
154
|
+
clearTimeout(timeoutId);
|
|
155
|
+
if (!worker.killed) worker.kill("SIGTERM");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
worker.on("message", (msg: ResultMessage | ErrorMessage) => {
|
|
159
|
+
killWorker();
|
|
160
|
+
if (msg.type === "error") {
|
|
161
|
+
const error = new Error(`Benchmark "${name}" failed: ${msg.error}`);
|
|
162
|
+
if (msg.stack) error.stack = msg.stack;
|
|
163
|
+
return reject(error);
|
|
233
164
|
}
|
|
234
|
-
|
|
165
|
+
const elapsed = getElapsed(startTime).toFixed(1);
|
|
166
|
+
logTiming(`Total worker time for ${name}: ${elapsed}ms`);
|
|
167
|
+
const { results, heapProfile, timeProfile, coverage } = msg;
|
|
168
|
+
attachProfilingData(
|
|
169
|
+
results,
|
|
170
|
+
gcEvents,
|
|
171
|
+
heapProfile,
|
|
172
|
+
timeProfile,
|
|
173
|
+
coverage,
|
|
174
|
+
);
|
|
235
175
|
resolve(results);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
176
|
+
});
|
|
177
|
+
worker.on("error", (error: Error) => {
|
|
178
|
+
killWorker();
|
|
179
|
+
const msg = `Worker process failed for "${name}": ${error.message}`;
|
|
180
|
+
reject(new Error(msg));
|
|
181
|
+
});
|
|
182
|
+
worker.on("exit", (code: number | null) => {
|
|
183
|
+
if (code !== 0 && code !== null) {
|
|
184
|
+
killWorker();
|
|
185
|
+
reject(new Error(`Worker exited with code ${code} for "${name}"`));
|
|
186
|
+
}
|
|
187
|
+
});
|
|
240
188
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
worker: ReturnType<typeof createWorkerProcess>,
|
|
244
|
-
specName: string,
|
|
245
|
-
handlers: WorkerHandlers,
|
|
246
|
-
) {
|
|
247
|
-
const { resolve, reject } = handlers;
|
|
248
|
-
const cleanup = createCleanup(worker, specName, reject);
|
|
249
|
-
worker.on(
|
|
250
|
-
"message",
|
|
251
|
-
createMessageHandler(specName, cleanup, resolve, reject),
|
|
252
|
-
);
|
|
253
|
-
worker.on("error", createErrorHandler(specName, cleanup, reject));
|
|
254
|
-
worker.on("exit", createExitHandler(specName, cleanup, reject));
|
|
189
|
+
worker.send(message);
|
|
190
|
+
});
|
|
255
191
|
}
|
|
256
192
|
|
|
257
|
-
/**
|
|
258
|
-
function
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
`Message sent to worker in ${getElapsed(createTime, messageTime).toFixed(1)}ms`,
|
|
267
|
-
);
|
|
193
|
+
/** Run matrix variant in-process (no worker isolation) */
|
|
194
|
+
async function runMatrixVariantDirect(
|
|
195
|
+
params: RunMatrixVariantParams,
|
|
196
|
+
name: string,
|
|
197
|
+
): Promise<MeasuredResults[]> {
|
|
198
|
+
const { runner, options } = params;
|
|
199
|
+
const { fn } = await resolveVariantFn(params);
|
|
200
|
+
const benchRunner = await createBenchRunner(runner, options);
|
|
201
|
+
return benchRunner.runBench({ name, fn }, options);
|
|
268
202
|
}
|
|
269
203
|
|
|
270
|
-
/**
|
|
271
|
-
function
|
|
204
|
+
/** Spawn worker process with V8 flags */
|
|
205
|
+
function spawnWorkerProcess(gcStats: boolean) {
|
|
272
206
|
const workerPath = resolveWorkerPath();
|
|
273
207
|
const execArgv = ["--expose-gc", "--allow-natives-syntax"];
|
|
274
208
|
if (gcStats) execArgv.push("--trace-gc-nvp");
|
|
275
209
|
|
|
210
|
+
const env = { ...process.env, NODE_OPTIONS: "" };
|
|
211
|
+
// silent mode captures stdout so we can parse --trace-gc-nvp output
|
|
276
212
|
return fork(workerPath, [], {
|
|
277
213
|
execArgv,
|
|
278
|
-
silent: gcStats,
|
|
279
|
-
env
|
|
280
|
-
|
|
281
|
-
NODE_OPTIONS: "",
|
|
282
|
-
},
|
|
214
|
+
silent: gcStats,
|
|
215
|
+
env,
|
|
216
|
+
serialization: "advanced",
|
|
283
217
|
});
|
|
284
218
|
}
|
|
285
219
|
|
|
286
|
-
/** Capture and parse GC lines from stdout (
|
|
220
|
+
/** Capture and parse GC lines from worker stdout (--trace-gc-nvp). */
|
|
287
221
|
function setupGcCapture(worker: ChildProcess, gcEvents: GcEvent[]): void {
|
|
288
222
|
let buffer = "";
|
|
289
223
|
worker.stdout!.on("data", (data: Buffer) => {
|
|
290
224
|
buffer += data.toString();
|
|
291
225
|
const lines = buffer.split("\n");
|
|
292
|
-
buffer = lines.pop() || "";
|
|
226
|
+
buffer = lines.pop() || "";
|
|
293
227
|
for (const line of lines) {
|
|
294
228
|
const event = parseGcLine(line);
|
|
295
|
-
if (event)
|
|
296
|
-
|
|
297
|
-
} else if (line.trim()) {
|
|
298
|
-
// Forward non-GC stdout to console (worker status messages)
|
|
299
|
-
process.stdout.write(line + "\n");
|
|
300
|
-
}
|
|
229
|
+
if (event) gcEvents.push(event);
|
|
230
|
+
else if (line.trim()) process.stdout.write(line + "\n");
|
|
301
231
|
}
|
|
302
232
|
});
|
|
303
233
|
}
|
|
304
234
|
|
|
305
|
-
/**
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return cleanup;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/** Handle worker messages (results or errors) */
|
|
323
|
-
function createMessageHandler(
|
|
324
|
-
specName: string,
|
|
325
|
-
cleanup: () => void,
|
|
326
|
-
resolve: (results: MeasuredResults[], heapProfile?: HeapProfile) => void,
|
|
327
|
-
reject: (error: Error) => void,
|
|
328
|
-
) {
|
|
329
|
-
return (msg: ResultMessage | ErrorMessage) => {
|
|
330
|
-
cleanup();
|
|
331
|
-
if (msg.type === "result") {
|
|
332
|
-
resolve(msg.results, msg.heapProfile);
|
|
333
|
-
} else if (msg.type === "error") {
|
|
334
|
-
const error = new Error(`Benchmark "${specName}" failed: ${msg.error}`);
|
|
335
|
-
if (msg.stack) error.stack = msg.stack;
|
|
336
|
-
reject(error);
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/** Handle worker process errors */
|
|
342
|
-
function createErrorHandler(
|
|
343
|
-
specName: string,
|
|
344
|
-
cleanup: () => void,
|
|
345
|
-
reject: (error: Error) => void,
|
|
346
|
-
) {
|
|
347
|
-
return (error: Error) => {
|
|
348
|
-
cleanup();
|
|
349
|
-
reject(
|
|
350
|
-
new Error(
|
|
351
|
-
`Worker process failed for benchmark "${specName}": ${error.message}`,
|
|
352
|
-
),
|
|
353
|
-
);
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/** Handle worker process exit */
|
|
358
|
-
function createExitHandler(
|
|
359
|
-
specName: string,
|
|
360
|
-
cleanup: () => void,
|
|
361
|
-
reject: (error: Error) => void,
|
|
362
|
-
) {
|
|
363
|
-
return (code: number | null, _signal: NodeJS.Signals | null) => {
|
|
364
|
-
if (code !== 0 && code !== null) {
|
|
365
|
-
cleanup();
|
|
366
|
-
const msg = `Worker exited with code ${code} for benchmark "${specName}"`;
|
|
367
|
-
reject(new Error(msg));
|
|
368
|
-
}
|
|
235
|
+
/** Attach profiling data collected by the worker to each result. */
|
|
236
|
+
function attachProfilingData(
|
|
237
|
+
results: MeasuredResults[],
|
|
238
|
+
gcEvents: GcEvent[] | undefined,
|
|
239
|
+
heapProfile?: HeapProfile,
|
|
240
|
+
timeProfile?: TimeProfile,
|
|
241
|
+
coverage?: CoverageData,
|
|
242
|
+
): void {
|
|
243
|
+
const gcStats = gcEvents?.length ? aggregateGcStats(gcEvents) : undefined;
|
|
244
|
+
const attach = <K extends keyof MeasuredResults>(
|
|
245
|
+
key: K,
|
|
246
|
+
value: MeasuredResults[K] | undefined,
|
|
247
|
+
) => {
|
|
248
|
+
if (value) for (const r of results) r[key] = value;
|
|
369
249
|
};
|
|
250
|
+
attach("gcStats", gcStats);
|
|
251
|
+
attach("heapProfile", heapProfile);
|
|
252
|
+
attach("timeProfile", timeProfile);
|
|
253
|
+
attach("coverage", coverage);
|
|
370
254
|
}
|
|
371
255
|
|
|
372
256
|
/** Resolve WorkerScript path for dev (.ts) or dist (.mjs) */
|
|
@@ -1,2 +1,76 @@
|
|
|
1
|
+
import { prepareBenchFn } from "../matrix/BenchMatrix.ts";
|
|
2
|
+
import { loadCaseData, loadCasesModule } from "../matrix/CaseLoader.ts";
|
|
3
|
+
import { loadVariant } from "../matrix/VariantLoader.ts";
|
|
4
|
+
import {
|
|
5
|
+
type AdaptiveOptions,
|
|
6
|
+
createAdaptiveWrapper,
|
|
7
|
+
} from "./AdaptiveWrapper.ts";
|
|
8
|
+
import type { BenchmarkFunction } from "./BenchmarkSpec.ts";
|
|
9
|
+
import type { BenchRunner, RunnerOptions } from "./BenchRunner.ts";
|
|
10
|
+
import { createRunner, type KnownRunner } from "./CreateRunner.ts";
|
|
11
|
+
|
|
1
12
|
export const msToNs = 1e6;
|
|
2
|
-
|
|
13
|
+
|
|
14
|
+
/** Get named or default export from module, throw if not a function */
|
|
15
|
+
// biome-ignore lint/complexity/noBannedTypes: generic function constraint
|
|
16
|
+
export function getModuleExport<T extends Function = Function>(
|
|
17
|
+
module: any,
|
|
18
|
+
exportName: string | undefined,
|
|
19
|
+
modulePath: string,
|
|
20
|
+
): T {
|
|
21
|
+
const fn = exportName ? module[exportName] : module.default || module;
|
|
22
|
+
if (typeof fn !== "function") {
|
|
23
|
+
const name = exportName || "default";
|
|
24
|
+
throw new Error(`Export '${name}' from ${modulePath} is not a function`);
|
|
25
|
+
}
|
|
26
|
+
return fn as T;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Import a benchmark function from a module, optionally running a setup export */
|
|
30
|
+
export async function importBenchFn(
|
|
31
|
+
modulePath: string,
|
|
32
|
+
exportName: string | undefined,
|
|
33
|
+
setupExportName: string | undefined,
|
|
34
|
+
params: unknown,
|
|
35
|
+
): Promise<{ fn: BenchmarkFunction; params: unknown }> {
|
|
36
|
+
const module = await import(modulePath);
|
|
37
|
+
const fn = getModuleExport<BenchmarkFunction>(module, exportName, modulePath);
|
|
38
|
+
if (!setupExportName) return { fn, params };
|
|
39
|
+
|
|
40
|
+
const setup = getModuleExport<BenchmarkFunction>(
|
|
41
|
+
module,
|
|
42
|
+
setupExportName,
|
|
43
|
+
modulePath,
|
|
44
|
+
);
|
|
45
|
+
return { fn, params: await setup(params) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Resolve a matrix variant to a benchmark function (shared by orchestrator and worker). */
|
|
49
|
+
export async function resolveVariantFn(params: {
|
|
50
|
+
variantDir: string;
|
|
51
|
+
variantId: string;
|
|
52
|
+
caseId?: string;
|
|
53
|
+
caseData?: unknown;
|
|
54
|
+
casesModule?: string;
|
|
55
|
+
}): Promise<{ fn: BenchmarkFunction; params: undefined }> {
|
|
56
|
+
let { caseData } = params;
|
|
57
|
+
if (params.casesModule && params.caseId) {
|
|
58
|
+
const cases = await loadCasesModule(params.casesModule);
|
|
59
|
+
caseData = (await loadCaseData(cases, params.caseId)).data;
|
|
60
|
+
}
|
|
61
|
+
const variant = await loadVariant(params.variantDir, params.variantId);
|
|
62
|
+
const fn = await prepareBenchFn(variant, caseData);
|
|
63
|
+
return { fn, params: undefined };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Create runner, wrapping with adaptive sampling if options.adaptive is set */
|
|
67
|
+
export async function createBenchRunner(
|
|
68
|
+
runnerName: KnownRunner,
|
|
69
|
+
options: RunnerOptions,
|
|
70
|
+
): Promise<BenchRunner> {
|
|
71
|
+
const base = await createRunner(runnerName);
|
|
72
|
+
if ("adaptive" in options && options.adaptive) {
|
|
73
|
+
return createAdaptiveWrapper(base, options as AdaptiveOptions);
|
|
74
|
+
}
|
|
75
|
+
return base;
|
|
76
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
coefficientOfVariation,
|
|
3
|
+
median,
|
|
4
|
+
medianAbsoluteDeviation,
|
|
5
|
+
percentile,
|
|
6
|
+
} from "../stats/StatisticalUtils.ts";
|
|
7
|
+
import {
|
|
8
|
+
type MeasuredResults,
|
|
9
|
+
type OptStatusInfo,
|
|
10
|
+
optStatusNames,
|
|
11
|
+
} from "./MeasuredResults.ts";
|
|
12
|
+
|
|
13
|
+
/** Compute percentiles, CV, MAD, and outlier rate from timing samples. */
|
|
14
|
+
export function computeStats(samples: number[]): MeasuredResults["time"] {
|
|
15
|
+
let min = Number.POSITIVE_INFINITY;
|
|
16
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
17
|
+
let sum = 0;
|
|
18
|
+
for (const s of samples) {
|
|
19
|
+
if (s < min) min = s;
|
|
20
|
+
if (s > max) max = s;
|
|
21
|
+
sum += s;
|
|
22
|
+
}
|
|
23
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
24
|
+
const pct = (p: number) =>
|
|
25
|
+
sorted[Math.max(0, Math.ceil(sorted.length * p) - 1)];
|
|
26
|
+
return {
|
|
27
|
+
min,
|
|
28
|
+
max,
|
|
29
|
+
avg: sum / samples.length,
|
|
30
|
+
p25: pct(0.25),
|
|
31
|
+
p50: pct(0.5),
|
|
32
|
+
p75: pct(0.75),
|
|
33
|
+
p95: pct(0.95),
|
|
34
|
+
p99: pct(0.99),
|
|
35
|
+
p999: pct(0.999),
|
|
36
|
+
cv: coefficientOfVariation(samples),
|
|
37
|
+
mad: medianAbsoluteDeviation(samples),
|
|
38
|
+
outlierRate: outlierImpactRatio(samples),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Measure outlier impact as proportion of excess time above 1.5*IQR threshold. */
|
|
43
|
+
export function outlierImpactRatio(samples: number[]): number {
|
|
44
|
+
if (samples.length === 0) return 0;
|
|
45
|
+
const med = median(samples);
|
|
46
|
+
const q75 = percentile(samples, 0.75);
|
|
47
|
+
const threshold = med + 1.5 * (q75 - med);
|
|
48
|
+
|
|
49
|
+
let excessTime = 0;
|
|
50
|
+
for (const sample of samples) {
|
|
51
|
+
if (sample > threshold) excessTime += sample - med;
|
|
52
|
+
}
|
|
53
|
+
const total = samples.reduce((a, b) => a + b, 0);
|
|
54
|
+
return total > 0 ? excessTime / total : 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Group samples by V8 optimization tier and count deopts. */
|
|
58
|
+
export function analyzeOptStatus(
|
|
59
|
+
samples: number[],
|
|
60
|
+
statuses: number[],
|
|
61
|
+
): OptStatusInfo | undefined {
|
|
62
|
+
if (statuses.length === 0 || statuses[0] === undefined) return undefined;
|
|
63
|
+
|
|
64
|
+
const byStatus = new Map<number, number[]>();
|
|
65
|
+
let deoptCount = 0;
|
|
66
|
+
for (let i = 0; i < samples.length; i++) {
|
|
67
|
+
const status = statuses[i];
|
|
68
|
+
if (status === undefined) continue;
|
|
69
|
+
if (status & 8) deoptCount++; // deopt flag (bit 3)
|
|
70
|
+
const group = byStatus.get(status);
|
|
71
|
+
if (group) group.push(samples[i]);
|
|
72
|
+
else byStatus.set(status, [samples[i]]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const entries = [...byStatus].map(([status, times]) => {
|
|
76
|
+
const name = optStatusNames[status] || `status=${status}`;
|
|
77
|
+
return [name, { count: times.length, medianMs: median(times) }] as const;
|
|
78
|
+
});
|
|
79
|
+
return { byTier: Object.fromEntries(entries), deoptCount };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** @return runtime gc() function, or a no-op if --expose-gc wasn't passed. */
|
|
83
|
+
export function gcFunction(): () => void {
|
|
84
|
+
const gc = globalThis.gc ?? (globalThis as any).__gc;
|
|
85
|
+
if (gc) return gc;
|
|
86
|
+
console.warn("gc() not available, run node/bun with --expose-gc");
|
|
87
|
+
return () => {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @return function that reads V8 optimization status via %GetOptimizationStatus. */
|
|
91
|
+
export function createOptStatusGetter(): ((fn: unknown) => number) | undefined {
|
|
92
|
+
try {
|
|
93
|
+
// %GetOptimizationStatus returns a bitmask
|
|
94
|
+
const fn = new Function("f", "return %GetOptimizationStatus(f)");
|
|
95
|
+
fn(() => {});
|
|
96
|
+
return fn as (fn: unknown) => number;
|
|
97
|
+
} catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|