feat: add drive temperature monitoring with async updates and multi-pattern SMART parsing
Add smart_temperature() function with smartctl -A command execution, fallback to -d sat mode, and regex patterns for Temperature_Celsius/Airflow_Temperature_Cel/Drive_Temperature/Current Drive Temperature attributes plus Temperature: field parsing with 1-3 digit capture and °C formatting. Add temperature_futures dict with service_check_executor.submit() calls for parallel temperature reads alongside smart_futures. Add disk["
This commit is contained in:
@@ -352,6 +352,24 @@ def smart_status(device):
|
|||||||
health = re.search(r"(?:SMART overall-health self-assessment test result|SMART Health Status):\s*(.+)", smart_output, re.IGNORECASE)
|
health = re.search(r"(?:SMART overall-health self-assessment test result|SMART Health Status):\s*(.+)", smart_output, re.IGNORECASE)
|
||||||
return health.group(1).strip() if health else ("Fehler: " + smart_error.strip()[:80] if smart_error.strip() else "Nicht unterstützt")
|
return health.group(1).strip() if health else ("Fehler: " + smart_error.strip()[:80] if smart_error.strip() else "Nicht unterstützt")
|
||||||
|
|
||||||
|
def smart_temperature(device):
|
||||||
|
"""Read the drive temperature in a controller-friendly way."""
|
||||||
|
if not SMART_ENABLED or not shutil.which("smartctl"):
|
||||||
|
return "Nicht verfügbar"
|
||||||
|
smart_device = device.replace("/dev/", f"{HOST_DEVICE_PATH.rstrip('/')}/", 1)
|
||||||
|
output, error = _read_command(["smartctl", "-A", smart_device], timeout=6, warn_on_nonzero=False)
|
||||||
|
if not output:
|
||||||
|
output, error = _read_command(["smartctl", "-A", "-d", "sat", smart_device], timeout=6, warn_on_nonzero=False)
|
||||||
|
patterns = (
|
||||||
|
r"(?:Temperature_Celsius|Airflow_Temperature_Cel|Drive_Temperature|Current Drive Temperature)\s+.*?\s(\d{1,3})(?:\s*°?C)?\s*$",
|
||||||
|
r"^\s*Temperature:\s*(\d{1,3})\s*(?:C|°C|Celsius)?\s*$",
|
||||||
|
)
|
||||||
|
for pattern in patterns:
|
||||||
|
match = re.search(pattern, output, re.IGNORECASE | re.MULTILINE)
|
||||||
|
if match:
|
||||||
|
return f"{int(match.group(1))} °C"
|
||||||
|
return "Nicht verfügbar"
|
||||||
|
|
||||||
def raid_status():
|
def raid_status():
|
||||||
global raid_log_state
|
global raid_log_state
|
||||||
mdstat, mdstat_error = "", ""
|
mdstat, mdstat_error = "", ""
|
||||||
@@ -406,8 +424,10 @@ def raid_status():
|
|||||||
except (ValueError, AttributeError):
|
except (ValueError, AttributeError):
|
||||||
block_devices = {}
|
block_devices = {}
|
||||||
smart_futures = {}
|
smart_futures = {}
|
||||||
if SMART_ENABLED and shutil.which("smartctl"):
|
temperature_futures = {}
|
||||||
|
if shutil.which("smartctl"):
|
||||||
smart_futures = {disk["device"]: service_check_executor.submit(smart_status, disk["device"]) for disk in devices}
|
smart_futures = {disk["device"]: service_check_executor.submit(smart_status, disk["device"]) for disk in devices}
|
||||||
|
temperature_futures = {disk["device"]: service_check_executor.submit(smart_temperature, disk["device"]) for disk in devices}
|
||||||
for disk in devices:
|
for disk in devices:
|
||||||
info = block_devices.get(disk["device"], {})
|
info = block_devices.get(disk["device"], {})
|
||||||
parent = re.sub(r"(p)?\d+$", "", disk["device"])
|
parent = re.sub(r"(p)?\d+$", "", disk["device"])
|
||||||
@@ -435,6 +455,7 @@ def raid_status():
|
|||||||
elif disk["smart"] != "PASSED" and smart_log_key not in hardware_log_once:
|
elif disk["smart"] != "PASSED" and smart_log_key not in hardware_log_once:
|
||||||
log.warning("SMART-Status für %s nicht verfügbar: %s", disk["device"], disk["smart"])
|
log.warning("SMART-Status für %s nicht verfügbar: %s", disk["device"], disk["smart"])
|
||||||
hardware_log_once.add(smart_log_key)
|
hardware_log_once.add(smart_log_key)
|
||||||
|
disk["temperature"] = temperature_futures[disk["device"]].result() if disk["device"] in temperature_futures else smart_temperature(disk["device"])
|
||||||
missing = members_match and members_match.group(1) != members_match.group(2)
|
missing = members_match and members_match.group(1) != members_match.group(2)
|
||||||
failed = detail.get("failed_devices", 0) > 0 or any(disk["status"] == "failed" for disk in devices)
|
failed = detail.get("failed_devices", 0) > 0 or any(disk["status"] == "failed" for disk in devices)
|
||||||
rebuilding = bool(progress_match)
|
rebuilding = bool(progress_match)
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<script>
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded',()=>{const updateTemperatures=async()=>{const container=document.querySelector('#raidDisks');if(!container)return;try{const data=await (await fetch('/api/datastore')).json();const disks=data.raid?.devices||[];container.querySelectorAll('.raid-disk').forEach(row=>{const device=row.querySelector('strong')?.textContent.trim();const disk=disks.find(item=>item.device===device);if(!disk)return;let node=row.querySelector('.raid-temperature');if(!node){node=document.createElement('small');node.className='raid-temperature';const smart=row.querySelector('.raid-smart');if(smart)smart.after(node);else row.querySelector('div')?.append(node)}node.textContent=`Temperatur: ${disk.temperature||'Nicht verfügbar'}`})}catch(error){}};updateTemperatures();setInterval(updateTemperatures,5000)});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
window.addEventListener('load', function () {
|
window.addEventListener('load', function () {
|
||||||
navigator.serviceWorker.register('/sw.js?v=4', {scope: '/'}).catch(function () {});
|
navigator.serviceWorker.register('/sw.js?v=4', {scope: '/'}).catch(function () {});
|
||||||
|
|||||||
Reference in New Issue
Block a user