// Parser and effective-limit tests. Every input is fixture text shaped like // the kernel's files; nothing reads /proc, /sys or a cgroup. import { test } from "node:test"; import assert from "node:assert/strict"; import { SupervisorError, parseLoadavg, parseMeminfo, parsePsi, parseCpuList, parseCpuMax, parseMemoryMax, parseMemoryCurrent, parseMemoryEvents, parseVmstat, effectiveCpus, effectiveMemory, } from "../src/index.mjs"; const GiB = 1024 ** 3; const isParse = (re) => (e) => e instanceof SupervisorError && e.kind === "parse" && re.test(e.message); const MEMINFO = [ "MemTotal: 32768000 kB", "MemFree: 1024000 kB", "MemAvailable: 8192000 kB", "SwapTotal: 4096000 kB", "SwapFree: 4000000 kB", "HugePages_Total: 0", "Hugepagesize: 2048 kB", "", ].join("\n"); test("loadavg parses the three loads", () => { assert.deepEqual(parseLoadavg("0.52 1.58 12.00 3/1234 56789\n"), { load1: 0.52, load5: 1.58, load15: 12 }); }); test("a malformed loadavg throws", () => { for (const bad of ["", "0.52 0.58 0.59\n", "0.52 0.58 0.59 1/2\n", "x 0.58 0.59 1/2 3\n", "0.52 0.58 0.59 1/2 3 4\n", 42]) { assert.throws(() => parseLoadavg(bad), isParse(/loadavg/), JSON.stringify(bad)); } }); test("meminfo returns bytes and keeps unitless counts", () => { assert.deepEqual(parseMeminfo(MEMINFO), { memTotal: 32768000 * 1024, memAvailable: 8192000 * 1024, swapTotal: 4096000 * 1024, swapFree: 4000000 * 1024, }); }); test("meminfo without MemAvailable, with a repeated key or a bad line throws", () => { assert.throws(() => parseMeminfo("MemTotal: 1 kB\n"), isParse(/no MemAvailable/)); assert.throws(() => parseMeminfo(MEMINFO + "MemTotal: 1 kB\n"), isParse(/repeated key MemTotal/)); assert.throws(() => parseMeminfo(MEMINFO + "Broken line\n"), isParse(/bad line/)); assert.throws(() => parseMeminfo(MEMINFO.replace("8192000", "-1")), isParse(/bad line/)); }); test("PSI parses some and full, and cpu may lack full", () => { const mem = "some avg10=1.50 avg60=0.20 avg300=0.00 total=123\nfull avg10=0.75 avg60=0.10 avg300=0.00 total=45\n"; assert.deepEqual(parsePsi(mem, "memory"), { some: { avg10: 1.5, avg60: 0.2, avg300: 0, total: 123 }, full: { avg10: 0.75, avg60: 0.1, avg300: 0, total: 45 }, }); assert.equal(parsePsi("some avg10=20.00 avg60=0.00 avg300=0.00 total=9\n", "cpu").full, null); }); test("PSI without a full line for memory or io, or with a bad line, throws", () => { const someOnly = "some avg10=0.00 avg60=0.00 avg300=0.00 total=0\n"; assert.throws(() => parsePsi(someOnly, "memory"), isParse(/no full line/)); assert.throws(() => parsePsi(someOnly, "io"), isParse(/no full line/)); assert.throws(() => parsePsi("full avg10=0.00 avg60=0.00 avg300=0.00 total=0\n", "cpu"), isParse(/no some line/)); assert.throws(() => parsePsi(someOnly + someOnly, "cpu"), isParse(/repeated some/)); assert.throws(() => parsePsi("some avg10=x avg60=0.00 avg300=0.00 total=0\n", "cpu"), isParse(/not a non-negative number/)); assert.throws(() => parsePsi(someOnly, "gpu"), isParse(/unknown resource/)); }); test("CPU lists count distinct CPUs", () => { assert.equal(parseCpuList("0-3,6,8-9\n"), 7); assert.equal(parseCpuList("0\n"), 1); assert.equal(parseCpuList("0-3,2-5"), 6); for (const bad of ["", "\n", "3-1", "0,,1", "a-b", "0-"]) assert.throws(() => parseCpuList(bad), isParse(/cpu list/), bad); }); test("cpu.max gives the quota in CPUs, or null for max", () => { assert.equal(parseCpuMax("max 100000\n"), null); assert.equal(parseCpuMax("50000 100000\n"), 0.5); assert.equal(parseCpuMax("200000 100000\n"), 2); for (const bad of ["max", "50000 0", "-1 100000", "max max"]) assert.throws(() => parseCpuMax(bad), isParse(/cpu.max/), bad); }); test("memory.max, memory.current and memory.events", () => { assert.equal(parseMemoryMax("max\n"), null); assert.equal(parseMemoryMax("4294967296\n"), 4 * GiB); assert.throws(() => parseMemoryMax("4G\n"), isParse(/memory.max/)); assert.equal(parseMemoryCurrent("123456\n"), 123456); assert.throws(() => parseMemoryCurrent(""), isParse(/memory.current/)); assert.deepEqual(parseMemoryEvents("low 0\nhigh 4\nmax 2\noom 1\noom_kill 1\noom_group_kill 0\n"), { oom: 1, oomKill: 1 }); assert.throws(() => parseMemoryEvents("low 0\noom 1\n"), isParse(/no oom_kill/)); assert.throws(() => parseMemoryEvents("oom_kill 1\noom_kill 2\n"), isParse(/repeated key/)); }); test("vmstat gives swap counters and the host OOM counter", () => { assert.deepEqual(parseVmstat("nr_free_pages 100\npswpin 7\npswpout 9\noom_kill 3\n"), { pswpin: 7, pswpout: 9, oomKill: 3 }); assert.deepEqual(parseVmstat("pswpin 0\npswpout 0\n"), { pswpin: 0, pswpout: 0, oomKill: null }); assert.throws(() => parseVmstat("pswpin 0\n"), isParse(/no pswpout/)); }); test("effective CPUs fall back to the host count with no cgroup input", () => { assert.equal(effectiveCpus({ online: "0-15\n" }), 16); }); test("effective CPUs take the smallest of host, cpuset and every quota level", () => { assert.equal(effectiveCpus({ online: "0-15\n", cpuset: "0-3\n" }), 4); assert.equal(effectiveCpus({ online: "0-15\n", cpuset: "0-3\n", cpuMax: ["max 100000\n", "150000 100000\n"] }), 1.5); // The outer level is the tighter one. assert.equal(effectiveCpus({ online: "0-15\n", cpuMax: ["800000 100000\n", "200000 100000\n"] }), 2); // A quota above the host count doesn't raise it. assert.equal(effectiveCpus({ online: "0-1\n", cpuMax: ["800000 100000\n"] }), 2); assert.equal(effectiveCpus({ online: "0-15\n", cpuMax: ["max 100000\n"] }), 16); }); test("effective memory falls back to the host with no cgroup levels, or only max", () => { const host = { limit: 32768000 * 1024, available: 8192000 * 1024 }; assert.deepEqual(effectiveMemory({ meminfo: MEMINFO }), host); assert.deepEqual(effectiveMemory({ meminfo: MEMINFO, levels: [{ max: "max\n" }] }), host); }); test("effective memory uses the tightest limited level", () => { // 4 GiB limit with 3 GiB used: 1 GiB available, below the host's MemAvailable. assert.deepEqual(effectiveMemory({ meminfo: MEMINFO, levels: [{ max: `${4 * GiB}\n`, current: `${3 * GiB}\n` }] }), { limit: 4 * GiB, available: 1 * GiB }); // Nested: the inner level is loose, the outer one is nearly full. assert.deepEqual(effectiveMemory({ meminfo: MEMINFO, levels: [ { max: "max\n" }, { max: `${16 * GiB}\n`, current: `${2 * GiB}\n` }, { max: `${6 * GiB}\n`, current: `${5.5 * GiB}\n` }, ] }), { limit: 6 * GiB, available: 0.5 * GiB }); // Usage above the limit floors at 0. assert.equal(effectiveMemory({ meminfo: MEMINFO, levels: [{ max: "100\n", current: "200\n" }] }).available, 0); // A limit above MemTotal doesn't raise the limit. assert.equal(effectiveMemory({ meminfo: MEMINFO, levels: [{ max: `${64 * GiB}\n`, current: "0\n" }] }).limit, 32768000 * 1024); }); test("a limited level without memory.current throws", () => { assert.throws(() => effectiveMemory({ meminfo: MEMINFO, levels: [{ max: `${4 * GiB}\n` }] }), isParse(/missing for a limited level/)); });