diff --git a/src/lib/metrics-poller.ts b/src/lib/metrics-poller.ts index f0a5034..3c84374 100644 --- a/src/lib/metrics-poller.ts +++ b/src/lib/metrics-poller.ts @@ -22,6 +22,18 @@ const AGGREGATE: Record number> = { fan: avg, }; +export function combineSeries(histories: number[][], kind: GpuMetricKind): { value?: number; history: number[] } { + const n = Math.max(...histories.map((h) => h.length), 0); + if (n === 0) return { value: undefined, history: [] }; + const history: number[] = []; + for (let i = 0; i < n; i++) { + const at = histories.map((h) => h[i]).filter((v): v is number => v !== undefined); + if (at.length === 0) continue; + history.push(AGGREGATE[kind](at)); + } + return { value: history.length > 0 ? history[history.length - 1] : undefined, history }; +} + function key(gpuId: string, kind: GpuMetricKind): string { return `${gpuId}|${kind}`; } diff --git a/tests/metrics-poller.test.ts b/tests/metrics-poller.test.ts index 79ff4ac..6df7952 100644 --- a/tests/metrics-poller.test.ts +++ b/tests/metrics-poller.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { test } from "node:test"; import { type LlamaSwapConfig } from "../src/lib/util"; -import { MetricsPoller } from "../src/lib/metrics-poller"; +import { combineSeries, MetricsPoller } from "../src/lib/metrics-poller"; const FIXTURE = readFileSync(new URL("./fixtures/metrics.txt", import.meta.url), "utf8"); @@ -61,3 +61,31 @@ test("poller reports offline after a fetch failure and recovers", async () => { assert.equal(poller.isOffline(), false); assert.ok(poller.getValue("0", "util_percent") !== undefined); }); + +test("combineSeries averages util and sums power", () => { + const util = combineSeries( + [ + [10, 20, 30], + [20, 40, 60], + ], + "util_percent", + ); + assert.deepEqual(util, { value: 45, history: [15, 30, 45] }); + + const power = combineSeries( + [ + [100, 200], + [150, 250], + ], + "power", + ); + assert.deepEqual(power, { value: 450, history: [250, 450] }); +}); + +test("combineSeries takes the max temperature and skips gaps", () => { + const temp = combineSeries([[50, 70], [60]], "temperature"); + assert.deepEqual(temp, { value: 70, history: [60, 70] }); + + const empty = combineSeries([[], []], "temperature"); + assert.deepEqual(empty, { value: undefined, history: [] }); +});