125 lines
4.9 KiB
QML
125 lines
4.9 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:[]}]
|
|
property var cpuCoreMaxFreq: []
|
|
property var cpuCoreTypes: []
|
|
|
|
// ── 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: ["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;
|
|
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 (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
|
|
}
|
|
}
|