From 5c08fb225a017e91e7a7ab61bdae20882f8cdca5 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 4 Jun 2026 08:52:42 +0200 Subject: [PATCH] fix(e2e): stream docker stats resource scans --- .../docker-stats/assert-resource-ceiling.mjs | 21 ++++++++++++---- .../docker-stats-resource-ceiling.test.ts | 25 +++++++++++++++---- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/scripts/e2e/lib/docker-stats/assert-resource-ceiling.mjs b/scripts/e2e/lib/docker-stats/assert-resource-ceiling.mjs index 08d5b2b1950a..4e9e56b62021 100644 --- a/scripts/e2e/lib/docker-stats/assert-resource-ceiling.mjs +++ b/scripts/e2e/lib/docker-stats/assert-resource-ceiling.mjs @@ -1,4 +1,5 @@ import fs from "node:fs"; +import { createInterface } from "node:readline"; const [statsFile, maxMemoryRaw, maxCpuRaw, label = "docker"] = process.argv.slice(2); const maxMemoryMiB = Number(maxMemoryRaw); @@ -55,9 +56,19 @@ function assertSampleValue(value, raw, name, labelLocal) { } } -const lines = fs.existsSync(statsFile) - ? fs.readFileSync(statsFile, "utf8").split(/\r?\n/u).filter(Boolean) - : []; +async function scanStatsFileLines(file, onLine) { + if (!fs.existsSync(file)) { + return; + } + const input = fs.createReadStream(file, { encoding: "utf8" }); + const lines = createInterface({ crlfDelay: Infinity, input }); + for await (const line of lines) { + if (line) { + onLine(line); + } + } +} + let maxObservedMemoryMiB = 0; let maxObservedCpuPercent = 0; let parsedSamples = 0; @@ -65,7 +76,7 @@ let parsedSamples = 0; assertFiniteLimit(maxMemoryMiB, maxMemoryRaw, "max memory MiB"); assertFiniteLimit(maxCpuPercent, maxCpuRaw, "max CPU percent"); -for (const line of lines) { +await scanStatsFileLines(statsFile, (line) => { let parsed; try { parsed = JSON.parse(line); @@ -79,7 +90,7 @@ for (const line of lines) { parsedSamples += 1; maxObservedMemoryMiB = Math.max(maxObservedMemoryMiB, observedMemoryMiB); maxObservedCpuPercent = Math.max(maxObservedCpuPercent, observedCpuPercent); -} +}); console.log( `${label} resource peak: memory=${maxObservedMemoryMiB.toFixed(1)}MiB cpu=${maxObservedCpuPercent.toFixed(1)}% samples=${parsedSamples}`, diff --git a/test/scripts/docker-stats-resource-ceiling.test.ts b/test/scripts/docker-stats-resource-ceiling.test.ts index cb1fcd7ba48a..7d8ab6bea407 100644 --- a/test/scripts/docker-stats-resource-ceiling.test.ts +++ b/test/scripts/docker-stats-resource-ceiling.test.ts @@ -1,5 +1,5 @@ import { spawnSync } from "node:child_process"; -import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; @@ -16,9 +16,13 @@ function writeStats(contents: string): string { } function runAssert(statsFile: string, maxMemoryMiB = "512", maxCpuPercent = "100") { - return spawnSync(process.execPath, [SCRIPT_PATH, statsFile, maxMemoryMiB, maxCpuPercent, "test"], { - encoding: "utf8", - }); + return spawnSync( + process.execPath, + [SCRIPT_PATH, statsFile, maxMemoryMiB, maxCpuPercent, "test"], + { + encoding: "utf8", + }, + ); } afterEach(() => { @@ -36,7 +40,10 @@ describe("scripts/e2e/lib/docker-stats/assert-resource-ceiling.mjs", () => { }); it("rejects invalid resource limits instead of disabling the ceiling", () => { - const result = runAssert(writeStats('{"MemUsage":"128MiB / 2GiB","CPUPerc":"25.0%"}\n'), "nope"); + const result = runAssert( + writeStats('{"MemUsage":"128MiB / 2GiB","CPUPerc":"25.0%"}\n'), + "nope", + ); expect(result.status).not.toBe(0); expect(result.stderr).toContain("max memory MiB must be a finite non-negative number"); @@ -67,6 +74,14 @@ describe("scripts/e2e/lib/docker-stats/assert-resource-ceiling.mjs", () => { expect(result.stdout).toContain("samples=1"); }); + it("streams stats logs instead of slurping them into memory", () => { + const source = readFileSync(SCRIPT_PATH, "utf8"); + + expect(source).toContain("createReadStream"); + expect(source).not.toContain("readFileSync(statsFile"); + expect(source).not.toContain("split(/\\r?\\n/u)"); + }); + it("accepts byte-unit Docker memory samples", () => { const result = runAssert(writeStats('{"MemUsage":"512B / 2GiB","CPUPerc":"0.5%"}\n'));