Files
ProxMenux/AppImage/scripts/hardware_monitor.py

90 lines
3.1 KiB
Python
Raw Normal View History

2025-10-06 17:25:08 +02:00
#!/usr/bin/env python3
2025-11-26 18:00:01 +01:00
"""
Hardware Monitor - RAPL Power Monitoring
2025-11-26 12:27:25 +01:00
2025-11-26 18:00:01 +01:00
This module provides CPU power consumption monitoring using Intel RAPL
(Running Average Power Limit) interface when IPMI is not available.
2025-10-06 17:25:08 +02:00
2025-11-26 18:00:01 +01:00
Only contains get_power_info() - all other hardware monitoring is handled
by flask_server.py to avoid code duplication.
"""
2025-10-06 17:25:08 +02:00
2025-11-26 18:00:01 +01:00
import os
import time
from typing import Dict, Any, Optional
2025-10-06 17:25:08 +02:00
2025-11-26 18:00:01 +01:00
# Global variable to store previous energy reading for power calculation
_last_energy_reading = {'energy_uj': None, 'timestamp': None}
2025-10-06 17:25:08 +02:00
def get_power_info() -> Optional[Dict[str, Any]]:
2025-11-26 18:00:01 +01:00
"""
Get CPU power consumption using Intel RAPL interface.
This function measures power consumption by reading energy counters
from /sys/class/powercap/intel-rapl interfaces and calculating
the power draw based on the change in energy over time.
Used as fallback when IPMI power monitoring is not available.
Returns:
dict: Power meter information with 'name', 'watts', and 'adapter' keys
or None if RAPL interface is unavailable
Example:
{
'name': 'CPU Power',
'watts': 45.32,
'adapter': 'Intel RAPL (CPU only)'
}
"""
2025-11-26 12:27:25 +01:00
global _last_energy_reading
2025-10-06 17:25:08 +02:00
rapl_path = '/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj'
if os.path.exists(rapl_path):
try:
2025-11-26 18:00:01 +01:00
# Read current energy value in microjoules
2025-10-06 17:25:08 +02:00
with open(rapl_path, 'r') as f:
2025-11-26 12:27:25 +01:00
current_energy_uj = int(f.read().strip())
current_time = time.time()
watts = 0.0
2025-11-26 18:00:01 +01:00
# Calculate power if we have a previous reading
2025-11-26 12:27:25 +01:00
if _last_energy_reading['energy_uj'] is not None and _last_energy_reading['timestamp'] is not None:
time_diff = current_time - _last_energy_reading['timestamp']
if time_diff > 0:
energy_diff = current_energy_uj - _last_energy_reading['energy_uj']
2025-11-26 18:00:01 +01:00
# Handle counter overflow (wraps around at max value)
2025-11-26 12:27:25 +01:00
if energy_diff < 0:
energy_diff = current_energy_uj
2025-11-26 18:00:01 +01:00
# Power (W) = Energy (µJ) / time (s) / 1,000,000
2025-11-26 12:27:25 +01:00
watts = round((energy_diff / time_diff) / 1000000, 2)
2025-11-26 18:00:01 +01:00
# Store current reading for next calculation
2025-11-26 12:27:25 +01:00
_last_energy_reading['energy_uj'] = current_energy_uj
_last_energy_reading['timestamp'] = current_time
2025-10-06 17:25:08 +02:00
2025-11-26 18:00:01 +01:00
# Detect CPU vendor for display purposes
2025-11-26 16:48:24 +01:00
cpu_vendor = 'CPU'
try:
with open('/proc/cpuinfo', 'r') as f:
cpuinfo = f.read()
if 'GenuineIntel' in cpuinfo:
cpu_vendor = 'Intel'
elif 'AuthenticAMD' in cpuinfo:
cpu_vendor = 'AMD'
except:
pass
2025-10-06 17:25:08 +02:00
return {
2025-11-26 16:48:24 +01:00
'name': 'CPU Power',
2025-11-26 12:27:25 +01:00
'watts': watts,
2025-11-26 16:48:24 +01:00
'adapter': f'{cpu_vendor} RAPL (CPU only)'
2025-10-06 17:25:08 +02:00
}
except Exception:
pass
return None