2024-02-21 21:10:28 +01:00
|
|
|
"""Utilities for stats."""
|
|
|
|
|
2022-11-29 02:24:20 +01:00
|
|
|
import asyncio
|
2023-05-29 12:31:17 +02:00
|
|
|
import os
|
|
|
|
import shutil
|
2021-01-04 00:35:58 +01:00
|
|
|
import time
|
2024-08-15 03:41:41 +02:00
|
|
|
from json import JSONDecodeError
|
2023-05-29 12:31:17 +02:00
|
|
|
from typing import Any, Optional
|
|
|
|
|
2021-02-03 13:36:13 +01:00
|
|
|
import psutil
|
2022-04-11 14:10:19 +02:00
|
|
|
import requests
|
2023-05-29 12:31:17 +02:00
|
|
|
from requests.exceptions import RequestException
|
2021-01-04 00:35:58 +01:00
|
|
|
|
2024-09-27 14:53:23 +02:00
|
|
|
from frigate.camera import CameraMetrics
|
2021-01-04 00:35:58 +01:00
|
|
|
from frigate.config import FrigateConfig
|
2024-02-21 00:21:54 +01:00
|
|
|
from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR
|
2022-11-04 03:23:09 +01:00
|
|
|
from frigate.object_detection import ObjectDetectProcess
|
2024-09-27 14:53:23 +02:00
|
|
|
from frigate.types import StatsTrackingTypes
|
2023-07-06 16:28:50 +02:00
|
|
|
from frigate.util.services import (
|
2023-05-29 12:31:17 +02:00
|
|
|
get_amd_gpu_stats,
|
|
|
|
get_bandwidth_stats,
|
|
|
|
get_cpu_stats,
|
|
|
|
get_intel_gpu_stats,
|
2023-07-26 12:50:41 +02:00
|
|
|
get_jetson_stats,
|
2023-05-29 12:31:17 +02:00
|
|
|
get_nvidia_gpu_stats,
|
2024-02-21 00:21:54 +01:00
|
|
|
is_vaapi_amd_driver,
|
2023-05-29 12:31:17 +02:00
|
|
|
)
|
|
|
|
from frigate.version import VERSION
|
2021-01-04 00:35:58 +01:00
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
|
2023-01-26 01:36:26 +01:00
|
|
|
def get_latest_version(config: FrigateConfig) -> str:
|
|
|
|
if not config.telemetry.version_check:
|
|
|
|
return "disabled"
|
|
|
|
|
2022-05-26 17:04:33 +02:00
|
|
|
try:
|
|
|
|
request = requests.get(
|
2022-10-01 15:58:23 +02:00
|
|
|
"https://api.github.com/repos/blakeblackshear/frigate/releases/latest",
|
|
|
|
timeout=10,
|
2022-05-26 17:04:33 +02:00
|
|
|
)
|
2024-08-15 03:41:41 +02:00
|
|
|
except (RequestException, JSONDecodeError):
|
2022-05-26 17:04:33 +02:00
|
|
|
return "unknown"
|
|
|
|
|
2022-04-11 14:10:19 +02:00
|
|
|
response = request.json()
|
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
if request.ok and response and "tag_name" in response:
|
|
|
|
return str(response.get("tag_name").replace("v", ""))
|
2022-04-11 14:10:19 +02:00
|
|
|
else:
|
|
|
|
return "unknown"
|
|
|
|
|
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def stats_init(
|
2023-01-26 01:36:26 +01:00
|
|
|
config: FrigateConfig,
|
2024-09-27 14:53:23 +02:00
|
|
|
camera_metrics: dict[str, CameraMetrics],
|
2022-11-04 03:23:09 +01:00
|
|
|
detectors: dict[str, ObjectDetectProcess],
|
2023-05-05 00:58:59 +02:00
|
|
|
processes: dict[str, int],
|
2022-04-16 17:40:04 +02:00
|
|
|
) -> StatsTrackingTypes:
|
|
|
|
stats_tracking: StatsTrackingTypes = {
|
2021-02-17 14:23:32 +01:00
|
|
|
"camera_metrics": camera_metrics,
|
|
|
|
"detectors": detectors,
|
|
|
|
"started": int(time.time()),
|
2023-01-26 01:36:26 +01:00
|
|
|
"latest_frigate_version": get_latest_version(config),
|
2023-01-27 13:20:41 +01:00
|
|
|
"last_updated": int(time.time()),
|
2023-05-05 00:58:59 +02:00
|
|
|
"processes": processes,
|
2021-01-04 00:35:58 +01:00
|
|
|
}
|
|
|
|
return stats_tracking
|
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def get_fs_type(path: str) -> str:
|
2021-02-03 13:36:13 +01:00
|
|
|
bestMatch = ""
|
|
|
|
fsType = ""
|
|
|
|
for part in psutil.disk_partitions(all=True):
|
|
|
|
if path.startswith(part.mountpoint) and len(bestMatch) < len(part.mountpoint):
|
|
|
|
fsType = part.fstype
|
|
|
|
bestMatch = part.mountpoint
|
|
|
|
return fsType
|
|
|
|
|
2021-12-12 17:27:01 +01:00
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def read_temperature(path: str) -> Optional[float]:
|
2021-11-29 22:52:58 +01:00
|
|
|
if os.path.isfile(path):
|
|
|
|
with open(path) as f:
|
2021-12-12 17:27:01 +01:00
|
|
|
line = f.readline().strip()
|
|
|
|
return int(line) / 1000
|
2021-11-29 22:52:58 +01:00
|
|
|
return None
|
|
|
|
|
2021-12-12 17:27:01 +01:00
|
|
|
|
2022-04-16 17:40:04 +02:00
|
|
|
def get_temperatures() -> dict[str, float]:
|
2021-12-12 17:27:01 +01:00
|
|
|
temps = {}
|
2021-11-29 22:52:58 +01:00
|
|
|
|
|
|
|
# Get temperatures for all attached Corals
|
2021-12-12 17:27:01 +01:00
|
|
|
base = "/sys/class/apex/"
|
|
|
|
if os.path.isdir(base):
|
|
|
|
for apex in os.listdir(base):
|
|
|
|
temp = read_temperature(os.path.join(base, apex, "temp"))
|
|
|
|
if temp is not None:
|
|
|
|
temps[apex] = temp
|
2021-11-29 22:52:58 +01:00
|
|
|
|
|
|
|
return temps
|
2021-02-17 14:23:32 +01:00
|
|
|
|
2021-12-12 17:27:01 +01:00
|
|
|
|
2023-01-05 01:12:51 +01:00
|
|
|
def get_processing_stats(
|
|
|
|
config: FrigateConfig, stats: dict[str, str], hwaccel_errors: list[str]
|
|
|
|
) -> None:
|
2022-11-29 02:24:20 +01:00
|
|
|
"""Get stats for cpu / gpu."""
|
|
|
|
|
|
|
|
async def run_tasks() -> None:
|
2023-06-11 15:26:34 +02:00
|
|
|
stats_tasks = [
|
|
|
|
asyncio.create_task(set_gpu_stats(config, stats, hwaccel_errors)),
|
|
|
|
asyncio.create_task(set_cpu_stats(stats)),
|
|
|
|
]
|
|
|
|
|
|
|
|
if config.telemetry.stats.network_bandwidth:
|
|
|
|
stats_tasks.append(asyncio.create_task(set_bandwidth_stats(config, stats)))
|
|
|
|
|
|
|
|
await asyncio.wait(stats_tasks)
|
2022-11-29 02:24:20 +01:00
|
|
|
|
|
|
|
loop = asyncio.new_event_loop()
|
|
|
|
asyncio.set_event_loop(loop)
|
|
|
|
loop.run_until_complete(run_tasks())
|
|
|
|
loop.close()
|
|
|
|
|
|
|
|
|
|
|
|
async def set_cpu_stats(all_stats: dict[str, Any]) -> None:
|
|
|
|
"""Set cpu usage from top."""
|
|
|
|
cpu_stats = get_cpu_stats()
|
|
|
|
|
|
|
|
if cpu_stats:
|
|
|
|
all_stats["cpu_usages"] = cpu_stats
|
|
|
|
|
|
|
|
|
2023-06-11 14:34:03 +02:00
|
|
|
async def set_bandwidth_stats(config: FrigateConfig, all_stats: dict[str, Any]) -> None:
|
2023-05-18 03:01:56 +02:00
|
|
|
"""Set bandwidth from nethogs."""
|
2023-06-11 14:34:03 +02:00
|
|
|
bandwidth_stats = get_bandwidth_stats(config)
|
2023-05-18 03:01:56 +02:00
|
|
|
|
|
|
|
if bandwidth_stats:
|
|
|
|
all_stats["bandwidth_usages"] = bandwidth_stats
|
|
|
|
|
|
|
|
|
2023-01-05 01:12:51 +01:00
|
|
|
async def set_gpu_stats(
|
|
|
|
config: FrigateConfig, all_stats: dict[str, Any], hwaccel_errors: list[str]
|
|
|
|
) -> None:
|
2022-11-29 02:24:20 +01:00
|
|
|
"""Parse GPUs from hwaccel args and use for stats."""
|
|
|
|
hwaccel_args = []
|
|
|
|
|
|
|
|
for camera in config.cameras.values():
|
|
|
|
args = camera.ffmpeg.hwaccel_args
|
|
|
|
|
|
|
|
if isinstance(args, list):
|
|
|
|
args = " ".join(args)
|
|
|
|
|
2023-01-07 02:31:25 +01:00
|
|
|
if args and args not in hwaccel_args:
|
2022-11-29 02:24:20 +01:00
|
|
|
hwaccel_args.append(args)
|
|
|
|
|
2023-01-04 14:37:42 +01:00
|
|
|
for stream_input in camera.ffmpeg.inputs:
|
|
|
|
args = stream_input.hwaccel_args
|
|
|
|
|
|
|
|
if isinstance(args, list):
|
|
|
|
args = " ".join(args)
|
|
|
|
|
|
|
|
if args and args not in hwaccel_args:
|
|
|
|
hwaccel_args.append(args)
|
|
|
|
|
2022-11-29 02:24:20 +01:00
|
|
|
stats: dict[str, dict] = {}
|
|
|
|
|
|
|
|
for args in hwaccel_args:
|
2023-01-07 02:31:25 +01:00
|
|
|
if args in hwaccel_errors:
|
|
|
|
# known erroring args should automatically return as error
|
2024-04-27 18:26:51 +02:00
|
|
|
stats["error-gpu"] = {"gpu": "", "mem": ""}
|
2023-01-07 02:31:25 +01:00
|
|
|
elif "cuvid" in args or "nvidia" in args:
|
2022-11-29 02:24:20 +01:00
|
|
|
# nvidia GPU
|
|
|
|
nvidia_usage = get_nvidia_gpu_stats()
|
|
|
|
|
|
|
|
if nvidia_usage:
|
2023-05-05 01:02:01 +02:00
|
|
|
for i in range(len(nvidia_usage)):
|
|
|
|
stats[nvidia_usage[i]["name"]] = {
|
|
|
|
"gpu": str(round(float(nvidia_usage[i]["gpu"]), 2)) + "%",
|
|
|
|
"mem": str(round(float(nvidia_usage[i]["mem"]), 2)) + "%",
|
2023-10-13 16:44:18 +02:00
|
|
|
"enc": str(round(float(nvidia_usage[i]["enc"]), 2)) + "%",
|
|
|
|
"dec": str(round(float(nvidia_usage[i]["dec"]), 2)) + "%",
|
2023-05-05 01:02:01 +02:00
|
|
|
}
|
|
|
|
|
2022-11-29 02:24:20 +01:00
|
|
|
else:
|
2024-04-27 18:26:51 +02:00
|
|
|
stats["nvidia-gpu"] = {"gpu": "", "mem": ""}
|
2023-01-05 01:12:51 +01:00
|
|
|
hwaccel_errors.append(args)
|
2023-07-26 12:50:41 +02:00
|
|
|
elif "nvmpi" in args or "jetson" in args:
|
|
|
|
# nvidia Jetson
|
|
|
|
jetson_usage = get_jetson_stats()
|
|
|
|
|
|
|
|
if jetson_usage:
|
|
|
|
stats["jetson-gpu"] = jetson_usage
|
|
|
|
else:
|
2024-04-27 18:26:51 +02:00
|
|
|
stats["jetson-gpu"] = {"gpu": "", "mem": ""}
|
2023-07-26 12:50:41 +02:00
|
|
|
hwaccel_errors.append(args)
|
2022-11-29 02:24:20 +01:00
|
|
|
elif "qsv" in args:
|
2023-06-11 15:26:34 +02:00
|
|
|
if not config.telemetry.stats.intel_gpu_stats:
|
|
|
|
continue
|
|
|
|
|
2022-11-29 02:24:20 +01:00
|
|
|
# intel QSV GPU
|
|
|
|
intel_usage = get_intel_gpu_stats()
|
|
|
|
|
2024-10-25 16:45:11 +02:00
|
|
|
if intel_usage is not None:
|
|
|
|
stats["intel-qsv"] = intel_usage or {"gpu": "", "mem": ""}
|
2022-11-29 02:24:20 +01:00
|
|
|
else:
|
2024-04-27 18:26:51 +02:00
|
|
|
stats["intel-qsv"] = {"gpu": "", "mem": ""}
|
2023-01-05 01:12:51 +01:00
|
|
|
hwaccel_errors.append(args)
|
2022-11-29 02:24:20 +01:00
|
|
|
elif "vaapi" in args:
|
2024-02-21 00:21:54 +01:00
|
|
|
if is_vaapi_amd_driver():
|
2023-06-11 15:26:34 +02:00
|
|
|
if not config.telemetry.stats.amd_gpu_stats:
|
|
|
|
continue
|
|
|
|
|
2022-11-29 02:24:20 +01:00
|
|
|
# AMD VAAPI GPU
|
|
|
|
amd_usage = get_amd_gpu_stats()
|
|
|
|
|
|
|
|
if amd_usage:
|
|
|
|
stats["amd-vaapi"] = amd_usage
|
|
|
|
else:
|
2024-04-27 18:26:51 +02:00
|
|
|
stats["amd-vaapi"] = {"gpu": "", "mem": ""}
|
2023-01-05 01:12:51 +01:00
|
|
|
hwaccel_errors.append(args)
|
2022-11-29 02:24:20 +01:00
|
|
|
else:
|
2023-06-11 15:26:34 +02:00
|
|
|
if not config.telemetry.stats.intel_gpu_stats:
|
|
|
|
continue
|
|
|
|
|
2022-11-29 02:24:20 +01:00
|
|
|
# intel VAAPI GPU
|
|
|
|
intel_usage = get_intel_gpu_stats()
|
|
|
|
|
2024-10-25 16:45:11 +02:00
|
|
|
if intel_usage is not None:
|
|
|
|
stats["intel-vaapi"] = intel_usage or {"gpu": "", "mem": ""}
|
2022-11-29 02:24:20 +01:00
|
|
|
else:
|
2024-04-27 18:26:51 +02:00
|
|
|
stats["intel-vaapi"] = {"gpu": "", "mem": ""}
|
2023-01-05 01:12:51 +01:00
|
|
|
hwaccel_errors.append(args)
|
2022-11-30 02:56:01 +01:00
|
|
|
elif "v4l2m2m" in args or "rpi" in args:
|
2022-11-29 02:24:20 +01:00
|
|
|
# RPi v4l2m2m is currently not able to get usage stats
|
2024-04-27 18:26:51 +02:00
|
|
|
stats["rpi-v4l2m2m"] = {"gpu": "", "mem": ""}
|
2022-11-29 02:24:20 +01:00
|
|
|
|
|
|
|
if stats:
|
|
|
|
all_stats["gpu_usages"] = stats
|
|
|
|
|
|
|
|
|
|
|
|
def stats_snapshot(
|
2023-01-05 01:12:51 +01:00
|
|
|
config: FrigateConfig, stats_tracking: StatsTrackingTypes, hwaccel_errors: list[str]
|
2022-11-29 02:24:20 +01:00
|
|
|
) -> dict[str, Any]:
|
|
|
|
"""Get a snapshot of the current stats that are being tracked."""
|
2021-02-17 14:23:32 +01:00
|
|
|
camera_metrics = stats_tracking["camera_metrics"]
|
2022-04-16 17:40:04 +02:00
|
|
|
stats: dict[str, Any] = {}
|
2021-01-04 00:35:58 +01:00
|
|
|
|
|
|
|
total_detection_fps = 0
|
|
|
|
|
2023-10-20 00:15:47 +02:00
|
|
|
stats["cameras"] = {}
|
2021-01-04 00:35:58 +01:00
|
|
|
for name, camera_stats in camera_metrics.items():
|
2024-09-27 14:53:23 +02:00
|
|
|
total_detection_fps += camera_stats.detection_fps.value
|
|
|
|
pid = camera_stats.process.pid if camera_stats.process else None
|
|
|
|
ffmpeg_pid = camera_stats.ffmpeg_pid.value if camera_stats.ffmpeg_pid else None
|
2024-09-17 17:41:46 +02:00
|
|
|
capture_pid = (
|
2024-09-27 14:53:23 +02:00
|
|
|
camera_stats.capture_process.pid if camera_stats.capture_process else None
|
2022-04-16 17:40:04 +02:00
|
|
|
)
|
2023-10-20 00:15:47 +02:00
|
|
|
stats["cameras"][name] = {
|
2024-09-27 14:53:23 +02:00
|
|
|
"camera_fps": round(camera_stats.camera_fps.value, 2),
|
|
|
|
"process_fps": round(camera_stats.process_fps.value, 2),
|
|
|
|
"skipped_fps": round(camera_stats.skipped_fps.value, 2),
|
|
|
|
"detection_fps": round(camera_stats.detection_fps.value, 2),
|
2024-02-19 14:26:59 +01:00
|
|
|
"detection_enabled": config.cameras[name].detect.enabled,
|
2022-04-16 17:40:04 +02:00
|
|
|
"pid": pid,
|
2024-09-17 17:41:46 +02:00
|
|
|
"capture_pid": capture_pid,
|
2022-11-13 19:48:14 +01:00
|
|
|
"ffmpeg_pid": ffmpeg_pid,
|
2024-09-27 14:53:23 +02:00
|
|
|
"audio_rms": round(camera_stats.audio_rms.value, 4),
|
|
|
|
"audio_dBFS": round(camera_stats.audio_dBFS.value, 4),
|
2021-01-04 00:35:58 +01:00
|
|
|
}
|
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
stats["detectors"] = {}
|
2021-01-04 00:35:58 +01:00
|
|
|
for name, detector in stats_tracking["detectors"].items():
|
2022-04-16 17:40:04 +02:00
|
|
|
pid = detector.detect_process.pid if detector.detect_process else None
|
2021-02-17 14:23:32 +01:00
|
|
|
stats["detectors"][name] = {
|
2023-07-01 14:47:16 +02:00
|
|
|
"inference_speed": round(detector.avg_inference_speed.value * 1000, 2), # type: ignore[attr-defined]
|
|
|
|
# issue https://github.com/python/typeshed/issues/8799
|
|
|
|
# from mypy 0.981 onwards
|
|
|
|
"detection_start": detector.detection_start.value, # type: ignore[attr-defined]
|
|
|
|
# issue https://github.com/python/typeshed/issues/8799
|
|
|
|
# from mypy 0.981 onwards
|
2022-04-16 17:40:04 +02:00
|
|
|
"pid": pid,
|
2021-01-04 00:35:58 +01:00
|
|
|
}
|
2021-02-17 14:23:32 +01:00
|
|
|
stats["detection_fps"] = round(total_detection_fps, 2)
|
2021-01-04 00:35:58 +01:00
|
|
|
|
2023-01-05 01:12:51 +01:00
|
|
|
get_processing_stats(config, stats, hwaccel_errors)
|
2022-11-13 19:48:14 +01:00
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
stats["service"] = {
|
|
|
|
"uptime": (int(time.time()) - stats_tracking["started"]),
|
|
|
|
"version": VERSION,
|
2022-04-11 14:10:19 +02:00
|
|
|
"latest_version": stats_tracking["latest_frigate_version"],
|
2021-02-17 14:23:32 +01:00
|
|
|
"storage": {},
|
2021-12-12 17:27:01 +01:00
|
|
|
"temperatures": get_temperatures(),
|
2023-01-27 13:20:41 +01:00
|
|
|
"last_updated": int(time.time()),
|
2021-01-04 00:35:58 +01:00
|
|
|
}
|
|
|
|
|
2021-02-03 13:36:13 +01:00
|
|
|
for path in [RECORD_DIR, CLIPS_DIR, CACHE_DIR, "/dev/shm"]:
|
2023-01-13 14:22:47 +01:00
|
|
|
try:
|
|
|
|
storage_stats = shutil.disk_usage(path)
|
|
|
|
except FileNotFoundError:
|
|
|
|
stats["service"]["storage"][path] = {}
|
2023-10-22 20:35:19 +02:00
|
|
|
continue
|
2023-01-13 14:22:47 +01:00
|
|
|
|
2021-02-17 14:23:32 +01:00
|
|
|
stats["service"]["storage"][path] = {
|
2023-06-11 21:49:13 +02:00
|
|
|
"total": round(storage_stats.total / pow(2, 20), 1),
|
|
|
|
"used": round(storage_stats.used / pow(2, 20), 1),
|
|
|
|
"free": round(storage_stats.free / pow(2, 20), 1),
|
2021-02-17 14:23:32 +01:00
|
|
|
"mount_type": get_fs_type(path),
|
2021-02-03 13:36:13 +01:00
|
|
|
}
|
|
|
|
|
2023-05-05 00:58:59 +02:00
|
|
|
stats["processes"] = {}
|
|
|
|
for name, pid in stats_tracking["processes"].items():
|
|
|
|
stats["processes"][name] = {
|
|
|
|
"pid": pid,
|
|
|
|
}
|
|
|
|
|
2021-01-04 00:35:58 +01:00
|
|
|
return stats
|