Skip to content

[Bug][Performance] Dashboard reparses the entire usage.jsonl every 5 seconds and blocks management APIs #500

Description

@0disoft

Client or integration

OpenCodex dashboard

Area

Dashboard

Summary

The dashboard becomes progressively unresponsive as usage.jsonl grows because every GET /api/usage request synchronously reads, splits, parses, normalizes, and summarizes the entire append-only file on the Bun server thread.

Observed on OpenCodex 2.7.40:

  • usage.jsonl: 81,556,089 bytes (77.8 MiB), 206,596 valid JSONL rows in the final backup
  • dashboard request: GET /api/usage?range=30d
  • repeated pre-rotation latency: approximately 3.7–4.2 seconds per request
  • dashboard polling interval: 5 seconds
  • other small management endpoints: normally about 1–80 ms, but delayed while the synchronous usage read/parse occupied the server

The dashboard initially appeared offline or showed empty provider/settings state, then populated several seconds later. This was not caused by provider discovery: the slow endpoint was isolated to /api/usage, and the dashboard waits on it in the same Promise.all as health/providers/settings.

After archiving the old log and starting a new usage.jsonl, /api/usage?range=30d dropped to roughly 4–26 ms in repeated warm measurements (one later cold sample was 233 ms), and the other dashboard data appeared immediately.

Reproduction

  1. Run OpenCodex long enough for ~/.opencodex/usage.jsonl to grow to tens of megabytes / hundreds of thousands of rows.
  2. Open the dashboard.
  3. Measure the usage endpoint repeatedly:
foreach ($i in 1..3) {
  $sw = [Diagnostics.Stopwatch]::StartNew()
  Invoke-RestMethod 'http://127.0.0.1:10100/api/usage?range=30d' | Out-Null
  $sw.Stop()
  "{0:N0} ms" -f $sw.Elapsed.TotalMilliseconds
}
  1. While one usage request is running, request /healthz, /api/config, /api/providers, or /api/models and observe that otherwise-small management calls can be delayed.
  2. Archive/rotate usage.jsonl, restart with a new empty live file, and repeat. In this case latency fell from about 4 seconds to tens of milliseconds.

The manual archive was only a temporary workaround. It reset the live dashboard's 30-day history even though the old rows were preserved in archive files.

Code-level evidence

In tag v2.7.40:

  • src/usage/log.ts:273-289 implements readUsageEntries() as:
const lines = readFileSync(path, "utf-8").split(/\r?\n/);
for (const line of lines) {
  const parsed = JSON.parse(line);
  // normalize and append
}
  • src/server/management/logs-usage-routes.ts:121-126 calls summarizeUsage(readUsageEntries(), range, now, surface) for every GET /api/usage request.
  • gui/src/pages/Dashboard.tsx:322-329 includes /api/usage?range=30d in the same Promise.all as health, providers, and settings.
  • gui/src/pages/Dashboard.tsx:414-415 invokes that fetch immediately and then every 5 seconds.
  • The frontend has no in-flight guard, so polling can overlap once endpoint latency exceeds the interval.
  • src/usage/log.ts already contains readRecentUsageEntries(limit), which avoids loading the entire file for startup request-log hydration, but the dashboard usage route does not have an equivalent bounded/incremental path.

I did not find a user-configurable retention/rotation policy for usage.jsonl.

Expected behavior

The dashboard and management API should remain responsive as usage history grows. Please consider one or a combination of:

  1. Cache summaries and invalidate/update them only when the file grows.
  2. Maintain an incremental index or rolling aggregate instead of reparsing unchanged rows.
  3. Move expensive historical aggregation off the main server thread.
  4. Add bounded rotation/retention while preserving correct 7d/30d totals through aggregates or archived chunks.
  5. Prevent overlapping dashboard polls and decouple health/settings rendering from usage completion.
  6. Add a performance regression test with a large JSONL fixture (for example 75–100 MiB) that asserts unrelated management endpoints remain responsive.

The important contract is not a specific storage implementation: unchanged history should not be fully read and reparsed every five seconds, and a large usage log should not block unrelated management endpoints.

Version

@bitkyc08/opencodex 2.7.40

Operating system

Windows NT build 10.0.26200.8875 (x64)

Provider and model

Not provider-specific. The file contained usage from multiple routes/models.

Logs or error output

Before manual rotation:
  usage.jsonl: 77.8 MiB, 206,596 rows
  /api/usage?range=30d: approximately 3.7–4.2 s

After manual rotation:
  /api/usage?range=30d: typically 4–26 ms
  /api/config: approximately 7–16 ms
  /api/providers: approximately 11–16 ms
  /api/models: approximately 6–15 ms

Redacted configuration

Not configuration-specific.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions