Source code for pi_dashboard.system_metrics_handler

"""System metrics collection handler for Pi Dashboard.

This module provides functions to collect system metrics from the host system,
even when running inside a Docker container. It uses psutil and direct file
reading from mounted host directories.
"""

import logging
import os
import platform
from functools import cache, lru_cache
from pathlib import Path

from psutil import cpu_percent, disk_usage, virtual_memory

from pi_dashboard.models import SystemInfo, SystemMetrics, current_timestamp_int

logger = logging.getLogger(__name__)


[docs] @cache def get_host_root() -> str: """Get the host root path for metrics collection. :return str: The host root path """ return os.getenv("HOST_ROOT", "/")
[docs] @lru_cache(maxsize=128) def get_host_path(relative_path: str) -> Path: """Get the path to a host system file from within Docker container. If running in Docker with host mounts, accesses files from /host/* paths. Otherwise, accesses system files directly. :param str relative_path: The relative path (e.g., "proc/uptime") :return Path: The full path to the file """ return Path(get_host_root()) / relative_path
[docs] @cache def get_hostname() -> str: """Get the system hostname, attempting to read from host filesystem if in Docker. :return str: The system hostname """ hostname = platform.node() try: hostname_file = get_host_path("etc/hostname") if hostname_file.exists(): hostname = hostname_file.read_text().strip() except PermissionError: logger.warning("Could not read hostname from host filesystem", exc_info=True) except Exception: logger.exception("Unexpected error reading hostname from host filesystem") return hostname
[docs] def read_cpu_temperature() -> float: """Read CPU temperature from thermal zone. :return float: CPU temperature in Celsius, or 0.0 if unavailable """ try: thermal_file = get_host_path("sys/class/thermal/thermal_zone0/temp") if thermal_file.exists(): temp_millidegrees = int(thermal_file.read_text().strip()) return temp_millidegrees / 1000.0 except (FileNotFoundError, ValueError, PermissionError): logger.warning("Could not read CPU temperature from file", exc_info=True) except Exception: logger.exception("Unexpected error reading CPU temperature") return 0.0
[docs] def read_uptime() -> int: """Read system uptime from /proc/uptime. :return int: System uptime in seconds, or 0 if unavailable """ try: uptime_file = get_host_path("proc/uptime") if uptime_file.exists(): uptime_str = uptime_file.read_text().strip().split()[0] return int(float(uptime_str)) except (FileNotFoundError, ValueError, IndexError, PermissionError): logger.warning("Could not read system uptime from file", exc_info=True) except Exception: logger.exception("Unexpected error reading system uptime") return 0
[docs] @cache def get_system_info() -> SystemInfo: """Get system information using platform module. When running in Docker, reads hostname from host filesystem. :return SystemInfo: System information data """ uname = platform.uname() return SystemInfo( hostname=get_hostname(), system=uname.system, release=uname.release, version=uname.version, machine=uname.machine, memory_total=virtual_memory().total / (1024 * 1024 * 1024), # Convert to GB disk_total=disk_usage(get_host_root()).total / (1024 * 1024 * 1024), # Convert to GB )
[docs] def get_system_metrics() -> SystemMetrics: """Get current system metrics. Collects CPU, memory, disk usage, uptime, and temperature. When running in Docker with host mounts, reads host system metrics. :return SystemMetrics: System metrics data """ return SystemMetrics( cpu_usage=cpu_percent(interval=0.1), memory_usage=virtual_memory().percent, disk_usage=disk_usage(get_host_root()).percent, uptime=read_uptime(), temperature=read_cpu_temperature(), timestamp=current_timestamp_int(), )