Only store PID instead of entire process reference

This commit is contained in:
Nicolas Mowen 2025-06-12 08:56:56 -06:00
parent 4dd5e9c66e
commit 85dcfdd670
5 changed files with 38 additions and 37 deletions

View File

@ -85,8 +85,8 @@ class FrigateApp:
self.detectors: dict[str, ObjectDetectProcess] = {} self.detectors: dict[str, ObjectDetectProcess] = {}
self.detection_shms: list[mp.shared_memory.SharedMemory] = [] self.detection_shms: list[mp.shared_memory.SharedMemory] = []
self.log_queue: Queue = mp.Queue() self.log_queue: Queue = mp.Queue()
self.camera_metrics: dict[str, CameraMetrics] = {}
self.metrics_manager = mp.Manager() self.metrics_manager = mp.Manager()
self.camera_metrics: dict[str, CameraMetrics] = self.metrics_manager.dict()
self.embeddings_metrics: DataProcessorMetrics | None = ( self.embeddings_metrics: DataProcessorMetrics | None = (
DataProcessorMetrics( DataProcessorMetrics(
self.metrics_manager, list(config.classification.custom.keys()) self.metrics_manager, list(config.classification.custom.keys())
@ -128,7 +128,7 @@ class FrigateApp:
def init_camera_metrics(self) -> None: def init_camera_metrics(self) -> None:
# create camera_metrics # create camera_metrics
for camera_name in self.config.cameras.keys(): for camera_name in self.config.cameras.keys():
self.camera_metrics[camera_name] = CameraMetrics() self.camera_metrics[camera_name] = CameraMetrics(self.metrics_manager)
self.ptz_metrics[camera_name] = PTZMetrics( self.ptz_metrics[camera_name] = PTZMetrics(
autotracker_enabled=self.config.cameras[ autotracker_enabled=self.config.cameras[
camera_name camera_name

View File

@ -1,7 +1,7 @@
import multiprocessing as mp import multiprocessing as mp
from multiprocessing.managers import SyncManager
from multiprocessing.sharedctypes import Synchronized from multiprocessing.sharedctypes import Synchronized
from multiprocessing.synchronize import Event from multiprocessing.synchronize import Event
from typing import Optional
class CameraMetrics: class CameraMetrics:
@ -16,25 +16,25 @@ class CameraMetrics:
frame_queue: mp.Queue frame_queue: mp.Queue
process: Optional[mp.Process] process_pid: Synchronized
capture_process: Optional[mp.Process] capture_process_pid: Synchronized
ffmpeg_pid: Synchronized ffmpeg_pid: Synchronized
def __init__(self): def __init__(self, manager: SyncManager):
self.camera_fps = mp.Value("d", 0) self.camera_fps = manager.Value("d", 0)
self.detection_fps = mp.Value("d", 0) self.detection_fps = manager.Value("d", 0)
self.detection_frame = mp.Value("d", 0) self.detection_frame = manager.Value("d", 0)
self.process_fps = mp.Value("d", 0) self.process_fps = manager.Value("d", 0)
self.skipped_fps = mp.Value("d", 0) self.skipped_fps = manager.Value("d", 0)
self.read_start = mp.Value("d", 0) self.read_start = manager.Value("d", 0)
self.audio_rms = mp.Value("d", 0) self.audio_rms = manager.Value("d", 0)
self.audio_dBFS = mp.Value("d", 0) self.audio_dBFS = manager.Value("d", 0)
self.frame_queue = mp.Queue(maxsize=2) self.frame_queue = manager.Queue(maxsize=2)
self.process = None self.process_pid = manager.Value("i", 0)
self.capture_process = None self.capture_process_pid = manager.Value("i", 0)
self.ffmpeg_pid = mp.Value("i", 0) self.ffmpeg_pid = manager.Value("i", 0)
class PTZMetrics: class PTZMetrics:

View File

@ -54,6 +54,8 @@ class CameraMaintainer(threading.Thread):
], ],
) )
self.shm_count = self.__calculate_shm_frame_count() self.shm_count = self.__calculate_shm_frame_count()
self.camera_processes: dict[str, mp.Process] = {}
self.capture_processes: dict[str, mp.Process] = {}
def __init_historical_regions(self) -> None: def __init_historical_regions(self) -> None:
# delete region grids for removed or renamed cameras # delete region grids for removed or renamed cameras
@ -120,7 +122,7 @@ class CameraMaintainer(threading.Thread):
def __start_camera_processor( def __start_camera_processor(
self, name: str, config: CameraConfig, runtime: bool = False self, name: str, config: CameraConfig, runtime: bool = False
) -> mp.Process: ) -> None:
if not config.enabled_in_config: if not config.enabled_in_config:
logger.info(f"Camera processor not started for disabled camera {name}") logger.info(f"Camera processor not started for disabled camera {name}")
return return
@ -168,13 +170,14 @@ class CameraMaintainer(threading.Thread):
), ),
daemon=True, daemon=True,
) )
self.camera_processes[config.name] = camera_process
camera_process.start() camera_process.start()
self.camera_metrics[config.name].process_pid.value = camera_process.pid
logger.info(f"Camera processor started for {config.name}: {camera_process.pid}") logger.info(f"Camera processor started for {config.name}: {camera_process.pid}")
return camera_process
def __start_camera_capture( def __start_camera_capture(
self, name: str, config: CameraConfig, runtime: bool = False self, name: str, config: CameraConfig, runtime: bool = False
) -> mp.Process: ) -> None:
if not config.enabled_in_config: if not config.enabled_in_config:
logger.info(f"Capture process not started for disabled camera {name}") logger.info(f"Capture process not started for disabled camera {name}")
return return
@ -191,26 +194,26 @@ class CameraMaintainer(threading.Thread):
args=(config, count, self.camera_metrics[name]), args=(config, count, self.camera_metrics[name]),
) )
capture_process.daemon = True capture_process.daemon = True
self.capture_processes[name] = capture_process
capture_process.start() capture_process.start()
self.camera_metrics[name].capture_process_pid.value = capture_process.pid
logger.info(f"Capture process started for {name}: {capture_process.pid}") logger.info(f"Capture process started for {name}: {capture_process.pid}")
return capture_process
def __stop_camera_capture_process(self, camera: str) -> None: def __stop_camera_capture_process(self, camera: str) -> None:
capture_process = self.camera_metrics[camera].capture_process capture_process = self.capture_processes[camera]
if capture_process is not None: if capture_process is not None:
logger.info(f"Waiting for capture process for {camera} to stop") logger.info(f"Waiting for capture process for {camera} to stop")
capture_process.terminate() capture_process.terminate()
capture_process.join() capture_process.join()
def __stop_camera_process(self, camera: str) -> None: def __stop_camera_process(self, camera: str) -> None:
metrics = self.camera_metrics[camera] camera_process = self.camera_processes[camera]
camera_process = metrics.process
if camera_process is not None: if camera_process is not None:
logger.info(f"Waiting for process for {camera} to stop") logger.info(f"Waiting for process for {camera} to stop")
camera_process.terminate() camera_process.terminate()
camera_process.join() camera_process.join()
logger.info(f"Closing frame queue for {camera}") logger.info(f"Closing frame queue for {camera}")
empty_and_close_queue(metrics.frame_queue) empty_and_close_queue(self.camera_metrics[camera].frame_queue)
def run(self): def run(self):
self.__init_historical_regions() self.__init_historical_regions()
@ -226,30 +229,26 @@ class CameraMaintainer(threading.Thread):
for update_type, updated_cameras in updates.items(): for update_type, updated_cameras in updates.items():
if update_type == CameraConfigUpdateEnum.add.name: if update_type == CameraConfigUpdateEnum.add.name:
for camera in updated_cameras: for camera in updated_cameras:
camera_process = self.__start_camera_processor( self.__start_camera_processor(
camera, camera,
self.update_subscriber.camera_configs[camera], self.update_subscriber.camera_configs[camera],
runtime=True, runtime=True,
) )
capture_process = self.__start_camera_capture( self.__start_camera_capture(
camera, camera,
self.update_subscriber.camera_configs[camera], self.update_subscriber.camera_configs[camera],
runtime=True, runtime=True,
) )
self.camera_metrics[config.name].process = camera_process
self.camera_metrics[
config.name
].capture_process = capture_process
elif update_type == CameraConfigUpdateEnum.remove.name: elif update_type == CameraConfigUpdateEnum.remove.name:
self.__stop_camera_capture_process(camera) self.__stop_camera_capture_process(camera)
self.__stop_camera_process(camera) self.__stop_camera_process(camera)
# ensure the capture processes are done # ensure the capture processes are done
for camera in self.camera_metrics.keys(): for camera in self.camera_processes.keys():
self.__stop_camera_capture_process(camera) self.__stop_camera_capture_process(camera)
# ensure the camera processors are done # ensure the camera processors are done
for camera in self.camera_metrics.keys(): for camera in self.capture_processes.keys():
self.__stop_camera_process(camera) self.__stop_camera_process(camera)
self.update_subscriber.stop() self.update_subscriber.stop()

View File

@ -271,10 +271,12 @@ def stats_snapshot(
stats["cameras"] = {} stats["cameras"] = {}
for name, camera_stats in camera_metrics.items(): for name, camera_stats in camera_metrics.items():
total_detection_fps += camera_stats.detection_fps.value total_detection_fps += camera_stats.detection_fps.value
pid = camera_stats.process.pid if camera_stats.process else None pid = camera_stats.process_pid.value if camera_stats.process_pid.value else None
ffmpeg_pid = camera_stats.ffmpeg_pid.value if camera_stats.ffmpeg_pid else None ffmpeg_pid = camera_stats.ffmpeg_pid.value if camera_stats.ffmpeg_pid else None
capture_pid = ( capture_pid = (
camera_stats.capture_process.pid if camera_stats.capture_process else None camera_stats.capture_process_pid.value
if camera_stats.capture_process_pid.value
else None
) )
stats["cameras"][name] = { stats["cameras"][name] = {
"camera_fps": round(camera_stats.camera_fps.value, 2), "camera_fps": round(camera_stats.camera_fps.value, 2),

View File

@ -173,7 +173,7 @@ export default function CameraMetrics({
}); });
series[key]["detect"].data.push({ series[key]["detect"].data.push({
x: statsIdx, x: statsIdx,
y: stats.cpu_usages[camStats.pid.toString()].cpu, y: stats.cpu_usages[camStats.pid?.toString()]?.cpu,
}); });
}); });
}); });