More accurately label Intel stats

This commit is contained in:
Nicolas Mowen 2026-03-28 11:13:09 -06:00
parent d03a24b8fe
commit db066df2c4
7 changed files with 160 additions and 8 deletions

View File

@ -360,6 +360,11 @@ class CustomCollector(object):
"GPU encoder utilisation %", "GPU encoder utilisation %",
labels=["gpu_name"], labels=["gpu_name"],
) )
gpu_compute_usages = GaugeMetricFamily(
"frigate_gpu_compute_usage_percent",
"GPU compute / encode utilisation %",
labels=["gpu_name"],
)
gpu_dec_usages = GaugeMetricFamily( gpu_dec_usages = GaugeMetricFamily(
"frigate_gpu_decoder_usage_percent", "frigate_gpu_decoder_usage_percent",
"GPU decoder utilisation %", "GPU decoder utilisation %",
@ -371,6 +376,9 @@ class CustomCollector(object):
self.add_metric(gpu_usages, [gpu_name], gpu_stats, "gpu") self.add_metric(gpu_usages, [gpu_name], gpu_stats, "gpu")
self.add_metric(gpu_mem_usages, [gpu_name], gpu_stats, "mem") self.add_metric(gpu_mem_usages, [gpu_name], gpu_stats, "mem")
self.add_metric(gpu_enc_usages, [gpu_name], gpu_stats, "enc") self.add_metric(gpu_enc_usages, [gpu_name], gpu_stats, "enc")
self.add_metric(
gpu_compute_usages, [gpu_name], gpu_stats, "compute"
)
self.add_metric(gpu_dec_usages, [gpu_name], gpu_stats, "dec") self.add_metric(gpu_dec_usages, [gpu_name], gpu_stats, "dec")
except KeyError: except KeyError:
pass pass
@ -378,6 +386,7 @@ class CustomCollector(object):
yield gpu_usages yield gpu_usages
yield gpu_mem_usages yield gpu_mem_usages
yield gpu_enc_usages yield gpu_enc_usages
yield gpu_compute_usages
yield gpu_dec_usages yield gpu_dec_usages
# service stats # service stats

View File

@ -45,6 +45,6 @@ class TestGpuStats(unittest.TestCase):
assert intel_stats == { assert intel_stats == {
"gpu": "26.08%", "gpu": "26.08%",
"mem": "-%", "mem": "-%",
"enc": "0.0%", "compute": "0.0%",
"dec": "2.27%", "dec": "2.27%",
} }

View File

@ -296,7 +296,7 @@ def get_intel_gpu_stats(intel_gpu_device: Optional[str]) -> Optional[dict[str, s
render.append(float(single)) render.append(float(single))
if render: if render:
results["enc"] = f"{round(sum(render) / len(render), 2)}%" results["compute"] = f"{round(sum(render) / len(render), 2)}%"
# Video engines are the fixed-function decode engines # Video engines are the fixed-function decode engines
video = [] video = []
@ -395,9 +395,9 @@ def get_intel_gpu_stats(intel_gpu_device: Optional[str]) -> Optional[dict[str, s
results["mem"] = "-%" results["mem"] = "-%"
# Encoder: Render/3D engine (compute/shader encode) # Compute: Render/3D engine (compute/shader workloads and QSV encode)
if render_global: if render_global:
results["enc"] = f"{round(sum(render_global) / len(render_global), 2)}%" results["compute"] = f"{round(sum(render_global) / len(render_global), 2)}%"
# Decoder: Video engine (fixed-function codec) # Decoder: Video engine (fixed-function codec)
if video_global: if video_global:

View File

@ -78,6 +78,7 @@
"gpuUsage": "GPU Usage", "gpuUsage": "GPU Usage",
"gpuMemory": "GPU Memory", "gpuMemory": "GPU Memory",
"gpuEncoder": "GPU Encoder", "gpuEncoder": "GPU Encoder",
"gpuCompute": "GPU Compute / Encode",
"gpuDecoder": "GPU Decoder", "gpuDecoder": "GPU Decoder",
"gpuTemperature": "GPU Temperature", "gpuTemperature": "GPU Temperature",
"gpuInfo": { "gpuInfo": {
@ -188,6 +189,7 @@
"cameraFfmpeg": "{{camName}} FFmpeg", "cameraFfmpeg": "{{camName}} FFmpeg",
"cameraCapture": "{{camName}} capture", "cameraCapture": "{{camName}} capture",
"cameraDetect": "{{camName}} detect", "cameraDetect": "{{camName}} detect",
"cameraGpu": "{{camName}} GPU",
"cameraFramesPerSecond": "{{camName}} frames per second", "cameraFramesPerSecond": "{{camName}} frames per second",
"cameraDetectionsPerSecond": "{{camName}} detections per second", "cameraDetectionsPerSecond": "{{camName}} detections per second",
"cameraSkippedDetectionsPerSecond": "{{camName}} skipped detections per second" "cameraSkippedDetectionsPerSecond": "{{camName}} skipped detections per second"

View File

@ -60,8 +60,10 @@ export type GpuStats = {
mem: string; mem: string;
enc?: string; enc?: string;
dec?: string; dec?: string;
compute?: string;
pstate?: string; pstate?: string;
temp?: number; temp?: number;
clients?: { [pid: string]: string };
}; };
export type NpuStats = { export type NpuStats = {

View File

@ -3,6 +3,7 @@ import { CameraLineGraph } from "@/components/graph/LineGraph";
import CameraInfoDialog from "@/components/overlay/CameraInfoDialog"; import CameraInfoDialog from "@/components/overlay/CameraInfoDialog";
import { ConnectionQualityIndicator } from "@/components/camera/ConnectionQualityIndicator"; import { ConnectionQualityIndicator } from "@/components/camera/ConnectionQualityIndicator";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { FrigateStats } from "@/types/stats"; import { FrigateStats } from "@/types/stats";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
@ -43,7 +44,7 @@ export default function CameraMetrics({
[ [
"stats/history", "stats/history",
{ {
keys: "cpu_usages,cameras,camera_fps,detection_fps,skipped_fps,service", keys: "cpu_usages,gpu_usages,cameras,camera_fps,detection_fps,skipped_fps,service",
}, },
], ],
{ {
@ -183,6 +184,64 @@ export default function CameraMetrics({
return series; return series;
}, [config, getCameraName, statsHistory, t]); }, [config, getCameraName, statsHistory, t]);
const cameraGpuSeries = useMemo(() => {
if (!statsHistory || statsHistory.length == 0) {
return {};
}
// Find GPU entry with per-client data
const firstGpu = statsHistory.find((s) => s?.gpu_usages);
if (!firstGpu?.gpu_usages) {
return {};
}
const gpuWithClients = Object.values(firstGpu.gpu_usages).find(
(g) => g.clients,
);
if (!gpuWithClients) {
return {};
}
const series: {
[cam: string]: {
[key: string]: { name: string; data: { x: number; y: string }[] };
};
} = {};
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
const gpuClients = Object.values(stats.gpu_usages || {}).find(
(g) => g.clients,
)?.clients;
Object.entries(stats.cameras).forEach(([key, camStats]) => {
if (!config?.cameras[key].enabled) {
return;
}
if (!(key in series)) {
const camName = getCameraName(key);
series[key] = {};
series[key]["gpu"] = {
name: t("cameras.label.cameraGpu", { camName: camName }),
data: [],
};
}
const pid = camStats.ffmpeg_pid?.toString();
series[key]["gpu"].data.push({
x: statsIdx,
y: (gpuClients && pid ? gpuClients[pid] : undefined)
?.slice(0, -1) ?? "0",
});
});
});
return series;
}, [config, getCameraName, statsHistory, t]);
const cameraFpsSeries = useMemo(() => { const cameraFpsSeries = useMemo(() => {
if (!statsHistory) { if (!statsHistory) {
return {}; return {};
@ -332,7 +391,11 @@ export default function CameraMetrics({
</div> </div>
<div <div
key={camera.name} key={camera.name}
className="grid gap-2 sm:grid-cols-2" className={cn(
"grid gap-2 sm:grid-cols-2",
Object.keys(cameraGpuSeries).length > 0 &&
"lg:grid-cols-3",
)}
> >
{Object.keys(cameraCpuSeries).includes(camera.name) ? ( {Object.keys(cameraCpuSeries).includes(camera.name) ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
@ -350,6 +413,20 @@ export default function CameraMetrics({
) : ( ) : (
<Skeleton className="aspect-video size-full" /> <Skeleton className="aspect-video size-full" />
)} )}
{Object.keys(cameraGpuSeries).includes(camera.name) && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">GPU</div>
<CameraLineGraph
graphId={`${camera.name}-gpu`}
unit="%"
dataLabels={["gpu"]}
updateTimes={updateTimes}
data={Object.values(
cameraGpuSeries[camera.name] || {},
)}
/>
</div>
)}
{Object.keys(cameraFpsSeries).includes(camera.name) ? ( {Object.keys(cameraFpsSeries).includes(camera.name) ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl"> <div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5"> <div className="mb-5">

View File

@ -334,6 +334,43 @@ 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]);
const gpuComputeSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: string }[] };
} = {};
let hasValidGpu = false;
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => {
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
if (stats.compute) {
hasValidGpu = true;
series[key].data.push({
x: statsIdx + 1,
y: stats.compute.slice(0, -1),
});
}
});
});
if (!hasValidGpu) {
return [];
}
return Object.keys(series).length > 0 ? Object.values(series) : undefined;
}, [statsHistory]);
const gpuDecSeries = useMemo(() => { const gpuDecSeries = useMemo(() => {
if (!statsHistory) { if (!statsHistory) {
return []; return [];
@ -742,8 +779,9 @@ export default function GeneralMetrics({
className={cn( className={cn(
"mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2", "mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2",
gpuTempSeries?.length && "md:grid-cols-3", gpuTempSeries?.length && "md:grid-cols-3",
gpuEncSeries?.length && "xl:grid-cols-4", (gpuEncSeries?.length || gpuComputeSeries?.length) &&
gpuEncSeries?.length && "xl:grid-cols-4",
(gpuEncSeries?.length || gpuComputeSeries?.length) &&
gpuTempSeries?.length && gpuTempSeries?.length &&
"3xl:grid-cols-5", "3xl:grid-cols-5",
)} )}
@ -856,6 +894,30 @@ export default function GeneralMetrics({
) : ( ) : (
<Skeleton className="aspect-video w-full" /> <Skeleton className="aspect-video w-full" />
)} )}
{statsHistory.length != 0 ? (
<>
{gpuComputeSeries && gpuComputeSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuCompute")}
</div>
{gpuComputeSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-compute`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? ( {statsHistory.length != 0 ? (
<> <>
{gpuDecSeries && gpuDecSeries?.length != 0 && ( {gpuDecSeries && gpuDecSeries?.length != 0 && (