nova-shell/modules/SystemStats.qml

158 lines
6.4 KiB
QML

pragma Singleton
import QtQuick
import Quickshell.Io
import "." as M
QtObject {
id: root
// ── CPU ──────────────────────────────────────────────────────────────
property int cpuUsage: 0
property real cpuFreqGhz: 0
property var cpuCores: [] // [{usage, freq_ghz, history:[]}] — only rebuilt while coreConsumers > 0
property int coreConsumers: 0
onCoreConsumersChanged: {
if (coreConsumers > 0)
_coreGraceTimer.stop();
else
_coreGraceTimer.start();
}
property var _coreGraceTimer: Timer {
interval: 30000
onTriggered: root.cpuCores = root.cpuCores.map(() => ({
"usage": 0,
"freq_ghz": 0,
"history": []
}))
}
property var cpuCoreMaxFreq: []
property var cpuCoreTypes: []
// ── Temperature ──────────────────────────────────────────────────────
property int tempCelsius: 0
// ── Memory ───────────────────────────────────────────────────────────
property int memPercent: 0
property real memUsedGb: 0
property real memTotalGb: 0
property real memAvailGb: 0
property real memCachedGb: 0
property real memBuffersGb: 0
// ── Disk ─────────────────────────────────────────────────────────────
property var diskMounts: []
property int diskRootPct: 0
// nova-stats stream (cpu + mem)
property var _statsProc: Process {
running: true
command: {
const ms = M.Modules.statsDaemon.interval;
return ms > 0 ? ["nova-stats", "--interval", ms.toString()] : ["nova-stats"];
}
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
try {
const ev = JSON.parse(line);
if (ev.type === "cpu") {
root.cpuUsage = ev.usage;
root.cpuFreqGhz = ev.freq_ghz;
if (root.coreConsumers > 0) {
const histLen = 16;
const prev = root.cpuCores;
root.cpuCores = ev.cores.map((c, i) => {
const oldHist = prev[i]?.history ?? [];
const hist = oldHist.concat([c.usage]);
return {
usage: c.usage,
freq_ghz: c.freq_ghz,
history: hist.length > histLen ? hist.slice(hist.length - histLen) : hist
};
});
} else if (root.cpuCores.length !== ev.cores.length) {
// Keep count in sync so panel can size correctly before consumers activate
root.cpuCores = ev.cores.map(c => ({
"usage": c.usage,
"freq_ghz": c.freq_ghz,
"history": []
}));
}
} else if (ev.type === "temp") {
root.tempCelsius = ev.celsius;
} else if (ev.type === "mem") {
root.memPercent = ev.percent;
root.memUsedGb = ev.used_gb;
root.memTotalGb = ev.total_gb;
root.memAvailGb = ev.avail_gb;
root.memCachedGb = ev.cached_gb;
root.memBuffersGb = ev.buffers_gb;
}
} catch (e) {}
}
}
}
// One-time: per-core max freq
property var _maxFreqProc: Process {
running: true
command: ["sh", "-c", "for f in /sys/devices/system/cpu/cpu[0-9]*/cpufreq/cpuinfo_max_freq; do [ -f \"$f\" ] && cat \"$f\" || echo 0; done 2>/dev/null"]
stdout: StdioCollector {
onStreamFinished: {
root.cpuCoreMaxFreq = text.trim().split("\n").filter(l => l).map(l => parseInt(l) / 1e6);
}
}
}
// One-time: P/E-core topology
property var _coreTypesProc: Process {
running: true
command: ["sh", "-c", "for d in /sys/devices/system/cpu/cpu[0-9]*/topology/core_type; do [ -f \"$d\" ] && cat \"$d\" || echo Performance; done 2>/dev/null"]
stdout: StdioCollector {
onStreamFinished: {
root.cpuCoreTypes = text.trim().split("\n").filter(l => l).map(l => l.trim());
}
}
}
// Disk via df
property var _diskProc: Process {
id: diskProc
running: true
command: ["sh", "-c", "df -x tmpfs -x devtmpfs -x squashfs -x efivarfs -x overlay -B1 --output=target,size,used 2>/dev/null | awk 'NR>1 && $2+0>0 {print $1\"|\"$2\"|\"$3}'"]
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().split("\n").filter(l => l);
const mounts = [];
for (const line of lines) {
const parts = line.split("|");
if (parts.length < 3)
continue;
const total = parseInt(parts[1]);
const used = parseInt(parts[2]);
if (total <= 0)
continue;
mounts.push({
"target": parts[0],
"pct": Math.round(used / total * 100),
"usedBytes": used,
"totalBytes": total
});
}
root.diskMounts = mounts;
const rm = mounts.find(m => m.target === "/");
if (rm)
root.diskRootPct = rm.pct;
}
}
}
property var _diskTimer: Timer {
interval: M.Modules.disk.interval || 30000
running: true
repeat: true
onTriggered: diskProc.running = true
}
}