diff --git a/web/src/components/graph/SystemGraph.tsx b/web/src/components/graph/SystemGraph.tsx new file mode 100644 index 000000000..1593548ca --- /dev/null +++ b/web/src/components/graph/SystemGraph.tsx @@ -0,0 +1,58 @@ +import Chart from "react-apexcharts"; + +type SystemGraphProps = { + graphId: string; + title: string; + unit: string; + data: ApexAxisChartSeries; +}; +export default function SystemGraph({ + graphId, + title, + unit, + data, +}: SystemGraphProps) { + return ( + { + return `${value.toFixed(2)} ${unit}`; + }, + }, + }, + }} + series={data} + height="100%" + /> + ); +} diff --git a/web/src/pages/System.tsx b/web/src/pages/System.tsx index 2a9c456a6..cdbb99cb0 100644 --- a/web/src/pages/System.tsx +++ b/web/src/pages/System.tsx @@ -1,9 +1,222 @@ import Heading from "@/components/ui/heading"; +import useSWR from "swr"; +import { FrigateStats } from "@/types/stats"; +import { useMemo } from "react"; +import SystemGraph from "@/components/graph/SystemGraph"; function System() { + // stats + const { data: statsHistory } = useSWR("stats/history", { + revalidateOnFocus: false, + }); + + // stats data pieces + const inferenceTimeSeries = useMemo(() => { + if (!statsHistory) { + return []; + } + + const series: { + [key: string]: { name: string; data: { x: any; y: any }[] }; + } = {}; + + statsHistory.forEach((stats) => { + const statTime = new Date(stats.service.last_updated * 1000); + + Object.entries(stats.detectors).forEach(([key, stats]) => { + if (!(key in series)) { + series[key] = { name: `${key} (${stats.pid})`, data: [] }; + } + + series[key].data.push({ x: statTime, y: stats.inference_speed }); + }); + }); + return Object.values(series); + }, [statsHistory]); + const cpuMemSeries = useMemo(() => { + if (!statsHistory) { + return []; + } + + const series: { + [key: string]: { name: string; data: { x: any; y: any }[] }; + } = {}; + + statsHistory.forEach((stats) => { + const statTime = new Date(stats.service.last_updated * 1000); + + Object.entries(stats.detectors).forEach(([key, detectorStats]) => { + const cpuKey = `${key}-cpu`; + const memKey = `${key}-mem`; + + if (!(cpuKey in series)) { + series[cpuKey] = { name: `${key} Cpu`, data: [] }; + } + + if (!(memKey in series)) { + series[memKey] = { name: `${key} Memory`, data: [] }; + } + + const detUsages = stats.cpu_usages[detectorStats.pid.toString()]; + + series[cpuKey].data.push({ x: statTime, y: detUsages.cpu }); + series[memKey].data.push({ x: statTime, y: detUsages.mem }); + }); + }); + return Object.values(series); + }, [statsHistory]); + const gpuSeries = useMemo(() => { + if (!statsHistory) { + return []; + } + + const series: { + [key: string]: { name: string; data: { x: any; y: any }[] }; + } = {}; + + statsHistory.forEach((stats) => { + const statTime = new Date(stats.service.last_updated * 1000); + + Object.entries(stats.gpu_usages || []).forEach(([key, stats]) => { + if (!(key in series)) { + series[key] = { name: key, data: [] }; + } + + series[key].data.push({ x: statTime, y: stats.gpu }); + }); + }); + return Object.keys(series).length > 0 ? Object.values(series) : []; + }, [statsHistory]); + const gpuMemSeries = useMemo(() => { + if (!statsHistory) { + return []; + } + + const series: { + [key: string]: { name: string; data: { x: any; y: any }[] }; + } = {}; + + statsHistory.forEach((stats) => { + const statTime = new Date(stats.service.last_updated * 1000); + + Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => { + if (!(key in series)) { + series[key] = { name: key, data: [] }; + } + + series[key].data.push({ x: statTime, y: stats.mem }); + }); + }); + return Object.values(series); + }, [statsHistory]); + const otherProcessCpuSeries = useMemo(() => { + if (!statsHistory) { + return []; + } + + const series: { + [key: string]: { name: string; data: { x: any; y: any }[] }; + } = {}; + + statsHistory.forEach((stats) => { + const statTime = new Date(stats.service.last_updated * 1000); + + Object.entries(stats.processes).forEach(([key, procStats]) => { + if (procStats.pid.toString() in stats.cpu_usages) { + if (!(key in series)) { + series[key] = { name: `${key} (${procStats.pid})`, data: [] }; + } + + series[key].data.push({ + x: statTime, + y: stats.cpu_usages[procStats.pid.toString()].cpu, + }); + } + }); + }); + return Object.keys(series).length > 0 ? Object.values(series) : []; + }, [statsHistory]); + const otherProcessMemSeries = useMemo(() => { + if (!statsHistory) { + return []; + } + + const series: { + [key: string]: { name: string; data: { x: any; y: any }[] }; + } = {}; + + statsHistory.forEach((stats) => { + const statTime = new Date(stats.service.last_updated * 1000); + + Object.entries(stats.processes).forEach(([key, procStats]) => { + if (procStats.pid.toString() in stats.cpu_usages) { + if (!(key in series)) { + series[key] = { name: key, data: [] }; + } + + series[key].data.push({ + x: statTime, + y: stats.cpu_usages[procStats.pid.toString()].mem, + }); + } + }); + }); + return Object.values(series); + }, [statsHistory]); + return ( <> System + + Detectors +
+ + +
+ {gpuSeries.length > 0 && ( + <> + GPUs +
+ + +
+ + )} + Other Processes +
+ + +
); }