feat: add Linux PSI I/O pressure monitoring with real-time dashboard panel and auto-refresh on DOM mutations

Add io_pressure() function to parse /proc/pressure/io with line splitting, key-value extraction from some/full metrics, and OSError/ValueError exception handling returning available:False on failure. Add io_pressure() call to datastore_snapshot() return dict. Add DOMContentLoaded script to create #ioPressurePanel section with datastore-panel/io-pressure-panel classes, innerHTML with I/O-
This commit is contained in:
2026-08-17 19:21:49 +02:00
parent c6c2437732
commit b275164be9
2 changed files with 18 additions and 2 deletions
+14 -1
View File
@@ -338,6 +338,19 @@ def format_remaining_time(value):
parts.append(f"{minutes} Min.") parts.append(f"{minutes} Min.")
return " ".join(parts) or "< 1 Min." return " ".join(parts) or "< 1 Min."
def io_pressure():
"""Read Linux PSI I/O pressure, if the kernel exposes it."""
try:
values = {}
for line in Path("/proc/pressure/io").read_text().splitlines():
parts = line.split()
if not parts:
continue
values[parts[0]] = {key: float(value) for key, value in (item.split("=", 1) for item in parts[1:] if "=" in item)}
return {"some": values.get("some", {}), "full": values.get("full", {}), "available": bool(values)}
except (OSError, ValueError):
return {"some": {}, "full": {}, "available": False}
def smart_status(device): def smart_status(device):
if not SMART_ENABLED: if not SMART_ENABLED:
return "Nicht aktiviert" return "Nicht aktiviert"
@@ -527,7 +540,7 @@ def datastore_snapshot():
elif current_raid.get("status") != "failed": elif current_raid.get("status") != "failed":
datastore_cache["raid"] = dict(current_raid) datastore_cache["raid"] = dict(current_raid)
datastore_cache["raid_at"] = now datastore_cache["raid_at"] = now
return {"storage": storage, "raid": current_raid, "cache_seconds": STORAGE_CACHE_SECONDS} return {"storage": storage, "raid": current_raid, "io_pressure": io_pressure(), "cache_seconds": STORAGE_CACHE_SECONDS}
def cpu_usage_percent(): def cpu_usage_percent():
global previous_cpu global previous_cpu
+4 -1
View File
@@ -1,6 +1,9 @@
<!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)}); document.addEventListener('DOMContentLoaded',()=>{const container=document.querySelector('#raidDisks');if(!container)return;const refresh=async()=>{try{const devices=(await (await fetch('/api/datastore')).json()).raid?.devices||[];container.querySelectorAll('.raid-disk').forEach(row=>{const disk=devices.find(item=>item.device===row.querySelector('strong')?.textContent.trim());if(!disk)return;let node=row.querySelector('.raid-temperature');if(!node){node=document.createElement('small');node.className='raid-temperature';row.querySelector('.raid-smart')?.after(node)}node.textContent=`Temperatur: ${disk.temperature||'Nicht verfügbar'}`})}catch(error){}};const observer=new MutationObserver(records=>{if(records.some(record=>record.target===container))setTimeout(refresh,0)});observer.observe(container,{childList:true,subtree:true});refresh()});
</script>
<script>
document.addEventListener('DOMContentLoaded',()=>{const root=document.querySelector('#datastore');if(!root)return;const grid=root.querySelector('.datastore-grid');if(grid&&!document.querySelector('#ioPressurePanel')){const panel=document.createElement('section');panel.id='ioPressurePanel';panel.className='datastore-panel io-pressure-panel';panel.innerHTML='<div class="datastore-panel-head"><div><h3>I/O-Auslastung</h3><p>Festplatten-Druck aus Linux PSI</p></div><strong id="ioPressureLabel">Wird geladen …</strong></div><div class="io-pressure-bars"><div><span>Some</span><b id="ioPressureSome">—</b><div class="datastore-progress"><span id="ioPressureSomeBar"></span></div></div><div><span>Full</span><b id="ioPressureFull">—</b><div class="datastore-progress"><span id="ioPressureFullBar"></span></div></div></div>';grid.before(panel)}const pressure=async()=>{try{const data=await (await fetch('/api/datastore')).json();const io=data.io_pressure||{};const some=Number(io.some?.avg10)||0,full=Number(io.full?.avg10)||0;const level=Math.max(some,full),label=level>=20?'Hoch':level>=5?'Beobachten':'Normal';const set=(id,value)=>{const node=document.querySelector(id);if(node)node.textContent=value};set('#ioPressureLabel',io.available?`${label} · ${level.toFixed(1)}%`:'Nicht verfügbar');set('#ioPressureSome',`${some.toFixed(1)}%`);set('#ioPressureFull',`${full.toFixed(1)}%`);const a=document.querySelector('#ioPressureSomeBar'),b=document.querySelector('#ioPressureFullBar');if(a)a.style.width=`${Math.min(100,some)}%`;if(b)b.style.width=`${Math.min(100,full)}%`}catch(error){}};pressure();setInterval(pressure,5000);const disks=document.querySelector('#raidDisks');if(disks){const observer=new MutationObserver(()=>{if([...disks.querySelectorAll('.raid-disk')].some(row=>!row.querySelector('.raid-temperature')))setTimeout(pressure,0)});observer.observe(disks,{childList:true,subtree:true})}});
</script> </script>
<script> <script>
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {