Commit 9ba9d45
authored
Make token usage scans cached, incremental, and memoized (#63)
* Cache all usage scanners and memoize assembled scans
The token usage scan re-parsed every source from scratch on each query:
codex (multi-GB of session JSONL) had no cache at all and did a full
serde_json::Value parse of every line, cursor re-read its multi-GB
state.vscdb each scan (and then failed on NULL values, so it never
reported anything), and opencode/pi/copilot were also uncached. Only
claude was cached, via one SQLite point query per file (11k+ queries
per scan on a large corpus).
Borrowing the working architecture from ccusage and CodexBar:
- Generalize the per-file cache to every source, keyed by
(source, path, parser_version, size, mtime), loaded with one batched
query per source and decoded in parallel; rows for vanished files are
pruned.
- Prefilter JSONL lines with memmem needles before any JSON parse and
stream lines as bytes into typed serde structs (claude) or Value
(codex survivors only); parse changed files in parallel.
- Treat cursor/opencode databases as volatile: reuse cached rows for
60s even when metadata changed, since a running editor rewrites them
continuously.
- Memoize the assembled (pre-filter) event set in-process with an
opt-in TTL; the TUI reuses one assembly across keystroke-driven
queries, which only differ in post-assembly filters.
- Fix the cursor scanner aborting on NULL cursorDiskKV values.
- Dedupe cursor/copilot/opencode copies in a reconcile pass now that
per-file results are cached independently.
On a 26GB corpus (742k events) the cold scan takes ~32s once, warm
full scans ~4s, and memoized TUI refreshes are effectively free.
* Store usage cache blobs as postcard instead of JSON
Decoding cached events dominated warm scans. Postcard's varint encoding
suits the token-count-heavy CachedUsageEvent shape: per-source warm
scans drop 2.5-3.5x (claude 2.2s -> 0.9s, codex 3.8s -> 1.1s on a 742k
event corpus) and the cache file shrinks ~2.8x. Parser versions double
as blob-format versions, so existing JSON rows re-parse once; the blob
column rename drops pre-rename tables on open.
* Limit OpenCode volatile cache reuse to databases
OpenCode message JSON files are rewritten in place while a response
streams, so the source-wide 60s volatile window could serve a stale or
empty cached blob for a just-changed message. Make the volatile window
a per-path decision: opencode applies it to its databases only, cursor
keeps it for all its databases, and every other source always re-parses
on metadata change.1 parent 8cf6340 commit 9ba9d45
5 files changed
Lines changed: 851 additions & 304 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1436 | 1436 | | |
1437 | 1437 | | |
1438 | 1438 | | |
| 1439 | + | |
1439 | 1440 | | |
1440 | 1441 | | |
1441 | 1442 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3090 | 3090 | | |
3091 | 3091 | | |
3092 | 3092 | | |
| 3093 | + | |
| 3094 | + | |
| 3095 | + | |
3093 | 3096 | | |
3094 | 3097 | | |
3095 | 3098 | | |
| |||
0 commit comments