Z ZPI 文档 v0.3.8
介绍页

源码

scripts/benchmark-lazy-restore.ts

typescript223 行查看原文
/** Opt-in large-fixture benchmark. Run: node --import tsx scripts/benchmark-lazy-restore.ts [100,1024] [repetitions]
 * Fixtures live in a temporary workspace directory and are removed after each case.
 * Uses real SQLite and JSONL; fresh processes, warm OS cache, no model/network/GUI.
 */

import { spawnSync } from "node:child_process";
import { randomUUID } from "node:crypto";
import {
  closeSync,
  fsyncSync,
  mkdirSync,
  mkdtempSync,
  openSync,
  readFileSync,
  rmSync,
  statSync,
  writeFileSync,
  writeSync,
} from "node:fs";
import { cpus, platform, release, totalmem } from "node:os";
import { join, resolve } from "node:path";
import { performance } from "node:perf_hooks";
import { DatabaseSync } from "node:sqlite";
import { type SessionEntry, SessionManager } from "@zpi-dev/coding-agent";
import { HistoryIndex } from "../packages/desktop/src/main/history-index.ts";
import { encodeTranscriptEntry } from "../packages/desktop/src/main/transcript-v2.ts";

const MiB = 1024 * 1024;
const script = resolve("scripts/benchmark-lazy-restore.ts");
const stamp = "2026-10-14T00:00:00.000Z";
function database(dir: string) {
  const db = new DatabaseSync(join(dir, "state.sqlite"));
  db.exec(
    "PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000",
  );
  return db;
}
function fixture(dir: string, sizeMiB: number, compressed: boolean) {
  const path = join(dir, "session.jsonl");
  const fd = openSync(path, "wx");
  let seq = 0;
  let bytes = 0;
  let parentId: string | null = null;
  const write = (value: unknown) => {
    const line = `${JSON.stringify(value)}\n`;
    bytes += Buffer.byteLength(line);
    writeSync(fd, line);
  };
  write({
    type: "session",
    version: 2,
    format: "zpi",
    generation: randomUUID(),
    id: "benchmark",
    cwd: dir,
    timestamp: stamp,
  });
  const append = (fields: object) => {
    const id = `entry-${++seq}`;
    write(encodeTranscriptEntry({ ...fields, id, parentId, timestamp: stamp } as SessionEntry, seq));
    parentId = id;
    return id;
  };
  const payload = "x".repeat(64 * 1024);
  let messageCount = 0;
  const message = () => {
    messageCount++;
    return append({ type: "message", message: { role: "user", content: payload, timestamp: 0 } });
  };
  while (bytes < sizeMiB * MiB - (compressed ? 5 * MiB : 0)) message();
  let keptSeq: number | undefined;
  let compactionSeq: number | undefined;
  if (compressed) {
    const kept = message();
    keptSeq = seq;
    for (let i = 1; i < 40; i++) message();
    append({
      type: "compaction",
      summary: "Fixed summary",
      firstKeptEntryId: kept,
      systemMessage: { role: "system", content: "Fixed instructions", timestamp: 0 },
    });
    compactionSeq = seq;
    for (let i = 0; i < 40; i++) message();
  }
  fsyncSync(fd);
  closeSync(fd);
  const manifest = {
    sizeMiB,
    compressed,
    fileBytes: bytes,
    entryCount: seq,
    messageCount,
    expectedContextMessages: compressed ? 82 : messageCount,
    expectedPayloadBytes: compressed ? 5 * MiB : messageCount * payload.length,
    keptSeq,
    compactionSeq,
  };
  writeFileSync(join(dir, "manifest.json"), JSON.stringify(manifest));
  return manifest;
}
async function worker(dir: string, mode: string) {
  const manifest = JSON.parse(readFileSync(join(dir, "manifest.json"), "utf8"));
  const db = database(dir);
  if (mode === "rebuild") {
    for (const { name } of db
      .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'transcript_%'")
      .all())
      db.exec(`DROP TABLE ${name}`);
  }
  global.gc?.();
  const before = process.memoryUsage();
  let parsedEntries = 0;
  const parse = JSON.parse;
  JSON.parse = (text, reviver) => {
    const value = parse(text, reviver);
    if (
      value &&
      typeof value === "object" &&
      Number.isSafeInteger(value.seq) &&
      typeof value.type === "string"
    )
      parsedEntries++;
    return value;
  };
  const start = performance.now();
  const index = new HistoryIndex(join(dir, "session.jsonl"), db);
  await index.load();
  const manager = await SessionManager.openAsync(index);
  const openMs = performance.now() - start;
  const openReadBytes = index.readBytes;
  const afterOpen = process.memoryUsage();
  const openParsedEntries = parsedEntries;
  let materializeMs = 0;
  let contextMessages = 0;
  let payloadBytes = 0;
  if (mode === "context") {
    const begin = performance.now();
    const context = await manager.materializeContext();
    materializeMs = performance.now() - begin;
    contextMessages = context.messages.length;
    for (const message of context.messages)
      if (message.role === "user" && typeof message.content === "string" && message.content.startsWith("xxx"))
        payloadBytes += message.content.length;
    if (
      contextMessages !== manifest.expectedContextMessages ||
      payloadBytes !== manifest.expectedPayloadBytes
    )
      throw new Error("Context truncation or fixture mismatch");
  }
  const after = process.memoryUsage();
  const result = {
    mode,
    openParsedEntries,
    contextParsedEntries: parsedEntries - openParsedEntries,
    openMs,
    materializeMs,
    openReadBytes,
    contextReadBytes: index.readBytes - openReadBytes,
    contextMessages,
    payloadBytes,
    before,
    afterOpen,
    after,
    processPeakRssBytes: process.resourceUsage().maxRSS * 1024,
    sqliteBytes: statSync(join(dir, "state.sqlite")).size,
  };
  db.close();
  console.log(JSON.stringify(result));
}
if (process.argv[2] === "--worker") {
  await worker(process.argv[3], process.argv[4]);
} else {
  const sizes = (process.argv[2] ?? "100,1024").split(",").map(Number);
  const repetitions = Number(process.argv[3] ?? 1);
  if (sizes.some((n) => !Number.isInteger(n) || n < 8) || !Number.isInteger(repetitions) || repetitions < 1)
    throw new Error("Expected MiB sizes >=8 and positive repetitions");
  const base = resolve(".benchmark-tmp");
  mkdirSync(base, { recursive: true });
  console.log(
    JSON.stringify({
      environment: {
        node: process.version,
        os: `${platform()} ${release()}`,
        cpu: cpus()[0]?.model,
        logicalCpus: cpus().length,
        totalMemoryBytes: totalmem(),
        repetitions,
        cache: "OS cache not flushed; warm-index measurements run after rebuild",
        memory: "Endpoint memoryUsage and process lifetime maxRSS, not sampled per-phase peaks",
      },
    }),
  );
  try {
    for (const size of sizes)
      for (const compressed of [true, false]) {
        const dir = mkdtempSync(join(base, "restore-"));
        try {
          const manifest = fixture(dir, size, compressed);
          const measurements: object[] = [];
          for (const mode of [
            "rebuild",
            ...Array.from({ length: repetitions }, () => ["open", "context"]).flat(),
          ]) {
            const child = spawnSync(
              process.execPath,
              ["--expose-gc", "--max-old-space-size=4096", "--import", "tsx", script, "--worker", dir, mode],
              { encoding: "utf8", maxBuffer: MiB },
            );
            if (child.status !== 0) throw new Error(`${mode} failed: ${child.stderr}\n${child.stdout}`);
            measurements.push(JSON.parse(child.stdout.trim().split("\n").at(-1) ?? ""));
          }
          const result = { manifest, measurements };
          console.log(JSON.stringify(result));
        } finally {
          rmSync(dir, { recursive: true, force: true });
        }
      }
  } finally {
    rmSync(base, { recursive: true, force: true });
  }
}