diff --git a/web/src/components/Statusbar.tsx b/web/src/components/Statusbar.tsx
index e5564eecf..52bbe3114 100644
--- a/web/src/components/Statusbar.tsx
+++ b/web/src/components/Statusbar.tsx
@@ -1,6 +1,8 @@
import { useFrigateStats } from "@/api/ws";
+import useStats from "@/hooks/use-stats";
import { FrigateStats } from "@/types/stats";
import { useMemo } from "react";
+import { IoIosWarning } from "react-icons/io";
import { MdCircle } from "react-icons/md";
import useSWR from "swr";
@@ -27,58 +29,70 @@ export default function Statusbar() {
return parseInt(systemCpu);
}, [stats]);
+ const { potentialProblems } = useStats(stats);
+
return (
-
- {cpuPercent && (
-
-
- CPU {cpuPercent}%
-
- )}
- {Object.entries(stats?.gpu_usages || {}).map(([name, stats]) => {
- if (name == "error-gpu") {
- return;
- }
-
- let gpuTitle;
- switch (name) {
- case "amd-vaapi":
- gpuTitle = "AMD GPU";
- break;
- case "intel-vaapi":
- case "intel-qsv":
- gpuTitle = "Intel GPU";
- break;
- default:
- gpuTitle = name;
- break;
- }
-
- const gpu = parseInt(stats.gpu);
-
- return (
-
+
+
+ {cpuPercent && (
+
- {gpuTitle} {gpu}%
+ CPU {cpuPercent}%
- );
- })}
+ )}
+ {Object.entries(stats?.gpu_usages || {}).map(([name, stats]) => {
+ if (name == "error-gpu") {
+ return;
+ }
+
+ let gpuTitle;
+ switch (name) {
+ case "amd-vaapi":
+ gpuTitle = "AMD GPU";
+ break;
+ case "intel-vaapi":
+ case "intel-qsv":
+ gpuTitle = "Intel GPU";
+ break;
+ default:
+ gpuTitle = name;
+ break;
+ }
+
+ const gpu = parseInt(stats.gpu);
+
+ return (
+
+
+ {gpuTitle} {gpu}%
+
+ );
+ })}
+
+
+ {potentialProblems.map((prob) => (
+
+
+ {prob.text}
+
+ ))}
+
);
}
diff --git a/web/src/hooks/use-stats.ts b/web/src/hooks/use-stats.ts
new file mode 100644
index 000000000..b5e0407a6
--- /dev/null
+++ b/web/src/hooks/use-stats.ts
@@ -0,0 +1,63 @@
+import { FrigateStats, PotentialProblem } from "@/types/stats";
+import { useMemo } from "react";
+
+export default function useStats(stats: FrigateStats | undefined) {
+ const potentialProblems = useMemo
(() => {
+ const problems: PotentialProblem[] = [];
+
+ if (!stats) {
+ return problems;
+ }
+
+ // check detectors for high inference speeds
+ Object.entries(stats["detectors"]).forEach(([key, det]) => {
+ if (det["inference_speed"] > 100) {
+ problems.push({
+ text: `${key} is very slow (${det["inference_speed"]} ms)`,
+ color: "text-danger",
+ });
+ } else if (det["inference_speed"] > 50) {
+ problems.push({
+ text: `${key} is slow (${det["inference_speed"]} ms)`,
+ color: "text-orange-400",
+ });
+ }
+ });
+
+ // check for offline cameras
+ Object.entries(stats["cameras"]).forEach(([name, cam]) => {
+ if (cam["camera_fps"] == 0) {
+ problems.push({
+ text: `${name.replaceAll("_", " ")} is offline`,
+ color: "text-danger",
+ });
+ }
+ });
+
+ // check camera cpu usages
+ Object.entries(stats["cameras"]).forEach(([name, cam]) => {
+ const ffmpegAvg = parseFloat(
+ stats["cpu_usages"][cam["ffmpeg_pid"]].cpu_average,
+ );
+ const detectAvg = parseFloat(stats["cpu_usages"][cam["pid"]].cpu_average);
+
+ if (!isNaN(ffmpegAvg) && ffmpegAvg >= 20.0) {
+ problems.push({
+ text: `${name.replaceAll("_", " ")} has high FFMPEG CPU usage (${ffmpegAvg}%)`,
+ color: "text-danger",
+ });
+ }
+
+ if (!isNaN(detectAvg) && detectAvg >= 40.0) {
+ problems.push({
+ text: `${name.replaceAll("_", " ")} has high detect CPU usage (${detectAvg}%)`,
+ color: "text-danger",
+ });
+ }
+ });
+
+ return problems;
+ }, [stats]);
+
+ return { potentialProblems };
+}
diff --git a/web/src/types/stats.ts b/web/src/types/stats.ts
index 1ae1199c0..831e2e639 100644
--- a/web/src/types/stats.ts
+++ b/web/src/types/stats.ts
@@ -58,3 +58,8 @@ export type StorageStats = {
used: number;
mount_type: string;
};
+
+export type PotentialProblem = {
+ text: string;
+ color: string;
+};