Files
llama-watch/src/actions/gpu-graph.ts
T

91 lines
2.9 KiB
TypeScript

import streamDeck, {
action,
type DidReceiveSettingsEvent,
type KeyAction,
type KeyDownEvent,
SingletonAction,
type WillAppearEvent,
type WillDisappearEvent,
} from "@elgato/streamdeck";
import { type GpuMetricKind } from "../lib/metrics-parser";
import { renderGpuGraph, svgDataUrl } from "../lib/render";
import { runtime } from "../lib/runtime";
import { cfgFromSettings, type CfgSettings } from "../lib/util";
type GpuSettings = CfgSettings & {
gpuId?: string;
metric?: GpuMetricKind;
};
type GpuState = {
settings: GpuSettings;
action?: KeyAction<GpuSettings>;
unsubscribe?: () => void;
lastSvg?: string;
};
@action({ UUID: "com.bryce.llamawatch.gpu" })
export class GpuGraph extends SingletonAction<GpuSettings> {
private states = new Map<string, GpuState>();
private stateFor(action: KeyAction<GpuSettings>): GpuState {
let state = this.states.get(action.id);
if (!state) {
state = { settings: {} };
this.states.set(action.id, state);
}
return state;
}
override onWillAppear(ev: WillAppearEvent<GpuSettings>): void {
if (!ev.action.isKey()) return;
const state = this.stateFor(ev.action);
state.settings = ev.payload.settings;
state.action = ev.action;
runtime.ensureConnections(cfgFromSettings(state.settings));
state.unsubscribe = runtime.subscribe(() => this.render(state));
this.render(state);
}
override onWillDisappear(ev: WillDisappearEvent<GpuSettings>): void {
const state = this.states.get(ev.action.id);
if (!state) return;
state.unsubscribe?.();
this.states.delete(ev.action.id);
}
override onDidReceiveSettings(ev: DidReceiveSettingsEvent<GpuSettings>): void {
if (!ev.action.isKey()) return;
const state = this.stateFor(ev.action);
state.settings = ev.payload.settings;
state.action = ev.action;
runtime.ensureConnections(cfgFromSettings(state.settings));
this.render(state);
}
override onKeyDown(ev: KeyDownEvent<GpuSettings>): void {
void streamDeck.system.openUrl(`${cfgFromSettings(ev.payload.settings).baseUrl}/ui`);
}
private render(state: GpuState): void {
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 offline = !poller || poller.isOffline();
const gpuName = gpuId === "all" ? "ALL GPUS" : this.gpuLabel(gpuId);
const svg = renderGpuGraph({ gpuName, metric, value, history, offline });
if (svg === state.lastSvg) return;
state.lastSvg = svg;
void action.setImage(svgDataUrl(svg));
}
private gpuLabel(gpuId: string): string {
const gpu = runtime.pollerInstance?.gpus().find((g) => g.id === gpuId);
return gpu && gpu.name ? `GPU ${gpuId} · ${gpu.name}` : `GPU ${gpuId}`;
}
}