feat: implement inflight monitor and GPU graph actions
This commit is contained in:
@@ -1 +1,72 @@
|
|||||||
export {};
|
import streamDeck, {
|
||||||
|
action,
|
||||||
|
type DidReceiveSettingsEvent,
|
||||||
|
type KeyAction,
|
||||||
|
type KeyDownEvent,
|
||||||
|
SingletonAction,
|
||||||
|
type WillAppearEvent,
|
||||||
|
} 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
@action({ UUID: "com.bryce.llamawatch.gpu" })
|
||||||
|
export class GpuGraph extends SingletonAction<GpuSettings> {
|
||||||
|
private settings: GpuSettings = {};
|
||||||
|
private action?: KeyAction<GpuSettings>;
|
||||||
|
private unsubscribe?: () => void;
|
||||||
|
private lastSvg?: string;
|
||||||
|
|
||||||
|
override onWillAppear(ev: WillAppearEvent<GpuSettings>): void {
|
||||||
|
if (!ev.action.isKey()) return;
|
||||||
|
this.settings = ev.payload.settings;
|
||||||
|
this.action = ev.action;
|
||||||
|
runtime.ensureConnections(cfgFromSettings(this.settings));
|
||||||
|
this.unsubscribe = runtime.subscribe(() => this.render());
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
override onWillDisappear(): void {
|
||||||
|
this.unsubscribe?.();
|
||||||
|
this.unsubscribe = undefined;
|
||||||
|
this.action = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
override onDidReceiveSettings(ev: DidReceiveSettingsEvent<GpuSettings>): void {
|
||||||
|
if (!ev.action.isKey()) return;
|
||||||
|
this.settings = ev.payload.settings;
|
||||||
|
this.action = ev.action;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
override onKeyDown(ev: KeyDownEvent<GpuSettings>): void {
|
||||||
|
void streamDeck.system.openUrl(`${cfgFromSettings(ev.payload.settings).baseUrl}/ui`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private render(): void {
|
||||||
|
const action = this.action;
|
||||||
|
if (!action) return;
|
||||||
|
const poller = runtime.pollerInstance;
|
||||||
|
const gpuId = this.settings.gpuId ?? "all";
|
||||||
|
const metric = this.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 === this.lastSvg) return;
|
||||||
|
this.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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1,87 @@
|
|||||||
export {};
|
import streamDeck, {
|
||||||
|
action,
|
||||||
|
type DidReceiveSettingsEvent,
|
||||||
|
type KeyAction,
|
||||||
|
type KeyDownEvent,
|
||||||
|
SingletonAction,
|
||||||
|
type WillAppearEvent,
|
||||||
|
} from "@elgato/streamdeck";
|
||||||
|
import { renderInflight, svgDataUrl } from "../lib/render";
|
||||||
|
import { runtime } from "../lib/runtime";
|
||||||
|
import { cfgFromSettings, type CfgSettings } from "../lib/util";
|
||||||
|
|
||||||
|
type InflightSettings = CfgSettings & {
|
||||||
|
modelId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
@action({ UUID: "com.bryce.llamawatch.inflight" })
|
||||||
|
export class InflightMonitor extends SingletonAction<InflightSettings> {
|
||||||
|
private settings: InflightSettings = {};
|
||||||
|
private action?: KeyAction<InflightSettings>;
|
||||||
|
private unsubscribe?: () => void;
|
||||||
|
private pulseTimer?: ReturnType<typeof setInterval>;
|
||||||
|
private pulse = false;
|
||||||
|
|
||||||
|
override onWillAppear(ev: WillAppearEvent<InflightSettings>): void {
|
||||||
|
if (!ev.action.isKey()) return;
|
||||||
|
this.settings = ev.payload.settings;
|
||||||
|
this.action = ev.action;
|
||||||
|
runtime.ensureConnections(cfgFromSettings(this.settings));
|
||||||
|
this.unsubscribe = runtime.subscribe(() => this.render());
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
override onWillDisappear(): void {
|
||||||
|
this.unsubscribe?.();
|
||||||
|
this.unsubscribe = undefined;
|
||||||
|
this.action = undefined;
|
||||||
|
this.clearPulse();
|
||||||
|
}
|
||||||
|
|
||||||
|
override onDidReceiveSettings(ev: DidReceiveSettingsEvent<InflightSettings>): void {
|
||||||
|
if (!ev.action.isKey()) return;
|
||||||
|
this.settings = ev.payload.settings;
|
||||||
|
this.action = ev.action;
|
||||||
|
runtime.ensureConnections(cfgFromSettings(this.settings));
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
override onKeyDown(ev: KeyDownEvent<InflightSettings>): void {
|
||||||
|
void streamDeck.system.openUrl(`${cfgFromSettings(ev.payload.settings).baseUrl}/ui`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private render(): void {
|
||||||
|
const action = this.action;
|
||||||
|
if (!action) return;
|
||||||
|
const modelId = this.settings.modelId ?? "";
|
||||||
|
const state = runtime.tracker.state(modelId);
|
||||||
|
const count = runtime.tracker.count(modelId);
|
||||||
|
const offline = runtime.offline || modelId.length === 0;
|
||||||
|
void action.setImage(
|
||||||
|
svgDataUrl(
|
||||||
|
renderInflight({
|
||||||
|
modelName: modelId.length > 0 ? modelId : "unset",
|
||||||
|
state,
|
||||||
|
count,
|
||||||
|
offline,
|
||||||
|
pulse: this.pulse,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (state === "ready" && count > 0 && !this.pulseTimer) {
|
||||||
|
this.pulseTimer = setInterval(() => {
|
||||||
|
this.pulse = !this.pulse;
|
||||||
|
this.render();
|
||||||
|
}, 500);
|
||||||
|
} else if (!(state === "ready" && count > 0) && this.pulseTimer) {
|
||||||
|
this.clearPulse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearPulse(): void {
|
||||||
|
if (this.pulseTimer) clearInterval(this.pulseTimer);
|
||||||
|
this.pulseTimer = undefined;
|
||||||
|
this.pulse = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user