import useSWR from "swr"; import { FrigateStats, GpuInfo } from "@/types/stats"; import { useEffect, useMemo, useState } from "react"; import { useFrigateStats } from "@/api/ws"; import { DetectorCpuThreshold, DetectorMemThreshold, DetectorTempThreshold, GPUMemThreshold, GPUUsageThreshold, InferenceThreshold, } from "@/types/graph"; import { Button } from "@/components/ui/button"; import GPUInfoDialog from "@/components/overlay/GPUInfoDialog"; import { Skeleton } from "@/components/ui/skeleton"; import { ThresholdBarGraph } from "@/components/graph/SystemGraph"; import { cn } from "@/lib/utils"; type GeneralMetricsProps = { lastUpdated: number; setLastUpdated: (last: number) => void; }; export default function GeneralMetrics({ lastUpdated, setLastUpdated, }: GeneralMetricsProps) { // extra info const [showVainfo, setShowVainfo] = useState(false); // stats const { data: initialStats } = useSWR( [ "stats/history", { keys: "cpu_usages,detectors,gpu_usages,processes,service" }, ], { revalidateOnFocus: false, }, ); const [statsHistory, setStatsHistory] = useState([]); const updatedStats = useFrigateStats(); useEffect(() => { if (initialStats == undefined || initialStats.length == 0) { return; } if (statsHistory.length == 0) { setStatsHistory(initialStats); return; } if (!updatedStats) { return; } if (updatedStats.service.last_updated > lastUpdated) { setStatsHistory([...statsHistory.slice(1), updatedStats]); setLastUpdated(Date.now() / 1000); } }, [initialStats, updatedStats, statsHistory, lastUpdated, setLastUpdated]); const [canGetGpuInfo, gpuType] = useMemo<[boolean, GpuInfo]>(() => { let vaCount = 0; let nvCount = 0; statsHistory.length > 0 && Object.keys(statsHistory[0]?.gpu_usages ?? {}).forEach((key) => { if (key == "amd-vaapi" || key == "intel-vaapi" || key == "intel-qsv") { vaCount += 1; } if (key.includes("NVIDIA")) { nvCount += 1; } }); return [vaCount > 0 || nvCount > 0, nvCount > 0 ? "nvinfo" : "vainfo"]; }, [statsHistory]); // timestamps const updateTimes = useMemo( () => statsHistory.map((stats) => stats.service.last_updated), [statsHistory], ); // detectors stats const detInferenceTimeSeries = useMemo(() => { if (!statsHistory) { return []; } const series: { [key: string]: { name: string; data: { x: number; y: number }[] }; } = {}; statsHistory.forEach((stats, statsIdx) => { if (!stats) { return; } Object.entries(stats.detectors).forEach(([key, stats]) => { if (!(key in series)) { series[key] = { name: key, data: [] }; } series[key].data.push({ x: statsIdx + 1, y: stats.inference_speed }); }); }); return Object.values(series); }, [statsHistory]); const detTempSeries = useMemo(() => { if (!statsHistory) { return undefined; } if ( statsHistory.length > 0 && Object.keys(statsHistory[0].service.temperatures).length == 0 ) { return undefined; } const series: { [key: string]: { name: string; data: { x: number; y: number }[] }; } = {}; statsHistory.forEach((stats, statsIdx) => { if (!stats) { return; } Object.entries(stats.detectors).forEach(([key], cIdx) => { if (!key.includes("coral")) { return; } if (cIdx <= Object.keys(stats.service.temperatures).length) { if (!(key in series)) { series[key] = { name: key, data: [], }; } const temp = Object.values(stats.service.temperatures)[cIdx]; series[key].data.push({ x: statsIdx + 1, y: Math.round(temp) }); } }); }); if (Object.keys(series).length > 0) { return Object.values(series); } return undefined; }, [statsHistory]); const detCpuSeries = useMemo(() => { if (!statsHistory) { return []; } const series: { [key: string]: { name: string; data: { x: number; y: string }[] }; } = {}; statsHistory.forEach((stats, statsIdx) => { if (!stats) { return; } Object.entries(stats.detectors).forEach(([key, detStats]) => { if (!(key in series)) { series[key] = { name: key, data: [] }; } const data = stats.cpu_usages[detStats.pid.toString()]?.cpu; if (data != undefined) { series[key].data.push({ x: statsIdx + 1, y: data, }); } }); }); return Object.values(series); }, [statsHistory]); const detMemSeries = useMemo(() => { if (!statsHistory) { return []; } const series: { [key: string]: { name: string; data: { x: number; y: string }[] }; } = {}; statsHistory.forEach((stats, statsIdx) => { if (!stats) { return; } Object.entries(stats.detectors).forEach(([key, detStats]) => { if (!(key in series)) { series[key] = { name: key, data: [] }; } series[key].data.push({ x: statsIdx + 1, y: stats.cpu_usages[detStats.pid.toString()].mem, }); }); }); return Object.values(series); }, [statsHistory]); // gpu stats const gpuSeries = 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.gpu) { hasValidGpu = true; series[key].data.push({ x: statsIdx + 1, y: stats.gpu.slice(0, -1) }); } }); }); if (!hasValidGpu) { return []; } return Object.keys(series).length > 0 ? Object.values(series) : []; }, [statsHistory]); const gpuMemSeries = useMemo(() => { if (!statsHistory) { return []; } if ( Object.keys(statsHistory?.at(0)?.gpu_usages ?? {}).length == 1 && Object.keys(statsHistory?.at(0)?.gpu_usages ?? {})[0].includes("intel") ) { // intel gpu stats do not support memory return undefined; } 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.mem) { hasValidGpu = true; series[key].data.push({ x: statsIdx + 1, y: stats.mem.slice(0, -1) }); } }); }); if (!hasValidGpu) { return []; } return Object.values(series); }, [statsHistory]); const gpuEncSeries = 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.enc) { hasValidGpu = true; series[key].data.push({ x: statsIdx + 1, y: stats.enc.slice(0, -1) }); } }); }); if (!hasValidGpu) { return []; } return Object.keys(series).length > 0 ? Object.values(series) : undefined; }, [statsHistory]); const gpuDecSeries = 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.dec) { hasValidGpu = true; series[key].data.push({ x: statsIdx + 1, y: stats.dec.slice(0, -1) }); } }); }); if (!hasValidGpu) { return []; } return Object.keys(series).length > 0 ? Object.values(series) : undefined; }, [statsHistory]); // other processes stats const otherProcessCpuSeries = useMemo(() => { if (!statsHistory) { return []; } const series: { [key: string]: { name: string; data: { x: number; y: string }[] }; } = {}; statsHistory.forEach((stats, statsIdx) => { if (!stats) { return; } Object.entries(stats.processes).forEach(([key, procStats]) => { if (procStats.pid.toString() in stats.cpu_usages) { if (!(key in series)) { series[key] = { name: key, data: [] }; } const data = stats.cpu_usages[procStats.pid.toString()]?.cpu; if (data != undefined) { series[key].data.push({ x: statsIdx + 1, y: data, }); } } }); }); return Object.keys(series).length > 0 ? Object.values(series) : []; }, [statsHistory]); const otherProcessMemSeries = useMemo(() => { if (!statsHistory) { return []; } const series: { [key: string]: { name: string; data: { x: number; y: string }[] }; } = {}; statsHistory.forEach((stats, statsIdx) => { if (!stats) { return; } Object.entries(stats.processes).forEach(([key, procStats]) => { if (procStats.pid.toString() in stats.cpu_usages) { if (!(key in series)) { series[key] = { name: key, data: [] }; } const data = stats.cpu_usages[procStats.pid.toString()]?.mem; if (data) { series[key].data.push({ x: statsIdx + 1, y: data, }); } } }); }); return Object.values(series); }, [statsHistory]); return ( <>
Detectors
{statsHistory.length != 0 ? (
Detector Inference Speed
{detInferenceTimeSeries.map((series) => ( ))}
) : ( )} {statsHistory.length != 0 && ( <> {detTempSeries && (
Detector Temperature
{detTempSeries.map((series) => ( ))}
)} )} {statsHistory.length != 0 ? (
Detector CPU Usage
{detCpuSeries.map((series) => ( ))}
) : ( )} {statsHistory.length != 0 ? (
Detector Memory Usage
{detMemSeries.map((series) => ( ))}
) : ( )}
{(statsHistory.length == 0 || statsHistory[0].gpu_usages) && ( <>
GPUs
{canGetGpuInfo && ( )}
{statsHistory.length != 0 ? (
GPU Usage
{gpuSeries.map((series) => ( ))}
) : ( )} {statsHistory.length != 0 ? ( <> {gpuMemSeries && (
GPU Memory
{gpuMemSeries.map((series) => ( ))}
)} ) : ( )} {statsHistory.length != 0 ? ( <> {gpuEncSeries && gpuEncSeries?.length != 0 && (
GPU Encoder
{gpuEncSeries.map((series) => ( ))}
)} ) : ( )} {statsHistory.length != 0 ? ( <> {gpuDecSeries && gpuDecSeries?.length != 0 && (
GPU Decoder
{gpuDecSeries.map((series) => ( ))}
)} ) : ( )}
)}
Other Processes
{statsHistory.length != 0 ? (
Process CPU Usage
{otherProcessCpuSeries.map((series) => ( ))}
) : ( )} {statsHistory.length != 0 ? (
Process Memory Usage
{otherProcessMemSeries.map((series) => ( ))}
) : ( )}
); }