mirror of
https://github.com/rightup/pyMC_Repeater.git
synced 2026-03-28 17:43:06 +01:00
hardware statistics integrate with storage collector
This commit is contained in:
@@ -3,7 +3,13 @@ Hardware statistics collection using psutil.
|
||||
KISS - Keep It Simple Stupid approach.
|
||||
"""
|
||||
|
||||
import psutil
|
||||
try:
|
||||
import psutil
|
||||
PSUTIL_AVAILABLE = True
|
||||
except ImportError:
|
||||
PSUTIL_AVAILABLE = False
|
||||
psutil = None
|
||||
|
||||
import time
|
||||
import logging
|
||||
|
||||
@@ -11,17 +17,19 @@ logger = logging.getLogger("HardwareStats")
|
||||
|
||||
|
||||
class HardwareStatsCollector:
|
||||
"""Simple hardware statistics collector using psutil."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the hardware stats collector."""
|
||||
|
||||
self.start_time = time.time()
|
||||
|
||||
def get_stats(self):
|
||||
"""
|
||||
Get current hardware statistics.
|
||||
Returns a dictionary with system stats.
|
||||
"""
|
||||
|
||||
if not PSUTIL_AVAILABLE:
|
||||
logger.error("psutil not available - cannot collect hardware stats")
|
||||
return {
|
||||
"error": "psutil library not available - cannot collect hardware statistics"
|
||||
}
|
||||
|
||||
try:
|
||||
# Get current timestamp
|
||||
now = time.time()
|
||||
@@ -34,7 +42,6 @@ class HardwareStatsCollector:
|
||||
|
||||
# Memory stats
|
||||
memory = psutil.virtual_memory()
|
||||
swap = psutil.swap_memory()
|
||||
|
||||
# Disk stats
|
||||
disk = psutil.disk_usage('/')
|
||||
@@ -42,123 +49,126 @@ class HardwareStatsCollector:
|
||||
# Network stats (total across all interfaces)
|
||||
net_io = psutil.net_io_counters()
|
||||
|
||||
# Temperature (if available)
|
||||
temperature = None
|
||||
try:
|
||||
temps = psutil.sensors_temperatures()
|
||||
if 'cpu_thermal' in temps and len(temps['cpu_thermal']) > 0:
|
||||
temperature = temps['cpu_thermal'][0].current
|
||||
elif temps:
|
||||
# Fallback to first available temperature sensor
|
||||
first_sensor = next(iter(temps.values()))
|
||||
if first_sensor:
|
||||
temperature = first_sensor[0].current
|
||||
except (AttributeError, OSError):
|
||||
# Temperature sensors not available
|
||||
pass
|
||||
|
||||
# Load average (Unix only)
|
||||
load_avg = None
|
||||
try:
|
||||
load_avg = psutil.getloadavg()
|
||||
except (AttributeError, OSError):
|
||||
# Not available on all systems
|
||||
pass
|
||||
# Not available on all systems - use zeros
|
||||
load_avg = (0.0, 0.0, 0.0)
|
||||
|
||||
# System boot time
|
||||
boot_time = psutil.boot_time()
|
||||
system_uptime = now - boot_time
|
||||
|
||||
# Temperature (if available)
|
||||
temperatures = {}
|
||||
try:
|
||||
temps = psutil.sensors_temperatures()
|
||||
for name, entries in temps.items():
|
||||
for i, entry in enumerate(entries):
|
||||
temp_name = f"{name}_{i}" if len(entries) > 1 else name
|
||||
temperatures[temp_name] = entry.current
|
||||
except (AttributeError, OSError):
|
||||
# Temperature sensors not available
|
||||
pass
|
||||
|
||||
# Format data structure to match Vue component expectations
|
||||
stats = {
|
||||
"timestamp": now,
|
||||
"uptime_seconds": uptime,
|
||||
"system_uptime_seconds": system_uptime,
|
||||
|
||||
# CPU
|
||||
"cpu": {
|
||||
"percent": cpu_percent,
|
||||
"usage_percent": cpu_percent,
|
||||
"count": cpu_count,
|
||||
"frequency_mhz": cpu_freq.current if cpu_freq else None,
|
||||
"load_average": list(load_avg) if load_avg else None
|
||||
"frequency": cpu_freq.current if cpu_freq else 0,
|
||||
"load_avg": {
|
||||
"1min": load_avg[0],
|
||||
"5min": load_avg[1],
|
||||
"15min": load_avg[2]
|
||||
}
|
||||
},
|
||||
|
||||
# Memory
|
||||
"memory": {
|
||||
"total_mb": round(memory.total / 1024 / 1024, 1),
|
||||
"available_mb": round(memory.available / 1024 / 1024, 1),
|
||||
"used_mb": round(memory.used / 1024 / 1024, 1),
|
||||
"percent": memory.percent
|
||||
"total": memory.total,
|
||||
"available": memory.available,
|
||||
"used": memory.used,
|
||||
"usage_percent": memory.percent
|
||||
},
|
||||
|
||||
# Swap
|
||||
"swap": {
|
||||
"total_mb": round(swap.total / 1024 / 1024, 1),
|
||||
"used_mb": round(swap.used / 1024 / 1024, 1),
|
||||
"percent": swap.percent
|
||||
},
|
||||
|
||||
# Disk
|
||||
"disk": {
|
||||
"total_gb": round(disk.total / 1024 / 1024 / 1024, 1),
|
||||
"used_gb": round(disk.used / 1024 / 1024 / 1024, 1),
|
||||
"free_gb": round(disk.free / 1024 / 1024 / 1024, 1),
|
||||
"percent": round((disk.used / disk.total) * 100, 1)
|
||||
"total": disk.total,
|
||||
"used": disk.used,
|
||||
"free": disk.free,
|
||||
"usage_percent": round((disk.used / disk.total) * 100, 1)
|
||||
},
|
||||
|
||||
# Network
|
||||
"network": {
|
||||
"bytes_sent": net_io.bytes_sent,
|
||||
"bytes_recv": net_io.bytes_recv,
|
||||
"packets_sent": net_io.packets_sent,
|
||||
"packets_recv": net_io.packets_recv,
|
||||
"errors_in": net_io.errin,
|
||||
"errors_out": net_io.errout,
|
||||
"drops_in": net_io.dropin,
|
||||
"drops_out": net_io.dropout
|
||||
"packets_recv": net_io.packets_recv
|
||||
},
|
||||
|
||||
# Temperature
|
||||
"temperature_celsius": temperature
|
||||
"system": {
|
||||
"uptime": system_uptime,
|
||||
"boot_time": boot_time
|
||||
}
|
||||
}
|
||||
|
||||
# Add temperatures if available
|
||||
if temperatures:
|
||||
stats["temperatures"] = temperatures
|
||||
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error collecting hardware stats: {e}")
|
||||
return {
|
||||
"timestamp": time.time(),
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def get_processes_summary(self, limit=10):
|
||||
"""
|
||||
Get top processes by CPU and memory usage.
|
||||
Returns a dictionary with process information.
|
||||
Returns a dictionary with process information in the format expected by the UI.
|
||||
"""
|
||||
if not PSUTIL_AVAILABLE:
|
||||
logger.error("psutil not available - cannot collect process stats")
|
||||
return {
|
||||
"processes": [],
|
||||
"total_processes": 0,
|
||||
"error": "psutil library not available - cannot collect process statistics"
|
||||
}
|
||||
|
||||
try:
|
||||
processes = []
|
||||
|
||||
# Get all processes
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent', 'memory_info']):
|
||||
try:
|
||||
processes.append(proc.info)
|
||||
pinfo = proc.info
|
||||
# Calculate memory in MB
|
||||
memory_mb = 0
|
||||
if pinfo['memory_info']:
|
||||
memory_mb = pinfo['memory_info'].rss / 1024 / 1024 # RSS in MB
|
||||
|
||||
process_data = {
|
||||
"pid": pinfo['pid'],
|
||||
"name": pinfo['name'] or 'Unknown',
|
||||
"cpu_percent": pinfo['cpu_percent'] or 0.0,
|
||||
"memory_percent": pinfo['memory_percent'] or 0.0,
|
||||
"memory_mb": round(memory_mb, 1)
|
||||
}
|
||||
processes.append(process_data)
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
pass
|
||||
|
||||
# Sort by CPU usage
|
||||
top_cpu = sorted(processes, key=lambda x: x['cpu_percent'] or 0, reverse=True)[:limit]
|
||||
|
||||
# Sort by memory usage
|
||||
top_memory = sorted(processes, key=lambda x: x['memory_percent'] or 0, reverse=True)[:limit]
|
||||
# Sort by CPU usage and get top processes
|
||||
top_processes = sorted(processes, key=lambda x: x['cpu_percent'], reverse=True)[:limit]
|
||||
|
||||
return {
|
||||
"top_cpu": top_cpu,
|
||||
"top_memory": top_memory,
|
||||
"processes": top_processes,
|
||||
"total_processes": len(processes)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error collecting process stats: {e}")
|
||||
return {
|
||||
"processes": [],
|
||||
"total_processes": 0,
|
||||
"error": str(e)
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import cherrypy
|
||||
from repeater import __version__
|
||||
from repeater.config import update_global_flood_policy
|
||||
from .cad_calibration_engine import CADCalibrationEngine
|
||||
from repeater.data_acquisition.hardware_stats import HardwareStatsCollector
|
||||
|
||||
logger = logging.getLogger("HTTPServer")
|
||||
|
||||
@@ -65,7 +64,6 @@ class APIEndpoints:
|
||||
self.daemon_instance = daemon_instance
|
||||
self._config_path = config_path or '/etc/pymc_repeater/config.yaml'
|
||||
self.cad_calibration = CADCalibrationEngine(daemon_instance, event_loop)
|
||||
self.hardware_stats = HardwareStatsCollector()
|
||||
|
||||
def _is_cors_enabled(self):
|
||||
return self.config.get("web", {}).get("cors_enabled", False)
|
||||
@@ -250,8 +248,15 @@ class APIEndpoints:
|
||||
def hardware_stats(self):
|
||||
"""Get comprehensive hardware statistics"""
|
||||
try:
|
||||
stats = self.hardware_stats.get_stats()
|
||||
return self._success(stats)
|
||||
# Get hardware stats from storage collector
|
||||
if hasattr(self.daemon_instance, 'storage_collector') and self.daemon_instance.storage_collector:
|
||||
stats = self.daemon_instance.storage_collector.get_hardware_stats()
|
||||
if stats:
|
||||
return self._success(stats)
|
||||
else:
|
||||
return self._error("Hardware stats not available (psutil may not be installed)")
|
||||
else:
|
||||
return self._error("Storage collector not available")
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting hardware stats: {e}")
|
||||
return self._error(e)
|
||||
@@ -261,8 +266,15 @@ class APIEndpoints:
|
||||
def hardware_processes(self):
|
||||
"""Get summary of top processes"""
|
||||
try:
|
||||
processes = self.hardware_stats.get_processes_summary()
|
||||
return self._success(processes)
|
||||
# Get process stats from storage collector
|
||||
if hasattr(self.daemon_instance, 'storage_collector') and self.daemon_instance.storage_collector:
|
||||
processes = self.daemon_instance.storage_collector.get_hardware_processes()
|
||||
if processes:
|
||||
return self._success(processes)
|
||||
else:
|
||||
return self._error("Process information not available (psutil may not be installed)")
|
||||
else:
|
||||
return self._error("Storage collector not available")
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting process stats: {e}")
|
||||
return self._error(e)
|
||||
|
||||
Reference in New Issue
Block a user