Coverage for openhcs/textual_tui/services/system_monitor.py: 0.0%
159 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
1"""
2System Monitor Core Module
3Real-time system monitoring with plotext visualization
4"""
6import plotext as plt
7import platform
8import psutil
9import subprocess
10import time
11from datetime import datetime
12from collections import deque
13import io
14import sys
16# Try to import GPU monitoring libraries
17try:
18 import GPUtil
19 GPU_AVAILABLE = True
20except ImportError:
21 GPU_AVAILABLE = False
24def is_wsl():
25 """Check if running in Windows Subsystem for Linux."""
26 return 'microsoft' in platform.uname().release.lower()
29def get_cpu_freq_mhz():
30 """Get CPU frequency in MHz, with WSL compatibility."""
31 if is_wsl():
32 try:
33 output = subprocess.check_output(
34 ['powershell.exe', '-Command',
35 'Get-CimInstance -ClassName Win32_Processor | Select-Object -ExpandProperty CurrentClockSpeed'],
36 stderr=subprocess.DEVNULL
37 )
38 return int(output.strip())
39 except Exception:
40 return 0
41 try:
42 freq = psutil.cpu_freq()
43 return int(freq.current) if freq else 0
44 except Exception:
45 return 0
48class SystemMonitor:
49 """Core system monitoring class with plotext visualization"""
51 def __init__(self, history_length=60):
52 self.history_length = history_length
54 # Initialize data storage
55 self.cpu_history = deque(maxlen=history_length)
56 self.ram_history = deque(maxlen=history_length)
57 self.gpu_history = deque(maxlen=history_length)
58 self.vram_history = deque(maxlen=history_length)
59 self.time_stamps = deque(maxlen=history_length)
61 # Cache current metrics to avoid duplicate system calls
62 self._current_metrics = {}
64 # Initialize with zeros
65 for _ in range(history_length):
66 self.cpu_history.append(0)
67 self.ram_history.append(0)
68 self.gpu_history.append(0)
69 self.vram_history.append(0)
70 self.time_stamps.append(0)
72 def update_metrics(self):
73 """Update system metrics and cache current values"""
74 # CPU usage
75 cpu_percent = psutil.cpu_percent(interval=None)
76 self.cpu_history.append(cpu_percent)
78 # RAM usage
79 ram = psutil.virtual_memory()
80 ram_percent = ram.percent
81 self.ram_history.append(ram_percent)
83 # Cache current metrics to avoid duplicate calls in get_metrics_dict()
84 self._current_metrics = {
85 'cpu_percent': cpu_percent,
86 'ram_percent': ram_percent,
87 'ram_used_gb': ram.used / (1024**3),
88 'ram_total_gb': ram.total / (1024**3),
89 'cpu_cores': psutil.cpu_count(),
90 'cpu_freq_mhz': get_cpu_freq_mhz(),
91 }
93 # GPU usage (if available)
94 if GPU_AVAILABLE:
95 try:
96 gpus = GPUtil.getGPUs()
97 if gpus:
98 gpu = gpus[0] # Use first GPU
99 gpu_load = gpu.load * 100
100 vram_util = gpu.memoryUtil * 100
101 self.gpu_history.append(gpu_load)
102 self.vram_history.append(vram_util)
104 # Cache GPU metrics
105 self._current_metrics.update({
106 'gpu_percent': gpu_load,
107 'vram_percent': vram_util,
108 'gpu_name': gpu.name,
109 'gpu_temp': gpu.temperature,
110 'vram_used_mb': gpu.memoryUsed,
111 'vram_total_mb': gpu.memoryTotal,
112 })
113 else:
114 self.gpu_history.append(0)
115 self.vram_history.append(0)
116 except:
117 self.gpu_history.append(0)
118 self.vram_history.append(0)
119 else:
120 self.gpu_history.append(0)
121 self.vram_history.append(0)
123 # Update timestamps
124 self.time_stamps.append(time.time())
126 def get_ascii_title(self):
127 """Return the OpenHCS ASCII art title"""
128 return """
129╔═══════════════════════════════════════════════════════════════════════╗
130║ ██████╗ ██████╗ ███████╗███╗ ██╗██╗ ██╗ ██████╗███████╗ ║
131║ ██╔═══██╗██╔══██╗██╔════╝████╗ ██║██║ ██║██╔════╝██╔════╝ ║
132║ ██║ ██║██████╔╝█████╗ ██╔██╗ ██║███████║██║ ███████╗ ║
133║ ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██╔══██║██║ ╚════██║ ║
134║ ╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║╚██████╗███████║ ║
135║ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝╚══════╝ ║
136╚═══════════════════════════════════════════════════════════════════════╝"""
138 def create_monitor_view(self, width=80, height=30):
139 """Create the system monitor visualization"""
140 plt.clear_figure()
142 # Create subplots
143 plt.subplots(2, 2)
144 plt.plot_size(width=width, height=height)
146 # Get current metrics for display
147 current_cpu = self.cpu_history[-1] if self.cpu_history else 0
148 current_ram = self.ram_history[-1] if self.ram_history else 0
149 current_gpu = self.gpu_history[-1] if self.gpu_history else 0
150 current_vram = self.vram_history[-1] if self.vram_history else 0
152 # Get system info
153 ram_info = psutil.virtual_memory()
154 ram_used_gb = ram_info.used / (1024**3)
155 ram_total_gb = ram_info.total / (1024**3)
157 x_range = list(range(len(self.cpu_history)))
159 # CPU Usage Plot (Top Left)
160 plt.subplot(1, 1)
161 plt.theme('dark')
162 plt.plot(x_range, list(self.cpu_history), color='cyan')
163 plt.ylim(0, 100)
164 plt.title(f"CPU Usage: {current_cpu:.1f}%")
165 plt.xlabel("Time (seconds)")
166 plt.ylabel("Usage %")
168 # RAM Usage Plot (Top Right)
169 plt.subplot(1, 2)
170 plt.theme('dark')
171 plt.plot(x_range, list(self.ram_history), color='green')
172 plt.ylim(0, 100)
173 plt.title(f"RAM: {ram_used_gb:.1f}/{ram_total_gb:.1f} GB ({current_ram:.1f}%)")
174 plt.xlabel("Time (seconds)")
175 plt.ylabel("Usage %")
177 # GPU Usage Plot (Bottom Left)
178 plt.subplot(2, 1)
179 plt.theme('dark')
180 if GPU_AVAILABLE and any(self.gpu_history):
181 plt.plot(x_range, list(self.gpu_history), color='yellow')
182 plt.ylim(0, 100)
183 plt.title(f"GPU Usage: {current_gpu:.1f}%")
184 else:
185 plt.plot([0, 1], [0, 0], color='red')
186 plt.title("GPU: Not Available")
187 plt.xlabel("Time (seconds)")
188 plt.ylabel("Usage %")
190 # VRAM Usage Plot (Bottom Right)
191 plt.subplot(2, 2)
192 plt.theme('dark')
193 if GPU_AVAILABLE and any(self.vram_history):
194 plt.plot(x_range, list(self.vram_history), color='magenta')
195 plt.ylim(0, 100)
196 plt.title(f"VRAM Usage: {current_vram:.1f}%")
197 else:
198 plt.plot([0, 1], [0, 0], color='red')
199 plt.title("VRAM: Not Available")
200 plt.xlabel("Time (seconds)")
201 plt.ylabel("Usage %")
203 # Capture the plot output
204 old_stdout = sys.stdout
205 sys.stdout = buffer = io.StringIO()
207 try:
208 # Print title
209 print(self.get_ascii_title())
211 # Show plots
212 plt.show()
214 # Additional system info
215 print("\n" + "═" * 75)
216 print(f"System Information | Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
217 print("═" * 75)
218 print(f"CPU Cores: {psutil.cpu_count()} | CPU Frequency: {get_cpu_freq_mhz()} MHz")
219 print(f"Total RAM: {ram_total_gb:.1f} GB | Available RAM: {ram_info.available/(1024**3):.1f} GB")
221 if GPU_AVAILABLE:
222 try:
223 gpus = GPUtil.getGPUs()
224 if gpus:
225 gpu = gpus[0]
226 print(f"GPU: {gpu.name} | VRAM: {gpu.memoryUsed:.0f}/{gpu.memoryTotal:.0f} MB")
227 print(f"GPU Temperature: {gpu.temperature}°C")
228 except:
229 pass
231 # Disk usage
232 disk = psutil.disk_usage('/')
233 print(f"Disk Usage: {disk.used/(1024**3):.1f}/{disk.total/(1024**3):.1f} GB ({disk.percent}%)")
235 # Network info
236 net = psutil.net_io_counters()
237 print(f"Network - Sent: {net.bytes_sent/(1024**2):.1f} MB | Recv: {net.bytes_recv/(1024**2):.1f} MB")
238 print("═" * 75)
240 output = buffer.getvalue()
241 finally:
242 sys.stdout = old_stdout
244 return output
246 def get_metrics_dict(self):
247 """Get current metrics as a dictionary - uses cached data from update_metrics()"""
248 # Return cached metrics to avoid duplicate system calls
249 # If no cached data exists (first call), return defaults
250 if not self._current_metrics:
251 return {
252 'cpu_percent': 0,
253 'ram_percent': 0,
254 'ram_used_gb': 0,
255 'ram_total_gb': 0,
256 'cpu_cores': 0,
257 'cpu_freq_mhz': 0,
258 }
260 return self._current_metrics.copy()
263# Standalone CLI usage
264def main():
265 """Run system monitor in standalone mode"""
266 monitor = SystemMonitor()
268 print("Starting System Monitor...")
269 print("Press Ctrl+C to exit")
271 try:
272 while True:
273 monitor.update_metrics()
274 print("\033[2J\033[H") # Clear screen
275 print(monitor.create_monitor_view())
276 time.sleep(1)
277 except KeyboardInterrupt:
278 print("\nExiting System Monitor...")
281if __name__ == "__main__":
282 main()