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.
@@ -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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"}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { g as runDefaultBench } from "../src-D7zxOFGA.mjs";
2
+ import { g as runDefaultBench } from "../src-B06_i1RD.mjs";
3
3
 
4
4
  //#region src/bin/benchforge.ts
5
5
  await runDefaultBench();
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-BLfGX2wQ.mjs";
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: "use adaptive sampling mode";
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 in seconds before adaptive convergence can stop";
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 with convergence */
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-D7zxOFGA.mjs";
2
- import { u as average } from "./TimingUtils-D4z1jpp2.mjs";
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-BLfGX2wQ.mjs";
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 { c as createAdaptiveWrapper, i as createRunner, n as getElapsed, p as variantModuleUrl, r as getPerfNow, t as debugWorkerTiming } from "../TimingUtils-D4z1jpp2.mjs";
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-BX3de22o.mjs");
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 BasicRunner, c as createAdaptiveWrapper, d as bootstrapDifferenceCI, f as discoverVariants, i as createRunner, l as msToNs, n as getElapsed, o as computeStats, r as getPerfNow, s as checkConvergence, t as debugWorkerTiming, u as average } from "./TimingUtils-D4z1jpp2.mjs";
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: "use adaptive sampling mode"
817
+ describe: "adaptive sampling (experimental)"
893
818
  },
894
819
  "min-time": {
895
820
  type: "number",
896
821
  default: 1,
897
- describe: "minimum time in seconds before adaptive convergence can stop"
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 with convergence */
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
- }, { columns: [{
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-D7zxOFGA.mjs.map
2837
+ //# sourceMappingURL=src-B06_i1RD.mjs.map