


























@@ -1,4 +1,4 @@
1-import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
1+import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
22import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
33import { request } from "node:http";
44import { createServer } from "node:net";
@@ -23,6 +23,7 @@ type GatewaySample = {
2323exitCode: number | null;
2424firstOutputMs: number | null;
2525healthz: ProbeResult;
26+maxRssMb: number | null;
2627outputTail: string;
2728readyLogMs: number | null;
2829readyz: ProbeResult;
@@ -45,6 +46,7 @@ type CaseResult = {
4546summary: {
4647firstOutputMs: SummaryStats | null;
4748healthzMs: SummaryStats | null;
49+maxRssMb: SummaryStats | null;
4850readyLogMs: SummaryStats | null;
4951readyzMs: SummaryStats | null;
5052startupTrace: Record<string, SummaryStats>;
@@ -253,6 +255,11 @@ function summarizeCase(benchCase: GatewayBenchCase, samples: GatewaySample[]): C
253255.map((sample) => sample.healthz.ms)
254256.filter((value): value is number => typeof value === "number"),
255257),
258+maxRssMb: summarizeNumbers(
259+samples
260+.map((sample) => sample.maxRssMb)
261+.filter((value): value is number => typeof value === "number"),
262+),
256263readyLogMs: summarizeNumbers(
257264samples
258265.map((sample) => sample.readyLogMs)
@@ -275,13 +282,27 @@ function formatMs(value: number | null): string {
275282return `${value.toFixed(1)}ms`;
276283}
277284285+function formatMb(value: number | null): string {
286+if (value == null) {
287+return "n/a";
288+}
289+return `${value.toFixed(1)}MB`;
290+}
291+278292function formatStats(stats: SummaryStats | null): string {
279293if (!stats) {
280294return "n/a";
281295}
282296return `p50=${formatMs(stats.p50)} avg=${formatMs(stats.avg)} min=${formatMs(stats.min)} max=${formatMs(stats.max)}`;
283297}
284298299+function formatMemoryStats(stats: SummaryStats | null): string {
300+if (!stats) {
301+return "n/a";
302+}
303+return `p50=${formatMb(stats.p50)} avg=${formatMb(stats.avg)} min=${formatMb(stats.min)} max=${formatMb(stats.max)}`;
304+}
305+285306async function getFreePort(): Promise<number> {
286307return new Promise((resolve, reject) => {
287308const server = createServer();
@@ -400,6 +421,7 @@ function sanitizedEnv(
400421OPENCLAW_GATEWAY_STARTUP_TRACE: "1",
401422OPENCLAW_HOME: root,
402423OPENCLAW_LOCAL_CHECK: "0",
424+OPENCLAW_NO_RESPAWN: "1",
403425OPENCLAW_STATE_DIR: path.join(root, "state"),
404426OPENCLAW_TEST_DISABLE_UPDATE_CHECK: "1",
405427 ...benchCase.env,
@@ -475,6 +497,21 @@ function parseStartupTraceMetrics(raw: string): Array<{ key: string; value: numb
475497return metrics;
476498}
477499500+function readProcessRssMb(pid: number | undefined): number | null {
501+if (!pid || process.platform === "win32") {
502+return null;
503+}
504+const result = spawnSync("ps", ["-o", "rss=", "-p", String(pid)], {
505+encoding: "utf8",
506+stdio: ["ignore", "pipe", "ignore"],
507+});
508+if (result.status !== 0) {
509+return null;
510+}
511+const rssKb = Number.parseInt(result.stdout.trim(), 10);
512+return Number.isFinite(rssKb) && rssKb > 0 ? rssKb / 1024 : null;
513+}
514+478515async function runGatewaySample(options: {
479516benchCase: GatewayBenchCase;
480517entry: string;
@@ -489,6 +526,7 @@ async function runGatewaySample(options: {
489526const startupTrace: Record<string, number> = {};
490527const output: string[] = [];
491528let firstOutputMs: number | null = null;
529+let maxRssMb: number | null = null;
492530let readyLogMs: number | null = null;
493531let childExited = false;
494532@@ -510,6 +548,15 @@ async function runGatewaySample(options: {
510548],
511549{ cwd: process.cwd(), detached: process.platform !== "win32", env },
512550);
551+const sampleRss = () => {
552+const rssMb = readProcessRssMb(child.pid);
553+if (rssMb != null) {
554+maxRssMb = maxRssMb == null ? rssMb : Math.max(maxRssMb, rssMb);
555+}
556+};
557+sampleRss();
558+const rssTimer = setInterval(sampleRss, 100);
559+rssTimer.unref?.();
513560const childExitPromise = new Promise<{ exitCode: number | null; signal: string | null }>(
514561(resolve) => {
515562child.once("exit", (exitCode, signal) => {
@@ -555,13 +602,16 @@ async function runGatewaySample(options: {
555602}),
556603]);
557604const exit = await stopChild(child);
605+clearInterval(rssTimer);
606+sampleRss();
558607await childExitPromise.catch(() => null);
559608rmSync(root, { force: true, maxRetries: 3, recursive: true, retryDelay: 100 });
560609561610return {
562611exitCode: exit.exitCode,
563612 firstOutputMs,
564613 healthz,
614+ maxRssMb,
565615outputTail: output.join("").split(/\r?\n/u).slice(-20).join("\n"),
566616 readyLogMs,
567617 readyz,
@@ -588,11 +638,11 @@ async function runCase(options: {
588638if (index >= options.warmup) {
589639samples.push(sample);
590640console.log(
591-`[gateway-startup-bench] ${options.benchCase.id} run ${samples.length}/${options.runs}: healthz=${formatMs(sample.healthz.ms)} readyz=${formatMs(sample.readyz.ms)} readyLog=${formatMs(sample.readyLogMs)}`,
641+`[gateway-startup-bench] ${options.benchCase.id} run ${samples.length}/${options.runs}: healthz=${formatMs(sample.healthz.ms)} readyz=${formatMs(sample.readyz.ms)} readyLog=${formatMs(sample.readyLogMs)} rss=${formatMb(sample.maxRssMb)}`,
592642);
593643} else {
594644console.log(
595-`[gateway-startup-bench] ${options.benchCase.id} warmup ${index + 1}/${options.warmup}: healthz=${formatMs(sample.healthz.ms)} readyz=${formatMs(sample.readyz.ms)}`,
645+`[gateway-startup-bench] ${options.benchCase.id} warmup ${index + 1}/${options.warmup}: healthz=${formatMs(sample.healthz.ms)} readyz=${formatMs(sample.readyz.ms)} rss=${formatMb(sample.maxRssMb)}`,
596646);
597647}
598648}
@@ -605,6 +655,7 @@ function printResult(result: CaseResult): void {
605655console.log(` /healthz: ${formatStats(result.summary.healthzMs)}`);
606656console.log(` ready log: ${formatStats(result.summary.readyLogMs)}`);
607657console.log(` /readyz: ${formatStats(result.summary.readyzMs)}`);
658+console.log(` max RSS: ${formatMemoryStats(result.summary.maxRssMb)}`);
608659const trace = Object.entries(result.summary.startupTrace)
609660.filter(([name]) => !name.endsWith(".total"))
610661.toSorted((a, b) => (b[1].avg ?? 0) - (a[1].avg ?? 0))
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。