test(server): diagnose recurring config responsiveness failure

This commit is contained in:
paseo-bot[bot]
2026-10-30 12:08:00 +00:00
parent fbe24fcdb6
commit 14329d1b98
2 changed files with 42 additions and 16 deletions
@@ -133,22 +133,25 @@ describe("paseo config file substrate", () => {
expect(blockingMs).toBeLessThan(20);
});
it("keeps main-thread work below 20ms while admitting large configs", async () => {
const config = {
scripts: Object.fromEntries(
Array.from({ length: 7_000 }, (_, i) => [`script${i}`, { command: "echo hi" }]),
),
worktree: { setup: "npm ci" },
};
await writeFile(join(tempDir, "paseo.json"), JSON.stringify(config));
const { result, blockingMs, filesystemRequests, yields, workerMessages } =
await measureConfigRead(tempDir);
expect(result).toEqual({ ok: true, config, revision: await statPaseoConfigPath(tempDir) });
expect(blockingMs).toBeLessThan(20);
expect(filesystemRequests).toBeGreaterThan(0);
expect(yields).toBeGreaterThan(0);
expect(workerMessages).toBe(0);
});
it.each(Array.from({ length: 30 }, (_, i) => i))(
"keeps main-thread work below 20ms while admitting large configs (%i)",
async () => {
const config = {
scripts: Object.fromEntries(
Array.from({ length: 7_000 }, (_, i) => [`script${i}`, { command: "echo hi" }]),
),
worktree: { setup: "npm ci" },
};
await writeFile(join(tempDir, "paseo.json"), JSON.stringify(config));
const { result, blockingMs, filesystemRequests, yields, workerMessages } =
await measureConfigRead(tempDir);
expect(result).toEqual({ ok: true, config, revision: await statPaseoConfigPath(tempDir) });
expect(blockingMs).toBeLessThan(20);
expect(filesystemRequests).toBeGreaterThan(0);
expect(yields).toBeGreaterThan(0);
expect(workerMessages).toBe(0);
},
);
it("rejects a 32MiB config with main-thread work below 20ms", async () => {
await writeFile(
@@ -1,4 +1,5 @@
import { createHook, executionAsyncResource } from "node:async_hooks";
import { Session } from "node:inspector/promises";
import { MessagePort } from "node:worker_threads";
import assert from "node:assert/strict";
import { setImmediate as nextTurn } from "node:timers/promises";
@@ -17,8 +18,30 @@ const activity = createHook({
if (executionAsyncResource() instanceof MessagePort) workerMessages++;
},
}).enable();
const profiler = new Session();
profiler.connect();
await profiler.post("Profiler.enable");
await profiler.post("Profiler.setSamplingInterval", { interval: 100 });
await profiler.post("Profiler.start");
const measured = await measureMainThreadWork("project config main-thread work", () =>
readPaseoConfigForEdit(directory),
);
activity.disable();
const { profile } = await profiler.post("Profiler.stop");
profiler.disconnect();
if (measured.blockingMs >= 20) {
const nodes = new Map(profile.nodes.map((node) => [node.id, node]));
const samples = new Map<number, number>();
for (const [index, id] of (profile.samples ?? []).entries()) {
samples.set(id, (samples.get(id) ?? 0) + (profile.timeDeltas?.[index] ?? 0));
}
const hottest = [...samples]
.sort((a, b) => b[1] - a[1])
.slice(0, 20)
.map(([id, micros]) => {
const frame = nodes.get(id)!.callFrame;
return { function: frame.functionName, line: frame.lineNumber, milliseconds: micros / 1000 };
});
process.stderr.write(`Config CPU diagnostic: ${JSON.stringify(hottest)}\n`);
}
process.stdout.write(JSON.stringify({ ...measured, workerMessages }));