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
|
@@ -8,28 +8,15 @@ import type {
|
|
|
8
8
|
import type { BenchRunner, RunnerOptions } from "./BenchRunner.ts";
|
|
9
9
|
import { executeBenchmark } from "./BenchRunner.ts";
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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;
|
|
11
|
+
export type SampleTimeStats = {
|
|
12
|
+
min: number;
|
|
13
|
+
max: number;
|
|
14
|
+
avg: number;
|
|
15
|
+
p50: number;
|
|
16
|
+
p75: number;
|
|
17
|
+
p99: number;
|
|
18
|
+
p999: number;
|
|
19
|
+
};
|
|
33
20
|
|
|
34
21
|
type CollectParams<T = unknown> = {
|
|
35
22
|
benchmark: BenchmarkSpec<T>;
|
|
@@ -56,14 +43,68 @@ type CollectResult = {
|
|
|
56
43
|
pausePoints: PausePoint[]; // where pauses occurred
|
|
57
44
|
};
|
|
58
45
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
46
|
+
type SampleLoopResult = {
|
|
47
|
+
samples: number[];
|
|
48
|
+
heapSamples?: number[];
|
|
49
|
+
timestamps?: number[];
|
|
50
|
+
optStatuses: number[];
|
|
51
|
+
pausePoints: PausePoint[];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type SampleArrays = {
|
|
55
|
+
samples: number[];
|
|
56
|
+
timestamps: number[];
|
|
57
|
+
heapSamples: number[];
|
|
58
|
+
optStatuses: number[];
|
|
59
|
+
pausePoints: PausePoint[];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Wait time after gc() for V8 to stabilize (ms).
|
|
64
|
+
*
|
|
65
|
+
* V8 has 4 compilation tiers: Ignition (interpreter) -> Sparkplug (baseline) ->
|
|
66
|
+
* Maglev (mid-tier optimizer) -> TurboFan (full optimizer). Tiering thresholds:
|
|
67
|
+
* - Ignition -> Sparkplug: 8 invocations
|
|
68
|
+
* - Sparkplug -> Maglev: 500 invocations
|
|
69
|
+
* - Maglev -> TurboFan: 6000 invocations
|
|
70
|
+
*
|
|
71
|
+
* Optimization compilation happens on background threads and requires idle time
|
|
72
|
+
* on the main thread to complete. Without sufficient warmup + settle time,
|
|
73
|
+
* benchmarks exhibit bimodal timing: slow Sparkplug samples (~30% slower) mixed
|
|
74
|
+
* with fast optimized samples.
|
|
75
|
+
*
|
|
76
|
+
* The warmup iterations trigger the optimization decision, then gcSettleTime
|
|
77
|
+
* provides idle time for background compilation to finish before measurement.
|
|
78
|
+
*
|
|
79
|
+
* @see https://v8.dev/blog/sparkplug
|
|
80
|
+
* @see https://v8.dev/blog/maglev
|
|
81
|
+
* @see https://v8.dev/blog/background-compilation
|
|
82
|
+
*/
|
|
83
|
+
const gcSettleTime = 1000;
|
|
84
|
+
|
|
85
|
+
const defaultCollectOptions = {
|
|
86
|
+
maxTime: 5000,
|
|
87
|
+
maxIterations: 1000000,
|
|
88
|
+
warmup: 0,
|
|
89
|
+
traceOpt: false,
|
|
90
|
+
noSettle: false,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* V8 optimization status bit meanings:
|
|
95
|
+
* Bit 0 (1): is_function
|
|
96
|
+
* Bit 4 (16): is_optimized (TurboFan)
|
|
97
|
+
* Bit 5 (32): is_optimized (Maglev)
|
|
98
|
+
* Bit 7 (128): is_baseline (Sparkplug)
|
|
99
|
+
* Bit 3 (8): maybe_deoptimized
|
|
100
|
+
*/
|
|
101
|
+
const statusNames: Record<number, string> = {
|
|
102
|
+
1: "interpreted",
|
|
103
|
+
129: "sparkplug", // 1 + 128
|
|
104
|
+
17: "turbofan", // 1 + 16
|
|
105
|
+
33: "maglev", // 1 + 32
|
|
106
|
+
49: "turbofan+maglev", // 1 + 16 + 32
|
|
107
|
+
32769: "optimized", // common optimized status
|
|
67
108
|
};
|
|
68
109
|
|
|
69
110
|
/** @return runner with time and iteration limits */
|
|
@@ -79,27 +120,18 @@ export class BasicRunner implements BenchRunner {
|
|
|
79
120
|
}
|
|
80
121
|
}
|
|
81
122
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
traceOpt: false,
|
|
87
|
-
noSettle: false,
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
function buildMeasuredResults(name: string, c: CollectResult): MeasuredResults {
|
|
91
|
-
const time = computeStats(c.samples);
|
|
123
|
+
/** @return percentiles and basic statistics */
|
|
124
|
+
export function computeStats(samples: number[]): SampleTimeStats {
|
|
125
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
126
|
+
const avg = samples.reduce((sum, s) => sum + s, 0) / samples.length;
|
|
92
127
|
return {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
optStatus: c.optStatus,
|
|
101
|
-
optSamples: c.optSamples,
|
|
102
|
-
pausePoints: c.pausePoints,
|
|
128
|
+
min: sorted[0],
|
|
129
|
+
max: sorted[sorted.length - 1],
|
|
130
|
+
avg,
|
|
131
|
+
p50: percentile(sorted, 0.5),
|
|
132
|
+
p75: percentile(sorted, 0.75),
|
|
133
|
+
p99: percentile(sorted, 0.99),
|
|
134
|
+
p999: percentile(sorted, 0.999),
|
|
103
135
|
};
|
|
104
136
|
}
|
|
105
137
|
|
|
@@ -136,6 +168,34 @@ async function collectSamples<T>(p: CollectParams<T>): Promise<CollectResult> {
|
|
|
136
168
|
};
|
|
137
169
|
}
|
|
138
170
|
|
|
171
|
+
function buildMeasuredResults(name: string, c: CollectResult): MeasuredResults {
|
|
172
|
+
const time = computeStats(c.samples);
|
|
173
|
+
return {
|
|
174
|
+
name,
|
|
175
|
+
samples: c.samples,
|
|
176
|
+
warmupSamples: c.warmupSamples,
|
|
177
|
+
heapSamples: c.heapSamples,
|
|
178
|
+
timestamps: c.timestamps,
|
|
179
|
+
time,
|
|
180
|
+
heapSize: { avg: c.heapGrowth, min: c.heapGrowth, max: c.heapGrowth },
|
|
181
|
+
optStatus: c.optStatus,
|
|
182
|
+
optSamples: c.optSamples,
|
|
183
|
+
pausePoints: c.pausePoints,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** @return percentile value with linear interpolation */
|
|
188
|
+
function percentile(sortedArray: number[], p: number): number {
|
|
189
|
+
const index = (sortedArray.length - 1) * p;
|
|
190
|
+
const lower = Math.floor(index);
|
|
191
|
+
const upper = Math.ceil(index);
|
|
192
|
+
const weight = index % 1;
|
|
193
|
+
|
|
194
|
+
if (upper >= sortedArray.length) return sortedArray[sortedArray.length - 1];
|
|
195
|
+
|
|
196
|
+
return sortedArray[lower] * (1 - weight) + sortedArray[upper] * weight;
|
|
197
|
+
}
|
|
198
|
+
|
|
139
199
|
/** Run warmup iterations with gc + settle time for V8 optimization */
|
|
140
200
|
async function runWarmup<T>(p: CollectParams<T>): Promise<number[]> {
|
|
141
201
|
const gc = gcFunction();
|
|
@@ -153,55 +213,6 @@ async function runWarmup<T>(p: CollectParams<T>): Promise<number[]> {
|
|
|
153
213
|
return samples;
|
|
154
214
|
}
|
|
155
215
|
|
|
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
216
|
/** Collect timing samples with periodic pauses for V8 optimization */
|
|
206
217
|
async function runSampleLoop<T>(
|
|
207
218
|
p: CollectParams<T>,
|
|
@@ -255,82 +266,6 @@ async function runSampleLoop<T>(
|
|
|
255
266
|
};
|
|
256
267
|
}
|
|
257
268
|
|
|
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
269
|
/** @return analysis of V8 optimization status per sample */
|
|
335
270
|
function analyzeOptStatus(
|
|
336
271
|
samples: number[],
|
|
@@ -362,3 +297,68 @@ function analyzeOptStatus(
|
|
|
362
297
|
|
|
363
298
|
return { byTier, deoptCount };
|
|
364
299
|
}
|
|
300
|
+
|
|
301
|
+
/** @return runtime gc() function, or no-op if unavailable */
|
|
302
|
+
function gcFunction(): () => void {
|
|
303
|
+
const gc = globalThis.gc || (globalThis as any).__gc;
|
|
304
|
+
if (gc) return gc;
|
|
305
|
+
console.warn("gc() not available, run node/bun with --expose-gc");
|
|
306
|
+
return () => {};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** @return function to get V8 optimization status (requires --allow-natives-syntax) */
|
|
310
|
+
function createOptStatusGetter(): ((fn: unknown) => number) | undefined {
|
|
311
|
+
try {
|
|
312
|
+
// %GetOptimizationStatus returns a bitmask
|
|
313
|
+
const getter = new Function("f", "return %GetOptimizationStatus(f)");
|
|
314
|
+
getter(() => {});
|
|
315
|
+
return getter as (fn: unknown) => number;
|
|
316
|
+
} catch {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Estimate sample count for pre-allocation */
|
|
322
|
+
function estimateSampleCount(maxTime: number, maxIterations: number): number {
|
|
323
|
+
return maxIterations || Math.ceil(maxTime / 0.1); // assume 0.1ms per iteration minimum
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Pre-allocate arrays to reduce GC pressure during measurement */
|
|
327
|
+
function createSampleArrays(
|
|
328
|
+
n: number,
|
|
329
|
+
trackHeap: boolean,
|
|
330
|
+
trackOpt: boolean,
|
|
331
|
+
): SampleArrays {
|
|
332
|
+
const arr = (track: boolean) => (track ? new Array<number>(n) : []);
|
|
333
|
+
return {
|
|
334
|
+
samples: new Array<number>(n),
|
|
335
|
+
timestamps: new Array<number>(n),
|
|
336
|
+
heapSamples: arr(trackHeap),
|
|
337
|
+
optStatuses: arr(trackOpt),
|
|
338
|
+
pausePoints: [],
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** Check if we should pause at this iteration for V8 optimization */
|
|
343
|
+
function shouldPause(
|
|
344
|
+
iter: number,
|
|
345
|
+
first: number | undefined,
|
|
346
|
+
interval: number,
|
|
347
|
+
): boolean {
|
|
348
|
+
if (first !== undefined && iter === first) return true;
|
|
349
|
+
if (interval <= 0) return false;
|
|
350
|
+
if (first === undefined) return iter % interval === 0;
|
|
351
|
+
return (iter - first) % interval === 0;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Trim arrays to actual sample count */
|
|
355
|
+
function trimArrays(
|
|
356
|
+
a: SampleArrays,
|
|
357
|
+
count: number,
|
|
358
|
+
trackHeap: boolean,
|
|
359
|
+
trackOpt: boolean,
|
|
360
|
+
): void {
|
|
361
|
+
a.samples.length = a.timestamps.length = count;
|
|
362
|
+
if (trackHeap) a.heapSamples.length = count;
|
|
363
|
+
if (trackOpt) a.optStatuses.length = count;
|
|
364
|
+
}
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { BenchmarkSpec } from "../Benchmark.ts";
|
|
2
2
|
import type { MeasuredResults } from "../MeasuredResults.ts";
|
|
3
3
|
|
|
4
|
-
/** Execute benchmark with optional parameters */
|
|
5
|
-
export function executeBenchmark<T>(
|
|
6
|
-
benchmark: BenchmarkSpec<T>,
|
|
7
|
-
params?: T,
|
|
8
|
-
): void {
|
|
9
|
-
(benchmark.fn as (params?: T) => void)(params);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
4
|
/** Interface for benchmark execution libraries */
|
|
13
5
|
export interface BenchRunner {
|
|
14
6
|
runBench<T = unknown>(
|
|
@@ -58,3 +50,11 @@ export interface RunnerOptions {
|
|
|
58
50
|
/** Heap sampling stack depth */
|
|
59
51
|
heapDepth?: number;
|
|
60
52
|
}
|
|
53
|
+
|
|
54
|
+
/** Execute benchmark with optional parameters */
|
|
55
|
+
export function executeBenchmark<T>(
|
|
56
|
+
benchmark: BenchmarkSpec<T>,
|
|
57
|
+
params?: T,
|
|
58
|
+
): void {
|
|
59
|
+
(benchmark.fn as (params?: T) => void)(params);
|
|
60
|
+
}
|
package/src/runners/GcStats.ts
CHANGED
|
@@ -46,28 +46,6 @@ export function parseGcLine(line: string): GcEvent | undefined {
|
|
|
46
46
|
return { type, pauseMs, allocated, collected, promoted, survived };
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/** Parse name=value pairs from trace-gc-nvp line */
|
|
50
|
-
function parseNvpFields(line: string): Record<string, string> {
|
|
51
|
-
const fields: Record<string, string> = {};
|
|
52
|
-
// Format: "key=value, key=value, ..." or "key=value key=value"
|
|
53
|
-
const matches = line.matchAll(/(\w+)=([^\s,]+)/g);
|
|
54
|
-
for (const [, key, value] of matches) {
|
|
55
|
-
fields[key] = value;
|
|
56
|
-
}
|
|
57
|
-
return fields;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Map V8 gc type codes to our types */
|
|
61
|
-
function parseGcType(gcField: string): GcEvent["type"] {
|
|
62
|
-
// V8 uses: s=scavenge, mc=mark-compact, mmc=minor-mc (young gen mark-compact)
|
|
63
|
-
if (gcField === "s" || gcField === "scavenge") return "scavenge";
|
|
64
|
-
if (gcField === "mc" || gcField === "ms" || gcField === "mark-compact")
|
|
65
|
-
return "mark-compact";
|
|
66
|
-
if (gcField === "mmc" || gcField === "minor-mc" || gcField === "minor-ms")
|
|
67
|
-
return "minor-ms";
|
|
68
|
-
return "unknown";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
49
|
/** Aggregate GC events into summary stats */
|
|
72
50
|
export function aggregateGcStats(events: GcEvent[]): GcStats {
|
|
73
51
|
let scavenges = 0;
|
|
@@ -105,3 +83,25 @@ export function aggregateGcStats(events: GcEvent[]): GcStats {
|
|
|
105
83
|
export function emptyGcStats(): GcStats {
|
|
106
84
|
return { scavenges: 0, markCompacts: 0, totalCollected: 0, gcPauseTime: 0 };
|
|
107
85
|
}
|
|
86
|
+
|
|
87
|
+
/** Parse name=value pairs from trace-gc-nvp line */
|
|
88
|
+
function parseNvpFields(line: string): Record<string, string> {
|
|
89
|
+
const fields: Record<string, string> = {};
|
|
90
|
+
// Format: "key=value, key=value, ..." or "key=value key=value"
|
|
91
|
+
const matches = line.matchAll(/(\w+)=([^\s,]+)/g);
|
|
92
|
+
for (const [, key, value] of matches) {
|
|
93
|
+
fields[key] = value;
|
|
94
|
+
}
|
|
95
|
+
return fields;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Map V8 gc type codes to our types */
|
|
99
|
+
function parseGcType(gcField: string): GcEvent["type"] {
|
|
100
|
+
// V8 uses: s=scavenge, mc=mark-compact, mmc=minor-mc (young gen mark-compact)
|
|
101
|
+
if (gcField === "s" || gcField === "scavenge") return "scavenge";
|
|
102
|
+
if (gcField === "mc" || gcField === "ms" || gcField === "mark-compact")
|
|
103
|
+
return "mark-compact";
|
|
104
|
+
if (gcField === "mmc" || gcField === "minor-mc" || gcField === "minor-ms")
|
|
105
|
+
return "minor-ms";
|
|
106
|
+
return "unknown";
|
|
107
|
+
}
|