diff --git a/package-lock.json b/package-lock.json index 9de1076..c53ec5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "perfkit", - "version": "0.1.9", + "version": "0.1.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "perfkit", - "version": "0.1.9", + "version": "0.1.10", "license": "MIT", "dependencies": { "commander": "^14.0.3", diff --git a/package.json b/package.json index ac07eda..c1ab3ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "perfkit", - "version": "0.1.9", + "version": "0.1.10", "description": "Benchmark harness for Fonoster projects: DigitalOcean runs, one report.json schema, one branded report page", "license": "MIT", "author": "Pedro Sanders ", diff --git a/src/orchestrate/createDockerEngine.ts b/src/orchestrate/createDockerEngine.ts index e001cb0..9ede4b1 100644 --- a/src/orchestrate/createDockerEngine.ts +++ b/src/orchestrate/createDockerEngine.ts @@ -60,15 +60,17 @@ export function createDockerEngine(socketPath: string): EngineRequest { export function demuxLogs(buffer: Buffer): { stdout: string; stderr: string } { const out: Buffer[] = []; const err: Buffer[] = []; - const read = (offset: number): void => { - if (offset + 8 > buffer.length) return; + // A loop, not recursion: a long command output arrives as tens of + // thousands of frames, past the call stack's depth. + let offset = 0; + while (offset + 8 <= buffer.length) { const stream = buffer[offset]; const size = buffer.readUInt32BE(offset + 4); - const frame = buffer.subarray(offset + 8, offset + 8 + size); - (stream === 2 ? err : out).push(frame); - read(offset + 8 + size); - }; - read(0); + (stream === 2 ? err : out).push( + buffer.subarray(offset + 8, offset + 8 + size) + ); + offset += 8 + size; + } return { stdout: Buffer.concat(out).toString("utf8"), stderr: Buffer.concat(err).toString("utf8") diff --git a/test/orchestrate/helpers.unit.test.ts b/test/orchestrate/helpers.unit.test.ts index a673eb9..bc3e223 100644 --- a/test/orchestrate/helpers.unit.test.ts +++ b/test/orchestrate/helpers.unit.test.ts @@ -99,6 +99,24 @@ describe("orchestration helpers", () => { ).to.deep.equal({ stdout: "out put", stderr: "err" }); }); + it("should demux a long log of small frames without overflowing the stack", () => { + // A 6-hour sampler file read over SSH arrives as tens of thousands of + // small frames; a recursive reader overflowed the stack on it. + const frame = (stream: number, text: string) => { + const body = Buffer.from(text); + const header = Buffer.alloc(8); + header[0] = stream; + header.writeUInt32BE(body.length, 4); + return Buffer.concat([header, body]); + }; + const lines = Array.from({ length: 200_000 }, (_, i) => `${i}\n`); + const { stdout, stderr } = demuxLogs( + Buffer.concat(lines.map((line) => frame(1, line))) + ); + expect(stdout).to.equal(lines.join("")); + expect(stderr).to.equal(""); + }); + it("should read docker info and refuse what it can't read", () => { expect( parseDockerInfo("8;17179869184;Ubuntu 24.04.1 LTS;27.3.1\n")