diff options
Diffstat (limited to 'desktop')
| -rw-r--r-- | desktop/modules/perf/Perf.js | 18 | ||||
| -rw-r--r-- | desktop/modules/perf/Perf.qml | 22 | ||||
| -rw-r--r-- | desktop/modules/perf/PerfPanel.qml | 30 | ||||
| -rw-r--r-- | desktop/modules/perf/README.md | 10 | ||||
| -rwxr-xr-x | desktop/modules/perf/gpu.sh (renamed from desktop/modules/perf/vram.sh) | 21 | ||||
| -rwxr-xr-x | desktop/modules/perf/test_gpu.sh (renamed from desktop/modules/perf/test_vram.sh) | 16 | ||||
| -rw-r--r-- | desktop/modules/perf/test_perf.js | 9 |
7 files changed, 103 insertions, 23 deletions
diff --git a/desktop/modules/perf/Perf.js b/desktop/modules/perf/Perf.js index eb0dbd7..b94e5b6 100644 --- a/desktop/modules/perf/Perf.js +++ b/desktop/modules/perf/Perf.js @@ -44,6 +44,24 @@ function load(prev, cur) { return Math.max(0, Math.min(100, 100 * (1 - (cur.idle - prev.idle) / dt))); } +// gpu.sh's line as numbers: [vram, rcs busy, rcs clock, ccs busy, ccs clock], +// null when it printed nothing. +function gpuSample(text) { + const n = text.trim().split(/\s+/).map(Number); + return n.length === 5 && n.every(Number.isFinite) ? n : null; +} + +// {render, compute} percent busy between two gpuSample()s, each null when +// its clock did not move. A client exiting mid-sample takes its cycles with +// it, so a dip below zero is clamped. +function gpuLoad(prev, cur) { + const pct = (b, c) => { + if (!prev || !cur || cur[c] <= prev[c]) return null; + return Math.max(0, Math.min(100, 100 * (cur[b] - prev[b]) / (cur[c] - prev[c]))); + }; + return { render: pct(1, 2), compute: pct(3, 4) }; +} + // {total, used} in bytes; used is MemTotal - MemAvailable. function meminfo(text) { const kb = key => { diff --git a/desktop/modules/perf/Perf.qml b/desktop/modules/perf/Perf.qml index 5fcf0ab..33b5cbc 100644 --- a/desktop/modules/perf/Perf.qml +++ b/desktop/modules/perf/Perf.qml @@ -56,6 +56,9 @@ Singleton { property var temps: [] // [{label, value, warn, crit}], value null if unread property var disks: [] // Perf.js df rows property var vram: null // bytes + property var gpu: null // gpuSample, the last scan + property var gpuRender: null // percent, rcs + property var gpuCompute: null // percent, ccs property var fan: null // rpm property var rx: [] // bytes/s, newest last property var tx: [] @@ -181,18 +184,27 @@ Singleton { root.temps = t; if (fanView.path) { fanView.reload(); root.fan = root.num(fanView); } - if (!vramProc.running) vramProc.running = true; + if (!gpuProc.running) gpuProc.running = true; } } // ponytail: a shell glob over every fd on the system, ~14ms every 2s, // always; gate it on the panel again if that shows up. Only the user's own processes are readable, so a // system service's GPU use is not counted, as in conky. - // Summed in vram.sh, so JS gets one number, never the scan itself. + // Summed in gpu.sh, so JS gets five numbers, never the scan itself. Process { - id: vramProc - command: ["sh", `${Quickshell.shellDir}/modules/perf/vram.sh`] - stdout: StdioCollector { onStreamFinished: root.vram = text.trim() === "" ? null : Number(text) } + id: gpuProc + command: ["sh", `${Quickshell.shellDir}/modules/perf/gpu.sh`] + stdout: StdioCollector { + onStreamFinished: { + const cur = P.gpuSample(text); + const l = P.gpuLoad(root.gpu, cur); + root.vram = cur ? cur[0] : null; + root.gpuRender = l.render; + root.gpuCompute = l.compute; + root.gpu = cur; + } + } } // --- network ------------------------------------------------------------ diff --git a/desktop/modules/perf/PerfPanel.qml b/desktop/modules/perf/PerfPanel.qml index 784284a..8ea4af6 100644 --- a/desktop/modules/perf/PerfPanel.qml +++ b/desktop/modules/perf/PerfPanel.qml @@ -320,13 +320,32 @@ Column { // --- GPU ---------------------------------------------------------------- - // No utilisation: xe exposes no busy percentage. + // Busy per engine from fdinfo's cycle counters: render is games, compute + // local LLMs; the headline is the busier of the two. Card { + readonly property var busy: Perf.gpuRender === null && Perf.gpuCompute === null ? null + : Math.max(Perf.gpuRender ?? 0, Perf.gpuCompute ?? 0) title: "GPU · " + Perf.gpuName - headline: panel.deg(panel.gpuTemp.value) - headlineColor: panel.tone(panel.gpuTemp.value, panel.gpuTemp.warn, panel.gpuTemp.crit) + headline: busy === null ? "--" : Math.round(busy) + "%" + headlineColor: panel.tone(busy, 25, 75) Pair { + label: "Render" + value: Perf.gpuRender === null ? "--" : Math.round(Perf.gpuRender) + "%" + } + Bar { + frac: (Perf.gpuRender ?? 0) / 100 + fill: panel.tone(Perf.gpuRender, 25, 75) + } + Pair { + label: "Compute" + value: Perf.gpuCompute === null ? "--" : Math.round(Perf.gpuCompute) + "%" + } + Bar { + frac: (Perf.gpuCompute ?? 0) / 100 + fill: panel.tone(Perf.gpuCompute, 25, 75) + } + Pair { label: "VRAM" value: Perf.vram === null ? "--" : P.bytes(Perf.vram) + " / " + P.bytes(Perf.vramTotal) } @@ -335,6 +354,11 @@ Column { fill: panel.tone(panel.pct(Perf.vram, Perf.vramTotal), 25, 75) } Pair { + label: "Temperature" + value: panel.deg(panel.gpuTemp.value) + valueColor: panel.tone(panel.gpuTemp.value, panel.gpuTemp.warn, panel.gpuTemp.crit) + } + Pair { label: "Fan" value: Perf.fan === null ? "--" : Perf.fan + " rpm" } diff --git a/desktop/modules/perf/README.md b/desktop/modules/perf/README.md index d5ae0b3..9fdbc6e 100644 --- a/desktop/modules/perf/README.md +++ b/desktop/modules/perf/README.md @@ -2,10 +2,12 @@ The figures the conky dashboard used to show, as a drawer page of cards, one per row; the page scrolls. CPU load with a bar per core, the CPU model and -RAM; every temperature as a tile; the Arc B580's VRAM and fan (no -utilisation, xe exposes none); the five mounts with their usage; and +RAM; every temperature as a tile; the Arc B580's render and compute load +(games and local LLMs, from fdinfo's cycle counters), VRAM, temperature and +fan; the five mounts with their usage; and `~/.cache` with its five largest children. `Perf.qml` reads them; `Perf.js` -holds the parsing, tested by `node desktop/modules/perf/test_perf.js`. +holds the parsing, tested by `node desktop/modules/perf/test_perf.js`; +`gpu.sh` sums the fdinfo scan, tested by `sh desktop/modules/perf/test_gpu.sh`. The tile shows CPU and RAM, and a dot for the worst of these, yellow or red: @@ -27,7 +29,7 @@ into the LAN depends on the ISP's fixed address. The fetch is never a mismatch: the old value goes stale after 4 hours and shows as a dash. Everything is sampled always, including per-core load, the network rate, -VRAM (a scan of every fdinfo, ~14ms every 2s) and the fan. The network rate +GPU load and VRAM (a scan of every fdinfo, ~14ms every 2s) and the fan. The network rate is drawn by the network page, which reads it from here, so its graph opens on the last minute of history instead of empty. The mount list, `br0`, the GPU name and the 12 GiB VRAM size are constants in `Perf.qml`. diff --git a/desktop/modules/perf/vram.sh b/desktop/modules/perf/gpu.sh index 83db27d..31f8a6e 100755 --- a/desktop/modules/perf/vram.sh +++ b/desktop/modules/perf/gpu.sh @@ -11,10 +11,14 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# VRAM in bytes from the fdinfo scan: drm-resident-vram0 of every xe fd, -# counted once per drm-client-id because a client holds several fds. Prints -# nothing when no xe client is found. With a file argument it reads that -# instead of scanning, for test_vram.sh. +# GPU from the fdinfo scan, every xe fd counted once per drm-client-id +# because a client holds several fds. Prints one line, "vram rcs_busy +# rcs_clock ccs_busy ccs_clock": VRAM in bytes (drm-resident-vram0), then +# per engine the busy cycles summed over clients and the GPU's cycle +# counter, which Perf.js turns into a percentage between two scans. rcs is +# render (games), ccs compute (local LLMs). Prints nothing when no xe +# client is found. With a file argument it reads that instead of scanning, +# for test_gpu.sh. # # Summed here rather than in JS: a process holding thousands of DRM fds # makes the scan a few hundred KB, which is a lot of string churn to parse @@ -28,6 +32,7 @@ scan() { if [ -n "$1" ]; then cat "$1" else grep -sH -e '^drm-driver' -e '^drm-client-id' -e '^drm-resident-vram0' \ + -e '^drm-cycles-[rc]cs' -e '^drm-total-cycles-[rc]cs' \ /proc/[0-9]*/fdinfo/* 2>/dev/null fi } @@ -45,10 +50,16 @@ scan "$1" | awk -F: ' id = d[p, "drm-client-id"] if (d[p, "drm-driver"] != "xe" || id == "" || (id in seen)) continue if (split(d[p, "drm-resident-vram0"], n, " ") < 1 || n[1] !~ /^[0-9]+$/) continue + for (i = 1; i <= 2; i++) { + e = i == 1 ? "rcs" : "ccs" + busy[e] += d[p, "drm-cycles-" e] + c = d[p, "drm-total-cycles-" e] + 0 + if (c > clock[e]) clock[e] = c + } seen[id] u = n[2] == "KiB" ? 1024 : n[2] == "MiB" ? 1048576 : n[2] == "GiB" ? 1073741824 : 1 total += n[1] * u any = 1 } - if (any) printf "%.0f\n", total + if (any) printf "%.0f %.0f %.0f %.0f %.0f\n", total, busy["rcs"], clock["rcs"], busy["ccs"], clock["ccs"] }' diff --git a/desktop/modules/perf/test_vram.sh b/desktop/modules/perf/test_gpu.sh index 7ef4ade..8467c74 100755 --- a/desktop/modules/perf/test_vram.sh +++ b/desktop/modules/perf/test_gpu.sh @@ -11,8 +11,8 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# vram.sh: summed once per xe client id, units honoured, other drivers -# ignored; nothing at all when no xe client is found. +# gpu.sh: VRAM and engine cycles summed once per xe client id, units +# honoured, other drivers ignored; nothing at all when no xe client is found. set -e dir=$(dirname "$0") tmp=$(mktemp) @@ -22,19 +22,25 @@ printf '%s\n' \ "/proc/1/fdinfo/7:drm-driver: xe" \ "/proc/1/fdinfo/7:drm-client-id: 38" \ "/proc/1/fdinfo/7:drm-resident-vram0: 1024 KiB" \ + "/proc/1/fdinfo/7:drm-cycles-rcs: 30" \ + "/proc/1/fdinfo/7:drm-total-cycles-rcs: 1000" \ "/proc/1/fdinfo/10:drm-driver: xe" \ "/proc/1/fdinfo/10:drm-client-id: 38" \ "/proc/1/fdinfo/10:drm-resident-vram0: 1024 KiB" \ + "/proc/1/fdinfo/10:drm-cycles-rcs: 30" \ + "/proc/1/fdinfo/10:drm-total-cycles-rcs: 1000" \ "/proc/2/fdinfo/5:drm-driver: xe" \ "/proc/2/fdinfo/5:drm-client-id: 40" \ "/proc/2/fdinfo/5:drm-resident-vram0: 2 MiB" \ + "/proc/2/fdinfo/5:drm-cycles-ccs: 7" \ + "/proc/2/fdinfo/5:drm-total-cycles-ccs: 900" \ "/proc/3/fdinfo/4:drm-driver: amdgpu" \ "/proc/3/fdinfo/4:drm-client-id: 1" \ "/proc/3/fdinfo/4:drm-resident-vram0: 9 GiB" > "$tmp" -got=$(sh "$dir/vram.sh" "$tmp") -[ "$got" = $((1024 * 1024 + 2 * 1024 * 1024)) ] || { echo "vram: got '$got'"; exit 1; } +got=$(sh "$dir/gpu.sh" "$tmp") +[ "$got" = "$((1024 * 1024 + 2 * 1024 * 1024)) 30 1000 7 900" ] || { echo "gpu: got '$got'"; exit 1; } : > "$tmp" -got=$(sh "$dir/vram.sh" "$tmp") +got=$(sh "$dir/gpu.sh" "$tmp") [ -z "$got" ] || { echo "empty: got '$got'"; exit 1; } echo ok diff --git a/desktop/modules/perf/test_perf.js b/desktop/modules/perf/test_perf.js index d5856e8..a1a068d 100644 --- a/desktop/modules/perf/test_perf.js +++ b/desktop/modules/perf/test_perf.js @@ -20,7 +20,7 @@ const src = fs.readFileSync(__dirname + "/Perf.js", "utf8") .replace(/^\.pragma library$/m, ""); const P = new Function(src + "\nreturn { cpuTimes, load, meminfo, df, rate, " + "hwmonMap, lanAddr, cpuModel, du, band, worst, isIPv4, parsePubip, formatPubip, " + - "ipCheck, due, push, bytes, speed, procs, sockets, netProcs };")(); + "ipCheck, due, push, bytes, speed, procs, sockets, netProcs, gpuSample, gpuLoad };")(); // cpuTimes: aggregate first, then cores; idle is idle + iowait. const stat = [ @@ -183,4 +183,11 @@ assert.deepStrictEqual(P.netProcs([{ key: "a", name: "firefox", rx: 5000, tx: 10 [{ name: "firefox", rx: 3000, tx: 0 }]); assert.deepStrictEqual(P.netProcs([], later, 0), []); + +// gpu.sh: five numbers or nothing; load per engine between two scans. +assert.strictEqual(P.gpuSample(""), null); +const g1 = P.gpuSample("100 0 1000 0 1000\n"), g2 = P.gpuSample("100 200 2000 900 2000"); +assert.deepStrictEqual(P.gpuLoad(g1, g2), { render: 20, compute: 90 }); +assert.deepStrictEqual(P.gpuLoad(null, g2), { render: null, compute: null }); +assert.deepStrictEqual(P.gpuLoad(g2, [100, 0, 3000, 0, 2000]), { render: 0, compute: null }); console.log("test_perf: ok"); |
