Top: one beat — graphs, numbers and the process table change on the same tick

Graphs stepped once a history point (1 s at a 5 min window) while the numbers changed every
tick; they now slide a fraction of a column each tick. The process table (and the GPU boxes,
MHz and watts it carries) was polled on its own timer; it is now read per sample and committed
with it. GPU clients are walked every sample, and the 1–2 s floors on the gallery, Network,
Docker and ZFS ARC live views are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-10-23 01:13:19 -07:00
co-authored by Claude Opus 5.5
parent 8fc71496f0
commit 70bebcb18b
10 changed files with 148 additions and 53 deletions
+10 -8
View File
@@ -84,9 +84,9 @@ type gpuDev struct {
type gpuState struct {
devs []*gpuDev
listedAt time.Time
clients map[string]clientPrev // device key + client id → engine totals at the previous scan
last []drmFD // the newest client walk, reused between walks
scanAt time.Time
clients map[string]clientPrev // device key + client id → engine totals at the previous scan
last []drmFD // the newest client walk
scan func() ([]drmFD, bool) // the walk; nil is scanClients (tests stand a fake list in)
helper *drmHelper
ok bool // the last walk saw every process
}
@@ -96,8 +96,7 @@ type clientPrev struct {
at time.Time
}
const gpuRelist = time.Minute // how often the card list is re-read: hotplug is rare, the readdir is cheap
const gpuScanEvery = time.Second // engine shares over less than this are noise
const gpuRelist = time.Minute // how often the card list is re-read: hotplug is rare, the readdir is cheap
// gpus is the GPU section of a sample. cpuTemp stands in for an integrated GPU's
// temperature: it is the same die.
@@ -112,10 +111,13 @@ func (c *Collector) gpus(now time.Time, cpuTemp float64) []GPU {
if g.clients == nil {
g.clients = map[string]clientPrev{}
}
if g.scanAt.IsZero() || now.Sub(g.scanAt) >= gpuScanEvery {
g.last, g.ok = c.scanClients()
g.scanAt = now
// The clients are read on every sample, like everything else in it: their engine shares
// move on the same tick as the device's own numbers, at whatever rate is set.
scan := c.scanClients
if g.scan != nil {
scan = g.scan
}
g.last, g.ok = scan()
out := make([]GPU, 0, len(g.devs))
live := map[string]bool{}
for _, d := range g.devs {