





















@@ -132,11 +132,7 @@ function formatCapturedOutput(label, buffer) {
132132133133export function runCommand(command, args, options = {}) {
134134return new Promise((resolve, reject) => {
135-const {
136- timeoutKillGraceMs = 2000,
137- timeoutMs = COMMAND_TIMEOUT_MS,
138- ...spawnOptions
139-} = options;
135+const { timeoutKillGraceMs = 2000, timeoutMs = COMMAND_TIMEOUT_MS, ...spawnOptions } = options;
140136const child = childProcess.spawn(command, args, {
141137stdio: ["ignore", "pipe", "pipe"],
142138 ...spawnOptions,
@@ -149,10 +145,7 @@ export function runCommand(command, args, options = {}) {
149145const timer = setTimeout(() => {
150146timedOut = true;
151147signalProcessGroup(child, "SIGTERM");
152-forceKillTimer = setTimeout(
153-() => signalProcessGroup(child, "SIGKILL"),
154-timeoutKillGraceMs,
155-);
148+forceKillTimer = setTimeout(() => signalProcessGroup(child, "SIGKILL"), timeoutKillGraceMs);
156149forceKillTimer.unref();
157150}, timeoutMs);
158151child.stdout?.on("data", (chunk) => {
@@ -672,6 +665,46 @@ function assertToolInvokeResult(payload) {
672665}
673666}
674667668+export function assertDiagnosticStabilityClean(payload) {
669+const problems = [];
670+if (!payload || typeof payload !== "object") {
671+throw new Error(`diagnostics.stability returned invalid payload: ${JSON.stringify(payload)}`);
672+}
673+if ((payload.dropped ?? 0) > 0) {
674+problems.push(`dropped=${payload.dropped}`);
675+}
676+const payloadLarge = payload.summary?.payloadLarge;
677+if (payloadLarge) {
678+if ((payloadLarge.rejected ?? 0) > 0) {
679+problems.push(`payload.large rejected=${payloadLarge.rejected}`);
680+}
681+if ((payloadLarge.truncated ?? 0) > 0) {
682+problems.push(`payload.large truncated=${payloadLarge.truncated}`);
683+}
684+}
685+const asyncDropCount = countDiagnosticEvents(payload, "diagnostic.async_queue.dropped");
686+if (asyncDropCount > 0) {
687+problems.push(`async diagnostic drops=${asyncDropCount}`);
688+}
689+if (problems.length > 0) {
690+throw new Error(
691+`diagnostics.stability reported instability: ${problems.join(", ")}\n${tailText(
692+ JSON.stringify(payload, null, 2),
693+ )}`,
694+);
695+}
696+}
697+698+function countDiagnosticEvents(payload, type) {
699+const summaryCount = payload.summary?.byType?.[type];
700+if (Number.isFinite(summaryCount)) {
701+return summaryCount;
702+}
703+return (Array.isArray(payload.events) ? payload.events : []).filter(
704+(event) => event?.type === type,
705+).length;
706+}
707+675708export async function sampleProcess(pid, options = {}) {
676709const platform = options.platform ?? process.platform;
677710const run = options.runCommand ?? runCommand;
@@ -690,7 +723,9 @@ export function summarizeProcessSamples(samples) {
690723return null;
691724}
692725const peakRssSample = validSamples.reduce((peak, sample) =>
693-sample.rssMiB > peak.rssMiB ? sample : peak,
726+(sample.aggregateRssMiB ?? sample.rssMiB) > (peak.aggregateRssMiB ?? peak.rssMiB)
727+ ? sample
728+ : peak,
694729);
695730const numericCpuSamples = validSamples
696731.map((sample) => sample.cpuPercent)
@@ -710,20 +745,24 @@ async function samplePosixProcess(pid, run, commandLineNeedles = []) {
710745if (needles.length > 0) {
711746return samplePosixProcessTree(pid, run, needles);
712747}
748+return samplePosixProcessWithDescendants(pid, run);
749+}
750+751+async function samplePosixProcessWithDescendants(pid, run) {
752+const safePid = Number(pid);
753+if (!Number.isInteger(safePid) || safePid <= 0) {
754+return null;
755+}
713756try {
714-const { stdout } = await run("ps", ["-o", "rss=,pcpu=", "-p", String(pid)], {
757+const { stdout } = await run("ps", ["-axo", "pid=,ppid=,rss=,pcpu=,command="], {
715758timeoutMs: 5000,
716759});
717-const [rssKbRaw, cpuRaw] = stdout.trim().split(/\s+/u);
718-const rssKb = Number.parseInt(rssKbRaw ?? "", 10);
719-const cpuPercent = Number.parseFloat(cpuRaw ?? "");
720-if (!Number.isFinite(rssKb)) {
760+const rows = parsePosixProcessRows(stdout);
761+const selected = rows.find((row) => row.processId === safePid);
762+if (!selected) {
721763return null;
722764}
723-return {
724-rssMiB: Math.round((rssKb / 1024) * 10) / 10,
725-cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : null,
726-};
765+return formatPosixProcessTreeSample(selected, collectPosixProcessTree(rows, safePid));
727766} catch {
728767return null;
729768}
@@ -760,7 +799,10 @@ async function samplePosixProcessTree(pid, run, commandLineNeedles) {
760799if (!selected) {
761800return null;
762801}
763-return formatPosixProcessSample(selected);
802+return formatPosixProcessTreeSample(
803+selected,
804+collectPosixProcessTree(rows, selected.processId),
805+);
764806} catch {
765807return null;
766808}
@@ -824,11 +866,20 @@ function selectPeakRssProcess(rows) {
824866function formatPosixProcessSample(row) {
825867return {
826868rssMiB: Math.round((row.rssKb / 1024) * 10) / 10,
869+aggregateRssMiB: Math.round((row.rssKb / 1024) * 10) / 10,
827870cpuPercent: row.cpuPercent,
828871processId: row.processId,
829872};
830873}
831874875+function formatPosixProcessTreeSample(selected, rows) {
876+const aggregateRssKb = rows.reduce((sum, row) => sum + row.rssKb, 0);
877+return {
878+ ...formatPosixProcessSample(selected),
879+aggregateRssMiB: Math.round((aggregateRssKb / 1024) * 10) / 10,
880+};
881+}
882+832883function parseTasklistCsvLine(line) {
833884const values = [];
834885let current = "";
@@ -926,46 +977,50 @@ async function sampleWindowsProcess(pid, run, commandLineNeedles = []) {
926977.map((needle) => String(needle ?? "").trim())
927978.filter((needle) => needle.length > 0);
928979const powershellNeedles = `@(${needles.map(powershellSingleQuoted).join(", ")})`;
929-const command =
930-needles.length === 0
931- ? [
932-"$ErrorActionPreference = 'Stop'",
933-`$process = Get-Process -Id ${safePid} -ErrorAction Stop`,
934-"$cpu = 0",
935-"if ($null -ne $process.CPU) { $cpu = $process.CPU }",
936-"[Console]::Out.Write(('{0} {1} {2}' -f $process.WorkingSet64, $cpu, $process.Id))",
937-].join("; ")
938- : [
939-"$ErrorActionPreference = 'Stop'",
940-`$rootPid = ${safePid}`,
941-`$commandLineNeedles = ${powershellNeedles}`,
942-"$ids = [System.Collections.Generic.HashSet[int]]::new()",
943-"[void]$ids.Add($rootPid)",
944-'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) } } }',
945-"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 } } } }",
946-"$samples = foreach ($id in $ids) { try { Get-Process -Id $id -ErrorAction Stop } catch {} }",
947-"$process = $samples | Sort-Object WorkingSet64 -Descending | Select-Object -First 1",
948-"if ($null -eq $process) { exit 2 }",
949-"$cpu = 0",
950-"if ($null -ne $process.CPU) { $cpu = $process.CPU }",
951-"[Console]::Out.Write(('{0} {1} {2}' -f $process.WorkingSet64, $cpu, $process.Id))",
952-].join("; ");
980+const command = [
981+"$ErrorActionPreference = 'Stop'",
982+`$rootPid = ${safePid}`,
983+`$commandLineNeedles = ${powershellNeedles}`,
984+"$ids = [System.Collections.Generic.HashSet[int]]::new()",
985+"[void]$ids.Add($rootPid)",
986+'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) } } }',
987+"$processes = Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId",
988+"$changed = $true",
989+"$whileGuard = 0",
990+"while ($changed -and $whileGuard -lt 1024) { $whileGuard += 1; $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 } } }",
991+"$samples = foreach ($id in $ids) { try { Get-Process -Id $id -ErrorAction Stop } catch {} }",
992+"$process = $samples | Sort-Object WorkingSet64 -Descending | Select-Object -First 1",
993+"if ($null -eq $process) { exit 2 }",
994+"$totalWorkingSet = ($samples | Measure-Object -Property WorkingSet64 -Sum).Sum",
995+"$cpu = 0",
996+"if ($null -ne $process.CPU) { $cpu = $process.CPU }",
997+"[Console]::Out.Write(('{0} {1} {2} {3}' -f $process.WorkingSet64, $cpu, $process.Id, $totalWorkingSet))",
998+].join("; ");
953999for (const powershell of ["powershell.exe", "powershell"]) {
9541000try {
9551001const { stdout } = await run(
9561002powershell,
9571003["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", command],
9581004{ timeoutMs: 15000 },
9591005);
960-const [workingSetBytesRaw, cpuSecondsRaw, processIdRaw] = stdout.trim().split(/\s+/u);
1006+const [workingSetBytesRaw, cpuSecondsRaw, processIdRaw, aggregateWorkingSetBytesRaw] = stdout
1007+.trim()
1008+.split(/\s+/u);
9611009const workingSetBytes = Number.parseInt(workingSetBytesRaw ?? "", 10);
1010+const aggregateWorkingSetBytes = Number.parseInt(
1011+aggregateWorkingSetBytesRaw ?? workingSetBytesRaw ?? "",
1012+10,
1013+);
9621014const cpuSeconds = Number.parseFloat(cpuSecondsRaw ?? "");
9631015const processId = Number.parseInt(processIdRaw ?? "", 10);
9641016if (!Number.isFinite(workingSetBytes)) {
9651017return null;
9661018}
9671019return {
9681020rssMiB: Math.round((workingSetBytes / 1024 / 1024) * 10) / 10,
1021+aggregateRssMiB: Number.isFinite(aggregateWorkingSetBytes)
1022+ ? Math.round((aggregateWorkingSetBytes / 1024 / 1024) * 10) / 10
1023+ : Math.round((workingSetBytes / 1024 / 1024) * 10) / 10,
9691024cpuPercent: null,
9701025cpuSeconds: Number.isFinite(cpuSeconds) ? cpuSeconds : null,
9711026processId: Number.isFinite(processId) ? processId : safePid,
@@ -984,6 +1039,11 @@ export function assertResourceCeiling(sample) {
9841039if (sample.rssMiB > MAX_RSS_MIB) {
9851040throw new Error(`gateway RSS exceeded ${MAX_RSS_MIB} MiB: ${sample.rssMiB} MiB`);
9861041}
1042+if ((sample.aggregateRssMiB ?? sample.rssMiB) > MAX_RSS_MIB) {
1043+throw new Error(
1044+`gateway aggregate RSS exceeded ${MAX_RSS_MIB} MiB: ${sample.aggregateRssMiB} MiB`,
1045+);
1046+}
9871047}
98810489891049function assertNoErrorLogs(logPath) {
@@ -1169,7 +1229,8 @@ export async function main() {
11691229`plugins.uiDescriptors returned invalid payload: ${JSON.stringify(uiDescriptors)}`,
11701230);
11711231}
1172-await retryRpcCall("diagnostics.stability", {}, { runner, port, env });
1232+const stability = await retryRpcCall("diagnostics.stability", {}, { runner, port, env });
1233+assertDiagnosticStabilityClean(stability);
11731234await sampleInFlight?.catch(() => {});
11741235const finalSample = await sampleGateway();
11751236assertResourceCeiling(finalSample);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。