1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
|
# Performance panel design
A performance panel that drops from the island's status pill: CPU, RAM,
temperatures, GPU, disks and network, ported from the four conky widgets that
show them today (`system`, `gpu`, `disks`, `network` in conky-theme-udt). An
icon in the status pill opens it and turns yellow or red when something needs
attention, including the public IP drifting from the address the user's VPN
depends on. This is sub-project 4 of the roadmap in
`2026-10-07-island-core-design.md`.
Once the panel is verified, the four conky widgets are removed: two dashboards
drawing the same numbers would drift apart.
## Scope
In v1:
- A 3-column card grid: CPU (per-core bars, RAM, temperatures), GPU, network
(rate graph, LAN and public IP, pin button), disks.
- A status-pill icon tinted by the worst severity of temperatures, RAM, disks
and the public IP check.
- A pinned public IP, set from the panel, and a red warning on mismatch.
- Removal of the four conky widgets and their samplers.
Not in v1: a drawer page (sub-project 6 reuses `Perf.qml`); history beyond the
network graph; process lists; GPU utilisation (the xe driver exposes none, see
below); auto-discovered mounts; a keybind.
## Where it lives
desktop/island/perf/
Perf.js pure parsing and maths, node-tested
test_perf.js node test for Perf.js
Perf.qml singleton: FileViews, Processes, Timers; exposes state
PerfPanel.qml the card grid
desktop/island/StatusPill.qml + the performance icon
desktop/island/Island.qml + panel "perf"
desktop/Prefs.qml + pinnedIp
## Data sources
All of these are what conky reads today; nothing new is measured.
| Figure | Source | When |
| --- | --- | --- |
| CPU load, aggregate | `/proc/stat` deltas | always, 2s |
| CPU load, per core | `/proc/stat` `cpuN` lines | panel open, 2s |
| RAM | `/proc/meminfo`, `MemTotal - MemAvailable` | always, 2s |
| temperatures | hwmon `temp*_input` | always, 2s |
| GPU fan | hwmon `xe` `fan1_input` | panel open, 2s |
| VRAM used | `/proc/[0-9]*/fdinfo/*`, `drm-resident-vram0` summed per `drm-client-id` for `drm-driver: xe` | panel open, 2s |
| disks | `timeout 5 /usr/bin/df -P -B1 <mounts>` | always, 60s |
| network rate | `/sys/class/net/br0/statistics/{rx,tx}_bytes` | panel open, 1s |
| LAN address | `ip -4 addr show br0`, memoized on first success | panel open |
| public IP | `curl -fsS --max-time 10 https://ipinfo.io/ip` | 30 min, and on panel open if stale |
Reads of `/proc` and `/sys` are `FileView`s reloaded by a `Timer`, no
subprocess. `df`, the fdinfo scan, `ip` and `curl` are `Process` calls; each
`Process` reused for a sequence sets `running = false` before each start.
`Perf.detailed` is true while the panel is open and gates the rows marked
"panel open", the way `Virsh.sampling` gates the VM stats. The always rows
exist so the icon can warn while the panel is closed.
### Sensors
hwmon chips are found by their `name` file once at start, because `hwmonN`
numbering changes across boots. The table is conky's:
| Chip | File | Label | Warn | Crit |
| --- | --- | --- | --- | --- |
| `k10temp` | `temp1_input` | CPU | 75 | 90 |
| `k10temp` | `temp3_input` | CCD1 | 75 | 90 |
| `nvme` | `temp1_input` | NVME | 60 | 70 |
| `gigabyte_wmi` | `temp2_input` | BOARD | 60 | 70 |
| `gigabyte_wmi` | `temp3_input` | BOARD | 60 | 70 |
| `xe` | `temp2_input` | GPU | 75 | 85 |
| `xe` | `temp3_input` | VRAM | 80 | 90 |
The GPU has no utilisation figure: xe exposes no `gpu_busy_percent` and
`intel_gpu_top` refuses the device. The integrated AMD GPU is not shown.
### Host constants
Kept in `Perf.qml`, each marked `ponytail:` with its ceiling, as conky had
them: the mount list (`/`, `/home`, `/data`, `/mnt/nfs/Library`,
`/mnt/nfs/shared`, one NFS mount per server), the interface `br0` (a bridge, so
VM-to-host traffic shows in the graph), and 12 GiB of VRAM (xe puts no
capacity in sysfs). Mount icons are conky's, re-checked in Inconsolata Nerd
Font: Slackware for `/`, house for `/home`, hard disk for `/data`, network
share for NFS, where the label is the server address read from the mount's
device field, so no address is committed.
## Public IP
The fetch writes conky's cache format to `~/.cache/udt/pubip.txt`
(`ip <addr>` and `fetched <epoch>` lines, atomic), so the existing file is
reused. A response that is not an IPv4 address is discarded and the cache left
alone. A value older than 4 hours is shown as a dash.
A pin button beside the address stores the current address as
`perf.pinnedIp` in `desktop.json` through `Prefs`, which gains a `pinnedIp`
property and `setPinnedIp()`, written only on change like `setControls()`. The
address lives in the user's state file, never in the repo. Pressing the pin
again with the same address does nothing; a different address replaces it.
The check:
| State | Result |
| --- | --- |
| no pin | ok |
| fresh fetch equals pin | ok |
| fresh fetch differs from pin | crit |
| fetch failed, or value stale | ok, address shown as a dash or with its age |
A failed fetch is never a mismatch: a dropped connection must not raise the
alarm.
## The icon
`utilities-system-monitor` through `Quickshell.iconPath`, placed before the
controls icon in the status pill. It is tinted by the worst severity:
| Input | Warn | Crit |
| --- | --- | --- |
| each temperature | its warn | its crit |
| RAM | 85% | 95% |
| each disk | 90% | 95% |
| public IP | | mismatch with the pin |
Neutral when ok, `Theme.yellow` on warn, `Theme.red` on crit. Not the accent,
which udt can snap onto a state colour. CPU load is left out: spikes are
normal and would flicker the icon, and sustained load shows up as heat.
Left-click toggles the panel. No right-click until a drawer page exists. The
status pill expands for `perf` as it does for `sound` and `controls`.
## The panel
A grid of three columns, the same palette and type as the other island panels:
- **CPU**, spanning two rows: aggregate load as the headline, the CPU model,
one bar per core, RAM used and total with a bar, then the temperature rows
for CPU, CCD1, NVME and both BOARD sensors.
- **GPU**: package temperature as the headline, VRAM used and total with a
bar, VRAM temperature, fan rpm.
- **Network**: download and upload as two lines over a 60-point history,
current rates, LAN address, public IP with its pin button. The public IP is
red on mismatch.
- **Disks**, spanning two columns: one row per mount with icon, label,
percentage and bar.
Colours follow conky's bands so the numbers read as before: load, per-core
bars, RAM and disk bars at 25 and 75; temperatures at each sensor's own warn
and crit. Escape closes the panel, as for the other panels.
## Errors
- A missing chip, file or interface shows a dash and counts as ok for the
icon.
- `df` under `timeout 5`: a hung NFS server keeps the last value, or a dash
before there is one. The island never blocks.
- VRAM counts only processes readable by the user, so a system service's GPU
use is missing. Noted as a ceiling, as in conky.
- The network history is cleared when the panel closes; it is not sampled
while closed.
## Conky removal
In conky-theme-udt, after the panel is verified on screen beside the conky
widgets, in that repo's own commits:
- Delete `widgets/system.lua`, `gpu.lua`, `disks.lua`, `network.lua` and their
entries in the `dashboard.lua` layout.
- Drop `disks-sample.sh` and `pubip-sample.sh` from `conky.text` and delete
them; the island owns the public IP fetch, and two fetchers would double the
outside traffic.
- Remove readers in `lib/data.lua` used only by those widgets, each checked
against its remaining callers.
- Leave the other widgets where they are. Rearranging the freed space is the
user's call, on screen.
## Testing
- `node desktop/island/perf/test_perf.js`: `/proc/stat` aggregate and per-core
deltas, meminfo, `df -P -B1` parsing, the fdinfo sum with duplicate client
ids, the network rate across a counter reset, severity across every input,
the IP check (no pin, match, mismatch, failed, stale), and the cache file
format both ways.
- A smoke run of `qs -p desktop` reading the log, after the user restarts the
desktop component, since the new directory needs a reindex.
- On screen, by the user: the panel beside the conky widgets before they are
removed, the icon colours, the pin button.
## AGENTS.md
Add any gotcha found while building. The island's README gains the panel and
the icon thresholds.
|