From 1ae0843c00a699a510d7dcffd5ecb0c50764b564 Mon Sep 17 00:00:00 2001 From: patrykorwat Date: Wed, 19 Aug 2026 20:05:58 +0000 Subject: [PATCH] feat(axelera): busy time as npu usage in npu_usages The Metis has no utilization readout without the axsystemserver broker, which cannot run alongside a live detector. Report the worker busy time (fps * inference time) as a real load indicator instead, plus the board temperature, under npu_usages[axelera]. --- frigate/stats/util.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/frigate/stats/util.py b/frigate/stats/util.py index af3a0b7892..cd9d95d429 100644 --- a/frigate/stats/util.py +++ b/frigate/stats/util.py @@ -180,6 +180,7 @@ def get_processing_stats( config: FrigateConfig, stats: dict[str, str], hwaccel_errors: dict[str, float], + stats_tracking: StatsTrackingTypes, ) -> None: """Get stats for cpu / gpu.""" @@ -187,7 +188,7 @@ def get_processing_stats( stats_tasks = [ asyncio.create_task(set_gpu_stats(config, stats, hwaccel_errors)), asyncio.create_task(set_cpu_stats(stats)), - asyncio.create_task(set_npu_usages(config, stats)), + asyncio.create_task(set_npu_usages(config, stats, stats_tracking)), ] if config.telemetry.stats.network_bandwidth: @@ -324,7 +325,11 @@ async def set_gpu_stats( all_stats["gpu_usages"] = stats -async def set_npu_usages(config: FrigateConfig, all_stats: dict[str, Any]) -> None: +async def set_npu_usages( + config: FrigateConfig, + all_stats: dict[str, Any], + stats_tracking: StatsTrackingTypes, +) -> None: stats: dict[str, dict] = {} for detector in config.detectors.values(): @@ -341,13 +346,31 @@ async def set_npu_usages(config: FrigateConfig, all_stats: dict[str, Any]) -> No axcl_usage = get_axcl_npu_stats() stats["axengine"] = axcl_usage elif detector.type == "axelera": - # Axelera Metis: temperature from the board controller. - # There is no utilization readout without the axsystemserver - # broker, which cannot run alongside a live detector. + # Axelera Metis: board temperature + busy time from the worker. + # MVM utilization needs the axsystemserver broker, which cannot + # run alongside a live detector; busy time (fps * s) is computed + # from Frigate's own measurements as a real load indicator. + axelera_entry = None metis_temp = get_axelera_board_temp() if metis_temp is not None: - stats["axelera"] = {"mem": "-%", "temp": metis_temp} + axelera_entry = {"mem": "-%", "temp": metis_temp} + npu_det = stats_tracking.get("detectors", {}).get("axelera") + + if npu_det is not None: + total_fps = sum( + cam.detection_fps.value + for cam in stats_tracking.get("camera_metrics", {}).values() + ) + avg_sec = npu_det.avg_inference_speed.value + + if total_fps > 0 and avg_sec > 0: + axelera_entry["npu"] = round( + min(100.0, total_fps * avg_sec * 100.0), 1 + ) + + if axelera_entry is not None: + stats["axelera"] = axelera_entry if stats: all_stats["npu_usages"] = stats @@ -499,7 +522,7 @@ def stats_snapshot( embeddings_metrics.classification_cps[key].value, 2 ) - get_processing_stats(config, stats, hwaccel_errors) + get_processing_stats(config, stats, hwaccel_errors, stats_tracking) stats["service"] = { "uptime": (int(time.time()) - stats_tracking["started"]),