Skip to content

Benchmarks

This page started in February 2026, when llm-usage and ccusage were both TypeScript programs running on Node.js. ccusage 20.0.0 (May 2026) replaced its TypeScript code with a Rust binary. Its npm package is now a small Node launcher that starts the native binary for your platform. The direct comparison below is therefore Node against native code, with ccusage’s launcher included because its package executable is the launcher. The npx scenario adds npm’s package resolution on top.

All numbers below were measured on 2026-10-17 with 8 timed runs per cell, using ccusage 20.0.35 and a development build of llm-usage 0.12.0 (unreleased). ccusage is ahead on Codex. llm-usage is ahead on Claude Code and on the warm monthly terminal check.

These cells invoke the built llm-usage entry and ccusage’s installed package executable directly. They include process startup and captured JSON output, but exclude shell package-manager launchers such as npx.

Tool Application state Median (s) Mean (s) Min (s) Max (s)
ccusage codex daily --no-offline --timezone UTC --json fresh 0.153 0.153 0.151 0.155
ccusage codex daily --offline --timezone UTC --json warmed 0.056 0.056 0.055 0.058
llm-usage daily --all --source codex --timezone UTC --json fresh 0.303 0.304 0.298 0.313
llm-usage daily --all --source codex --timezone UTC --json --pricing-offline warmed 0.089 0.090 0.087 0.093

From median runtime, ccusage was 1.99x faster in fresh application state and 1.58x faster in warmed application state.

Tool Application state Median (s) Mean (s) Min (s) Max (s)
ccusage claude daily --no-offline --timezone UTC --json fresh 0.998 1.031 0.969 1.251
ccusage claude daily --offline --timezone UTC --json warmed 0.416 0.416 0.408 0.424
llm-usage daily --all --source claude --timezone UTC --json fresh 0.961 0.959 0.945 0.967
llm-usage daily --all --source claude --timezone UTC --json --pricing-offline warmed 0.306 0.308 0.301 0.320

From median runtime, the two were close in fresh application state (llm-usage 1.04x faster) and 1.36x faster in warmed application state.

A second full run right after this one gave medians within 2% of every cell above.

These are runtime comparisons of similar report commands, not byte-for-byte output-equivalence claims. The tools use different output schemas and normalization rules. On this snapshot both tools reported the same number of days, and their grand totals for tokens and cost agreed within 0.05% for both sources.

The usual shell commands do not have equivalent launch paths: llm-usage is already installed, while npx ccusage@<version> starts npm, resolves the package, and then runs the ccusage launcher, which starts the native binary. This monthly terminal scenario uses the same stable Codex snapshot and the pinned ccusage version:

Command Launch path Median (s) Mean (s) Min (s) Max (s)
llm-usage monthly --per-model-columns --source codex installed command 0.089 0.089 0.085 0.092
ccusage codex monthly package executable (launcher) 0.126 0.126 0.121 0.133
npx --yes ccusage@20.0.35 codex monthly npm + launcher + native binary 0.559 0.560 0.533 0.588

From median runtime, the installed llm-usage command was 1.41x faster than ccusage’s package executable and 6.29x faster than the npx command. npx added about 0.43 s to ccusage.

The llm-usage row runs dist/bin.js, the entry the installed command uses, with its default event store, so after the warm-up it reads parsed events from the store. ccusage has no equivalent store and reads the logs on every run. Both report the full history (five months on this snapshot).

Each command received one untimed warm-up, then 8 timed runs in rotating order, so no command always ran first. Output was discarded through non-TTY pipes, and the system timezone plus default online pricing and update behavior were left in place. ccusage 20.0.35 was already in npm’s package cache before the timed npx runs.

  • Fresh application state: a new XDG_CACHE_HOME and XDG_CONFIG_HOME for every timed run, live pricing, and LLM_USAGE_EVENT_STORE=0 for llm-usage. This is not disk-cold: both corpora were read during setup, and the OS filesystem page cache was not flushed.
  • Warmed application state: one live setup run populates each tool’s pricing cache and llm-usage’s event store. Timed runs then use offline pricing (--offline or --pricing-offline) and reuse that application state.
  • ccusage does not expose an event ledger equivalent to llm-usage’s SQLite store; “warmed” does not imply that both tools cache parsed source events in the same way.
Spec Value
OS Linux 7.2.9-arch1-1 (x64)
CPU AMD Ryzen 9 9950X3D (32 logical CPUs)
Memory 62.2 GiB RAM
Node.js v24.21.0
pnpm 12.9.1
ccusage 20.0.35 (Node launcher + native linux-x64 binary)
llm-usage 0.12.0 (unreleased), development build

The machine was otherwise idle: the load average was 0.2 before the first run.

Corpus Files Size on disk
codex 157 JSONL session files ≈ 279 MiB
claude 1,567 JSONL files (projects + subagents) ≈ 4.04 GiB

The source trees were copied to a read-only snapshot immediately before the run, so active sessions could not grow between samples. This is a different corpus and machine from the July 2026 baseline (1,168 Codex files of ≈ 1.77 GiB, 271 Claude Code files of ≈ 178.8 MiB), so its runtimes are not comparable with these.

  • 8 timed runs per cell. The four cells rotate execution order each run, so every cell occupies every position twice.
  • Both tools use UTC, isolated application cache/config directories, and the same stable source snapshot.
  • Inherited LLM_USAGE_* overrides are removed. The warmed llm-usage event store is explicitly enabled and placed inside the temporary benchmark root; fresh-state runs explicitly disable it.
  • Fresh-state cells use live pricing. Warmed-state cells use pricing populated by one live setup run and then run offline.
  • The OS filesystem page cache is not flushed. No true disk-cold claim is made.
  • Direct-process cells exclude npm/npx/pnpm launcher overhead. ccusage’s own Node launcher is part of its package executable, so its cells include it.
  • llm-usage gets LLM_USAGE_SKIP_UPDATE_CHECK=1 in the direct-process comparison. ccusage exposes no equivalent update-check switch.

On the July 2026 Codex corpus (1,168 files, ≈ 1.77 GiB), a fresh run took 8.7 s before the parse-pipeline work and 1.527 s after it. The mechanisms, in pipeline order:

  1. Byte prefilters and a buffer-based JSONL reader (packages/core/src/utils/read-jsonl-objects.ts) skip irrelevant lines before any JSON parsing.
  2. A per-timezone local-date window cache for period bucketing (src/utils/time-buckets.ts) avoids repeated date math per event.
  3. An event-store warm read path serves already-parsed events.
  4. A worker-thread parse pool (src/cli/parse-worker-pool.ts) parses large JSONL files in parallel.

scripts/benchmark-baseline.mjs times the built CLI over a generated pi and codex corpus (200,000 lines per source over 4 files, 399,984 events), with a temp HOME and the store, pricing and update check pinned. It measures what is cheap to repeat: a cold run with the store off, a warm daily, a statusline tick, a growing JSONL file, and a statusline tick where most files are older than its 53-week window (statusline-aged).

0.11.0 and 0.12.0 (unreleased) were compared with it on 2026-10-18 on one machine (AMD Ryzen 9 9950X3D, 32 logical CPUs, 62.2 GiB RAM, Linux 7.2.9, Node.js v24.21.0, load average 1.2–1.6). Two serial rounds alternated the versions (0.11.0 then 0.12.0, then 0.12.0 then 0.11.0), with 5 timed runs per scenario per round. Each cell gives the two rounds’ medians, and the change compares their means:

Scenario 0.11.0 (s) 0.12.0 (unreleased) (s) Change
cold 1.206 / 1.212 1.239 / 1.245 +2.7%
warm 0.688 / 0.699 0.552 / 0.569 −19.2%
statusline 0.806 / 0.861 0.520 / 0.525 −37.3%
active-jsonl 1.034 / 1.048 0.986 / 0.987 −5.2%
statusline-aged 0.280 / 0.283 0.215 / 0.218 −23.1%

The store-off cold run is about 3% slower in 0.12.0, in both rounds. Peak RSS was about the same on cold (417–435 MB against 417–447 MB) and lower in 0.12.0 on warm (363–368 MB against 432–435 MB) and statusline (331–355 MB against 430–434 MB). No source in this corpus reaches the parse-worker threshold, so the worker-pool change behind the real-log cold gain below does not show here. These are synthetic files with dense usage lines, so they show relative change between builds, not what real logs cost.

The synthetic corpus above has dense usage lines. Real logs are mostly conversation text, so 0.11.0 and 0.12.0 (unreleased) were also compared on the maintainer’s own logs on 2026-10-18, on the same machine (Node.js v24.21.0, load average 2.6–3.3 during the runs):

  • Each run uses a temp HOME that links to the log directories read-only, plus temp cache, data and config directories and TZ=Europe/Paris.
  • “Cold” is statusline on an empty event store. “Warm” runs reuse the store that cold run filled.
  • The two versions alternate over 3 rounds, each round with a fresh store. Each cell gives the range over the rounds.
Run 0.11.0 0.12.0 (unreleased)
Cold statusline 5.75–5.83 s 3.08–3.36 s
Cold peak RSS 616–701 MB 724–787 MB
Warm statusline 533–728 ms 448–497 ms
Warm summary --json 526–607 ms 465–533 ms
Warm daily --json 447–460 ms 421–522 ms
Warm tools --json not in 0.11.0 0.72–0.79 s
Event store size 75 MB 66 MB

A warm daily --json takes about the same time in both versions. The 0.12.0 store is smaller although it also holds tool calls and messages.

Most of the cold gain comes from parsing Claude Code and pi files on the worker pool. The cost is memory, because a cold run keeps several parse workers busy at once. Cold runs happen only on the first run and after a parser update; a lower parseWorkers lowers the peak. Warm runs read unchanged files from the event store, parse only files that changed, and read only the appended lines of a claude, pi or qwen file that grew.

Build the current checkout and install the pinned ccusage version into a scratch directory, so npx is not part of the timing:

Terminal window
pnpm run build
npm install --prefix "$HOME/.cache/ccusage-bench" ccusage@20.0.35
CCUSAGE_BIN="$HOME/.cache/ccusage-bench/node_modules/.bin/ccusage"
"$CCUSAGE_BIN" --version
node scripts/perf-production-benchmark.mjs --runs 8 --scenario all --ccusage-bin "$CCUSAGE_BIN"

Stop Codex and Claude while the benchmark runs, or point HOME at a stable snapshot containing .codex/sessions and .claude/projects. Replace the pinned ccusage version only when intentionally creating a new baseline. The script also accepts --json-output <path> and --markdown-output <path> to export artifacts.

The script does not run the npx scenario. To repeat it, time the three commands from its table with the same HOME, one untimed warm-up each first.

Results are machine-, dataset-, command-, and launcher-specific. Re-run the exact scenario you care about before drawing a broader conclusion.