Skip to content

feat(qwen): add Qwen usage adapter#1030

Merged
ryoppippi merged 20 commits into
mainfrom
codex/qwen-usage-adapter
May 19, 2026
Merged

feat(qwen): add Qwen usage adapter#1030
ryoppippi merged 20 commits into
mainfrom
codex/qwen-usage-adapter

Conversation

@ryoppippi
Copy link
Copy Markdown
Owner

@ryoppippi ryoppippi commented May 17, 2026

Try This PR

bunx -p https://pkg.pr.new/ryoppippi/ccusage@1030 ccusage qwen daily --help

Summary

  • add Qwen Code local JSONL discovery under QWEN_DATA_DIR / ~/.qwen
  • parse assistant usageMetadata into ccusage rows with cache read and reasoning tokens
  • wire Qwen into focused commands, all-source reports, config schema, README, docs, and CLI snapshots
  • preserve adapter-provided totalTokens in compact tables so reasoning-token totals stay consistent with JSON output

Validation

  • pnpm run format
  • pnpm typecheck
  • pnpm run test
  • pnpm --filter docs build
  • git diff --check

Summary by cubic

Add a Rust-based Qwen usage adapter that reads local Qwen Code JSONL chats and plugs into focused and unified reports. Totals include reasoning tokens; tables now prefer adapter-provided totalTokens and fall back to a computed sum when missing so counts and costs match Qwen logs and LiteLLM pricing.

  • New Features

    • Auto-discover chats under QWEN_DATA_DIR or ~/.qwen at projects/*/chats/*.jsonl.
    • Commands: ccusage qwen daily|monthly|session; included in unified reports and CLI help; supports --offline.
    • Parse assistant usageMetadata (prompt, candidates, thoughts, cache reads); include reasoning tokens in totalTokens.
    • Pricing via LiteLLM candidates using model, qwen/model, and alibaba/model; config schema adds qwen namespace; docs add a Qwen guide and env var updates.
  • Bug Fixes

    • Safer parsing: skip non-assistant lines, ignore invalid JSON, dedupe entries; fallback to file mtime; robust session/project from paths.
    • Unified detection: mark source present when chat files exist even if entries fall outside the date range.
    • Pricing: honor explicit $0.00 models and prefer base model pricing over provider-prefixed aliases.
    • Config schema/tests: align root schema and labels to include qwen; restore full property set in tests.
    • Session filters: normalize ISO date bounds for qwen session so since/until filtering works correctly.
    • Output totals: prefer adapter totalTokens; fall back to input+output+cache sums; remove a duplicate binding to satisfy linting.

Written for commit b957810. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Added Qwen as a supported coding-agent data source with daily, monthly, and session reports
    • New CLI commands: ccusage qwen daily, ccusage qwen monthly, ccusage qwen session
    • Qwen included in unified/aggregated reports, progress/output labels, help text, and configuration schema
  • Documentation

    • Added experimental Qwen guide with setup, data layout, examples, pricing notes, and troubleshooting
    • Updated guides, sidebar, examples, and QWEN_DATA_DIR docs to include Qwen

Review Change Stack

Add a Qwen adapter for local chat JSONL files under QWEN_DATA_DIR, including path discovery, parsing, LiteLLM pricing candidates, focused commands, all-source detection, docs, schema, and CLI output coverage.

Qwen exposes reasoning tokens separately from ordinary candidate output, so table rendering now accepts adapter-provided totalTokens instead of recomputing totals from visible token columns.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Qwen as a local agent data source: discovers Qwen chat JSONL files, parses token/cost data, summarizes into daily/monthly/session reports, wires CLI/config/schema/help/tests, integrates into the multi-agent loader and progress, updates output totals, and adds documentation.

Changes

Qwen Agent Support

Layer / File(s) Summary
Documentation and README updates
docs/guide/qwen/index.md, docs/..., apps/ccusage/README.md
Adds Qwen guide, sidebar entry, homepage/guide examples, QWEN_DATA_DIR env docs, and README usage examples (ccusage qwen ...).
Qwen data path discovery
rust/crates/ccusage/src/adapter/qwen/paths.rs
Resolve QWEN_DATA_DIR (comma-separated or default ~/.qwen), dedupe roots, discover projects/*/chats/*.jsonl, and extract project id from file paths.
Qwen JSONL parsing and cost calculation
rust/crates/ccusage/src/adapter/qwen/parser.rs
Parse chat JSONL assistant records, extract usageMetadata, timestamp fallback to file mtime, normalize session_id/model, compute display vs billable tokens (include thoughtsTokenCount), compute cost via model candidates, dedupe entries by deterministic entry_id, and add tests.
Adapter runtime and summarization
rust/crates/ccusage/src/adapter/qwen/mod.rs
Load entries with usage-load tracking, apply date/session filtering, summarize into daily/monthly/session (monthly rebuckets daily, sessions group by session_id), produce JSON/table outputs with totals: null when empty, and include unit tests.
Adapter module declaration and aggregator wiring
rust/crates/ccusage/src/adapter/mod.rs, rust/crates/ccusage/src/adapter/all.rs
Declare qwen adapter module, import it, add AgentLoadSpec for "qwen", implement load_qwen_rows aggregation, and map agent label to "Qwen".
CLI parsing, help, and tests
rust/crates/ccusage/src/cli.rs
Add Command::Qwen(AgentCommandArgs), implement parse_qwen_command supporting `daily
Configuration schema and tests
apps/ccusage/config-schema.json, rust/crates/ccusage/src/config_schema.rs
Add optional top-level qwen config, define QwenConfig and QwenCommandsConfig referencing SharedOptions, update JSON-schema generation tests and CLI config fixtures.
Main dispatch, progress, and output adjustments
rust/crates/ccusage/src/main.rs, rust/crates/ccusage/src/progress.rs, rust/crates/ccusage/src/output.rs
Dispatch Command::Qwen to adapter::qwen::run, add UsageLoadAgent::Qwen, and read overall totalTokens from totals JSON with fallback in print_usage_table.

Sequence Diagram:

sequenceDiagram
  participant User
  participant CLI as ccusage CLI
  participant Adapter as adapter::qwen::run
  participant Paths as qwen::paths
  participant Parser as qwen::parser
  User->>CLI: ccusage qwen daily --json
  CLI->>Adapter: Command::Qwen(args)
  Adapter->>Paths: discover_chat_files()
  Paths->>Adapter: list of chat files
  Adapter->>Parser: load_entries(shared)
  Parser->>Adapter: LoadedEntry[]
  Adapter->>Adapter: summarize_entries -> rows/json
  Adapter->>User: print JSON/table
Loading

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

Possibly related PRs:

Suggested labels:
enhancement

"I hop through logs with eager paws,
counting tokens, noting flaws.
Qwen files found, parsed, and summed,
neat reports leap out, nicely plumbed.
🐇📊"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.49% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(qwen): add Qwen usage adapter' accurately summarizes the main change—adding a new Qwen usage adapter to the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/qwen-usage-adapter

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 and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 17, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ccusage-guide b957810 Commit Preview URL

Branch Preview URL
May 19 2026, 07:25 PM

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 17, 2026

ccusage performance comparison

This compares the PR build against the base branch build on the same CI runner.

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Runtime: package ccusage bin from apps/ccusage/package.json through bun -b, --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 55.0ms 54.6ms 1.01x 0.03 MiB/s 0.03 MiB/s
claude session --offline --json 0.00 MiB 54.4ms 53.1ms 1.02x 0.03 MiB/s 0.03 MiB/s
codex daily --offline --json 0.00 MiB 54.0ms 54.5ms 0.99x 0.02 MiB/s 0.02 MiB/s
codex session --offline --json 0.00 MiB 53.8ms 53.7ms 1.00x 0.02 MiB/s 0.02 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Runtime: package ccusage bin from apps/ccusage/package.json through bun -b, --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 1.376s 1.363s 1.01x 749.02 MiB/s 756.48 MiB/s
codex --offline --json 1.01 GiB 1.651s 1.808s 0.91x 624.52 MiB/s 570.34 MiB/s

Package size

Package artifact Base PR Delta Ratio
packed ccusage-*.tgz 67.58 KiB 68.64 KiB +1.06 KiB 0.98x

Lower medians and smaller packed package sizes are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/ccusage/test/snapshots/cli-output/qwen-direct-daily-json.txt (1)

1-24: ⚡ Quick win

Consider adding a reasoningTokens field for transparency.

The JSON output shows totalTokens: 165 while the sum of component fields is only 155 (100+50+0+5). The 10-token difference represents reasoning tokens, which per the PR design are included in the adapter-provided totalTokens but not broken out separately.

For user clarity and transparency, consider adding an explicit reasoningTokens field to the output schema so users can track reasoning token usage independently.

💡 Suggested enhancement
 {
   "daily": [
     {
       "date": "2026-01-02",
       "inputTokens": 100,
       "outputTokens": 50,
       "cacheCreationTokens": 0,
       "cacheReadTokens": 5,
+      "reasoningTokens": 10,
       "totalTokens": 165,
       "totalCost": 0,
       "modelsUsed": [
         "qwen3-coder-plus"
       ]
     }
   ],
   "totals": {
     "inputTokens": 100,
     "outputTokens": 50,
     "cacheCreationTokens": 0,
     "cacheReadTokens": 5,
+    "reasoningTokens": 10,
     "totalTokens": 165,
     "totalCost": 0
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ccusage/test/snapshots/cli-output/qwen-direct-daily-json.txt` around
lines 1 - 24, Add an explicit reasoningTokens field computed as totalTokens -
(inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens) to both
each object in the "daily" array and the top-level "totals" object (e.g., update
the code that builds daily entries and the totals aggregation to set
reasoningTokens = Math.max(0, totalTokens - sumOfComponents) to avoid
negatives), and update the output schema/snapshots accordingly so consumers see
reasoningTokens alongside
inputTokens/outputTokens/cacheCreationTokens/cacheReadTokens and totalTokens.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/ccusage/config-schema.json`:
- Around line 635-781: The schema is missing a top-level "qwen" object while
runtime accepts config.qwen (see AGENT_CONFIG_KEYS and ConfigData), causing
validation to reject commands like "qwen:daily"/"qwen:monthly"/"qwen:session";
add a top-level "qwen" schema object mirroring other agent namespaces that
includes "defaults" and "commands" (with properties referencing the existing
"qwen:daily", "qwen:monthly", "qwen:session" definitions or duplicating their
structure) and ensure additionalProperties is set consistently so
autocomplete/validation accepts config.qwen and its subcommands.

In `@apps/ccusage/src/adapter/qwen/paths.ts`:
- Around line 20-35: isQwenChatFile is too permissive (matches any parent named
"chats"); update it to only accept files that follow projects/*/chats/*.jsonl by
checking that path.basename(path.dirname(filePath)) === QWEN_CHATS_DIR_NAME and
that the ancestor three levels up has basename === QWEN_PROJECTS_DIR_NAME (i.e.,
verify path.basename(path.dirname(path.dirname(path.dirname(filePath)))) ===
QWEN_PROJECTS_DIR_NAME); keep discoverQwenChatFiles, getQwenPaths,
QWEN_PROJECTS_DIR_NAME and QWEN_CHATS_DIR_NAME intact and use the revised
isQwenChatFile for filtering.

---

Nitpick comments:
In `@apps/ccusage/test/snapshots/cli-output/qwen-direct-daily-json.txt`:
- Around line 1-24: Add an explicit reasoningTokens field computed as
totalTokens - (inputTokens + outputTokens + cacheCreationTokens +
cacheReadTokens) to both each object in the "daily" array and the top-level
"totals" object (e.g., update the code that builds daily entries and the totals
aggregation to set reasoningTokens = Math.max(0, totalTokens - sumOfComponents)
to avoid negatives), and update the output schema/snapshots accordingly so
consumers see reasoningTokens alongside
inputTokens/outputTokens/cacheCreationTokens/cacheReadTokens and totalTokens.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 832d95f5-09d1-490e-a469-95c83ed6eea6

📥 Commits

Reviewing files that changed from the base of the PR and between 4aedac0 and b65ff9a.

📒 Files selected for processing (31)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • apps/ccusage/src/adapter/index.ts
  • apps/ccusage/src/adapter/qwen/index.ts
  • apps/ccusage/src/adapter/qwen/parser.ts
  • apps/ccusage/src/adapter/qwen/paths.ts
  • apps/ccusage/src/adapter/qwen/pricing.ts
  • apps/ccusage/src/adapter/qwen/types.ts
  • apps/ccusage/src/adapter/types.ts
  • apps/ccusage/src/commands/agent.ts
  • apps/ccusage/src/commands/all.ts
  • apps/ccusage/src/commands/index.ts
  • apps/ccusage/src/config-loader-tokens.ts
  • apps/ccusage/test/cli-output.test.ts
  • apps/ccusage/test/snapshots/cli-output/all-agent-daily-json.txt
  • apps/ccusage/test/snapshots/cli-output/all-agent-daily-table.txt
  • apps/ccusage/test/snapshots/cli-output/qwen-direct-daily-json.txt
  • docs/.vitepress/config.ts
  • docs/guide/all-reports.md
  • docs/guide/claude/index.md
  • docs/guide/codex/index.md
  • docs/guide/config-files.md
  • docs/guide/configuration.md
  • docs/guide/daily-reports.md
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/index.md
  • docs/guide/monthly-reports.md
  • docs/guide/qwen/index.md
  • docs/guide/session-reports.md
  • packages/terminal/src/table.ts

Comment thread apps/ccusage/config-schema.json Outdated
Comment thread apps/ccusage/src/adapter/qwen/paths.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/ccusage/scripts/generate-json-schema.ts (1)

468-468: ⚡ Quick win

Strengthen the “expected properties” test to cover all agent namespaces.

This check now includes qwen, but it still misses assertions for existing namespaces (opencode, amp, pi). Adding them keeps this test from allowing accidental regressions.

Suggested test expansion
 			expect(properties).toHaveProperty('claude');
 			expect(properties).toHaveProperty('codex');
+			expect(properties).toHaveProperty('opencode');
+			expect(properties).toHaveProperty('amp');
+			expect(properties).toHaveProperty('pi');
 			expect(properties).toHaveProperty('qwen');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ccusage/scripts/generate-json-schema.ts` at line 468, The test currently
only asserts that properties includes 'qwen'
(expect(properties).toHaveProperty('qwen');) and should be expanded to assert
all agent namespaces to prevent regressions; update the test in
generate-json-schema.ts where properties is checked to also include
toHaveProperty('opencode'), toHaveProperty('amp'), and toHaveProperty('pi') (in
addition to the existing 'qwen') so the suite verifies all expected agent
namespaces are present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/ccusage/scripts/generate-json-schema.ts`:
- Line 468: The test currently only asserts that properties includes 'qwen'
(expect(properties).toHaveProperty('qwen');) and should be expanded to assert
all agent namespaces to prevent regressions; update the test in
generate-json-schema.ts where properties is checked to also include
toHaveProperty('opencode'), toHaveProperty('amp'), and toHaveProperty('pi') (in
addition to the existing 'qwen') so the suite verifies all expected agent
namespaces are present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2b40de5-f46f-4268-9e39-de0dfa307e89

📥 Commits

Reviewing files that changed from the base of the PR and between b65ff9a and 34a5105.

📒 Files selected for processing (3)
  • apps/ccusage/config-schema.json
  • apps/ccusage/scripts/generate-json-schema.ts
  • apps/ccusage/src/adapter/qwen/paths.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/ccusage/src/adapter/qwen/paths.ts
  • apps/ccusage/config-schema.json

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 17, 2026

Open in StackBlitz

ccusage

npx https://pkg.pr.new/ryoppippi/ccusage@1030

@ccusage/ccusage-darwin-arm64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-darwin-arm64@1030

@ccusage/ccusage-darwin-x64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-darwin-x64@1030

@ccusage/ccusage-linux-arm64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-linux-arm64@1030

@ccusage/ccusage-linux-x64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-linux-x64@1030

@ccusage/ccusage-win32-arm64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-win32-arm64@1030

@ccusage/ccusage-win32-x64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-win32-x64@1030

commit: b957810

@ryoppippi
Copy link
Copy Markdown
Owner Author

@coderabbitai review it

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read key Qwen adapter files
echo "=== paths.ts ==="
cat apps/ccusage/src/adapter/qwen/paths.ts

echo "=== parser.ts ==="
cat apps/ccusage/src/adapter/qwen/parser.ts

echo "=== types.ts ==="
cat apps/ccusage/src/adapter/qwen/types.ts

echo "=== pricing.ts ==="
cat apps/ccusage/src/adapter/qwen/pricing.ts

echo "=== index.ts (qwen adapter) ==="
cat apps/ccusage/src/adapter/qwen/index.ts

echo "=== adapter/index.ts ==="
cat apps/ccusage/src/adapter/index.ts

echo "=== adapter/types.ts ==="
cat apps/ccusage/src/adapter/types.ts

echo "=== table.ts ==="
cat packages/terminal/src/table.ts

echo "=== commands/agent.ts ==="
cat apps/ccusage/src/commands/agent.ts

echo "=== commands/all.ts (relevant parts) ==="
cat apps/ccusage/src/commands/all.ts

Length of output: 99164


🏁 Script executed:

#!/bin/bash
echo "=== fs-fixture imports across adapter files ==="
rg "from 'fs-fixture'" apps/ccusage/src/adapter/ -n

echo ""
echo "=== shared.ts (createAgentPricingContext) ==="
cat apps/ccusage/src/adapter/shared.ts

echo ""
echo "=== weekly command registration for other agents ==="
rg "weekly" apps/ccusage/src/commands/index.ts -n -A2 -B2

echo ""
echo "=== pushBreakdownRows callers ==="
rg "pushBreakdownRows" apps/ -n -A3 -B3

Length of output: 30222


@ryoppippi: Overall this is a clean, well-structured implementation that follows the established project patterns throughout (fs-fixture in-file test imports, defineAgentLogLoader, createAgentPricingContext, Result.try error handling, etc.). A few items worth addressing:


🔴 Dedup key separator collision

apps/ccusage/src/adapter/qwen/parser.ts

const key = [
    'qwen',
    entry.sessionId,
    entry.timestamp,
    entry.model,         // e.g. "qwen3:latest" or "qwen3.5-coder:instruct"
    entry.inputTokens,
    entry.outputTokens,
    entry.cacheReadTokens,
    entry.reasoningTokens,
].join(':');

Model names from Ollama/local Qwen registries often use the name:tag format (e.g. qwen3:latest, qwen3.5-coder:instruct), and sessionId — while currently generated as project-stem — is an uncontrolled string field from the JSONL record. Using : as a separator means entries with models like qwen3:latest can produce a key segment that collides with a hypothetical entry where the model is qwen3 and another field boundary shifts.

Suggested fix — use a separator that cannot appear in any of the components, or serialize with JSON.stringify:

const key = JSON.stringify([
    'qwen',
    entry.sessionId,
    entry.timestamp,
    entry.model,
    entry.inputTokens,
    entry.outputTokens,
    entry.cacheReadTokens,
    entry.reasoningTokens,
]);

🟡 getFileTimestamp falls back to Unix epoch

apps/ccusage/src/adapter/qwen/parser.ts

async function getFileTimestamp(filePath: string): Promise<string> {
    const result = await Result.try({ try: stat(filePath), catch: (error) => error });
    return Result.isSuccess(result) ? result.value.mtime.toISOString() : new Date(0).toISOString();
    //                                                                        ^^^^^^^^^^^
    //                                                     "1970-01-01T00:00:00.000Z"
}

If stat fails (e.g. a race between discovery and parsing, or a permissions issue), every entry in that file gets a 1970-01-01 timestamp. Those entries will all be grouped under 1970-01-01 in daily/monthly reports — silently and confusingly. Using new Date().toISOString() (current time) as the fallback would be less surprising, or you could log a warning before falling back.


🟡 calculateQwenCost skips zero-priced model entries

apps/ccusage/src/adapter/qwen/pricing.ts

if (Result.isSuccess(result) && result.value > 0) {
    return result.value;
}

If a model IS present in the LiteLLM price table but its price is explicitly set to 0 (a free/internal model), this check falls through to the next candidate, and if no candidate returns > 0, the function returns 0 anyway. The end result is the same, but the condition may be surprising to future readers — and it means a zero-price hit on the first candidate will still try the remaining candidates unnecessarily.

Consider result.value >= 0 and returning on the first successful lookup (success = model found), accepting 0 as a valid price:

if (Result.isSuccess(result)) {
    // Return immediately on the first model found in the pricing table,
    // even if its cost is 0 (explicitly free/zero-priced model).
    return result.value;
}

✅ Things that look good

  • createAgentPricingContext correctly reuses context.pricingFetcher when the caller already owns a fetcher — confirmed in shared.ts.
  • defineAgentLogLoader's aggregation correctly picks up the explicit totalTokens (which includes reasoningTokens) returned from getUsage, so reasoning tokens are properly counted in all aggregated rows.
  • The isQwenChatFile filter in discoverQwenChatFiles ensures getQwenProject's backward search always sees a well-formed path.
  • Omitting weekly for Qwen is consistent with codex, amp, and pi.
  • resolveDetectedAgents tuple index for qwen (index 5) is correct.
  • Error handling via Result.try is consistent with the rest of the codebase.

@ryoppippi
Copy link
Copy Markdown
Owner Author

@cubic-dev-ai review it

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai Bot commented May 17, 2026

@cubic-dev-ai review it

@ryoppippi I have started the AI code review. It will take a few minutes to complete.

Replace the inline agent label conditional in the schema generator with a small exhaustive switch helper. This keeps future agent-specific labels out of nested ternaries and makes the config schema label rules easier to extend.
@ryoppippi
Copy link
Copy Markdown
Owner Author

@coderabbitai review it

@ryoppippi
Copy link
Copy Markdown
Owner Author

@cubic-dev-ai review it

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai Bot commented May 17, 2026

@cubic-dev-ai review it

@ryoppippi I have started the AI code review. It will take a few minutes to complete.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

@ryoppippi Sure, I'll kick off a fresh full review of this PR!

✅ Actions performed

Full review triggered.

ryoppippi added 2 commits May 19, 2026 12:18
Merge origin/main into the Qwen adapter PR and replace the previous TypeScript adapter wiring with Rust CLI support.

Add Rust Qwen chat discovery, JSONL parsing, report aggregation, command/config schema integration, and documentation updates. Preserve Qwen reasoning tokens in total token counts without inflating displayed output tokens.

Validation: nix develop --command pnpm run format; nix develop --command pnpm typecheck; nix develop --command pnpm run test.
Keep the Qwen Rust adapter PR focused by reverting local pre-commit sync changes to .claude/skills that are unrelated to this adapter port.
@ryoppippi
Copy link
Copy Markdown
Owner Author

@coderabbitai review

Updated this PR to merge current main and port Qwen support to the Rust CLI implementation. Local validation passed:

  • nix develop --command pnpm run format
  • nix develop --command pnpm typecheck
  • nix develop --command pnpm run test

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

@ryoppippi: Thanks for the update! I'll kick off a fresh full review covering the merged main changes and the new Rust CLI port for Qwen support.

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/crates/ccusage/src/adapter/all.rs`:
- Around line 334-335: The table total calculation is recomputing totals from
input/output/cache and thus misses summary.extra totals; change the table/JSON
total path to use the precomputed total on each row (row.total_tokens) instead
of summing row.input_tokens + row.output_tokens + row.cache_tokens (affecting
the block that currently calls summary.total_tokens() and the downstream
aggregation near the table total logic around the previous lines ~612-613).
Locate usages of summary.total_tokens() and the manual sum of row fields and
replace them with aggregating row.total_tokens so table and JSON outputs remain
consistent when extra_total_tokens are present.

In `@rust/crates/ccusage/src/adapter/qwen/mod.rs`:
- Around line 121-145: The test mutates the process-global QWEN_DATA_DIR which
causes races when tests run in parallel; update the test(s) (e.g.,
loads_qwen_jsonl_usage_entries and the similar test at 157-179) to set
QWEN_DATA_DIR in a safe, per-test way: capture the current env var, set
QWEN_DATA_DIR to the temp_qwen_dir value before calling load_entries, then
always restore the original value and remove the temp dir in a
panic-safe/finally manner (or use a helper like temp_env or the serial_test
attribute) so SharedArgs::default()/load_entries see the intended directory
without leaking or racing with other tests. Ensure the same pattern is applied
to the other test(s) referencing QWEN_DATA_DIR.

In `@rust/crates/ccusage/src/adapter/qwen/parser.rs`:
- Around line 165-172: The file_timestamp function currently falls back to
TimestampMs::UNIX_EPOCH on any metadata/mtime error which yields misleading 1970
dates; change the fallback to use the current time instead and emit a warning
when the metadata lookup fails. Specifically, update the error path in
file_timestamp (replace the final unwrap_or(TimestampMs::UNIX_EPOCH)) so that
failed metadata/modified/duration_since cases convert SystemTime::now() (or call
TimestampMs::now() if available) into a TimestampMs and call log::warn! (or the
crate's logger) to record the failure and filepath.
- Around line 174-185: entry_id currently concatenates user-controlled fields
with ":" in the entry_id function, which can collide if session_id, model or
timestamp contain ":"; change entry_id to produce an unambiguous key by safely
encoding or delimiting components (e.g., JSON-serialize the tuple, use
length-prefixed fields, escape delimiters, or base64-encode
session_id/model/timestamp) instead of raw ":" joining; update the entry_id
function to encode entry.session_id, entry.data.timestamp,
entry.model.as_deref().unwrap_or_default(), usage.input_tokens,
usage.output_tokens, usage.cache_read_input_tokens, and entry.extra_total_tokens
into a single non-ambiguous string (keeping other field names and logic intact).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b8d96a1-d054-4cc6-b2cb-a3f7b4afc179

📥 Commits

Reviewing files that changed from the base of the PR and between 0fa8220 and 2bad7fd.

📒 Files selected for processing (23)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • docs/.vitepress/config.ts
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • rust/crates/ccusage/src/adapter/all.rs
  • rust/crates/ccusage/src/adapter/amp.rs
  • rust/crates/ccusage/src/adapter/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/parser.rs
  • rust/crates/ccusage/src/adapter/pi.rs
  • rust/crates/ccusage/src/adapter/qwen/mod.rs
  • rust/crates/ccusage/src/adapter/qwen/parser.rs
  • rust/crates/ccusage/src/adapter/qwen/paths.rs
  • rust/crates/ccusage/src/claude_loader.rs
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/main.rs
  • rust/crates/ccusage/src/output.rs
  • rust/crates/ccusage/src/progress.rs
  • rust/crates/ccusage/src/summary.rs
  • rust/crates/ccusage/src/types.rs
✅ Files skipped from review due to trivial changes (4)
  • rust/crates/ccusage/src/progress.rs
  • docs/.vitepress/config.ts
  • apps/ccusage/README.md
  • docs/guide/getting-started.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/guide/environment-variables.md

Comment thread rust/crates/ccusage/src/adapter/all.rs
Comment thread rust/crates/ccusage/src/adapter/qwen/mod.rs
Comment thread rust/crates/ccusage/src/adapter/qwen/parser.rs Outdated
Comment thread rust/crates/ccusage/src/adapter/qwen/parser.rs Outdated
@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: 2bad7fda94f4
Base SHA: 27047aca4573

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 27047aca4573 423.8ms 495.8ms 34.7ms 3
PR pkg.pr.new 2bad7fda94f4 728.0ms 991.1ms 34.6ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 27047aca4573; PR package: 2bad7fda94f4. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 675.1ms 674.3ms 1.00x 1.49 GiB/s 1.49 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 869.9ms 869.7ms 1.00x 1.16 GiB/s 1.16 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 672.1ms 1.50 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 634.0ms 1.59 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 740.5ms 1.36 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 848.8ms 1.19 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 831.1ms 1.21 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 822.6ms 1.22 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 29.9ms 3.9ms 7.68x 0.05 MiB/s 0.40 MiB/s
claude session --offline --json 0.00 MiB 30.5ms 4.0ms 7.59x 0.05 MiB/s 0.38 MiB/s
codex daily --offline --json 0.00 MiB 30.4ms 3.6ms 8.40x 0.03 MiB/s 0.24 MiB/s
codex session --offline --json 0.00 MiB 30.0ms 3.6ms 8.32x 0.03 MiB/s 0.24 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 667.3ms 742.7ms 0.90x 1.51 GiB/s 1.36 GiB/s
codex --offline --json 1.01 GiB 862.5ms 832.1ms 1.04x 1.17 GiB/s 1.21 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 8.86 KiB 8.92 KiB +0.05 KiB 0.99x
installed native package binary 2772.96 KiB 2772.96 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2699.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: 2bad7fda94f4
Base SHA: 27047aca4573

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 27047aca4573 545.6ms 471.2ms 33.0ms 3
PR pkg.pr.new 2bad7fda94f4 727.5ms 550.0ms 34.0ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 27047aca4573; PR package: 2bad7fda94f4. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 662.3ms 681.1ms 0.97x 1.52 GiB/s 1.48 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 834.9ms 824.5ms 1.01x 1.21 GiB/s 1.22 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 665.1ms 1.51 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 637.5ms 1.58 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 701.3ms 1.44 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 816.5ms 1.23 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 788.3ms 1.28 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 803.8ms 1.25 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 29.4ms 29.5ms 1.00x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 29.7ms 29.4ms 1.01x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 29.3ms 29.2ms 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 28.6ms 29.0ms 0.99x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 660.4ms 666.8ms 0.99x 1.52 GiB/s 1.51 GiB/s
codex --offline --json 1.01 GiB 826.0ms 831.9ms 0.99x 1.22 GiB/s 1.21 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 8.86 KiB 8.92 KiB +0.05 KiB 0.99x
installed native package binary 2772.96 KiB 2772.96 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2699.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/guide/environment-variables.md (1)

16-25: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include QWEN in the debug grep example.

After documenting QWEN_DATA_DIR, the debug command should also match it (currently it filters only CLAUDE|CODEX|OPENCODE|AMP|PI_AGENT|CCUSAGE|LOG_LEVEL), otherwise users can miss Qwen env diagnostics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/guide/environment-variables.md` around lines 16 - 25, The debug grep
example is missing QWEN so add QWEN to the diagnostic filter: update the
grep/regex that currently contains
CLAUDE|CODEX|OPENCODE|AMP|PI_AGENT|CCUSAGE|LOG_LEVEL to also include QWEN (and
ensure it will match QWEN_DATA_DIR); locate the debug command in the docs and
modify the pattern string to include the token "QWEN" so QWEN_DATA_DIR appears
in the debug output.
♻️ Duplicate comments (3)
rust/crates/ccusage/src/adapter/qwen/mod.rs (1)

121-145: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Serialize tests that mutate QWEN_DATA_DIR and restore env panic-safely.

Both tests mutate the same process-global env var without synchronization, so they can race under parallel test execution and produce flaky results.

Suggested direction
+use std::sync::{Mutex, OnceLock};
+
+fn qwen_env_lock() -> &'static Mutex<()> {
+    static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
+    LOCK.get_or_init(|| Mutex::new(()))
+}
+
 #[test]
 fn loads_qwen_jsonl_usage_entries() {
+    let _guard = qwen_env_lock().lock().unwrap();
+    let prev = env::var_os("QWEN_DATA_DIR");
     // ...
     env::set_var("QWEN_DATA_DIR", &qwen_dir);
@@
-    env::remove_var("QWEN_DATA_DIR");
+    match prev {
+        Some(v) => env::set_var("QWEN_DATA_DIR", v),
+        None => env::remove_var("QWEN_DATA_DIR"),
+    }
 }

Apply the same pattern to the second test.

Also applies to: 157-179

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/ccusage/src/adapter/qwen/mod.rs` around lines 121 - 145, The test
loads_qwen_jsonl_usage_entries (and the second test at lines 157-179) mutates
the global QWEN_DATA_DIR via env::set_var and then env::remove_var, which can
race when tests run in parallel; change both tests to capture the previous
env::var("QWEN_DATA_DIR") state, set the new value, and install a panic-safe
guard (a small RAII Drop guard or use temp_env/test-env crate) that restores the
original value (either by env::set_var(prev) or env::remove_var if None) when
the test scope exits, and use the same pattern for temp_qwen_dir cleanup so both
environment and temp directory are always cleaned up even on panic.
rust/crates/ccusage/src/adapter/qwen/parser.rs (2)

174-185: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make dedup keys unambiguous.

Building the key with : separators can collide when any component contains : (session/model/timestamp), causing distinct entries to be dropped.

Suggested patch
 fn entry_id(entry: &LoadedEntry) -> String {
     let usage = entry.data.message.usage;
-    format!(
-        "{}:{}:{}:{}:{}:{}:{}",
-        entry.session_id,
-        entry.data.timestamp,
-        entry.model.as_deref().unwrap_or_default(),
-        usage.input_tokens,
-        usage.output_tokens,
-        usage.cache_read_input_tokens,
-        entry.extra_total_tokens
-    )
+    serde_json::json!([
+        entry.session_id.as_ref(),
+        entry.data.timestamp.as_str(),
+        entry.model.as_deref().unwrap_or_default(),
+        usage.input_tokens,
+        usage.output_tokens,
+        usage.cache_read_input_tokens,
+        entry.extra_total_tokens
+    ])
+    .to_string()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/ccusage/src/adapter/qwen/parser.rs` around lines 174 - 185, The
dedup key built in entry_id can collide because components like
session_id/model/timestamp may contain ":"; update the entry_id function to
produce an unambiguous key by serializing the identifying components into a
single canonical representation (e.g., use serde_json::to_string on a
tuple/array of (entry.session_id, entry.data.timestamp,
entry.model.as_deref().unwrap_or_default(), usage.input_tokens,
usage.output_tokens, usage.cache_read_input_tokens, entry.extra_total_tokens) or
alternatively base64-encode/escape the string components) so separators cannot
collide; modify the entry_id function to construct the key via that
serialization and return the serialized string (referencing entry_id,
LoadedEntry, entry.session_id, entry.data.timestamp, entry.model, usage fields).

165-172: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a non-epoch fallback when file mtime lookup fails.

Falling back to Unix epoch can silently push records into 1970 buckets and distort daily/monthly summaries. Use current time (and log a warning) instead.

Suggested patch
 use std::{
     collections::HashSet,
     fs::{self, File},
     io::{BufRead, BufReader},
     path::Path,
     sync::Arc,
-    time::UNIX_EPOCH,
+    time::{SystemTime, UNIX_EPOCH},
 };
@@
 fn file_timestamp(file: &Path) -> TimestampMs {
     fs::metadata(file)
         .and_then(|metadata| metadata.modified())
         .ok()
         .and_then(|modified| modified.duration_since(UNIX_EPOCH).ok())
         .map(|duration| TimestampMs::from_millis(duration.as_millis().min(i64::MAX as u128) as i64))
-        .unwrap_or(TimestampMs::UNIX_EPOCH)
+        .unwrap_or_else(|| {
+            log::warn!("failed to read mtime for {:?}; falling back to current time", file);
+            SystemTime::now()
+                .duration_since(UNIX_EPOCH)
+                .ok()
+                .map(|duration| {
+                    TimestampMs::from_millis(duration.as_millis().min(i64::MAX as u128) as i64)
+                })
+                .unwrap_or(TimestampMs::UNIX_EPOCH)
+        })
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/ccusage/src/adapter/qwen/parser.rs` around lines 165 - 172,
file_timestamp currently falls back to TimestampMs::UNIX_EPOCH on any
metadata/mtime error which skews time-based aggregations; change the fallback to
use the current system time (SystemTime::now()) converted to TimestampMs instead
and emit a warning when the metadata or modified() call fails. Update the
file_timestamp function to attempt fs::metadata(...).and_then(...).map(...) as
before but on the final unwrap_or call compute TimestampMs from
SystemTime::now(), and use the crate's logging facility (e.g., log::warn! or
tracing::warn!) to log the original error or a short warning message indicating
mtime lookup failed and current time was used.
🧹 Nitpick comments (2)
rust/crates/ccusage/src/output.rs (1)

69-84: ⚡ Quick win

Derive aggregated totalTokens from UsageSummary::total_tokens() directly.

totals_json currently re-implements token total math. Reusing row.total_tokens() avoids drift if token fields evolve again.

♻️ Proposed refactor
 pub(crate) fn totals_json(rows: &[UsageSummary]) -> Value {
     let input = rows.iter().map(|row| row.input_tokens).sum::<u64>();
     let output = rows.iter().map(|row| row.output_tokens).sum::<u64>();
     let cache_create = rows
         .iter()
         .map(|row| row.cache_creation_tokens)
         .sum::<u64>();
     let cache_read = rows.iter().map(|row| row.cache_read_tokens).sum::<u64>();
-    let extra = rows.iter().map(|row| row.extra_total_tokens).sum::<u64>();
+    let total_tokens = rows.iter().map(|row| row.total_tokens()).sum::<u64>();
     let mut value = json!({
         "inputTokens": input,
         "outputTokens": output,
         "cacheCreationTokens": cache_create,
         "cacheReadTokens": cache_read,
-        "totalTokens": input + output + cache_create + cache_read + extra,
+        "totalTokens": total_tokens,
         "totalCost": rows.iter().map(|row| row.total_cost).sum::<f64>(),
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/ccusage/src/output.rs` around lines 69 - 84, In totals_json,
replace the manual aggregation used to set "totalTokens" (currently input +
output + cache_create + cache_read + extra) by summing each row's
UsageSummary::total_tokens() to avoid duplicating token arithmetic; update the
totals_json function to compute totalTokens as rows.iter().map(|row|
row.total_tokens()).sum::<u64>() and remove the duplicated calculation (you can
keep the individual input/output/cache sums for separate fields but derive the
aggregate only from UsageSummary::total_tokens()).
rust/crates/ccusage/src/main.rs (1)

810-810: ⚡ Quick win

Add at least one non-zero extra_total_tokens fixture assertion.

All updated fixtures set extra_total_tokens to 0, so the new total-token aggregation path is not actually exercised.

🧪 Minimal test hardening example
         let entry = LoadedEntry {
             data: UsageEntry {
                 session_id: Some("thread-a".to_string()),
@@
             model: Some("claude-sonnet-4-20250514".to_string()),
             usage_limit_reset_time: None,
-            extra_total_tokens: 0,
+            extra_total_tokens: 5,
         };
@@
-        assert_eq!(report["daily"][0]["totalTokens"], 180);
+        assert_eq!(report["daily"][0]["totalTokens"], 185);

Also applies to: 895-895, 942-942

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/ccusage/src/main.rs` at line 810, One or more test fixtures
currently set extra_total_tokens to 0 so the total-token aggregation path isn't
exercised; pick at least one fixture where extra_total_tokens is assigned (the
fields named extra_total_tokens in the test data) and change it to a non-zero
value (e.g., 1 or 5), then update the corresponding expected aggregated total
assertions to include that extra amount so the aggregation logic (where totals
are computed) is actually validated; ensure you update every related
fixture/assertion pair you modify so tests remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/crates/ccusage/src/adapter/qwen/parser.rs`:
- Around line 152-160: The loop currently treats any cost <= 0.0 as a miss and
keeps trying aliases; change the lookup contract and loop to return the first
successful lookup even if it yields 0.0. Update calculate_cost_for_usage to
return an Option<f64> (or otherwise signal success vs miss), then in the
candidate loop call calculate_cost_for_usage(Some(&candidate), usage, None,
mode, pricing) and if it returns Some(cost) immediately return that cost (do not
gate on cost > 0.0); only continue the loop on None (lookup miss).

---

Outside diff comments:
In `@docs/guide/environment-variables.md`:
- Around line 16-25: The debug grep example is missing QWEN so add QWEN to the
diagnostic filter: update the grep/regex that currently contains
CLAUDE|CODEX|OPENCODE|AMP|PI_AGENT|CCUSAGE|LOG_LEVEL to also include QWEN (and
ensure it will match QWEN_DATA_DIR); locate the debug command in the docs and
modify the pattern string to include the token "QWEN" so QWEN_DATA_DIR appears
in the debug output.

---

Duplicate comments:
In `@rust/crates/ccusage/src/adapter/qwen/mod.rs`:
- Around line 121-145: The test loads_qwen_jsonl_usage_entries (and the second
test at lines 157-179) mutates the global QWEN_DATA_DIR via env::set_var and
then env::remove_var, which can race when tests run in parallel; change both
tests to capture the previous env::var("QWEN_DATA_DIR") state, set the new
value, and install a panic-safe guard (a small RAII Drop guard or use
temp_env/test-env crate) that restores the original value (either by
env::set_var(prev) or env::remove_var if None) when the test scope exits, and
use the same pattern for temp_qwen_dir cleanup so both environment and temp
directory are always cleaned up even on panic.

In `@rust/crates/ccusage/src/adapter/qwen/parser.rs`:
- Around line 174-185: The dedup key built in entry_id can collide because
components like session_id/model/timestamp may contain ":"; update the entry_id
function to produce an unambiguous key by serializing the identifying components
into a single canonical representation (e.g., use serde_json::to_string on a
tuple/array of (entry.session_id, entry.data.timestamp,
entry.model.as_deref().unwrap_or_default(), usage.input_tokens,
usage.output_tokens, usage.cache_read_input_tokens, entry.extra_total_tokens) or
alternatively base64-encode/escape the string components) so separators cannot
collide; modify the entry_id function to construct the key via that
serialization and return the serialized string (referencing entry_id,
LoadedEntry, entry.session_id, entry.data.timestamp, entry.model, usage fields).
- Around line 165-172: file_timestamp currently falls back to
TimestampMs::UNIX_EPOCH on any metadata/mtime error which skews time-based
aggregations; change the fallback to use the current system time
(SystemTime::now()) converted to TimestampMs instead and emit a warning when the
metadata or modified() call fails. Update the file_timestamp function to attempt
fs::metadata(...).and_then(...).map(...) as before but on the final unwrap_or
call compute TimestampMs from SystemTime::now(), and use the crate's logging
facility (e.g., log::warn! or tracing::warn!) to log the original error or a
short warning message indicating mtime lookup failed and current time was used.

---

Nitpick comments:
In `@rust/crates/ccusage/src/main.rs`:
- Line 810: One or more test fixtures currently set extra_total_tokens to 0 so
the total-token aggregation path isn't exercised; pick at least one fixture
where extra_total_tokens is assigned (the fields named extra_total_tokens in the
test data) and change it to a non-zero value (e.g., 1 or 5), then update the
corresponding expected aggregated total assertions to include that extra amount
so the aggregation logic (where totals are computed) is actually validated;
ensure you update every related fixture/assertion pair you modify so tests
remain consistent.

In `@rust/crates/ccusage/src/output.rs`:
- Around line 69-84: In totals_json, replace the manual aggregation used to set
"totalTokens" (currently input + output + cache_create + cache_read + extra) by
summing each row's UsageSummary::total_tokens() to avoid duplicating token
arithmetic; update the totals_json function to compute totalTokens as
rows.iter().map(|row| row.total_tokens()).sum::<u64>() and remove the duplicated
calculation (you can keep the individual input/output/cache sums for separate
fields but derive the aggregate only from UsageSummary::total_tokens()).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5096d0ec-546d-4907-b0fd-a173ba4c8616

📥 Commits

Reviewing files that changed from the base of the PR and between 27047ac and 2bad7fd.

📒 Files selected for processing (33)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • docs/.vitepress/config.ts
  • docs/guide/all-reports.md
  • docs/guide/claude/index.md
  • docs/guide/codex/index.md
  • docs/guide/config-files.md
  • docs/guide/configuration.md
  • docs/guide/daily-reports.md
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/index.md
  • docs/guide/monthly-reports.md
  • docs/guide/qwen/index.md
  • docs/guide/session-reports.md
  • rust/crates/ccusage/src/adapter/all.rs
  • rust/crates/ccusage/src/adapter/amp.rs
  • rust/crates/ccusage/src/adapter/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/parser.rs
  • rust/crates/ccusage/src/adapter/pi.rs
  • rust/crates/ccusage/src/adapter/qwen/mod.rs
  • rust/crates/ccusage/src/adapter/qwen/parser.rs
  • rust/crates/ccusage/src/adapter/qwen/paths.rs
  • rust/crates/ccusage/src/claude_loader.rs
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/main.rs
  • rust/crates/ccusage/src/output.rs
  • rust/crates/ccusage/src/progress.rs
  • rust/crates/ccusage/src/summary.rs
  • rust/crates/ccusage/src/types.rs

Comment thread rust/crates/ccusage/src/adapter/qwen/parser.rs
ryoppippi added 2 commits May 19, 2026 16:03
# Conflicts:
#	apps/ccusage/README.md
#	docs/.vitepress/config.ts
#	docs/guide/all-reports.md
#	docs/guide/claude/index.md
#	docs/guide/codex/index.md
#	docs/guide/config-files.md
#	docs/guide/configuration.md
#	docs/guide/environment-variables.md
#	docs/guide/getting-started.md
#	docs/guide/index.md
#	rust/crates/ccusage/src/adapter/all.rs
#	rust/crates/ccusage/src/cli.rs
#	rust/crates/ccusage/src/config.rs
#	rust/crates/ccusage/src/config_schema.rs
#	rust/crates/ccusage/src/main.rs
#	rust/crates/ccusage/src/progress.rs
#	rust/crates/ccusage/src/types.rs
Keep the generated config schema formatting in sync with the pre-commit generator after merging origin/main into the Qwen adapter branch.
@ryoppippi
Copy link
Copy Markdown
Owner Author

@coderabbitai review

Pushed the Qwen branch after merging the latest origin/main and resolving conflicts against current native Rust all-agent, Copilot/Gemini, config schema, pricing, and docs changes.

Changes in this push:

  • preserved the Qwen Rust adapter and wired it into the current CLI/all-report/config schema/progress code
  • integrated Qwen docs/navigation/environment-variable references with the current Copilot/Gemini docs
  • regenerated and formatted the config schema
  • removed duplicate extra_total_tokens initializers introduced by the old branch/main merge

Local validation:

  • `nix develop --command cargo test --manifest-path rust/Cargo.toml -p ccusage qwen`
  • `nix develop --command pnpm run format`
  • `nix develop --command cargo test --manifest-path rust/Cargo.toml --workspace`
  • `nix develop --command pnpm typecheck`
  • `nix develop --command pnpm run test`

Pre-push validation also passed clippy/oxfmt/eslint hooks.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

@ryoppippi Thanks for the detailed update — rebasing against the latest main with Copilot/Gemini, config schema, and pricing changes is a solid integration step. I'll kick off a fresh full review covering all the updates, including the Rust CLI port, the conflict resolutions, the duplicate extra_total_tokens cleanup, and the previous open items (dedup key separator, epoch fallback, zero-price skip).

✅ Actions performed

Full review triggered.

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: 589fab79f960
Base SHA: 1d2ef9bc75e6

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 1d2ef9bc75e6 503.9ms 554.0ms 34.9ms 3
PR pkg.pr.new 589fab79f960 617.6ms 630.1ms 34.7ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 1d2ef9bc75e6; PR package: 589fab79f960. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 692.0ms 705.6ms 0.98x 1.45 GiB/s 1.43 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 879.0ms 891.8ms 0.99x 1.15 GiB/s 1.13 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 745.7ms 1.35 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 666.9ms 1.51 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 728.6ms 1.38 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 879.9ms 1.14 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 848.0ms 1.19 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 827.3ms 1.22 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 32.6ms 32.8ms 0.99x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 32.3ms 32.7ms 0.99x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 32.3ms 32.5ms 0.99x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 32.4ms 32.9ms 0.98x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 689.6ms 698.3ms 0.99x 1.46 GiB/s 1.44 GiB/s
codex --offline --json 1.01 GiB 883.1ms 884.5ms 1.00x 1.14 GiB/s 1.14 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.15 KiB 9.21 KiB +0.06 KiB 0.99x
installed native package binary 3224.24 KiB 3224.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2891.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@ryoppippi
Copy link
Copy Markdown
Owner Author

@coderabbitai review

Pushed a CI fix for the Qwen branch: restored the generated schema property expectation to the complete latest-main set plus qwen.

Local checks:

  • pnpm run format
  • cargo test --manifest-path rust/Cargo.toml -p ccusage generated_schema_keeps_legacy_root_definition_shape
  • pre-push hook: oxfmt, clippy

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

@ryoppippi Thanks for the CI fix! Restoring the full schema property allowlist to include the latest-main set plus qwen is exactly what's needed for the generated_schema_keeps_legacy_root_definition_shape test. Kicking off a fresh full review to verify the fix and confirm everything is still in order.

✅ Actions performed

Full review triggered.

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: f865c1d65915
Base SHA: 1d2ef9bc75e6

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 1d2ef9bc75e6 527.2ms 435.1ms 34.2ms 3
PR pkg.pr.new f865c1d65915 544.3ms 429.5ms 34.3ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 1d2ef9bc75e6; PR package: f865c1d65915. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 750.7ms 700.7ms 1.07x 1.34 GiB/s 1.44 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 864.5ms 863.4ms 1.00x 1.16 GiB/s 1.17 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 694.2ms 1.45 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 664.5ms 1.52 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 723.3ms 1.39 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 865.3ms 1.16 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 842.0ms 1.20 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 816.3ms 1.23 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 32.1ms 4.7ms 6.81x 0.05 MiB/s 0.33 MiB/s
claude session --offline --json 0.00 MiB 32.0ms 4.9ms 6.49x 0.05 MiB/s 0.31 MiB/s
codex daily --offline --json 0.00 MiB 31.4ms 4.5ms 7.05x 0.03 MiB/s 0.19 MiB/s
codex session --offline --json 0.00 MiB 31.8ms 4.6ms 6.93x 0.03 MiB/s 0.19 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 685.3ms 716.4ms 0.96x 1.47 GiB/s 1.41 GiB/s
codex --offline --json 1.01 GiB 853.6ms 822.4ms 1.04x 1.18 GiB/s 1.22 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.15 KiB 9.21 KiB +0.06 KiB 0.99x
installed native package binary 3224.24 KiB 3224.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2891.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: f865c1d65915
Base SHA: 1d2ef9bc75e6

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 1d2ef9bc75e6 896.3ms 585.7ms 36.8ms 3
PR pkg.pr.new f865c1d65915 840.5ms 587.7ms 38.1ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 1d2ef9bc75e6; PR package: f865c1d65915. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 708.9ms 729.3ms 0.97x 1.42 GiB/s 1.38 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 941.2ms 934.4ms 1.01x 1.07 GiB/s 1.08 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 705.6ms 1.43 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 670.7ms 1.50 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 754.3ms 1.33 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 903.3ms 1.11 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 920.5ms 1.09 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 869.7ms 1.16 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 34.2ms 34.5ms 0.99x 0.05 MiB/s 0.04 MiB/s
claude session --offline --json 0.00 MiB 34.0ms 34.7ms 0.98x 0.05 MiB/s 0.04 MiB/s
codex daily --offline --json 0.00 MiB 33.4ms 33.7ms 0.99x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 33.7ms 33.3ms 1.01x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 700.2ms 711.7ms 0.98x 1.44 GiB/s 1.41 GiB/s
codex --offline --json 1.01 GiB 937.6ms 923.4ms 1.02x 1.07 GiB/s 1.09 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.15 KiB 9.21 KiB +0.06 KiB 0.99x
installed native package binary 3224.24 KiB 3224.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2891.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/crates/ccusage/src/adapter/all.rs (1)

145-152: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix duplicate loader index for Gemini/Kimi ordering.

Line 151 reuses index 10 already assigned on Line 145. That makes Gemini vs Kimi load/error ordering nondeterministic.

🔧 Proposed fix
             AgentLoadSpec {
-                index: 10,
+                index: 11,
                 agent: "kimi",
                 progress_agent: crate::progress::UsageLoadAgent::Kimi,
                 load: Box::new(|| load_kimi_rows(load_kind, shared, &pricing)),
             },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/ccusage/src/adapter/all.rs` around lines 145 - 152, AgentLoadSpec
entries for agent "gemini" and "kimi" currently both use index 10 which breaks
deterministic ordering; update the duplicate index on the "kimi" AgentLoadSpec
to the next unique value (e.g., 11) so ordering/errors are deterministic. Locate
the AgentLoadSpec block that sets agent: "gemini" (uses load: Box::new(||
load_gemini_rows(...))) and the following AgentLoadSpec with agent: "kimi" and
change the index field there to a unique value (match adjacent indices sequence)
so each AgentLoadSpec has a distinct index.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/crates/ccusage/src/adapter/qwen/mod.rs`:
- Around line 105-115: The comparison in filter_session_summaries currently
compares the normalized row date (YYYYMMDD) to raw shared.since/shared.until
which may still be YYYY-MM-DD; update filter_session_summaries (the rows.retain
closure using row.last_activity) to normalize shared.since and shared.until to
the same format as the row date (remove '-' or otherwise produce YYYYMMDD)
before doing the >= / <= comparisons so that valid inputs like "YYYY-MM-DD" are
correctly compared against the row date.

---

Outside diff comments:
In `@rust/crates/ccusage/src/adapter/all.rs`:
- Around line 145-152: AgentLoadSpec entries for agent "gemini" and "kimi"
currently both use index 10 which breaks deterministic ordering; update the
duplicate index on the "kimi" AgentLoadSpec to the next unique value (e.g., 11)
so ordering/errors are deterministic. Locate the AgentLoadSpec block that sets
agent: "gemini" (uses load: Box::new(|| load_gemini_rows(...))) and the
following AgentLoadSpec with agent: "kimi" and change the index field there to a
unique value (match adjacent indices sequence) so each AgentLoadSpec has a
distinct index.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e3c49a2-16ed-4427-a50e-030ff9bc6d70

📥 Commits

Reviewing files that changed from the base of the PR and between 1d2ef9b and 08ff1af.

📒 Files selected for processing (27)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • docs/.vitepress/config.ts
  • docs/guide/all-reports.md
  • docs/guide/claude/index.md
  • docs/guide/codex/index.md
  • docs/guide/config-files.md
  • docs/guide/configuration.md
  • docs/guide/daily-reports.md
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/index.md
  • docs/guide/monthly-reports.md
  • docs/guide/qwen/index.md
  • docs/guide/session-reports.md
  • docs/index.md
  • rust/crates/ccusage/src/adapter/all.rs
  • rust/crates/ccusage/src/adapter/mod.rs
  • rust/crates/ccusage/src/adapter/qwen/mod.rs
  • rust/crates/ccusage/src/adapter/qwen/parser.rs
  • rust/crates/ccusage/src/adapter/qwen/paths.rs
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/main.rs
  • rust/crates/ccusage/src/output.rs
  • rust/crates/ccusage/src/progress.rs

Comment thread rust/crates/ccusage/src/adapter/qwen/mod.rs
@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: 08ff1af9459c
Base SHA: 1d2ef9bc75e6

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 1d2ef9bc75e6 622.2ms 578.6ms 35.5ms 3
PR pkg.pr.new 08ff1af9459c 875.3ms 538.1ms 34.6ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 1d2ef9bc75e6; PR package: 08ff1af9459c. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 692.8ms 710.9ms 0.97x 1.45 GiB/s 1.42 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 896.4ms 889.7ms 1.01x 1.12 GiB/s 1.13 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 687.6ms 1.46 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 664.9ms 1.51 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 709.9ms 1.42 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 867.4ms 1.16 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 839.1ms 1.20 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 820.0ms 1.23 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 33.3ms 5.1ms 6.51x 0.05 MiB/s 0.30 MiB/s
claude session --offline --json 0.00 MiB 33.4ms 5.2ms 6.44x 0.05 MiB/s 0.30 MiB/s
codex daily --offline --json 0.00 MiB 32.9ms 4.8ms 6.91x 0.03 MiB/s 0.18 MiB/s
codex session --offline --json 0.00 MiB 32.4ms 4.9ms 6.67x 0.03 MiB/s 0.18 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 700.1ms 746.3ms 0.94x 1.44 GiB/s 1.35 GiB/s
codex --offline --json 1.01 GiB 896.3ms 834.5ms 1.07x 1.12 GiB/s 1.21 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.15 KiB 9.21 KiB +0.06 KiB 0.99x
installed native package binary 3224.24 KiB 3224.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2891.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: 08ff1af9459c
Base SHA: 1d2ef9bc75e6

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 1d2ef9bc75e6 648.1ms 628.8ms 35.5ms 3
PR pkg.pr.new 08ff1af9459c 704.3ms 575.1ms 36.5ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 1d2ef9bc75e6; PR package: 08ff1af9459c. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 694.9ms 701.8ms 0.99x 1.45 GiB/s 1.43 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 902.8ms 898.3ms 1.00x 1.12 GiB/s 1.12 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 708.1ms 1.42 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 660.5ms 1.52 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 729.9ms 1.38 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 883.7ms 1.14 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 895.6ms 1.12 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 841.7ms 1.20 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 34.0ms 34.3ms 0.99x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 34.0ms 34.0ms 1.00x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 33.6ms 34.0ms 0.99x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 33.6ms 33.9ms 0.99x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 696.6ms 705.6ms 0.99x 1.45 GiB/s 1.43 GiB/s
codex --offline --json 1.01 GiB 898.4ms 903.5ms 0.99x 1.12 GiB/s 1.11 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.15 KiB 9.21 KiB +0.06 KiB 0.99x
installed native package binary 3224.24 KiB 3224.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2891.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

ryoppippi added 2 commits May 19, 2026 19:52
# Conflicts:
#	apps/ccusage/README.md
#	docs/guide/all-reports.md
#	docs/guide/config-files.md
#	docs/guide/configuration.md
#	docs/guide/getting-started.md
#	docs/guide/index.md
#	docs/index.md
#	rust/crates/ccusage/src/adapter/all.rs
#	rust/crates/ccusage/src/cli.rs
#	rust/crates/ccusage/src/config_schema.rs
@ryoppippi
Copy link
Copy Markdown
Owner Author

@coderabbitai review

Synced this Qwen PR with the latest main after #1019 landed, resolved the OpenClaw/Kimi integration conflicts, kept the Qwen env grep/schema fixes, regenerated/formatted the config schema, and pushed the updates.

Local checks run:

  • pnpm --dir apps/ccusage run generate:schema
  • pnpm run format
  • cargo test --manifest-path rust/Cargo.toml -p ccusage qwen
  • cargo test --manifest-path rust/Cargo.toml -p ccusage generated_schema_keeps_legacy_root_definition_shape
  • pre-push hook: oxfmt, docs eslint, clippy

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

@ryoppippi Thanks for the thorough sync after #1019 landed! Resolving the OpenClaw/Kimi integration conflicts while keeping the Qwen env grep/schema fixes and regenerating the config schema is a solid integration step. I'll kick off a fresh full review to verify the conflict resolutions are clean and confirm all previously addressed items (serialized dedupe keys, current-time fallback, zero-price pricing fix, session-date filtering, and the schema property allowlist) are still intact.

✅ Actions performed

Full review triggered.

Normalize since/until bounds before comparing Qwen session summaries so ISO date inputs compare against the YYYYMMDD row key consistently.

Adds a regression test for filtering Qwen session rows with matching ISO date bounds.
@ryoppippi
Copy link
Copy Markdown
Owner Author

@coderabbitai review

Addressed the Qwen session date-filter review thread and pushed a follow-up fix.

Local checks run:

  • pnpm run format
  • cargo test --manifest-path rust/Cargo.toml -p ccusage qwen

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

@ryoppippi Thanks for the follow-up fix on the Qwen session date-filter thread! I'll kick off a fresh full review to verify the fix and confirm all previously addressed items — serialized dedupe keys, current-time timestamp fallback, zero-price pricing fix, and now the session date-filter — are all in good shape.

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
rust/crates/ccusage/src/adapter/all.rs (1)

770-770: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use row.total_tokens for table totals to keep table/JSON consistent.

The table path recomputes totals from input/output/cache fields, which can undercount adapters that provide additional token components via total_tokens. Use row.total_tokens directly for both row and footer totals.

Proposed fix
-    let table_total_tokens = rows.iter().map(table_total_tokens).sum::<u64>();
+    let table_total_tokens = rows.iter().map(|row| row.total_tokens).sum::<u64>();
@@
-        format_number(table_total_tokens(row)),
+        format_number(row.total_tokens),
@@
 fn table_total_tokens(row: &AllRow) -> u64 {
-    row.input_tokens
-        .saturating_add(row.output_tokens)
-        .saturating_add(row.cache_creation_tokens)
-        .saturating_add(row.cache_read_tokens)
+    row.total_tokens
 }

Also applies to: 923-933

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/ccusage/src/adapter/all.rs` at line 770, The table total
calculation currently recomputes totals from input/output/cache fields (using
the table_total_tokens mapper), which can undercount adapters that already
provide a combined total in row.total_tokens; change the table/footer
aggregation to use row.total_tokens directly (i.e., map rows.iter().map(|row|
row.total_tokens).sum::<u64>()) and similarly ensure any footer/summary logic
that sums per-row components (the code referenced by table_total_tokens and the
footer totals logic) uses row.total_tokens so table and JSON outputs remain
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/guide/claude/index.md`:
- Line 3: Update the peer sources lists in the paragraph that begins "ccusage
can read Claude Code usage data..." and the repeated peer list later in the file
to include the missing adapters "Hermes Agent", "Goose", and "OpenClaw" so the
list matches supported namespaces/env-var coverage; ensure both occurrences (the
initial description of supported local data sources and the later peer sources
list) are edited to add these three names in the same style and punctuation as
the existing entries (e.g., after "Kimi" or before "GitHub Copilot CLI") so the
documentation remains consistent.

In `@docs/guide/codex/index.md`:
- Line 7: Update the sentence that lists peer tools for Codex support so it
includes the missing peers; specifically edit the line containing "Codex uses
the same unified and focused report model as Claude Code, OpenCode, Amp,
pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI" to also include
"Hermes Agent, Goose, and OpenClaw" so the peer-source list is complete and
consistent with the support matrix.

In `@rust/crates/ccusage/src/adapter/qwen/parser.rs`:
- Around line 114-116: The TokenUsageRaw construction for billable_usage
currently adds output_tokens and reasoning_tokens directly which can overflow;
change the arithmetic to use saturating_add() when computing output_tokens
(e.g., set output_tokens: output_tokens.saturating_add(reasoning_tokens)) and
keep the rest of the struct spread from display_usage so the resulting
TokenUsageRaw uses saturating addition like the pattern in goose.rs to avoid
integer wraparound.

---

Duplicate comments:
In `@rust/crates/ccusage/src/adapter/all.rs`:
- Line 770: The table total calculation currently recomputes totals from
input/output/cache fields (using the table_total_tokens mapper), which can
undercount adapters that already provide a combined total in row.total_tokens;
change the table/footer aggregation to use row.total_tokens directly (i.e., map
rows.iter().map(|row| row.total_tokens).sum::<u64>()) and similarly ensure any
footer/summary logic that sums per-row components (the code referenced by
table_total_tokens and the footer totals logic) uses row.total_tokens so table
and JSON outputs remain consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c723558-5c7a-4426-ab49-498aec56fbdb

📥 Commits

Reviewing files that changed from the base of the PR and between 687be19 and d1e218c.

📒 Files selected for processing (27)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • docs/.vitepress/config.ts
  • docs/guide/all-reports.md
  • docs/guide/claude/index.md
  • docs/guide/codex/index.md
  • docs/guide/config-files.md
  • docs/guide/configuration.md
  • docs/guide/daily-reports.md
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/index.md
  • docs/guide/monthly-reports.md
  • docs/guide/qwen/index.md
  • docs/guide/session-reports.md
  • docs/index.md
  • rust/crates/ccusage/src/adapter/all.rs
  • rust/crates/ccusage/src/adapter/mod.rs
  • rust/crates/ccusage/src/adapter/qwen/mod.rs
  • rust/crates/ccusage/src/adapter/qwen/parser.rs
  • rust/crates/ccusage/src/adapter/qwen/paths.rs
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/main.rs
  • rust/crates/ccusage/src/output.rs
  • rust/crates/ccusage/src/progress.rs

Comment thread docs/guide/claude/index.md Outdated
Comment thread docs/guide/codex/index.md Outdated
Comment thread rust/crates/ccusage/src/adapter/qwen/parser.rs
@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: d1e218ca2dbf
Base SHA: 687be1906691

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 687be1906691 480.7ms 577.8ms 35.4ms 3
PR pkg.pr.new d1e218ca2dbf 730.5ms 829.1ms 36.8ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 687be1906691; PR package: d1e218ca2dbf. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 698.1ms 703.6ms 0.99x 1.44 GiB/s 1.43 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 875.0ms 873.5ms 1.00x 1.15 GiB/s 1.15 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 686.1ms 1.47 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 655.1ms 1.54 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 730.2ms 1.38 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 921.8ms 1.09 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 835.4ms 1.21 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 831.2ms 1.21 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 33.0ms 33.4ms 0.99x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 34.0ms 33.4ms 1.02x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 32.6ms 32.6ms 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 32.7ms 32.9ms 0.99x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 695.6ms 693.0ms 1.00x 1.45 GiB/s 1.45 GiB/s
codex --offline --json 1.01 GiB 866.5ms 870.1ms 1.00x 1.16 GiB/s 1.16 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.29 KiB 9.34 KiB +0.05 KiB 0.99x
installed native package binary 3224.24 KiB 3224.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2891.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: d1e218ca2dbf
Base SHA: 687be1906691

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 687be1906691 389.8ms 468.7ms 34.3ms 3
PR pkg.pr.new d1e218ca2dbf 553.8ms 579.0ms 33.6ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 687be1906691; PR package: d1e218ca2dbf. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 687.0ms 686.8ms 1.00x 1.47 GiB/s 1.47 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 857.7ms 840.5ms 1.02x 1.17 GiB/s 1.20 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 683.7ms 1.47 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 649.4ms 1.55 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 723.0ms 1.39 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 836.1ms 1.20 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 822.5ms 1.22 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 812.3ms 1.24 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 32.2ms 4.8ms 6.77x 0.05 MiB/s 0.32 MiB/s
claude session --offline --json 0.00 MiB 32.3ms 4.8ms 6.69x 0.05 MiB/s 0.32 MiB/s
codex daily --offline --json 0.00 MiB 31.5ms 4.4ms 7.15x 0.03 MiB/s 0.19 MiB/s
codex session --offline --json 0.00 MiB 31.5ms 4.3ms 7.28x 0.03 MiB/s 0.20 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 686.9ms 722.6ms 0.95x 1.47 GiB/s 1.39 GiB/s
codex --offline --json 1.01 GiB 855.8ms 819.7ms 1.04x 1.18 GiB/s 1.23 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.29 KiB 9.34 KiB +0.05 KiB 0.99x
installed native package binary 3224.24 KiB 3224.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2891.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
docs/guide/claude/index.md (1)

3-3: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Complete the source lists to avoid excluding supported adapters.

Lines 3 and 78 still omit Hermes Agent, Goose, and OpenClaw from the peer source lists, which can mislead readers about supported namespaces.

📝 Suggested doc fix
-ccusage can read Claude Code usage data as one of its supported local data sources. Claude Code is no longer treated as the only ccusage target; it uses the same unified and focused report model as Codex, OpenCode, Amp, pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI.
+ccusage can read Claude Code usage data as one of its supported local data sources. Claude Code is no longer treated as the only ccusage target; it uses the same unified and focused report model as Codex, OpenCode, Amp, Hermes Agent, pi-agent, Goose, OpenClaw, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI.

Also applies to Line 78.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/guide/claude/index.md` at line 3, Update the peer source lists in the
Claude guide where the sentence starting "ccusage can read Claude Code usage
data..." (and the repeated peer-source list later in the document) omits
supported adapters; add "Hermes Agent", "Goose", and "OpenClaw" to those
comma-separated source lists so the examples list all supported namespaces
consistently (match the style and punctuation used for existing entries like
Codex, OpenCode, Amp, pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini
CLI).
docs/guide/codex/index.md (1)

7-7: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the peer-source list complete for consistency.

Line 7 still omits Hermes Agent, Goose, and OpenClaw from the peer source list, which makes the support matrix appear narrower than it is.

📝 Suggested doc fix
-ccusage can read OpenAI Codex CLI session logs as one of its supported local data sources. Codex uses the same unified and focused report model as Claude Code, OpenCode, Amp, pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI.
+ccusage can read OpenAI Codex CLI session logs as one of its supported local data sources. Codex uses the same unified and focused report model as Claude Code, OpenCode, Amp, Hermes Agent, pi-agent, Goose, OpenClaw, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/guide/codex/index.md` at line 7, Update the peer-source list in the
sentence that starts "ccusage can read OpenAI Codex CLI session logs..." (the
line listing "Claude Code, OpenCode, Amp, pi-agent, Kilo, Kimi, Qwen, GitHub
Copilot CLI, and Gemini CLI") to also include "Hermes Agent", "Goose", and
"OpenClaw" so the supported local data sources list is complete and consistent
with the support matrix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@docs/guide/claude/index.md`:
- Line 3: Update the peer source lists in the Claude guide where the sentence
starting "ccusage can read Claude Code usage data..." (and the repeated
peer-source list later in the document) omits supported adapters; add "Hermes
Agent", "Goose", and "OpenClaw" to those comma-separated source lists so the
examples list all supported namespaces consistently (match the style and
punctuation used for existing entries like Codex, OpenCode, Amp, pi-agent, Kilo,
Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI).

In `@docs/guide/codex/index.md`:
- Line 7: Update the peer-source list in the sentence that starts "ccusage can
read OpenAI Codex CLI session logs..." (the line listing "Claude Code, OpenCode,
Amp, pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI") to also
include "Hermes Agent", "Goose", and "OpenClaw" so the supported local data
sources list is complete and consistent with the support matrix.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2f9b91f9-a340-48db-917a-47e8d055d129

📥 Commits

Reviewing files that changed from the base of the PR and between 687be19 and 6687e7b.

📒 Files selected for processing (27)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • docs/.vitepress/config.ts
  • docs/guide/all-reports.md
  • docs/guide/claude/index.md
  • docs/guide/codex/index.md
  • docs/guide/config-files.md
  • docs/guide/configuration.md
  • docs/guide/daily-reports.md
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/index.md
  • docs/guide/monthly-reports.md
  • docs/guide/qwen/index.md
  • docs/guide/session-reports.md
  • docs/index.md
  • rust/crates/ccusage/src/adapter/all.rs
  • rust/crates/ccusage/src/adapter/mod.rs
  • rust/crates/ccusage/src/adapter/qwen/mod.rs
  • rust/crates/ccusage/src/adapter/qwen/parser.rs
  • rust/crates/ccusage/src/adapter/qwen/paths.rs
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/main.rs
  • rust/crates/ccusage/src/output.rs
  • rust/crates/ccusage/src/progress.rs

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: 6687e7b8a871
Base SHA: 687be1906691

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 687be1906691 409.8ms 524.3ms 34.0ms 3
PR pkg.pr.new 6687e7b8a871 825.4ms 716.1ms 35.2ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 687be1906691; PR package: 6687e7b8a871. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 684.8ms 699.2ms 0.98x 1.47 GiB/s 1.44 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 882.6ms 897.8ms 0.98x 1.14 GiB/s 1.12 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 680.2ms 1.48 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 655.4ms 1.54 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 726.1ms 1.39 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 877.8ms 1.15 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 850.3ms 1.18 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 837.6ms 1.20 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 33.4ms 5.0ms 6.62x 0.05 MiB/s 0.31 MiB/s
claude session --offline --json 0.00 MiB 32.8ms 5.0ms 6.53x 0.05 MiB/s 0.31 MiB/s
codex daily --offline --json 0.00 MiB 32.3ms 4.6ms 7.05x 0.03 MiB/s 0.19 MiB/s
codex session --offline --json 0.00 MiB 32.1ms 4.6ms 6.91x 0.03 MiB/s 0.18 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 688.6ms 716.0ms 0.96x 1.46 GiB/s 1.41 GiB/s
codex --offline --json 1.01 GiB 899.0ms 826.8ms 1.09x 1.12 GiB/s 1.22 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.29 KiB 9.34 KiB +0.05 KiB 0.99x
installed native package binary 3224.24 KiB 3224.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2891.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: 6687e7b8a871
Base SHA: 687be1906691

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 687be1906691 1.100s 516.3ms 34.9ms 3
PR pkg.pr.new 6687e7b8a871 664.9ms 584.0ms 33.4ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 687be1906691; PR package: 6687e7b8a871. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 675.6ms 680.5ms 0.99x 1.49 GiB/s 1.48 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 851.4ms 849.5ms 1.00x 1.18 GiB/s 1.19 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 678.0ms 1.48 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 648.9ms 1.55 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 707.8ms 1.42 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 847.1ms 1.19 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 816.3ms 1.23 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 851.5ms 1.18 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 32.3ms 32.0ms 1.01x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 32.4ms 31.9ms 1.01x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 31.9ms 32.2ms 0.99x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 32.4ms 32.1ms 1.01x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 678.7ms 685.0ms 0.99x 1.48 GiB/s 1.47 GiB/s
codex --offline --json 1.01 GiB 916.1ms 871.0ms 1.05x 1.10 GiB/s 1.16 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.29 KiB 9.34 KiB +0.05 KiB 0.99x
installed native package binary 3224.24 KiB 3224.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2891.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

ryoppippi added 2 commits May 19, 2026 20:23
# Conflicts:
#	apps/ccusage/README.md
#	docs/guide/all-reports.md
#	docs/guide/claude/index.md
#	docs/guide/codex/index.md
#	docs/guide/config-files.md
#	docs/guide/configuration.md
#	docs/guide/environment-variables.md
#	docs/guide/index.md
#	docs/index.md
#	rust/crates/ccusage/src/adapter/all.rs
#	rust/crates/ccusage/src/cli.rs
#	rust/crates/ccusage/src/config_schema.rs
Remove the unused duplicate total_tokens binding exposed by the latest main merge so clippy passes with -D warnings.
@ryoppippi
Copy link
Copy Markdown
Owner Author

@coderabbitai review

Synced this Qwen PR again after #1028 and #1029 landed, resolved the Droid/Codebuff/OpenClaw/Kimi integration conflicts, addressed the latest CodeRabbit doc/parser threads, regenerated/formatted schema output, and pushed the updates.

Local checks run:

  • pnpm --dir apps/ccusage run generate:schema
  • pnpm run format
  • cargo test --manifest-path rust/Cargo.toml -p ccusage qwen
  • cargo test --manifest-path rust/Cargo.toml -p ccusage generated_schema_keeps_legacy_root_definition_shape
  • cargo clippy --manifest-path rust/Cargo.toml -p ccusage --all-targets -- -D warnings

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

@ryoppippi Thanks for the thorough sync after #1028 and #1029 landed! Resolving the Droid/Codebuff/OpenClaw/Kimi conflicts while keeping all the Qwen fixes intact, addressing the latest doc/parser threads, and regenerating the schema all looks like solid integration work. The local checks — especially cargo test qwen, generated_schema_keeps_legacy_root_definition_shape, and clippy — are exactly the right set to run after a merge like this. Kicking off a fresh full review now!

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
rust/crates/ccusage/src/adapter/all.rs (1)

491-508: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use row.total_tokens in table totals to keep all-source table and JSON consistent.

With Qwen rows, summary.total_tokens() can include extra totals, but non-compact table output still recomputes totals from input/output/cache (Line 815 and Line 973), which can underreport displayed totals.

💡 Minimal fix
-    let table_total_tokens = rows.iter().map(table_total_tokens).sum::<u64>();
+    let table_total_tokens = rows.iter().map(|row| row.total_tokens).sum::<u64>();
@@
-        format_number(table_total_tokens(row)),
+        format_number(row.total_tokens),
@@
 fn table_total_tokens(row: &AllRow) -> u64 {
-    row.input_tokens
-        .saturating_add(row.output_tokens)
-        .saturating_add(row.cache_creation_tokens)
-        .saturating_add(row.cache_read_tokens)
+    row.total_tokens
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/ccusage/src/adapter/all.rs` around lines 491 - 508, Some table
totals recompute tokens from input/output/cache and underreport Qwen totals;
update the table/JSON totals logic to use each row's precomputed total
(row.total_tokens) instead of summing input/output/cache so it matches
summary.total_tokens(), e.g. change the totals accumulation in the table
rendering code (the code path that builds rows via summary_rows and later
computes totals for non-compact output) to use row.total_tokens for the tokens
column and overall totals.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@rust/crates/ccusage/src/adapter/all.rs`:
- Around line 491-508: Some table totals recompute tokens from
input/output/cache and underreport Qwen totals; update the table/JSON totals
logic to use each row's precomputed total (row.total_tokens) instead of summing
input/output/cache so it matches summary.total_tokens(), e.g. change the totals
accumulation in the table rendering code (the code path that builds rows via
summary_rows and later computes totals for non-compact output) to use
row.total_tokens for the tokens column and overall totals.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b728da17-1643-4130-9685-56b39e6e0e81

📥 Commits

Reviewing files that changed from the base of the PR and between 2cc4dfe and b957810.

📒 Files selected for processing (27)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • docs/.vitepress/config.ts
  • docs/guide/all-reports.md
  • docs/guide/claude/index.md
  • docs/guide/codex/index.md
  • docs/guide/config-files.md
  • docs/guide/configuration.md
  • docs/guide/daily-reports.md
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/index.md
  • docs/guide/monthly-reports.md
  • docs/guide/qwen/index.md
  • docs/guide/session-reports.md
  • docs/index.md
  • rust/crates/ccusage/src/adapter/all.rs
  • rust/crates/ccusage/src/adapter/mod.rs
  • rust/crates/ccusage/src/adapter/qwen/mod.rs
  • rust/crates/ccusage/src/adapter/qwen/parser.rs
  • rust/crates/ccusage/src/adapter/qwen/paths.rs
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/main.rs
  • rust/crates/ccusage/src/output.rs
  • rust/crates/ccusage/src/progress.rs

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: b9578102856b
Base SHA: 2cc4dfebcf1d

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 2cc4dfebcf1d 1.086s 551.6ms 33.8ms 3
PR pkg.pr.new b9578102856b 985.5ms 453.4ms 31.8ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 2cc4dfebcf1d; PR package: b9578102856b. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 689.3ms 680.7ms 1.01x 1.46 GiB/s 1.48 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 804.4ms 842.3ms 0.96x 1.25 GiB/s 1.20 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 682.0ms 1.48 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 645.6ms 1.56 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 721.3ms 1.40 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 839.4ms 1.20 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 811.4ms 1.24 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 809.6ms 1.24 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 30.4ms 4.7ms 6.52x 0.05 MiB/s 0.33 MiB/s
claude session --offline --json 0.00 MiB 30.3ms 4.5ms 6.68x 0.05 MiB/s 0.34 MiB/s
codex daily --offline --json 0.00 MiB 29.3ms 4.1ms 7.07x 0.03 MiB/s 0.21 MiB/s
codex session --offline --json 0.00 MiB 29.6ms 4.2ms 7.00x 0.03 MiB/s 0.20 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 694.8ms 721.8ms 0.96x 1.45 GiB/s 1.39 GiB/s
codex --offline --json 1.01 GiB 833.9ms 827.3ms 1.01x 1.21 GiB/s 1.22 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.38 KiB 9.45 KiB +0.07 KiB 0.99x
installed native package binary 3224.24 KiB 3288.24 KiB +64.00 KiB 0.98x
Rust release binary rust/target/release/ccusage - 2955.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: b9578102856b
Base SHA: 2cc4dfebcf1d

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 2cc4dfebcf1d 595.6ms 595.0ms 31.7ms 3
PR pkg.pr.new b9578102856b 739.0ms 618.7ms 31.2ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 2cc4dfebcf1d; PR package: b9578102856b. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 682.0ms 680.8ms 1.00x 1.48 GiB/s 1.48 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 834.7ms 814.4ms 1.02x 1.21 GiB/s 1.24 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 669.0ms 1.51 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 644.9ms 1.56 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 701.3ms 1.44 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 818.8ms 1.23 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 780.5ms 1.29 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 800.1ms 1.26 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 29.3ms 29.4ms 1.00x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 29.9ms 29.7ms 1.01x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 29.5ms 29.2ms 1.01x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 29.4ms 29.2ms 1.01x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 681.6ms 678.1ms 1.01x 1.48 GiB/s 1.48 GiB/s
codex --offline --json 1.01 GiB 814.1ms 803.5ms 1.01x 1.24 GiB/s 1.25 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.38 KiB 9.45 KiB +0.07 KiB 0.99x
installed native package binary 3224.24 KiB 3288.24 KiB +64.00 KiB 0.98x
Rust release binary rust/target/release/ccusage - 2955.68 KiB - -

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@ryoppippi ryoppippi merged commit 7e20024 into main May 19, 2026
38 checks passed
@ryoppippi ryoppippi deleted the codex/qwen-usage-adapter branch May 19, 2026 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant