aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/island/perf/Perf.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-16 09:34:51 +0200
committerDanilo M. <danix@danix.xyz>2026-10-16 09:35:05 +0200
commitcb936e93be75bae93fbd5a81f39376cd31142e71 (patch)
tree89df0e88f53b87170c6f1e4e24236c68b87ee20e /desktop/island/perf/Perf.qml
parentcf89a5d88fb738883e9663b9e44afcc7b7f98807 (diff)
downloadquickshell-cb936e93be75bae93fbd5a81f39376cd31142e71.tar.gz
quickshell-cb936e93be75bae93fbd5a81f39376cd31142e71.zip
docs(spec): user details in the drawer
A drawer row greets the login user and opens a page that edits name, email, avatar, language and location, written where other programs already read them: AccountsService (and through it the passwd GECOS LibreOffice reads), KDE's emaildefaults and the global git config, with the surname shortened to an initial for git. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/island/perf/Perf.qml')
-rw-r--r--desktop/island/perf/Perf.qml303
1 files changed, 0 insertions, 303 deletions
diff --git a/desktop/island/perf/Perf.qml b/desktop/island/perf/Perf.qml
deleted file mode 100644
index 53d8670..0000000
--- a/desktop/island/perf/Perf.qml
+++ /dev/null
@@ -1,303 +0,0 @@
-// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 2 as
-// published by the Free Software Foundation.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-pragma Singleton
-
-import Quickshell
-import Quickshell.Io
-import QtQuick
-import QtQml
-import "../.."
-import "Perf.js" as P
-
-// The numbers behind the performance panel and the status pill's icon. CPU,
-// RAM, temperatures and disks are sampled always, so the icon can warn while
-// the panel is closed; per-core load, the network rate, VRAM and the fan only
-// while `detailed`, which is any PerfPanel existing (island or drawer).
-// /proc and /sys are FileViews reloaded on a timer, no subprocess; df, the
-// fdinfo scan, ip and curl are Processes.
-Singleton {
- id: root
-
- // PerfPanel instances alive; each counts itself in and out.
- property int viewers: 0
- readonly property bool detailed: viewers > 0
-
- // ponytail: host constants, as conky had them. One mount per NFS server
- // (Library and Slackware are one export, so are shared and backup); br0
- // is a bridge, so VM-to-host copies show in the graph; xe puts no VRAM
- // capacity in sysfs. Edit here when the hardware changes.
- 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
- // rest are the CPU card's rows.
- readonly property var sensors: [
- { chip: "k10temp", file: "temp1_input", label: "CPU", warn: 75, crit: 90 },
- { chip: "k10temp", file: "temp3_input", label: "CCD1", warn: 75, crit: 90 },
- { chip: "nvme", file: "temp1_input", label: "NVME", warn: 60, crit: 70 },
- { chip: "gigabyte_wmi", file: "temp2_input", label: "BOARD", warn: 60, crit: 70 },
- { chip: "gigabyte_wmi", file: "temp3_input", label: "BOARD", warn: 60, crit: 70 },
- { chip: "xe", file: "temp2_input", label: "GPU", warn: 75, crit: 85 },
- { chip: "xe", file: "temp3_input", label: "VRAM", warn: 80, crit: 90 },
- ]
-
- property var hwmon: ({}) // chip name -> /sys/class/hwmon/hwmonN
- property string cpuModel: ""
- property var load: null // aggregate percent
- property var cores: [] // percent per core, while detailed
- property var mem: null // {total, used} bytes
- property var temps: [] // [{label, value, warn, crit}], value null if unread
- property var disks: [] // Perf.js df rows
- property var vram: null // bytes, while detailed
- property var fan: null // rpm, while detailed
- property var rx: [] // bytes/s, newest last, while detailed
- property var tx: []
- property string lan: ""
- property var pub: null // {ip, fetched}
- property real now: Date.now() / 1000
-
- readonly property var ip: P.ipCheck(root.pub, root.now, Prefs.pinnedIp)
-
- // The icon's dot. CPU load is left out: spikes would flicker it, and
- // sustained load shows up as heat.
- readonly property string severity: P.worst(
- root.temps.map(t => P.band(t.value, t.warn, t.crit))
- .concat([P.band(root.mem ? 100 * root.mem.used / root.mem.total : null, 85, 95)])
- .concat(root.disks.map(d => P.band(d.pct, 90, 95)))
- .concat([root.ip.mismatch ? "crit" : "ok"]))
-
- readonly property string pubipPath:
- (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env("HOME") + "/.cache") + "/udt/pubip.txt"
-
- property var prevCpu: []
- property var prevNet: null // {rx, tx, t}
-
- function num(view) {
- const v = parseInt(view.text(), 10);
- 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() {
- 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;
- root.fetchPubip();
- } else {
- root.cores = [];
- root.rx = [];
- root.tx = [];
- root.prevNet = null;
- root.vram = null;
- root.fan = null;
- }
- }
-
- // --- /proc and /sys ---------------------------------------------------
-
- FileView { id: stat; path: "/proc/stat"; blockLoading: true }
- FileView { id: meminfo; path: "/proc/meminfo"; blockLoading: true }
- FileView {
- path: "/proc/cpuinfo"
- blockLoading: true
- onLoaded: root.cpuModel = P.cpuModel(text())
- }
-
- Instantiator {
- id: tempViews
- model: root.sensors
- delegate: FileView {
- required property var modelData
- path: root.hwmon[modelData.chip] ? root.hwmon[modelData.chip] + "/" + modelData.file : ""
- blockLoading: true
- printErrors: false
- }
- }
-
- FileView {
- id: fanView
- path: root.hwmon.xe ? root.hwmon.xe + "/fan1_input" : ""
- blockLoading: true
- printErrors: false
- }
-
- FileView {
- id: rxView
- path: "/sys/class/net/" + root.iface + "/statistics/rx_bytes"
- blockLoading: true
- printErrors: false
- }
-
- FileView {
- id: txView
- path: "/sys/class/net/" + root.iface + "/statistics/tx_bytes"
- blockLoading: true
- printErrors: false
- }
-
- Process {
- running: true
- command: ["sh", "-c", "grep -H . /sys/class/hwmon/hwmon*/name"]
- stdout: StdioCollector { onStreamFinished: root.hwmon = P.hwmonMap(text) }
- }
-
- // CPU, RAM and temperatures always; the fan and VRAM while detailed.
- Timer {
- interval: 2000
- running: true
- repeat: true
- triggeredOnStart: true
- onTriggered: {
- root.now = Date.now() / 1000;
-
- stat.reload();
- const cur = P.cpuTimes(stat.text());
- root.load = P.load(root.prevCpu[0], cur[0]);
- root.cores = root.detailed
- ? cur.slice(1).map((c, i) => P.load(root.prevCpu[i + 1], c) ?? 0)
- : [];
- root.prevCpu = cur;
-
- meminfo.reload();
- root.mem = P.meminfo(meminfo.text());
-
- const t = [];
- for (let i = 0; i < root.sensors.length; i++) {
- const s = root.sensors[i];
- const v = tempViews.objectAt(i);
- let value = null;
- if (v && v.path) {
- v.reload();
- const n = root.num(v);
- value = n === null ? null : n / 1000;
- }
- t.push({ label: s.label, value: value, warn: s.warn, crit: s.crit });
- }
- root.temps = t;
-
- if (root.detailed) {
- if (fanView.path) { fanView.reload(); root.fan = root.num(fanView); }
- if (!vramProc.running) vramProc.running = true;
- }
- }
- }
-
- // ponytail: a shell glob over every fd on the system, ~14ms; only while
- // the panel is open. Only the user's own processes are readable, so a
- // system service's GPU use is not counted, as in conky.
- Process {
- id: vramProc
- command: ["sh", "-c", "grep -sH -e '^drm-driver' -e '^drm-client-id' "
- + "-e '^drm-resident-vram0' /proc/[0-9]*/fdinfo/* 2>/dev/null"]
- stdout: StdioCollector { onStreamFinished: root.vram = P.vram(text) }
- }
-
- // --- network ------------------------------------------------------------
-
- Timer {
- interval: 1000
- running: root.detailed
- repeat: true
- triggeredOnStart: true
- onTriggered: {
- rxView.reload();
- txView.reload();
- const cur = { rx: root.num(rxView), tx: root.num(txView), t: Date.now() / 1000 };
- const p = root.prevNet;
- if (p) {
- const r = P.rate(p.rx, cur.rx, cur.t - p.t);
- const s = P.rate(p.tx, cur.tx, cur.t - p.t);
- if (r !== null) root.rx = P.push(root.rx, r, 60);
- if (s !== null) root.tx = P.push(root.tx, s, 60);
- }
- root.prevNet = cur;
- }
- }
-
- Process {
- id: lanProc
- command: ["ip", "-4", "-o", "addr", "show", root.iface]
- stdout: StdioCollector { onStreamFinished: root.lan = P.lanAddr(text) }
- }
-
- // --- disks --------------------------------------------------------------
-
- // df under timeout: an unreachable NFS server blocks statfs, and the
- // whole call then yields nothing, so the last rows are kept.
- Timer {
- interval: 60000
- running: true
- repeat: true
- triggeredOnStart: true
- onTriggered: { dfProc.running = false; dfProc.running = true; }
- }
-
- Process {
- id: dfProc
- command: ["timeout", "-k", "2", "5", "/usr/bin/df", "-P", "-B1"].concat(root.mounts)
- stdout: StdioCollector {
- onStreamFinished: {
- const rows = P.df(text);
- if (rows.length) root.disks = rows;
- }
- }
- }
-
- // --- public IP ----------------------------------------------------------
-
- // conky's cache file, so the address survives a restart and the fetch
- // keeps conky's 30 minute pace. Checked every minute, fetched when due.
- FileView {
- id: pubFile
- path: root.pubipPath
- atomicWrites: true
- watchChanges: true
- printErrors: false
- onFileChanged: reload()
- onLoaded: root.pub = P.parsePubip(text())
- }
-
- Timer {
- interval: 60000
- running: true
- repeat: true
- 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", "-4fsS", "--max-time", "10", "https://ipinfo.io/ip"]
- stdout: StdioCollector {
- onStreamFinished: {
- const ip = text.trim();
- if (!P.isIPv4(ip)) return;
- const at = Math.floor(Date.now() / 1000);
- root.pub = { ip: ip, fetched: at };
- pubFile.setText(P.formatPubip(ip, at));
- }
- }
- }
-}