




















@@ -557,7 +557,7 @@ export async function sampleProcess(pid, options = {}) {
557557return null;
558558}
559559if (platform === "win32") {
560-return sampleWindowsProcess(pid, run);
560+return sampleWindowsProcess(pid, run, options.windowsCommandLineNeedles);
561561}
562562return samplePosixProcess(pid, run);
563563}
@@ -601,35 +601,146 @@ async function samplePosixProcess(pid, run) {
601601}
602602}
603603604-async function sampleWindowsProcess(pid, run) {
604+function parseTasklistCsvLine(line) {
605+const values = [];
606+let current = "";
607+let inQuotes = false;
608+for (let index = 0; index < line.length; index += 1) {
609+const char = line[index];
610+if (char === '"') {
611+if (inQuotes && line[index + 1] === '"') {
612+current += '"';
613+index += 1;
614+} else {
615+inQuotes = !inQuotes;
616+}
617+continue;
618+}
619+if (char === "," && !inQuotes) {
620+values.push(current);
621+current = "";
622+continue;
623+}
624+current += char;
625+}
626+values.push(current);
627+return values;
628+}
629+630+async function sampleWindowsPidWithTasklist(pid, run) {
631+const safePid = Number(pid);
632+if (!Number.isInteger(safePid) || safePid <= 0) {
633+return null;
634+}
635+try {
636+const { stdout } = await run(
637+"tasklist.exe",
638+["/FI", `PID eq ${safePid}`, "/FO", "CSV", "/NH"],
639+{ timeoutMs: 15000 },
640+);
641+const line = stdout
642+.split(/\r?\n/u)
643+.map((entry) => entry.trim())
644+.find((entry) => entry.startsWith('"'));
645+if (!line) {
646+return null;
647+}
648+const [, processIdRaw, , , memoryRaw] = parseTasklistCsvLine(line);
649+const processId = Number.parseInt(processIdRaw ?? "", 10);
650+const memoryKiB = Number.parseInt((memoryRaw ?? "").replace(/[^\d]/gu, ""), 10);
651+if (!Number.isFinite(memoryKiB)) {
652+return null;
653+}
654+return {
655+rssMiB: Math.round((memoryKiB / 1024) * 10) / 10,
656+cpuPercent: null,
657+cpuSeconds: null,
658+processId: Number.isFinite(processId) ? processId : safePid,
659+};
660+} catch {
661+return null;
662+}
663+}
664+665+export async function sampleWindowsProcessByPort(port, options = {}) {
666+const safePort = Number(port);
667+if (!Number.isInteger(safePort) || safePort <= 0) {
668+return null;
669+}
670+const run = options.runCommand ?? runCommand;
671+try {
672+const { stdout } = await run("netstat.exe", ["-ano", "-p", "tcp"], { timeoutMs: 15000 });
673+const pid = stdout
674+.split(/\r?\n/u)
675+.map((line) => line.trim())
676+.filter((line) => line.includes(`:${safePort}`) && /\bLISTENING\b/iu.test(line))
677+.map((line) => Number.parseInt(line.split(/\s+/u).at(-1) ?? "", 10))
678+.find((candidate) => Number.isInteger(candidate) && candidate > 0);
679+if (!pid) {
680+return null;
681+}
682+return (await sampleWindowsProcess(pid, run)) ?? sampleWindowsPidWithTasklist(pid, run);
683+} catch {
684+return null;
685+}
686+}
687+688+function powershellSingleQuoted(value) {
689+return `'${String(value).replace(/'/gu, "''")}'`;
690+}
691+692+async function sampleWindowsProcess(pid, run, commandLineNeedles = []) {
605693const safePid = Number(pid);
606694if (!Number.isInteger(safePid) || safePid <= 0) {
607695return null;
608696}
609-const command = [
610-"$ErrorActionPreference = 'Stop'",
611-`$process = Get-Process -Id ${safePid} -ErrorAction Stop`,
612-"$cpu = 0",
613-"if ($null -ne $process.CPU) { $cpu = $process.CPU }",
614-"[Console]::Out.Write(('{0} {1}' -f $process.WorkingSet64, $cpu))",
615-].join("; ");
697+const needles = commandLineNeedles
698+.map((needle) => String(needle ?? "").trim())
699+.filter((needle) => needle.length > 0);
700+const powershellNeedles = `@(${needles.map(powershellSingleQuoted).join(", ")})`;
701+const command =
702+needles.length === 0
703+ ? [
704+"$ErrorActionPreference = 'Stop'",
705+`$process = Get-Process -Id ${safePid} -ErrorAction Stop`,
706+"$cpu = 0",
707+"if ($null -ne $process.CPU) { $cpu = $process.CPU }",
708+"[Console]::Out.Write(('{0} {1} {2}' -f $process.WorkingSet64, $cpu, $process.Id))",
709+].join("; ")
710+ : [
711+"$ErrorActionPreference = 'Stop'",
712+`$rootPid = ${safePid}`,
713+`$commandLineNeedles = ${powershellNeedles}`,
714+"$ids = [System.Collections.Generic.HashSet[int]]::new()",
715+"[void]$ids.Add($rootPid)",
716+'if ($commandLineNeedles.Count -gt 0) { $queryNeedle = $commandLineNeedles[$commandLineNeedles.Count - 1].Replace("\'", "\'\'"); $candidates = Get-CimInstance Win32_Process -Filter "CommandLine LIKE \'%$queryNeedle%\'" | Select-Object ProcessId, CommandLine; foreach ($process in $candidates) { if ([int]$process.ProcessId -eq $PID) { continue }; $line = [string]$process.CommandLine; $matches = $true; foreach ($needle in $commandLineNeedles) { if ($line.IndexOf($needle, [StringComparison]::OrdinalIgnoreCase) -lt 0) { $matches = $false; break } }; if ($matches) { [void]$ids.Add([int]$process.ProcessId) } } }',
717+"if ($ids.Count -le 1) { $processes = Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId; $changed = $true; while ($changed) { $changed = $false; foreach ($process in $processes) { if ($ids.Contains([int]$process.ParentProcessId) -and -not $ids.Contains([int]$process.ProcessId)) { [void]$ids.Add([int]$process.ProcessId); $changed = $true } } } }",
718+"$samples = foreach ($id in $ids) { try { Get-Process -Id $id -ErrorAction Stop } catch {} }",
719+"$process = $samples | Sort-Object WorkingSet64 -Descending | Select-Object -First 1",
720+"if ($null -eq $process) { exit 2 }",
721+"$cpu = 0",
722+"if ($null -ne $process.CPU) { $cpu = $process.CPU }",
723+"[Console]::Out.Write(('{0} {1} {2}' -f $process.WorkingSet64, $cpu, $process.Id))",
724+].join("; ");
616725for (const powershell of ["powershell.exe", "powershell"]) {
617726try {
618727const { stdout } = await run(
619728powershell,
620729["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", command],
621-{ timeoutMs: 5000 },
730+{ timeoutMs: 15000 },
622731);
623-const [workingSetBytesRaw, cpuSecondsRaw] = stdout.trim().split(/\s+/u);
732+const [workingSetBytesRaw, cpuSecondsRaw, processIdRaw] = stdout.trim().split(/\s+/u);
624733const workingSetBytes = Number.parseInt(workingSetBytesRaw ?? "", 10);
625734const cpuSeconds = Number.parseFloat(cpuSecondsRaw ?? "");
735+const processId = Number.parseInt(processIdRaw ?? "", 10);
626736if (!Number.isFinite(workingSetBytes)) {
627737return null;
628738}
629739return {
630740rssMiB: Math.round((workingSetBytes / 1024 / 1024) * 10) / 10,
631741cpuPercent: null,
632742cpuSeconds: Number.isFinite(cpuSeconds) ? cpuSeconds : null,
743+processId: Number.isFinite(processId) ? processId : safePid,
633744};
634745} catch {
635746// Try the next Windows PowerShell command name.
@@ -689,7 +800,7 @@ function isNonEmptyString(value) {
689800}
690801691802export async function main() {
692-const runner = resolveOpenClawRunner();
803+let runner = resolveOpenClawRunner();
693804const port = readPositiveInt(process.env.OPENCLAW_KITCHEN_SINK_RPC_PORT, DEFAULT_PORT);
694805const { root, env } = makeEnv();
695806const logPath = path.join(root, "gateway.log");
@@ -698,6 +809,8 @@ export async function main() {
698809await runOpenClaw(runner, ["plugins", "install", PLUGIN_SPEC], env, {
699810timeoutMs: INSTALL_TIMEOUT_MS,
700811});
812+runner = resolveOpenClawRunner();
813+console.log(`Kitchen Sink RPC runtime runner: ${runner.label}`);
701814configureKitchenSink(env, port);
702815await runOpenClaw(runner, ["plugins", "enable", PLUGIN_ID], env, { timeoutMs: 60000 });
703816const inspect = parseJsonOutput(
@@ -717,18 +830,31 @@ export async function main() {
717830const child = await startGateway(runner, port, env, logPath);
718831const processSamples = [];
719832const sampleGateway = async () => {
720-const sample = await sampleProcess(child.pid);
833+const windowsSampleOptions = runner.pnpm
834+ ? { windowsCommandLineNeedles: ["gateway", "--port", String(port)] }
835+ : {};
836+let sample = await sampleProcess(child.pid, windowsSampleOptions);
837+if (!sample && process.platform === "win32") {
838+sample = await sampleWindowsProcessByPort(port);
839+}
721840if (sample) {
722841processSamples.push(sample);
723842}
724843return sample;
725844};
845+let sampleInFlight = null;
846+const collectTimedSample = () => {
847+sampleInFlight ??= sampleGateway().finally(() => {
848+sampleInFlight = null;
849+});
850+return sampleInFlight;
851+};
726852let sampleTimer;
727853try {
728854await waitForGatewayReady(child, port, logPath);
729855const initialSample = await sampleGateway();
730856sampleTimer = setInterval(() => {
731-void sampleGateway().catch(() => {});
857+void collectTimedSample().catch(() => {});
732858}, 1000);
733859sampleTimer.unref?.();
734860const healthz = await fetchJson(`http://127.0.0.1:${port}/healthz`);
@@ -808,6 +934,7 @@ export async function main() {
808934);
809935}
810936await retryRpcCall("diagnostics.stability", {}, { runner, port, env });
937+await sampleInFlight?.catch(() => {});
811938const finalSample = await sampleGateway();
812939assertResourceCeiling(finalSample);
813940const peakSample = summarizeProcessSamples(processSamples);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。