perf(gateway): add startup CPU profile option

This commit is contained in:
Peter Steinberger
2026-05-03 13:30:23 +01:00
parent 0b1fbeabed
commit 706132c655
3 changed files with 42 additions and 18 deletions

View File

@@ -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.

View File

@@ -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 <n> Measured runs per case (default: ${DEFAULT_RUNS})
--warmup <n> Warmup runs per case (default: ${DEFAULT_WARMUP})
--timeout-ms <ms> Per-run timeout (default: ${DEFAULT_TIMEOUT_MS})
--cpu-prof-dir <dir> Write one V8 CPU profile per run
--output <path> 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<GatewaySample> {
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,

View File

@@ -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 <id>");
expect(result.stdout).toContain("--cpu-prof-dir <dir>");
expect(result.stdout).toContain("default (gateway default)");
expect(result.stdout).not.toContain("[gateway-startup-bench]");
expect(result.stderr).toBe("");