Files
redwoodjs__local-ci/scripts/rust-performance.mjs
2026-05-22 16:43:31 +02:00

106 lines
3.4 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { performance } from "node:perf_hooks";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const rustBin = path.join(root, "target", "release", "agent-ci");
const tsCli = ["node", "packages/cli/dist/native-launcher.js"];
function runRequired(command, args) {
const result = spawnSync(command, args, { cwd: root, stdio: "inherit", env: process.env });
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}
function timed(command, args, env = {}) {
const start = performance.now();
const result = spawnSync(command, args, {
cwd: root,
encoding: "utf8",
env: { ...process.env, ...env },
});
const durationMs = performance.now() - start;
return { durationMs, status: result.status ?? 1, stdout: result.stdout, stderr: result.stderr };
}
function bench(label, command, args, options = {}) {
const iterations = options.iterations ?? 5;
const expectedStatuses = new Set(options.expectedStatuses ?? [0]);
const times = [];
for (let i = 0; i < iterations; i += 1) {
const result = timed(command, args, options.env);
if (!expectedStatuses.has(result.status)) {
throw new Error(
`${label}: unexpected exit ${result.status}\n${result.stdout}\n${result.stderr}`,
);
}
times.push(result.durationMs);
}
times.sort((a, b) => a - b);
const avg = times.reduce((sum, value) => sum + value, 0) / times.length;
return { label, min: times[0], avg, max: times.at(-1) };
}
runRequired("pnpm", ["--filter", "@redwoodjs/agent-ci", "build"]);
runRequired("cargo", ["build", "--release", "-p", "agent-ci"]);
const rows = [
bench("startup: TypeScript --help", tsCli[0], [...tsCli.slice(1), "--help"], {
env: { AGENT_CI_FORCE_TYPESCRIPT: "1" },
}),
bench("startup: Rust --help", rustBin, ["--help"]),
bench("workflow parse: TypeScript diff parser", "node", ["scripts/diff-parse.ts"], {
iterations: 3,
}),
bench(
"workflow parse: Rust smoke discovery",
rustBin,
["run", "--workflow", ".github/workflows/smoke-expressions.yml", "--quiet", "--no-matrix"],
{
iterations: 3,
expectedStatuses: [0, 1, 2],
},
),
bench(
"job orchestration: Rust matrix discovery",
rustBin,
["run", "--workflow", ".github/workflows/smoke-matrix.yml", "--quiet"],
{
iterations: 3,
expectedStatuses: [0, 1, 2],
},
),
];
function formatMs(value) {
return `${value.toFixed(1)} ms`;
}
const lines = [
"# Rust rewrite performance snapshot",
"",
`Generated by \`pnpm rust:perf\` on ${new Date().toISOString()}.`,
"",
"| Benchmark | min | avg | max |",
"| --- | ---: | ---: | ---: |",
...rows.map(
(row) =>
`| ${row.label} | ${formatMs(row.min)} | ${formatMs(row.avg)} | ${formatMs(row.max)} |`,
),
"",
"Notes:",
"",
"- Rust workflow execution parity is validated separately; workflow benchmarks time the release binary path and accept any known Agent CI exit status.",
"- The TypeScript workflow-parse comparison uses the existing differential parser script to avoid launching Docker jobs.",
"- Re-run before switching defaults so release notes include current numbers from the final release build.",
"",
];
const outPath = path.join(root, ".docs/rust-performance.md");
fs.writeFileSync(outPath, `${lines.join("\n")}`);
console.log(lines.join("\n"));