chore: import upstream snapshot with attribution
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
#!/usr/bin/env -S node --experimental-strip-types --disable-warning=ExperimentalWarning
|
||||
import { execFile } from "node:child_process";
|
||||
import { mkdir, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const zero = process.env.ZERO_BIN || "bin/zero";
|
||||
const target = process.env.ZERO_GRAPH_BUILD_PERF_TARGET || "linux-musl-x64";
|
||||
const outRoot = process.env.ZERO_GRAPH_BUILD_PERF_DIR || `/tmp/zero-graph-build-perf-${process.pid}`;
|
||||
const maxBuffer = 128 * 1024 * 1024;
|
||||
|
||||
type Fixture = {
|
||||
name: string;
|
||||
input: string;
|
||||
target?: string;
|
||||
checkTarget?: string;
|
||||
};
|
||||
|
||||
type CommandRun = {
|
||||
ok: boolean;
|
||||
command: string[];
|
||||
wallMs: number;
|
||||
body: any;
|
||||
stderr: string;
|
||||
};
|
||||
|
||||
type PerformanceBudget = {
|
||||
name: string;
|
||||
fixture: string;
|
||||
pass: "cold" | "warm";
|
||||
command: "build" | "check";
|
||||
metric: string;
|
||||
max: number;
|
||||
unit: "ms" | "bytes";
|
||||
};
|
||||
|
||||
const fixtures: Fixture[] = [
|
||||
{ name: "hello-artifact", input: "examples/hello.graph" },
|
||||
{ name: "stdlib-heavy-artifact", input: "conformance/native/pass/stdlib-target-neutral.graph" },
|
||||
{ name: "crm-api-package", input: "examples/crm-api" },
|
||||
];
|
||||
|
||||
const budgetMode = process.env.ZERO_GRAPH_BUILD_PERF_BUDGET || "warn";
|
||||
|
||||
function envNumber(name: string, fallback: number) {
|
||||
const raw = process.env[name];
|
||||
if (!raw) return fallback;
|
||||
const parsed = Number(raw);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
const performanceBudgets: PerformanceBudget[] = [
|
||||
{
|
||||
name: "simple graph cold build",
|
||||
fixture: "hello-artifact",
|
||||
pass: "cold",
|
||||
command: "build",
|
||||
metric: "compilerElapsedMs",
|
||||
max: envNumber("ZERO_GRAPH_BUILD_PERF_SIMPLE_COLD_MAX_MS", 5000),
|
||||
unit: "ms",
|
||||
},
|
||||
{
|
||||
name: "simple graph warm build",
|
||||
fixture: "hello-artifact",
|
||||
pass: "warm",
|
||||
command: "build",
|
||||
metric: "compilerElapsedMs",
|
||||
max: envNumber("ZERO_GRAPH_BUILD_PERF_SIMPLE_WARM_MAX_MS", 3000),
|
||||
unit: "ms",
|
||||
},
|
||||
{
|
||||
name: "simple graph artifact size",
|
||||
fixture: "hello-artifact",
|
||||
pass: "warm",
|
||||
command: "build",
|
||||
metric: "artifactBytes",
|
||||
max: envNumber("ZERO_GRAPH_BUILD_PERF_SIMPLE_ARTIFACT_MAX_BYTES", 8192),
|
||||
unit: "bytes",
|
||||
},
|
||||
{
|
||||
name: "simple graph lowered IR size",
|
||||
fixture: "hello-artifact",
|
||||
pass: "warm",
|
||||
command: "build",
|
||||
metric: "loweredIrBytes",
|
||||
max: envNumber("ZERO_GRAPH_BUILD_PERF_SIMPLE_LOWERED_IR_MAX_BYTES", 16384),
|
||||
unit: "bytes",
|
||||
},
|
||||
{
|
||||
name: "stdlib-heavy cold build",
|
||||
fixture: "stdlib-heavy-artifact",
|
||||
pass: "cold",
|
||||
command: "build",
|
||||
metric: "compilerElapsedMs",
|
||||
max: envNumber("ZERO_GRAPH_BUILD_PERF_STDLIB_HEAVY_COLD_MAX_MS", 120000),
|
||||
unit: "ms",
|
||||
},
|
||||
{
|
||||
name: "stdlib-heavy warm build",
|
||||
fixture: "stdlib-heavy-artifact",
|
||||
pass: "warm",
|
||||
command: "build",
|
||||
metric: "compilerElapsedMs",
|
||||
max: envNumber("ZERO_GRAPH_BUILD_PERF_STDLIB_HEAVY_WARM_MAX_MS", 120000),
|
||||
unit: "ms",
|
||||
},
|
||||
{
|
||||
name: "stdlib-heavy warm graph load",
|
||||
fixture: "stdlib-heavy-artifact",
|
||||
pass: "warm",
|
||||
command: "build",
|
||||
metric: "timings.graphLoadMs",
|
||||
max: envNumber("ZERO_GRAPH_BUILD_PERF_STDLIB_HEAVY_GRAPH_LOAD_MAX_MS", 30000),
|
||||
unit: "ms",
|
||||
},
|
||||
{
|
||||
name: "stdlib-heavy warm stdlib merge",
|
||||
fixture: "stdlib-heavy-artifact",
|
||||
pass: "warm",
|
||||
command: "build",
|
||||
metric: "timings.stdlibMergeMs",
|
||||
max: envNumber("ZERO_GRAPH_BUILD_PERF_STDLIB_HEAVY_STDLIB_MERGE_MAX_MS", 70000),
|
||||
unit: "ms",
|
||||
},
|
||||
{
|
||||
name: "stdlib-heavy artifact size",
|
||||
fixture: "stdlib-heavy-artifact",
|
||||
pass: "warm",
|
||||
command: "build",
|
||||
metric: "artifactBytes",
|
||||
max: envNumber("ZERO_GRAPH_BUILD_PERF_STDLIB_HEAVY_ARTIFACT_MAX_BYTES", 262144),
|
||||
unit: "bytes",
|
||||
},
|
||||
{
|
||||
name: "stdlib-heavy lowered IR size",
|
||||
fixture: "stdlib-heavy-artifact",
|
||||
pass: "warm",
|
||||
command: "build",
|
||||
metric: "loweredIrBytes",
|
||||
max: envNumber("ZERO_GRAPH_BUILD_PERF_STDLIB_HEAVY_LOWERED_IR_MAX_BYTES", 4194304),
|
||||
unit: "bytes",
|
||||
},
|
||||
];
|
||||
|
||||
function selectedFixtures() {
|
||||
const raw = process.env.ZERO_GRAPH_BUILD_PERF_CASES;
|
||||
if (!raw) return fixtures;
|
||||
const wanted = new Set(raw.split(",").map((part) => part.trim()).filter(Boolean));
|
||||
return fixtures.filter((fixture) => wanted.has(fixture.name) || wanted.has(fixture.input));
|
||||
}
|
||||
|
||||
function phaseMs(body: any, name: string) {
|
||||
const phase = Array.isArray(body?.compilerPhases)
|
||||
? body.compilerPhases.find((item: any) => item?.name === name)
|
||||
: null;
|
||||
return typeof phase?.elapsedMs === "number" ? phase.elapsedMs : null;
|
||||
}
|
||||
|
||||
function graphBuildTimings(body: any) {
|
||||
const timings = body?.graphBuild?.timings || {};
|
||||
return {
|
||||
graphLoadMs: timings.graphLoadMs ?? null,
|
||||
stdlibMergeMs: timings.stdlibMergeMs ?? null,
|
||||
stdlibReferenceScanMs: timings.stdlibReferenceScanMs ?? null,
|
||||
stdlibCleanupMs: timings.stdlibCleanupMs ?? null,
|
||||
stdlibModuleLoadMs: timings.stdlibModuleLoadMs ?? null,
|
||||
stdlibNodeMergeMs: timings.stdlibNodeMergeMs ?? null,
|
||||
stdlibEdgeMergeMs: timings.stdlibEdgeMergeMs ?? null,
|
||||
stdlibFinalizeMs: timings.stdlibFinalizeMs ?? null,
|
||||
readinessCheckMs: timings.readinessCheckMs ?? null,
|
||||
mirCacheLoadMs: timings.mirCacheLoadMs ?? null,
|
||||
mirLowerMs: timings.mirLowerMs ?? null,
|
||||
mirCacheWriteMs: timings.mirCacheWriteMs ?? null,
|
||||
mirCacheReloadMs: timings.mirCacheReloadMs ?? null,
|
||||
mirLoadOrLowerMs: timings.mirLoadOrLowerMs ?? null,
|
||||
lowerPhaseMs: timings.lowerPhaseMs ?? phaseMs(body, "lower"),
|
||||
codegenMs: timings.codegenMs ?? phaseMs(body, "codegen"),
|
||||
objectMs: timings.objectMs ?? phaseMs(body, "object"),
|
||||
linkMs: timings.linkMs ?? phaseMs(body, "link"),
|
||||
};
|
||||
}
|
||||
|
||||
function graphBuildStdlibMergeFacts(body: any) {
|
||||
const merge = body?.graphBuild?.stdlibMerge || {};
|
||||
return {
|
||||
modulesMerged: merge.modulesMerged ?? null,
|
||||
nodesMerged: merge.nodesMerged ?? null,
|
||||
edgesMerged: merge.edgesMerged ?? null,
|
||||
cacheHit: merge.cacheHit ?? null,
|
||||
cacheStored: merge.cacheStored ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function graphCheckTimings(body: any) {
|
||||
const timings = body?.graphCompiler?.timings || {};
|
||||
const resolveMs = timings.resolveMs ?? phaseMs(body, "resolve");
|
||||
const checkMs = timings.checkMs ?? phaseMs(body, "check");
|
||||
const lowerMs = timings.lowerMs ?? phaseMs(body, "lower");
|
||||
return {
|
||||
graphLoadMs: timings.loadMs ?? null,
|
||||
resolveMs: resolveMs ?? null,
|
||||
checkMs: checkMs ?? null,
|
||||
readinessCheckMs: typeof resolveMs === "number" && typeof checkMs === "number" ? resolveMs + checkMs : null,
|
||||
readinessLowerMs: lowerMs ?? null,
|
||||
cacheMs: timings.cacheMs ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function cacheFacts(body: any) {
|
||||
const caches = Array.isArray(body?.compilerCaches) ? body.compilerCaches : [];
|
||||
const mapped = caches.find((cache: any) => cache?.name === "mappedFinalMir") || body?.graphBuild?.mappedFinalMir || null;
|
||||
return {
|
||||
hits: caches.filter((cache: any) => cache?.hit === true).length,
|
||||
misses: caches.filter((cache: any) => cache?.hit === false).length,
|
||||
mappedFinalMir: mapped ? {
|
||||
hit: mapped.hit ?? null,
|
||||
written: mapped.written ?? null,
|
||||
byteLength: mapped.byteLength ?? null,
|
||||
memoryMapped: mapped.memoryMapped ?? null,
|
||||
codegenImmediate: mapped.codegenImmediate ?? null,
|
||||
programReconstructed: mapped.programReconstructed ?? null,
|
||||
} : null,
|
||||
};
|
||||
}
|
||||
|
||||
function valueAtPath(value: any, path: string) {
|
||||
return path.split(".").reduce((current, part) => current?.[part], value);
|
||||
}
|
||||
|
||||
function evaluateBudgets(results: any[]) {
|
||||
const mode = budgetMode === "fail" || budgetMode === "warn" || budgetMode === "off" ? budgetMode : "warn";
|
||||
if (mode === "off") {
|
||||
return { schemaVersion: 1, mode, status: "off", ok: true, overCount: 0, skippedCount: 0, items: [] };
|
||||
}
|
||||
const items = performanceBudgets.map((budget) => {
|
||||
const fixture = results.find((item) => item.name === budget.fixture);
|
||||
const run = fixture?.[budget.pass]?.[budget.command];
|
||||
const actual = valueAtPath(run, budget.metric);
|
||||
if (!fixture) {
|
||||
return { ...budget, status: "skipped", actual: null, reason: "fixture not selected" };
|
||||
}
|
||||
if (typeof actual !== "number") {
|
||||
return { ...budget, status: "skipped", actual: null, reason: "metric missing" };
|
||||
}
|
||||
const overBy = Number(Math.max(0, actual - budget.max).toFixed(3));
|
||||
const unitSuffix = budget.unit === "ms" ? "Ms" : "Bytes";
|
||||
return {
|
||||
...budget,
|
||||
status: actual <= budget.max ? "ok" : "over",
|
||||
actual,
|
||||
max: budget.max,
|
||||
[`actual${unitSuffix}`]: actual,
|
||||
[`max${unitSuffix}`]: budget.max,
|
||||
[`overBy${unitSuffix}`]: overBy,
|
||||
ratio: Number((actual / budget.max).toFixed(3)),
|
||||
};
|
||||
});
|
||||
const overCount = items.filter((item) => item.status === "over").length;
|
||||
const skippedCount = items.filter((item) => item.status === "skipped").length;
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
mode,
|
||||
status: overCount === 0 ? "ok" : mode === "fail" ? "fail" : "warn",
|
||||
ok: overCount === 0,
|
||||
overCount,
|
||||
skippedCount,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
function writeBudgetMessages(budget: any) {
|
||||
if (!budget || budget.mode === "off") return;
|
||||
for (const item of budget.items || []) {
|
||||
if (item.status === "over") {
|
||||
process.stderr.write(
|
||||
`graph perf budget ${budget.status}: ${item.name} ${item.actual} ${item.unit} > ${item.max} ${item.unit} (${item.metric})\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function runJson(args: string[], env: Record<string, string | undefined>): Promise<CommandRun> {
|
||||
const started = performance.now();
|
||||
try {
|
||||
const result = await execFileAsync(zero, args, {
|
||||
env: { ...process.env, ...env },
|
||||
maxBuffer,
|
||||
});
|
||||
const wallMs = Number((performance.now() - started).toFixed(3));
|
||||
return { ok: true, command: [zero, ...args], wallMs, body: JSON.parse(result.stdout), stderr: result.stderr };
|
||||
} catch (error: any) {
|
||||
const wallMs = Number((performance.now() - started).toFixed(3));
|
||||
let body: any = null;
|
||||
try {
|
||||
body = JSON.parse(error.stdout?.toString() ?? "");
|
||||
} catch {
|
||||
body = null;
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
command: [zero, ...args],
|
||||
wallMs,
|
||||
body,
|
||||
stderr: error.stderr?.toString() ?? error.message ?? "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function buildCompiler() {
|
||||
if (process.env.ZERO_GRAPH_BUILD_PERF_SKIP_BUILD === "1") return null;
|
||||
const started = performance.now();
|
||||
await execFileAsync("make", ["-C", "native/zero-c"], { maxBuffer });
|
||||
return Number((performance.now() - started).toFixed(3));
|
||||
}
|
||||
|
||||
async function zeroVersion() {
|
||||
const result = await runJson(["--version", "--json"], {});
|
||||
if (!result.ok) throw new Error(`zero version failed: ${result.stderr}`);
|
||||
return result.body;
|
||||
}
|
||||
|
||||
function requireOk(run: CommandRun) {
|
||||
if (run.ok && run.body?.ok !== false) return;
|
||||
const diagnostic = run.body?.diagnostics?.[0];
|
||||
const message = diagnostic ? `${diagnostic.code}: ${diagnostic.message}` : run.stderr || "command failed";
|
||||
throw new Error(`${run.command.join(" ")} failed: ${message}`);
|
||||
}
|
||||
|
||||
function normalizeBuild(run: CommandRun) {
|
||||
requireOk(run);
|
||||
return {
|
||||
ok: true,
|
||||
wallMs: run.wallMs,
|
||||
compilerElapsedMs: run.body?.elapsedMs ?? null,
|
||||
graphHash: run.body?.graph?.graphHash ?? null,
|
||||
lowering: run.body?.graph?.lowering ?? null,
|
||||
artifactBytes: run.body?.artifactBytes ?? null,
|
||||
loweredIrBytes: run.body?.loweredIrBytes ?? null,
|
||||
timings: graphBuildTimings(run.body),
|
||||
stdlibMerge: graphBuildStdlibMergeFacts(run.body),
|
||||
caches: cacheFacts(run.body),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCheck(run: CommandRun) {
|
||||
requireOk(run);
|
||||
return {
|
||||
ok: true,
|
||||
wallMs: run.wallMs,
|
||||
graphHash: run.body?.graph?.graphHash ?? run.body?.graphHash ?? null,
|
||||
targetReady: run.body?.targetReadiness?.ok ?? null,
|
||||
timings: graphCheckTimings(run.body),
|
||||
caches: cacheFacts(run.body),
|
||||
};
|
||||
}
|
||||
|
||||
async function runFixture(fixture: Fixture) {
|
||||
const fixtureTarget = fixture.target || target;
|
||||
const checkTarget = fixture.checkTarget || process.env.ZERO_GRAPH_BUILD_PERF_CHECK_TARGET || "";
|
||||
const fixtureRoot = join(outRoot, fixture.name);
|
||||
const buildCache = join(fixtureRoot, "build-cache");
|
||||
const checkCache = join(fixtureRoot, "check-cache");
|
||||
await mkdir(fixtureRoot, { recursive: true });
|
||||
|
||||
async function runPass(pass: "cold" | "warm") {
|
||||
const artifactPath = join(fixtureRoot, `${fixture.name}-${pass}.o`);
|
||||
process.stderr.write(`graph perf ${fixture.name} ${pass} build\n`);
|
||||
const build = await runJson(["build", "--json", "--emit", "obj", "--target", fixtureTarget, fixture.input, "--out", artifactPath], {
|
||||
ZERO_CACHE_DIR: buildCache,
|
||||
});
|
||||
process.stderr.write(`graph perf ${fixture.name} ${pass} check\n`);
|
||||
const checkArgs = ["check", "--json"];
|
||||
if (checkTarget) checkArgs.push("--target", checkTarget);
|
||||
checkArgs.push(fixture.input);
|
||||
const check = await runJson(checkArgs, {
|
||||
ZERO_CACHE_DIR: checkCache,
|
||||
});
|
||||
return {
|
||||
build: normalizeBuild(build),
|
||||
check: normalizeCheck(check),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: fixture.name,
|
||||
input: fixture.input,
|
||||
target: fixtureTarget,
|
||||
checkTarget: checkTarget || "host-default",
|
||||
cold: await runPass("cold"),
|
||||
warm: await runPass("warm"),
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await rm(outRoot, { recursive: true, force: true });
|
||||
await mkdir(outRoot, { recursive: true });
|
||||
const nativeBuildMs = await buildCompiler();
|
||||
const version = await zeroVersion();
|
||||
const cases = selectedFixtures();
|
||||
if (cases.length === 0) throw new Error("ZERO_GRAPH_BUILD_PERF_CASES selected no fixtures");
|
||||
const started = performance.now();
|
||||
const results = [];
|
||||
for (const fixture of cases) results.push(await runFixture(fixture));
|
||||
const elapsedMs = Number((performance.now() - started).toFixed(3));
|
||||
const budgets = evaluateBudgets(results);
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
kind: "zero-graph-build-perf",
|
||||
generatedAt: new Date().toISOString(),
|
||||
zero: version,
|
||||
target,
|
||||
outDir: outRoot,
|
||||
nativeBuildMs,
|
||||
elapsedMs,
|
||||
budgets,
|
||||
cases: results,
|
||||
};
|
||||
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
||||
writeBudgetMessages(budgets);
|
||||
if (budgets.status === "fail") process.exit(1);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user