From 9e847940d376d9cecef32b0bdaa772cf542fe927 Mon Sep 17 00:00:00 2001
From: c4ch3c4d3 <23181631+c4ch3c4d3@users.noreply.github.com>
Date: Fri, 14 Aug 2026 13:00:46 -0600
Subject: [PATCH] feat: support GPU combinations on the GPU Graph action
---
com.bryce.llamawatch.sdPlugin/ui/gpu.html | 3 ++
src/actions/gpu-graph.ts | 34 ++++++++++++++++++++---
2 files changed, 33 insertions(+), 4 deletions(-)
diff --git a/com.bryce.llamawatch.sdPlugin/ui/gpu.html b/com.bryce.llamawatch.sdPlugin/ui/gpu.html
index 8cb8e29..4b65c31 100644
--- a/com.bryce.llamawatch.sdPlugin/ui/gpu.html
+++ b/com.bryce.llamawatch.sdPlugin/ui/gpu.html
@@ -14,6 +14,9 @@
+
+
+
diff --git a/src/actions/gpu-graph.ts b/src/actions/gpu-graph.ts
index 44eb9de..17c4369 100644
--- a/src/actions/gpu-graph.ts
+++ b/src/actions/gpu-graph.ts
@@ -8,6 +8,7 @@ import streamDeck, {
type WillDisappearEvent,
} from "@elgato/streamdeck";
import { type GpuMetricKind } from "../lib/metrics-parser";
+import { combineSeries } from "../lib/metrics-poller";
import { renderGpuGraph, svgDataUrl } from "../lib/render";
import { runtime } from "../lib/runtime";
import { cfgFromSettings, type CfgSettings } from "../lib/util";
@@ -15,6 +16,7 @@ import { cfgFromSettings, type CfgSettings } from "../lib/util";
type GpuSettings = CfgSettings & {
gpuId?: string;
metric?: GpuMetricKind;
+ gpuCombo?: string;
};
type GpuState = {
@@ -71,12 +73,28 @@ export class GpuGraph extends SingletonAction {
const action = state.action;
if (!action) return;
const poller = runtime.pollerInstance;
- const gpuId = state.settings.gpuId ?? "all";
const metric = state.settings.metric ?? "util_percent";
- const value = poller?.getValue(gpuId, metric);
- const history = poller?.getHistory(gpuId, metric) ?? [];
+ const combo = parseCombo(state.settings.gpuCombo);
const offline = !poller || poller.isOffline();
- const gpuName = gpuId === "all" ? "ALL GPUS" : this.gpuLabel(gpuId);
+
+ let value: number | undefined;
+ let history: number[] = [];
+ let gpuName: string;
+ if (combo) {
+ const known = new Set((poller?.gpus() ?? []).map((g) => g.id));
+ const ids = combo.filter((id) => known.has(id));
+ const histories = ids.map((id) => poller?.getHistory(id, metric) ?? []);
+ const combined = combineSeries(histories, metric);
+ value = combined.value;
+ history = combined.history;
+ gpuName = `GPU ${ids.join("+")}`;
+ } else {
+ const gpuId = state.settings.gpuId ?? "all";
+ value = poller?.getValue(gpuId, metric);
+ history = poller?.getHistory(gpuId, metric) ?? [];
+ gpuName = gpuId === "all" ? "ALL GPUS" : this.gpuLabel(gpuId);
+ }
+
const svg = renderGpuGraph({ gpuName, metric, value, history, offline });
if (svg === state.lastSvg) return;
state.lastSvg = svg;
@@ -88,3 +106,11 @@ export class GpuGraph extends SingletonAction {
return gpu && gpu.name ? `GPU ${gpuId} · ${gpu.name}` : `GPU ${gpuId}`;
}
}
+
+function parseCombo(raw: string | undefined): string[] | undefined {
+ const ids = (raw ?? "")
+ .split(",")
+ .map((s) => s.trim())
+ .filter(Boolean);
+ return ids.length > 0 ? ids : undefined;
+}