perf(app): coalesce streamed assistant chunks per flush#2167
perf(app): coalesce streamed assistant chunks per flush#2167BetterAndBetterII wants to merge 2 commits into
Conversation
|
| Filename | Overview |
|---|---|
| packages/app/src/timeline/session-stream-reducers.ts | Core coalescing logic added: four new private helpers plus a rewritten processAgentStreamEvents loop. Logic is correct — first chunk takes the existing path, continuations are batched; gap/epoch/provider boundaries all fall back to per-event processing. |
| packages/app/src/timeline/session-stream-reducers.test.ts | Four new tests added covering: first-chunk individual processing, cross-provider boundary, sequence-gap fallback, and cross-epoch boundary. Tests assert through the module's public interface. |
| packages/app/scripts/benchmark-agent-stream-reducer.ts | Standalone benchmark implementation. Validation logic (including "x".repeat(messageBytes)) runs inside the timed runWorkload function, slightly contaminating the timing measurement for large workloads. |
| scripts/benchmarks/run.ts | Root benchmark orchestrator: arg parsing, task selection, spawnSync per task, git metadata, and JSON envelope. stdout is written unconditionally even when --output is specified. |
| scripts/benchmarks/types.ts | Typed contract and hand-rolled parser for BenchmarkTaskResult and BenchmarkRunResult. Validation is thorough: finite-number checks on metric values and samples. |
| scripts/benchmarks/stats.ts | Percentile helper: sorts a copy of samples, applies ceil-based index formula. Correct for the 7-sample MEASURED_RUNS workload. |
| scripts/benchmarks/tasks.ts | Registry of benchmark task definitions; one task registered. Platform-aware npx command for Windows compatibility. |
| scripts/benchmarks/types.test.ts | Tests parseBenchmarkTaskResult: happy path and malformed metric values. Also tests summarizeSamples for non-mutation and correct p50/p95. |
Comments Outside Diff (1)
-
scripts/benchmarks/run.ts, line 663-668 (link)stdout write is unconditional when
--outputis specifiedWhen
--output /tmp/out.jsonis passed, the runner writes the full JSON to the file and then immediately writes the same JSON to stdout. Users who just want the file (e.g. in CI, storing a baseline) will have several KiB of JSON printed to the terminal as well. If this is intentional for piping (npm run benchmark -- --output ... | jq), a comment explaining that would help; if not, gatingprocess.stdout.writeon!command.outputPathwould make the behaviour match the documented flag semantics.
Reviews (1): Last reviewed commit: "build: add benchmark task registry" | Re-trigger Greptile
| duration: { | ||
| unit: "ms", | ||
| values: { | ||
| p50: result.p50Ms, | ||
| p95: result.p95Ms, | ||
| }, | ||
| samples: result.samplesMs, | ||
| }, | ||
| }, | ||
| })), | ||
| } satisfies BenchmarkTaskResult; | ||
| const serialized = `${JSON.stringify(output, null, 2)}\n`; | ||
| const outputPath = process.env.PASEO_BENCHMARK_OUTPUT; | ||
| if (outputPath) { | ||
| writeFileSync(outputPath, serialized); |
There was a problem hiding this comment.
Correctness validation is inside the timed region
runWorkload both runs the workload and validates the result, so the measured samples include the cost of [...tail, ...head].filter(...), the "x".repeat(messageBytes) allocation, and the string-equality check. For the 1 MiB case "x".repeat(1048576) creates a 1 MiB string on every measured run. The overhead is modest (~0.1–0.5 ms against ~164 ms p50), but it makes the absolute numbers slightly misleading and means any future increase in validation cost silently inflates the benchmark. Moving the validation call outside the timed loop (run it once after warmup, before the timing loop) keeps the measurement focused purely on processAgentStreamEvents.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
Benchmark runner
The versioned JSON envelope records Git commit/dirty state, Node runtime identity, task and case IDs, dimensions, named metrics, and raw samples. New benchmarks implement
BenchmarkTaskResultand register a command inscripts/benchmarks/tasks.ts.Agent stream benchmark
Workload: 512-byte assistant chunks, 8 chunks per reducer flush, 2 warmups, 7 measured runs. The task validates final text, item count, and timeline cursor on every run.
Validation
npx vitest run scripts/benchmarks/types.test.ts packages/app/src/timeline/session-stream-reducers.test.ts --bail=1(70 tests)npm run typechecknpm run lintnpm run formatThe existing browser terminal stress fixture is not used for this result: its coalesced mock mode combines assistant updates server-side into one WebSocket message, so it does not reproduce multiple client reducer events in one flush.