mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-14 16:23:01 +00:00
test(e2e): sample kitchen sink RSS on Windows
This commit is contained in:
@@ -4,7 +4,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { setTimeout as delay } from "node:timers/promises";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
const PLUGIN_SPEC =
|
||||
process.env.OPENCLAW_KITCHEN_SINK_NPM_SPEC || "npm:@openclaw/kitchen-sink@latest";
|
||||
@@ -517,12 +517,21 @@ function assertToolInvokeResult(payload) {
|
||||
}
|
||||
}
|
||||
|
||||
async function sampleProcess(pid) {
|
||||
if (!pid || process.platform === "win32") {
|
||||
export async function sampleProcess(pid, options = {}) {
|
||||
const platform = options.platform ?? process.platform;
|
||||
const run = options.runCommand ?? runCommand;
|
||||
if (!pid) {
|
||||
return null;
|
||||
}
|
||||
if (platform === "win32") {
|
||||
return sampleWindowsProcess(pid, run);
|
||||
}
|
||||
return samplePosixProcess(pid, run);
|
||||
}
|
||||
|
||||
async function samplePosixProcess(pid, run) {
|
||||
try {
|
||||
const { stdout } = await runCommand("ps", ["-o", "rss=,pcpu=", "-p", String(pid)], {
|
||||
const { stdout } = await run("ps", ["-o", "rss=,pcpu=", "-p", String(pid)], {
|
||||
timeoutMs: 5000,
|
||||
});
|
||||
const [rssKbRaw, cpuRaw] = stdout.trim().split(/\s+/u);
|
||||
@@ -540,7 +549,44 @@ async function sampleProcess(pid) {
|
||||
}
|
||||
}
|
||||
|
||||
function assertResourceCeiling(sample) {
|
||||
async function sampleWindowsProcess(pid, run) {
|
||||
const safePid = Number(pid);
|
||||
if (!Number.isInteger(safePid) || safePid <= 0) {
|
||||
return null;
|
||||
}
|
||||
const command = [
|
||||
"$ErrorActionPreference = 'Stop'",
|
||||
`$process = Get-Process -Id ${safePid} -ErrorAction Stop`,
|
||||
"$cpu = 0",
|
||||
"if ($null -ne $process.CPU) { $cpu = $process.CPU }",
|
||||
"[Console]::Out.Write(('{0} {1}' -f $process.WorkingSet64, $cpu))",
|
||||
].join("; ");
|
||||
for (const powershell of ["powershell.exe", "powershell"]) {
|
||||
try {
|
||||
const { stdout } = await run(
|
||||
powershell,
|
||||
["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", command],
|
||||
{ timeoutMs: 5000 },
|
||||
);
|
||||
const [workingSetBytesRaw, cpuSecondsRaw] = stdout.trim().split(/\s+/u);
|
||||
const workingSetBytes = Number.parseInt(workingSetBytesRaw ?? "", 10);
|
||||
const cpuSeconds = Number.parseFloat(cpuSecondsRaw ?? "");
|
||||
if (!Number.isFinite(workingSetBytes)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
rssMiB: Math.round((workingSetBytes / 1024 / 1024) * 10) / 10,
|
||||
cpuPercent: null,
|
||||
cpuSeconds: Number.isFinite(cpuSeconds) ? cpuSeconds : null,
|
||||
};
|
||||
} catch {
|
||||
// Try the next Windows PowerShell command name.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function assertResourceCeiling(sample) {
|
||||
if (!sample) {
|
||||
return;
|
||||
}
|
||||
@@ -590,7 +636,7 @@ function isNonEmptyString(value) {
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
export async function main() {
|
||||
const runner = resolveOpenClawRunner();
|
||||
const port = readPositiveInt(process.env.OPENCLAW_KITCHEN_SINK_RPC_PORT, DEFAULT_PORT);
|
||||
const { root, env } = makeEnv();
|
||||
@@ -725,4 +771,6 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
await main();
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
await main();
|
||||
}
|
||||
|
||||
55
test/scripts/kitchen-sink-rpc-walk.test.ts
Normal file
55
test/scripts/kitchen-sink-rpc-walk.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { assertResourceCeiling, sampleProcess } from "../../scripts/e2e/kitchen-sink-rpc-walk.mjs";
|
||||
|
||||
describe("kitchen-sink RPC process sampling", () => {
|
||||
it("samples RSS on Windows instead of silently disabling the resource guard", async () => {
|
||||
const calls: Array<{ command: string; args: string[] }> = [];
|
||||
const sample = await sampleProcess(1234, {
|
||||
platform: "win32",
|
||||
runCommand: async (command: string, args: string[]) => {
|
||||
calls.push({ command, args });
|
||||
return { stdout: `${256 * 1024 * 1024} 1.5`, stderr: "" };
|
||||
},
|
||||
});
|
||||
|
||||
expect(sample).toEqual({ cpuPercent: null, cpuSeconds: 1.5, rssMiB: 256 });
|
||||
expect(calls[0]?.command).toBe("powershell.exe");
|
||||
expect(calls[0]?.args.join(" ")).toContain("Get-Process -Id 1234");
|
||||
});
|
||||
|
||||
it("falls back to the legacy powershell command name on Windows", async () => {
|
||||
const commands: string[] = [];
|
||||
const sample = await sampleProcess(1234, {
|
||||
platform: "win32",
|
||||
runCommand: async (command: string) => {
|
||||
commands.push(command);
|
||||
if (command === "powershell.exe") {
|
||||
throw new Error("missing powershell.exe");
|
||||
}
|
||||
return { stdout: `${96 * 1024 * 1024} 0`, stderr: "" };
|
||||
},
|
||||
});
|
||||
|
||||
expect(commands).toEqual(["powershell.exe", "powershell"]);
|
||||
expect(sample?.rssMiB).toBe(96);
|
||||
});
|
||||
|
||||
it("samples RSS and CPU percent with ps on POSIX", async () => {
|
||||
const sample = await sampleProcess(4321, {
|
||||
platform: "linux",
|
||||
runCommand: async (command: string, args: string[]) => {
|
||||
expect(command).toBe("ps");
|
||||
expect(args).toEqual(["-o", "rss=,pcpu=", "-p", "4321"]);
|
||||
return { stdout: "262144 12.5\n", stderr: "" };
|
||||
},
|
||||
});
|
||||
|
||||
expect(sample).toEqual({ cpuPercent: 12.5, rssMiB: 256 });
|
||||
});
|
||||
|
||||
it("fails when the sampled RSS exceeds the configured ceiling", () => {
|
||||
expect(() => assertResourceCeiling({ rssMiB: 2049 })).toThrow(
|
||||
"gateway RSS exceeded 2048 MiB: 2049 MiB",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user