123 lines
5.4 KiB
JavaScript
123 lines
5.4 KiB
JavaScript
import { spawn } from "node:child_process";
|
||
import fs from "node:fs";
|
||
import os from "node:os";
|
||
import path from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
|
||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||
const rounds = Math.max(1, Number(process.env.FIREFOX_BENCHMARK_ROUNDS || 5));
|
||
const cold = process.argv.includes("--cold");
|
||
const fast = process.argv.includes("--fast");
|
||
const command = fast ? "build:firefox52:fast" : "build:firefox52";
|
||
const reportDir = path.join(rootDir, ".runtime", "firefox52-benchmark");
|
||
|
||
function removeWorkspaceDirectory(relativePath) {
|
||
const target = path.resolve(rootDir, relativePath);
|
||
if (path.dirname(target) !== rootDir) throw new Error(`拒绝清理工作区之外的目录:${target}`);
|
||
fs.rmSync(target, { recursive: true, force: true });
|
||
}
|
||
|
||
function runBuild() {
|
||
return new Promise((resolve) => {
|
||
const startedAt = performance.now();
|
||
const executable = process.platform === "win32" ? (process.env.ComSpec || "cmd.exe") : "pnpm";
|
||
const args = process.platform === "win32" ? ["/d", "/s", "/c", `pnpm ${command}`] : [command];
|
||
const child = spawn(executable, args, {
|
||
cwd: rootDir,
|
||
env: process.env,
|
||
stdio: "inherit",
|
||
shell: false,
|
||
});
|
||
child.on("exit", (code, signal) => {
|
||
resolve({
|
||
durationMs: Math.round(performance.now() - startedAt),
|
||
exitCode: code,
|
||
signal,
|
||
});
|
||
});
|
||
child.on("error", (error) => resolve({ durationMs: Math.round(performance.now() - startedAt), exitCode: -1, error: String(error) }));
|
||
});
|
||
}
|
||
|
||
function percentile(values, ratio) {
|
||
if (values.length === 0) return null;
|
||
const sorted = [...values].sort((a, b) => a - b);
|
||
return sorted[Math.ceil(ratio * sorted.length) - 1];
|
||
}
|
||
|
||
function artifactStats() {
|
||
const assetsDir = path.join(rootDir, "dist-firefox52", "assets");
|
||
const stats = { jsFiles: 0, jsBytes: 0, cssFiles: 0, cssBytes: 0 };
|
||
if (!fs.existsSync(assetsDir)) return stats;
|
||
for (const entry of fs.readdirSync(assetsDir, { withFileTypes: true })) {
|
||
if (!entry.isFile()) continue;
|
||
const size = fs.statSync(path.join(assetsDir, entry.name)).size;
|
||
if (/\.js$/i.test(entry.name)) {
|
||
stats.jsFiles += 1;
|
||
stats.jsBytes += size;
|
||
} else if (/\.css$/i.test(entry.name)) {
|
||
stats.cssFiles += 1;
|
||
stats.cssBytes += size;
|
||
}
|
||
}
|
||
return stats;
|
||
}
|
||
|
||
if (cold) {
|
||
const productionCache = path.resolve(rootDir, ".rspack-cache", "firefox52-production");
|
||
if (!productionCache.startsWith(`${path.resolve(rootDir, ".rspack-cache")}${path.sep}`)) {
|
||
throw new Error(`拒绝清理预期缓存目录之外的路径:${productionCache}`);
|
||
}
|
||
fs.rmSync(productionCache, { recursive: true, force: true });
|
||
removeWorkspaceDirectory("dist-firefox52");
|
||
}
|
||
|
||
const samples = [];
|
||
for (let index = 0; index < rounds; index += 1) {
|
||
console.log(`\n[Firefox 52 benchmark] ${index + 1}/${rounds} ${command}`);
|
||
const sample = await runBuild();
|
||
samples.push(sample);
|
||
if (sample.exitCode !== 0) break;
|
||
}
|
||
|
||
const successfulDurations = samples.filter((item) => item.exitCode === 0).map((item) => item.durationMs);
|
||
const steadyDurations = successfulDurations.slice(1);
|
||
const now = new Date();
|
||
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
||
const report = {
|
||
generatedAt: now.toISOString(),
|
||
command: `pnpm ${command}`,
|
||
coldCacheBeforeFirstRun: cold,
|
||
requestedRounds: rounds,
|
||
environment: {
|
||
platform: `${os.platform()} ${os.release()} ${os.arch()}`,
|
||
cpu: os.cpus()[0]?.model || "unknown",
|
||
logicalCpus: os.cpus().length,
|
||
totalMemoryBytes: os.totalmem(),
|
||
node: process.version,
|
||
pnpm: process.env.npm_config_user_agent || "run via pnpm.cmd",
|
||
},
|
||
samples,
|
||
summary: {
|
||
successfulRuns: successfulDurations.length,
|
||
medianMs: percentile(successfulDurations, 0.5),
|
||
p95Ms: percentile(successfulDurations, 0.95),
|
||
minMs: successfulDurations.length ? Math.min(...successfulDurations) : null,
|
||
maxMs: successfulDurations.length ? Math.max(...successfulDurations) : null,
|
||
steadyStateMedianMs: percentile(steadyDurations, 0.5),
|
||
steadyStateP95Ms: percentile(steadyDurations, 0.95),
|
||
},
|
||
artifacts: artifactStats(),
|
||
};
|
||
|
||
fs.mkdirSync(reportDir, { recursive: true });
|
||
const jsonPath = path.join(reportDir, `${stamp}.json`);
|
||
const markdownPath = path.join(reportDir, `${stamp}.md`);
|
||
fs.writeFileSync(jsonPath, `${JSON.stringify(report, null, 2)}\n`);
|
||
fs.writeFileSync(markdownPath, `# Firefox 52 Rspack 构建基准\n\n- 时间:${report.generatedAt}\n- 命令:\`${report.command}\`\n- 轮次:${samples.length}\n- 全部中位数:${report.summary.medianMs ?? "-"} ms\n- 全部 P95:${report.summary.p95Ms ?? "-"} ms\n- 首轮后的稳态中位数:${report.summary.steadyStateMedianMs ?? "-"} ms\n- 首轮后的稳态 P95:${report.summary.steadyStateP95Ms ?? "-"} ms\n- JS:${report.artifacts.jsFiles} 个 / ${report.artifacts.jsBytes} bytes\n- CSS:${report.artifacts.cssFiles} 个 / ${report.artifacts.cssBytes} bytes\n\n| 轮次 | 耗时(ms) | 退出码 |\n|---:|---:|---:|\n${samples.map((sample, index) => `| ${index + 1} | ${sample.durationMs} | ${sample.exitCode} |`).join("\n")}\n`);
|
||
|
||
console.log(`\nFirefox 52 benchmark median=${report.summary.medianMs}ms p95=${report.summary.p95Ms}ms steadyMedian=${report.summary.steadyStateMedianMs}ms`);
|
||
console.log(`JSON: ${jsonPath}`);
|
||
console.log(`Markdown: ${markdownPath}`);
|
||
if (successfulDurations.length !== rounds) process.exitCode = 1;
|