aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-13 11:30:07 +0200
committerDanilo M. <danix@danix.xyz>2026-10-13 11:30:07 +0200
commit6849707c43bb8b87f9913fc26b6a69d51777d209 (patch)
treefdf165dd2da7783bf4cadd6fc78e72ef32e69dbe
parent2bec673df8ebcb756ec9b605dd7942018a32fdcb (diff)
downloadquickshell-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.qml11
-rw-r--r--desktop/island/perf/Perf.js6
-rw-r--r--desktop/island/perf/Perf.qml20
-rw-r--r--desktop/island/perf/PerfPanel.qml3
-rw-r--r--desktop/island/perf/test_perf.js2
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);