benchforge 0.1.8 → 0.1.11
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/README.md +69 -42
- package/dist/{BenchRunner-CSKN9zPy.d.mts → BenchRunner-BzyUfiyB.d.mts} +32 -8
- package/dist/{BrowserHeapSampler-DCeL42RE.mjs → BrowserHeapSampler-B6asLKWQ.mjs} +57 -57
- package/dist/BrowserHeapSampler-B6asLKWQ.mjs.map +1 -0
- package/dist/{GcStats-ByEovUi1.mjs → GcStats-wX7Xyblu.mjs} +15 -15
- package/dist/GcStats-wX7Xyblu.mjs.map +1 -0
- package/dist/HeapSampler-B8dtKHn1.mjs.map +1 -1
- package/dist/{TimingUtils-ClclVQ7E.mjs → TimingUtils-DwOwkc8G.mjs} +225 -225
- package/dist/TimingUtils-DwOwkc8G.mjs.map +1 -0
- package/dist/bin/benchforge.mjs +1 -1
- package/dist/browser/index.js +210 -210
- package/dist/index.d.mts +106 -48
- package/dist/index.mjs +3 -3
- package/dist/runners/WorkerScript.d.mts +1 -1
- package/dist/runners/WorkerScript.mjs +66 -66
- package/dist/runners/WorkerScript.mjs.map +1 -1
- package/dist/{src-HfimYuW_.mjs → src-B-DDaCa9.mjs} +1250 -991
- package/dist/src-B-DDaCa9.mjs.map +1 -0
- package/package.json +4 -3
- package/src/BenchMatrix.ts +125 -125
- package/src/BenchmarkReport.ts +50 -45
- package/src/HtmlDataPrep.ts +21 -21
- package/src/PermutationTest.ts +24 -24
- package/src/StandardSections.ts +45 -45
- package/src/StatisticalUtils.ts +60 -61
- package/src/browser/BrowserGcStats.ts +5 -5
- package/src/browser/BrowserHeapSampler.ts +63 -63
- package/src/cli/CliArgs.ts +20 -6
- package/src/cli/FilterBenchmarks.ts +5 -5
- package/src/cli/RunBenchCLI.ts +533 -476
- package/src/export/JsonExport.ts +10 -10
- package/src/export/PerfettoExport.ts +74 -74
- package/src/export/SpeedscopeExport.ts +202 -0
- package/src/heap-sample/HeapSampleReport.ts +143 -70
- package/src/heap-sample/HeapSampler.ts +55 -12
- package/src/heap-sample/ResolvedProfile.ts +89 -0
- package/src/html/HtmlReport.ts +33 -33
- package/src/html/HtmlTemplate.ts +67 -67
- package/src/html/browser/CIPlot.ts +50 -50
- package/src/html/browser/HistogramKde.ts +13 -13
- package/src/html/browser/LegendUtils.ts +48 -48
- package/src/html/browser/RenderPlots.ts +98 -98
- package/src/html/browser/SampleTimeSeries.ts +79 -79
- package/src/index.ts +6 -0
- package/src/matrix/MatrixFilter.ts +6 -6
- package/src/matrix/MatrixReport.ts +96 -96
- package/src/matrix/VariantLoader.ts +5 -5
- package/src/runners/AdaptiveWrapper.ts +151 -151
- package/src/runners/BasicRunner.ts +175 -175
- package/src/runners/BenchRunner.ts +8 -8
- package/src/runners/GcStats.ts +22 -22
- package/src/runners/RunnerOrchestrator.ts +168 -168
- package/src/runners/WorkerScript.ts +96 -96
- package/src/table-util/Formatters.ts +41 -36
- package/src/table-util/TableReport.ts +122 -122
- package/src/table-util/test/TableValueExtractor.ts +9 -9
- package/src/test/AdaptiveStatistics.integration.ts +7 -39
- package/src/test/HeapAttribution.test.ts +51 -0
- package/src/test/RunBenchCLI.test.ts +36 -11
- package/src/test/TestUtils.ts +24 -24
- package/src/test/fixtures/fn-export-bench.ts +3 -0
- package/src/test/fixtures/suite-export-bench.ts +16 -0
- package/src/tests/BenchMatrix.test.ts +12 -12
- package/src/tests/MatrixFilter.test.ts +15 -15
- package/dist/BrowserHeapSampler-DCeL42RE.mjs.map +0 -1
- package/dist/GcStats-ByEovUi1.mjs.map +0 -1
- package/dist/TimingUtils-ClclVQ7E.mjs.map +0 -1
- package/dist/src-HfimYuW_.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as MeasuredResults, i as BenchmarkSpec, n as BenchGroup, r as BenchSuite, t as RunnerOptions } from "./BenchRunner-
|
|
1
|
+
import { a as MeasuredResults, i as BenchmarkSpec, n as BenchGroup, o as HeapProfile, r as BenchSuite, t as RunnerOptions } from "./BenchRunner-BzyUfiyB.mjs";
|
|
2
2
|
import { Alignment } from "table";
|
|
3
3
|
import { Argv, InferredOptionTypes } from "yargs";
|
|
4
4
|
import * as node_http0 from "node:http";
|
|
@@ -59,8 +59,6 @@ interface MatrixResults {
|
|
|
59
59
|
name: string;
|
|
60
60
|
variants: VariantResult[];
|
|
61
61
|
}
|
|
62
|
-
/** @return true if variant is a StatefulVariant (has setup + run) */
|
|
63
|
-
declare function isStatefulVariant<T, S>(v: Variant<T, S>): v is StatefulVariant<T, S>;
|
|
64
62
|
/** Options for runMatrix */
|
|
65
63
|
interface RunMatrixOptions {
|
|
66
64
|
iterations?: number;
|
|
@@ -81,6 +79,8 @@ interface RunMatrixOptions {
|
|
|
81
79
|
heapInterval?: number;
|
|
82
80
|
heapDepth?: number;
|
|
83
81
|
}
|
|
82
|
+
/** @return true if variant is a StatefulVariant (has setup + run) */
|
|
83
|
+
declare function isStatefulVariant<T, S>(v: Variant<T, S>): v is StatefulVariant<T, S>;
|
|
84
84
|
/** Run a BenchMatrix with inline variants or variantDir */
|
|
85
85
|
declare function runMatrix<T>(matrix: BenchMatrix<T>, options?: RunMatrixOptions): Promise<MatrixResults>;
|
|
86
86
|
//#endregion
|
|
@@ -104,7 +104,7 @@ interface ColumnFormat<T> {
|
|
|
104
104
|
alignment?: Alignment;
|
|
105
105
|
width?: number;
|
|
106
106
|
}
|
|
107
|
-
/**
|
|
107
|
+
/** Build formatted table with column groups and baselines */
|
|
108
108
|
//#endregion
|
|
109
109
|
//#region src/BenchmarkReport.d.ts
|
|
110
110
|
/** Benchmark results with optional baseline for comparison */
|
|
@@ -138,8 +138,10 @@ declare function reportResults<S extends ReadonlyArray<ResultsMapper<any>>>(grou
|
|
|
138
138
|
//#endregion
|
|
139
139
|
//#region src/cli/CliArgs.d.ts
|
|
140
140
|
type Configure<T> = (yargs: Argv) => Argv<T>;
|
|
141
|
-
/** CLI args type inferred from cliOptions */
|
|
142
|
-
type DefaultCliArgs = InferredOptionTypes<typeof cliOptions
|
|
141
|
+
/** CLI args type inferred from cliOptions, plus optional file positional */
|
|
142
|
+
type DefaultCliArgs = InferredOptionTypes<typeof cliOptions> & {
|
|
143
|
+
file?: string;
|
|
144
|
+
};
|
|
143
145
|
declare const cliOptions: {
|
|
144
146
|
readonly time: {
|
|
145
147
|
readonly type: "number";
|
|
@@ -217,11 +219,21 @@ declare const cliOptions: {
|
|
|
217
219
|
readonly requiresArg: true;
|
|
218
220
|
readonly describe: "export benchmark data to JSON file";
|
|
219
221
|
};
|
|
220
|
-
readonly perfetto: {
|
|
222
|
+
readonly "export-perfetto": {
|
|
221
223
|
readonly type: "string";
|
|
222
224
|
readonly requiresArg: true;
|
|
223
225
|
readonly describe: "export Perfetto trace file (view at ui.perfetto.dev)";
|
|
224
226
|
};
|
|
227
|
+
readonly speedscope: {
|
|
228
|
+
readonly type: "boolean";
|
|
229
|
+
readonly default: false;
|
|
230
|
+
readonly describe: "open heap profile in speedscope (via npx)";
|
|
231
|
+
};
|
|
232
|
+
readonly "export-speedscope": {
|
|
233
|
+
readonly type: "string";
|
|
234
|
+
readonly requiresArg: true;
|
|
235
|
+
readonly describe: "export heap profile as speedscope JSON";
|
|
236
|
+
};
|
|
225
237
|
readonly "trace-opt": {
|
|
226
238
|
readonly type: "boolean";
|
|
227
239
|
readonly default: false;
|
|
@@ -286,6 +298,11 @@ declare const cliOptions: {
|
|
|
286
298
|
readonly default: false;
|
|
287
299
|
readonly describe: "verbose output with file:// paths and line numbers";
|
|
288
300
|
};
|
|
301
|
+
readonly "heap-raw": {
|
|
302
|
+
readonly type: "boolean";
|
|
303
|
+
readonly default: false;
|
|
304
|
+
readonly describe: "dump every raw heap sample (ordinal, size, stack)";
|
|
305
|
+
};
|
|
289
306
|
readonly "heap-user-only": {
|
|
290
307
|
readonly type: "boolean";
|
|
291
308
|
readonly default: false;
|
|
@@ -422,13 +439,14 @@ interface CallFrame {
|
|
|
422
439
|
fn: string;
|
|
423
440
|
url: string;
|
|
424
441
|
line: number;
|
|
425
|
-
col
|
|
442
|
+
col?: number;
|
|
426
443
|
}
|
|
427
444
|
type UserCodeFilter = (site: CallFrame) => boolean;
|
|
428
445
|
interface HeapReportOptions {
|
|
429
446
|
topN: number;
|
|
430
447
|
stackDepth?: number;
|
|
431
448
|
verbose?: boolean;
|
|
449
|
+
raw?: boolean;
|
|
432
450
|
userOnly?: boolean;
|
|
433
451
|
isUserCode?: UserCodeFilter;
|
|
434
452
|
totalAll?: number;
|
|
@@ -451,16 +469,29 @@ interface MatrixReportOptions {
|
|
|
451
469
|
sections?: ResultsMapper[];
|
|
452
470
|
variantTitle?: string;
|
|
453
471
|
}
|
|
454
|
-
/** Format matrix results as one table per case */
|
|
455
|
-
declare function reportMatrixResults(results: MatrixResults, options?: MatrixReportOptions): string;
|
|
456
472
|
/** GC statistics columns - derived from gcStatsSection for consistency */
|
|
457
473
|
declare const gcStatsColumns: ExtraColumn[];
|
|
458
474
|
/** GC pause time column */
|
|
459
475
|
declare const gcPauseColumn: ExtraColumn;
|
|
460
476
|
/** Heap sampling total bytes column */
|
|
461
477
|
declare const heapTotalColumn: ExtraColumn;
|
|
478
|
+
/** Format matrix results as one table per case */
|
|
479
|
+
declare function reportMatrixResults(results: MatrixResults, options?: MatrixReportOptions): string;
|
|
462
480
|
//#endregion
|
|
463
481
|
//#region src/cli/RunBenchCLI.d.ts
|
|
482
|
+
interface ExportOptions {
|
|
483
|
+
results: ReportGroup[];
|
|
484
|
+
args: DefaultCliArgs;
|
|
485
|
+
sections?: any[];
|
|
486
|
+
suiteName?: string;
|
|
487
|
+
currentVersion?: GitVersion;
|
|
488
|
+
baselineVersion?: GitVersion;
|
|
489
|
+
}
|
|
490
|
+
interface MatrixExportOptions {
|
|
491
|
+
sections?: any[];
|
|
492
|
+
currentVersion?: GitVersion;
|
|
493
|
+
baselineVersion?: GitVersion;
|
|
494
|
+
}
|
|
464
495
|
/** Parse CLI with custom configuration */
|
|
465
496
|
declare function parseBenchArgs<T = DefaultCliArgs>(configureArgs?: Configure<T>): T & DefaultCliArgs;
|
|
466
497
|
/** Run suite with CLI arguments */
|
|
@@ -477,14 +508,6 @@ declare function runDefaultBench(suite?: BenchSuite, configureArgs?: Configure<a
|
|
|
477
508
|
declare function reportOptStatus(groups: ReportGroup[]): void;
|
|
478
509
|
/** @return true if any result has the specified field with a defined value */
|
|
479
510
|
declare function hasField(results: ReportGroup[], field: keyof MeasuredResults): boolean;
|
|
480
|
-
interface ExportOptions {
|
|
481
|
-
results: ReportGroup[];
|
|
482
|
-
args: DefaultCliArgs;
|
|
483
|
-
sections?: any[];
|
|
484
|
-
suiteName?: string;
|
|
485
|
-
currentVersion?: GitVersion;
|
|
486
|
-
baselineVersion?: GitVersion;
|
|
487
|
-
}
|
|
488
511
|
/** Export reports (HTML, JSON, Perfetto) based on CLI args */
|
|
489
512
|
declare function exportReports(options: ExportOptions): Promise<void>;
|
|
490
513
|
/** Run matrix suite with CLI arguments.
|
|
@@ -499,11 +522,6 @@ declare function defaultMatrixReport(results: MatrixResults[], reportOptions?: M
|
|
|
499
522
|
declare function runDefaultMatrixBench(suite: MatrixSuite, configureArgs?: Configure<any>, reportOptions?: MatrixReportOptions): Promise<void>;
|
|
500
523
|
/** Convert MatrixResults to ReportGroup[] for export compatibility */
|
|
501
524
|
declare function matrixToReportGroups(results: MatrixResults[]): ReportGroup[];
|
|
502
|
-
interface MatrixExportOptions {
|
|
503
|
-
sections?: any[];
|
|
504
|
-
currentVersion?: GitVersion;
|
|
505
|
-
baselineVersion?: GitVersion;
|
|
506
|
-
}
|
|
507
525
|
/** Run matrix benchmarks, display table, and generate exports */
|
|
508
526
|
declare function matrixBenchExports(suite: MatrixSuite, args: DefaultCliArgs, reportOptions?: MatrixReportOptions, exportOptions?: MatrixExportOptions): Promise<void>;
|
|
509
527
|
//#endregion
|
|
@@ -593,6 +611,43 @@ interface BenchmarkResult {
|
|
|
593
611
|
/** Export benchmark results to Perfetto-compatible trace file */
|
|
594
612
|
declare function exportPerfettoTrace(groups: ReportGroup[], outputPath: string, args: DefaultCliArgs): void;
|
|
595
613
|
//#endregion
|
|
614
|
+
//#region src/export/SpeedscopeExport.d.ts
|
|
615
|
+
/** speedscope file format (https://www.speedscope.app/file-format-schema.json) */
|
|
616
|
+
interface SpeedscopeFile {
|
|
617
|
+
$schema: "https://www.speedscope.app/file-format-schema.json";
|
|
618
|
+
shared: {
|
|
619
|
+
frames: SpeedscopeFrame[];
|
|
620
|
+
};
|
|
621
|
+
profiles: SpeedscopeProfile[];
|
|
622
|
+
name?: string;
|
|
623
|
+
exporter?: string;
|
|
624
|
+
}
|
|
625
|
+
interface SpeedscopeFrame {
|
|
626
|
+
name: string;
|
|
627
|
+
file?: string;
|
|
628
|
+
line?: number;
|
|
629
|
+
col?: number;
|
|
630
|
+
}
|
|
631
|
+
interface SpeedscopeProfile {
|
|
632
|
+
type: "sampled";
|
|
633
|
+
name: string;
|
|
634
|
+
unit: "bytes";
|
|
635
|
+
startValue: number;
|
|
636
|
+
endValue: number;
|
|
637
|
+
samples: number[][];
|
|
638
|
+
weights: number[];
|
|
639
|
+
}
|
|
640
|
+
/** Export heap profiles from benchmark results to speedscope JSON format.
|
|
641
|
+
* Creates one speedscope profile per benchmark that has a heapProfile.
|
|
642
|
+
* @returns resolved output path, or undefined if no profiles were found */
|
|
643
|
+
declare function exportSpeedscope(groups: ReportGroup[], outputPath: string): string | undefined;
|
|
644
|
+
/** Export to a temp file and open in speedscope via npx */
|
|
645
|
+
declare function exportAndLaunchSpeedscope(groups: ReportGroup[]): void;
|
|
646
|
+
/** Launch speedscope viewer on a file via npx */
|
|
647
|
+
declare function launchSpeedscope(filePath: string): void;
|
|
648
|
+
/** Convert a single HeapProfile to speedscope format (for standalone use) */
|
|
649
|
+
declare function heapProfileToSpeedscope(name: string, profile: HeapProfile): SpeedscopeFile;
|
|
650
|
+
//#endregion
|
|
596
651
|
//#region src/HtmlDataPrep.d.ts
|
|
597
652
|
interface PrepareHtmlOptions {
|
|
598
653
|
cliArgs?: Record<string, unknown>;
|
|
@@ -622,13 +677,13 @@ interface MatrixFilter {
|
|
|
622
677
|
case?: string;
|
|
623
678
|
variant?: string;
|
|
624
679
|
}
|
|
625
|
-
/** Parse filter string: "case/variant", "case/", "/variant", or "case" */
|
|
626
|
-
declare function parseMatrixFilter(filter: string): MatrixFilter;
|
|
627
680
|
/** Filtered matrix with explicit case and variant lists */
|
|
628
681
|
interface FilteredMatrix<T = unknown> extends BenchMatrix<T> {
|
|
629
682
|
filteredCases?: string[];
|
|
630
683
|
filteredVariants?: string[];
|
|
631
684
|
}
|
|
685
|
+
/** Parse filter string: "case/variant", "case/", "/variant", or "case" */
|
|
686
|
+
declare function parseMatrixFilter(filter: string): MatrixFilter;
|
|
632
687
|
/** Apply filter to a matrix, merging with existing filters via intersection */
|
|
633
688
|
declare function filterMatrix<T>(matrix: FilteredMatrix<T>, filter?: MatrixFilter): Promise<FilteredMatrix<T>>;
|
|
634
689
|
//#endregion
|
|
@@ -638,13 +693,9 @@ interface TimeStats {
|
|
|
638
693
|
p50?: number;
|
|
639
694
|
p99?: number;
|
|
640
695
|
}
|
|
641
|
-
/** Section: mean, p50, p99 timing */
|
|
642
|
-
declare const timeSection: ResultsMapper<TimeStats>;
|
|
643
696
|
interface GcSectionStats {
|
|
644
697
|
gc?: number;
|
|
645
698
|
}
|
|
646
|
-
/** Section: GC time as fraction of total benchmark time (Node performance hooks) */
|
|
647
|
-
declare const gcSection: ResultsMapper<GcSectionStats>;
|
|
648
699
|
interface GcStatsInfo {
|
|
649
700
|
allocPerIter?: number;
|
|
650
701
|
collected?: number;
|
|
@@ -653,42 +704,46 @@ interface GcStatsInfo {
|
|
|
653
704
|
promoPercent?: number;
|
|
654
705
|
pausePerIter?: number;
|
|
655
706
|
}
|
|
656
|
-
/** Section: detailed GC stats from --trace-gc-nvp (allocation, promotion, pauses) */
|
|
657
|
-
declare const gcStatsSection: ResultsMapper<GcStatsInfo>;
|
|
658
707
|
interface CpuStats {
|
|
659
708
|
cpuCacheMiss?: number;
|
|
660
709
|
cpuStall?: number;
|
|
661
710
|
}
|
|
662
|
-
/** Section: CPU L1 cache miss rate and stall rate (requires @mitata/counters) */
|
|
663
|
-
declare const cpuSection: ResultsMapper<CpuStats>;
|
|
664
711
|
interface RunStats {
|
|
665
712
|
runs?: number;
|
|
666
713
|
}
|
|
667
|
-
/** Section: number of sample iterations */
|
|
668
|
-
declare const runsSection: ResultsMapper<RunStats>;
|
|
669
|
-
/** Section: total sampling duration in seconds (brackets if >= 30s) */
|
|
670
|
-
declare const totalTimeSection: ResultsMapper<{
|
|
671
|
-
totalTime?: number;
|
|
672
|
-
}>;
|
|
673
714
|
interface AdaptiveStats {
|
|
674
715
|
median?: number;
|
|
675
716
|
mean?: number;
|
|
676
717
|
p99?: number;
|
|
677
718
|
convergence?: number;
|
|
678
719
|
}
|
|
720
|
+
interface OptStats {
|
|
721
|
+
tiers?: string;
|
|
722
|
+
deopt?: number;
|
|
723
|
+
}
|
|
724
|
+
/** Section: mean, p50, p99 timing */
|
|
725
|
+
declare const timeSection: ResultsMapper<TimeStats>;
|
|
726
|
+
/** Section: GC time as fraction of total benchmark time (Node performance hooks) */
|
|
727
|
+
declare const gcSection: ResultsMapper<GcSectionStats>;
|
|
728
|
+
/** Section: detailed GC stats from --trace-gc-nvp (allocation, promotion, pauses) */
|
|
729
|
+
declare const gcStatsSection: ResultsMapper<GcStatsInfo>;
|
|
730
|
+
/** Section: CPU L1 cache miss rate and stall rate (requires @mitata/counters) */
|
|
731
|
+
declare const cpuSection: ResultsMapper<CpuStats>;
|
|
732
|
+
/** Section: number of sample iterations */
|
|
733
|
+
declare const runsSection: ResultsMapper<RunStats>;
|
|
734
|
+
/** Section: total sampling duration in seconds (brackets if >= 30s) */
|
|
735
|
+
declare const totalTimeSection: ResultsMapper<{
|
|
736
|
+
totalTime?: number;
|
|
737
|
+
}>;
|
|
679
738
|
/** Section: median, mean, p99, and convergence for adaptive mode */
|
|
680
739
|
declare const adaptiveSection: ResultsMapper<AdaptiveStats>;
|
|
740
|
+
/** Section: V8 optimization tier distribution and deopt count */
|
|
741
|
+
declare const optSection: ResultsMapper<OptStats>;
|
|
681
742
|
/** Build generic sections based on CLI flags */
|
|
682
743
|
declare function buildGenericSections(args: {
|
|
683
744
|
"gc-stats"?: boolean;
|
|
684
745
|
"heap-sample"?: boolean;
|
|
685
746
|
}): ResultsMapper[];
|
|
686
|
-
interface OptStats {
|
|
687
|
-
tiers?: string;
|
|
688
|
-
deopt?: number;
|
|
689
|
-
}
|
|
690
|
-
/** Section: V8 optimization tier distribution and deopt count */
|
|
691
|
-
declare const optSection: ResultsMapper<OptStats>;
|
|
692
747
|
//#endregion
|
|
693
748
|
//#region src/StatisticalUtils.d.ts
|
|
694
749
|
/** @return mean of values */
|
|
@@ -703,10 +758,13 @@ declare function formatConvergence(v: unknown): string;
|
|
|
703
758
|
declare function timeMs(ms: unknown): string | null;
|
|
704
759
|
/** Format integer with thousand separators */
|
|
705
760
|
declare function integer(x: unknown): string | null;
|
|
706
|
-
/** Format bytes with appropriate units (B, KB, MB, GB)
|
|
707
|
-
|
|
761
|
+
/** Format bytes with appropriate units (B, KB, MB, GB).
|
|
762
|
+
* Use `space: true` for human-readable console output (`1.5 KB`). */
|
|
763
|
+
declare function formatBytes(bytes: unknown, opts?: {
|
|
764
|
+
space?: boolean;
|
|
765
|
+
}): string | null;
|
|
708
766
|
/** @return truncated string with ellipsis if over maxLen */
|
|
709
767
|
declare function truncate(str: string, maxLen?: number): string;
|
|
710
768
|
//#endregion
|
|
711
|
-
export { type AnyVariant, type BenchGroup, type BenchMatrix, type BenchSuite, BenchmarkGroup, BenchmarkJsonData, type BenchmarkReport, BenchmarkResult, type BenchmarkSpec, BenchmarkSuite, type CaseResult, type CasesModule, type Configure, type DefaultCliArgs, type ExportOptions, type ExtraColumn, type FilteredMatrix, type GitVersion, type HtmlReportOptions, type LoadedCase, type MatrixDefaults, type MatrixExportOptions, type MatrixFilter, type MatrixReportOptions, type MatrixResults, type MatrixSuite, type MeasuredResults, type PrepareHtmlOptions, type ReportColumnGroup, type ReportData, type ReportGroup, type ResultsMapper, type RunMatrixOptions, type RunnerOptions, type StatefulVariant, type UnknownRecord, type Variant, type VariantFn, type VariantResult, adaptiveSection, average, benchExports, buildGenericSections, cliToMatrixOptions, cpuSection, defaultCliArgs, defaultMatrixReport, defaultReport, exportPerfettoTrace, exportReports, filterMatrix, formatBytes, formatConvergence, formatDateWithTimezone, formatGitVersion, gcPauseColumn, gcSection, gcStatsColumns, gcStatsSection, generateHtmlReport, getBaselineVersion, getCurrentGitVersion, hasField, heapTotalColumn, integer, isStatefulVariant, loadCaseData, loadCasesModule, matrixBenchExports, matrixToReportGroups, optSection, parseBenchArgs, parseCliArgs, parseMatrixFilter, prepareHtmlData, printHeapReports, reportMatrixResults, reportOptStatus, reportResults, runBenchmarks, runDefaultBench, runDefaultMatrixBench, runMatrix, runMatrixSuite, runsSection, timeMs, timeSection, totalTimeSection, truncate };
|
|
769
|
+
export { type AnyVariant, type BenchGroup, type BenchMatrix, type BenchSuite, BenchmarkGroup, BenchmarkJsonData, type BenchmarkReport, BenchmarkResult, type BenchmarkSpec, BenchmarkSuite, type CaseResult, type CasesModule, type Configure, type DefaultCliArgs, type ExportOptions, type ExtraColumn, type FilteredMatrix, type GitVersion, type HtmlReportOptions, type LoadedCase, type MatrixDefaults, type MatrixExportOptions, type MatrixFilter, type MatrixReportOptions, type MatrixResults, type MatrixSuite, type MeasuredResults, type PrepareHtmlOptions, type ReportColumnGroup, type ReportData, type ReportGroup, type ResultsMapper, type RunMatrixOptions, type RunnerOptions, type StatefulVariant, type UnknownRecord, type Variant, type VariantFn, type VariantResult, adaptiveSection, average, benchExports, buildGenericSections, cliToMatrixOptions, cpuSection, defaultCliArgs, defaultMatrixReport, defaultReport, exportAndLaunchSpeedscope, exportPerfettoTrace, exportReports, exportSpeedscope, filterMatrix, formatBytes, formatConvergence, formatDateWithTimezone, formatGitVersion, gcPauseColumn, gcSection, gcStatsColumns, gcStatsSection, generateHtmlReport, getBaselineVersion, getCurrentGitVersion, hasField, heapProfileToSpeedscope, heapTotalColumn, integer, isStatefulVariant, launchSpeedscope, loadCaseData, loadCasesModule, matrixBenchExports, matrixToReportGroups, optSection, parseBenchArgs, parseCliArgs, parseMatrixFilter, prepareHtmlData, printHeapReports, reportMatrixResults, reportOptStatus, reportResults, runBenchmarks, runDefaultBench, runDefaultMatrixBench, runMatrix, runMatrixSuite, runsSection, timeMs, timeSection, totalTimeSection, truncate };
|
|
712
770
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as timeSection, B as
|
|
2
|
-
import { o as average } from "./TimingUtils-
|
|
1
|
+
import { $ as loadCasesModule, A as timeSection, B as heapProfileToSpeedscope, C as adaptiveSection, D as gcStatsSection, E as gcSection, F as generateHtmlReport, G as reportResults, H as exportPerfettoTrace, I as formatDateWithTimezone, J as timeMs, K as formatBytes, L as prepareHtmlData, M as formatConvergence, N as filterMatrix, O as optSection, P as parseMatrixFilter, Q as loadCaseData, R as exportAndLaunchSpeedscope, S as reportMatrixResults, T as cpuSection, U as defaultCliArgs, V as launchSpeedscope, W as parseCliArgs, X as isStatefulVariant, Y as truncate, Z as runMatrix, _ as runDefaultMatrixBench, a as cliToMatrixOptions, b as gcStatsColumns, c as exportReports, d as matrixToReportGroups, f as parseBenchArgs, g as runDefaultBench, h as runBenchmarks, i as benchExports, j as totalTimeSection, k as runsSection, l as hasField, m as reportOptStatus, n as getBaselineVersion, o as defaultMatrixReport, p as printHeapReports, q as integer, r as getCurrentGitVersion, s as defaultReport, t as formatGitVersion, u as matrixBenchExports, v as runMatrixSuite, w as buildGenericSections, x as heapTotalColumn, y as gcPauseColumn, z as exportSpeedscope } from "./src-B-DDaCa9.mjs";
|
|
2
|
+
import { o as average } from "./TimingUtils-DwOwkc8G.mjs";
|
|
3
3
|
|
|
4
|
-
export { adaptiveSection, average, benchExports, buildGenericSections, cliToMatrixOptions, cpuSection, defaultCliArgs, defaultMatrixReport, defaultReport, exportPerfettoTrace, exportReports, filterMatrix, formatBytes, formatConvergence, formatDateWithTimezone, formatGitVersion, gcPauseColumn, gcSection, gcStatsColumns, gcStatsSection, generateHtmlReport, getBaselineVersion, getCurrentGitVersion, hasField, heapTotalColumn, integer, isStatefulVariant, loadCaseData, loadCasesModule, matrixBenchExports, matrixToReportGroups, optSection, parseBenchArgs, parseCliArgs, parseMatrixFilter, prepareHtmlData, printHeapReports, reportMatrixResults, reportOptStatus, reportResults, runBenchmarks, runDefaultBench, runDefaultMatrixBench, runMatrix, runMatrixSuite, runsSection, timeMs, timeSection, totalTimeSection, truncate };
|
|
4
|
+
export { adaptiveSection, average, benchExports, buildGenericSections, cliToMatrixOptions, cpuSection, defaultCliArgs, defaultMatrixReport, defaultReport, exportAndLaunchSpeedscope, exportPerfettoTrace, exportReports, exportSpeedscope, filterMatrix, formatBytes, formatConvergence, formatDateWithTimezone, formatGitVersion, gcPauseColumn, gcSection, gcStatsColumns, gcStatsSection, generateHtmlReport, getBaselineVersion, getCurrentGitVersion, hasField, heapProfileToSpeedscope, heapTotalColumn, integer, isStatefulVariant, launchSpeedscope, loadCaseData, loadCasesModule, matrixBenchExports, matrixToReportGroups, optSection, parseBenchArgs, parseCliArgs, parseMatrixFilter, prepareHtmlData, printHeapReports, reportMatrixResults, reportOptStatus, reportResults, runBenchmarks, runDefaultBench, runDefaultMatrixBench, runMatrix, runMatrixSuite, runsSection, timeMs, timeSection, totalTimeSection, truncate };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as MeasuredResults, i as BenchmarkSpec, o as HeapProfile, t as RunnerOptions } from "../BenchRunner-
|
|
1
|
+
import { a as MeasuredResults, i as BenchmarkSpec, o as HeapProfile, t as RunnerOptions } from "../BenchRunner-BzyUfiyB.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runners/CreateRunner.d.ts
|
|
4
4
|
type KnownRunner = "basic";
|
|
@@ -1,61 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as createAdaptiveWrapper, d as variantModuleUrl, i as createRunner, n as getElapsed, r as getPerfNow, t as debugWorkerTiming } from "../TimingUtils-
|
|
2
|
+
import { a as createAdaptiveWrapper, d as variantModuleUrl, i as createRunner, n as getElapsed, r as getPerfNow, t as debugWorkerTiming } from "../TimingUtils-DwOwkc8G.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/runners/WorkerScript.ts
|
|
5
5
|
const workerStartTime = getPerfNow();
|
|
6
6
|
const maxLifetime = 300 * 1e3;
|
|
7
|
-
/**
|
|
8
|
-
* Worker process for isolated benchmark execution.
|
|
9
|
-
* Uses eval() safely in isolated child process with trusted code.
|
|
10
|
-
*/
|
|
11
|
-
process.on("message", async (message) => {
|
|
12
|
-
if (message.type !== "run") return;
|
|
13
|
-
logTiming(`Processing ${message.spec.name} with ${message.runnerName}`);
|
|
14
|
-
try {
|
|
15
|
-
const start = getPerfNow();
|
|
16
|
-
const baseRunner = await createRunner(message.runnerName);
|
|
17
|
-
const runner = message.options.adaptive ? createAdaptiveWrapper(baseRunner, message.options) : baseRunner;
|
|
18
|
-
logTiming("Runner created in", getElapsed(start));
|
|
19
|
-
const benchStart = getPerfNow();
|
|
20
|
-
if (message.options.heapSample) {
|
|
21
|
-
const { withHeapSampling } = await import("../HeapSampler-B8dtKHn1.mjs");
|
|
22
|
-
const { result: results, profile: heapProfile } = await withHeapSampling({
|
|
23
|
-
samplingInterval: message.options.heapInterval,
|
|
24
|
-
stackDepth: message.options.heapDepth
|
|
25
|
-
}, async () => {
|
|
26
|
-
const { fn, params } = await resolveBenchmarkFn(message);
|
|
27
|
-
return runner.runBench({
|
|
28
|
-
...message.spec,
|
|
29
|
-
fn
|
|
30
|
-
}, message.options, params);
|
|
31
|
-
});
|
|
32
|
-
logTiming("Benchmark execution took", getElapsed(benchStart));
|
|
33
|
-
sendAndExit({
|
|
34
|
-
type: "result",
|
|
35
|
-
results,
|
|
36
|
-
heapProfile
|
|
37
|
-
}, 0);
|
|
38
|
-
} else {
|
|
39
|
-
const { fn, params } = await resolveBenchmarkFn(message);
|
|
40
|
-
const results = await runner.runBench({
|
|
41
|
-
...message.spec,
|
|
42
|
-
fn
|
|
43
|
-
}, message.options, params);
|
|
44
|
-
logTiming("Benchmark execution took", getElapsed(benchStart));
|
|
45
|
-
sendAndExit({
|
|
46
|
-
type: "result",
|
|
47
|
-
results
|
|
48
|
-
}, 0);
|
|
49
|
-
}
|
|
50
|
-
} catch (error) {
|
|
51
|
-
sendAndExit(createErrorMessage(error), 1);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
setTimeout(() => {
|
|
55
|
-
console.error("WorkerScript: Maximum lifetime exceeded, exiting");
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}, maxLifetime);
|
|
58
|
-
process.stdin.pause();
|
|
59
7
|
/** Log timing with consistent format */
|
|
60
8
|
const logTiming = debugWorkerTiming ? _logTiming : () => {};
|
|
61
9
|
function _logTiming(operation, duration) {
|
|
@@ -104,13 +52,6 @@ async function importVariantModule(message) {
|
|
|
104
52
|
params: void 0
|
|
105
53
|
};
|
|
106
54
|
}
|
|
107
|
-
/** Load case data from a cases module */
|
|
108
|
-
async function loadCaseFromModule(casesModuleUrl, caseId) {
|
|
109
|
-
logTiming(`Loading case '${caseId}' from ${casesModuleUrl}`);
|
|
110
|
-
const module = await import(casesModuleUrl);
|
|
111
|
-
if (typeof module.loadCase === "function") return module.loadCase(caseId);
|
|
112
|
-
return { data: caseId };
|
|
113
|
-
}
|
|
114
55
|
/** Import benchmark function and optionally run setup */
|
|
115
56
|
async function importBenchmarkWithSetup(message) {
|
|
116
57
|
const { modulePath, exportName, setupExportName, params } = message;
|
|
@@ -129,6 +70,19 @@ async function importBenchmarkWithSetup(message) {
|
|
|
129
70
|
params
|
|
130
71
|
};
|
|
131
72
|
}
|
|
73
|
+
/** Reconstruct function from string code */
|
|
74
|
+
function reconstructFunction(fnCode) {
|
|
75
|
+
const fn = eval(`(${fnCode})`);
|
|
76
|
+
if (typeof fn !== "function") throw new Error("Reconstructed code is not a function");
|
|
77
|
+
return fn;
|
|
78
|
+
}
|
|
79
|
+
/** Load case data from a cases module */
|
|
80
|
+
async function loadCaseFromModule(casesModuleUrl, caseId) {
|
|
81
|
+
logTiming(`Loading case '${caseId}' from ${casesModuleUrl}`);
|
|
82
|
+
const module = await import(casesModuleUrl);
|
|
83
|
+
if (typeof module.loadCase === "function") return module.loadCase(caseId);
|
|
84
|
+
return { data: caseId };
|
|
85
|
+
}
|
|
132
86
|
/** Get named or default export from module */
|
|
133
87
|
function getModuleExport(module, exportName, modulePath) {
|
|
134
88
|
const fn = exportName ? module[exportName] : module.default || module;
|
|
@@ -138,12 +92,6 @@ function getModuleExport(module, exportName, modulePath) {
|
|
|
138
92
|
}
|
|
139
93
|
return fn;
|
|
140
94
|
}
|
|
141
|
-
/** Reconstruct function from string code */
|
|
142
|
-
function reconstructFunction(fnCode) {
|
|
143
|
-
const fn = eval(`(${fnCode})`);
|
|
144
|
-
if (typeof fn !== "function") throw new Error("Reconstructed code is not a function");
|
|
145
|
-
return fn;
|
|
146
|
-
}
|
|
147
95
|
/** Create error message from exception */
|
|
148
96
|
function createErrorMessage(error) {
|
|
149
97
|
return {
|
|
@@ -152,6 +100,58 @@ function createErrorMessage(error) {
|
|
|
152
100
|
stack: error instanceof Error ? error.stack : void 0
|
|
153
101
|
};
|
|
154
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Worker process for isolated benchmark execution.
|
|
105
|
+
* Uses eval() safely in isolated child process with trusted code.
|
|
106
|
+
*/
|
|
107
|
+
process.on("message", async (message) => {
|
|
108
|
+
if (message.type !== "run") return;
|
|
109
|
+
logTiming(`Processing ${message.spec.name} with ${message.runnerName}`);
|
|
110
|
+
try {
|
|
111
|
+
const start = getPerfNow();
|
|
112
|
+
const baseRunner = await createRunner(message.runnerName);
|
|
113
|
+
const runner = message.options.adaptive ? createAdaptiveWrapper(baseRunner, message.options) : baseRunner;
|
|
114
|
+
logTiming("Runner created in", getElapsed(start));
|
|
115
|
+
const benchStart = getPerfNow();
|
|
116
|
+
if (message.options.heapSample) {
|
|
117
|
+
const { withHeapSampling } = await import("../HeapSampler-B8dtKHn1.mjs");
|
|
118
|
+
const { result: results, profile: heapProfile } = await withHeapSampling({
|
|
119
|
+
samplingInterval: message.options.heapInterval,
|
|
120
|
+
stackDepth: message.options.heapDepth
|
|
121
|
+
}, async () => {
|
|
122
|
+
const { fn, params } = await resolveBenchmarkFn(message);
|
|
123
|
+
return runner.runBench({
|
|
124
|
+
...message.spec,
|
|
125
|
+
fn
|
|
126
|
+
}, message.options, params);
|
|
127
|
+
});
|
|
128
|
+
logTiming("Benchmark execution took", getElapsed(benchStart));
|
|
129
|
+
sendAndExit({
|
|
130
|
+
type: "result",
|
|
131
|
+
results,
|
|
132
|
+
heapProfile
|
|
133
|
+
}, 0);
|
|
134
|
+
} else {
|
|
135
|
+
const { fn, params } = await resolveBenchmarkFn(message);
|
|
136
|
+
const results = await runner.runBench({
|
|
137
|
+
...message.spec,
|
|
138
|
+
fn
|
|
139
|
+
}, message.options, params);
|
|
140
|
+
logTiming("Benchmark execution took", getElapsed(benchStart));
|
|
141
|
+
sendAndExit({
|
|
142
|
+
type: "result",
|
|
143
|
+
results
|
|
144
|
+
}, 0);
|
|
145
|
+
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
sendAndExit(createErrorMessage(error), 1);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
console.error("WorkerScript: Maximum lifetime exceeded, exiting");
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}, maxLifetime);
|
|
154
|
+
process.stdin.pause();
|
|
155
155
|
|
|
156
156
|
//#endregion
|
|
157
157
|
export { };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkerScript.mjs","names":[],"sources":["../../src/runners/WorkerScript.ts"],"sourcesContent":["#!/usr/bin/env node\nimport type { BenchmarkFunction, BenchmarkSpec } from \"../Benchmark.ts\";\nimport type { HeapProfile } from \"../heap-sample/HeapSampler.ts\";\nimport type { MeasuredResults } from \"../MeasuredResults.ts\";\nimport { variantModuleUrl } from \"../matrix/VariantLoader.ts\";\nimport {\n type AdaptiveOptions,\n createAdaptiveWrapper,\n} from \"./AdaptiveWrapper.ts\";\nimport type { RunnerOptions } from \"./BenchRunner.ts\";\nimport { createRunner, type KnownRunner } from \"./CreateRunner.ts\";\nimport { debugWorkerTiming, getElapsed, getPerfNow } from \"./TimingUtils.ts\";\n\nconst workerStartTime = getPerfNow();\nconst maxLifetime = 5 * 60 * 1000; // 5 minutes\n\n/** Message sent to worker process to start a benchmark run. */\nexport interface RunMessage {\n type: \"run\";\n spec: BenchmarkSpec;\n runnerName: KnownRunner;\n options: RunnerOptions;\n fnCode?: string; // Made optional - either fnCode or modulePath is required\n modulePath?: string; // Path to module for dynamic import\n exportName?: string; // Export name from module\n setupExportName?: string; // Setup function export name - called once, result passed to fn\n params?: unknown;\n // Variant directory mode (BenchMatrix)\n variantDir?: string; // Directory URL containing variant .ts files\n variantId?: string; // Variant filename (without .ts)\n caseData?: unknown; // Data to pass to variant\n caseId?: string; // Case identifier\n casesModule?: string; // URL to cases module (exports cases[] and loadCase())\n}\n\n/** Message returned from worker process with benchmark results. */\nexport interface ResultMessage {\n type: \"result\";\n results: MeasuredResults[];\n heapProfile?: HeapProfile;\n}\n\n/** Message returned from worker process when benchmark fails. */\nexport interface ErrorMessage {\n type: \"error\";\n error: string;\n stack?: string;\n}\n\nexport type WorkerMessage = RunMessage | ResultMessage | ErrorMessage;\n\n/**\n * Worker process for isolated benchmark execution.\n * Uses eval() safely in isolated child process with trusted code.\n */\nprocess.on(\"message\", async (message: RunMessage) => {\n if (message.type !== \"run\") return;\n\n logTiming(`Processing ${message.spec.name} with ${message.runnerName}`);\n\n try {\n const start = getPerfNow();\n const baseRunner = await createRunner(message.runnerName);\n\n const runner = (message.options as any).adaptive\n ? createAdaptiveWrapper(baseRunner, message.options as AdaptiveOptions)\n : baseRunner;\n\n logTiming(\"Runner created in\", getElapsed(start));\n\n const benchStart = getPerfNow();\n\n // Run with heap sampling if enabled (covers module import + execution)\n if (message.options.heapSample) {\n const { withHeapSampling } = await import(\n \"../heap-sample/HeapSampler.ts\"\n );\n const heapOpts = {\n samplingInterval: message.options.heapInterval,\n stackDepth: message.options.heapDepth,\n };\n const { result: results, profile: heapProfile } = await withHeapSampling(\n heapOpts,\n async () => {\n const { fn, params } = await resolveBenchmarkFn(message);\n return runner.runBench(\n { ...message.spec, fn },\n message.options,\n params,\n );\n },\n );\n logTiming(\"Benchmark execution took\", getElapsed(benchStart));\n sendAndExit({ type: \"result\", results, heapProfile }, 0);\n } else {\n const { fn, params } = await resolveBenchmarkFn(message);\n const results = await runner.runBench(\n { ...message.spec, fn },\n message.options,\n params,\n );\n logTiming(\"Benchmark execution took\", getElapsed(benchStart));\n sendAndExit({ type: \"result\", results }, 0);\n }\n } catch (error) {\n sendAndExit(createErrorMessage(error), 1);\n }\n});\n\n// Exit after 5 minutes to prevent zombie processes\nsetTimeout(() => {\n console.error(\"WorkerScript: Maximum lifetime exceeded, exiting\");\n process.exit(1);\n}, maxLifetime);\n\nprocess.stdin.pause();\n\n/** Log timing with consistent format */\nconst logTiming = debugWorkerTiming ? _logTiming : () => {};\nfunction _logTiming(operation: string, duration?: number) {\n if (duration === undefined) {\n console.log(`[Worker] ${operation}`);\n } else {\n console.log(`[Worker] ${operation} ${duration.toFixed(1)}ms`);\n }\n}\n\n/** Send message and exit with duration log */\nfunction sendAndExit(msg: ResultMessage | ErrorMessage, exitCode: number) {\n process.send!(msg, undefined, undefined, (err: Error | null): void => {\n if (err) {\n const kind = msg.type === \"result\" ? \"results\" : \"error message\";\n console.error(`[Worker] Error sending ${kind}:`, err);\n }\n const suffix = exitCode === 0 ? \"\" : \" (error)\";\n logTiming(`Total worker duration${suffix}:`, getElapsed(workerStartTime));\n process.exit(exitCode);\n });\n}\n\ninterface BenchmarkImportResult {\n fn: BenchmarkFunction;\n params: unknown;\n}\n\n/** Resolve benchmark function from message (variant dir, module path, or fnCode) */\nasync function resolveBenchmarkFn(\n message: RunMessage,\n): Promise<BenchmarkImportResult> {\n if (message.variantDir && message.variantId) {\n return importVariantModule(message);\n }\n if (message.modulePath) {\n return importBenchmarkWithSetup(message);\n }\n return { fn: reconstructFunction(message.fnCode!), params: message.params };\n}\n\n/** Import variant from directory and prepare benchmark function */\nasync function importVariantModule(\n message: RunMessage,\n): Promise<BenchmarkImportResult> {\n const { variantDir, variantId, caseId, casesModule } = message;\n let { caseData } = message;\n const moduleUrl = variantModuleUrl(variantDir!, variantId!);\n logTiming(`Importing variant ${variantId} from ${variantDir}`);\n\n if (casesModule && caseId) {\n caseData = (await loadCaseFromModule(casesModule, caseId)).data;\n }\n\n const module = await import(moduleUrl);\n const { setup, run } = module;\n\n if (typeof run !== \"function\") {\n throw new Error(`Variant '${variantId}' must export 'run' function`);\n }\n\n // Stateful variant: setup returns state, run receives state\n if (typeof setup === \"function\") {\n logTiming(`Calling setup for ${variantId}`);\n const state = await setup(caseData);\n return { fn: () => run(state), params: undefined };\n }\n\n // Stateless variant: run receives caseData directly\n return { fn: () => run(caseData), params: undefined };\n}\n\n/** Load case data from a cases module */\nasync function loadCaseFromModule(\n casesModuleUrl: string,\n caseId: string,\n): Promise<{ data: unknown; metadata?: Record<string, unknown> }> {\n logTiming(`Loading case '${caseId}' from ${casesModuleUrl}`);\n const module = await import(casesModuleUrl);\n if (typeof module.loadCase === \"function\") {\n return module.loadCase(caseId);\n }\n return { data: caseId };\n}\n\n/** Import benchmark function and optionally run setup */\nasync function importBenchmarkWithSetup(\n message: RunMessage,\n): Promise<BenchmarkImportResult> {\n const { modulePath, exportName, setupExportName, params } = message;\n logTiming(\n `Importing from ${modulePath}${exportName ? ` (${exportName})` : \"\"}`,\n );\n const module = await import(modulePath!);\n\n const fn = getModuleExport(module, exportName, modulePath!);\n\n if (setupExportName) {\n logTiming(`Calling setup: ${setupExportName}`);\n const setupFn = getModuleExport(module, setupExportName, modulePath!);\n const setupResult = await setupFn(params);\n return { fn, params: setupResult };\n }\n\n return { fn, params };\n}\n\n/** Get named or default export from module */\nfunction getModuleExport(\n module: any,\n exportName: string | undefined,\n modulePath: string,\n): BenchmarkFunction {\n const fn = exportName ? module[exportName] : module.default || module;\n if (typeof fn !== \"function\") {\n const name = exportName || \"default\";\n throw new Error(`Export '${name}' from ${modulePath} is not a function`);\n }\n return fn;\n}\n\n/** Reconstruct function from string code */\nfunction reconstructFunction(fnCode: string): BenchmarkFunction {\n // biome-ignore lint/security/noGlobalEval: Necessary for worker process isolation, code is from trusted source\n const fn = eval(`(${fnCode})`); // eslint-disable-line no-eval\n if (typeof fn !== \"function\") {\n throw new Error(\"Reconstructed code is not a function\");\n }\n return fn;\n}\n\n/** Create error message from exception */\nfunction createErrorMessage(error: unknown): ErrorMessage {\n return {\n type: \"error\",\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n };\n}\n"],"mappings":";;;;AAaA,MAAM,kBAAkB,YAAY;AACpC,MAAM,cAAc,MAAS;;;;;AAyC7B,QAAQ,GAAG,WAAW,OAAO,YAAwB;AACnD,KAAI,QAAQ,SAAS,MAAO;AAE5B,WAAU,cAAc,QAAQ,KAAK,KAAK,QAAQ,QAAQ,aAAa;AAEvE,KAAI;EACF,MAAM,QAAQ,YAAY;EAC1B,MAAM,aAAa,MAAM,aAAa,QAAQ,WAAW;EAEzD,MAAM,SAAU,QAAQ,QAAgB,WACpC,sBAAsB,YAAY,QAAQ,QAA2B,GACrE;AAEJ,YAAU,qBAAqB,WAAW,MAAM,CAAC;EAEjD,MAAM,aAAa,YAAY;AAG/B,MAAI,QAAQ,QAAQ,YAAY;GAC9B,MAAM,EAAE,qBAAqB,MAAM,OACjC;GAMF,MAAM,EAAE,QAAQ,SAAS,SAAS,gBAAgB,MAAM,iBAJvC;IACf,kBAAkB,QAAQ,QAAQ;IAClC,YAAY,QAAQ,QAAQ;IAC7B,EAGC,YAAY;IACV,MAAM,EAAE,IAAI,WAAW,MAAM,mBAAmB,QAAQ;AACxD,WAAO,OAAO,SACZ;KAAE,GAAG,QAAQ;KAAM;KAAI,EACvB,QAAQ,SACR,OACD;KAEJ;AACD,aAAU,4BAA4B,WAAW,WAAW,CAAC;AAC7D,eAAY;IAAE,MAAM;IAAU;IAAS;IAAa,EAAE,EAAE;SACnD;GACL,MAAM,EAAE,IAAI,WAAW,MAAM,mBAAmB,QAAQ;GACxD,MAAM,UAAU,MAAM,OAAO,SAC3B;IAAE,GAAG,QAAQ;IAAM;IAAI,EACvB,QAAQ,SACR,OACD;AACD,aAAU,4BAA4B,WAAW,WAAW,CAAC;AAC7D,eAAY;IAAE,MAAM;IAAU;IAAS,EAAE,EAAE;;UAEtC,OAAO;AACd,cAAY,mBAAmB,MAAM,EAAE,EAAE;;EAE3C;AAGF,iBAAiB;AACf,SAAQ,MAAM,mDAAmD;AACjE,SAAQ,KAAK,EAAE;GACd,YAAY;AAEf,QAAQ,MAAM,OAAO;;AAGrB,MAAM,YAAY,oBAAoB,mBAAmB;AACzD,SAAS,WAAW,WAAmB,UAAmB;AACxD,KAAI,aAAa,OACf,SAAQ,IAAI,YAAY,YAAY;KAEpC,SAAQ,IAAI,YAAY,UAAU,GAAG,SAAS,QAAQ,EAAE,CAAC,IAAI;;;AAKjE,SAAS,YAAY,KAAmC,UAAkB;AACxE,SAAQ,KAAM,KAAK,QAAW,SAAY,QAA4B;AACpE,MAAI,KAAK;GACP,MAAM,OAAO,IAAI,SAAS,WAAW,YAAY;AACjD,WAAQ,MAAM,0BAA0B,KAAK,IAAI,IAAI;;AAGvD,YAAU,wBADK,aAAa,IAAI,KAAK,WACI,IAAI,WAAW,gBAAgB,CAAC;AACzE,UAAQ,KAAK,SAAS;GACtB;;;AASJ,eAAe,mBACb,SACgC;AAChC,KAAI,QAAQ,cAAc,QAAQ,UAChC,QAAO,oBAAoB,QAAQ;AAErC,KAAI,QAAQ,WACV,QAAO,yBAAyB,QAAQ;AAE1C,QAAO;EAAE,IAAI,oBAAoB,QAAQ,OAAQ;EAAE,QAAQ,QAAQ;EAAQ;;;AAI7E,eAAe,oBACb,SACgC;CAChC,MAAM,EAAE,YAAY,WAAW,QAAQ,gBAAgB;CACvD,IAAI,EAAE,aAAa;CACnB,MAAM,YAAY,iBAAiB,YAAa,UAAW;AAC3D,WAAU,qBAAqB,UAAU,QAAQ,aAAa;AAE9D,KAAI,eAAe,OACjB,aAAY,MAAM,mBAAmB,aAAa,OAAO,EAAE;CAI7D,MAAM,EAAE,OAAO,QADA,MAAM,OAAO;AAG5B,KAAI,OAAO,QAAQ,WACjB,OAAM,IAAI,MAAM,YAAY,UAAU,8BAA8B;AAItE,KAAI,OAAO,UAAU,YAAY;AAC/B,YAAU,qBAAqB,YAAY;EAC3C,MAAM,QAAQ,MAAM,MAAM,SAAS;AACnC,SAAO;GAAE,UAAU,IAAI,MAAM;GAAE,QAAQ;GAAW;;AAIpD,QAAO;EAAE,UAAU,IAAI,SAAS;EAAE,QAAQ;EAAW;;;AAIvD,eAAe,mBACb,gBACA,QACgE;AAChE,WAAU,iBAAiB,OAAO,SAAS,iBAAiB;CAC5D,MAAM,SAAS,MAAM,OAAO;AAC5B,KAAI,OAAO,OAAO,aAAa,WAC7B,QAAO,OAAO,SAAS,OAAO;AAEhC,QAAO,EAAE,MAAM,QAAQ;;;AAIzB,eAAe,yBACb,SACgC;CAChC,MAAM,EAAE,YAAY,YAAY,iBAAiB,WAAW;AAC5D,WACE,kBAAkB,aAAa,aAAa,KAAK,WAAW,KAAK,KAClE;CACD,MAAM,SAAS,MAAM,OAAO;CAE5B,MAAM,KAAK,gBAAgB,QAAQ,YAAY,WAAY;AAE3D,KAAI,iBAAiB;AACnB,YAAU,kBAAkB,kBAAkB;AAG9C,SAAO;GAAE;GAAI,QADO,MADJ,gBAAgB,QAAQ,iBAAiB,WAAY,CACnC,OAAO;GACP;;AAGpC,QAAO;EAAE;EAAI;EAAQ;;;AAIvB,SAAS,gBACP,QACA,YACA,YACmB;CACnB,MAAM,KAAK,aAAa,OAAO,cAAc,OAAO,WAAW;AAC/D,KAAI,OAAO,OAAO,YAAY;EAC5B,MAAM,OAAO,cAAc;AAC3B,QAAM,IAAI,MAAM,WAAW,KAAK,SAAS,WAAW,oBAAoB;;AAE1E,QAAO;;;AAIT,SAAS,oBAAoB,QAAmC;CAE9D,MAAM,KAAK,KAAK,IAAI,OAAO,GAAG;AAC9B,KAAI,OAAO,OAAO,WAChB,OAAM,IAAI,MAAM,uCAAuC;AAEzD,QAAO;;;AAIT,SAAS,mBAAmB,OAA8B;AACxD,QAAO;EACL,MAAM;EACN,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;EAC7D,OAAO,iBAAiB,QAAQ,MAAM,QAAQ;EAC/C"}
|
|
1
|
+
{"version":3,"file":"WorkerScript.mjs","names":[],"sources":["../../src/runners/WorkerScript.ts"],"sourcesContent":["#!/usr/bin/env node\nimport type { BenchmarkFunction, BenchmarkSpec } from \"../Benchmark.ts\";\nimport type { HeapProfile } from \"../heap-sample/HeapSampler.ts\";\nimport type { MeasuredResults } from \"../MeasuredResults.ts\";\nimport { variantModuleUrl } from \"../matrix/VariantLoader.ts\";\nimport {\n type AdaptiveOptions,\n createAdaptiveWrapper,\n} from \"./AdaptiveWrapper.ts\";\nimport type { RunnerOptions } from \"./BenchRunner.ts\";\nimport { createRunner, type KnownRunner } from \"./CreateRunner.ts\";\nimport { debugWorkerTiming, getElapsed, getPerfNow } from \"./TimingUtils.ts\"; // 5 minutes\n\n/** Message sent to worker process to start a benchmark run. */\nexport interface RunMessage {\n type: \"run\";\n spec: BenchmarkSpec;\n runnerName: KnownRunner;\n options: RunnerOptions;\n fnCode?: string; // Made optional - either fnCode or modulePath is required\n modulePath?: string; // Path to module for dynamic import\n exportName?: string; // Export name from module\n setupExportName?: string; // Setup function export name - called once, result passed to fn\n params?: unknown;\n // Variant directory mode (BenchMatrix)\n variantDir?: string; // Directory URL containing variant .ts files\n variantId?: string; // Variant filename (without .ts)\n caseData?: unknown; // Data to pass to variant\n caseId?: string; // Case identifier\n casesModule?: string; // URL to cases module (exports cases[] and loadCase())\n}\n\n/** Message returned from worker process with benchmark results. */\nexport interface ResultMessage {\n type: \"result\";\n results: MeasuredResults[];\n heapProfile?: HeapProfile;\n}\n\n/** Message returned from worker process when benchmark fails. */\nexport interface ErrorMessage {\n type: \"error\";\n error: string;\n stack?: string;\n}\n\nexport type WorkerMessage = RunMessage | ResultMessage | ErrorMessage;\n\ninterface BenchmarkImportResult {\n fn: BenchmarkFunction;\n params: unknown;\n}\n\nconst workerStartTime = getPerfNow();\nconst maxLifetime = 5 * 60 * 1000;\n\n/** Log timing with consistent format */\nconst logTiming = debugWorkerTiming ? _logTiming : () => {};\nfunction _logTiming(operation: string, duration?: number) {\n if (duration === undefined) {\n console.log(`[Worker] ${operation}`);\n } else {\n console.log(`[Worker] ${operation} ${duration.toFixed(1)}ms`);\n }\n}\n\n/** Send message and exit with duration log */\nfunction sendAndExit(msg: ResultMessage | ErrorMessage, exitCode: number) {\n process.send!(msg, undefined, undefined, (err: Error | null): void => {\n if (err) {\n const kind = msg.type === \"result\" ? \"results\" : \"error message\";\n console.error(`[Worker] Error sending ${kind}:`, err);\n }\n const suffix = exitCode === 0 ? \"\" : \" (error)\";\n logTiming(`Total worker duration${suffix}:`, getElapsed(workerStartTime));\n process.exit(exitCode);\n });\n}\n\n/** Resolve benchmark function from message (variant dir, module path, or fnCode) */\nasync function resolveBenchmarkFn(\n message: RunMessage,\n): Promise<BenchmarkImportResult> {\n if (message.variantDir && message.variantId) {\n return importVariantModule(message);\n }\n if (message.modulePath) {\n return importBenchmarkWithSetup(message);\n }\n return { fn: reconstructFunction(message.fnCode!), params: message.params };\n}\n\n/** Import variant from directory and prepare benchmark function */\nasync function importVariantModule(\n message: RunMessage,\n): Promise<BenchmarkImportResult> {\n const { variantDir, variantId, caseId, casesModule } = message;\n let { caseData } = message;\n const moduleUrl = variantModuleUrl(variantDir!, variantId!);\n logTiming(`Importing variant ${variantId} from ${variantDir}`);\n\n if (casesModule && caseId) {\n caseData = (await loadCaseFromModule(casesModule, caseId)).data;\n }\n\n const module = await import(moduleUrl);\n const { setup, run } = module;\n\n if (typeof run !== \"function\") {\n throw new Error(`Variant '${variantId}' must export 'run' function`);\n }\n\n // Stateful variant: setup returns state, run receives state\n if (typeof setup === \"function\") {\n logTiming(`Calling setup for ${variantId}`);\n const state = await setup(caseData);\n return { fn: () => run(state), params: undefined };\n }\n\n // Stateless variant: run receives caseData directly\n return { fn: () => run(caseData), params: undefined };\n}\n\n/** Import benchmark function and optionally run setup */\nasync function importBenchmarkWithSetup(\n message: RunMessage,\n): Promise<BenchmarkImportResult> {\n const { modulePath, exportName, setupExportName, params } = message;\n logTiming(\n `Importing from ${modulePath}${exportName ? ` (${exportName})` : \"\"}`,\n );\n const module = await import(modulePath!);\n\n const fn = getModuleExport(module, exportName, modulePath!);\n\n if (setupExportName) {\n logTiming(`Calling setup: ${setupExportName}`);\n const setupFn = getModuleExport(module, setupExportName, modulePath!);\n const setupResult = await setupFn(params);\n return { fn, params: setupResult };\n }\n\n return { fn, params };\n}\n\n/** Reconstruct function from string code */\nfunction reconstructFunction(fnCode: string): BenchmarkFunction {\n // biome-ignore lint/security/noGlobalEval: Necessary for worker process isolation, code is from trusted source\n const fn = eval(`(${fnCode})`); // eslint-disable-line no-eval\n if (typeof fn !== \"function\") {\n throw new Error(\"Reconstructed code is not a function\");\n }\n return fn;\n}\n\n/** Load case data from a cases module */\nasync function loadCaseFromModule(\n casesModuleUrl: string,\n caseId: string,\n): Promise<{ data: unknown; metadata?: Record<string, unknown> }> {\n logTiming(`Loading case '${caseId}' from ${casesModuleUrl}`);\n const module = await import(casesModuleUrl);\n if (typeof module.loadCase === \"function\") {\n return module.loadCase(caseId);\n }\n return { data: caseId };\n}\n\n/** Get named or default export from module */\nfunction getModuleExport(\n module: any,\n exportName: string | undefined,\n modulePath: string,\n): BenchmarkFunction {\n const fn = exportName ? module[exportName] : module.default || module;\n if (typeof fn !== \"function\") {\n const name = exportName || \"default\";\n throw new Error(`Export '${name}' from ${modulePath} is not a function`);\n }\n return fn;\n}\n\n/** Create error message from exception */\nfunction createErrorMessage(error: unknown): ErrorMessage {\n return {\n type: \"error\",\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n };\n}\n\n/**\n * Worker process for isolated benchmark execution.\n * Uses eval() safely in isolated child process with trusted code.\n */\nprocess.on(\"message\", async (message: RunMessage) => {\n if (message.type !== \"run\") return;\n\n logTiming(`Processing ${message.spec.name} with ${message.runnerName}`);\n\n try {\n const start = getPerfNow();\n const baseRunner = await createRunner(message.runnerName);\n\n const runner = (message.options as any).adaptive\n ? createAdaptiveWrapper(baseRunner, message.options as AdaptiveOptions)\n : baseRunner;\n\n logTiming(\"Runner created in\", getElapsed(start));\n\n const benchStart = getPerfNow();\n\n // Run with heap sampling if enabled (covers module import + execution)\n if (message.options.heapSample) {\n const { withHeapSampling } = await import(\n \"../heap-sample/HeapSampler.ts\"\n );\n const heapOpts = {\n samplingInterval: message.options.heapInterval,\n stackDepth: message.options.heapDepth,\n };\n const { result: results, profile: heapProfile } = await withHeapSampling(\n heapOpts,\n async () => {\n const { fn, params } = await resolveBenchmarkFn(message);\n return runner.runBench(\n { ...message.spec, fn },\n message.options,\n params,\n );\n },\n );\n logTiming(\"Benchmark execution took\", getElapsed(benchStart));\n sendAndExit({ type: \"result\", results, heapProfile }, 0);\n } else {\n const { fn, params } = await resolveBenchmarkFn(message);\n const results = await runner.runBench(\n { ...message.spec, fn },\n message.options,\n params,\n );\n logTiming(\"Benchmark execution took\", getElapsed(benchStart));\n sendAndExit({ type: \"result\", results }, 0);\n }\n } catch (error) {\n sendAndExit(createErrorMessage(error), 1);\n }\n});\n\n// Exit after 5 minutes to prevent zombie processes\nsetTimeout(() => {\n console.error(\"WorkerScript: Maximum lifetime exceeded, exiting\");\n process.exit(1);\n}, maxLifetime);\n\nprocess.stdin.pause();\n"],"mappings":"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"}
|