127 lines
5 KiB
QML
127 lines
5 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 cpuCoreUsage: []
|
|
property var cpuCoreFreq: []
|
|
property var cpuCoreHistory: []
|
|
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;
|
|
root.cpuCoreUsage = ev.core_usage;
|
|
root.cpuCoreFreq = ev.core_freq_ghz;
|
|
const histLen = 16;
|
|
const oldH = root.cpuCoreHistory;
|
|
const newH = [];
|
|
for (let i = 0; i < ev.core_usage.length; i++) {
|
|
const prev = i < oldH.length ? oldH[i] : [];
|
|
const next = prev.concat([ev.core_usage[i]]);
|
|
newH.push(next.length > histLen ? next.slice(next.length - histLen) : next);
|
|
}
|
|
root.cpuCoreHistory = newH;
|
|
} 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
|
|
}
|
|
}
|