mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-01 11:06:03 +00:00
349 lines
12 KiB
TypeScript
349 lines
12 KiB
TypeScript
// Benchmarks the real Codex transcript mirror against a large indexed SQLite transcript.
|
|
import { execFileSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import type { SQLInputValue } from "node:sqlite";
|
|
import { codexTranscriptMirrorRuntime } from "../extensions/codex/src/app-server/transcript-mirror.js";
|
|
import { attachCodexMirrorIdentity } from "../extensions/codex/src/app-server/upstream-prompt-provenance.js";
|
|
import { upsertSessionEntry } from "../src/config/sessions/session-accessor.js";
|
|
import type { AgentMessage } from "../src/plugin-sdk/agent-core.js";
|
|
import {
|
|
closeOpenClawAgentDatabasesForTest,
|
|
openOpenClawAgentDatabase,
|
|
} from "../src/state/openclaw-agent-db.js";
|
|
import { closeOpenClawStateDatabaseForTest } from "../src/state/openclaw-state-db.js";
|
|
|
|
const DEFAULT_EVENT_COUNT = 100_000;
|
|
const DEFAULT_PAYLOAD_BYTES = 64;
|
|
const DEFAULT_RUNS = 8;
|
|
const DEFAULT_WARMUPS = 2;
|
|
const NEW_MESSAGES_PER_OPERATION = 2;
|
|
|
|
type MirrorTarget = {
|
|
agentId: string;
|
|
sessionId: string;
|
|
sessionKey: string;
|
|
storePath: string;
|
|
};
|
|
|
|
type WorkCounters = {
|
|
fullTranscriptQueries: number;
|
|
seededEventJsonParses: number;
|
|
selectQueries: number;
|
|
};
|
|
|
|
function readIntegerArg(name: string, fallback: number): number {
|
|
const raw = process.argv.find((arg) => arg.startsWith(`--${name}=`))?.slice(name.length + 3);
|
|
if (raw === undefined) {
|
|
return fallback;
|
|
}
|
|
const value = Number(raw);
|
|
if (!Number.isSafeInteger(value) || value < 1) {
|
|
throw new Error(`--${name} must be a positive integer`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function readSourceSha(): string {
|
|
const value = process.argv
|
|
.find((arg) => arg.startsWith("--source-sha="))
|
|
?.slice("--source-sha=".length);
|
|
if (!value || !/^[a-f0-9]{40}$/u.test(value)) {
|
|
throw new Error("benchmark requires --source-sha=<40-character commit SHA>");
|
|
}
|
|
const checkoutSha = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
cwd: path.resolve(import.meta.dirname, ".."),
|
|
encoding: "utf8",
|
|
}).trim();
|
|
if (checkoutSha !== value) {
|
|
throw new Error(`source SHA ${value} does not match checkout HEAD ${checkoutSha}`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function median(values: readonly number[]): number {
|
|
const sorted = values.toSorted((left, right) => left - right);
|
|
const upperIndex = Math.floor(sorted.length / 2);
|
|
const upper = sorted[upperIndex] ?? 0;
|
|
const lower = sorted.length % 2 === 0 ? (sorted[upperIndex - 1] ?? upper) : upper;
|
|
return Number(((lower + upper) / 2).toFixed(3));
|
|
}
|
|
|
|
function percentile(values: readonly number[], fraction: number): number {
|
|
const sorted = values.toSorted((left, right) => left - right);
|
|
const index = Math.min(sorted.length - 1, Math.ceil(sorted.length * fraction) - 1);
|
|
return Number((sorted[Math.max(0, index)] ?? 0).toFixed(3));
|
|
}
|
|
|
|
/** Seeds a fully indexed linear transcript without charging setup to measured owner calls. */
|
|
function seedTranscript(params: {
|
|
database: ReturnType<typeof openOpenClawAgentDatabase>;
|
|
eventCount: number;
|
|
payloadText: string;
|
|
sessionId: string;
|
|
}): void {
|
|
const { database, eventCount, payloadText, sessionId } = params;
|
|
const insertEvent = database.db.prepare(
|
|
`INSERT INTO transcript_events (session_id, seq, event_json, created_at)
|
|
VALUES (?, ?, ?, ?)`,
|
|
);
|
|
const insertIdentity = database.db.prepare(
|
|
`INSERT INTO transcript_event_identities (
|
|
session_id, event_id, seq, event_type, parent_id, message_idempotency_key, created_at
|
|
) VALUES (?, ?, ?, 'message', ?, ?, ?)`,
|
|
);
|
|
const insertActive = database.db.prepare(
|
|
`INSERT INTO session_transcript_active_events (
|
|
session_id, active_position, event_seq, message_position
|
|
) VALUES (?, ?, ?, ?)`,
|
|
);
|
|
const now = Date.now();
|
|
database.db.exec("BEGIN IMMEDIATE");
|
|
try {
|
|
for (let seq = 0; seq < eventCount; seq += 1) {
|
|
const eventId = `benchmark-event-${seq}`;
|
|
const parentId = seq === 0 ? null : `benchmark-event-${seq - 1}`;
|
|
const idempotencyKey = `seed:${sessionId}:${seq}`;
|
|
const role = seq % 2 === 0 ? "user" : "assistant";
|
|
const event = {
|
|
id: eventId,
|
|
message: {
|
|
content: role === "user" ? payloadText : [{ type: "text", text: payloadText }],
|
|
idempotencyKey,
|
|
role,
|
|
timestamp: now + seq,
|
|
},
|
|
parentId,
|
|
timestamp: now + seq,
|
|
type: "message",
|
|
};
|
|
insertEvent.run(sessionId, seq, JSON.stringify(event), now + seq);
|
|
const identityValues = [
|
|
sessionId,
|
|
eventId,
|
|
seq,
|
|
parentId,
|
|
idempotencyKey,
|
|
now + seq,
|
|
] satisfies SQLInputValue[];
|
|
insertIdentity.run(...identityValues);
|
|
insertActive.run(sessionId, seq, seq, seq);
|
|
}
|
|
database.db
|
|
.prepare(
|
|
`INSERT INTO session_transcript_index_state (
|
|
session_id, indexed_seq, leaf_event_id, needs_rebuild,
|
|
active_event_count, active_message_count, updated_at
|
|
) VALUES (?, ?, ?, 0, ?, ?, ?)`,
|
|
)
|
|
.run(
|
|
sessionId,
|
|
eventCount - 1,
|
|
`benchmark-event-${eventCount - 1}`,
|
|
eventCount,
|
|
eventCount,
|
|
now + eventCount,
|
|
);
|
|
database.db.exec("COMMIT");
|
|
} catch (error) {
|
|
database.db.exec("ROLLBACK");
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function instrumentWork(database: ReturnType<typeof openOpenClawAgentDatabase>): {
|
|
counters: WorkCounters;
|
|
reset: () => void;
|
|
restore: () => void;
|
|
} {
|
|
const counters: WorkCounters = {
|
|
fullTranscriptQueries: 0,
|
|
seededEventJsonParses: 0,
|
|
selectQueries: 0,
|
|
};
|
|
const originalPrepare = database.db.prepare.bind(database.db);
|
|
const originalParse = JSON.parse;
|
|
Object.defineProperty(database.db, "prepare", {
|
|
configurable: true,
|
|
value: (sql: string) => {
|
|
const normalized = sql.replaceAll(/\s+/gu, " ").trim().toLowerCase();
|
|
if (normalized.startsWith("select ")) {
|
|
counters.selectQueries += 1;
|
|
}
|
|
if (
|
|
/from "?transcript_events"?/u.test(normalized) &&
|
|
normalized.includes("event_json") &&
|
|
/order by "?seq"? asc/u.test(normalized)
|
|
) {
|
|
counters.fullTranscriptQueries += 1;
|
|
}
|
|
return originalPrepare(sql);
|
|
},
|
|
});
|
|
JSON.parse = ((text: string, reviver?: Parameters<typeof JSON.parse>[1]) => {
|
|
if (text.includes('"id":"benchmark-event-')) {
|
|
counters.seededEventJsonParses += 1;
|
|
}
|
|
return originalParse(text, reviver);
|
|
}) as typeof JSON.parse;
|
|
return {
|
|
counters,
|
|
reset: () => {
|
|
counters.fullTranscriptQueries = 0;
|
|
counters.seededEventJsonParses = 0;
|
|
counters.selectQueries = 0;
|
|
},
|
|
restore: () => {
|
|
Object.defineProperty(database.db, "prepare", {
|
|
configurable: true,
|
|
value: originalPrepare,
|
|
});
|
|
JSON.parse = originalParse;
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildPromptFinalBatch(ordinal: number): AgentMessage[] {
|
|
return [
|
|
attachCodexMirrorIdentity(
|
|
{
|
|
role: "user",
|
|
content: `benchmark prompt ${ordinal}`,
|
|
timestamp: 2_000_000_000_000 + ordinal,
|
|
} as AgentMessage,
|
|
`turn-${ordinal}:prompt`,
|
|
),
|
|
attachCodexMirrorIdentity(
|
|
{
|
|
role: "assistant",
|
|
content: [{ type: "text", text: `benchmark final ${ordinal}` }],
|
|
timestamp: 2_000_000_100_000 + ordinal,
|
|
} as AgentMessage,
|
|
`turn-${ordinal}:assistant`,
|
|
),
|
|
];
|
|
}
|
|
|
|
async function runMirror(target: MirrorTarget, ordinal: number): Promise<void> {
|
|
await codexTranscriptMirrorRuntime.mirror({
|
|
...target,
|
|
idempotencyScope: "codex-app-server:benchmark",
|
|
messages: buildPromptFinalBatch(ordinal),
|
|
});
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const sourceSha = readSourceSha();
|
|
const eventCount = readIntegerArg("events", DEFAULT_EVENT_COUNT);
|
|
const payloadBytes = readIntegerArg("payload-bytes", DEFAULT_PAYLOAD_BYTES);
|
|
const runs = readIntegerArg("runs", DEFAULT_RUNS);
|
|
const warmups = readIntegerArg("warmups", DEFAULT_WARMUPS);
|
|
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-codex-mirror-bench-"));
|
|
const agentId = "benchmark";
|
|
const sessionId = "codex-mirror-benchmark";
|
|
const sessionKey = `agent:${agentId}:${sessionId}`;
|
|
try {
|
|
const database = openOpenClawAgentDatabase({
|
|
agentId,
|
|
path: path.join(stateDir, "openclaw-agent.sqlite"),
|
|
});
|
|
await upsertSessionEntry(
|
|
{ agentId, sessionKey, storePath: database.path },
|
|
{ sessionId, updatedAt: 1 },
|
|
);
|
|
seedTranscript({
|
|
database,
|
|
eventCount,
|
|
payloadText: "x".repeat(payloadBytes),
|
|
sessionId,
|
|
});
|
|
const target = { agentId, sessionId, sessionKey, storePath: database.path };
|
|
const instrumentation = instrumentWork(database);
|
|
try {
|
|
for (let ordinal = 0; ordinal < warmups; ordinal += 1) {
|
|
await runMirror(target, ordinal);
|
|
}
|
|
instrumentation.reset();
|
|
const beforeMaxRssKb = process.resourceUsage().maxRSS;
|
|
const durations: number[] = [];
|
|
for (let run = 0; run < runs; run += 1) {
|
|
const startedAt = performance.now();
|
|
await runMirror(target, warmups + run);
|
|
durations.push(performance.now() - startedAt);
|
|
}
|
|
const afterMaxRssKb = process.resourceUsage().maxRSS;
|
|
const measuredWork = { ...instrumentation.counters };
|
|
const lastOrdinal = warmups + runs - 1;
|
|
await runMirror(target, lastOrdinal);
|
|
const row = database.db
|
|
.prepare(
|
|
`SELECT COUNT(*) AS count,
|
|
SUM(LENGTH(CAST(event_json AS BLOB))) AS bytes
|
|
FROM transcript_events
|
|
WHERE session_id = ?`,
|
|
)
|
|
.get(sessionId) as { bytes: number; count: number };
|
|
const expectedEvents = eventCount + NEW_MESSAGES_PER_OPERATION * (warmups + runs);
|
|
if (row.count !== expectedEvents) {
|
|
throw new Error(`mirror wrote ${row.count} events; expected ${expectedEvents}`);
|
|
}
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
sourceSha,
|
|
fixture: {
|
|
initialMessageEvents: eventCount,
|
|
payloadBytes,
|
|
sqliteTranscriptBytesAfterOperations: row.bytes,
|
|
},
|
|
operation: "real Codex mirror owner with one new prompt and one new final",
|
|
runtime: {
|
|
arch: process.arch,
|
|
node: process.version,
|
|
platform: `${os.platform()} ${os.release()}`,
|
|
},
|
|
warmups,
|
|
runs,
|
|
latencyMs: {
|
|
median: median(durations),
|
|
p95: percentile(durations, 0.95),
|
|
raw: durations.map((value) => Number(value.toFixed(3))),
|
|
},
|
|
memoryProxy: {
|
|
maxRssKbBeforeOperations: beforeMaxRssKb,
|
|
maxRssKbAfterOperations: afterMaxRssKb,
|
|
maxRssGrowthKb: Math.max(0, afterMaxRssKb - beforeMaxRssKb),
|
|
},
|
|
measuredWork: {
|
|
...measuredWork,
|
|
perOperation: {
|
|
fullTranscriptQueries: Number(
|
|
(measuredWork.fullTranscriptQueries / runs).toFixed(3),
|
|
),
|
|
seededEventJsonParses: Number(
|
|
(measuredWork.seededEventJsonParses / runs).toFixed(3),
|
|
),
|
|
selectQueries: Number((measuredWork.selectQueries / runs).toFixed(3)),
|
|
},
|
|
},
|
|
correctness: {
|
|
idempotentReplayAddedRows: 0,
|
|
storedEventCount: row.count,
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
} finally {
|
|
instrumentation.restore();
|
|
}
|
|
} finally {
|
|
closeOpenClawAgentDatabasesForTest();
|
|
closeOpenClawStateDatabaseForTest();
|
|
fs.rmSync(stateDir, { force: true, recursive: true });
|
|
}
|
|
}
|
|
|
|
await main();
|