mirror of https://github.com/openclaw/openclaw.git
142 lines
3.8 KiB
JavaScript
142 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { spawnSync } from "node:child_process";
|
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const isLinux = process.platform === "linux";
|
|
const isMac = process.platform === "darwin";
|
|
|
|
if (!isLinux && !isMac) {
|
|
console.log(`[startup-memory] Skipping on unsupported platform: ${process.platform}`);
|
|
process.exit(0);
|
|
}
|
|
|
|
const repoRoot = process.cwd();
|
|
const tmpHome = mkdtempSync(path.join(os.tmpdir(), "openclaw-startup-memory-"));
|
|
const tmpDir = process.env.TMPDIR || process.env.TEMP || process.env.TMP || os.tmpdir();
|
|
const rssHookPath = path.join(tmpHome, "measure-rss.mjs");
|
|
const MAX_RSS_MARKER = "__OPENCLAW_MAX_RSS_KB__=";
|
|
|
|
writeFileSync(
|
|
rssHookPath,
|
|
[
|
|
"process.on('exit', () => {",
|
|
" const usage = typeof process.resourceUsage === 'function' ? process.resourceUsage() : null;",
|
|
` if (usage && typeof usage.maxRSS === 'number') console.error('${MAX_RSS_MARKER}' + String(usage.maxRSS));`,
|
|
"});",
|
|
"",
|
|
].join("\n"),
|
|
"utf8",
|
|
);
|
|
|
|
const DEFAULT_LIMITS_MB = {
|
|
help: 500,
|
|
statusJson: 900,
|
|
gatewayStatus: 900,
|
|
};
|
|
|
|
const cases = [
|
|
{
|
|
id: "help",
|
|
label: "--help",
|
|
args: ["openclaw.mjs", "--help"],
|
|
limitMb: Number(process.env.OPENCLAW_STARTUP_MEMORY_HELP_MB ?? DEFAULT_LIMITS_MB.help),
|
|
},
|
|
{
|
|
id: "statusJson",
|
|
label: "status --json",
|
|
args: ["openclaw.mjs", "status", "--json"],
|
|
limitMb: Number(
|
|
process.env.OPENCLAW_STARTUP_MEMORY_STATUS_JSON_MB ?? DEFAULT_LIMITS_MB.statusJson,
|
|
),
|
|
},
|
|
{
|
|
id: "gatewayStatus",
|
|
label: "gateway status",
|
|
args: ["openclaw.mjs", "gateway", "status"],
|
|
limitMb: Number(
|
|
process.env.OPENCLAW_STARTUP_MEMORY_GATEWAY_STATUS_MB ?? DEFAULT_LIMITS_MB.gatewayStatus,
|
|
),
|
|
},
|
|
];
|
|
|
|
function parseMaxRssMb(stderr) {
|
|
const match = stderr.match(new RegExp(`^${MAX_RSS_MARKER}(\\d+)\\s*$`, "m"));
|
|
if (!match) {
|
|
return null;
|
|
}
|
|
return Number(match[1]) / 1024;
|
|
}
|
|
|
|
function buildBenchEnv() {
|
|
const env = {
|
|
HOME: tmpHome,
|
|
USERPROFILE: tmpHome,
|
|
XDG_CONFIG_HOME: path.join(tmpHome, ".config"),
|
|
XDG_DATA_HOME: path.join(tmpHome, ".local", "share"),
|
|
XDG_CACHE_HOME: path.join(tmpHome, ".cache"),
|
|
PATH: process.env.PATH ?? "",
|
|
TMPDIR: tmpDir,
|
|
TEMP: tmpDir,
|
|
TMP: tmpDir,
|
|
LANG: process.env.LANG ?? "C.UTF-8",
|
|
TERM: process.env.TERM ?? "dumb",
|
|
};
|
|
|
|
if (process.env.LC_ALL) {
|
|
env.LC_ALL = process.env.LC_ALL;
|
|
}
|
|
if (process.env.CI) {
|
|
env.CI = process.env.CI;
|
|
}
|
|
if (process.env.NODE_DISABLE_COMPILE_CACHE) {
|
|
env.NODE_DISABLE_COMPILE_CACHE = process.env.NODE_DISABLE_COMPILE_CACHE;
|
|
}
|
|
|
|
return env;
|
|
}
|
|
|
|
function runCase(testCase) {
|
|
const env = buildBenchEnv();
|
|
const result = spawnSync(process.execPath, ["--import", rssHookPath, ...testCase.args], {
|
|
cwd: repoRoot,
|
|
env,
|
|
encoding: "utf8",
|
|
maxBuffer: 20 * 1024 * 1024,
|
|
});
|
|
const stderr = result.stderr ?? "";
|
|
const maxRssMb = parseMaxRssMb(stderr);
|
|
const matrixBootstrapWarning = /matrix: crypto runtime bootstrap failed/i.test(stderr);
|
|
|
|
if (result.status !== 0) {
|
|
throw new Error(
|
|
`${testCase.label} exited with ${String(result.status)}\n${stderr.trim() || result.stdout || ""}`,
|
|
);
|
|
}
|
|
if (maxRssMb == null) {
|
|
throw new Error(`${testCase.label} did not report max RSS\n${stderr.trim()}`);
|
|
}
|
|
if (matrixBootstrapWarning) {
|
|
throw new Error(`${testCase.label} triggered Matrix crypto bootstrap during startup`);
|
|
}
|
|
if (maxRssMb > testCase.limitMb) {
|
|
throw new Error(
|
|
`${testCase.label} used ${maxRssMb.toFixed(1)} MB RSS (limit ${testCase.limitMb} MB)`,
|
|
);
|
|
}
|
|
|
|
console.log(
|
|
`[startup-memory] ${testCase.label}: ${maxRssMb.toFixed(1)} MB RSS (limit ${testCase.limitMb} MB)`,
|
|
);
|
|
}
|
|
|
|
try {
|
|
for (const testCase of cases) {
|
|
runCase(testCase);
|
|
}
|
|
} finally {
|
|
rmSync(tmpHome, { recursive: true, force: true });
|
|
}
|