diff --git a/frigate/test/test_gpu_stats.py b/frigate/test/test_gpu_stats.py index c99414a419..160756860e 100644 --- a/frigate/test/test_gpu_stats.py +++ b/frigate/test/test_gpu_stats.py @@ -1,7 +1,12 @@ import unittest +from io import StringIO from unittest.mock import MagicMock, patch -from frigate.util.services import get_amd_gpu_stats, get_intel_gpu_stats +from frigate.util.services import ( + get_amd_gpu_stats, + get_intel_gpu_stats, + get_openvino_npu_stats, +) class TestGpuStats(unittest.TestCase): @@ -17,6 +22,38 @@ class TestGpuStats(unittest.TestCase): amd_stats = get_amd_gpu_stats() assert amd_stats == {"gpu": "4.17%", "mem": "60.37%"} + @patch("frigate.util.services.time.sleep") + @patch("frigate.util.services.time.time", side_effect=[0.0, 1.0]) + @patch( + "builtins.open", + side_effect=[StringIO("1000"), StringIO("1250")], + ) + def test_openvino_npu_stats_runtime_active_time(self, open_file, time, sleep): + assert get_openvino_npu_stats() == {"npu": "25.0", "mem": "-%"} + + assert open_file.call_args_list[0].args[0].endswith("runtime_active_time") + + @patch("frigate.util.services.time.sleep") + @patch("frigate.util.services.time.time", side_effect=[0.0, 1.0]) + @patch( + "builtins.open", + side_effect=[ + FileNotFoundError, + StringIO("1000000"), + StringIO("1250000"), + ], + ) + def test_openvino_npu_stats_busy_time_fallback(self, open_file, time, sleep): + assert get_openvino_npu_stats() == {"npu": "25.0", "mem": "-%"} + + assert open_file.call_args_list[0].args[0].endswith("runtime_active_time") + assert open_file.call_args_list[1].args[0].endswith("npu_busy_time_us") + + @patch("builtins.open", side_effect=FileNotFoundError) + def test_openvino_npu_stats_unavailable(self, open_file): + assert get_openvino_npu_stats() is None + assert open_file.call_count == 2 + @patch("frigate.stats.intel_gpu_info.intel_gpu_name_resolver.get_names") @patch("frigate.util.services.time.sleep") @patch("frigate.util.services.time.monotonic") diff --git a/frigate/util/services.py b/frigate/util/services.py index 38737907ef..f367235b28 100644 --- a/frigate/util/services.py +++ b/frigate/util/services.py @@ -670,26 +670,42 @@ def get_intel_gpu_stats( def get_openvino_npu_stats() -> dict[str, str] | None: """Get NPU stats using openvino.""" - NPU_RUNTIME_PATH = "/sys/devices/pci0000:00/0000:00:0b.0/power/runtime_active_time" + NPU_RUNTIME_PATHS = ( + ( + "/sys/devices/pci0000:00/0000:00:0b.0/power/runtime_active_time", + 1_000, # Milliseconds per second + ), + ( + "/sys/class/accel/accel0/device/npu_busy_time_us", + 1_000_000, # Microseconds per second + ), + ) + + for runtime_path, units_per_second in NPU_RUNTIME_PATHS: + try: + with open(runtime_path) as f: + initial_runtime = float(f.read().strip()) + break + except (FileNotFoundError, PermissionError, ValueError): + continue + else: + return None try: - with open(NPU_RUNTIME_PATH) as f: - initial_runtime = float(f.read().strip()) - initial_time = time.time() # Sleep for 1 second to get an accurate reading time.sleep(1.0) # Read runtime value again - with open(NPU_RUNTIME_PATH) as f: + with open(runtime_path) as f: current_runtime = float(f.read().strip()) current_time = time.time() # Calculate usage percentage runtime_diff = current_runtime - initial_runtime - time_diff = (current_time - initial_time) * 1000.0 # Convert to milliseconds + time_diff = (current_time - initial_time) * units_per_second if time_diff > 0: usage = min(100.0, max(0.0, (runtime_diff / time_diff * 100.0)))