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/components/navigation/Bottombar.tsx b/web/src/components/navigation/Bottombar.tsx
index d4e1e0760..68b28d22a 100644
--- a/web/src/components/navigation/Bottombar.tsx
+++ b/web/src/components/navigation/Bottombar.tsx
@@ -1,6 +1,13 @@
import { navbarLinks } from "@/pages/site-navigation";
import NavItem from "./NavItem";
import SettingsNavItems from "../settings/SettingsNavItems";
+import { IoIosWarning } from "react-icons/io";
+import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
+import useSWR from "swr";
+import { FrigateStats } from "@/types/stats";
+import { useFrigateStats } from "@/api/ws";
+import { useMemo } from "react";
+import useStats from "@/hooks/use-stats";
function Bottombar() {
return (
@@ -17,10 +24,46 @@ function Bottombar() {
/>
))}
+
);
}
-//
+function StatusAlertNav() {
+ const { data: initialStats } = useSWR
("stats", {
+ revalidateOnFocus: false,
+ });
+ const { payload: latestStats } = useFrigateStats();
+ const stats = useMemo(() => {
+ if (latestStats) {
+ return latestStats;
+ }
+
+ return initialStats;
+ }, [initialStats, latestStats]);
+ const { potentialProblems } = useStats(stats);
+
+ if (!potentialProblems || potentialProblems.length == 0) {
+ return;
+ }
+
+ return (
+
+
+
+
+
+
+ {potentialProblems.map((prob) => (
+
+
+ {prob.text}
+
+ ))}
+
+
+
+ );
+}
export default Bottombar;
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;
+};