Skip to content

Commit b882861

Browse files
committed
Benchmark upstream binary directly
1 parent ad8889a commit b882861

2 files changed

Lines changed: 53 additions & 11 deletions

File tree

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,21 @@ Run the local benchmark helper against your own Codex archive:
146146
npm run benchmark -- --since 2026-05-01 --upstream-timeout 25 --cdxusage-timeout 90
147147
```
148148

149+
The helper resolves `@ccusage/codex@latest` and times the actual
150+
`ccusage-codex` binary so RAM reflects the scanner, not the `npx` wrapper.
151+
149152
Recent local sanity check on a large Codex history:
150153

151154
| Tool | Scenario | Time | RAM | Result |
152155
| --- | --- | ---: | ---: | --- |
153-
| `@ccusage/codex@18.0.11` | `--since 2026-05-01`, 25s limit | `>25.03s` | `0.10 GB` before timeout | timed out |
154-
| `cdxusage` | same filter, cold full scan | `36.90s` | `0.32 GB` | complete |
155-
| `cdxusage` | same filter, warm cached | `0.46s` | `0.14 GB` | complete |
156+
| `@ccusage/codex@18.0.11` | `--since 2026-05-01`, 45s limit | `>45.03s` | `2.38 GB` before timeout | timed out |
157+
| `cdxusage` | same filter, cold full scan | `31.61s` | `0.37 GB` | complete |
158+
| `cdxusage` | same filter, warm cached | `0.41s` | `0.16 GB` | complete |
156159

157160
Cold scans read every matching JSONL file for correctness, including resumed
158161
long-lived sessions whose recent events may live in older session files. After
159162
the cache is built, the same report is dramatically faster: in this run, the
160-
warm cached path was at least 98.2% faster than the upstream timeout window.
163+
warm cached path was at least 99.1% faster than the upstream timeout window.
161164

162165
The timeout keeps the upstream run from reaching its worst failure mode. The
163166
upstream path reads and sorts a large archive-shaped set of token events in

scripts/benchmark-local.mjs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22
import { spawn } from 'node:child_process';
3-
import { mkdtemp, rm } from 'node:fs/promises';
3+
import { mkdtemp, realpath, rm } from 'node:fs/promises';
44
import { tmpdir } from 'node:os';
55
import path from 'node:path';
66
import { fileURLToPath } from 'node:url';
@@ -36,13 +36,14 @@ try {
3636
pricingCacheFile,
3737
];
3838
const rows = [];
39+
const upstream = await resolveUpstreamBinary();
3940
rows.push(await measure('cdxusage cold', process.execPath, [...commonCdxArgs, '--clear-cache'], cdxusageTimeoutSeconds));
4041
rows.push(await measure('cdxusage warm', process.execPath, commonCdxArgs, cdxusageTimeoutSeconds));
4142
rows.push(
4243
await measure(
43-
'upstream latest',
44-
'npx',
45-
['-y', '@ccusage/codex@latest', 'monthly', '--since', since, '--json'],
44+
`@ccusage/codex@${upstream.version}`,
45+
upstream.binPath,
46+
['monthly', '--since', since, '--json'],
4647
upstreamTimeoutSeconds,
4748
),
4849
);
@@ -95,9 +96,39 @@ async function measure(label, command, commandArgs, timeoutSeconds) {
9596
};
9697
}
9798

98-
function run(command, args, timeoutSeconds) {
99+
async function resolveUpstreamBinary() {
100+
const versionResult = await run('npm', ['view', '@ccusage/codex@latest', 'version'], 60, { captureStdout: true });
101+
if (versionResult.status !== 0 || !versionResult.stdout.trim()) {
102+
throw new Error(`failed to resolve @ccusage/codex@latest version: ${versionResult.stderr.trim() || `exit ${versionResult.status}`}`);
103+
}
104+
const pathResult = await run(
105+
'npm',
106+
process.platform === 'win32'
107+
? ['exec', '--yes', '--package', '@ccusage/codex@latest', '--', 'cmd', '/d', '/s', '/c', 'where ccusage-codex']
108+
: ['exec', '--yes', '--package', '@ccusage/codex@latest', '--', 'sh', '-c', 'command -v ccusage-codex'],
109+
60,
110+
{ captureStdout: true },
111+
);
112+
if (pathResult.status !== 0 || !pathResult.stdout.trim()) {
113+
throw new Error(`failed to resolve @ccusage/codex@latest binary: ${pathResult.stderr.trim() || `exit ${pathResult.status}`}`);
114+
}
115+
const binPath = pathResult.stdout
116+
.trim()
117+
.split(/\r?\n/)
118+
.find((line) => line.trim());
119+
if (!binPath) {
120+
throw new Error(`failed to parse @ccusage/codex binary path: ${pathResult.stdout.trim()}`);
121+
}
122+
return {
123+
version: versionResult.stdout.trim().split(/\r?\n/).at(-1),
124+
binPath: process.platform === 'win32' ? binPath : await realpath(binPath),
125+
};
126+
}
127+
128+
function run(command, args, timeoutSeconds, options = {}) {
99129
return new Promise((resolve) => {
100-
const child = spawn(command, args, { cwd: repoRoot, stdio: ['ignore', 'ignore', 'pipe'] });
130+
const child = spawn(command, args, { cwd: repoRoot, stdio: ['ignore', options.captureStdout ? 'pipe' : 'ignore', 'pipe'] });
131+
let stdout = '';
101132
let stderr = '';
102133
let timedOut = false;
103134
const timer = setTimeout(() => {
@@ -106,13 +137,21 @@ function run(command, args, timeoutSeconds) {
106137
setTimeout(() => child.kill('SIGKILL'), 1_000).unref();
107138
}, Math.ceil(timeoutSeconds * 1000) + 500);
108139
timer.unref();
140+
child.stdout?.setEncoding('utf8');
141+
child.stdout?.on('data', (chunk) => {
142+
stdout += chunk;
143+
});
109144
child.stderr.setEncoding('utf8');
110145
child.stderr.on('data', (chunk) => {
111146
stderr += chunk;
112147
});
148+
child.on('error', (error) => {
149+
clearTimeout(timer);
150+
resolve({ status: 127, signal: null, stdout, stderr: `${stderr}${error.message}`, timedOut });
151+
});
113152
child.on('close', (status, signal) => {
114153
clearTimeout(timer);
115-
resolve({ status, signal, stderr, timedOut });
154+
resolve({ status, signal, stdout, stderr, timedOut });
116155
});
117156
});
118157
}

0 commit comments

Comments
 (0)