// SQLite state benchmark seeds OpenClaw DBs and reports hot-query proof lines. import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import type { DatabaseSync, SQLInputValue } from "node:sqlite"; import { pathToFileURL } from "node:url"; import { expectDefined } from "../packages/normalization-core/src/expect.js"; import { OPENCLAW_AGENT_SCHEMA_VERSION } from "../src/state/openclaw-agent-db-contract.js"; import { openOpenClawAgentDatabase, closeOpenClawAgentDatabasesForTest, } from "../src/state/openclaw-agent-db.js"; import { OPENCLAW_STATE_SCHEMA_VERSION } from "../src/state/openclaw-state-db-contract.js"; import { closeOpenClawStateDatabaseForTest, openOpenClawStateDatabase, } from "../src/state/openclaw-state-db.js"; import { parseStrictIntegerOption } from "./lib/dev-tooling-safety.ts"; import { collectSqliteQueryPlanEvidence, type SqliteQueryPlanEvidence, } from "./lib/sqlite-query-plan-evidence.js"; import { CliUsageError, parseSqliteStateBenchmarkCli, type ProfileId, } from "./lib/sqlite-state-benchmark-cli.js"; type ProfileConfig = { agentCacheEntries: number; agentCount: number; channelIngressEvents: number; cronJobs: number; cronTaskRuns: number; deliveryQueueEntries: number; pluginStateEntries: number; queryRuns: number; }; type TimedQuery = { database: "agent" | "state"; id: string; p50Ms: number; p95Ms: number; plan: SqliteQueryPlanEvidence; runs: number; rows: number; sql: string; }; type BenchmarkReport = { integrity: { agent: string[]; state: string; }; node: string; schemaVersion: 2; versions: { agentSchema: number; sqlite: string; stateSchema: number; }; paths: { agentDatabases: string[]; artifact: string | null; stateDatabase: string; stateDir: string; }; profile: ProfileId; queries: TimedQuery[]; rows: { agentCacheEntries: number; agentDatabases: number; channelIngressEvents: number; cronJobs: number; cronTaskRuns: number; deliveryQueueEntries: number; pluginStateEntries: number; stateRows: number; transcriptEvents: number; }; timingsMs: { checkpoint: number; seed: number; total: number; }; walBytes: { agentAfter: number[]; agentBefore: number[]; stateAfter: number; stateBefore: number; }; }; const PROFILES: Record = { smoke: { agentCacheEntries: 1_000, agentCount: 2, channelIngressEvents: 1_000, cronJobs: 100, cronTaskRuns: 1_000, deliveryQueueEntries: 1_000, pluginStateEntries: 1_000, queryRuns: 20, }, default: { agentCacheEntries: 20_000, agentCount: 5, channelIngressEvents: 10_000, cronJobs: 1_000, cronTaskRuns: 50_000, deliveryQueueEntries: 50_000, pluginStateEntries: 20_000, queryRuns: 30, }, large: { agentCacheEntries: 50_000, agentCount: 10, channelIngressEvents: 100_000, cronJobs: 5_000, cronTaskRuns: 250_000, deliveryQueueEntries: 200_000, pluginStateEntries: 100_000, queryRuns: 40, }, }; const SQLITE_PERF_FULL_LOAD_RUNS = 20; const SQLITE_PERF_TRANSCRIPT_EVENTS = 256; const SQLITE_PERF_TRANSCRIPT_MESSAGE_BYTES = 4_096; const SQLITE_PERF_TRANSCRIPT_PAGE_MESSAGES = 256; const SQLITE_PERF_TRANSCRIPT_SESSION_ID = "perf-history"; const SQLITE_PERF_INGRESS_QUEUE = JSON.stringify(["telegram", "bench-account"]); const SQLITE_PERF_PLUGIN_ID = "benchmark-plugin"; const SQLITE_PERF_PLUGIN_NAMESPACE = "journal"; const SQLITE_PERF_PLUGIN_NOW = 1_750_000_000_000; const SQLITE_PERF_CATALOG_SCOPE = "plugin-model-catalog-v1"; const SQLITE_PERF_PAGE_SIZE = 100; function applyScale(config: ProfileConfig): ProfileConfig { const scale = parseStrictIntegerOption({ fallback: 1, label: "SQLITE_PERF_SCALE", min: 1, raw: process.env["SQLITE_PERF_SCALE"], }); if (scale === 1) { return config; } return { agentCacheEntries: config.agentCacheEntries * scale, agentCount: config.agentCount, channelIngressEvents: config.channelIngressEvents * scale, cronJobs: config.cronJobs * scale, cronTaskRuns: config.cronTaskRuns * scale, deliveryQueueEntries: config.deliveryQueueEntries * scale, pluginStateEntries: config.pluginStateEntries * scale, queryRuns: config.queryRuns, }; } function printUsage(): void { console.log(`OpenClaw SQLite state benchmark Usage: node --import tsx scripts/bench-sqlite-state.ts [options] Options: --profile Data volume profile (default: default) --state-dir Reuse a state directory instead of a temp dir --output Write machine-readable JSON report --help Show this text Environment: SQLITE_PERF_SCALE= Multiplies row counts for the selected profile `); } function nowMs(): number { return Number(process.hrtime.bigint()) / 1e6; } function fileSize(pathname: string): number { try { return fs.statSync(pathname).size; } catch { return 0; } } function walSize(pathname: string): number { return fileSize(`${pathname}-wal`); } function stateRowCount(config: ProfileConfig): number { return ( config.channelIngressEvents + config.cronJobs + config.cronTaskRuns + config.deliveryQueueEntries + config.pluginStateEntries ); } function seedStateDatabase(db: DatabaseSync, config: ProfileConfig): void { db.exec("BEGIN IMMEDIATE;"); try { seedCronJobs(db, config.cronJobs); seedCronTaskRuns(db, config.cronTaskRuns, config.cronJobs); seedDeliveryQueue(db, config.deliveryQueueEntries); seedPluginState(db, config.pluginStateEntries); seedChannelIngress(db, config.channelIngressEvents); db.exec("COMMIT;"); } catch (err) { db.exec("ROLLBACK;"); throw err; } } function seedCronJobs(db: DatabaseSync, count: number): void { const insert = db.prepare(` INSERT INTO cron_jobs ( store_key, job_id, name, enabled, agent_id, payload_kind, job_json, state_json, runtime_updated_at_ms, schedule_identity, sort_order, updated_at ) VALUES (?, ?, ?, ?, ?, 'agentTurn', ?, ?, ?, ?, ?, ?) `); for (let i = 0; i < count; i += 1) { const jobId = `job-${String(i).padStart(8, "0")}`; const storeKey = `/state/cron/jobs-${i % 8}.json`; const updatedAt = 1_700_000_000_000 + i; const name = `Benchmark job ${i}`; const enabled = i % 5 !== 0; const agentId = `agent-${i % 16}`; const job = { id: jobId, name, enabled, createdAtMs: updatedAt - 100_000, agentId, sessionKey: `agent:${agentId}:main`, schedule: { kind: "every", everyMs: 60_000 + (i % 120) * 1_000, anchorMs: updatedAt - 60_000, }, sessionTarget: "isolated", wakeMode: "now", payload: { kind: "agentTurn", message: `Benchmark payload ${i}`, model: "openai/gpt-5.6-luna", timeoutSeconds: 60, allowUnsafeExternalContent: false, lightContext: true, }, delivery: { mode: "announce", channel: "telegram", to: `chat-${i % 32}`, accountId: "bench-account", bestEffort: true, }, state: {}, }; const state = { nextRunAtMs: updatedAt + (i % 2_000) * 1_000, lastRunAtMs: updatedAt - 1_000, lastRunStatus: "completed", lastDurationMs: 50 + (i % 500), consecutiveErrors: 0, consecutiveSkipped: 0, scheduleErrorCount: 0, lastDeliveryStatus: "sent", lastDelivered: true, }; insert.run( storeKey, jobId, name, enabled ? 1 : 0, agentId, JSON.stringify(job), JSON.stringify(state), updatedAt, `schedule-${i % 512}`, i, updatedAt, ); } } function seedCronTaskRuns(db: DatabaseSync, count: number, cronJobCount: number): void { const insert = db.prepare(` INSERT INTO task_runs ( task_id, runtime, source_id, requester_session_key, owner_key, scope_kind, child_session_key, run_id, task, status, delivery_status, notify_policy, created_at, started_at, ended_at, last_event_at, error, terminal_summary, terminal_outcome, detail_json ) VALUES (?, 'cron', ?, '', '', 'system', ?, ?, ?, ?, 'not_applicable', 'silent', ?, ?, ?, ?, ?, ?, ?, ?) `); for (let i = 0; i < count; i += 1) { const jobIndex = i % 4 === 0 ? 0 : i % Math.max(1, cronJobCount); const jobId = `job-${String(jobIndex).padStart(8, "0")}`; const ts = 1_700_000_000_000 + i; const succeeded = i % 17 !== 0; const runId = `run-${i}`; const status = succeeded ? "ok" : "error"; const storeKey = `/state/cron/jobs-${i % 8}.json`; insert.run( `cron-benchmark-${i}`, jobId, `agent:agent-${i % 16}:main`, runId, jobId, succeeded ? "succeeded" : "failed", ts, ts, ts + 20 + (i % 1_000), ts + 20 + (i % 1_000), succeeded ? null : `run ${i} failed`, `run ${i}`, succeeded ? "succeeded" : null, JSON.stringify({ kind: "cron-run", storeKey, action: "finished", status, runId }), ); } } function seedDeliveryQueue(db: DatabaseSync, count: number): void { const insert = db.prepare(` INSERT INTO delivery_queue_entries ( queue_name, id, status, entry_kind, session_key, channel, target, account_id, retry_count, last_attempt_at, last_error, recovery_state, platform_send_started_at, entry_json, enqueued_at, updated_at, failed_at ) VALUES (?, ?, ?, 'message', ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, ?, ?, ?, ?) `); for (let i = 0; i < count; i += 1) { const status = i % 13 === 0 ? "failed" : i % 17 === 0 ? "completed" : "pending"; const queueName = i % 5 === 0 ? "session" : "outbound"; const enqueuedAt = 1_700_000_000_000 + i; const channel = i % 2 === 0 ? "telegram" : "discord"; const target = `target-${i % 256}`; const accountId = `account-${i % 8}`; const sessionKey = `agent:agent-${i % 16}:main`; insert.run( queueName, `delivery-${String(i).padStart(8, "0")}`, status, sessionKey, channel, target, accountId, i % 5, status === "failed" ? enqueuedAt + 500 : null, JSON.stringify({ id: `delivery-${String(i).padStart(8, "0")}`, retryCount: i % 5, enqueuedAt, sessionKey, route: { channel, to: target, accountId }, }), enqueuedAt, enqueuedAt + 100, status === "failed" ? enqueuedAt + 1_000 : null, ); } } function seedPluginState(db: DatabaseSync, count: number): void { const insert = db.prepare(` INSERT INTO plugin_state_entries ( plugin_id, namespace, entry_key, value_json, created_at, expires_at ) VALUES (?, ?, ?, ?, ?, ?) `); for (let i = 0; i < count; i += 1) { const concentrated = i < Math.ceil(count * 0.75); const expiresAt = i % 10 === 0 ? SQLITE_PERF_PLUGIN_NOW - 1_000 - i : i % 4 === 0 ? SQLITE_PERF_PLUGIN_NOW + 1_000 + i : null; insert.run( concentrated ? SQLITE_PERF_PLUGIN_ID : `plugin-${i % 12}`, concentrated ? SQLITE_PERF_PLUGIN_NAMESPACE : `namespace-${i % 16}`, `entry-${String(i).padStart(8, "0")}`, JSON.stringify({ value: i, text: `payload ${i}` }), 1_700_000_000_000 + i, expiresAt, ); } } function seedChannelIngress(db: DatabaseSync, count: number): void { const insert = db.prepare(` INSERT INTO channel_ingress_events ( queue_name, event_id, channel_id, account_id, status, lane_key, payload_json, metadata_json, received_at, updated_at, claim_token, claim_owner, claimed_at, attempts, last_attempt_at, last_error, failed_reason, failed_at, completed_at, completed_metadata_json ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, ?, NULL) `); for (let i = 0; i < count; i += 1) { const status = i % 17 === 0 ? "claimed" : i % 29 === 0 ? "completed" : "pending"; const timestamp = 1_700_000_000_000 + i; insert.run( SQLITE_PERF_INGRESS_QUEUE, `event-${String(i).padStart(8, "0")}`, "telegram", "bench-account", status, `lane-${i % 128}`, JSON.stringify({ messageId: `message-${i}`, text: `message ${i}` }), JSON.stringify({ source: "benchmark" }), timestamp, timestamp, status === "claimed" ? `claim-${i}` : null, status === "claimed" ? "benchmark-worker" : null, status === "claimed" ? timestamp : null, i % 3, status === "claimed" ? timestamp : null, status === "completed" ? timestamp : null, ); } } function seedAgentDatabase(db: DatabaseSync, count: number, agentIndex: number): void { db.exec("BEGIN IMMEDIATE;"); try { const insert = db.prepare(` INSERT INTO cache_entries (scope, key, value_json, blob, expires_at, updated_at) VALUES (?, ?, ?, NULL, ?, ?) `); const catalogEntries = Math.min(count, 64); for (let i = 0; i < count; i += 1) { const isCatalog = i < catalogEntries; insert.run( isCatalog ? SQLITE_PERF_CATALOG_SCOPE : `runtime-cache-${i % 16}`, isCatalog ? `plugin-${String(i).padStart(3, "0")}` : `agent-${agentIndex}-entry-${String(i).padStart(8, "0")}`, JSON.stringify( isCatalog ? { generatedBy: "openclaw-plugin-model-catalog-v1", models: [{ id: `model-${i}`, name: `Benchmark model ${i}` }], pluginId: `plugin-${i}`, } : { agentIndex, i, value: `cache ${i}` }, ), null, 1_700_000_000_000 + i, ); } if (agentIndex === 0) { seedTranscriptHistory(db); } db.exec("COMMIT;"); } catch (err) { db.exec("ROLLBACK;"); throw err; } } function seedTranscriptHistory(db: DatabaseSync): void { const sessionKey = "agent:perf-agent-0:history"; db.prepare( `INSERT INTO session_nodes (session_key, current_session_id, entry_json, updated_at) VALUES (?, ?, ?, ?)`, ).run(sessionKey, SQLITE_PERF_TRANSCRIPT_SESSION_ID, "{}", 1_700_000_000_000); db.prepare( `INSERT INTO session_windows (session_id, session_key, created_at, updated_at) VALUES (?, ?, ?, ?)`, ).run(SQLITE_PERF_TRANSCRIPT_SESSION_ID, sessionKey, 1_700_000_000_000, 1_700_000_000_000); const insertEvent = db.prepare( `INSERT INTO transcript_events (session_id, seq, event_json, created_at) VALUES (?, ?, ?, ?)`, ); const insertIdentity = db.prepare( `INSERT INTO transcript_event_identities (session_id, event_id, seq, event_type, parent_id, message_idempotency_key, created_at) VALUES (?, ?, ?, 'message', NULL, NULL, ?)`, ); const insertActive = db.prepare( `INSERT INTO session_transcript_active_events (session_id, active_position, event_seq, message_position) VALUES (?, ?, ?, ?)`, ); const messageContent = "x".repeat(SQLITE_PERF_TRANSCRIPT_MESSAGE_BYTES); for (let seq = 1; seq <= SQLITE_PERF_TRANSCRIPT_EVENTS; seq += 1) { const eventId = `history-${seq}`; const message = { type: "message", id: eventId, message: { role: "user", content: messageContent }, }; insertEvent.run(SQLITE_PERF_TRANSCRIPT_SESSION_ID, seq, JSON.stringify(message), seq); insertIdentity.run(SQLITE_PERF_TRANSCRIPT_SESSION_ID, eventId, seq, seq); insertActive.run(SQLITE_PERF_TRANSCRIPT_SESSION_ID, seq - 1, seq, seq - 1); } db.prepare( `INSERT INTO session_transcript_index_state (session_id, indexed_seq, leaf_event_id, active_event_count, active_message_count, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, ).run( SQLITE_PERF_TRANSCRIPT_SESSION_ID, SQLITE_PERF_TRANSCRIPT_EVENTS, `history-${SQLITE_PERF_TRANSCRIPT_EVENTS}`, SQLITE_PERF_TRANSCRIPT_EVENTS, SQLITE_PERF_TRANSCRIPT_EVENTS, 1_700_000_000_000, ); } function readIntegrity(db: DatabaseSync): string { const row = db.prepare("PRAGMA integrity_check").get() as { integrity_check?: unknown }; return typeof row.integrity_check === "string" ? row.integrity_check : "missing"; } function readSqliteVersion(db: DatabaseSync): string { const row = db.prepare("SELECT sqlite_version() AS version").get() as { version?: unknown }; return typeof row.version === "string" ? row.version : "unknown"; } function checkpoint(db: DatabaseSync): void { db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").all(); } function percentile(values: number[], pct: number): number { if (values.length === 0) { return 0; } const sorted = values.toSorted((left, right) => left - right); const index = Math.min(sorted.length - 1, Math.ceil((pct / 100) * sorted.length) - 1); return Number(expectDefined(sorted[index], `SQLite benchmark percentile ${pct}`).toFixed(3)); } function readQueryPlan( db: DatabaseSync, sql: string, params: SQLInputValue[], ): SqliteQueryPlanEvidence { const raw = ( db.prepare(`EXPLAIN QUERY PLAN ${sql}`).all(...params) as Array<{ detail?: unknown }> ).map((row) => (typeof row.detail === "string" ? row.detail : JSON.stringify(row.detail ?? ""))); return collectSqliteQueryPlanEvidence(raw); } function runTimedQuery(params: { database: "agent" | "state"; db: DatabaseSync; fullLoad?: boolean; id: string; queryParams: SQLInputValue[]; requestedRuns: number; sql: string; }): TimedQuery { const runs = params.fullLoad ? Math.min(params.requestedRuns, SQLITE_PERF_FULL_LOAD_RUNS) : params.requestedRuns; const statement = params.db.prepare(params.sql); const samples: number[] = []; let rows = statement.all(...params.queryParams).length; for (let i = 0; i < runs; i += 1) { const started = nowMs(); rows = statement.all(...params.queryParams).length; samples.push(nowMs() - started); } return { database: params.database, id: params.id, p50Ms: percentile(samples, 50), p95Ms: percentile(samples, 95), plan: readQueryPlan(params.db, params.sql, params.queryParams), runs, rows, sql: params.sql, }; } function taskRunSelectSql(where: string): string { return `SELECT task_id, runtime, task_kind, source_id, requester_session_key, owner_key, scope_kind, child_session_key, parent_flow_id, parent_task_id, agent_id, requester_agent_id, run_id, label, task, status, delivery_status, notify_policy, created_at, started_at, ended_at, last_event_at, cleanup_after, tool_use_count, last_tool_name, error, progress_summary, terminal_summary, terminal_outcome, detail_json FROM task_runs WHERE ${where} ORDER BY created_at ASC, task_id ASC`; } function runHotQueries(params: { agentDb: DatabaseSync; config: ProfileConfig; stateDb: DatabaseSync; }): TimedQuery[] { const transcriptPositions = Array.from( { length: SQLITE_PERF_TRANSCRIPT_PAGE_MESSAGES }, (_, index) => SQLITE_PERF_TRANSCRIPT_EVENTS - SQLITE_PERF_TRANSCRIPT_PAGE_MESSAGES + index, ); const transcriptPlaceholders = transcriptPositions.map(() => "?").join(", "); return [ runTimedQuery({ database: "state", db: params.stateDb, id: "cron.store.load", queryParams: ["/state/cron/jobs-0.json"], requestedRuns: params.config.queryRuns, sql: `SELECT * FROM cron_jobs WHERE store_key = ? ORDER BY sort_order ASC, updated_at ASC, job_id ASC`, }), runTimedQuery({ database: "state", db: params.stateDb, fullLoad: true, id: "task-runs.cron.list", queryParams: ["cron"], requestedRuns: params.config.queryRuns, sql: taskRunSelectSql("runtime = ?"), }), runTimedQuery({ database: "state", db: params.stateDb, fullLoad: true, id: "task-runs.cron-source.list", queryParams: ["cron", "job-00000000"], requestedRuns: params.config.queryRuns, sql: taskRunSelectSql("runtime = ? AND source_id = ?"), }), runTimedQuery({ database: "state", db: params.stateDb, fullLoad: true, id: "delivery.pending.load", queryParams: ["outbound", "pending"], requestedRuns: params.config.queryRuns, sql: `SELECT id, entry_json, enqueued_at, retry_count, last_attempt_at, last_error, platform_send_started_at, recovery_state FROM delivery_queue_entries WHERE queue_name = ? AND status = ? ORDER BY enqueued_at ASC, id ASC`, }), runTimedQuery({ database: "state", db: params.stateDb, id: "ingress.pending.first-page", queryParams: [SQLITE_PERF_INGRESS_QUEUE, "pending", SQLITE_PERF_PAGE_SIZE], requestedRuns: params.config.queryRuns, sql: `SELECT * FROM channel_ingress_events WHERE queue_name = ? AND status = ? ORDER BY received_at ASC, event_id ASC LIMIT ?`, }), runTimedQuery({ database: "state", db: params.stateDb, id: "ingress.pending.seek-page", queryParams: [ SQLITE_PERF_INGRESS_QUEUE, "pending", 1_700_000_000_500, 1_700_000_000_500, "event-00000500", SQLITE_PERF_PAGE_SIZE, ], requestedRuns: params.config.queryRuns, sql: `SELECT * FROM channel_ingress_events WHERE queue_name = ? AND status = ? AND (received_at > ? OR (received_at = ? AND event_id > ?)) ORDER BY received_at ASC, event_id ASC LIMIT ?`, }), runTimedQuery({ database: "state", db: params.stateDb, id: "ingress.pending.id-page", queryParams: [SQLITE_PERF_INGRESS_QUEUE, "pending", SQLITE_PERF_PAGE_SIZE], requestedRuns: params.config.queryRuns, sql: `SELECT * FROM channel_ingress_events WHERE queue_name = ? AND status = ? ORDER BY event_id ASC LIMIT ?`, }), runTimedQuery({ database: "state", db: params.stateDb, id: "ingress.pending.id-seek-page", queryParams: [SQLITE_PERF_INGRESS_QUEUE, "pending", "event-00000500", SQLITE_PERF_PAGE_SIZE], requestedRuns: params.config.queryRuns, sql: `SELECT * FROM channel_ingress_events WHERE queue_name = ? AND status = ? AND event_id > ? ORDER BY event_id ASC LIMIT ?`, }), runTimedQuery({ database: "state", db: params.stateDb, fullLoad: true, id: "plugin-state.namespace.live", queryParams: [SQLITE_PERF_PLUGIN_ID, SQLITE_PERF_PLUGIN_NAMESPACE, SQLITE_PERF_PLUGIN_NOW], requestedRuns: params.config.queryRuns, sql: `SELECT plugin_id, namespace, entry_key, value_json, created_at, expires_at FROM plugin_state_entries WHERE plugin_id = ? AND namespace = ? AND (expires_at IS NULL OR expires_at > ?) ORDER BY created_at ASC, entry_key ASC`, }), runTimedQuery({ database: "agent", db: params.agentDb, id: "agent-cache.plugin-model-catalog.list", queryParams: [SQLITE_PERF_CATALOG_SCOPE], requestedRuns: params.config.queryRuns, sql: `SELECT key, value_json FROM cache_entries WHERE scope = ? ORDER BY key ASC`, }), runTimedQuery({ database: "agent", db: params.agentDb, id: "transcript.tail.metadata", queryParams: [SQLITE_PERF_TRANSCRIPT_SESSION_ID, ...transcriptPositions], requestedRuns: params.config.queryRuns, sql: `SELECT active.message_position, LENGTH(CAST(event.event_json AS BLOB)) + 1 AS serialized_bytes FROM session_transcript_active_events AS active JOIN transcript_events AS event ON event.session_id = active.session_id AND event.seq = active.event_seq WHERE active.session_id = ? AND active.message_position IN (${transcriptPlaceholders}) ORDER BY active.message_position DESC`, }), runTimedQuery({ database: "agent", db: params.agentDb, id: "transcript.tail.payload", queryParams: [SQLITE_PERF_TRANSCRIPT_SESSION_ID, ...transcriptPositions], requestedRuns: params.config.queryRuns, sql: `SELECT active.message_position, event.event_json FROM session_transcript_active_events AS active JOIN transcript_events AS event ON event.session_id = active.session_id AND event.seq = active.event_seq WHERE active.session_id = ? AND active.message_position IN (${transcriptPlaceholders}) ORDER BY active.message_position ASC`, }), ]; } function printProofLines(report: BenchmarkReport): void { const p95 = Math.max(...report.queries.map((query) => query.p95Ms)); console.log(`SQLITE_PERF_PROFILE=${report.profile}`); console.log(`SQLITE_PERF_STATE_ROWS=${report.rows.stateRows}`); console.log(`SQLITE_PERF_AGENT_ROWS=${report.rows.agentCacheEntries}`); console.log(`SQLITE_PERF_TRANSCRIPT_ROWS=${report.rows.transcriptEvents}`); console.log(`SQLITE_PERF_INTEGRITY=${report.integrity.state}`); console.log(`SQLITE_PERF_WAL_BYTES_BEFORE=${report.walBytes.stateBefore}`); console.log(`SQLITE_PERF_WAL_BYTES_AFTER=${report.walBytes.stateAfter}`); console.log(`SQLITE_PERF_QUERY_P95_MS=${p95.toFixed(3)}`); for (const query of report.queries) { console.log(`SQLITE_PERF_SCENARIO ${JSON.stringify(query)}`); } if (report.paths.artifact) { console.log(`SQLITE_PERF_ARTIFACT=${report.paths.artifact}`); } } function main(): void { const argv = process.argv.slice(2); const cli = parseSqliteStateBenchmarkCli(argv); if (cli.help) { printUsage(); return; } const { options } = cli; const config = applyScale(PROFILES[options.profile]); const stateDir = options.stateDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sqlite-perf-")); const env = { OPENCLAW_STATE_DIR: stateDir }; const started = nowMs(); try { const stateDatabase = openOpenClawStateDatabase({ env }); const agentDatabases = Array.from({ length: config.agentCount }, (_, index) => openOpenClawAgentDatabase({ agentId: `perf-agent-${index}`, env }), ); const seedStarted = nowMs(); seedStateDatabase(stateDatabase.db, config); const perAgentEntries = Math.ceil(config.agentCacheEntries / config.agentCount); agentDatabases.forEach((database, index) => seedAgentDatabase(database.db, perAgentEntries, index), ); const seedMs = nowMs() - seedStarted; const stateWalBefore = walSize(stateDatabase.path); const agentWalBefore = agentDatabases.map((database) => walSize(database.path)); const stateIntegrity = readIntegrity(stateDatabase.db); const agentIntegrity = agentDatabases.map((database) => readIntegrity(database.db)); const queries = runHotQueries({ agentDb: agentDatabases[0]?.db ?? stateDatabase.db, config, stateDb: stateDatabase.db, }); const checkpointStarted = nowMs(); checkpoint(stateDatabase.db); agentDatabases.forEach((database) => checkpoint(database.db)); const checkpointMs = nowMs() - checkpointStarted; const report: BenchmarkReport = { integrity: { agent: agentIntegrity, state: stateIntegrity, }, node: process.version, schemaVersion: 2, paths: { agentDatabases: agentDatabases.map((database) => database.path), artifact: options.output, stateDatabase: stateDatabase.path, stateDir, }, profile: options.profile, queries, versions: { agentSchema: OPENCLAW_AGENT_SCHEMA_VERSION, sqlite: readSqliteVersion(stateDatabase.db), stateSchema: OPENCLAW_STATE_SCHEMA_VERSION, }, rows: { agentCacheEntries: perAgentEntries * config.agentCount, agentDatabases: config.agentCount, channelIngressEvents: config.channelIngressEvents, cronJobs: config.cronJobs, cronTaskRuns: config.cronTaskRuns, deliveryQueueEntries: config.deliveryQueueEntries, pluginStateEntries: config.pluginStateEntries, stateRows: stateRowCount(config), transcriptEvents: SQLITE_PERF_TRANSCRIPT_EVENTS, }, timingsMs: { checkpoint: Number(checkpointMs.toFixed(3)), seed: Number(seedMs.toFixed(3)), total: Number((nowMs() - started).toFixed(3)), }, walBytes: { agentAfter: agentDatabases.map((database) => walSize(database.path)), agentBefore: agentWalBefore, stateAfter: walSize(stateDatabase.path), stateBefore: stateWalBefore, }, }; if (options.output) { fs.mkdirSync(path.dirname(options.output), { recursive: true }); fs.writeFileSync(options.output, `${JSON.stringify(report, null, 2)}\n`, "utf8"); } printProofLines(report); } finally { closeOpenClawAgentDatabasesForTest(); closeOpenClawStateDatabaseForTest(); if (!options.stateDir) { fs.rmSync(stateDir, { recursive: true, force: true }); } } } if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { try { main(); } catch (error) { if (error instanceof CliUsageError) { console.error(`error: ${error.message}`); process.exit(2); } throw error; } }