Coverage for orchestrant/pipeline/monitoring/system.py: 31%
42 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-19 11:22 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-19 11:22 +0000
1"""System resource monitoring helpers."""
3from __future__ import annotations
5import psutil
6from loguru import logger
8from orchestrant.monitoring.gpu import GPUProbe
9from orchestrant.monitoring.snapshot import read_raw_snapshot
10from orchestrant.pipeline.types import SystemStats
13class SystemMonitor:
14 """Live per-frame system snapshotter for pipeline UIs; keeps no history."""
16 def __init__(self, gpu_device_id: int = 0) -> None:
17 """Initialize system monitoring and optional GPU probing."""
18 self._gpu = GPUProbe(gpu_device_id)
20 # Expose convenience attributes for backward compatibility
21 self.gpu_available = self._gpu.available
22 self.gpu_name = self._gpu.gpu_name
24 psutil.cpu_percent(interval=None)
25 self.process = psutil.Process()
26 self.process.cpu_percent()
28 @property
29 def gpu_handle(self) -> object | None:
30 """Return the GPU handle if initialized."""
31 return self._gpu._handle # noqa: SLF001
33 def get_stats(self) -> SystemStats:
34 """Collect current system-wide CPU, RAM, and GPU statistics."""
35 stats = SystemStats()
37 raw = read_raw_snapshot(self._gpu)
38 stats.cpu_percent = raw.cpu_percent
39 stats.ram_percent = raw.ram.percent
40 stats.ram_used_gb = raw.ram.used / (1024**3)
41 stats.ram_total_gb = raw.ram.total / (1024**3)
43 snapshot = raw.gpu
44 if snapshot is not None:
45 stats.gpu_percent = snapshot.utilization
46 stats.gpu_memory_used_gb = snapshot.memory_used_bytes / (1024**3)
47 stats.gpu_memory_total_gb = snapshot.memory_total_bytes / (1024**3)
48 stats.gpu_memory_percent = (
49 (snapshot.memory_used_bytes / snapshot.memory_total_bytes) * 100
50 if snapshot.memory_total_bytes > 0
51 else 0
52 )
53 stats.gpu_temp_celsius = snapshot.temperature_celsius
54 stats.gpu_power_watts = snapshot.power_watts
55 stats.gpu_name = self._gpu.gpu_name
57 return stats
59 def get_process_stats(self) -> dict:
60 """Collect current process CPU/RAM/thread statistics."""
61 try:
62 return {
63 # Relative to one logical core, so it can exceed 100% on multi-core hosts.
64 "cpu_percent": self.process.cpu_percent(),
65 "memory_mb": self.process.memory_info().rss / (1024**2),
66 "threads": self.process.num_threads(),
67 }
68 except (psutil.NoSuchProcess, psutil.AccessDenied) as exc:
69 logger.debug("Failed to read process stats: {}", exc)
70 return {"cpu_percent": 0, "memory_mb": 0, "threads": 0}
72 def shutdown(self) -> None:
73 """Shutdown GPU monitoring resources, if available."""
74 self._gpu.shutdown()