diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-16 09:34:51 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-16 09:35:05 +0200 |
| commit | cb936e93be75bae93fbd5a81f39376cd31142e71 (patch) | |
| tree | 89df0e88f53b87170c6f1e4e24236c68b87ee20e /desktop/island/perf/Perf.qml | |
| parent | cf89a5d88fb738883e9663b9e44afcc7b7f98807 (diff) | |
| download | quickshell-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.qml | 303 |
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)); - } - } - } -} |
