Skip to content

Dashboard: /api/costs/stream blocks the event loop ~1.6 s per poll with a sync scan of events.jsonl #4544

Description

@eltmon

Found while profiling the dashboard event-loop stall for PAN-4543.

What is still blocking

GET /api/costs/stream (src/dashboard/server/routes/costs.ts, search readEvents({ startDate: since, limit }) : tailEvents(limit)) answers with a synchronous full scan of ~/.overdeck/costs/events.jsonl through scanEventLinesSync (src/lib/costs/events.ts):

  • tailEvents(limit) walks every line of the log to keep the last N.
  • readEvents({ startDate, limit }) walks every line older than since before it reaches the window.

On this machine the log is 386 MB. One synchronous scan measured ~1.6 s of blocked event loop (readEvents timed out of process, 2026-10-04). The Pipeline view polls this route through useCostStream (src/dashboard/frontend/src/hooks/useCostStream.ts, refetchIntervalInBackground: true), so every poll freezes every HTTP and WebSocket request on the dashboard for ~1.6 s.

What PAN-4543 already fixed

GET /api/costs/summary (God View polls it every 30 s, project overview every 60 s) did three synchronous scans per request, ~4.8 s blocked. That matched the 4.05 s / 4.66 s /api/health stalls that failed DoD row 8 on 6 of 22 close-outs. PAN-4543 added readEventsSince() (async chunked reader, max event-loop block measured at 10 ms) and the summary route now does one async scan.

Proposed fix

Give the stream route an async read path: an async tail (read backwards from the end of the file in chunks, or reuse readEventsSince for the since case and an async tail for the no-since case). Then audit the other request-reachable callers of scanEventLinesSync / readEvents / forEachCostEvent in the dashboard server. Keep the synchronous variants only for documented CLI callers (src/lib/costs/retention.ts), per docs/EFFECT-BRIDGING.md.

Acceptance

  • /api/costs/stream never blocks the event loop for more than ~50 ms on a 400 MB log (measure with monitorEventLoopDelay).
  • Unit test with a multi-chunk log proving the read yields the event loop (see tests/unit/lib/costs/read-events-since.test.ts from PAN-4543 for the pattern).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions