Skip to content

fix(usage): stop per-append 64MiB usage reparses - #1620

Merged
lidge-jun merged 4 commits into
devfrom
codex/260813-usage-parse-retention
Aug 13, 2026
Merged

fix(usage): stop per-append 64MiB usage reparses#1620
lidge-jun merged 4 commits into
devfrom
codex/260813-usage-parse-retention

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Summary\n\n- Stop the local proxy memory blow-up caused by repeated 64 MiB parses of a growing .\n- Cache compact usage summaries for 60s by ledger identity (path/dev/ino/birthtime), not size/mtime, so appends no longer invalidate the cache.\n- Share one in-flight 64 MiB window parse across dashboard tabs and prime the API-key rollup from that same snapshot.\n- Remove the 60s dashboard/Codex usage polls and subscribe provider surfaces to one shared 30-day resource.\n\n## Verification\n\n- bun test v1.3.14 (0d9b296a)

🚀 opencodex proxy running on http://localhost:55536
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55538
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55540
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55542
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55544
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55546
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55548
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55550
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55552
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55554
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55557
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55559
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55561
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55563
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55565
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55567
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55569
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55571
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55573
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55575
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55577
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55579
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55581
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55583
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55640
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55642
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55644
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55646
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard
🚀 opencodex proxy running on http://localhost:55648
POST /v1/responses → provider translation
POST /v1/chat/completions → OpenAI-compatible clients
GET /healthz → health check
GET /api/* → management API
GET / → GUI dashboard — 100 pass, 0 fail.\n- bun test v1.3.14 (0d9b296a) — 13 pass, 0 fail.\n- — pass.\n- — pass.\n- Independent leak review: Opus PASS; Sol FAIL on sequential API-key reread, folded in .\n\n## Checklist\n\n- [x] Scope stays focused and avoids unrelated cleanup.\n- [x] Docs or release notes were updated when needed.\n- [x] Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.\n

Summary by CodeRabbit

  • New Features
    • Usage summaries now use a shared 30-day data resource across dashboards, provider views, and Codex usage.
    • Provider usage rankings are derived consistently from the shared usage response.
  • Bug Fixes
    • Reduced duplicate usage-log reads and unnecessary polling.
    • Usage data now remains available while refreshed and updates correctly after new activity.
    • Cache invalidation better handles changed, shortened, or stale usage data.
  • Documentation
    • Added plans covering usage retention, validation, review, and release activities.

Incident: local ocx RSS 9.5 GiB from repeated 64 MiB /api/usage
parses on a 243 MB usage.jsonl. Docs-only WP0: one owner flight,
compact artifacts, 60s generation freshness, GUI stampede stop,
WS deferred to PR 1608, release via clone of origin URL.
Keep the 64 MiB window parse, but key the in-flight read and compact
summary cache on ledger identity rather than size/mtime. Appends within
60s reuse the cached summaries; replacements that shrink the file still
supersede. Dashboard and provider surfaces share one 30d resource and
no longer poll /api/usage every minute.
A sequential /api/keys read after /api/usage no longer starts a second
64 MiB parse. The usage owner installs the compact attribution map from
the same entries it just summarized.
Move the 30-day usage cache key into a helper so dashboard, providers,
and the workspace subscribe to the same store. Defer workspace usage
state updates so the GUI lint gate stays green.
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Aug 13, 2026
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • UI screenshot required.

What to do

  • Add a screenshot of the UI change to the PR description.

This pull request was already a draft. Its draft status will be preserved after every issue above is resolved.

@github-actions
github-actions Bot marked this pull request as draft August 13, 2026 15:06
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 454afd12-9d9c-417c-a6b3-4c2babe15ae0

📥 Commits

Reviewing files that changed from the base of the PR and between 0325a5a and 678974a.

📒 Files selected for processing (20)
  • devlog/_plan/260813_usage_parse_retention/000_plan.md
  • devlog/_plan/260813_usage_parse_retention/001_ws_queue_decision.md
  • devlog/_plan/260813_usage_parse_retention/010_append_tolerant_usage_snapshot.md
  • devlog/_plan/260813_usage_parse_retention/020_gui_usage_stampede.md
  • devlog/_plan/260813_usage_parse_retention/030_ws_queue_if_unbounded.md
  • devlog/_plan/260813_usage_parse_retention/040_opus_sol_leak_review.md
  • devlog/_plan/260813_usage_parse_retention/050_push_preview_main_release.md
  • gui/src/components/AddProviderModal.tsx
  • gui/src/components/provider-workspace/ProviderWorkspaceShell.tsx
  • gui/src/hooks/useCodexAccountPool.ts
  • gui/src/pages/Providers.tsx
  • gui/src/pages/use-dashboard-data.ts
  • gui/src/usage-summary-resource.ts
  • gui/tests/dashboard-contracts.test.ts
  • src/server/management/api-key-usage.ts
  • src/server/management/logs-usage-routes.ts
  • src/server/management/usage-summary-cache.ts
  • src/usage/log.ts
  • tests/api-usage.test.ts
  • tests/settings-stream-mode.test.ts

📝 Walkthrough

Walkthrough

The change adds usage-retention plans and implements stable usage-log read coalescing, metadata-aware server caches, snapshot-based API-key rollups, shared GUI resource keys, reduced polling, and updated cache and dashboard contract tests.

Changes

Usage retention

Layer / File(s) Summary
Incident scope and release plan
devlog/_plan/260813_usage_parse_retention/*
The plans define retention objectives, excluded WebSocket work, review gates, validation, and release procedures.
Usage-log identity and read coalescing
src/usage/log.ts
Management reads share flights by stable ledger identity and read window. File replacement, shrinkage, key changes, and stale flights terminate existing reads.
Cache metadata and API-key rollups
src/server/management/usage-summary-cache.ts, src/server/management/api-key-usage.ts
Caches track identity, read limits, freshness, truncation, and observed size. API-key rollups can populate directly from a loaded snapshot.
Usage route snapshot integration
src/server/management/logs-usage-routes.ts, tests/api-usage.test.ts, tests/settings-stream-mode.test.ts
The route validates cache metadata, populates range and surface summaries from one snapshot, and caches API-key usage. Tests cover append freshness and retained entries.
Shared GUI usage resources
gui/src/usage-summary-resource.ts, gui/src/components/*, gui/src/hooks/*, gui/src/pages/*, gui/tests/dashboard-contracts.test.ts
GUI callers use shared all-surface or Codex-specific 30-day resource keys. Dedicated 60-second polling is removed, and keyed resource data drives provider views.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UsageRoute
  participant UsageLogReader
  participant UsageSummaryCache
  participant ApiKeyUsageCache
  UsageRoute->>UsageSummaryCache: Validate cache metadata
  UsageRoute->>UsageLogReader: Read one usage snapshot
  UsageLogReader-->>UsageRoute: Return entries and revision metadata
  UsageRoute->>UsageSummaryCache: Populate summaries for ranges and surfaces
  UsageRoute->>ApiKeyUsageCache: Cache API-key usage from the snapshot
  UsageRoute-->>UsageRoute: Return the usage response
Loading

Possibly related PRs

Suggested reviewers: wibias, ingwannu

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/260813-usage-parse-retention

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lidge-jun
lidge-jun merged commit 678974a into dev Aug 13, 2026
21 of 23 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 678974a445

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +264 to +267
for (const nextRange of ranges) {
for (const nextSurface of surfaces) {
const nextSummary = nextRange === range && nextSurface === surface ? summary : {
...summarizeUsage(snapshot.entries, nextRange, now, nextSurface),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Aggregate all usage buckets in one pass

On a cold /api/usage request with a large ledger, handleLogsUsageRoutes computes the requested summary and then invokes summarizeUsage for the other eleven buckets; each invocation filters the entire snapshot and performs several additional passes for totals, models, providers, accounts, and costs. With the supported 500,000-entry/64 MiB window this substantially increases transient allocation and CPU, and concurrent requests that joined the same read each repeat this whole loop because the cache is not rechecked after the await. Build and install the twelve summaries through one shared single-pass generation, or avoid eagerly computing unrequested buckets.

Useful? React with 👍 / 👎.

Comment on lines 224 to 230
if (cached
&& cached.revisionKey === observedRevisionKey
&& cached.identityKey === identityKey
&& cached.maxReadBytes === effectiveReadLimit
&& cached.overlayVersion === userCostOverlayVersion()
&& now < cached.expiresAt) {
&& now < cached.freshUntil
&& observedSize >= cached.lastSeenSize) {
return jsonResponse(refreshedUsageSummary(cached.summary, range, now));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect non-shrinking in-place ledger rewrites

When usage.jsonl is rewritten in place within the 60-second freshness window to different content whose size is equal to or greater than lastSeenSize, the inode/birthtime identity remains unchanged and this condition serves the old summary. This is a regression from the prior revision key, which included mtime/ctime, and it affects the explicitly supported hand-edited ledger case; distinguish verified appends from rewrites rather than treating every nondecreasing size as an append, and add an equal/larger in-place rewrite regression test.

AGENTS.md reference: src/AGENTS.md:L24-L26

Useful? React with 👍 / 👎.

@Wibias
Wibias deleted the codex/260813-usage-parse-retention branch August 13, 2026 22:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant