benchforge 0.1.2 → 0.1.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/README.md +15 -129
- package/dist/{BenchRunner-BLfGX2wQ.d.mts → BenchRunner-CSKN9zPy.d.mts} +1 -1
- package/dist/BrowserHeapSampler-DQwmmuDu.mjs +187 -0
- package/dist/BrowserHeapSampler-DQwmmuDu.mjs.map +1 -0
- package/dist/GcStats-ByEovUi1.mjs +77 -0
- package/dist/GcStats-ByEovUi1.mjs.map +1 -0
- package/dist/{HeapSampler-BX3de22o.mjs → HeapSampler-B8dtKHn1.mjs} +1 -1
- package/dist/{HeapSampler-BX3de22o.mjs.map → HeapSampler-B8dtKHn1.mjs.map} +1 -1
- package/dist/{TimingUtils-D4z1jpp2.mjs → TimingUtils-ClclVQ7E.mjs} +276 -278
- package/dist/TimingUtils-ClclVQ7E.mjs.map +1 -0
- package/dist/bin/benchforge.mjs +1 -1
- package/dist/index.d.mts +10 -6
- package/dist/index.mjs +2 -2
- package/dist/runners/WorkerScript.d.mts +1 -1
- package/dist/runners/WorkerScript.mjs +2 -2
- package/dist/{src-D7zxOFGA.mjs → src-B06_i1RD.mjs} +21 -270
- package/dist/src-B06_i1RD.mjs.map +1 -0
- package/package.json +10 -2
- package/src/StandardSections.ts +1 -8
- package/src/browser/BrowserHeapSampler.ts +3 -2
- package/src/cli/CliArgs.ts +4 -3
- package/src/cli/RunBenchCLI.ts +16 -8
- package/src/runners/BasicRunner.ts +0 -4
- package/dist/TimingUtils-D4z1jpp2.mjs.map +0 -1
- package/dist/src-D7zxOFGA.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimingUtils-ClclVQ7E.mjs","names":["percentile"],"sources":["../src/matrix/VariantLoader.ts","../src/runners/BenchRunner.ts","../src/runners/BasicRunner.ts","../src/StatisticalUtils.ts","../src/runners/RunnerUtils.ts","../src/runners/AdaptiveWrapper.ts","../src/runners/CreateRunner.ts","../src/runners/TimingUtils.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport type { Variant } from \"../BenchMatrix.ts\";\n\n/** Discover variant ids from a directory of .ts files */\nexport async function discoverVariants(dirUrl: string): Promise<string[]> {\n const dirPath = fileURLToPath(dirUrl);\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n return entries\n .filter(e => e.isFile() && e.name.endsWith(\".ts\"))\n .map(e => e.name.slice(0, -3))\n .sort();\n}\n\n/** Load a variant module and extract run/setup exports */\nexport async function loadVariant<T = unknown>(\n dirUrl: string,\n variantId: string,\n): Promise<Variant<T>> {\n const moduleUrl = variantModuleUrl(dirUrl, variantId);\n const module = await import(moduleUrl);\n return extractVariant(module, variantId, moduleUrl);\n}\n\n/** Extract variant from module exports */\nfunction extractVariant<T>(\n module: Record<string, unknown>,\n variantId: string,\n moduleUrl: string,\n): Variant<T> {\n const { setup, run } = module;\n const loc = `Variant '${variantId}' at ${moduleUrl}`;\n if (typeof run !== \"function\") {\n throw new Error(`${loc} must export 'run'`);\n }\n if (setup === undefined) return run as (data: T) => void;\n if (typeof setup !== \"function\") {\n throw new Error(`${loc}: 'setup' must be a function`);\n }\n return { setup: setup as (data: T) => unknown, run: run as () => void };\n}\n\n/** Get module URL for a variant in a directory */\nexport function variantModuleUrl(dirUrl: string, variantId: string): string {\n return new URL(`${variantId}.ts`, dirUrl).href;\n}\n","import type { BenchmarkSpec } from \"../Benchmark.ts\";\nimport type { MeasuredResults } from \"../MeasuredResults.ts\";\n\n/** Execute benchmark with optional parameters */\nexport function executeBenchmark<T>(\n benchmark: BenchmarkSpec<T>,\n params?: T,\n): void {\n (benchmark.fn as (params?: T) => void)(params);\n}\n\n/** Interface for benchmark execution libraries */\nexport interface BenchRunner {\n runBench<T = unknown>(\n benchmark: BenchmarkSpec<T>,\n options: RunnerOptions,\n params?: T,\n ): Promise<MeasuredResults[]>;\n}\n\nexport interface RunnerOptions {\n /** Minimum time to run each benchmark (milliseconds) */\n minTime?: number;\n /** Maximum time to run each benchmark - ignored by mitata (milliseconds) */\n maxTime?: number;\n /** Maximum iterations per benchmark - ignored by TinyBench */\n maxIterations?: number;\n /** Warmup iterations before measurement (default: 0) */\n warmup?: number;\n /** Warmup time before measurement (milliseconds) */\n warmupTime?: number;\n /** Warmup samples - mitata only, for reducing test time */\n warmupSamples?: number;\n /** Warmup threshold - mitata only (nanoseconds) */\n warmupThreshold?: number;\n /** Minimum samples required - mitata only */\n minSamples?: number;\n /** Force GC after each iteration (requires --expose-gc) */\n collect?: boolean;\n /** Enable CPU performance counters (requires root access) */\n cpuCounters?: boolean;\n /** Trace V8 optimization tiers (requires --allow-natives-syntax) */\n traceOpt?: boolean;\n /** Skip post-warmup settle time (default: false) */\n noSettle?: boolean;\n /** Iterations before first pause (then pauseInterval applies) */\n pauseFirst?: number;\n /** Iterations between pauses for V8 optimization (0 to disable) */\n pauseInterval?: number;\n /** Pause duration in ms for V8 optimization */\n pauseDuration?: number;\n /** Collect GC stats via --trace-gc-nvp (requires worker mode) */\n gcStats?: boolean;\n /** Heap sampling allocation attribution */\n heapSample?: boolean;\n /** Heap sampling interval in bytes */\n heapInterval?: number;\n /** Heap sampling stack depth */\n heapDepth?: number;\n}\n","import { getHeapStatistics } from \"node:v8\";\nimport type { BenchmarkSpec } from \"../Benchmark.ts\";\nimport type {\n MeasuredResults,\n OptStatusInfo,\n PausePoint,\n} from \"../MeasuredResults.ts\";\nimport type { BenchRunner, RunnerOptions } from \"./BenchRunner.ts\";\nimport { executeBenchmark } from \"./BenchRunner.ts\";\n\n/**\n * Wait time after gc() for V8 to stabilize (ms).\n *\n * V8 has 4 compilation tiers: Ignition (interpreter) -> Sparkplug (baseline) ->\n * Maglev (mid-tier optimizer) -> TurboFan (full optimizer). Tiering thresholds:\n * - Ignition -> Sparkplug: 8 invocations\n * - Sparkplug -> Maglev: 500 invocations\n * - Maglev -> TurboFan: 6000 invocations\n *\n * Optimization compilation happens on background threads and requires idle time\n * on the main thread to complete. Without sufficient warmup + settle time,\n * benchmarks exhibit bimodal timing: slow Sparkplug samples (~30% slower) mixed\n * with fast optimized samples.\n *\n * The warmup iterations trigger the optimization decision, then gcSettleTime\n * provides idle time for background compilation to finish before measurement.\n *\n * @see https://v8.dev/blog/sparkplug\n * @see https://v8.dev/blog/maglev\n * @see https://v8.dev/blog/background-compilation\n */\nconst gcSettleTime = 1000;\n\ntype CollectParams<T = unknown> = {\n benchmark: BenchmarkSpec<T>;\n maxTime: number;\n maxIterations: number;\n warmup: number;\n params?: T;\n skipWarmup?: boolean;\n traceOpt?: boolean;\n noSettle?: boolean;\n pauseFirst?: number;\n pauseInterval?: number;\n pauseDuration?: number;\n};\n\ntype CollectResult = {\n samples: number[];\n warmupSamples: number[]; // timing of warmup iterations\n heapGrowth: number; // amortized KB per sample\n heapSamples?: number[]; // heap size per sample (bytes)\n timestamps?: number[]; // wall-clock μs per sample for Perfetto\n optStatus?: OptStatusInfo;\n optSamples?: number[]; // per-sample V8 opt status codes\n pausePoints: PausePoint[]; // where pauses occurred\n};\n\nexport type SampleTimeStats = {\n min: number;\n max: number;\n avg: number;\n p50: number;\n p75: number;\n p99: number;\n p999: number;\n};\n\n/** @return runner with time and iteration limits */\nexport class BasicRunner implements BenchRunner {\n async runBench<T = unknown>(\n benchmark: BenchmarkSpec<T>,\n options: RunnerOptions,\n params?: T,\n ): Promise<MeasuredResults[]> {\n const opts = { ...defaultCollectOptions, ...(options as any) };\n const collected = await collectSamples({ benchmark, params, ...opts });\n return [buildMeasuredResults(benchmark.name, collected)];\n }\n}\n\nconst defaultCollectOptions = {\n maxTime: 5000,\n maxIterations: 1000000,\n warmup: 0,\n traceOpt: false,\n noSettle: false,\n};\n\nfunction buildMeasuredResults(name: string, c: CollectResult): MeasuredResults {\n const time = computeStats(c.samples);\n return {\n name,\n samples: c.samples,\n warmupSamples: c.warmupSamples,\n heapSamples: c.heapSamples,\n timestamps: c.timestamps,\n time,\n heapSize: { avg: c.heapGrowth, min: c.heapGrowth, max: c.heapGrowth },\n optStatus: c.optStatus,\n optSamples: c.optSamples,\n pausePoints: c.pausePoints,\n };\n}\n\n/** @return timing samples and amortized allocation from benchmark execution */\nasync function collectSamples<T>(p: CollectParams<T>): Promise<CollectResult> {\n if (!p.maxIterations && !p.maxTime) {\n throw new Error(`At least one of maxIterations or maxTime must be set`);\n }\n const warmupSamples = p.skipWarmup ? [] : await runWarmup(p);\n const heapBefore = process.memoryUsage().heapUsed;\n const { samples, heapSamples, timestamps, optStatuses, pausePoints } =\n await runSampleLoop(p);\n const heapGrowth =\n Math.max(0, process.memoryUsage().heapUsed - heapBefore) /\n 1024 /\n samples.length;\n if (samples.length === 0) {\n throw new Error(`No samples collected for benchmark: ${p.benchmark.name}`);\n }\n const optStatus = p.traceOpt\n ? analyzeOptStatus(samples, optStatuses)\n : undefined;\n const optSamples =\n p.traceOpt && optStatuses.length > 0 ? optStatuses : undefined;\n return {\n samples,\n warmupSamples,\n heapGrowth,\n heapSamples,\n timestamps,\n optStatus,\n optSamples,\n pausePoints,\n };\n}\n\n/** Run warmup iterations with gc + settle time for V8 optimization */\nasync function runWarmup<T>(p: CollectParams<T>): Promise<number[]> {\n const gc = gcFunction();\n const samples = new Array<number>(p.warmup);\n for (let i = 0; i < p.warmup; i++) {\n const start = performance.now();\n executeBenchmark(p.benchmark, p.params);\n samples[i] = performance.now() - start;\n }\n gc();\n if (!p.noSettle) {\n await new Promise(r => setTimeout(r, gcSettleTime));\n gc();\n }\n return samples;\n}\n\ntype SampleLoopResult = {\n samples: number[];\n heapSamples?: number[];\n timestamps?: number[];\n optStatuses: number[];\n pausePoints: PausePoint[];\n};\n\n/** Estimate sample count for pre-allocation */\nfunction estimateSampleCount(maxTime: number, maxIterations: number): number {\n return maxIterations || Math.ceil(maxTime / 0.1); // assume 0.1ms per iteration minimum\n}\n\ntype SampleArrays = {\n samples: number[];\n timestamps: number[];\n heapSamples: number[];\n optStatuses: number[];\n pausePoints: PausePoint[];\n};\n\n/** Pre-allocate arrays to reduce GC pressure during measurement */\nfunction createSampleArrays(\n n: number,\n trackHeap: boolean,\n trackOpt: boolean,\n): SampleArrays {\n const arr = (track: boolean) => (track ? new Array<number>(n) : []);\n return {\n samples: new Array<number>(n),\n timestamps: new Array<number>(n),\n heapSamples: arr(trackHeap),\n optStatuses: arr(trackOpt),\n pausePoints: [],\n };\n}\n\n/** Trim arrays to actual sample count */\nfunction trimArrays(\n a: SampleArrays,\n count: number,\n trackHeap: boolean,\n trackOpt: boolean,\n): void {\n a.samples.length = a.timestamps.length = count;\n if (trackHeap) a.heapSamples.length = count;\n if (trackOpt) a.optStatuses.length = count;\n}\n\n/** Collect timing samples with periodic pauses for V8 optimization */\nasync function runSampleLoop<T>(\n p: CollectParams<T>,\n): Promise<SampleLoopResult> {\n const {\n maxTime,\n maxIterations,\n pauseFirst,\n pauseInterval = 0,\n pauseDuration = 100,\n } = p;\n const trackHeap = true; // Always track heap for charts\n const getOptStatus = p.traceOpt ? createOptStatusGetter() : undefined;\n const estimated = estimateSampleCount(maxTime, maxIterations);\n const a = createSampleArrays(estimated, trackHeap, !!getOptStatus);\n\n let count = 0;\n let elapsed = 0;\n let totalPauseTime = 0;\n const loopStart = performance.now();\n\n while (\n (!maxIterations || count < maxIterations) &&\n (!maxTime || elapsed < maxTime)\n ) {\n const start = performance.now();\n executeBenchmark(p.benchmark, p.params);\n const end = performance.now();\n a.samples[count] = end - start;\n a.timestamps[count] = Number(process.hrtime.bigint() / 1000n);\n if (trackHeap) a.heapSamples[count] = getHeapStatistics().used_heap_size;\n if (getOptStatus) a.optStatuses[count] = getOptStatus(p.benchmark.fn);\n count++;\n\n if (shouldPause(count, pauseFirst, pauseInterval)) {\n a.pausePoints.push({ sampleIndex: count - 1, durationMs: pauseDuration });\n const pauseStart = performance.now();\n await new Promise(r => setTimeout(r, pauseDuration));\n totalPauseTime += performance.now() - pauseStart;\n }\n elapsed = performance.now() - loopStart - totalPauseTime;\n }\n\n trimArrays(a, count, trackHeap, !!getOptStatus);\n return {\n samples: a.samples,\n heapSamples: trackHeap ? a.heapSamples : undefined,\n timestamps: a.timestamps,\n optStatuses: a.optStatuses,\n pausePoints: a.pausePoints,\n };\n}\n\n/** Check if we should pause at this iteration for V8 optimization */\nfunction shouldPause(\n iter: number,\n first: number | undefined,\n interval: number,\n): boolean {\n if (first !== undefined && iter === first) return true;\n if (interval <= 0) return false;\n if (first === undefined) return iter % interval === 0;\n return (iter - first) % interval === 0;\n}\n\n/** @return percentiles and basic statistics */\nexport function computeStats(samples: number[]): SampleTimeStats {\n const sorted = [...samples].sort((a, b) => a - b);\n const avg = samples.reduce((sum, s) => sum + s, 0) / samples.length;\n return {\n min: sorted[0],\n max: sorted[sorted.length - 1],\n avg,\n p50: percentile(sorted, 0.5),\n p75: percentile(sorted, 0.75),\n p99: percentile(sorted, 0.99),\n p999: percentile(sorted, 0.999),\n };\n}\n\n/** @return percentile value with linear interpolation */\nfunction percentile(sortedArray: number[], p: number): number {\n const index = (sortedArray.length - 1) * p;\n const lower = Math.floor(index);\n const upper = Math.ceil(index);\n const weight = index % 1;\n\n if (upper >= sortedArray.length) return sortedArray[sortedArray.length - 1];\n\n return sortedArray[lower] * (1 - weight) + sortedArray[upper] * weight;\n}\n\n/** @return runtime gc() function, or no-op if unavailable */\nfunction gcFunction(): () => void {\n const gc = globalThis.gc || (globalThis as any).__gc;\n if (gc) return gc;\n console.warn(\"gc() not available, run node/bun with --expose-gc\");\n return () => {};\n}\n\n/** @return function to get V8 optimization status (requires --allow-natives-syntax) */\nfunction createOptStatusGetter(): ((fn: unknown) => number) | undefined {\n try {\n // %GetOptimizationStatus returns a bitmask\n const getter = new Function(\"f\", \"return %GetOptimizationStatus(f)\");\n getter(() => {});\n return getter as (fn: unknown) => number;\n } catch {\n return undefined;\n }\n}\n\n/**\n * V8 optimization status bit meanings:\n * Bit 0 (1): is_function\n * Bit 4 (16): is_optimized (TurboFan)\n * Bit 5 (32): is_optimized (Maglev)\n * Bit 7 (128): is_baseline (Sparkplug)\n * Bit 3 (8): maybe_deoptimized\n */\nconst statusNames: Record<number, string> = {\n 1: \"interpreted\",\n 129: \"sparkplug\", // 1 + 128\n 17: \"turbofan\", // 1 + 16\n 33: \"maglev\", // 1 + 32\n 49: \"turbofan+maglev\", // 1 + 16 + 32\n 32769: \"optimized\", // common optimized status\n};\n\n/** @return analysis of V8 optimization status per sample */\nfunction analyzeOptStatus(\n samples: number[],\n statuses: number[],\n): OptStatusInfo | undefined {\n if (statuses.length === 0 || statuses[0] === undefined) return undefined;\n\n const byStatusCode = new Map<number, number[]>();\n let deoptCount = 0;\n\n for (let i = 0; i < samples.length; i++) {\n const status = statuses[i];\n if (status === undefined) continue;\n\n // Check deopt flag (bit 3)\n if (status & 8) deoptCount++;\n\n if (!byStatusCode.has(status)) byStatusCode.set(status, []);\n byStatusCode.get(status)!.push(samples[i]);\n }\n\n const byTier: Record<string, { count: number; medianMs: number }> = {};\n for (const [status, times] of byStatusCode) {\n const name = statusNames[status] || `status=${status}`;\n const sorted = [...times].sort((a, b) => a - b);\n const median = sorted[Math.floor(sorted.length / 2)];\n byTier[name] = { count: times.length, medianMs: median };\n }\n\n return { byTier, deoptCount };\n}\n","const outlierMultiplier = 1.5; // Tukey's fence multiplier\nconst bootstrapSamples = 10000;\nconst confidence = 0.95;\n\n/** Options for bootstrap resampling methods */\ntype BootstrapOptions = {\n resamples?: number;\n confidence?: number;\n};\n\n/** Bootstrap estimate with confidence interval and raw resample data */\nexport interface BootstrapResult {\n estimate: number;\n ci: [number, number];\n samples: number[];\n}\n\n/** @return relative standard deviation (coefficient of variation) */\nexport function coefficientOfVariation(samples: number[]): number {\n const mean = average(samples);\n if (mean === 0) return 0;\n const stdDev = standardDeviation(samples);\n return stdDev / mean;\n}\n\n/** @return median absolute deviation for robust variability measure */\nexport function medianAbsoluteDeviation(samples: number[]): number {\n const median = percentile(samples, 0.5);\n const deviations = samples.map(x => Math.abs(x - median));\n return percentile(deviations, 0.5);\n}\n\n/** @return outliers detected via Tukey's interquartile range method */\nexport function findOutliers(samples: number[]): {\n rate: number;\n indices: number[];\n} {\n const q1 = percentile(samples, 0.25);\n const q3 = percentile(samples, 0.75);\n const iqr = q3 - q1;\n const lowerBound = q1 - outlierMultiplier * iqr;\n const upperBound = q3 + outlierMultiplier * iqr;\n\n const indices = samples\n .map((v, i) => (v < lowerBound || v > upperBound ? i : -1))\n .filter(i => i >= 0);\n return { rate: indices.length / samples.length, indices };\n}\n\n/** @return bootstrap confidence interval for median */\nexport function bootstrapMedian(\n samples: number[],\n options: BootstrapOptions = {},\n): BootstrapResult {\n const { resamples = bootstrapSamples, confidence: conf = confidence } =\n options;\n const medians = generateMedians(samples, resamples);\n const ci = computeInterval(medians, conf);\n\n return {\n estimate: percentile(samples, 0.5),\n ci,\n samples: medians,\n };\n}\n\n/** @return mean of values */\nexport function average(values: number[]): number {\n const sum = values.reduce((a, b) => a + b, 0);\n return sum / values.length;\n}\n\n/** @return standard deviation with Bessel's correction */\nexport function standardDeviation(samples: number[]): number {\n if (samples.length <= 1) return 0;\n const mean = average(samples);\n const variance =\n samples.reduce((sum, x) => sum + (x - mean) ** 2, 0) / (samples.length - 1);\n return Math.sqrt(variance);\n}\n\n/** @return value at percentile p (0-1) */\nexport function percentile(values: number[], p: number): number {\n const sorted = [...values].sort((a, b) => a - b);\n const index = Math.ceil(sorted.length * p) - 1;\n return sorted[Math.max(0, index)];\n}\n\n/** @return medians from bootstrap resamples */\nfunction generateMedians(samples: number[], resamples: number): number[] {\n return Array.from({ length: resamples }, () =>\n percentile(createResample(samples), 0.5),\n );\n}\n\n/** @return bootstrap resample with replacement */\nexport function createResample(samples: number[]): number[] {\n const n = samples.length;\n const rand = () => samples[Math.floor(Math.random() * n)];\n return Array.from({ length: n }, rand);\n}\n\n/** @return confidence interval [lower, upper] */\nfunction computeInterval(\n medians: number[],\n confidence: number,\n): [number, number] {\n const alpha = (1 - confidence) / 2;\n const lower = percentile(medians, alpha);\n const upper = percentile(medians, 1 - alpha);\n return [lower, upper];\n}\n\nexport type CIDirection = \"faster\" | \"slower\" | \"uncertain\";\n\n/** Binned histogram for efficient transfer to browser */\nexport interface HistogramBin {\n x: number; // bin center\n count: number;\n}\n\n/** Bootstrap confidence interval for percentage difference between two samples */\nexport interface DifferenceCI {\n percent: number;\n ci: [number, number];\n direction: CIDirection;\n /** Histogram of bootstrap distribution for visualization */\n histogram?: HistogramBin[];\n}\n\n/** Bin values into histogram for compact visualization */\nfunction binValues(values: number[], binCount = 30): HistogramBin[] {\n const sorted = [...values].sort((a, b) => a - b);\n const min = sorted[0];\n const max = sorted[sorted.length - 1];\n if (min === max) return [{ x: min, count: values.length }];\n\n const step = (max - min) / binCount;\n const counts = new Array(binCount).fill(0);\n for (const v of values) {\n const bin = Math.min(Math.floor((v - min) / step), binCount - 1);\n counts[bin]++;\n }\n return counts.map((count, i) => ({ x: min + (i + 0.5) * step, count }));\n}\n\n/** @return bootstrap CI for percentage difference between baseline and current medians */\nexport function bootstrapDifferenceCI(\n baseline: number[],\n current: number[],\n options: BootstrapOptions = {},\n): DifferenceCI {\n const { resamples = bootstrapSamples, confidence: conf = confidence } =\n options;\n\n const baselineMedian = percentile(baseline, 0.5);\n const currentMedian = percentile(current, 0.5);\n const observedPercent =\n ((currentMedian - baselineMedian) / baselineMedian) * 100;\n\n const diffs: number[] = [];\n for (let i = 0; i < resamples; i++) {\n const resB = createResample(baseline);\n const resC = createResample(current);\n const medB = percentile(resB, 0.5);\n const medC = percentile(resC, 0.5);\n diffs.push(((medC - medB) / medB) * 100);\n }\n\n const ci = computeInterval(diffs, conf);\n const excludesZero = ci[0] > 0 || ci[1] < 0;\n let direction: CIDirection = \"uncertain\";\n if (excludesZero) direction = observedPercent < 0 ? \"faster\" : \"slower\";\n const histogram = binValues(diffs);\n return { percent: observedPercent, ci, direction, histogram };\n}\n","export const msToNs = 1e6;\nexport const nsToMs = 1e-6;\n","import type { BenchmarkSpec } from \"../Benchmark.ts\";\nimport type { MeasuredResults } from \"../MeasuredResults.ts\";\nimport {\n coefficientOfVariation,\n medianAbsoluteDeviation,\n percentile,\n} from \"../StatisticalUtils.ts\";\nimport type { BenchRunner, RunnerOptions } from \"./BenchRunner.ts\";\nimport { msToNs } from \"./RunnerUtils.ts\";\n\nconst minTime = 1000;\nconst maxTime = 10000;\nconst targetConfidence = 95;\nconst fallbackThreshold = 80;\nconst windowSize = 50;\nconst stability = 0.05; // 5% drift threshold (was 2%, too strict for real benchmarks)\nconst initialBatch = 100;\nconst continueBatch = 100;\nconst continueIterations = 10;\n\ntype Metrics = {\n medianDrift: number;\n impactDrift: number;\n medianStable: boolean;\n impactStable: boolean;\n};\n\ninterface ConvergenceResult {\n converged: boolean;\n confidence: number;\n reason: string;\n}\n\nexport interface AdaptiveOptions extends RunnerOptions {\n adaptive?: boolean;\n minTime?: number;\n maxTime?: number;\n targetConfidence?: number;\n convergence?: number; // Confidence threshold (0-100)\n}\n\n/** @return adaptive sampling runner wrapper */\nexport function createAdaptiveWrapper(\n baseRunner: BenchRunner,\n options: AdaptiveOptions,\n): BenchRunner {\n return {\n async runBench<T = unknown>(\n benchmark: BenchmarkSpec<T>,\n runnerOptions: RunnerOptions,\n params?: T,\n ): Promise<MeasuredResults[]> {\n return runAdaptiveBench(\n baseRunner,\n benchmark,\n runnerOptions,\n options,\n params,\n );\n },\n };\n}\n\n/** @return results using adaptive sampling strategy */\nasync function runAdaptiveBench<T>(\n baseRunner: BenchRunner,\n benchmark: BenchmarkSpec<T>,\n runnerOptions: RunnerOptions,\n options: AdaptiveOptions,\n params?: T,\n): Promise<MeasuredResults[]> {\n const {\n minTime: min = options.minTime ?? minTime,\n maxTime: max = options.maxTime ?? maxTime,\n targetConfidence: target = options.convergence ?? targetConfidence,\n } = runnerOptions as AdaptiveOptions;\n const allSamples: number[] = [];\n\n // Collect initial batch (includes warmup + settle)\n const warmup = await collectInitial(\n baseRunner,\n benchmark,\n runnerOptions,\n params,\n allSamples,\n );\n\n // Start timing AFTER warmup - warmup time doesn't count against maxTime\n const startTime = performance.now();\n\n const limits = {\n minTime: min,\n maxTime: max,\n targetConfidence: target,\n startTime,\n };\n await collectAdaptive(\n baseRunner,\n benchmark,\n runnerOptions,\n params,\n allSamples,\n limits,\n );\n\n const convergence = checkConvergence(allSamples.map(s => s * msToNs));\n return buildResults(\n allSamples,\n startTime,\n convergence,\n benchmark.name,\n warmup,\n );\n}\n\n/** @return warmupSamples from initial batch */\nasync function collectInitial<T>(\n baseRunner: BenchRunner,\n benchmark: BenchmarkSpec<T>,\n runnerOptions: RunnerOptions,\n params: T | undefined,\n allSamples: number[],\n): Promise<number[] | undefined> {\n // Don't pass adaptive flag to base runner to avoid double wrapping\n const opts = {\n ...(runnerOptions as any),\n maxTime: initialBatch,\n maxIterations: undefined,\n };\n const results = await baseRunner.runBench(benchmark, opts, params);\n appendSamples(results[0], allSamples);\n return results[0].warmupSamples;\n}\n\n/** @return samples until convergence or timeout */\nasync function collectAdaptive<T>(\n baseRunner: BenchRunner,\n benchmark: BenchmarkSpec<T>,\n runnerOptions: RunnerOptions,\n params: T | undefined,\n allSamples: number[],\n limits: {\n minTime: number;\n maxTime: number;\n targetConfidence: number;\n startTime: number;\n },\n): Promise<void> {\n const { minTime, maxTime, targetConfidence, startTime } = limits;\n let lastLog = 0;\n while (performance.now() - startTime < maxTime) {\n const samplesNs = allSamples.map(s => s * msToNs);\n const convergence = checkConvergence(samplesNs);\n const elapsed = performance.now() - startTime;\n\n if (elapsed - lastLog > 1000) {\n const elapsedSec = (elapsed / 1000).toFixed(1);\n const conf = convergence.confidence.toFixed(0);\n process.stderr.write(\n `\\r◊ ${benchmark.name}: ${conf}% confident (${elapsedSec}s) `,\n );\n lastLog = elapsed;\n }\n\n if (shouldStop(convergence, targetConfidence, elapsed, minTime)) {\n break;\n }\n\n // Skip warmup for continuation batches (warmup done in initial batch)\n const opts = {\n ...(runnerOptions as any),\n maxTime: continueBatch,\n maxIterations: continueIterations,\n skipWarmup: true,\n };\n const batchResults = await baseRunner.runBench(benchmark, opts, params);\n appendSamples(batchResults[0], allSamples);\n }\n process.stderr.write(\"\\r\" + \" \".repeat(60) + \"\\r\");\n}\n\n/** Append samples one-by-one to avoid stack overflow from spread on large arrays */\nfunction appendSamples(result: MeasuredResults, samples: number[]): void {\n if (!result.samples?.length) return;\n for (const sample of result.samples) samples.push(sample);\n}\n\n/** @return true if convergence reached or timeout */\nfunction shouldStop(\n convergence: ConvergenceResult,\n targetConfidence: number,\n elapsedTime: number,\n minTime: number,\n): boolean {\n if (convergence.converged && convergence.confidence >= targetConfidence) {\n return true;\n }\n // After minTime, accept whichever is higher: targetConfidence or fallbackThreshold\n const threshold = Math.max(targetConfidence, fallbackThreshold);\n return elapsedTime >= minTime && convergence.confidence >= threshold;\n}\n\n/** @return measured results with convergence metrics */\nfunction buildResults(\n samplesMs: number[],\n startTime: number,\n convergence: ConvergenceResult,\n name: string,\n warmupSamples?: number[],\n): MeasuredResults[] {\n const totalTime = (performance.now() - startTime) / 1000;\n const samplesNs = samplesMs.map(s => s * msToNs);\n const timeStats = computeTimeStats(samplesNs);\n\n return [\n {\n name,\n samples: samplesMs,\n warmupSamples,\n time: timeStats,\n totalTime,\n convergence,\n },\n ];\n}\n\n/** @return time percentiles and statistics in ms */\nfunction computeTimeStats(samplesNs: number[]) {\n const samplesMs = samplesNs.map(s => s / msToNs);\n const { min, max, sum } = getMinMaxSum(samplesNs);\n const percentiles = getPercentiles(samplesNs);\n const robust = getRobustMetrics(samplesMs);\n\n return {\n min: min / msToNs,\n max: max / msToNs,\n avg: sum / samplesNs.length / msToNs,\n ...percentiles,\n ...robust,\n };\n}\n\n/** @return min, max, sum of samples */\nfunction getMinMaxSum(samples: number[]) {\n const min = samples.reduce(\n (a, b) => Math.min(a, b),\n Number.POSITIVE_INFINITY,\n );\n const max = samples.reduce(\n (a, b) => Math.max(a, b),\n Number.NEGATIVE_INFINITY,\n );\n const sum = samples.reduce((a, b) => a + b, 0);\n return { min, max, sum };\n}\n\n/** @return percentiles in ms */\nfunction getPercentiles(samples: number[]) {\n return {\n p25: percentile(samples, 0.25) / msToNs,\n p50: percentile(samples, 0.5) / msToNs,\n p75: percentile(samples, 0.75) / msToNs,\n p95: percentile(samples, 0.95) / msToNs,\n p99: percentile(samples, 0.99) / msToNs,\n p999: percentile(samples, 0.999) / msToNs,\n };\n}\n\n/** @return robust variability metrics */\nfunction getRobustMetrics(samplesMs: number[]) {\n const impact = getOutlierImpact(samplesMs);\n return {\n cv: coefficientOfVariation(samplesMs),\n mad: medianAbsoluteDeviation(samplesMs),\n outlierRate: impact.ratio,\n };\n}\n\n/** @return outlier impact as proportion of total time */\nfunction getOutlierImpact(samples: number[]): { ratio: number; count: number } {\n if (samples.length === 0) return { ratio: 0, count: 0 };\n\n const median = percentile(samples, 0.5);\n const q75 = percentile(samples, 0.75);\n const threshold = median + 1.5 * (q75 - median);\n\n let excessTime = 0;\n let count = 0;\n\n for (const sample of samples) {\n if (sample > threshold) {\n excessTime += sample - median;\n count++;\n }\n }\n\n const totalTime = samples.reduce((a, b) => a + b, 0);\n return {\n ratio: totalTime > 0 ? excessTime / totalTime : 0,\n count,\n };\n}\n\n/** @return convergence based on window stability */\nexport function checkConvergence(samples: number[]): ConvergenceResult {\n const windowSize = getWindowSize(samples);\n const minSamples = windowSize * 2;\n\n if (samples.length < minSamples) {\n return buildProgressResult(samples.length, minSamples);\n }\n\n const metrics = getStability(samples, windowSize);\n return buildConvergence(metrics);\n}\n\n/** @return progress when samples insufficient */\nfunction buildProgressResult(\n currentSamples: number,\n minSamples: number,\n): ConvergenceResult {\n return {\n converged: false,\n confidence: (currentSamples / minSamples) * 100,\n reason: `Collecting samples: ${currentSamples}/${minSamples}`,\n };\n}\n\n/** @return stability metrics between windows */\nfunction getStability(samples: number[], windowSize: number): Metrics {\n const recent = samples.slice(-windowSize);\n const previous = samples.slice(-windowSize * 2, -windowSize);\n\n const recentMs = recent.map(s => s / msToNs);\n const previousMs = previous.map(s => s / msToNs);\n\n const medianRecent = percentile(recentMs, 0.5);\n const medianPrevious = percentile(previousMs, 0.5);\n const medianDrift = Math.abs(medianRecent - medianPrevious) / medianPrevious;\n\n const impactRecent = getOutlierImpact(recentMs);\n const impactPrevious = getOutlierImpact(previousMs);\n const impactDrift = Math.abs(impactRecent.ratio - impactPrevious.ratio);\n\n return {\n medianDrift,\n impactDrift,\n medianStable: medianDrift < stability,\n impactStable: impactDrift < stability,\n };\n}\n\n/** @return convergence from stability metrics */\nfunction buildConvergence(metrics: Metrics): ConvergenceResult {\n const { medianDrift, impactDrift, medianStable, impactStable } = metrics;\n\n if (medianStable && impactStable) {\n return {\n converged: true,\n confidence: 100,\n reason: \"Stable performance pattern\",\n };\n }\n\n const confidence = Math.min(\n 100,\n (1 - medianDrift / stability) * 50 + (1 - impactDrift / stability) * 50,\n );\n\n const reason =\n medianDrift > impactDrift\n ? `Median drifting: ${(medianDrift * 100).toFixed(1)}%`\n : `Outlier impact changing: ${(impactDrift * 100).toFixed(1)}%`;\n\n return { converged: false, confidence: Math.max(0, confidence), reason };\n}\n\n/** @return window size scaled to execution time */\nfunction getWindowSize(samples: number[]): number {\n if (samples.length < 20) return windowSize; // Default for initial samples\n\n const recentMs = samples.slice(-20).map(s => s / msToNs);\n const recentMedian = percentile(recentMs, 0.5);\n\n // Inverse scaling with execution time\n if (recentMedian < 0.01) return 200; // <10μs\n if (recentMedian < 0.1) return 100; // <100μs\n if (recentMedian < 1) return 50; // <1ms\n if (recentMedian < 10) return 30; // <10ms\n return 20; // >10ms\n}\n","import { BasicRunner } from \"./BasicRunner.ts\";\nimport type { BenchRunner } from \"./BenchRunner.ts\";\n\nexport type KnownRunner = \"basic\";\n\n/** @return benchmark runner */\nexport async function createRunner(\n _runnerName: KnownRunner,\n): Promise<BenchRunner> {\n return new BasicRunner();\n}\n","export const debugWorkerTiming = false;\n\n/** Get current time or 0 if debugging disabled */\nexport function getPerfNow(): number {\n return debugWorkerTiming ? performance.now() : 0;\n}\n\n/** Calculate elapsed milliseconds between marks */\nexport function getElapsed(startMark: number, endMark?: number): number {\n if (!debugWorkerTiming) return 0;\n const end = endMark ?? performance.now();\n return end - startMark;\n}\n"],"mappings":"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"}
|
package/dist/bin/benchforge.mjs
CHANGED
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, r as BenchSuite, t as RunnerOptions } from "./BenchRunner-CSKN9zPy.mjs";
|
|
2
2
|
import { Alignment } from "table";
|
|
3
3
|
import { Argv, InferredOptionTypes } from "yargs";
|
|
4
4
|
import * as node_http0 from "node:http";
|
|
@@ -185,17 +185,17 @@ declare const cliOptions: {
|
|
|
185
185
|
readonly adaptive: {
|
|
186
186
|
readonly type: "boolean";
|
|
187
187
|
readonly default: false;
|
|
188
|
-
readonly describe: "
|
|
188
|
+
readonly describe: "adaptive sampling (experimental)";
|
|
189
189
|
};
|
|
190
190
|
readonly "min-time": {
|
|
191
191
|
readonly type: "number";
|
|
192
192
|
readonly default: 1;
|
|
193
|
-
readonly describe: "minimum time
|
|
193
|
+
readonly describe: "minimum time before adaptive convergence can stop";
|
|
194
194
|
};
|
|
195
195
|
readonly convergence: {
|
|
196
196
|
readonly type: "number";
|
|
197
197
|
readonly default: 95;
|
|
198
|
-
readonly describe: "confidence threshold (0-100)";
|
|
198
|
+
readonly describe: "adaptive confidence threshold (0-100)";
|
|
199
199
|
};
|
|
200
200
|
readonly warmup: {
|
|
201
201
|
readonly type: "number";
|
|
@@ -306,6 +306,11 @@ declare const cliOptions: {
|
|
|
306
306
|
readonly default: 60;
|
|
307
307
|
readonly describe: "browser page timeout in seconds";
|
|
308
308
|
};
|
|
309
|
+
readonly "chrome-args": {
|
|
310
|
+
readonly type: "string";
|
|
311
|
+
readonly requiresArg: true;
|
|
312
|
+
readonly describe: "extra Chromium flags (space-separated)";
|
|
313
|
+
};
|
|
309
314
|
};
|
|
310
315
|
/** @return yargs with standard benchmark options */
|
|
311
316
|
declare function defaultCliArgs(yargsInstance: Argv): Argv<DefaultCliArgs>;
|
|
@@ -631,9 +636,8 @@ interface TimeStats {
|
|
|
631
636
|
mean?: number;
|
|
632
637
|
p50?: number;
|
|
633
638
|
p99?: number;
|
|
634
|
-
convergence?: number;
|
|
635
639
|
}
|
|
636
|
-
/** Section: mean, p50, p99 timing
|
|
640
|
+
/** Section: mean, p50, p99 timing */
|
|
637
641
|
declare const timeSection: ResultsMapper<TimeStats>;
|
|
638
642
|
interface GcSectionStats {
|
|
639
643
|
gc?: number;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as timeSection, B as parseCliArgs, C as adaptiveSection, D as gcStatsSection, E as gcSection, F as generateHtmlReport, G as truncate, H as formatBytes, I as formatDateWithTimezone, J as loadCaseData, K as isStatefulVariant, L as prepareHtmlData, M as formatConvergence, N as filterMatrix, O as optSection, P as parseMatrixFilter, R as exportPerfettoTrace, S as reportMatrixResults, T as cpuSection, U as integer, V as reportResults, W as timeMs, Y as loadCasesModule, _ 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 runMatrix, 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 defaultCliArgs } from "./src-
|
|
2
|
-
import {
|
|
1
|
+
import { A as timeSection, B as parseCliArgs, C as adaptiveSection, D as gcStatsSection, E as gcSection, F as generateHtmlReport, G as truncate, H as formatBytes, I as formatDateWithTimezone, J as loadCaseData, K as isStatefulVariant, L as prepareHtmlData, M as formatConvergence, N as filterMatrix, O as optSection, P as parseMatrixFilter, R as exportPerfettoTrace, S as reportMatrixResults, T as cpuSection, U as integer, V as reportResults, W as timeMs, Y as loadCasesModule, _ 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 runMatrix, 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 defaultCliArgs } from "./src-B06_i1RD.mjs";
|
|
2
|
+
import { o as average } from "./TimingUtils-ClclVQ7E.mjs";
|
|
3
3
|
|
|
4
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 };
|
|
@@ -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-CSKN9zPy.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runners/CreateRunner.d.ts
|
|
4
4
|
type KnownRunner = "basic";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { a as createAdaptiveWrapper, d as variantModuleUrl, i as createRunner, n as getElapsed, r as getPerfNow, t as debugWorkerTiming } from "../TimingUtils-ClclVQ7E.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/runners/WorkerScript.ts
|
|
5
5
|
const workerStartTime = getPerfNow();
|
|
@@ -18,7 +18,7 @@ process.on("message", async (message) => {
|
|
|
18
18
|
logTiming("Runner created in", getElapsed(start));
|
|
19
19
|
const benchStart = getPerfNow();
|
|
20
20
|
if (message.options.heapSample) {
|
|
21
|
-
const { withHeapSampling } = await import("../HeapSampler-
|
|
21
|
+
const { withHeapSampling } = await import("../HeapSampler-B8dtKHn1.mjs");
|
|
22
22
|
const { result: results, profile: heapProfile } = await withHeapSampling({
|
|
23
23
|
samplingInterval: message.options.heapInterval,
|
|
24
24
|
stackDepth: message.options.heapDepth
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as createAdaptiveWrapper, c as BasicRunner, i as createRunner, l as computeStats, n as getElapsed, o as average, r as getPerfNow, s as bootstrapDifferenceCI, t as debugWorkerTiming, u as discoverVariants } from "./TimingUtils-ClclVQ7E.mjs";
|
|
2
|
+
import { n as parseGcLine, t as aggregateGcStats } from "./GcStats-ByEovUi1.mjs";
|
|
2
3
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { execSync, fork, spawn } from "node:child_process";
|
|
@@ -8,7 +9,6 @@ import pico from "picocolors";
|
|
|
8
9
|
import { table } from "table";
|
|
9
10
|
import yargs from "yargs";
|
|
10
11
|
import { hideBin } from "yargs/helpers";
|
|
11
|
-
import { chromium } from "playwright";
|
|
12
12
|
import { createServer } from "node:http";
|
|
13
13
|
import open from "open";
|
|
14
14
|
|
|
@@ -30,81 +30,6 @@ async function loadCaseData(casesModule, caseId) {
|
|
|
30
30
|
return { data: caseId };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/runners/GcStats.ts
|
|
35
|
-
/** Parse a single --trace-gc-nvp stderr line */
|
|
36
|
-
function parseGcLine(line) {
|
|
37
|
-
if (!line.includes("pause=")) return void 0;
|
|
38
|
-
const fields = parseNvpFields(line);
|
|
39
|
-
if (!fields.gc) return void 0;
|
|
40
|
-
const int = (k) => Number.parseInt(fields[k] || "0", 10);
|
|
41
|
-
const type = parseGcType(fields.gc);
|
|
42
|
-
const pauseMs = Number.parseFloat(fields.pause || "0");
|
|
43
|
-
const allocated = int("allocated");
|
|
44
|
-
const promoted = int("promoted");
|
|
45
|
-
const survived = int("new_space_survived") || int("survived");
|
|
46
|
-
const startSize = int("start_object_size");
|
|
47
|
-
const endSize = int("end_object_size");
|
|
48
|
-
const collected = startSize > endSize ? startSize - endSize : 0;
|
|
49
|
-
if (Number.isNaN(pauseMs)) return void 0;
|
|
50
|
-
return {
|
|
51
|
-
type,
|
|
52
|
-
pauseMs,
|
|
53
|
-
allocated,
|
|
54
|
-
collected,
|
|
55
|
-
promoted,
|
|
56
|
-
survived
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
/** Parse name=value pairs from trace-gc-nvp line */
|
|
60
|
-
function parseNvpFields(line) {
|
|
61
|
-
const fields = {};
|
|
62
|
-
const matches = line.matchAll(/(\w+)=([^\s,]+)/g);
|
|
63
|
-
for (const [, key, value] of matches) fields[key] = value;
|
|
64
|
-
return fields;
|
|
65
|
-
}
|
|
66
|
-
/** Map V8 gc type codes to our types */
|
|
67
|
-
function parseGcType(gcField) {
|
|
68
|
-
if (gcField === "s" || gcField === "scavenge") return "scavenge";
|
|
69
|
-
if (gcField === "mc" || gcField === "ms" || gcField === "mark-compact") return "mark-compact";
|
|
70
|
-
if (gcField === "mmc" || gcField === "minor-mc" || gcField === "minor-ms") return "minor-ms";
|
|
71
|
-
return "unknown";
|
|
72
|
-
}
|
|
73
|
-
/** Aggregate GC events into summary stats */
|
|
74
|
-
function aggregateGcStats(events) {
|
|
75
|
-
let scavenges = 0;
|
|
76
|
-
let markCompacts = 0;
|
|
77
|
-
let gcPauseTime = 0;
|
|
78
|
-
let totalCollected = 0;
|
|
79
|
-
let hasNodeFields = false;
|
|
80
|
-
let totalAllocated = 0;
|
|
81
|
-
let totalPromoted = 0;
|
|
82
|
-
let totalSurvived = 0;
|
|
83
|
-
for (const e of events) {
|
|
84
|
-
if (e.type === "scavenge" || e.type === "minor-ms") scavenges++;
|
|
85
|
-
else if (e.type === "mark-compact") markCompacts++;
|
|
86
|
-
gcPauseTime += e.pauseMs;
|
|
87
|
-
totalCollected += e.collected;
|
|
88
|
-
if (e.allocated != null) {
|
|
89
|
-
hasNodeFields = true;
|
|
90
|
-
totalAllocated += e.allocated;
|
|
91
|
-
totalPromoted += e.promoted ?? 0;
|
|
92
|
-
totalSurvived += e.survived ?? 0;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
scavenges,
|
|
97
|
-
markCompacts,
|
|
98
|
-
totalCollected,
|
|
99
|
-
gcPauseTime,
|
|
100
|
-
...hasNodeFields && {
|
|
101
|
-
totalAllocated,
|
|
102
|
-
totalPromoted,
|
|
103
|
-
totalSurvived
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
33
|
//#endregion
|
|
109
34
|
//#region src/runners/RunnerOrchestrator.ts
|
|
110
35
|
const logTiming = debugWorkerTiming ? (message) => console.log(`[RunnerOrchestrator] ${message}`) : () => {};
|
|
@@ -889,17 +814,17 @@ const cliOptions = {
|
|
|
889
814
|
adaptive: {
|
|
890
815
|
type: "boolean",
|
|
891
816
|
default: false,
|
|
892
|
-
describe: "
|
|
817
|
+
describe: "adaptive sampling (experimental)"
|
|
893
818
|
},
|
|
894
819
|
"min-time": {
|
|
895
820
|
type: "number",
|
|
896
821
|
default: 1,
|
|
897
|
-
describe: "minimum time
|
|
822
|
+
describe: "minimum time before adaptive convergence can stop"
|
|
898
823
|
},
|
|
899
824
|
convergence: {
|
|
900
825
|
type: "number",
|
|
901
826
|
default: 95,
|
|
902
|
-
describe: "confidence threshold (0-100)"
|
|
827
|
+
describe: "adaptive confidence threshold (0-100)"
|
|
903
828
|
},
|
|
904
829
|
warmup: {
|
|
905
830
|
type: "number",
|
|
@@ -1009,6 +934,11 @@ const cliOptions = {
|
|
|
1009
934
|
type: "number",
|
|
1010
935
|
default: 60,
|
|
1011
936
|
describe: "browser page timeout in seconds"
|
|
937
|
+
},
|
|
938
|
+
"chrome-args": {
|
|
939
|
+
type: "string",
|
|
940
|
+
requiresArg: true,
|
|
941
|
+
describe: "extra Chromium flags (space-separated)"
|
|
1012
942
|
}
|
|
1013
943
|
};
|
|
1014
944
|
/** @return yargs with standard benchmark options */
|
|
@@ -1020,185 +950,6 @@ function parseCliArgs(args, configure = defaultCliArgs) {
|
|
|
1020
950
|
return configure(yargs(args)).parseSync();
|
|
1021
951
|
}
|
|
1022
952
|
|
|
1023
|
-
//#endregion
|
|
1024
|
-
//#region src/browser/BrowserGcStats.ts
|
|
1025
|
-
/** Parse CDP trace events (MinorGC/MajorGC) into GcEvent[] */
|
|
1026
|
-
function parseGcTraceEvents(traceEvents) {
|
|
1027
|
-
return traceEvents.flatMap((e) => {
|
|
1028
|
-
if (e.ph !== "X") return [];
|
|
1029
|
-
const type = gcType(e.name);
|
|
1030
|
-
if (!type) return [];
|
|
1031
|
-
const durUs = e.dur ?? 0;
|
|
1032
|
-
const heapBefore = e.args?.usedHeapSizeBefore ?? 0;
|
|
1033
|
-
const heapAfter = e.args?.usedHeapSizeAfter ?? 0;
|
|
1034
|
-
return [{
|
|
1035
|
-
type,
|
|
1036
|
-
pauseMs: durUs / 1e3,
|
|
1037
|
-
collected: Math.max(0, heapBefore - heapAfter)
|
|
1038
|
-
}];
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
|
-
function gcType(name) {
|
|
1042
|
-
if (name === "MinorGC") return "scavenge";
|
|
1043
|
-
if (name === "MajorGC") return "mark-compact";
|
|
1044
|
-
}
|
|
1045
|
-
/** Parse CDP trace events and aggregate into GcStats */
|
|
1046
|
-
function browserGcStats(traceEvents) {
|
|
1047
|
-
return aggregateGcStats(parseGcTraceEvents(traceEvents));
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
//#endregion
|
|
1051
|
-
//#region src/browser/BrowserHeapSampler.ts
|
|
1052
|
-
/** Run browser benchmark, auto-detecting page API mode.
|
|
1053
|
-
* Bench function (window.__bench): CLI controls iteration and timing.
|
|
1054
|
-
* Lap mode (__start/__lap/__done): page controls the measured region. */
|
|
1055
|
-
async function profileBrowser(params) {
|
|
1056
|
-
const { url, headless = true, timeout = 60 } = params;
|
|
1057
|
-
const { gcStats: collectGc } = params;
|
|
1058
|
-
const { samplingInterval = 32768 } = params.heapOptions ?? {};
|
|
1059
|
-
const browser = await chromium.launch({ headless });
|
|
1060
|
-
try {
|
|
1061
|
-
const page = await browser.newPage();
|
|
1062
|
-
page.setDefaultTimeout(timeout * 1e3);
|
|
1063
|
-
const cdp = await page.context().newCDPSession(page);
|
|
1064
|
-
const pageErrors = [];
|
|
1065
|
-
page.on("pageerror", (err) => pageErrors.push(err.message));
|
|
1066
|
-
const traceEvents = collectGc ? await startGcTracing(cdp) : [];
|
|
1067
|
-
const lapMode = await setupLapMode(page, cdp, params, samplingInterval, timeout, pageErrors);
|
|
1068
|
-
await page.goto(url, { waitUntil: "load" });
|
|
1069
|
-
const hasBench = await page.evaluate(() => typeof globalThis.__bench === "function");
|
|
1070
|
-
let result;
|
|
1071
|
-
if (hasBench) {
|
|
1072
|
-
lapMode.cancel();
|
|
1073
|
-
lapMode.promise.catch(() => {});
|
|
1074
|
-
result = await runBenchLoop(page, cdp, params, samplingInterval);
|
|
1075
|
-
} else {
|
|
1076
|
-
result = await lapMode.promise;
|
|
1077
|
-
lapMode.cancel();
|
|
1078
|
-
}
|
|
1079
|
-
if (collectGc) result = {
|
|
1080
|
-
...result,
|
|
1081
|
-
gcStats: await collectTracing(cdp, traceEvents)
|
|
1082
|
-
};
|
|
1083
|
-
return result;
|
|
1084
|
-
} finally {
|
|
1085
|
-
await browser.close();
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
/** Inject __start/__lap as in-page functions, expose __done for results collection.
|
|
1089
|
-
* __start/__lap are pure in-page (zero CDP overhead). First __start() triggers
|
|
1090
|
-
* instrument start. __done() stops instruments and collects timing data. */
|
|
1091
|
-
async function setupLapMode(page, cdp, params, samplingInterval, timeout, pageErrors) {
|
|
1092
|
-
const { heapSample } = params;
|
|
1093
|
-
const { promise, resolve, reject } = Promise.withResolvers();
|
|
1094
|
-
let instrumentsStarted = false;
|
|
1095
|
-
await page.exposeFunction("__benchInstrumentStart", async () => {
|
|
1096
|
-
if (instrumentsStarted) return;
|
|
1097
|
-
instrumentsStarted = true;
|
|
1098
|
-
if (heapSample) await cdp.send("HeapProfiler.startSampling", heapSamplingParams(samplingInterval));
|
|
1099
|
-
});
|
|
1100
|
-
await page.exposeFunction("__benchCollect", async (samples, wallTimeMs) => {
|
|
1101
|
-
let heapProfile;
|
|
1102
|
-
if (heapSample && instrumentsStarted) heapProfile = (await cdp.send("HeapProfiler.stopSampling")).profile;
|
|
1103
|
-
resolve({
|
|
1104
|
-
samples,
|
|
1105
|
-
heapProfile,
|
|
1106
|
-
wallTimeMs
|
|
1107
|
-
});
|
|
1108
|
-
});
|
|
1109
|
-
await page.addInitScript(injectLapFunctions);
|
|
1110
|
-
const timer = setTimeout(() => {
|
|
1111
|
-
const lines = [`Timed out after ${timeout}s`];
|
|
1112
|
-
if (pageErrors.length) lines.push("Page JS errors:", ...pageErrors.map((e) => ` ${e}`));
|
|
1113
|
-
else lines.push("Page did not call __done() or define window.__bench");
|
|
1114
|
-
reject(new Error(lines.join("\n")));
|
|
1115
|
-
}, timeout * 1e3);
|
|
1116
|
-
return {
|
|
1117
|
-
promise,
|
|
1118
|
-
cancel: () => clearTimeout(timer)
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
/** In-page timing functions injected via addInitScript (zero CDP overhead).
|
|
1122
|
-
* __start/__lap collect timestamps, __done delegates to exposed __benchCollect. */
|
|
1123
|
-
function injectLapFunctions() {
|
|
1124
|
-
const g = globalThis;
|
|
1125
|
-
g.__benchSamples = [];
|
|
1126
|
-
g.__benchLastTime = 0;
|
|
1127
|
-
g.__benchFirstStart = 0;
|
|
1128
|
-
g.__start = () => {
|
|
1129
|
-
const now = performance.now();
|
|
1130
|
-
g.__benchLastTime = now;
|
|
1131
|
-
if (!g.__benchFirstStart) {
|
|
1132
|
-
g.__benchFirstStart = now;
|
|
1133
|
-
return g.__benchInstrumentStart();
|
|
1134
|
-
}
|
|
1135
|
-
};
|
|
1136
|
-
g.__lap = () => {
|
|
1137
|
-
const now = performance.now();
|
|
1138
|
-
g.__benchSamples.push(now - g.__benchLastTime);
|
|
1139
|
-
g.__benchLastTime = now;
|
|
1140
|
-
};
|
|
1141
|
-
g.__done = () => {
|
|
1142
|
-
const wall = g.__benchFirstStart ? performance.now() - g.__benchFirstStart : 0;
|
|
1143
|
-
return g.__benchCollect(g.__benchSamples.slice(), wall);
|
|
1144
|
-
};
|
|
1145
|
-
}
|
|
1146
|
-
function heapSamplingParams(samplingInterval) {
|
|
1147
|
-
return {
|
|
1148
|
-
samplingInterval,
|
|
1149
|
-
includeObjectsCollectedByMajorGC: true,
|
|
1150
|
-
includeObjectsCollectedByMinorGC: true
|
|
1151
|
-
};
|
|
1152
|
-
}
|
|
1153
|
-
/** Start CDP GC tracing, returns the event collector array. */
|
|
1154
|
-
async function startGcTracing(cdp) {
|
|
1155
|
-
const events = [];
|
|
1156
|
-
cdp.on("Tracing.dataCollected", ({ value }) => {
|
|
1157
|
-
for (const e of value) events.push(e);
|
|
1158
|
-
});
|
|
1159
|
-
await cdp.send("Tracing.start", { traceConfig: { includedCategories: ["v8", "v8.gc"] } });
|
|
1160
|
-
return events;
|
|
1161
|
-
}
|
|
1162
|
-
/** Bench function mode: run window.__bench in a timed iteration loop. */
|
|
1163
|
-
async function runBenchLoop(page, cdp, params, samplingInterval) {
|
|
1164
|
-
const { heapSample } = params;
|
|
1165
|
-
const maxTime = params.maxTime ?? 642;
|
|
1166
|
-
const maxIter = params.maxIterations ?? Number.MAX_SAFE_INTEGER;
|
|
1167
|
-
if (heapSample) await cdp.send("HeapProfiler.startSampling", heapSamplingParams(samplingInterval));
|
|
1168
|
-
const { samples, totalMs } = await page.evaluate(async ({ maxTime, maxIter }) => {
|
|
1169
|
-
const bench = globalThis.__bench;
|
|
1170
|
-
const samples = [];
|
|
1171
|
-
const startAll = performance.now();
|
|
1172
|
-
const deadline = startAll + maxTime;
|
|
1173
|
-
for (let i = 0; i < maxIter && performance.now() < deadline; i++) {
|
|
1174
|
-
const t0 = performance.now();
|
|
1175
|
-
await bench();
|
|
1176
|
-
samples.push(performance.now() - t0);
|
|
1177
|
-
}
|
|
1178
|
-
return {
|
|
1179
|
-
samples,
|
|
1180
|
-
totalMs: performance.now() - startAll
|
|
1181
|
-
};
|
|
1182
|
-
}, {
|
|
1183
|
-
maxTime,
|
|
1184
|
-
maxIter
|
|
1185
|
-
});
|
|
1186
|
-
let heapProfile;
|
|
1187
|
-
if (heapSample) heapProfile = (await cdp.send("HeapProfiler.stopSampling")).profile;
|
|
1188
|
-
return {
|
|
1189
|
-
samples,
|
|
1190
|
-
heapProfile,
|
|
1191
|
-
wallTimeMs: totalMs
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
/** Stop CDP tracing and parse GC events into GcStats. */
|
|
1195
|
-
async function collectTracing(cdp, traceEvents) {
|
|
1196
|
-
const complete = new Promise((resolve) => cdp.once("Tracing.tracingComplete", () => resolve()));
|
|
1197
|
-
await cdp.send("Tracing.end");
|
|
1198
|
-
await complete;
|
|
1199
|
-
return browserGcStats(traceEvents);
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
953
|
//#endregion
|
|
1203
954
|
//#region src/export/JsonExport.ts
|
|
1204
955
|
/** Export benchmark results to JSON file */
|
|
@@ -2069,13 +1820,12 @@ function formatConvergence(v) {
|
|
|
2069
1820
|
|
|
2070
1821
|
//#endregion
|
|
2071
1822
|
//#region src/StandardSections.ts
|
|
2072
|
-
/** Section: mean, p50, p99 timing
|
|
1823
|
+
/** Section: mean, p50, p99 timing */
|
|
2073
1824
|
const timeSection = {
|
|
2074
1825
|
extract: (results) => ({
|
|
2075
1826
|
mean: results.time?.avg,
|
|
2076
1827
|
p50: results.time?.p50,
|
|
2077
|
-
p99: results.time?.p99
|
|
2078
|
-
convergence: results.convergence?.confidence
|
|
1828
|
+
p99: results.time?.p99
|
|
2079
1829
|
}),
|
|
2080
1830
|
columns: () => [{
|
|
2081
1831
|
groupTitle: "time",
|
|
@@ -2099,11 +1849,7 @@ const timeSection = {
|
|
|
2099
1849
|
comparable: true
|
|
2100
1850
|
}
|
|
2101
1851
|
]
|
|
2102
|
-
}
|
|
2103
|
-
key: "convergence",
|
|
2104
|
-
title: "conv%",
|
|
2105
|
-
formatter: formatConvergence
|
|
2106
|
-
}] }]
|
|
1852
|
+
}]
|
|
2107
1853
|
};
|
|
2108
1854
|
/** Section: GC time as fraction of total benchmark time (Node performance hooks) */
|
|
2109
1855
|
const gcSection = {
|
|
@@ -2651,7 +2397,6 @@ function mergeResults(results) {
|
|
|
2651
2397
|
const allSamples = results.flatMap((r) => r.samples);
|
|
2652
2398
|
const allWarmup = results.flatMap((r) => r.warmupSamples || []);
|
|
2653
2399
|
const time = computeStats(allSamples);
|
|
2654
|
-
const convergence = checkConvergence(allSamples.map((s) => s * msToNs));
|
|
2655
2400
|
let offset = 0;
|
|
2656
2401
|
const allPausePoints = results.flatMap((r) => {
|
|
2657
2402
|
const pts = (r.pausePoints ?? []).map((p) => ({
|
|
@@ -2667,7 +2412,6 @@ function mergeResults(results) {
|
|
|
2667
2412
|
warmupSamples: allWarmup.length ? allWarmup : void 0,
|
|
2668
2413
|
time,
|
|
2669
2414
|
totalTime: results.reduce((sum, r) => sum + (r.totalTime || 0), 0),
|
|
2670
|
-
convergence,
|
|
2671
2415
|
pausePoints: allPausePoints.length ? allPausePoints : void 0
|
|
2672
2416
|
};
|
|
2673
2417
|
}
|
|
@@ -2704,6 +2448,12 @@ async function benchExports(suite, args) {
|
|
|
2704
2448
|
/** Run browser profiling via Playwright + CDP, report with standard pipeline */
|
|
2705
2449
|
async function browserBenchExports(args) {
|
|
2706
2450
|
warnBrowserFlags(args);
|
|
2451
|
+
let profileBrowser;
|
|
2452
|
+
try {
|
|
2453
|
+
({profileBrowser} = await import("./BrowserHeapSampler-DQwmmuDu.mjs"));
|
|
2454
|
+
} catch {
|
|
2455
|
+
throw new Error("playwright is required for browser benchmarking (--url).\n\nQuick start: npx benchforge-browser --url <your-url>\n\nOr install manually:\n npm install playwright\n npx playwright install chromium");
|
|
2456
|
+
}
|
|
2707
2457
|
const url = args.url;
|
|
2708
2458
|
const { iterations, time } = args;
|
|
2709
2459
|
const result = await profileBrowser({
|
|
@@ -2714,6 +2464,7 @@ async function browserBenchExports(args) {
|
|
|
2714
2464
|
stackDepth: args["heap-depth"]
|
|
2715
2465
|
},
|
|
2716
2466
|
headless: args.headless,
|
|
2467
|
+
chromeArgs: args["chrome-args"]?.split(/\s+/).filter(Boolean),
|
|
2717
2468
|
timeout: args.timeout,
|
|
2718
2469
|
gcStats: args["gc-stats"],
|
|
2719
2470
|
maxTime: iterations ? Number.MAX_SAFE_INTEGER : time * 1e3,
|
|
@@ -3083,4 +2834,4 @@ function getMostRecentModifiedDate(dir) {
|
|
|
3083
2834
|
|
|
3084
2835
|
//#endregion
|
|
3085
2836
|
export { timeSection as A, parseCliArgs as B, adaptiveSection as C, gcStatsSection as D, gcSection as E, generateHtmlReport as F, truncate as G, formatBytes as H, formatDateWithTimezone as I, loadCaseData as J, isStatefulVariant as K, prepareHtmlData as L, formatConvergence as M, filterMatrix as N, optSection as O, parseMatrixFilter as P, exportPerfettoTrace as R, reportMatrixResults as S, cpuSection as T, integer as U, reportResults as V, timeMs as W, loadCasesModule as Y, runDefaultMatrixBench as _, cliToMatrixOptions as a, gcStatsColumns as b, exportReports as c, matrixToReportGroups as d, parseBenchArgs as f, runDefaultBench as g, runBenchmarks as h, benchExports as i, totalTimeSection as j, runsSection as k, hasField as l, reportOptStatus as m, getBaselineVersion as n, defaultMatrixReport as o, printHeapReports as p, runMatrix as q, getCurrentGitVersion as r, defaultReport as s, formatGitVersion as t, matrixBenchExports as u, runMatrixSuite as v, buildGenericSections as w, heapTotalColumn as x, gcPauseColumn as y, defaultCliArgs as z };
|
|
3086
|
-
//# sourceMappingURL=src-
|
|
2837
|
+
//# sourceMappingURL=src-B06_i1RD.mjs.map
|