diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-13 11:30:07 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-13 11:30:07 +0200 |
| commit | 6849707c43bb8b87f9913fc26b6a69d51777d209 (patch) | |
| tree | fdf165dd2da7783bf4cadd6fc78e72ef32e69dbe | |
| parent | 2bec673df8ebcb756ec9b605dd7942018a32fdcb (diff) | |
| download | quickshell-6849707c43bb8b87f9913fc26b6a69d51777d209.tar.gz quickshell-6849707c43bb8b87f9913fc26b6a69d51777d209.zip | |
fix(perf): review findings in the service and the panel
- The panel loads only while open. Its models are fresh arrays every
sample, so a hidden instance rebuilt its rows every 2s for the whole
session.
- The public IP fetch never kills one already running, and waits 30
minutes from the last attempt, not the last success: a rate-limited
or offline fetch was retried every minute.
- curl -4, so an IPv6 answer cannot silently stall the VPN check.
- CPU load sums only the first eight /proc/stat fields. guest and
guest_nice are already inside user and nice, and counting them again
read high while VMs ran.
- df gets timeout -k; the VRAM scan is not restarted over itself.
- The GPU name joins the host constants in Perf.qml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | desktop/island/StatusPill.qml | 11 | ||||
| -rw-r--r-- | desktop/island/perf/Perf.js | 6 | ||||
| -rw-r--r-- | desktop/island/perf/Perf.qml | 20 | ||||
| -rw-r--r-- | desktop/island/perf/PerfPanel.qml | 3 | ||||
| -rw-r--r-- | desktop/island/perf/test_perf.js | 2 |
5 files changed, 26 insertions, 16 deletions
diff --git a/desktop/island/StatusPill.qml b/desktop/island/StatusPill.qml index dd97855..d62b87f 100644 --- a/desktop/island/StatusPill.qml +++ b/desktop/island/StatusPill.qml @@ -42,7 +42,7 @@ Capsule { restWidth: restRow.implicitWidth + 24 panelWidth: panel === "perf" ? 600 : 320 panelHeight: (panel === "controls" ? controlsPanel.implicitHeight - : panel === "perf" ? perfPanel.implicitHeight + : panel === "perf" ? (perfPanel.item ? perfPanel.item.implicitHeight : 0) : soundPanel.implicitHeight) + 28 // The detailed sampling (per-core, network, VRAM) runs only while the @@ -191,13 +191,14 @@ Capsule { onOpenDrawer: page => pill.openPage(page) } - PerfPanel { + // Loaded only while open: its models are fresh arrays every sample, and + // a hidden instance would rebuild its rows every 2s for the whole session. + Loader { id: perfPanel x: 14 y: 14 width: 600 - 28 - opacity: pill.panel === "perf" ? 1 : 0 - visible: opacity > 0 - Behavior on opacity { NumberAnimation { duration: 160 } } + active: pill.panel === "perf" + sourceComponent: PerfPanel { width: 600 - 28 } } } diff --git a/desktop/island/perf/Perf.js b/desktop/island/perf/Perf.js index d23d900..b853f50 100644 --- a/desktop/island/perf/Perf.js +++ b/desktop/island/perf/Perf.js @@ -23,7 +23,9 @@ const UNITS = { "": 1, B: 1, KiB: 1024, MiB: 1024 ** 2, GiB: 1024 ** 3 }; const STALE = 4 * 3600; // One {total, idle} per cpu line of /proc/stat; index 0 is the aggregate, -// then the cores in file order. idle counts iowait, as conky did. +// then the cores in file order. idle counts iowait, as conky did. Only the +// first eight fields are summed: guest and guest_nice are already inside +// user and nice, and adding them again overstates load while VMs run. function cpuTimes(stat) { const out = []; for (const line of stat.split("\n")) { @@ -31,7 +33,7 @@ function cpuTimes(stat) { if (!m) continue; const v = m[1].trim().split(/\s+/).map(Number); if (v.length < 5) continue; - out.push({ total: v.reduce((a, b) => a + b, 0), idle: v[3] + v[4] }); + out.push({ total: v.slice(0, 8).reduce((a, b) => a + b, 0), idle: v[3] + v[4] }); } return out; } diff --git a/desktop/island/perf/Perf.qml b/desktop/island/perf/Perf.qml index c2843f6..3153cec 100644 --- a/desktop/island/perf/Perf.qml +++ b/desktop/island/perf/Perf.qml @@ -36,6 +36,7 @@ Singleton { readonly property var mounts: ["/", "/home", "/data", "/mnt/nfs/Library", "/mnt/nfs/shared"] readonly property string iface: "br0" readonly property real vramTotal: 12 * 1024 * 1024 * 1024 + readonly property string gpuName: "ARC B580" // Conky's table. Ceilings are per sensor: 70C is unremarkable on a CPU // package and alarming on an NVMe. GPU and VRAM are the GPU card's; the @@ -86,15 +87,21 @@ Singleton { return isNaN(v) ? null : v; } + // Measured from the last attempt, not the last success, so a failing + // fetch (rate limited, offline) still waits 30 minutes; and never over + // one already running, which would kill it before it could answer. + property real pubAttempt: 0 function fetchPubip() { - pubProc.running = false; + const now = Date.now() / 1000; + if (pubProc.running || !P.due(root.pub, now, 1800) || now - root.pubAttempt < 1800) return; + root.pubAttempt = now; pubProc.running = true; } onDetailedChanged: { if (detailed) { if (!root.lan) lanProc.running = true; - if (P.due(root.pub, Date.now() / 1000, 1800)) root.fetchPubip(); + root.fetchPubip(); } else { root.cores = []; root.rx = []; @@ -189,8 +196,7 @@ Singleton { if (root.detailed) { if (fanView.path) { fanView.reload(); root.fan = root.num(fanView); } - vramProc.running = false; - vramProc.running = true; + if (!vramProc.running) vramProc.running = true; } } } @@ -247,7 +253,7 @@ Singleton { Process { id: dfProc - command: ["timeout", "5", "/usr/bin/df", "-P", "-B1"].concat(root.mounts) + command: ["timeout", "-k", "2", "5", "/usr/bin/df", "-P", "-B1"].concat(root.mounts) stdout: StdioCollector { onStreamFinished: { const rows = P.df(text); @@ -274,14 +280,14 @@ Singleton { interval: 60000 running: true repeat: true - onTriggered: if (P.due(root.pub, Date.now() / 1000, 1800)) root.fetchPubip() + onTriggered: root.fetchPubip() } // Anything but an IPv4 address (a failure, an error page) leaves the // cache alone: the old value then goes stale rather than mismatching. Process { id: pubProc - command: ["curl", "-fsS", "--max-time", "10", "https://ipinfo.io/ip"] + command: ["curl", "-4fsS", "--max-time", "10", "https://ipinfo.io/ip"] stdout: StdioCollector { onStreamFinished: { const ip = text.trim(); diff --git a/desktop/island/perf/PerfPanel.qml b/desktop/island/perf/PerfPanel.qml index 5d3f6ff..5aad9b6 100644 --- a/desktop/island/perf/PerfPanel.qml +++ b/desktop/island/perf/PerfPanel.qml @@ -102,7 +102,6 @@ GridLayout { color: Qt.alpha(Theme.surface, 0.6) implicitHeight: body.implicitHeight + 20 Layout.fillHeight: true - Layout.alignment: Qt.AlignTop Column { id: body @@ -200,7 +199,7 @@ GridLayout { Layout.row: 0 Layout.column: 1 Layout.preferredWidth: panel.col - title: "GPU · ARC B580" + title: "GPU · " + Perf.gpuName headline: panel.deg(panel.gpuTemp.value) headlineColor: panel.tone(panel.gpuTemp.value, panel.gpuTemp.warn, panel.gpuTemp.crit) diff --git a/desktop/island/perf/test_perf.js b/desktop/island/perf/test_perf.js index c336cf0..5f39f5d 100644 --- a/desktop/island/perf/test_perf.js +++ b/desktop/island/perf/test_perf.js @@ -35,6 +35,8 @@ assert.strictEqual(t.length, 3); assert.deepStrictEqual(t[0], { total: 1000, idle: 850 }); assert.deepStrictEqual(t[1], { total: 500, idle: 425 }); assert.deepStrictEqual(P.cpuTimes(""), []); +// guest and guest_nice are already inside user and nice: not added twice. +assert.deepStrictEqual(P.cpuTimes("cpu 100 0 0 100 0 0 0 0 80 0"), [{ total: 200, idle: 100 }]); // load: busy share of the delta; no prev, or no time passed, is null. assert.strictEqual(P.load({ total: 1000, idle: 850 }, { total: 1100, idle: 925 }), 25); |
