quickshell: read system metrics natively and harden update polling
Replace the per-tick sh -c subprocess reads in the system popout with Quickshell FileView reads (/proc/stat, /proc/meminfo, and once-resolved hwmon/battery sysfs paths), dropping the 5s tick from ~7 process spawns to ~2. Wrap checkupdates in timeout 120 and guard against restarting an in-flight run so a slow sync can no longer thrash or stall.
This commit is contained in:
@@ -924,29 +924,91 @@ Item {
|
||||
// DATA FETCHING
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
// ─── Native sysfs/proc readers (no per-tick subprocess) ───────────────────
|
||||
// Fixed paths read directly; hwmon/battery paths resolved once by
|
||||
// pathDiscovery below. FileView.reload()+text() is synchronous (verified).
|
||||
|
||||
FileView { id: statFile; path: "/proc/stat"; blockLoading: true }
|
||||
FileView { id: meminfoFile; path: "/proc/meminfo"; blockLoading: true }
|
||||
FileView { id: cpuTempFile; blockLoading: true }
|
||||
FileView { id: nvmeTempFile; blockLoading: true }
|
||||
FileView { id: batCapFile; blockLoading: true }
|
||||
FileView { id: batStatusFile; blockLoading: true }
|
||||
|
||||
property real prevCpuActive: -1
|
||||
property real prevCpuTotal: -1
|
||||
Process { id: cpuProc; command: ["sh", "-c", "awk '/^cpu / {print $2+$4, $2+$4+$5}' /proc/stat"]; stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
let p = this.text.trim().split(" ");
|
||||
let active = parseFloat(p[0]), total = parseFloat(p[1]);
|
||||
if (!isNaN(active) && root.prevCpuTotal > 0) {
|
||||
let da = active - root.prevCpuActive;
|
||||
let dt = total - root.prevCpuTotal;
|
||||
if (dt > 0) { root.cpuVal = (da / dt) * 100; root.cpuHistory = root.pushHistory(root.cpuHistory, root.cpuVal / 100); }
|
||||
}
|
||||
root.prevCpuActive = active;
|
||||
root.prevCpuTotal = total;
|
||||
function readCpu() {
|
||||
statFile.reload();
|
||||
let p = statFile.text().split("\n")[0].trim().split(/\s+/); // cpu user nice system idle …
|
||||
let active = parseFloat(p[1]) + parseFloat(p[3]); // user + system
|
||||
let total = active + parseFloat(p[4]); // + idle
|
||||
if (!isNaN(active) && root.prevCpuTotal > 0) {
|
||||
let da = active - root.prevCpuActive, dt = total - root.prevCpuTotal;
|
||||
if (dt > 0) { root.cpuVal = (da / dt) * 100; root.cpuHistory = root.pushHistory(root.cpuHistory, root.cpuVal / 100); }
|
||||
}
|
||||
}}
|
||||
root.prevCpuActive = active;
|
||||
root.prevCpuTotal = total;
|
||||
}
|
||||
|
||||
Process { id: memProc; command: ["sh", "-c", "free -m | awk '/Mem:/ {printf \"%.1f/%.0fG %.4f\", $3/1024, $2/1024, $3/$2}'"]; stdout: StdioCollector {
|
||||
onStreamFinished: { let p = this.text.trim().split(" "); if (p.length >= 2) { root.memText = p[0]; root.memVal = parseFloat(p[1]) || 0; root.memHistory = root.pushHistory(root.memHistory, root.memVal); } }
|
||||
}}
|
||||
function readMem() {
|
||||
meminfoFile.reload();
|
||||
let t = meminfoFile.text();
|
||||
let total = parseInt((t.match(/MemTotal:\s+(\d+)/) || [])[1]); // kB
|
||||
let avail = parseInt((t.match(/MemAvailable:\s+(\d+)/) || [])[1]); // kB
|
||||
if (!isNaN(total) && !isNaN(avail) && total > 0) {
|
||||
let used = total - avail;
|
||||
root.memText = (used / 1048576).toFixed(1) + "/" + (total / 1048576).toFixed(0) + "G";
|
||||
root.memVal = used / total;
|
||||
root.memHistory = root.pushHistory(root.memHistory, root.memVal);
|
||||
}
|
||||
}
|
||||
|
||||
Process { id: tempProc; command: ["sh", "-c", "sensors 2>/dev/null | awk '/Tctl:|Tdie:|Package id 0:/ {gsub(/\\+|°C/,\"\",$2); printf \"%d\", $2; exit}'"]; stdout: StdioCollector {
|
||||
onStreamFinished: { let v = parseInt(this.text.trim()); if (!isNaN(v)) { root.tempVal = v; root.tempText = v + "°C"; } }
|
||||
}}
|
||||
function readTemp() {
|
||||
if (!cpuTempFile.path) return;
|
||||
cpuTempFile.reload();
|
||||
let v = parseInt(cpuTempFile.text().trim()); // millidegrees
|
||||
if (!isNaN(v)) { root.tempVal = Math.round(v / 1000); root.tempText = root.tempVal + "°C"; }
|
||||
}
|
||||
|
||||
function readNvme() {
|
||||
if (!nvmeTempFile.path) return;
|
||||
nvmeTempFile.reload();
|
||||
let v = parseInt(nvmeTempFile.text().trim());
|
||||
if (!isNaN(v) && v > 0) { root.nvmeTempVal = Math.round(v / 1000); root.nvmeTempText = root.nvmeTempVal + "°C"; }
|
||||
}
|
||||
|
||||
function readBattery() {
|
||||
if (!batCapFile.path) return;
|
||||
batCapFile.reload(); batStatusFile.reload();
|
||||
let c = parseInt(batCapFile.text().trim());
|
||||
let s = batStatusFile.text().trim();
|
||||
if (!isNaN(c)) root.batteryPercent = c;
|
||||
if (s) root.batteryState = s;
|
||||
}
|
||||
|
||||
// Resolve hwmon/battery sysfs paths once per popout open, then read.
|
||||
Process {
|
||||
id: pathDiscovery
|
||||
command: ["sh", "-c",
|
||||
"for h in /sys/class/hwmon/hwmon*; do n=$(cat \"$h/name\" 2>/dev/null); " +
|
||||
"case \"$n\" in k10temp|zenpower|coretemp) sel=''; for l in \"$h\"/temp*_label; do [ -r \"$l\" ] || continue; " +
|
||||
"case \"$(cat \"$l\")\" in Tctl|Tdie|'Package id 0') sel=\"${l%_label}_input\"; break;; esac; done; " +
|
||||
"[ -z \"$sel\" ] && sel=\"$h/temp1_input\"; echo \"cpuTemp=$sel\";; " +
|
||||
"nvme) echo \"nvmeTemp=$h/temp1_input\";; esac; done; " +
|
||||
"b=$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -1); [ -n \"$b\" ] && echo \"batDir=$b\" || true"
|
||||
]
|
||||
stdout: StdioCollector { onStreamFinished: {
|
||||
for (let ln of this.text.trim().split("\n")) {
|
||||
let i = ln.indexOf("="); if (i < 0) continue;
|
||||
let k = ln.slice(0, i), v = ln.slice(i + 1);
|
||||
if (k === "cpuTemp") cpuTempFile.path = v;
|
||||
else if (k === "nvmeTemp") nvmeTempFile.path = v;
|
||||
else if (k === "batDir") { batCapFile.path = v + "/capacity"; batStatusFile.path = v + "/status"; }
|
||||
}
|
||||
root.readTemp(); root.readNvme();
|
||||
if (Shared.Config.hasBattery) root.readBattery();
|
||||
}}
|
||||
}
|
||||
|
||||
Process { id: gpuProc; command: [Shared.Config.gpuScript]; stdout: StdioCollector {
|
||||
onStreamFinished: { try {
|
||||
@@ -967,10 +1029,6 @@ Item {
|
||||
} catch(e) {} }
|
||||
}}
|
||||
|
||||
Process { id: nvmeTempProc; command: ["sh", "-c", "for d in /sys/class/hwmon/hwmon*; do if grep -q nvme \"$d/name\" 2>/dev/null; then awk '{printf \"%d\", $1/1000}' \"$d/temp1_input\" 2>/dev/null; break; fi; done"]; stdout: StdioCollector {
|
||||
onStreamFinished: { let v = parseInt(this.text.trim()); if (!isNaN(v) && v > 0) { root.nvmeTempVal = v; root.nvmeTempText = v + "°C"; } }
|
||||
}}
|
||||
|
||||
Process { id: diskProc; command: ["sh", "-c",
|
||||
"df -h " + Shared.Config.diskMount1 + " --output=pcent,size 2>/dev/null | tail -1 && " +
|
||||
"df -h " + Shared.Config.diskMount2 + " --output=pcent,size 2>/dev/null | tail -1 && " +
|
||||
@@ -986,7 +1044,7 @@ Item {
|
||||
}
|
||||
}}
|
||||
|
||||
Process { id: updateProc; command: ["bash", "-c", "checkupdates 2>/dev/null; echo \":$?\""]; stdout: StdioCollector {
|
||||
Process { id: updateProc; command: ["bash", "-c", "timeout 120 checkupdates 2>/dev/null; echo \":$?\""]; stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
let lines = this.text.trim().split("\n");
|
||||
let exitMatch = lines[lines.length - 1].match(/:(\d+)$/);
|
||||
@@ -1020,10 +1078,7 @@ Item {
|
||||
onStreamFinished: { let l = this.text.trim(); if (l) { let p = l.split(":"); root.networkIface = p[0] || "--"; root.networkIp = p[1] || "--"; } else { root.networkIface = "Network"; root.networkIp = "Offline"; } }
|
||||
}}
|
||||
|
||||
// Battery & power profile (laptop)
|
||||
Process { id: batteryProc; command: ["sh", "-c", "cat /sys/class/power_supply/BAT*/capacity /sys/class/power_supply/BAT*/status 2>/dev/null"]; stdout: StdioCollector {
|
||||
onStreamFinished: { let l = this.text.trim().split("\n"); if (l.length >= 2) { root.batteryPercent = parseInt(l[0]) || -1; root.batteryState = l[1].trim(); } }
|
||||
}}
|
||||
// Power profile (laptop) — battery is read natively via readBattery().
|
||||
Process { id: ppGetProc; command: ["powerprofilesctl", "get"]; stdout: StdioCollector {
|
||||
onStreamFinished: root.powerProfile = this.text.trim()
|
||||
}}
|
||||
@@ -1043,16 +1098,21 @@ Item {
|
||||
|
||||
function rerun(proc) { proc.running = false; proc.running = true; }
|
||||
Timer { interval: 5000; running: true; repeat: true; onTriggered: {
|
||||
rerun(cpuProc); rerun(memProc); rerun(tempProc); rerun(nvmeTempProc); rerun(idleProc);
|
||||
root.readCpu(); root.readMem(); root.readTemp(); root.readNvme(); rerun(idleProc);
|
||||
if (Shared.Config.hasDiscreteGpu) rerun(gpuProc);
|
||||
if (Shared.Config.hasBattery) rerun(batteryProc);
|
||||
if (Shared.Config.hasBattery) root.readBattery();
|
||||
}}
|
||||
Timer { interval: 30000; running: true; repeat: true; onTriggered: { rerun(diskProc); rerun(netProc); if (Shared.Config.hasPowerProfiles) rerun(ppGetProc); } }
|
||||
Timer { interval: 300000; running: true; repeat: true; onTriggered: { rerun(updateProc); rerun(alhpProc); } }
|
||||
// checkupdates can be slow/flaky; don't restart it while a run is still in flight.
|
||||
Timer { interval: 300000; running: true; repeat: true; onTriggered: {
|
||||
if (!updateProc.running) updateProc.running = true;
|
||||
if (!alhpProc.running) alhpProc.running = true;
|
||||
}}
|
||||
|
||||
// Stagger initial launches to avoid many concurrent process spawns
|
||||
// Native reads are synchronous; stagger only the remaining process spawns.
|
||||
Component.onCompleted: {
|
||||
rerun(cpuProc); rerun(memProc); rerun(tempProc);
|
||||
pathDiscovery.running = true;
|
||||
root.readCpu(); root.readMem();
|
||||
staggerTimer.running = true;
|
||||
}
|
||||
Timer {
|
||||
@@ -1060,8 +1120,8 @@ Item {
|
||||
interval: 200
|
||||
onTriggered: {
|
||||
if (Shared.Config.hasDiscreteGpu) rerun(gpuProc);
|
||||
rerun(nvmeTempProc); rerun(idleProc); rerun(diskProc); rerun(netProc); rerun(updateProc); rerun(alhpProc);
|
||||
if (Shared.Config.hasBattery) rerun(batteryProc);
|
||||
rerun(idleProc); rerun(diskProc); rerun(netProc);
|
||||
updateProc.running = true; alhpProc.running = true;
|
||||
if (Shared.Config.hasPowerProfiles) rerun(ppGetProc);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user