mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* Verify pinned transcript reads and stream Codex usage aggregation * Resume Claude usage scans with compact token checkpoints * Harden transcript snapshots and reject incomplete discovery * Keep Codex race fixtures inside their temporary data home * Verify pinned transcript rotation with portable file moves * test: keep Claude usage mock registration in test suites * Compress saved Claude usage without dropping history * Keep Claude cache writes bound to their original request * Register Claude contracts without conflicting with chat contracts * Assert unchanged Claude sources skip the actual cache writer
400 lines
14 KiB
JavaScript
400 lines
14 KiB
JavaScript
#!/usr/bin/env node
|
|
// --compare counterbalances HEAD/current; --verify checks current byte/state budgets.
|
|
import assert from 'node:assert/strict'
|
|
import { appendFile, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
import { cpus, release, tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { buildCounterbalancedSchedule } from './counterbalanced-benchmark-schedule.mjs'
|
|
import {
|
|
describeClaudeUsageBenchmarkTooling,
|
|
loadClaudeUsageBenchmarkScanner,
|
|
measureClaudeUsageBenchmarkScan as measure,
|
|
readClaudeUsageBenchmarkBaselineSources,
|
|
verifyClaudeUsageBenchmarkReadAccounting
|
|
} from './claude-usage-benchmark-scanner.mjs'
|
|
|
|
const ROUNDS = Number(process.env.ORCA_CLAUDE_USAGE_APPEND_BENCH_ROUNDS ?? '8')
|
|
assert(Number.isSafeInteger(ROUNDS) && ROUNDS > 0, 'benchmark rounds must be a positive integer')
|
|
const VERIFY = process.argv.includes('--verify')
|
|
const BASELINE = process.argv.includes('--baseline')
|
|
const COMPARE = process.argv.includes('--compare')
|
|
assert(!(VERIFY && BASELINE), '--verify and --baseline cannot be combined')
|
|
assert(!(COMPARE && BASELINE), '--compare and --baseline cannot be combined')
|
|
assert(!COMPARE || ROUNDS % 2 === 0, '--compare requires an even round count')
|
|
const TURN_COUNT = 256
|
|
const NEW_TURN_COUNT = 8
|
|
const MIB = 1024 * 1024
|
|
|
|
function usageRecord(cwd, index, inputTokens = 100, cacheReadTokens = 0) {
|
|
return `${JSON.stringify({
|
|
type: 'assistant',
|
|
sessionId: 'active-session',
|
|
timestamp: new Date(Date.UTC(2026, 9, 9, 12, 0, index)).toISOString(),
|
|
requestId: `request-${index}`,
|
|
cwd,
|
|
gitBranch: 'benchmark',
|
|
message: {
|
|
id: `message-${index}`,
|
|
model: 'claude-sonnet-4-6',
|
|
usage: {
|
|
input_tokens: inputTokens,
|
|
output_tokens: 10,
|
|
cache_read_input_tokens: cacheReadTokens,
|
|
cache_creation_input_tokens: 20,
|
|
cache_creation: { ephemeral_1h_input_tokens: 5 }
|
|
}
|
|
}
|
|
})}\n`
|
|
}
|
|
|
|
function userRecord(content) {
|
|
return `${JSON.stringify({
|
|
type: 'user',
|
|
message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'tool', content }] }
|
|
})}\n`
|
|
}
|
|
|
|
function createHistory(cwd, toolMiB, turnCount) {
|
|
const piece = '工具結果 🌊 benchmark tool output\n'
|
|
const tool = userRecord(piece.repeat(Math.ceil((2 * MIB) / Buffer.byteLength(piece))))
|
|
const rows = Array.from({ length: toolMiB / 2 }, () => tool)
|
|
for (let index = 0; index < turnCount; index++) {
|
|
rows.push(usageRecord(cwd, index))
|
|
if (index % 7 === 0) {
|
|
rows.push(usageRecord(cwd, index, 150))
|
|
}
|
|
}
|
|
return { content: rows.join(''), lineCount: rows.length }
|
|
}
|
|
|
|
function createAppend(cwd, turnCount) {
|
|
const rows = [userRecord('新しい入力 🐳'), usageRecord(cwd, 7, 2_000, 60)]
|
|
for (let index = turnCount; index < turnCount + NEW_TURN_COUNT; index++) {
|
|
rows.push(usageRecord(cwd, index))
|
|
}
|
|
rows.push(usageRecord(cwd, turnCount, 225, 40))
|
|
return { content: rows.join(''), lineCount: rows.length }
|
|
}
|
|
|
|
function median(values) {
|
|
const sorted = [...values].sort((left, right) => left - right)
|
|
const middle = Math.floor(sorted.length / 2)
|
|
return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2
|
|
}
|
|
|
|
function summarize(samples) {
|
|
return Object.fromEntries(
|
|
Object.keys(samples[0]).map((key) => [
|
|
key,
|
|
Number(median(samples.map((sample) => sample[key])).toFixed(3))
|
|
])
|
|
)
|
|
}
|
|
|
|
function measurementSchedule(arms) {
|
|
const names = Object.keys(arms)
|
|
return names.length === 2
|
|
? buildCounterbalancedSchedule(ROUNDS, names[0], names[1])
|
|
: Array.from({ length: ROUNDS }, () => names)
|
|
}
|
|
|
|
function behaviorSnapshot(result) {
|
|
return {
|
|
sessions: result.sessions,
|
|
dailyAggregates: result.dailyAggregates,
|
|
processedFiles: result.processedFiles.map((file) => ({
|
|
path: file.path,
|
|
size: file.size,
|
|
lineCount: file.lineCount,
|
|
sessions: file.sessions,
|
|
dailyAggregates: file.dailyAggregates,
|
|
ownedDedupeKeys: file.ownedDedupeKeys,
|
|
hasDeferredClaims: file.hasDeferredClaims
|
|
}))
|
|
}
|
|
}
|
|
|
|
function recordRound(samples, measurements, arm, round, order, reference) {
|
|
const behavior = Object.fromEntries(
|
|
Object.entries(measurements).map(([phase, measurement]) => [
|
|
phase,
|
|
behaviorSnapshot(measurement.result)
|
|
])
|
|
)
|
|
if (reference) {
|
|
assert.deepEqual(behavior, reference, `cross-arm behavior differs in round ${round + 1}`)
|
|
}
|
|
samples.push({
|
|
arm,
|
|
round: round + 1,
|
|
order,
|
|
phases: Object.fromEntries(
|
|
Object.entries(measurements).map(([phase, measurement]) => [phase, measurement.metrics])
|
|
)
|
|
})
|
|
return behavior
|
|
}
|
|
|
|
function finishSamples(samples) {
|
|
return Object.fromEntries(
|
|
[...new Set(samples.map((sample) => sample.arm))].map((arm) => {
|
|
const rawSamples = samples.filter((sample) => sample.arm === arm)
|
|
return [
|
|
arm,
|
|
{
|
|
phases: Object.fromEntries(
|
|
Object.keys(rawSamples[0].phases).map((phase) => [
|
|
phase,
|
|
summarize(rawSamples.map((sample) => sample.phases[phase]))
|
|
])
|
|
),
|
|
rawSamples
|
|
}
|
|
]
|
|
})
|
|
)
|
|
}
|
|
|
|
async function runScenario(arms, transcriptPath, cwd, toolMiB, turnCount = TURN_COUNT) {
|
|
const history = createHistory(cwd, toolMiB, turnCount)
|
|
const append = createAppend(cwd, turnCount)
|
|
const historyBytes = Buffer.byteLength(history.content)
|
|
const appendBytes = Buffer.byteLength(append.content)
|
|
const samples = []
|
|
for (const [round, order] of measurementSchedule(arms).entries()) {
|
|
let reference = null
|
|
for (const arm of order) {
|
|
const scanner = arms[arm].scanner
|
|
await writeFile(transcriptPath, history.content)
|
|
const cold = await measure(scanner, transcriptPath)
|
|
const warm = await measure(scanner, transcriptPath, cold.cache)
|
|
assert.deepEqual(warm.result, cold.result, 'warm scan differs from a fresh scan')
|
|
assert.equal(cold.result.sessions[0]?.turnCount, turnCount)
|
|
assert.equal(cold.result.processedFiles[0]?.lineCount, history.lineCount)
|
|
assert(cold.metrics.bytesRead >= historyBytes, 'cold scan skipped transcript bytes')
|
|
assert.equal(warm.metrics.bytesRead, 0, 'unchanged scan reread transcript bytes')
|
|
|
|
await appendFile(transcriptPath, append.content)
|
|
const appended = await measure(scanner, transcriptPath, warm.cache)
|
|
const fresh = await measure(scanner, transcriptPath)
|
|
assert.deepEqual(appended.result, fresh.result, 'append scan differs from a fresh scan')
|
|
assert.equal(appended.result.sessions[0]?.turnCount, turnCount + NEW_TURN_COUNT)
|
|
assert.equal(
|
|
appended.result.sessions[0]?.totalInputTokens,
|
|
turnCount * 100 + Math.ceil(turnCount / 7) * 50 + 1_850 + NEW_TURN_COUNT * 100 + 125
|
|
)
|
|
assert.equal(appended.result.sessions[0]?.totalCacheReadTokens, 100)
|
|
assert.equal(
|
|
appended.result.dailyAggregates.reduce(
|
|
(sum, daily) => sum + daily.zeroCacheReadTurnCount,
|
|
0
|
|
),
|
|
turnCount + NEW_TURN_COUNT - 2
|
|
)
|
|
assert.equal(
|
|
appended.result.processedFiles[0]?.lineCount,
|
|
history.lineCount + append.lineCount
|
|
)
|
|
if (VERIFY && arm === 'current') {
|
|
assert(
|
|
appended.metrics.bytesRead <= appendBytes + 64 * 1024,
|
|
'append reread transcript history'
|
|
)
|
|
}
|
|
reference = recordRound(
|
|
samples,
|
|
{ cold, warm, append: appended, freshAfterAppend: fresh },
|
|
arm,
|
|
round,
|
|
order,
|
|
reference
|
|
)
|
|
}
|
|
}
|
|
return { toolMiB, usageKeys: turnCount, historyBytes, appendBytes, arms: finishSamples(samples) }
|
|
}
|
|
|
|
async function runSmallCorpus(arms, project, cwd) {
|
|
const directory = join(project, 'small-transcripts')
|
|
await mkdir(directory)
|
|
const files = Array.from({ length: 1_000 }, (_, index) => ({
|
|
path: join(directory, `${String(index).padStart(4, '0')}.jsonl`),
|
|
prefix: usageRecord(cwd, index),
|
|
append: usageRecord(cwd, index, 200, 30)
|
|
}))
|
|
const paths = files.map((file) => file.path)
|
|
const historyBytes = files.reduce((sum, file) => sum + Buffer.byteLength(file.prefix), 0)
|
|
const appendBytes = files.reduce((sum, file) => sum + Buffer.byteLength(file.append), 0)
|
|
const samples = []
|
|
try {
|
|
for (const [round, order] of measurementSchedule(arms).entries()) {
|
|
let reference = null
|
|
for (const arm of order) {
|
|
const scanner = arms[arm].scanner
|
|
for (let start = 0; start < files.length; start += 64) {
|
|
await Promise.all(
|
|
files.slice(start, start + 64).map((file) => writeFile(file.path, file.prefix))
|
|
)
|
|
}
|
|
const cold = await measure(scanner, paths)
|
|
const warm = await measure(scanner, paths, cold.cache)
|
|
assert.deepEqual(warm.result, cold.result)
|
|
assert.equal(cold.result.sessions[0]?.turnCount, files.length)
|
|
assert.equal(warm.metrics.bytesRead, 0)
|
|
for (let start = 0; start < files.length; start += 64) {
|
|
await Promise.all(
|
|
files.slice(start, start + 64).map((file) => appendFile(file.path, file.append))
|
|
)
|
|
}
|
|
const changed = await measure(scanner, paths, warm.cache)
|
|
const fresh = await measure(scanner, paths)
|
|
assert.deepEqual(changed.result, fresh.result)
|
|
assert.equal(changed.result.sessions[0]?.turnCount, files.length)
|
|
assert.equal(changed.result.sessions[0]?.totalInputTokens, files.length * 200)
|
|
if (VERIFY && arm === 'current') {
|
|
// A small snapshot needs its parse and one content recheck.
|
|
assert(
|
|
cold.metrics.bytesRead <= historyBytes * 2,
|
|
'tiny cold files paid repeated checkpoint overhead'
|
|
)
|
|
assert(
|
|
changed.metrics.bytesRead <= (historyBytes + appendBytes) * 2,
|
|
'tiny changed files paid repeated checkpoint overhead'
|
|
)
|
|
}
|
|
reference = recordRound(
|
|
samples,
|
|
{ cold, warm, changed, freshAfterChange: fresh },
|
|
arm,
|
|
round,
|
|
order,
|
|
reference
|
|
)
|
|
}
|
|
}
|
|
return {
|
|
scenario: 'small-transcript-corpus',
|
|
files: files.length,
|
|
historyBytes,
|
|
appendBytes,
|
|
arms: finishSamples(samples)
|
|
}
|
|
} finally {
|
|
await rm(directory, { recursive: true, force: true })
|
|
}
|
|
}
|
|
|
|
const home = await mkdtemp(join(tmpdir(), 'orca-claude-usage-append-benchmark-'))
|
|
try {
|
|
const project = join(home, '.claude', 'projects', 'benchmark')
|
|
const cwd = join(home, 'repo')
|
|
await mkdir(project, { recursive: true })
|
|
await mkdir(cwd)
|
|
const transcriptPath = join(project, 'active-session.jsonl')
|
|
const baseline = BASELINE || COMPARE ? await readClaudeUsageBenchmarkBaselineSources() : null
|
|
const names = BASELINE ? ['baseline'] : COMPARE ? ['baseline', 'current'] : ['current']
|
|
const worktreeSources = new Map()
|
|
const arms = Object.fromEntries(
|
|
await Promise.all(
|
|
names.map(async (name) => [
|
|
name,
|
|
await loadClaudeUsageBenchmarkScanner(
|
|
home,
|
|
name === 'baseline' ? baseline.sources : new Map(),
|
|
worktreeSources,
|
|
name === 'baseline' ? baseline.baselineCommit : null
|
|
)
|
|
])
|
|
)
|
|
)
|
|
for (const { scanner } of Object.values(arms)) {
|
|
await verifyClaudeUsageBenchmarkReadAccounting(scanner, transcriptPath)
|
|
}
|
|
await rm(transcriptPath)
|
|
const processors = cpus()
|
|
console.log(
|
|
JSON.stringify({
|
|
node: process.version,
|
|
nodeFlags: process.execArgv,
|
|
platform: process.platform,
|
|
osRelease: release(),
|
|
arch: process.arch,
|
|
cpuModel: processors[0]?.model,
|
|
cpuCount: processors.length,
|
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
rounds: ROUNDS,
|
|
verify: VERIFY,
|
|
mode: COMPARE ? 'compare' : names[0],
|
|
baselineRef: baseline?.baselineRef ?? null,
|
|
baselineCommit: baseline?.baselineCommit ?? null,
|
|
tooling: await describeClaudeUsageBenchmarkTooling(),
|
|
bundleSha256: Object.fromEntries(
|
|
Object.entries(arms).map(([name, arm]) => [name, arm.bundleSha256])
|
|
),
|
|
sourceFingerprints: Object.fromEntries(
|
|
Object.entries(arms).map(([name, arm]) => [name, arm.sourceFingerprints])
|
|
),
|
|
measurement: {
|
|
cold: 'scanner-cache cold; fixtures freshly written and page-cache warm',
|
|
durationMs: 'production scan only; excludes fixture I/O and cache serialization',
|
|
totalDurationMs: 'production scan plus cache stringify; excludes JSON.parse hydration',
|
|
bytesRead: 'raw filesystem API bytes including checkpoint windows; excludes metadata calls',
|
|
behaviorParity:
|
|
'exact legacy per-file and aggregate projections; excludes mtime and additive cache metadata'
|
|
}
|
|
})
|
|
)
|
|
console.log(JSON.stringify(await runSmallCorpus(arms, project, cwd)))
|
|
const results = []
|
|
for (const toolMiB of [2, 32]) {
|
|
const result = await runScenario(arms, transcriptPath, cwd, toolMiB)
|
|
results.push(result)
|
|
console.log(JSON.stringify({ scenario: 'history-scaling', ...result }))
|
|
}
|
|
const [small, large] = results
|
|
const scaling = Object.fromEntries(
|
|
names.map((arm) => [
|
|
arm,
|
|
{
|
|
historyByteRatio: large.historyBytes / small.historyBytes,
|
|
appendReadByteRatio:
|
|
large.arms[arm].phases.append.bytesRead / small.arms[arm].phases.append.bytesRead,
|
|
appendDurationRatio:
|
|
large.arms[arm].phases.append.durationMs / small.arms[arm].phases.append.durationMs,
|
|
cachedStateByteRatio:
|
|
large.arms[arm].phases.append.cachedStateBytes /
|
|
small.arms[arm].phases.append.cachedStateBytes
|
|
}
|
|
])
|
|
)
|
|
console.log(JSON.stringify({ scaling, exactColdParity: true, crossArmBehaviorParity: COMPARE }))
|
|
if (VERIFY) {
|
|
assert(
|
|
scaling.current.appendReadByteRatio <= 1.1,
|
|
'append reads scale with raw transcript history'
|
|
)
|
|
assert(
|
|
large.arms.current.phases.append.cachedStateBytes <=
|
|
small.arms.current.phases.append.cachedStateBytes + 512,
|
|
'cache retained raw tool history'
|
|
)
|
|
}
|
|
const keyResults = [large]
|
|
for (const usageKeys of [4_096, 16_384]) {
|
|
const result = await runScenario(arms, transcriptPath, cwd, 32, usageKeys)
|
|
keyResults.push(result)
|
|
console.log(JSON.stringify({ scenario: 'usage-key-scaling', ...result }))
|
|
}
|
|
console.log(
|
|
JSON.stringify({
|
|
keyScaling: keyResults.map((result) => ({
|
|
usageKeys: result.usageKeys,
|
|
historyBytes: result.historyBytes,
|
|
arms: Object.fromEntries(names.map((arm) => [arm, result.arms[arm].phases.append]))
|
|
}))
|
|
})
|
|
)
|
|
} finally {
|
|
await rm(home, { recursive: true, force: true })
|
|
}
|