Last active
August 23, 2025 02:36
-
-
Save BioPhoton/4af1e2838cdc9f0340d72b08fca225f1 to your computer and use it in GitHub Desktop.
Revisions
-
BioPhoton revised this gist
Aug 23, 2025 . 1 changed file with 266 additions and 410 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ # ESLint Concurrency Benchmark ## Overview - Benchmarks ESLint v9.34+ `--concurrency` across one or more targets @@ -9,11 +9,7 @@ ## Defaults - **concurrency**: `off,4,8,auto` - **runs**: `3` - **outDir**: `./eslint-perf` ## Flags - `--targets=dir[,dir...]`: Comma-separated target directories @@ -26,33 +22,34 @@ ## Examples - Minimal (defaults): ```bash node --import tsx bench.ts ``` - Custom targets + concurrency: ```bash node --import tsx bench.ts \ --targets=packages/lib-a,packages/lib-b \ --concurrency=off,2,4,auto \ --runs=5 ``` - Custom patterns (bench.ts): ```bash node --import tsx bench.ts \ --targets=packages/lib-a \ --patterns="packages/lib-a/**/*.ts,packages/lib-a/**/*.tsx" \ --concurrency=off,4,auto ``` ## Output - **Raw JSON**: `./eslint-perf/system-info-*.json` and `./eslint-perf/eslint-benchmark-*.json` - **Summary**: `./eslint-perf/*.summary.md` - **Terminal**: fastest table + per-target breakdown (★ marks best) ### Output Example ```bash > node --import tsx tools/eslint-perf/ --targets=packages/lib-a --concurrency=off,1,2,4,6,8,auto --runs=3 --verbose 📁 Target: packages/lib-a (files: 106, ts: 101, js: 5) 🔧 Concurrency: off @@ -65,7 +62,7 @@ node --import tsx tools/eslint-perf/bench.ts \ ✅ Benchmark complete Raw results: tools/eslint-perf/results/eslint-benchmark-2025-08-23T01-39-46-558Z.json Summary: ./eslint-perf/eslint-benchmark-2025-08-23T01-39-46-558Z.summary.md Fastest per target: Target Best Avg(s) Baseline(s) Speedup @@ -86,18 +83,29 @@ off 8.849 0.078 1.00x ★ auto 9.490 0.095 0.93x ``` # CPU Measures ```bash # Profile CPU usage while running ESLint with explicit config and patterns npx @push-based/cpu-prof@latest -- \ npx eslint -c packages/lib-a/eslint.config.js \ "packages/lib-a/**/*.ts" \ --concurrency=4 ``` What this does: - Starts ESLint with Node CPU profiling enabled and collects .cpuprofile files - Merges them into a single Chrome trace JSON for easy inspection Expected output: - Profiles folder: `./profiles` (or a printed absolute path) - Individual `.cpuprofile` files for the run - Merged trace JSON like `Trace-<timestamp>.json` - Open the merged JSON in Chrome DevTools → Performance → Load profile to view threads, tasks, and CPU usage with `--concurrency=4` # bench.ts ```ts import { exec as execCb } from 'node:child_process'; import { mkdir, readdir, stat, writeFile } from 'node:fs/promises'; import os from 'node:os'; @@ -106,17 +114,16 @@ import { promisify } from 'node:util'; const exec = promisify(execCb); type C = 'off' | 'auto' | `${number}`; type Run = { target: string; concurrency: C; run: number; timing: { durationSeconds: number; realTimeSeconds: null; userTimeSeconds: null; sysTimeSeconds: null; }; eslintResults: { filesProcessed: number; @@ -126,295 +133,194 @@ type BenchmarkRun = { }; timestamp: string; }; type Stats = { runs: number; min: number; max: number; avg: number; stdDev: number; }; type TI = { target: string; tsFiles: number; jsFiles: number; totalFiles: number; }; const DFLT_TARGETS = [ 'packages/models', 'packages/plugin-eslint', 'packages/cli', 'packages/core', 'packages/utils', ]; const DFLT_CONC: C[] = ['off', '4', '8', 'auto']; const DFLT_RUNS = 3; const pad = (s: string, n: number) => s.length >= n ? s : s + ' '.repeat(n - s.length); const exists = async (p: string) => !!(await stat(p).catch(() => null)); const ensureDir = (d: string) => mkdir(d, { recursive: true }); function args() { const a = process.argv.slice(2); const get = (n: string) => a.find(x => x.startsWith(`--${n}=`))?.split('=')[1]; const targets = get('targets')?.split(',').filter(Boolean) ?? DFLT_TARGETS; const conc = (get('concurrency')?.split(',').filter(Boolean) as C[] | undefined) ?? DFLT_CONC; const runs = Number.parseInt(get('runs') ?? `${DFLT_RUNS}`, 10); const outDir = get('outDir') ?? path.join('tools', 'eslint-perf', 'results'); const verbose = a.includes('--verbose') || get('verbose') === 'true'; const useLocalConfig = get('useLocalConfig') === 'false' ? false : true; const tsOnly = get('tsOnly') === 'false' ? false : true; return { targets, conc, runs, outDir, verbose, useLocalConfig, tsOnly }; } async function sys() { const nx = await exec('npx nx --version') .then(r => r.stdout.trim().split('\n')[0]) .catch(() => null); return { timestamp: new Date().toISOString(), system: { os: os.platform(), osVersion: os.release(), architecture: os.arch(), cpuCores: os.cpus()?.length ?? 0, totalMemGb: Math.round((os.totalmem() / 1024 ** 3) * 100) / 100, hostname: os.hostname(), }, software: { nodeVersion: process.version, npmVersion: (await exec('npm --version')).stdout.trim(), eslintVersion: (await exec('npx eslint --version')).stdout.trim(), nxVersion: nx, }, }; } async function count(dir: string, exts: string[]) { let n = 0; const w = async (d: string): Promise<void> => { const es = await readdir(d, { withFileTypes: true }); for (const e of es) { const p = path.join(d, e.name); if (e.isDirectory()) { if (['node_modules', 'dist', 'coverage'].includes(e.name)) continue; await w(p); } else if (e.isFile() && exts.some(ext => e.name.endsWith(ext))) n++; } }; await w(dir); return n; } async function info(t: string): Promise<TE> { const abs = path.resolve(t); if (!(await exists(abs))) return { target: t, tsFiles: 0, jsFiles: 0, totalFiles: 0 }; const ts = await count(abs, ['.ts', '.tsx']); const js = await count(abs, ['.js', '.jsx', '.mjs', '.cjs']); return { target: t, tsFiles: ts, jsFiles: js, totalFiles: ts + js }; } type TE = TI; const cmd = ( t: string, c: C, o: { local: boolean; ts: boolean; json: boolean }, ) => `npx eslint${o.local ? ` --config=${JSON.stringify(path.join(t, 'eslint.config.js'))}` : ''} --concurrency=${c} ${o.json ? '--format=json' : '--format=stylish'} ${[o.ts ? path.join(t, '**', '*.ts') : t].map(p => JSON.stringify(p)).join(' ')}`; async function once( t: string, c: C, i: number, v: boolean, local: boolean, ts: boolean, ): Promise<Run> { const s = process.hrtime.bigint(); const k = cmd(t, c, { local, ts, json: true }); let out = '', err = ''; try { const r = await exec(k, { maxBuffer: 1024 * 1024 * 200 }); out = r.stdout; err = r.stderr; } catch (e: any) { out = e.stdout ?? ''; err = e.stderr ?? ''; } if (v) { console.log(` $ ${k}`); if (err.trim()) { console.log(' [stderr]'); console.log( err .split('\n') .map(l => ` ${l}`) .join('\n'), ); } } const d = Number(process.hrtime.bigint() - s) / 1_000_000_000; let files = 0, errors = 0, warnings = 0; try { const j = JSON.parse(out) as Array<{ errorCount: number; warningCount: number; }>; files = j.length; for (const r of j) { errors += r.errorCount || 0; warnings += r.warningCount || 0; } } catch {} const warns = err.includes('ESLintPoorConcurrencyWarning') ? ['ESLintPoorConcurrencyWarning'] : []; return { target: t, concurrency: c, run: i, timing: { durationSeconds: d, realTimeSeconds: null, userTimeSeconds: null, sysTimeSeconds: null, }, eslintResults: { filesProcessed: files, errors, warnings, eslintWarnings: warns, }, timestamp: new Date().toISOString(), }; } const statz = (xs: number[]): Stats => { const n = xs.length, min = Math.min(...xs), max = Math.max(...xs), avg = xs.reduce((a, b) => a + b, 0) / n, sd = Math.sqrt(xs.reduce((a, v) => a + (v - avg) ** 2, 0) / n); return { runs: n, min, max, avg: Number(avg.toFixed(3)), stdDev: Number(sd.toFixed(3)), }; }; async function stylish(t: string) { const k = cmd(t, 'off', { local: true, ts: true, json: false }); try { const { stdout, stderr } = await exec(k, { maxBuffer: 1024 * 1024 * 200 }); if (stdout.trim()) { console.log(' [stylish output]'); console.log( @@ -435,22 +341,22 @@ async function printStylishOutputOnce( ); } } catch (e: any) { const o = e.stdout ?? '', er = e.stderr ?? ''; if (o.trim()) { console.log(' [stylish output]'); console.log( o .split('\n') .slice(0, 300) .map((l: string) => ` ${l}`) .join('\n'), ); } if (er.trim()) { console.log(' [stylish stderr]'); console.log( er .split('\n') .map((l: string) => ` ${l}`) .join('\n'), @@ -459,205 +365,155 @@ async function printStylishOutputOnce( } } async function main() { const { targets, conc, runs, outDir, verbose, useLocalConfig, tsOnly } = args(); await ensureDir(outDir); const si = await sys(), ts = new Date().toISOString().replace(/[:.]/g, '-'); const resPath = path.join(outDir, `eslint-benchmark-${ts}.json`), sumPath = path.join(outDir, `eslint-benchmark-${ts}.summary.md`); const results: any[] = []; await writeFile( path.join(outDir, `system-info-${ts}.json`), JSON.stringify(si, null, 2), ); for (const t of targets) { if (!(await exists(path.resolve(t)))) { console.warn(`Skipping missing target: ${t}`); continue; } const ti = await info(t); console.log( `\n📁 Target: ${t} (files: ${ti.totalFiles}, ts: ${ti.tsFiles}, js: ${ti.jsFiles})`, ); if (verbose) await stylish(t); for (const c of conc) { console.log(` 🔧 Concurrency: ${c}`); const rs: Run[] = []; for (let i = 1; i <= runs; i++) { console.log(` Run ${i}/${runs} ...`); const r = await once(t, c, i, verbose, useLocalConfig, tsOnly); rs.push(r); console.log( ` Time: ${r.timing.durationSeconds.toFixed(3)}s, Files: ${r.eslintResults.filesProcessed}, Errors: ${r.eslintResults.errors}, Warnings: ${r.eslintResults.warnings}${r.eslintResults.eslintWarnings.length ? ' ⚠️ ' + r.eslintResults.eslintWarnings.join(',') : ''}`, ); } const s = statz(rs.map(r => r.timing.durationSeconds)); results.push({ target: t, targetInfo: ti, concurrency: c, statistics: s, runs: rs, }); console.log( ` ✅ Avg: ${s.avg}s (min ${s.min.toFixed(3)}s, max ${s.max.toFixed(3)}s, ±${s.stdDev}s)`, ); } } await writeFile( resPath, JSON.stringify({ systemInfo: si, results }, null, 2), ); // Build Markdown summary (system info, fastest table, per-target breakdown) const tset = Array.from(new Set(results.map((r: any) => r.target))); const rows = tset.map(t => { const xs = results.filter((r: any) => r.target === t); const best = xs.reduce((a: any, b: any) => a.statistics.avg <= b.statistics.avg ? a : b, ); const base = xs.find((r: any) => r.concurrency === 'off')?.statistics.avg ?? null; const speed = base ? (base / best.statistics.avg).toFixed(2) + 'x' : 'n/a'; return { t, bestConc: best.concurrency as C, bestAvg: Number(best.statistics.avg.toFixed(3)), base, speed, }; }); const md: string[] = []; md.push('# ESLint Concurrency Benchmark Report'); md.push(''); md.push(`Generated: ${new Date().toString()}`); md.push(''); md.push('## System'); md.push(`- **OS**: ${si.system.os} ${si.system.osVersion}`); md.push(`- **CPU Cores**: ${si.system.cpuCores}`); md.push(`- **Memory**: ${si.system.totalMemGb} GB`); md.push(`- **Node**: \\`${si.software.nodeVersion}\\``); md.push(`- **ESLint**: \\`${si.software.eslintVersion}\\``); if (si.software.nxVersion) md.push(`- **Nx**: \\`${si.software.nxVersion}\\``); md.push(''); md.push('## Fastest per target'); md.push(''); md.push('| Target | Best | Avg (s) | Baseline (s) | Speedup |'); md.push('|---|:---:|---:|---:|---:|'); for (const r of rows) { md.push(`| \\`${r.t}\\` | **${r.bestConc}** | **${r.bestAvg.toFixed(3)}** | ${r.base ? r.base.toFixed(3) : 'n/a'} | **${r.speed}** |`); } md.push(''); md.push('## Per-target breakdown'); md.push('Best rows are marked with ★.'); for (const t of tset) { const xs = results.filter((r: any) => r.target === t); const base = xs.find((r: any) => r.concurrency === 'off')?.statistics.avg ?? null; const bestAvg = Math.min(...xs.map((r: any) => r.statistics.avg)); md.push(''); md.push(`### \\`${t}\\``); md.push(''); md.push('| Concurrency | Avg (s) | StdDev | Speedup |'); md.push('|:-----------:|---:|---:|---:|'); for (const c of Array.from(new Set(xs.map((r: any) => r.concurrency)))) { const r = xs.find((q: any) => q.concurrency === c)!; const isBest = Math.abs(r.statistics.avg - bestAvg) < 1e-6; const sp = base ? `${(base / r.statistics.avg).toFixed(2)}x` : 'n/a'; const row = `| \\`${String(c)}\\` | ${r.statistics.avg.toFixed(3)} | ${r.statistics.stdDev.toFixed(3)} | ${sp} | ${isBest ? '★' : ''}`; md.push(isBest ? row.replace(/\| ([^|]+) \|/, match => match.replace(/([^|]+)/, '**$1**')) : row); } } await writeFile(sumPath, md.join('\n')); console.log('\n✅ Benchmark complete'); console.log(`Raw results: ${resPath}`); console.log(`Summary: ${sumPath}`); // Reuse computed tset/rows for terminal tables const header = `${pad('Target', 28)} ${pad('Best', 6)} ${pad('Avg(s)', 8)} ${pad('Baseline(s)', 12)} Speedup`; console.log('\nFastest per target:'); console.log(header); console.log('-'.repeat(header.length)); for (const r of rows) console.log( `${pad(r.t, 28)} ${pad(String(r.bestConc), 6)} ${pad(r.bestAvg.toFixed(3), 8)} ${pad(r.base ? r.base.toFixed(3) : 'n/a', 12)} ${r.speed}`, ); console.log('\nPer-target breakdown (best marked with ★):'); for (const t of tset) { const xs = results.filter((r: any) => r.target === t); const base = xs.find((r: any) => r.concurrency === 'off')?.statistics.avg ?? null; const best = Math.min(...xs.map((r: any) => r.statistics.avg)); console.log(`\n${t}`); const h2 = `${pad('Concurrency', 12)} ${pad('Avg(s)', 8)} ${pad('StdDev', 8)} ${pad('Speedup', 8)} Mark`; console.log(h2); console.log('-'.repeat(h2.length)); for (const c of Array.from(new Set(xs.map((r: any) => r.concurrency)))) { const r = xs.find((q: any) => q.concurrency === c)!; const sp = base ? `${(base / r.statistics.avg).toFixed(2)}x` : 'n/a'; console.log( `${pad(String(c), 12)} ${pad(r.statistics.avg.toFixed(3), 8)} ${pad(r.statistics.stdDev.toFixed(3), 8)} ${pad(sp, 8)} ${Math.abs(r.statistics.avg - best) < 1e-6 ? '★' : ''}`, ); } } } main().catch(e => { console.error(e); process.exitCode = 1; }); -
BioPhoton revised this gist
Aug 23, 2025 . 1 changed file with 14 additions and 15 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ # ESLint Concurrency Benchmark (compact) ## Overview - Benchmarks ESLint v9.34+ `--concurrency` across one or more targets @@ -9,18 +9,19 @@ ## Defaults - **concurrency**: `off,4,8,auto` - **runs**: `3` - **outDir**: `tools/eslint-perf/results` > Note: > - `bench.compact.ts` defaults: uses `<target>/eslint.config.js` if present and lints TypeScript files under `<target>`. > - `bench.ts` supports custom patterns via `--patterns` and will default to `<target>/**/*.ts` and auto-use `<target>/eslint.config.js` if present when `--patterns` is not provided. ## Flags - `--targets=dir[,dir...]`: Comma-separated target directories - `--concurrency=v[,v...]`: values in `{off,auto,1..N}` - `--runs=N`: Runs per configuration (default `3`) - `--verbose`: Show ESLint commands/stderr and one stylish preview per target - `--outDir=path`: Directory for JSON + summary outputs - `--patterns=glob[,glob...]`: (bench.ts) Explicit file globs to lint (e.g. `packages/utils/**/*.ts,packages/utils/**/*.tsx`) ## Examples - Minimal (defaults): @@ -36,10 +37,12 @@ node --import tsx tools/eslint-perf/bench.compact.ts \ --runs=5 ``` - Custom patterns (bench.ts): ```bash node --import tsx tools/eslint-perf/bench.ts \ --targets=packages/lib-a \ --patterns="packages/lib-a/**/*.ts,packages/lib-a/**/*.tsx" \ --concurrency=off,4,auto ``` ## Output @@ -49,8 +52,7 @@ node --import tsx tools/eslint-perf/bench.compact.ts \ ### Output Example ```bash > node --import tsx tools/eslint-perf/bench.ts --targets=packages/lib-a --concurrency=off,1,2,4,6,8,auto --runs=3 --verbose 📁 Target: packages/lib-a (files: 106, ts: 101, js: 5) 🔧 Concurrency: off @@ -63,7 +65,7 @@ node --import tsx tools/eslint-perf/bench.compact.ts \ ✅ Benchmark complete Raw results: tools/eslint-perf/results/eslint-benchmark-2025-08-23T01-39-46-558Z.json Summary: tools/eslint-perf/results/eslint-benchmark-2025-08-23T01-39-46-558Z.summary.md Fastest per target: Target Best Avg(s) Baseline(s) Speedup @@ -82,14 +84,11 @@ off 8.849 0.078 1.00x ★ 6 11.652 0.439 0.76x 8 13.749 0.195 0.64x auto 9.490 0.095 0.93x ``` # CPU Measures ```bash npx @push-based/cpu-prof@latest npx eslint . --concurrency=4 ``` -
BioPhoton revised this gist
Aug 23, 2025 . 1 changed file with 396 additions and 238 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -94,6 +94,11 @@ npx @push-based/cpu-prof@latest npx eslint . --concurrency=4 ```ts /* * ESLint Concurrency Benchmark Suite * Runs ESLint with multiple concurrency settings across selected targets, * collects timings and quantitative data, and writes structured results. */ import { exec as execCb } from 'node:child_process'; import { mkdir, readdir, stat, writeFile } from 'node:fs/promises'; import os from 'node:os'; @@ -102,16 +107,17 @@ import { promisify } from 'node:util'; const exec = promisify(execCb); type Concurrency = 'off' | 'auto' | `${number}`; type BenchmarkRun = { target: string; concurrency: Concurrency; run: number; timing: { durationSeconds: number; realTimeSeconds: number | null; userTimeSeconds: number | null; sysTimeSeconds: number | null; }; eslintResults: { filesProcessed: number; @@ -121,194 +127,295 @@ type Run = { }; timestamp: string; }; type BenchmarkStats = { avg: number; min: number; max: number; stdDev: number; runs: number; }; type TargetInfo = { target: string; tsFiles: number; jsFiles: number; totalFiles: number; }; type SuiteResult = { target: string; targetInfo: TargetInfo; concurrency: Concurrency; statistics: BenchmarkStats; runs: BenchmarkRun[]; }; type SystemInfo = { timestamp: string; system: { os: string; osVersion: string; architecture: string; cpuCores: number; totalMemGb: number; hostname: string; }; software: { nodeVersion: string; npmVersion: string; eslintVersion: string; nxVersion: string | null; }; }; // ----------------------------- // Config via CLI args (minimal) // ----------------------------- const DEFAULT_TARGETS = [ 'packages/models', 'packages/plugin-eslint', 'packages/cli', 'packages/core', 'packages/utils', ]; const DEFAULT_CONCURRENCY: Concurrency[] = [ 'off', '1', '2', '4', '6', '8', 'auto', ]; const DEFAULT_RUNS = 3; function parseArgs() { const args = process.argv.slice(2); const getArg = (name: string): string | undefined => { const prefix = `--${name}=`; return args.find(a => a.startsWith(prefix))?.slice(prefix.length); }; const targets = getArg('targets')?.split(',').filter(Boolean) ?? DEFAULT_TARGETS; const conc = getArg('concurrency')?.split(',').filter(Boolean) as | Concurrency[] | undefined; const concurrency = conc && conc.length > 0 ? conc : DEFAULT_CONCURRENCY; const runs = Number.parseInt(getArg('runs') ?? `${DEFAULT_RUNS}`, 10); const outDir = getArg('outDir') ?? path.join('tools', 'eslint-perf', 'results'); const verbose = args.includes('--verbose') || getArg('verbose') === 'true'; const patterns = getArg('patterns') ?.split(',') .map(p => p.trim()) .filter(Boolean); return { targets, concurrency, runs, outDir, verbose, patterns, }; } // ----------------------------- // Utilities // ----------------------------- async function pathExists(p: string): Promise<boolean> { try { await stat(p); return true; } catch { return false; } } async function collectSystemInfo(): Promise<SystemInfo> { const cpuCores = os.cpus()?.length ?? 0; const totalMemGb = Math.round((os.totalmem() / 1024 ** 3) * 100) / 100; const nodeVersion = process.version; const npmVersion = (await exec('npm --version')).stdout.trim(); const eslintVersion = (await exec('npx eslint --version')).stdout.trim(); let nxVersion: string | null = null; try { nxVersion = (await exec('npx nx --version')).stdout.trim().split('\n')[0] ?? null; } catch { nxVersion = null; } return { timestamp: new Date().toISOString(), system: { os: os.platform(), osVersion: os.release(), architecture: os.arch(), cpuCores, totalMemGb, hostname: os.hostname(), }, software: { nodeVersion, npmVersion, eslintVersion, nxVersion, }, }; } async function walkCountFiles( dir: string, extensions: string[], ): Promise<number> { let count = 0; async function walk(current: string): Promise<void> { const entries = await readdir(current, { withFileTypes: true }); for (const entry of entries) { const p = path.join(current, entry.name); if (entry.isDirectory()) { if ( entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'coverage' ) continue; await walk(p); } else if (entry.isFile()) { if (extensions.some(ext => entry.name.endsWith(ext))) count += 1; } } } await walk(dir); return count; } async function analyzeTarget(target: string): Promise<TargetInfo> { const abs = path.resolve(target); const tsFiles = (await pathExists(abs)) ? await walkCountFiles(abs, ['.ts', '.tsx']) : 0; const jsFiles = (await pathExists(abs)) ? await walkCountFiles(abs, ['.js', '.jsx', '.mjs', '.cjs']) : 0; return { target, tsFiles, jsFiles, totalFiles: tsFiles + jsFiles, }; } function buildEslintCommand( concurrency: Concurrency, options: { patterns: string[]; json: boolean; configPath?: string }, ): string { const configArg = options.configPath ? ` --config=${JSON.stringify(options.configPath)}` : ''; const patternArgs = options.patterns.map(p => JSON.stringify(p)).join(' '); const formatArg = options.json ? '--format=json' : '--format=stylish'; return `npx eslint${configArg} --concurrency=${concurrency} ${formatArg} ${patternArgs}`; } async function runESLintOnce( target: string, concurrency: Concurrency, run: number, verbose = false, patterns: string[], configPath?: string, ): Promise<BenchmarkRun> { const start = process.hrtime.bigint(); const cmd = buildEslintCommand(concurrency, { patterns, json: true, configPath, }); let stdout = ''; let stderr = ''; try { const result = await exec(cmd, { maxBuffer: 1024 * 1024 * 200 }); stdout = result.stdout; stderr = result.stderr; } catch (e: any) { stdout = e.stdout ?? ''; stderr = e.stderr ?? ''; } if (verbose) { console.log(` $ ${cmd}`); if (stderr.trim()) { console.log(' [stderr]'); console.log( stderr .split('\n') .map(l => ` ${l}`) .join('\n'), ); } } const end = process.hrtime.bigint(); const durationSeconds = Number(end - start) / 1_000_000_000; // Parse JSON output let filesProcessed = 0; let errors = 0; let warnings = 0; try { const json = JSON.parse(stdout) as Array<{ errorCount: number; warningCount: number; }>; filesProcessed = json.length; for (const r of json) { errors += r.errorCount || 0; warnings += r.warningCount || 0; } } catch { // ignore parse errors } const eslintWarnings: string[] = []; if (stderr.includes('ESLintPoorConcurrencyWarning')) eslintWarnings.push('ESLintPoorConcurrencyWarning'); return { target, concurrency, run, timing: { durationSeconds, realTimeSeconds: null, userTimeSeconds: null, sysTimeSeconds: null, }, eslintResults: { filesProcessed, errors, warnings, eslintWarnings, }, timestamp: new Date().toISOString(), }; } async function printStylishOutputOnce( patterns: string[], configPath?: string, ): Promise<void> { const cmdStylish = buildEslintCommand('off', { patterns, json: false, configPath, }); try { const { stdout, stderr } = await exec(cmdStylish, { maxBuffer: 1024 * 1024 * 200, }); if (stdout.trim()) { console.log(' [stylish output]'); console.log( @@ -329,22 +436,22 @@ async function stylish(t: string) { ); } } catch (e: any) { const out = e.stdout ?? ''; const err = e.stderr ?? ''; if (out.trim()) { console.log(' [stylish output]'); console.log( out .split('\n') .slice(0, 300) .map((l: string) => ` ${l}`) .join('\n'), ); } if (err.trim()) { console.log(' [stylish stderr]'); console.log( err .split('\n') .map((l: string) => ` ${l}`) .join('\n'), @@ -353,155 +460,206 @@ async function stylish(t: string) { } } function statsFrom(values: number[]): BenchmarkStats { const runs = values.length; const min = Math.min(...values); const max = Math.max(...values); const avg = values.reduce((a, b) => a + b, 0) / runs; const variance = values.reduce((acc, v) => acc + (v - avg) ** 2, 0) / runs; const stdDev = Math.sqrt(variance); return { runs, min, max, avg: Number(avg.toFixed(3)), stdDev: Number(stdDev.toFixed(3)), }; } async function ensureDir(dir: string): Promise<void> { await mkdir(dir, { recursive: true }); } async function main(): Promise<void> { const { targets, concurrency, runs, outDir, verbose, patterns } = parseArgs(); await ensureDir(outDir); const systemInfo = await collectSystemInfo(); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const resultsPath = path.join(outDir, `eslint-benchmark-${timestamp}.json`); const summaryPath = path.join( outDir, `eslint-benchmark-${timestamp}.summary.txt`, ); const suiteResults: SuiteResult[] = []; // Write system info immediately await ensureDir(outDir); await writeFile( path.join(outDir, `system-info-${timestamp}.json`), JSON.stringify(systemInfo, null, 2), ); for (const target of targets) { const absTarget = path.resolve(target); if (!(await pathExists(absTarget))) { console.warn(`Skipping missing target: ${target}`); continue; } const tInfo = await analyzeTarget(target); console.log( `\n📁 Target: ${target} (files: ${tInfo.totalFiles}, ts: ${tInfo.tsFiles}, js: ${tInfo.jsFiles})`, ); if (verbose) { const resolvedPatterns = patterns && patterns.length > 0 ? patterns : [path.join(target, '**', '*.ts')]; const configPath = path.join(target, 'eslint.config.js'); await printStylishOutputOnce( resolvedPatterns, (await pathExists(configPath)) ? configPath : undefined, ); } for (const c of concurrency) { console.log(` 🔧 Concurrency: ${c}`); const runsData: BenchmarkRun[] = []; for (let i = 1; i <= runs; i++) { console.log(` Run ${i}/${runs} ...`); const resolvedPatterns = patterns && patterns.length > 0 ? patterns : [path.join(target, '**', '*.ts')]; const configPath = path.join(target, 'eslint.config.js'); const run = await runESLintOnce( target, c, i, verbose, resolvedPatterns, (await pathExists(configPath)) ? configPath : undefined, ); runsData.push(run); console.log( ` Time: ${run.timing.durationSeconds.toFixed(3)}s, Files: ${run.eslintResults.filesProcessed}, Errors: ${run.eslintResults.errors}, Warnings: ${run.eslintResults.warnings}${run.eslintResults.eslintWarnings.length ? ' ⚠️ ' + run.eslintResults.eslintWarnings.join(',') : ''}`, ); } const stats = statsFrom(runsData.map(r => r.timing.durationSeconds)); suiteResults.push({ target, targetInfo: tInfo, concurrency: c, statistics: stats, runs: runsData, }); console.log( ` ✅ Avg: ${stats.avg}s (min ${stats.min.toFixed(3)}s, max ${stats.max.toFixed(3)}s, ±${stats.stdDev}s)`, ); } } await writeFile( resultsPath, JSON.stringify({ systemInfo, results: suiteResults }, null, 2), ); // Write human-friendly summary const lines: string[] = []; lines.push('ESLint Concurrency Benchmark Summary'); lines.push(`Generated: ${new Date().toString()}`); lines.push(''); lines.push('System:'); lines.push(`- OS: ${systemInfo.system.os} ${systemInfo.system.osVersion}`); lines.push(`- CPU Cores: ${systemInfo.system.cpuCores}`); lines.push(`- Memory: ${systemInfo.system.totalMemGb} GB`); lines.push(`- Node: ${systemInfo.software.nodeVersion}`); lines.push(`- ESLint: ${systemInfo.software.eslintVersion}`); if (systemInfo.software.nxVersion) lines.push(`- Nx: ${systemInfo.software.nxVersion}`); lines.push(''); for (const group of suiteResults) { lines.push( `Target: ${group.target} (files: ${group.targetInfo.totalFiles}, ts: ${group.targetInfo.tsFiles}, js: ${group.targetInfo.jsFiles})`, ); lines.push( ` Concurrency=${group.concurrency} → Avg=${group.statistics.avg}s (min=${group.statistics.min.toFixed(3)}s, max=${group.statistics.max.toFixed(3)}s, stdDev=${group.statistics.stdDev}s)`, ); } await writeFile(summaryPath, lines.join('\n')); console.log('\n✅ Benchmark complete'); console.log(`Raw results: ${resultsPath}`); console.log(`Summary: ${summaryPath}`); // Print fastest table to console const targetsSet = Array.from(new Set(suiteResults.map(r => r.target))); const tableRows: Array<{ target: string; bestConc: Concurrency; bestAvg: number; baseline: number | null; speedup: string; }> = []; for (const t of targetsSet) { const byTarget = suiteResults.filter(r => r.target === t); if (byTarget.length === 0) continue; const best = byTarget.reduce((a, b) => a.statistics.avg <= b.statistics.avg ? a : b, ); const baseline = byTarget.find(r => r.concurrency === 'off')?.statistics.avg ?? null; const speedupVal = baseline ? baseline / best.statistics.avg : null; tableRows.push({ target: t, bestConc: best.concurrency, bestAvg: Number(best.statistics.avg.toFixed(3)), baseline, speedup: speedupVal ? `${speedupVal.toFixed(2)}x` : 'n/a', }); } const pad = (s: string, n: number) => s.length >= n ? s : s + ' '.repeat(n - s.length); const header = `${pad('Target', 28)} ${pad('Best', 6)} ${pad('Avg(s)', 8)} ${pad('Baseline(s)', 12)} Speedup`; console.log('\nFastest per target:'); console.log(header); console.log('-'.repeat(header.length)); for (const row of tableRows) { console.log( `${pad(row.target, 28)} ${pad(String(row.bestConc), 6)} ${pad(row.bestAvg.toFixed(3), 8)} ${pad(row.baseline ? row.baseline.toFixed(3) : 'n/a', 12)} ${row.speedup}`, ); } // Per-target detailed table marking the best console.log('\nPer-target breakdown (best marked with ★):'); for (const t of targetsSet) { const byTarget = suiteResults.filter(r => r.target === t); if (byTarget.length === 0) continue; const baseline = byTarget.find(r => r.concurrency === 'off')?.statistics.avg ?? null; const bestAvg = Math.min(...byTarget.map(r => r.statistics.avg)); console.log(`\n${t}`); const h2 = `${pad('Concurrency', 12)} ${pad('Avg(s)', 8)} ${pad('StdDev', 8)} ${pad('Speedup', 8)} Mark`; console.log(h2); console.log('-'.repeat(h2.length)); for (const c of new Set(byTarget.map(r => r.concurrency))) { const r = byTarget.find(x => x.concurrency === c); if (!r) continue; const isBest = Math.abs(r.statistics.avg - bestAvg) < 1e-6; const speed = baseline ? `${(baseline / r.statistics.avg).toFixed(2)}x` : 'n/a'; console.log( `${pad(String(c), 12)} ${pad(r.statistics.avg.toFixed(3), 8)} ${pad(r.statistics.stdDev.toFixed(3), 8)} ${pad(speed, 8)} ${isBest ? '★' : ''}`, ); } } } main().catch(err => { console.error(err); process.exitCode = 1; }); ``` -
BioPhoton revised this gist
Aug 23, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ # ESLint Concurrency Benchmark Script ## Overview - Benchmarks ESLint v9.34+ `--concurrency` across one or more targets -
BioPhoton revised this gist
Aug 23, 2025 . 1 changed file with 48 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ # ESLint Concurrency Benchmark (compact) ## Overview - Benchmarks ESLint v9.34+ `--concurrency` across one or more targets @@ -31,7 +31,7 @@ node --import tsx tools/eslint-perf/bench.compact.ts - Custom targets + concurrency: ```bash node --import tsx tools/eslint-perf/bench.compact.ts \ --targets=packages/lib-a,packages/lib-b \ --concurrency=off,2,4,auto \ --runs=5 ``` @@ -47,6 +47,52 @@ node --import tsx tools/eslint-perf/bench.compact.ts \ - **Summary**: `tools/eslint-perf/results/*.summary.md` - **Terminal**: fastest table + per-target breakdown (★ marks best) ### Output Example ```bash > node --import tsx bench.ts --targets=packages/lib-a --concurrency=off,1,2,4,6,8,auto --runs=3 --verbose --useLocalConfig --tsOnly 📁 Target: packages/lib-a (files: 106, ts: 101, js: 5) 🔧 Concurrency: off Run 1/3 ... $ npx eslint --config="packages/lib-a/eslint.config.js" --concurrency=off --format=json "packages/lib-a/**/*.ts" Time: 8.764s, Files: 101, Errors: 0, Warnings: 0 Run 2/3 ... ✅ Avg: 9.49s (min 9.385s, max 9.615s, ±0.095s) ... ✅ Benchmark complete Raw results: tools/eslint-perf/results/eslint-benchmark-2025-08-23T01-39-46-558Z.json Summary: tools/eslint-perf/results/eslint-benchmark-2025-08-23T01-39-46-558Z.summary.txt Fastest per target: Target Best Avg(s) Baseline(s) Speedup --------------------------------------------------------------------- packages/lib-a off 8.849 8.849 1.00x Per-target breakdown (best marked with ★): packages/lib-a Concurrency Avg(s) StdDev Speedup Mark ------------------------------------------------ off 8.849 0.078 1.00x ★ 1 9.024 0.094 0.98x 2 9.615 0.107 0.92x 4 9.742 0.190 0.91x 6 11.652 0.439 0.76x 8 13.749 0.195 0.64x auto 9.490 0.095 0.93x ``` # CPU Measures ```bash npx @push-based/cpu-prof@latest npx eslint . --concurrency=4 ``` ```ts import { exec as execCb } from 'node:child_process'; import { mkdir, readdir, stat, writeFile } from 'node:fs/promises'; -
BioPhoton created this gist
Aug 23, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,461 @@ # ESLint Concurrency Benchmark Script ## Overview - Benchmarks ESLint v9.34+ `--concurrency` across one or more targets - Runs each concurrency setting multiple times and aggregates stats - Prints a fastest-per-target table and a per-target breakdown (★ marks best) - Optional verbose mode prints ESLint commands, stderr, and one stylish preview ## Defaults - **concurrency**: `off,4,8,auto` - **runs**: `3` - **useLocalConfig**: `true` (uses `<target>/eslint.config.js`) - **tsOnly**: `true` (globs TS files under `<target>` recursively) - **outDir**: `tools/eslint-perf/results` ## Flags - `--targets=dir[,dir...]`: Comma-separated target directories - `--concurrency=v[,v...]`: values in `{off,auto,1..N}` - `--runs=N`: Runs per configuration (default `3`) - `--verbose`: Show ESLint commands/stderr and one stylish preview per target - `--useLocalConfig=true|false`: Pass `--config <target>/eslint.config.js` (default `true`) - `--tsOnly=true|false`: Limit to TS files under `<target>` recursively (default `true`) - `--outDir=path`: Directory for JSON + summary outputs ## Examples - Minimal (defaults): ```bash node --import tsx tools/eslint-perf/bench.compact.ts ``` - Custom targets + concurrency: ```bash node --import tsx tools/eslint-perf/bench.compact.ts \ --targets=packages/models,packages/utils \ --concurrency=off,2,4,auto \ --runs=5 ``` - Disable local config or TS-only: ```bash node --import tsx tools/eslint-perf/bench.compact.ts \ --useLocalConfig=false --tsOnly=false ``` ## Output - **Raw JSON**: `tools/eslint-perf/results/system-info-*.json` and `eslint-benchmark-*.json` - **Summary**: `tools/eslint-perf/results/*.summary.md` - **Terminal**: fastest table + per-target breakdown (★ marks best) ```ts import { exec as execCb } from 'node:child_process'; import { mkdir, readdir, stat, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { promisify } from 'node:util'; const exec = promisify(execCb); type C = 'off' | 'auto' | `${number}`; type Run = { target: string; concurrency: C; run: number; timing: { durationSeconds: number; realTimeSeconds: null; userTimeSeconds: null; sysTimeSeconds: null; }; eslintResults: { filesProcessed: number; errors: number; warnings: number; eslintWarnings: string[]; }; timestamp: string; }; type Stats = { runs: number; min: number; max: number; avg: number; stdDev: number; }; type TI = { target: string; tsFiles: number; jsFiles: number; totalFiles: number; }; const DFLT_TARGETS = [ 'packages/models', 'packages/plugin-eslint', 'packages/cli', 'packages/core', 'packages/utils', ]; const DFLT_CONC: C[] = ['off', '4', '8', 'auto']; const DFLT_RUNS = 3; const pad = (s: string, n: number) => s.length >= n ? s : s + ' '.repeat(n - s.length); const exists = async (p: string) => !!(await stat(p).catch(() => null)); const ensureDir = (d: string) => mkdir(d, { recursive: true }); function args() { const a = process.argv.slice(2); const get = (n: string) => a.find(x => x.startsWith(`--${n}=`))?.split('=')[1]; const targets = get('targets')?.split(',').filter(Boolean) ?? DFLT_TARGETS; const conc = (get('concurrency')?.split(',').filter(Boolean) as C[] | undefined) ?? DFLT_CONC; const runs = Number.parseInt(get('runs') ?? `${DFLT_RUNS}`, 10); const outDir = get('outDir') ?? path.join('tools', 'eslint-perf', 'results'); const verbose = a.includes('--verbose') || get('verbose') === 'true'; const useLocalConfig = get('useLocalConfig') === 'false' ? false : true; const tsOnly = get('tsOnly') === 'false' ? false : true; return { targets, conc, runs, outDir, verbose, useLocalConfig, tsOnly }; } async function sys() { const nx = await exec('npx nx --version') .then(r => r.stdout.trim().split('\n')[0]) .catch(() => null); return { timestamp: new Date().toISOString(), system: { os: os.platform(), osVersion: os.release(), architecture: os.arch(), cpuCores: os.cpus()?.length ?? 0, totalMemGb: Math.round((os.totalmem() / 1024 ** 3) * 100) / 100, hostname: os.hostname(), }, software: { nodeVersion: process.version, npmVersion: (await exec('npm --version')).stdout.trim(), eslintVersion: (await exec('npx eslint --version')).stdout.trim(), nxVersion: nx, }, }; } async function count(dir: string, exts: string[]) { let n = 0; const w = async (d: string): Promise<void> => { const es = await readdir(d, { withFileTypes: true }); for (const e of es) { const p = path.join(d, e.name); if (e.isDirectory()) { if (['node_modules', 'dist', 'coverage'].includes(e.name)) continue; await w(p); } else if (e.isFile() && exts.some(ext => e.name.endsWith(ext))) n++; } }; await w(dir); return n; } async function info(t: string): Promise<TE> { const abs = path.resolve(t); if (!(await exists(abs))) return { target: t, tsFiles: 0, jsFiles: 0, totalFiles: 0 }; const ts = await count(abs, ['.ts', '.tsx']); const js = await count(abs, ['.js', '.jsx', '.mjs', '.cjs']); return { target: t, tsFiles: ts, jsFiles: js, totalFiles: ts + js }; } type TE = TI; const cmd = ( t: string, c: C, o: { local: boolean; ts: boolean; json: boolean }, ) => `npx eslint${o.local ? ` --config=${JSON.stringify(path.join(t, 'eslint.config.js'))}` : ''} --concurrency=${c} ${o.json ? '--format=json' : '--format=stylish'} ${[o.ts ? path.join(t, '**', '*.ts') : t].map(p => JSON.stringify(p)).join(' ')}`; async function once( t: string, c: C, i: number, v: boolean, local: boolean, ts: boolean, ): Promise<Run> { const s = process.hrtime.bigint(); const k = cmd(t, c, { local, ts, json: true }); let out = '', err = ''; try { const r = await exec(k, { maxBuffer: 1024 * 1024 * 200 }); out = r.stdout; err = r.stderr; } catch (e: any) { out = e.stdout ?? ''; err = e.stderr ?? ''; } if (v) { console.log(` $ ${k}`); if (err.trim()) { console.log(' [stderr]'); console.log( err .split('\n') .map(l => ` ${l}`) .join('\n'), ); } } const d = Number(process.hrtime.bigint() - s) / 1_000_000_000; let files = 0, errors = 0, warnings = 0; try { const j = JSON.parse(out) as Array<{ errorCount: number; warningCount: number; }>; files = j.length; for (const r of j) { errors += r.errorCount || 0; warnings += r.warningCount || 0; } } catch {} const warns = err.includes('ESLintPoorConcurrencyWarning') ? ['ESLintPoorConcurrencyWarning'] : []; return { target: t, concurrency: c, run: i, timing: { durationSeconds: d, realTimeSeconds: null, userTimeSeconds: null, sysTimeSeconds: null, }, eslintResults: { filesProcessed: files, errors, warnings, eslintWarnings: warns, }, timestamp: new Date().toISOString(), }; } const statz = (xs: number[]): Stats => { const n = xs.length, min = Math.min(...xs), max = Math.max(...xs), avg = xs.reduce((a, b) => a + b, 0) / n, sd = Math.sqrt(xs.reduce((a, v) => a + (v - avg) ** 2, 0) / n); return { runs: n, min, max, avg: Number(avg.toFixed(3)), stdDev: Number(sd.toFixed(3)), }; }; async function stylish(t: string) { const k = cmd(t, 'off', { local: true, ts: true, json: false }); try { const { stdout, stderr } = await exec(k, { maxBuffer: 1024 * 1024 * 200 }); if (stdout.trim()) { console.log(' [stylish output]'); console.log( stdout .split('\n') .slice(0, 300) .map(l => ` ${l}`) .join('\n'), ); } if (stderr.trim()) { console.log(' [stylish stderr]'); console.log( stderr .split('\n') .map(l => ` ${l}`) .join('\n'), ); } } catch (e: any) { const o = e.stdout ?? '', er = e.stderr ?? ''; if (o.trim()) { console.log(' [stylish output]'); console.log( o .split('\n') .slice(0, 300) .map((l: string) => ` ${l}`) .join('\n'), ); } if (er.trim()) { console.log(' [stylish stderr]'); console.log( er .split('\n') .map((l: string) => ` ${l}`) .join('\n'), ); } } } async function main() { const { targets, conc, runs, outDir, verbose, useLocalConfig, tsOnly } = args(); await ensureDir(outDir); const si = await sys(), ts = new Date().toISOString().replace(/[:.]/g, '-'); const resPath = path.join(outDir, `eslint-benchmark-${ts}.json`), sumPath = path.join(outDir, `eslint-benchmark-${ts}.summary.md`); const results: any[] = []; await writeFile( path.join(outDir, `system-info-${ts}.json`), JSON.stringify(si, null, 2), ); for (const t of targets) { if (!(await exists(path.resolve(t)))) { console.warn(`Skipping missing target: ${t}`); continue; } const ti = await info(t); console.log( `\n📁 Target: ${t} (files: ${ti.totalFiles}, ts: ${ti.tsFiles}, js: ${ti.jsFiles})`, ); if (verbose) await stylish(t); for (const c of conc) { console.log(` 🔧 Concurrency: ${c}`); const rs: Run[] = []; for (let i = 1; i <= runs; i++) { console.log(` Run ${i}/${runs} ...`); const r = await once(t, c, i, verbose, useLocalConfig, tsOnly); rs.push(r); console.log( ` Time: ${r.timing.durationSeconds.toFixed(3)}s, Files: ${r.eslintResults.filesProcessed}, Errors: ${r.eslintResults.errors}, Warnings: ${r.eslintResults.warnings}${r.eslintResults.eslintWarnings.length ? ' ⚠️ ' + r.eslintResults.eslintWarnings.join(',') : ''}`, ); } const s = statz(rs.map(r => r.timing.durationSeconds)); results.push({ target: t, targetInfo: ti, concurrency: c, statistics: s, runs: rs, }); console.log( ` ✅ Avg: ${s.avg}s (min ${s.min.toFixed(3)}s, max ${s.max.toFixed(3)}s, ±${s.stdDev}s)`, ); } } await writeFile( resPath, JSON.stringify({ systemInfo: si, results }, null, 2), ); // Build Markdown summary (system info, fastest table, per-target breakdown) const tset = Array.from(new Set(results.map((r: any) => r.target))); const rows = tset.map(t => { const xs = results.filter((r: any) => r.target === t); const best = xs.reduce((a: any, b: any) => a.statistics.avg <= b.statistics.avg ? a : b, ); const base = xs.find((r: any) => r.concurrency === 'off')?.statistics.avg ?? null; const speed = base ? (base / best.statistics.avg).toFixed(2) + 'x' : 'n/a'; return { t, bestConc: best.concurrency as C, bestAvg: Number(best.statistics.avg.toFixed(3)), base, speed, }; }); const md: string[] = []; md.push('# ESLint Concurrency Benchmark Report'); md.push(''); md.push(`Generated: ${new Date().toString()}`); md.push(''); md.push('## System'); md.push(`- **OS**: ${si.system.os} ${si.system.osVersion}`); md.push(`- **CPU Cores**: ${si.system.cpuCores}`); md.push(`- **Memory**: ${si.system.totalMemGb} GB`); md.push(`- **Node**: \\`${si.software.nodeVersion}\\``); md.push(`- **ESLint**: \\`${si.software.eslintVersion}\\``); if (si.software.nxVersion) md.push(`- **Nx**: \\`${si.software.nxVersion}\\``); md.push(''); md.push('## Fastest per target'); md.push(''); md.push('| Target | Best | Avg (s) | Baseline (s) | Speedup |'); md.push('|---|:---:|---:|---:|---:|'); for (const r of rows) { md.push(`| \\`${r.t}\\` | **${r.bestConc}** | **${r.bestAvg.toFixed(3)}** | ${r.base ? r.base.toFixed(3) : 'n/a'} | **${r.speed}** |`); } md.push(''); md.push('## Per-target breakdown'); md.push('Best rows are marked with ★.'); for (const t of tset) { const xs = results.filter((r: any) => r.target === t); const base = xs.find((r: any) => r.concurrency === 'off')?.statistics.avg ?? null; const bestAvg = Math.min(...xs.map((r: any) => r.statistics.avg)); md.push(''); md.push(`### \\`${t}\\``); md.push(''); md.push('| Concurrency | Avg (s) | StdDev | Speedup |'); md.push('|:-----------:|---:|---:|---:|'); for (const c of Array.from(new Set(xs.map((r: any) => r.concurrency)))) { const r = xs.find((q: any) => q.concurrency === c)!; const isBest = Math.abs(r.statistics.avg - bestAvg) < 1e-6; const sp = base ? `${(base / r.statistics.avg).toFixed(2)}x` : 'n/a'; const row = `| \\`${String(c)}\\` | ${r.statistics.avg.toFixed(3)} | ${r.statistics.stdDev.toFixed(3)} | ${sp} | ${isBest ? '★' : ''}`; md.push(isBest ? row.replace(/\| ([^|]+) \|/, match => match.replace(/([^|]+)/, '**$1**')) : row); } } await writeFile(sumPath, md.join('\n')); console.log('\n✅ Benchmark complete'); console.log(`Raw results: ${resPath}`); console.log(`Summary: ${sumPath}`); // Reuse computed tset/rows for terminal tables const header = `${pad('Target', 28)} ${pad('Best', 6)} ${pad('Avg(s)', 8)} ${pad('Baseline(s)', 12)} Speedup`; console.log('\nFastest per target:'); console.log(header); console.log('-'.repeat(header.length)); for (const r of rows) console.log( `${pad(r.t, 28)} ${pad(String(r.bestConc), 6)} ${pad(r.bestAvg.toFixed(3), 8)} ${pad(r.base ? r.base.toFixed(3) : 'n/a', 12)} ${r.speed}`, ); console.log('\nPer-target breakdown (best marked with ★):'); for (const t of tset) { const xs = results.filter((r: any) => r.target === t); const base = xs.find((r: any) => r.concurrency === 'off')?.statistics.avg ?? null; const best = Math.min(...xs.map((r: any) => r.statistics.avg)); console.log(`\n${t}`); const h2 = `${pad('Concurrency', 12)} ${pad('Avg(s)', 8)} ${pad('StdDev', 8)} ${pad('Speedup', 8)} Mark`; console.log(h2); console.log('-'.repeat(h2.length)); for (const c of Array.from(new Set(xs.map((r: any) => r.concurrency)))) { const r = xs.find((q: any) => q.concurrency === c)!; const sp = base ? `${(base / r.statistics.avg).toFixed(2)}x` : 'n/a'; console.log( `${pad(String(c), 12)} ${pad(r.statistics.avg.toFixed(3), 8)} ${pad(r.statistics.stdDev.toFixed(3), 8)} ${pad(sp, 8)} ${Math.abs(r.statistics.avg - best) < 1e-6 ? '★' : ''}`, ); } } } main().catch(e => { console.error(e); process.exitCode = 1; }); ```