Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 58 additions & 12 deletions benchmarks/perf/format-pr-comment.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,28 @@ if (results.run.kind !== "pull_request") {
process.exit(0);
}

const response = JSON.parse(await readFile(responsePath, "utf8"));
const uploadedComparison = response.comparison;
if (!uploadedComparison)
throw new Error("Performance upload response did not include a comparison");

const resultBenchmarks = Array.isArray(results.benchmarks) ? results.benchmarks : [];
const resultsByBenchmark = new Map(
resultBenchmarks.map((benchmark) => [benchmark.benchmarkId, benchmark]),
);
const response = await readUploadResponse(responsePath);
const uploadedComparison = response.comparison;
if (!uploadedComparison && resultBenchmarks.length === 0) {
await writeFile(outputPath, "");
process.exit(0);
}
const hasPairedBaseline = resultBenchmarks.some((benchmark) => benchmark.baselineSamples);
const comparisonSource = uploadedComparison ?? localComparison(resultBenchmarks);
const comparison = {
...uploadedComparison,
...comparisonSource,
baseline: hasPairedBaseline
? {
sha: results.run.baseSha,
shortSha: results.run.baseSha.slice(0, 7),
shortSha: shortSha(results.run.baseSha),
measuredAt: results.run.measuredAt,
}
: uploadedComparison.baseline,
measurements: uploadedComparison.measurements.map((measurement) => {
: comparisonSource.baseline,
measurements: comparisonSource.measurements.map((measurement) => {
const benchmark = resultsByBenchmark.get(measurement.benchmarkId);
return benchmark?.baselineSamples
? {
Expand Down Expand Up @@ -64,6 +66,46 @@ function formatValue(value, unit) {
return `${Number(value.toFixed(2))} ${unit}`;
}

function shortSha(sha) {
return typeof sha === "string" && sha.length > 0 ? sha.slice(0, 7) : "unknown";
}

async function readUploadResponse(path) {
try {
return JSON.parse(await readFile(path, "utf8"));
} catch (error) {
if (error?.code === "ENOENT") return { uploaded: false, reason: "missing_response" };
throw error;
}
}

function localComparison(benchmarks) {
return {
uploaded: false,
Comment thread
NathanDrake2406 marked this conversation as resolved.
head: {
sha: results.run.commitSha,
shortSha: shortSha(results.run.commitSha),
measuredAt: results.run.measuredAt,
},
baseline: results.run.baseSha
? {
sha: results.run.baseSha,
shortSha: shortSha(results.run.baseSha),
measuredAt: results.run.measuredAt,
Comment thread
NathanDrake2406 marked this conversation as resolved.
Outdated
}
: null,
measurements: benchmarks.map((benchmark) => ({
Comment thread
NathanDrake2406 marked this conversation as resolved.
benchmarkId: benchmark.benchmarkId,
label: benchmark.label,
implementationLabel: benchmark.implementationLabel,
unit: benchmark.unit,
lowerIsBetter: benchmark.lowerIsBetter,
baseline: benchmark.baselineSamples,
current: benchmark.samples,
})),
};
}

function measurementChange(measurement) {
if (!measurement.baseline) return null;
return (
Expand Down Expand Up @@ -115,7 +157,9 @@ const hasCurrentOnlyMeasurement = comparison.measurements.some(
(measurement) =>
!measurement.baseline && !resultsByBenchmark.get(measurement.benchmarkId)?.baselineSamples,
);
const dashboardUrl = `https://vinext.dev/benchmarks/pull/${results.run.pullRequest}`;
const dashboardUrl = uploadedComparison
? `https://vinext.dev/benchmarks/pull/${results.run.pullRequest}`
: null;
const rows = measurements.map((measurement) =>
[
escapeCell(measurement.label),
Expand All @@ -140,7 +184,7 @@ const body = [
: `Compared \`${comparison.head.shortSha}\` against base \`${comparison.baseline.shortSha}\`. Paired benchmarks use alternating same-runner rounds; unpaired benchmarks have no baseline.${skippedNextjs ? " Next.js was unchanged and skipped." : ""}`
: `Compared \`${comparison.head.shortSha}\` against base \`${comparison.baseline.shortSha}\` using alternating same-runner rounds.${skippedNextjs ? " Next.js was unchanged and skipped." : ""}`
: `Compared \`${comparison.head.shortSha}\` against base \`${comparison.baseline.shortSha}\`.`
: `Measured \`${comparison.head.shortSha}\`. No benchmark run is available for base \`${results.run.baseSha.slice(0, 7)}\`.`,
: `Measured \`${comparison.head.shortSha}\`. No benchmark run is available for base \`${shortSha(results.run.baseSha)}\`.`,
"",
comparison.baseline
? `**${improvements} improved · ${regressions} regressed · ${neutral} within ±1.5%**`
Expand All @@ -150,7 +194,9 @@ const body = [
"|---|---|---:|---:|---:|",
...rows.map((row) => `| ${row} |`),
"",
`[View detailed results and traces](${dashboardUrl})`,
dashboardUrl
? `[View detailed results and traces](${dashboardUrl})`
: "Dashboard upload was unavailable for this run.",
"",
`<sub>🟢 improvement · 🔴 regression · ⚫ change below 1.5%${
hasPairedBaseline
Expand Down
13 changes: 11 additions & 2 deletions benchmarks/perf/upload-results.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ const secret = process.env.COMPAT_INGEST_SECRET;
const artifactRoot = process.env.VINEXT_PERF_ARTIFACT_ROOT
? resolve(process.env.VINEXT_PERF_ARTIFACT_ROOT)
: null;
const uploadResponsePath = process.env.VINEXT_PERF_UPLOAD_RESPONSE_PATH
? resolve(process.env.VINEXT_PERF_UPLOAD_RESPONSE_PATH)
: null;

if (!secret) {
if (uploadResponsePath) {
await writeFile(
uploadResponsePath,
`${JSON.stringify({ uploaded: false, reason: "missing_secret" })}\n`,
);
}
console.log("COMPAT_INGEST_SECRET is not configured; skipping performance upload.");
process.exit(0);
}
Expand Down Expand Up @@ -116,8 +125,8 @@ try {
const responseBody = await uploadMetadata();
metadataCommitted = true;

if (process.env.VINEXT_PERF_UPLOAD_RESPONSE_PATH) {
await writeFile(resolve(process.env.VINEXT_PERF_UPLOAD_RESPONSE_PATH), `${responseBody}\n`);
if (uploadResponsePath) {
await writeFile(uploadResponsePath, `${responseBody}\n`);
}
console.log(responseBody);
} catch (error) {
Expand Down
64 changes: 64 additions & 0 deletions tests/performance-benchmarks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,70 @@ describe("paired performance benchmarks", () => {
expect(comment).not.toContain("Next.js |");
});

it("writes a skipped upload response when the dashboard secret is unavailable", () => {
const directory = mkdtempSync(join(tmpdir(), "vinext-perf-upload-skip-"));
const resultsPath = join(directory, "results.json");
const responsePath = join(directory, "response.json");
writeFileSync(resultsPath, JSON.stringify({ benchmarks: [] }));

execFileSync(process.execPath, ["benchmarks/perf/upload-results.mjs", resultsPath], {
cwd: join(import.meta.dirname, ".."),
env: {
...process.env,
COMPAT_INGEST_SECRET: "",
VINEXT_PERF_UPLOAD_RESPONSE_PATH: responsePath,
},
});

expect(JSON.parse(readFileSync(responsePath, "utf8"))).toEqual({
uploaded: false,
reason: "missing_secret",
});
});

it("renders paired PR comments when the dashboard upload response is missing", () => {
const directory = mkdtempSync(join(tmpdir(), "vinext-perf-local-comment-"));
const resultsPath = join(directory, "results.json");
const responsePath = join(directory, "response.json");
const outputPath = join(directory, "comment.md");
writeFileSync(
resultsPath,
JSON.stringify({
run: {
kind: "pull_request",
pullRequest: 42,
commitSha: "a".repeat(40),
baseSha: "b".repeat(40),
measuredAt: "2026-01-01T00:00:00.000Z",
},
benchmarks: [
{
benchmarkId: "vinext-production-build",
label: "Production build time",
implementationLabel: "vinext",
unit: "ms",
lowerIsBetter: true,
samples: { median: 90 },
baselineSamples: { median: 100 },
},
],
}),
);

execFileSync(
process.execPath,
["benchmarks/perf/format-pr-comment.mjs", resultsPath, responsePath, outputPath],
{ cwd: join(import.meta.dirname, "..") },
);

const comment = readFileSync(outputPath, "utf8");
expect(comment).toContain("Compared `aaaaaaa` against base `bbbbbbb`");
expect(comment).toContain("1 improved · 0 regressed · 0 within ±1.5%");
expect(comment).toContain("| Production build time | vinext | 100 ms | 90 ms |");
expect(comment).toContain("Dashboard upload was unavailable for this run.");
expect(comment).not.toContain("View detailed results and traces");
});

it("labels mixed paired and historical PR comment baselines", () => {
const directory = mkdtempSync(join(tmpdir(), "vinext-perf-mixed-comment-"));
const resultsPath = join(directory, "results.json");
Expand Down
Loading