Benchmarks
This page publishes absolute runtimes for llm-usage and ccusage on stable snapshots of real local data. The result depends on what is timed: ccusage wins the current direct-process comparison, while putting npx in front of ccusage adds enough launcher overhead to reverse the result at the shell. All numbers below were measured on 2026-07-15 with 8 timed runs per cell.
Direct-process results
Section titled “Direct-process results”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.
codex — daily --json (8 runs each)
Section titled “codex — daily --json (8 runs each)”| Tool | Application state | Median (s) | Mean (s) | Min (s) | Max (s) |
|---|---|---|---|---|---|
ccusage codex daily --no-offline --timezone UTC --json |
fresh | 0.479 | 0.483 | 0.404 | 0.590 |
ccusage codex daily --offline --timezone UTC --json |
warmed | 0.271 | 0.275 | 0.244 | 0.328 |
llm-usage daily --source codex --timezone UTC --json |
fresh | 1.527 | 1.555 | 1.497 | 1.734 |
llm-usage daily --source codex --timezone UTC --json --pricing-offline |
warmed | 0.766 | 0.776 | 0.692 | 0.899 |
From median runtime, ccusage was 3.19x faster in fresh application state and 2.83x faster in warmed application state.
claude — daily --json (8 runs each)
Section titled “claude — daily --json (8 runs each)”| Tool | Application state | Median (s) | Mean (s) | Min (s) | Max (s) |
|---|---|---|---|---|---|
ccusage claude daily --no-offline --timezone UTC --json |
fresh | 0.363 | 0.328 | 0.242 | 0.392 |
ccusage claude daily --offline --timezone UTC --json |
warmed | 0.087 | 0.089 | 0.074 | 0.114 |
llm-usage daily --source claude --timezone UTC --json |
fresh | 0.754 | 0.773 | 0.713 | 0.837 |
llm-usage daily --source claude --timezone UTC --json --pricing-offline |
warmed | 0.182 | 0.185 | 0.170 | 0.204 |
From median runtime, ccusage was 2.08x faster in fresh application state and 2.10x faster in warmed application state.
These are runtime comparisons of similar report commands, not correctness or byte-for-byte output-equivalence claims. The tools use different output schemas and normalization rules.
Why an npx timing says llm-usage is faster
Section titled “Why an npx timing says llm-usage is faster”The reported shell commands do not have equivalent launch paths: llm-usage is already installed, while npx ccusage@latest starts npm, resolves the package, prepares its execution environment, and then starts ccusage. We repeated that monthly terminal scenario on the same stable Codex snapshot, using the pinned ccusage version from this baseline:
| Command | Launch path | Median (s) | Mean (s) | Min (s) | Max (s) |
|---|---|---|---|---|---|
llm-usage monthly --per-model-columns --source codex |
installed command | 0.893 | 0.939 | 0.759 | 1.172 |
ccusage codex monthly |
package executable | 0.578 | 0.573 | 0.539 | 0.612 |
npx --yes ccusage@20.0.17 codex monthly |
npm + ccusage | 3.771 | 3.814 | 3.448 | 4.110 |
With direct package executables, ccusage was 1.55x faster in this terminal scenario. With npx included, the installed llm-usage command appeared 4.22x faster. Both observations are valid; they answer different questions. Use launcher-inclusive timing to judge the one-off npx experience, and direct-process timing to compare the tools themselves.
This launcher check is an observed shell-path comparison, not the counterbalanced application benchmark above. Each command received one untimed warm-up followed by 8 sequential timed runs on the stable snapshot; commands were grouped by row, output was discarded through non-TTY pipes, and the system timezone plus default online pricing/update behavior were left in place. ccusage 20.0.17 was already resolved in npm’s package cache before both its direct and npx rows.
What the application states mean
Section titled “What the application states mean”- Fresh application state: a new
XDG_CACHE_HOMEandXDG_CONFIG_HOMEfor every timed run, live pricing, andLLM_USAGE_EVENT_STORE=0for 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 (
--offlineor--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.
Baseline machine
Section titled “Baseline machine”| Spec | Value |
|---|---|
| OS | Linux 6.18.33-2-cachyos-lts (x64) |
| CPU | Intel(R) Core(TM) Ultra 9 185H (22 logical CPUs) |
| Memory | 62.2 GiB RAM |
| Node.js | v24.12.0 |
| pnpm | 10.17.1 |
| ccusage | 20.0.17 (package executable + native binary) |
| llm-usage | 0.8.0 (git 7ebacde, built dist) |
Dataset scope
Section titled “Dataset scope”| Corpus | Files | Size on disk |
|---|---|---|
| codex | 1,168 JSONL session files | ≈ 1.77 GiB |
| claude | 271 JSONL files (projects + transcripts) | ≈ 178.8 MiB |
The source trees were copied to a filesystem snapshot immediately before the run so the active Codex session could not grow between samples.
Methodology
Section titled “Methodology”- 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. The separate monthly table intentionally includes
npxin its third row. - llm-usage gets
LLM_USAGE_SKIP_UPDATE_CHECK=1in the direct-process comparison. ccusage exposes no equivalent update-check switch.
Engineering notes: the fresh Codex path
Section titled “Engineering notes: the fresh Codex path”Before the parse-pipeline work, a fresh Codex run over this corpus took 8.7 s; the current measured median is 1.527 s. The mechanisms, in pipeline order:
- Byte prefilters and a buffer-based JSONL reader (
src/utils/read-jsonl-objects.ts) skip irrelevant lines before any JSON parsing. - A per-timezone local-date window cache for period bucketing (
src/utils/time-buckets.ts) avoids repeated date math per event. - An event-store warm read path serves already-parsed events.
- A worker-thread parse pool (
src/cli/parse-worker-pool.ts) parses large JSONL files in parallel.
Reproduce it
Section titled “Reproduce it”Build the current checkout and resolve the exact ccusage version without timing npx itself:
pnpm run buildCCUSAGE_BIN=$(npx --yes --package=ccusage@20.0.17 sh -c 'command -v ccusage')"$CCUSAGE_BIN" --versionnode 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 and .claude. 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.
Results are machine-, dataset-, command-, and launcher-specific. Re-run the exact scenario you care about before drawing a broader conclusion.