Files
openclaw/test/scripts/bench-sqlite-state.test.ts
Vincent Koc 608e365ce4 improve(sqlite): report production workloads independently (#130994)
* perf(scripts): report SQLite scenarios independently

* perf(sqlite): benchmark production state workloads

* test(sqlite): use tracked benchmark temp dirs

* perf(sqlite): harden workload evidence

* fix(perf): require matching SQLite workloads
2026-08-28 01:02:42 +08:00

166 lines
6.0 KiB
TypeScript

// Bench SQLite State tests cover benchmark CLI argument safety.
import { spawnSync } from "node:child_process";
import { readFileSync } from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { collectSqliteQueryPlanEvidence } from "../../scripts/lib/sqlite-query-plan-evidence.js";
import { parseSqliteStateBenchmarkCli } from "../../scripts/lib/sqlite-state-benchmark-cli.js";
import { OPENCLAW_AGENT_SCHEMA_VERSION } from "../../src/state/openclaw-agent-db-contract.js";
import { OPENCLAW_STATE_SCHEMA_VERSION } from "../../src/state/openclaw-state-db-contract.js";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
function runBench(args: string[]) {
return spawnSync(
process.execPath,
["--import", "tsx", "scripts/bench-sqlite-state.ts", ...args],
{
cwd: process.cwd(),
encoding: "utf8",
},
);
}
describe("scripts/bench-sqlite-state", () => {
it("rejects unknown args before seeding benchmark databases", () => {
const result = runBench(["--wat"]);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr.trim()).toBe("error: Unknown argument: --wat");
});
it("rejects missing output values before seeding benchmark databases", () => {
expect(() => parseSqliteStateBenchmarkCli(["--output", "--profile", "smoke"])).toThrow(
"--output requires a value",
);
});
it("rejects short flag output values before seeding benchmark databases", () => {
expect(() => parseSqliteStateBenchmarkCli(["--output", "-h"])).toThrow(
"--output requires a value",
);
});
it("rejects invalid profiles without printing a stack trace", () => {
expect(() => parseSqliteStateBenchmarkCli(["--profile", "huge"])).toThrow(
'--profile must be one of smoke, default, large; got "huge"',
);
});
it("rejects duplicate single-value controls before seeding benchmark databases", () => {
expect(() =>
parseSqliteStateBenchmarkCli(["--profile", "smoke", "--profile", "large"]),
).toThrow("--profile was provided more than once");
expect(parseSqliteStateBenchmarkCli(["--help", "--profile", "huge"])).toEqual({
help: true,
});
});
it("reports production-shaped SQLite scenarios in the existing smoke benchmark", () => {
const outputDir = tempDirs.make("openclaw-sqlite-bench-test-");
const outputPath = path.join(outputDir, "report.json");
const result = runBench(["--profile", "smoke", "--output", outputPath]);
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain("SQLITE_PERF_TRANSCRIPT_ROWS=256");
const proofLines = result.stdout
.split("\n")
.filter((line) => line.startsWith("SQLITE_PERF_SCENARIO "));
expect(proofLines).toHaveLength(12);
const report = JSON.parse(readFileSync(outputPath, "utf8")) as {
schemaVersion: number;
queries: Array<{
database: string;
id: string;
p50Ms: number;
p95Ms: number;
plan: {
fullTableScans: string[];
indexes: string[];
raw: string[];
tempSorts: string[];
};
rows: number;
runs: number;
sql: string;
}>;
versions: { agentSchema: number; sqlite: string; stateSchema: number };
};
expect(report.schemaVersion).toBe(2);
expect(report.versions).toEqual({
agentSchema: OPENCLAW_AGENT_SCHEMA_VERSION,
sqlite: expect.stringMatching(/^\d+\.\d+\.\d+$/u),
stateSchema: OPENCLAW_STATE_SCHEMA_VERSION,
});
const expectedIds = [
"cron.store.load",
"task-runs.cron.list",
"task-runs.cron-source.list",
"delivery.pending.load",
"ingress.pending.first-page",
"ingress.pending.seek-page",
"ingress.pending.id-page",
"ingress.pending.id-seek-page",
"plugin-state.namespace.live",
"agent-cache.plugin-model-catalog.list",
"transcript.tail.metadata",
"transcript.tail.payload",
];
expect(report.queries.map((query) => query.id)).toEqual(expectedIds);
expect(new Set(report.queries.map((query) => query.id)).size).toBe(expectedIds.length);
const expectedRows = new Map([
["cron.store.load", 13],
["task-runs.cron.list", 1_000],
["task-runs.cron-source.list", 250],
["delivery.pending.load", 696],
["ingress.pending.first-page", 100],
["ingress.pending.seek-page", 100],
["ingress.pending.id-page", 100],
["ingress.pending.id-seek-page", 100],
["plugin-state.namespace.live", 675],
["agent-cache.plugin-model-catalog.list", 64],
["transcript.tail.metadata", 256],
["transcript.tail.payload", 256],
]);
for (const query of report.queries) {
expect(["agent", "state"]).toContain(query.database);
expect(query.rows).toBe(expectedRows.get(query.id));
expect(query.runs).toBe(20);
expect(Number.isFinite(query.p50Ms)).toBe(true);
expect(Number.isFinite(query.p95Ms)).toBe(true);
expect(query.sql).toContain("SELECT");
expect(query.plan.raw.length).toBeGreaterThan(0);
expect(query.plan.fullTableScans).toEqual([]);
if (!query.id.startsWith("task-runs.")) {
expect(query.plan.tempSorts).toEqual([]);
}
}
});
it("normalizes modern plan forms without inventing table scans", () => {
expect(
collectSqliteQueryPlanEvidence([
"SEARCH events USING AUTOMATIC PARTIAL COVERING INDEX (status=?)",
"SCAN json_each VIRTUAL TABLE INDEX 1:",
"SCAN 2-ROW VALUES CLAUSE",
"SCAN task_runs",
]),
).toEqual({
fullTableScans: ["SCAN task_runs"],
indexes: ["AUTOMATIC PARTIAL COVERING INDEX"],
raw: [
"SEARCH events USING AUTOMATIC PARTIAL COVERING INDEX (status=?)",
"SCAN json_each VIRTUAL TABLE INDEX 1:",
"SCAN 2-ROW VALUES CLAUSE",
"SCAN task_runs",
],
tempSorts: [],
});
});
});