mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Show statusbar with cpu and gpu stats (#9968)
* Show statusbar with cpu and gpu stats * fix gif logic
This commit is contained in:
		
							parent
							
								
									33c77d03c7
								
							
						
					
					
						commit
						6626b8d758
					
				@ -616,8 +616,8 @@ def event_preview(id: str, max_cache_age=2592000):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    start_ts = event.start_time
 | 
					    start_ts = event.start_time
 | 
				
			||||||
    end_ts = (
 | 
					    end_ts = start_ts + (
 | 
				
			||||||
        start_ts + min(event.end_time - event.start_time, 20) if event.end_time else 20
 | 
					        min(event.end_time - event.start_time, 20) if event.end_time else 20
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if datetime.fromtimestamp(event.start_time) < datetime.now().replace(
 | 
					    if datetime.fromtimestamp(event.start_time) < datetime.now().replace(
 | 
				
			||||||
 | 
				
			|||||||
@ -103,8 +103,17 @@ def get_cpu_stats() -> dict[str, dict]:
 | 
				
			|||||||
    docker_memlimit = get_docker_memlimit_bytes() / 1024
 | 
					    docker_memlimit = get_docker_memlimit_bytes() / 1024
 | 
				
			||||||
    total_mem = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES") / 1024
 | 
					    total_mem = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES") / 1024
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    system_cpu = psutil.cpu_percent(
 | 
				
			||||||
 | 
					        interval=None
 | 
				
			||||||
 | 
					    )  # no interval as we don't want to be blocking
 | 
				
			||||||
 | 
					    system_mem = psutil.virtual_memory()
 | 
				
			||||||
 | 
					    usages["frigate.full_system"] = {
 | 
				
			||||||
 | 
					        "cpu": str(system_cpu),
 | 
				
			||||||
 | 
					        "mem": str(system_mem.percent),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for process in psutil.process_iter(["pid", "name", "cpu_percent", "cmdline"]):
 | 
					    for process in psutil.process_iter(["pid", "name", "cpu_percent", "cmdline"]):
 | 
				
			||||||
        pid = process.info["pid"]
 | 
					        pid = str(process.info["pid"])
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            cpu_percent = process.info["cpu_percent"]
 | 
					            cpu_percent = process.info["cpu_percent"]
 | 
				
			||||||
            cmdline = process.info["cmdline"]
 | 
					            cmdline = process.info["cmdline"]
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,8 @@ import NoMatch from "@/pages/NoMatch";
 | 
				
			|||||||
import Settings from "@/pages/Settings";
 | 
					import Settings from "@/pages/Settings";
 | 
				
			||||||
import UIPlayground from "./pages/UIPlayground";
 | 
					import UIPlayground from "./pages/UIPlayground";
 | 
				
			||||||
import Events from "./pages/Events";
 | 
					import Events from "./pages/Events";
 | 
				
			||||||
 | 
					import { isDesktop } from "react-device-detect";
 | 
				
			||||||
 | 
					import Statusbar from "./components/Statusbar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function App() {
 | 
					function App() {
 | 
				
			||||||
  const [sheetOpen, setSheetOpen] = useState(false);
 | 
					  const [sheetOpen, setSheetOpen] = useState(false);
 | 
				
			||||||
@ -30,9 +32,10 @@ function App() {
 | 
				
			|||||||
          <Header onToggleNavbar={toggleNavbar} />
 | 
					          <Header onToggleNavbar={toggleNavbar} />
 | 
				
			||||||
          <div className="w-full h-full pt-2 overflow-hidden">
 | 
					          <div className="w-full h-full pt-2 overflow-hidden">
 | 
				
			||||||
            <Sidebar sheetOpen={sheetOpen} setSheetOpen={setSheetOpen} />
 | 
					            <Sidebar sheetOpen={sheetOpen} setSheetOpen={setSheetOpen} />
 | 
				
			||||||
 | 
					            {isDesktop && <Statusbar />}
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
              id="pageRoot"
 | 
					              id="pageRoot"
 | 
				
			||||||
              className="absolute left-0 md:left-16 top-16 md:top-2 right-0 bottom-0 overflow-hidden"
 | 
					              className="absolute left-0 md:left-16 top-16 md:top-2 right-0 bottom-0 md:bottom-8 overflow-hidden"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              <Routes>
 | 
					              <Routes>
 | 
				
			||||||
                <Route path="/" element={<Live />} />
 | 
					                <Route path="/" element={<Live />} />
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ import { produce, Draft } from "immer";
 | 
				
			|||||||
import useWebSocket, { ReadyState } from "react-use-websocket";
 | 
					import useWebSocket, { ReadyState } from "react-use-websocket";
 | 
				
			||||||
import { FrigateConfig } from "@/types/frigateConfig";
 | 
					import { FrigateConfig } from "@/types/frigateConfig";
 | 
				
			||||||
import { FrigateEvent, ToggleableSetting } from "@/types/ws";
 | 
					import { FrigateEvent, ToggleableSetting } from "@/types/ws";
 | 
				
			||||||
 | 
					import { FrigateStats } from "@/types/stats";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ReducerState = {
 | 
					type ReducerState = {
 | 
				
			||||||
  [topic: string]: {
 | 
					  [topic: string]: {
 | 
				
			||||||
@ -221,6 +222,13 @@ export function useFrigateEvents(): { payload: FrigateEvent } {
 | 
				
			|||||||
  return { payload };
 | 
					  return { payload };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useFrigateStats(): { payload: FrigateStats } {
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    value: { payload },
 | 
				
			||||||
 | 
					  } = useWs("stats", "");
 | 
				
			||||||
 | 
					  return { payload };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useMotionActivity(camera: string): { payload: string } {
 | 
					export function useMotionActivity(camera: string): { payload: string } {
 | 
				
			||||||
  const {
 | 
					  const {
 | 
				
			||||||
    value: { payload },
 | 
					    value: { payload },
 | 
				
			||||||
 | 
				
			|||||||
@ -34,7 +34,7 @@ function Sidebar({
 | 
				
			|||||||
          />
 | 
					          />
 | 
				
			||||||
        ))}
 | 
					        ))}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <SettingsNavItems className="hidden md:flex flex-col items-center" />
 | 
					      <SettingsNavItems className="hidden md:flex flex-col items-center mb-8" />
 | 
				
			||||||
    </aside>
 | 
					    </aside>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										84
									
								
								web/src/components/Statusbar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								web/src/components/Statusbar.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					import { useFrigateStats } from "@/api/ws";
 | 
				
			||||||
 | 
					import { FrigateStats } from "@/types/stats";
 | 
				
			||||||
 | 
					import { useMemo } from "react";
 | 
				
			||||||
 | 
					import { MdCircle } from "react-icons/md";
 | 
				
			||||||
 | 
					import useSWR from "swr";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Statusbar({}) {
 | 
				
			||||||
 | 
					  const { data: initialStats } = useSWR<FrigateStats>("stats", {
 | 
				
			||||||
 | 
					    revalidateOnFocus: false,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const { payload: latestStats } = useFrigateStats();
 | 
				
			||||||
 | 
					  const stats = useMemo(() => {
 | 
				
			||||||
 | 
					    if (latestStats) {
 | 
				
			||||||
 | 
					      return latestStats;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return initialStats;
 | 
				
			||||||
 | 
					  }, [initialStats, latestStats]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const cpuPercent = useMemo(() => {
 | 
				
			||||||
 | 
					    const systemCpu = stats?.cpu_usages["frigate.full_system"]?.cpu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!systemCpu || systemCpu == "0.0") {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return parseInt(systemCpu);
 | 
				
			||||||
 | 
					  }, [stats]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="absolute left-0 bottom-0 right-0 w-full h-8 flex items-center px-4">
 | 
				
			||||||
 | 
					      {cpuPercent && (
 | 
				
			||||||
 | 
					        <div className="flex items-center text-sm mr-4">
 | 
				
			||||||
 | 
					          <MdCircle
 | 
				
			||||||
 | 
					            className={`w-2 h-2 mr-2 ${
 | 
				
			||||||
 | 
					              cpuPercent < 50
 | 
				
			||||||
 | 
					                ? "text-green-500"
 | 
				
			||||||
 | 
					                : cpuPercent < 80
 | 
				
			||||||
 | 
					                  ? "text-orange-400"
 | 
				
			||||||
 | 
					                  : "text-danger"
 | 
				
			||||||
 | 
					            }`}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          CPU {cpuPercent}%
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {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 (
 | 
				
			||||||
 | 
					          <div className="flex items-center text-sm">
 | 
				
			||||||
 | 
					            <MdCircle
 | 
				
			||||||
 | 
					              className={`w-2 h-2 mr-2 ${
 | 
				
			||||||
 | 
					                gpu < 50
 | 
				
			||||||
 | 
					                  ? "text-green-500"
 | 
				
			||||||
 | 
					                  : gpu < 80
 | 
				
			||||||
 | 
					                    ? "text-orange-400"
 | 
				
			||||||
 | 
					                    : "text-danger"
 | 
				
			||||||
 | 
					              }`}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            {gpuTitle} {gpu}%
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      })}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -79,7 +79,7 @@ function Live() {
 | 
				
			|||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="w-full h-full overflow-scroll">
 | 
					    <div className="w-full h-full overflow-scroll px-2">
 | 
				
			||||||
      {events && events.length > 0 && (
 | 
					      {events && events.length > 0 && (
 | 
				
			||||||
        <ScrollArea>
 | 
					        <ScrollArea>
 | 
				
			||||||
          <TooltipProvider>
 | 
					          <TooltipProvider>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										60
									
								
								web/src/types/stats.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								web/src/types/stats.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					export interface FrigateStats {
 | 
				
			||||||
 | 
					    cameras: { [camera_name: string]: CameraStats };
 | 
				
			||||||
 | 
					    cpu_usages: { [pid: string]: CpuStats };
 | 
				
			||||||
 | 
					    detectors: { [detectorKey: string]: DetectorStats };
 | 
				
			||||||
 | 
					    gpu_usages?: { [gpuKey: string]: GpuStats };
 | 
				
			||||||
 | 
					    processes: { [processKey: string]: ExtraProcessStats };
 | 
				
			||||||
 | 
					    service: ServiceStats;
 | 
				
			||||||
 | 
					    detection_fps: number;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export type CameraStats = {
 | 
				
			||||||
 | 
					    audio_dBFPS: number;
 | 
				
			||||||
 | 
					    audio_rms: number;
 | 
				
			||||||
 | 
					    camera_fps: number;
 | 
				
			||||||
 | 
					    capture_pid: number;
 | 
				
			||||||
 | 
					    detection_enabled: number;
 | 
				
			||||||
 | 
					    detection_fps: number;
 | 
				
			||||||
 | 
					    ffmpeg_pid: number;
 | 
				
			||||||
 | 
					    pid: number;
 | 
				
			||||||
 | 
					    process_fps: number;
 | 
				
			||||||
 | 
					    skipped_fps: number;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export type CpuStats = {
 | 
				
			||||||
 | 
					    cmdline: string;
 | 
				
			||||||
 | 
					    cpu: string;
 | 
				
			||||||
 | 
					    cpu_average: string;
 | 
				
			||||||
 | 
					    mem: string;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export type DetectorStats = {
 | 
				
			||||||
 | 
					    detection_start: number;
 | 
				
			||||||
 | 
					    inference_speed: number;
 | 
				
			||||||
 | 
					    pid: number;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export type ExtraProcessStats = {
 | 
				
			||||||
 | 
					    pid: number;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export type GpuStats = {
 | 
				
			||||||
 | 
					    gpu: string;
 | 
				
			||||||
 | 
					    mem: string;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export type ServiceStats = {
 | 
				
			||||||
 | 
					    last_updated: number;
 | 
				
			||||||
 | 
					    storage: { [path: string]: StorageStats };
 | 
				
			||||||
 | 
					    temperatures: { [apex: string]: number };
 | 
				
			||||||
 | 
					    update: number;
 | 
				
			||||||
 | 
					    latest_version: string;
 | 
				
			||||||
 | 
					    version: string;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export type StorageStats = {
 | 
				
			||||||
 | 
					    free: number;
 | 
				
			||||||
 | 
					    total: number;
 | 
				
			||||||
 | 
					    used: number;
 | 
				
			||||||
 | 
					    mount_type: string;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user