From e2b21428c033a65e486582637ff9d88b6c58086e Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 01:23:45 +0200 Subject: [PATCH] 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. --- .../scripts/measure-file-observer.test.ts | 59 +++++++++++++++++++ .../server/scripts/measure-file-observer.ts | 4 +- 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 packages/server/scripts/measure-file-observer.test.ts diff --git a/packages/server/scripts/measure-file-observer.test.ts b/packages/server/scripts/measure-file-observer.test.ts new file mode 100644 index 0000000000..345da462bb --- /dev/null +++ b/packages/server/scripts/measure-file-observer.test.ts @@ -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, +); diff --git a/packages/server/scripts/measure-file-observer.ts b/packages/server/scripts/measure-file-observer.ts index 49a54faf4d..2e37f9a414 100644 --- a/packages/server/scripts/measure-file-observer.ts +++ b/packages/server/scripts/measure-file-observer.ts @@ -199,8 +199,8 @@ async function measure(run: number): Promise { 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 { 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;