fix(qa): reject loose Windows sampler metrics

This commit is contained in:
Vincent Koc
2026-06-16 21:43:27 +02:00
parent b816dfbb9f
commit 642ae61828
2 changed files with 52 additions and 13 deletions

View File

@@ -1740,7 +1740,7 @@ function parsePosixProcessRows(stdout) {
const processId = Number.parseInt(pidRaw, 10);
const parentProcessId = Number.parseInt(ppidRaw, 10);
const rssKb = Number.parseInt(rssKbRaw, 10);
const cpuPercent = parsePosixCpuPercent(cpuRaw);
const cpuPercent = parseStrictNonNegativeDecimal(cpuRaw);
if (
!Number.isInteger(processId) ||
!Number.isInteger(parentProcessId) ||
@@ -1759,7 +1759,7 @@ function parsePosixProcessRows(stdout) {
.filter(Boolean);
}
function parsePosixCpuPercent(raw) {
function parseStrictNonNegativeDecimal(raw) {
const text = String(raw ?? "").trim();
if (!/^(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(text)) {
return null;
@@ -1768,6 +1768,15 @@ function parsePosixCpuPercent(raw) {
return Number.isFinite(parsed) ? parsed : null;
}
function parseStrictUnsignedInteger(raw) {
const text = String(raw ?? "").trim();
if (!/^(?:0|[1-9]\d*)$/u.test(text)) {
return null;
}
const parsed = Number(text);
return Number.isSafeInteger(parsed) ? parsed : null;
}
function collectPosixProcessTree(rows, rootPid) {
const byParent = new Map();
for (const row of rows) {
@@ -1949,24 +1958,24 @@ async function sampleWindowsProcess(pid, run, commandLineNeedles = []) {
const [workingSetBytesRaw, cpuSecondsRaw, processIdRaw, aggregateWorkingSetBytesRaw] = stdout
.trim()
.split(/\s+/u);
const workingSetBytes = Number.parseInt(workingSetBytesRaw ?? "", 10);
const aggregateWorkingSetBytes = Number.parseInt(
const workingSetBytes = parseStrictUnsignedInteger(workingSetBytesRaw);
const aggregateWorkingSetBytes = parseStrictUnsignedInteger(
aggregateWorkingSetBytesRaw ?? workingSetBytesRaw ?? "",
10,
);
const cpuSeconds = Number.parseFloat(cpuSecondsRaw ?? "");
const processId = Number.parseInt(processIdRaw ?? "", 10);
if (!Number.isFinite(workingSetBytes)) {
const cpuSeconds = parseStrictNonNegativeDecimal(cpuSecondsRaw);
const processId = parseStrictUnsignedInteger(processIdRaw);
if (workingSetBytes === null) {
return null;
}
return {
rssMiB: Math.round((workingSetBytes / 1024 / 1024) * 10) / 10,
aggregateRssMiB: Number.isFinite(aggregateWorkingSetBytes)
? Math.round((aggregateWorkingSetBytes / 1024 / 1024) * 10) / 10
: Math.round((workingSetBytes / 1024 / 1024) * 10) / 10,
aggregateRssMiB:
aggregateWorkingSetBytes !== null
? Math.round((aggregateWorkingSetBytes / 1024 / 1024) * 10) / 10
: Math.round((workingSetBytes / 1024 / 1024) * 10) / 10,
cpuPercent: null,
cpuSeconds: Number.isFinite(cpuSeconds) ? cpuSeconds : null,
processId: Number.isFinite(processId) ? processId : safePid,
cpuSeconds,
processId: processId ?? safePid,
};
} catch {
// Try the next Windows PowerShell command name.

View File

@@ -1349,6 +1349,36 @@ describe("kitchen-sink RPC process sampling", () => {
expect(sample?.aggregateRssMiB).toBe(96);
});
it("does not truncate malformed Windows PowerShell CPU or id samples", async () => {
const sample = await sampleProcess(1234, {
platform: "win32",
runCommand: async () => ({
stdout: `${256 * 1024 * 1024} 2.25oops 6789x ${512 * 1024 * 1024}oops`,
stderr: "",
}),
});
expect(sample).toEqual({
aggregateRssMiB: 256,
cpuPercent: null,
cpuSeconds: null,
processId: 1234,
rssMiB: 256,
});
});
it("rejects malformed Windows PowerShell RSS samples", async () => {
const sample = await sampleProcess(1234, {
platform: "win32",
runCommand: async () => ({
stdout: `${256 * 1024 * 1024}oops 2.25 6789 ${512 * 1024 * 1024}`,
stderr: "",
}),
});
expect(sample).toBeNull();
});
it("samples the Windows gateway process by listening port", async () => {
const calls: Array<{ command: string; args: string[] }> = [];
const sample = await sampleWindowsProcessByPort(19675, {