Support showing NPU stats in dashboard

This commit is contained in:
Nicolas Mowen 2025-04-19 08:02:28 -06:00
parent f15f09d542
commit b9c7b1caa6
3 changed files with 183 additions and 94 deletions

View File

@ -72,7 +72,9 @@
"toast": { "toast": {
"success": "Copied GPU info to clipboard" "success": "Copied GPU info to clipboard"
} }
} },
"npuUsage": "NPU Usage",
"npuMemory": "NPU Memory"
}, },
"otherProcesses": { "otherProcesses": {
"title": "Other Processes", "title": "Other Processes",

View File

@ -4,6 +4,7 @@ export interface FrigateStats {
detectors: { [detectorKey: string]: DetectorStats }; detectors: { [detectorKey: string]: DetectorStats };
embeddings?: EmbeddingsStats; embeddings?: EmbeddingsStats;
gpu_usages?: { [gpuKey: string]: GpuStats }; gpu_usages?: { [gpuKey: string]: GpuStats };
npu_usages?: { [npuKey: string]: NpuStats };
processes: { [processKey: string]: ExtraProcessStats }; processes: { [processKey: string]: ExtraProcessStats };
service: ServiceStats; service: ServiceStats;
detection_fps: number; detection_fps: number;
@ -54,6 +55,11 @@ export type GpuStats = {
pstate?: string; pstate?: string;
}; };
export type NpuStats = {
npu: number;
mem: string;
};
export type GpuInfo = "vainfo" | "nvinfo"; export type GpuInfo = "vainfo" | "nvinfo";
export type ServiceStats = { export type ServiceStats = {

View File

@ -34,7 +34,7 @@ export default function GeneralMetrics({
const { data: initialStats } = useSWR<FrigateStats[]>( const { data: initialStats } = useSWR<FrigateStats[]>(
[ [
"stats/history", "stats/history",
{ keys: "cpu_usages,detectors,gpu_usages,processes,service" }, { keys: "cpu_usages,detectors,gpu_usages,npu_usages,processes,service" },
], ],
{ {
revalidateOnFocus: false, revalidateOnFocus: false,
@ -369,8 +369,57 @@ export default function GeneralMetrics({
return Object.keys(series).length > 0 ? Object.values(series) : undefined; return Object.keys(series).length > 0 ? Object.values(series) : undefined;
}, [statsHistory]); }, [statsHistory]);
// npu stats
const npuSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: number }[] };
} = {};
let hasValidNpu = false;
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.npu_usages || []).forEach(([key, stats]) => {
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
if (stats.npu) {
hasValidNpu = true;
series[key].data.push({ x: statsIdx + 1, y: stats.npu });
}
});
});
if (!hasValidNpu) {
return [];
}
return Object.keys(series).length > 0 ? Object.values(series) : [];
}, [statsHistory]);
// other processes stats // other processes stats
const hardwareType = useMemo(() => {
const hasGpu = statsHistory[0]?.gpu_usages != undefined;
const hasNpu = statsHistory[0]?.npu_usages != undefined;
if (hasGpu && !hasNpu) {
return "GPUs";
} else if (!hasGpu && hasNpu) {
return "NPUs";
} else {
return "GPUs / NPUs";
}
}, [statsHistory]);
const otherProcessCpuSeries = useMemo(() => { const otherProcessCpuSeries = useMemo(() => {
if (!statsHistory) { if (!statsHistory) {
return []; return [];
@ -533,11 +582,13 @@ export default function GeneralMetrics({
)} )}
</div> </div>
{(statsHistory.length == 0 || statsHistory[0].gpu_usages) && ( {(statsHistory.length == 0 ||
statsHistory[0].gpu_usages ||
statsHistory[0].npu_usages) && (
<> <>
<div className="mt-4 flex items-center justify-between"> <div className="mt-4 flex items-center justify-between">
<div className="text-sm font-medium text-muted-foreground"> <div className="text-sm font-medium text-muted-foreground">
GPUs {hardwareType}
</div> </div>
{canGetGpuInfo && ( {canGetGpuInfo && (
<Button <Button
@ -556,97 +607,127 @@ export default function GeneralMetrics({
gpuEncSeries?.length && "md:grid-cols-4", gpuEncSeries?.length && "md:grid-cols-4",
)} )}
> >
{statsHistory.length != 0 ? ( {statsHistory[0]?.gpu_usages && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <>
<div className="mb-5"> {statsHistory.length != 0 ? (
{t("general.hardwareInfo.gpuUsage")} <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
</div> <div className="mb-5">
{gpuSeries.map((series) => ( {t("general.hardwareInfo.gpuUsage")}
<ThresholdBarGraph </div>
key={series.name} {gpuSeries.map((series) => (
graphId={`${series.name}-gpu`} <ThresholdBarGraph
name={series.name} key={series.name}
unit="%" graphId={`${series.name}-gpu`}
threshold={GPUUsageThreshold} name={series.name}
updateTimes={updateTimes} unit="%"
data={[series]} threshold={GPUUsageThreshold}
/> updateTimes={updateTimes}
))} data={[series]}
/>
))}
</div>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{gpuMemSeries && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuMemory")}
</div>
{gpuMemSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-mem`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{gpuEncSeries && gpuEncSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuEncoder")}
</div>
{gpuEncSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-enc`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{gpuDecSeries && gpuDecSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuDecoder")}
</div>
{gpuDecSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-dec`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
</>
)}
{statsHistory[0]?.npu_usages && (
<div
className={cn("mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2")}
>
{statsHistory.length != 0 ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.npuUsage")}
</div>
{npuSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-npu`}
name={series.name}
unit="%"
threshold={GPUUsageThreshold}
updateTimes={updateTimes}
data={[series]}
/>
))}
</div>
) : (
<Skeleton className="aspect-video w-full" />
)}
</div> </div>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{gpuMemSeries && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuMemory")}
</div>
{gpuMemSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-mem`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{gpuEncSeries && gpuEncSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuEncoder")}
</div>
{gpuEncSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-enc`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{gpuDecSeries && gpuDecSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuDecoder")}
</div>
{gpuDecSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-dec`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)} )}
</div> </div>
</> </>