diff --git a/CHANGELOG.md b/CHANGELOG.md index 9559ee35910..1affb4ae1be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai ### Changes +- Gateway/performance: lazy-load the heavy cron runtime after the rest of Gateway startup, defer restart-sentinel refresh after readiness, and let the Gateway startup benchmark write per-run V8 CPU profiles with `--cpu-prof-dir`. - Gateway/performance: keep raw channel-config schema parsing from discovering bundled plugin runtime metadata, and add `pnpm gateway:watch --benchmark-no-force` for profiling startup without the default port cleanup. - Plugins/onboarding: let Manual setup install optional official plugins, including ClawHub-backed diagnostics with npm fallback, and expose the external Codex plugin as a selectable provider setup choice. Thanks @vincentkoc. - Plugins/CLI: include package dependency install state in `openclaw plugins list --json` so scripts can spot missing plugin dependencies without runtime-loading plugins. diff --git a/scripts/bench-gateway-startup.ts b/scripts/bench-gateway-startup.ts index 1fefdf9eeb8..e7867bfc1d9 100644 --- a/scripts/bench-gateway-startup.ts +++ b/scripts/bench-gateway-startup.ts @@ -60,6 +60,7 @@ type CaseResult = { type CliOptions = { cases: GatewayBenchCase[]; + cpuProfDir?: string; entry: string; json: boolean; output?: string; @@ -201,6 +202,7 @@ function resolveCases(caseIds: string[]): GatewayBenchCase[] { function parseOptions(): CliOptions { return { cases: resolveCases(parseRepeatableFlag("--case")), + cpuProfDir: parseFlagValue("--cpu-prof-dir"), entry: parseFlagValue("--entry") ?? DEFAULT_ENTRY, json: hasFlag("--json"), output: parseFlagValue("--output"), @@ -223,6 +225,7 @@ Options: --runs Measured runs per case (default: ${DEFAULT_RUNS}) --warmup Warmup runs per case (default: ${DEFAULT_WARMUP}) --timeout-ms Per-run timeout (default: ${DEFAULT_TIMEOUT_MS}) + --cpu-prof-dir Write one V8 CPU profile per run --output Write machine-readable JSON to a file --json Emit machine-readable JSON --help, -h Show this text @@ -658,7 +661,9 @@ function readProcessTreeCpuMs(rootPid: number | undefined): number | null { async function runGatewaySample(options: { benchCase: GatewayBenchCase; + cpuProfDir?: string; entry: string; + sampleIndex: number; timeoutMs: number; }): Promise { const root = mkdtempSync(path.join(tmpdir(), "openclaw-gateway-bench-")); @@ -674,24 +679,34 @@ async function runGatewaySample(options: { let readyLogMs: number | null = null; let childExited = false; - const child = spawn( - process.execPath, - [ - options.entry, - "gateway", - "run", - "--port", - String(port), - "--bind", - "loopback", - "--auth", - "none", - "--tailscale", - "off", - "--allow-unconfigured", - ], - { cwd: process.cwd(), detached: process.platform !== "win32", env }, - ); + const childArgs = [ + ...(options.cpuProfDir + ? [ + "--cpu-prof", + "--cpu-prof-dir", + options.cpuProfDir, + "--cpu-prof-name", + `openclaw-gateway-${options.benchCase.id}-${options.sampleIndex}-${Date.now()}.cpuprofile`, + ] + : []), + options.entry, + "gateway", + "run", + "--port", + String(port), + "--bind", + "loopback", + "--auth", + "none", + "--tailscale", + "off", + "--allow-unconfigured", + ]; + const child = spawn(process.execPath, childArgs, { + cwd: process.cwd(), + detached: process.platform !== "win32", + env, + }); const cpuStartMs = readProcessTreeCpuMs(child.pid); const sampleRss = () => { const rssMb = readProcessRssMb(child.pid); @@ -773,6 +788,7 @@ async function runGatewaySample(options: { async function runCase(options: { benchCase: GatewayBenchCase; + cpuProfDir?: string; entry: string; runs: number; timeoutMs: number; @@ -783,7 +799,9 @@ async function runCase(options: { for (let index = 0; index < total; index += 1) { const sample = await runGatewaySample({ benchCase: options.benchCase, + cpuProfDir: options.cpuProfDir, entry: options.entry, + sampleIndex: index + 1, timeoutMs: options.timeoutMs, }); if (index >= options.warmup) { @@ -828,11 +846,15 @@ async function main() { } const options = parseOptions(); + if (options.cpuProfDir) { + mkdirSync(options.cpuProfDir, { recursive: true }); + } const results: CaseResult[] = []; for (const benchCase of options.cases) { results.push( await runCase({ benchCase, + cpuProfDir: options.cpuProfDir, entry: options.entry, runs: options.runs, timeoutMs: options.timeoutMs, diff --git a/test/scripts/bench-gateway-startup.test.ts b/test/scripts/bench-gateway-startup.test.ts index f6477726318..29ac4588d7b 100644 --- a/test/scripts/bench-gateway-startup.test.ts +++ b/test/scripts/bench-gateway-startup.test.ts @@ -16,6 +16,7 @@ describe("gateway startup benchmark script", () => { expect(result.status).toBe(0); expect(result.stdout).toContain("OpenClaw Gateway startup benchmark"); expect(result.stdout).toContain("--case "); + expect(result.stdout).toContain("--cpu-prof-dir "); expect(result.stdout).toContain("default (gateway default)"); expect(result.stdout).not.toContain("[gateway-startup-bench]"); expect(result.stderr).toBe("");