refactor: add SystemStats singleton + nova-stats daemon for cpu/mem polling

This commit is contained in:
Damocles 2026-04-15 02:10:45 +02:00
parent 71a843e0f3
commit 136ff53cb5
13 changed files with 371 additions and 196 deletions

View file

@ -1,5 +1,4 @@
import QtQuick
import Quickshell.Io
import "." as M
M.BarSection {
@ -7,7 +6,7 @@ M.BarSection {
spacing: Math.max(1, M.Theme.moduleSpacing - 2)
tooltip: ""
property int usage: 0
property int usage: M.SystemStats.cpuUsage
Behavior on usage {
NumberAnimation {
duration: 400
@ -15,7 +14,7 @@ M.BarSection {
}
}
property real freqGhz: 0
property real freqGhz: M.SystemStats.cpuFreqGhz
Behavior on freqGhz {
NumberAnimation {
duration: 400
@ -23,115 +22,11 @@ M.BarSection {
}
}
property var _prev: null
property var _corePrev: []
property var _coreUsage: []
property var _coreFreq: []
property var _coreHistory: [] // array of arrays, last 16 samples per core
property var _coreMaxFreq: [] // max freq in GHz per core, from cpufreq
property var _coreTypes: [] // "Performance" or "Efficiency" per core
FileView {
id: stat
path: "/proc/stat"
onLoaded: {
const lines = text().split("\n");
// Aggregate
const agg = lines[0].trim().split(/\s+/).slice(1).map(Number);
const idle = agg[3] + agg[4];
const total = agg.reduce((a, b) => a + b, 0);
if (root._prev) {
const dIdle = idle - root._prev.idle;
const dTotal = total - root._prev.total;
if (dTotal > 0)
root.usage = Math.round((1 - dIdle / dTotal) * 100);
}
root._prev = {
idle,
total
};
// Per-core
const coreLines = lines.filter(l => /^cpu\d+\s/.test(l));
const newUsage = [];
const newPrev = root._corePrev.length === coreLines.length ? root._corePrev : Array(coreLines.length).fill(null);
for (let i = 0; i < coreLines.length; i++) {
const f = coreLines[i].trim().split(/\s+/).slice(1).map(Number);
const ci = f[3] + f[4];
const ct = f.reduce((a, b) => a + b, 0);
if (newPrev[i]) {
const di = ci - newPrev[i].idle;
const dt = ct - newPrev[i].total;
newUsage.push(dt > 0 ? Math.round((1 - di / dt) * 100) : 0);
} else {
newUsage.push(0);
}
newPrev[i] = {
idle: ci,
total: ct
};
}
root._coreUsage = newUsage;
root._corePrev = newPrev;
// Update sparkline history
const histLen = 16;
const oldH = root._coreHistory;
const newH = [];
for (let i = 0; i < newUsage.length; i++) {
const prev = i < oldH.length ? oldH[i] : [];
const next = prev.concat([newUsage[i]]);
newH.push(next.length > histLen ? next.slice(next.length - histLen) : next);
}
root._coreHistory = newH;
}
}
FileView {
id: cpuinfo
path: "/proc/cpuinfo"
onLoaded: {
const lines = text().split("\n").filter(l => l.startsWith("cpu MHz"));
if (lines.length === 0)
return;
const freqs = lines.map(l => parseFloat(l.split(":")[1]) / 1000);
root.freqGhz = freqs.reduce((a, b) => a + b, 0) / freqs.length;
root._coreFreq = freqs;
}
}
// Read per-core max freq once at init
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._coreMaxFreq = text.trim().split("\n").filter(l => l).map(l => parseInt(l) / 1e6);
}
}
}
// Read P/E-core topology once at init
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._coreTypes = text.trim().split("\n").filter(l => l).map(l => l.trim());
}
}
}
Timer {
interval: M.Modules.cpu.interval || 1000
running: true
repeat: true
onTriggered: {
stat.reload();
cpuinfo.reload();
}
}
readonly property var _coreUsage: M.SystemStats.cpuCoreUsage
readonly property var _coreFreq: M.SystemStats.cpuCoreFreq
readonly property var _coreHistory: M.SystemStats.cpuCoreHistory
readonly property var _coreMaxFreq: M.SystemStats.cpuCoreMaxFreq
readonly property var _coreTypes: M.SystemStats.cpuCoreTypes
property bool _pinned: false
readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered