fix(server): collect large file observer benchmark batches

Startup inventories exceed the function argument limit when spread into push. Append each event individually in both benchmark collectors so large fixtures can reach their measurement summary.
This commit is contained in:
paseo-bot[bot]
2026-10-19 01:23:45 +02:00
parent 559c0200e0
commit e2b21428c0
2 changed files with 61 additions and 2 deletions
@@ -0,0 +1,59 @@
import { execFile } from "node:child_process";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { expect, test } from "vitest";
const execFileAsync = promisify(execFile);
// Linux delivers the existing inventory as one batch. Other backends have
// different startup semantics, so they do not exercise this regression.
test.skipIf(process.platform !== "linux").each(["healthy", "directory-churn"])(
"%s benchmark collects a startup batch larger than the argument limit",
async (mode) => {
const temporaryRoot = await mkdtemp(join(tmpdir(), "paseo-watch-benchmark-test-"));
try {
const { stdout } = await execFileAsync(
process.execPath,
[
// Lower the argument limit instead of creating hundreds of thousands
// of files. This still runs the real observer and benchmark CLI.
"--stack-size=256",
"--import",
"tsx",
fileURLToPath(new URL("./measure-file-observer.ts", import.meta.url)),
"--child",
],
{
timeout: 60_000,
env: {
...process.env,
TMPDIR: temporaryRoot,
PASEO_WATCH_BENCH_MODE: mode,
PASEO_WATCH_BENCH_ROOTS: "1",
PASEO_WATCH_BENCH_DIRS: "10",
PASEO_WATCH_BENCH_PREEXISTING_FILES: "40000",
PASEO_WATCH_BENCH_IGNORED_FILES: "1",
PASEO_WATCH_BENCH_EDITS: "1",
PASEO_WATCH_BENCH_SUSTAINED_EDITS: "1",
PASEO_WATCH_BENCH_SUSTAINED_INTERVAL_MS: "1",
PASEO_WATCH_BENCH_CHURN_DIRECTORIES: "10",
PASEO_WATCH_BENCH_CHURN_FILES_PER_DIRECTORY: "4000",
PASEO_WATCH_BENCH_CHURN_ROUNDS: "1",
PASEO_WATCH_BENCH_CHURN_DIRECTORIES_PER_ROUND: "2",
},
},
);
const measurement = JSON.parse(stdout);
expect(measurement.startupEvents).toBe(40_000);
expect(measurement.deliveredEvents).toBeGreaterThan(measurement.startupEvents);
expect(measurement.missedTrackedPaths).toBe(0);
expect(measurement.ignoredEvents).toBe(0);
} finally {
await rm(temporaryRoot, { recursive: true, force: true });
}
},
90_000,
);
@@ -199,8 +199,8 @@ async function measure(run: number): Promise<Measurement> {
root,
(nextError, events) => {
error ??= nextError;
delivered.push(...events);
for (const event of events) {
delivered.push(event);
if (!firstObservedAt.has(event.path))
firstObservedAt.set(event.path, performance.now());
}
@@ -456,7 +456,7 @@ async function measureDirectoryChurn(run: number): Promise<Measurement> {
const setupStarted = performance.now();
subscription = await observer.subscribe(root, (nextError, events) => {
error ??= nextError;
delivered.push(...events);
for (const event of events) delivered.push(event);
});
const setupMs = performance.now() - setupStarted;