A GPU is named from an embedded PCI ID table, the same on every machine

The router has no lspci, so its GeminiLake chip came up as 'Intel GPU
3185' beside the identical chip's 'Intel UHD Graphics 600' on hamtv.
A trimmed pci.ids — the graphics devices of Intel, AMD and NVIDIA, and the
virtual GPUs, 115 KB — rides in the binary and is consulted first; lspci
is the fallback for a card newer than the table. scripts/pci-ids-gpu.sh
regenerates it from the hub's copy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-10-02 23:48:05 -07:00
co-authored by Claude Fable 5.1
parent b5cecce435
commit 3a58e265c6
6 changed files with 3085 additions and 2 deletions
+10 -1
View File
@@ -313,7 +313,16 @@ something long in one, switch away and back, refresh — it's all still there.
exposes power, energy and fan but **no temperature**, so its box shows none; an integrated
GPU shows the CPU package temperature marked `*`. Asahi (the Mac mini) has neither a busy
counter nor fdinfo in 6.19, so it lists the device and says so. No NVIDIA path: nothing here
has one to test against.
has one to test against. **Names come from an embedded PCI ID table** (2026-10-02,
`internal/metrics/pciids_gpu.txt`, regenerated by `scripts/pci-ids-gpu.sh` from the hub's
`pci.ids`): `gate` has no `lspci` and showed "Intel GPU 3185" beside `hamtv`'s "Intel UHD
Graphics 600" for the identical chip. The table is tried first everywhere, so the name — and
the sensor ID built from it — does not depend on which distro's pciutils a machine has;
`lspci` is the fallback for a card newer than the table. Two fixes the same evening:
the gpu box's layout rules had been lost from the stylesheet (v0.1.0-166/167), and on
`grid` the sampler hung for 42 minutes in Go's poller on the Arc's `fan1_input` —
`readStr` now uses plain blocking syscalls and each GPU's sysfs reads run under a
3-second deadline (v0.1.0-168; `hamlet/docs/gotchas.md` has the mechanism).
- **The ARC on the ZFS tab** — **shipped 2026-10-02** (`internal/metrics/arc.go`,
`internal/storage/arc.go`, `GET /api/zfs/arc`, the Cache section in `ZFS.tsx`, `zfs.arc.*`
sensors). Asked for as "current ARC usage, including any types of caching services in the
+8 -1
View File
@@ -257,9 +257,16 @@ var (
bracketRe = regexp.MustCompile(`\[([^\]]+)\]`)
)
// gpuName is "Intel Arc A380", from lspci's bracketed marketing name when it has one.
// gpuName is "Intel Arc A380": from the embedded PCI ID table first, so the name is the
// same on every machine; from lspci for a card newer than the table; and failing both,
// the vendor and the raw device ID.
func gpuName(dev, bus, driver string) string {
if bus != "" {
vid := strings.TrimPrefix(strings.TrimSpace(readStr(dev+"/vendor")), "0x")
did := strings.TrimPrefix(strings.TrimSpace(readStr(dev+"/device")), "0x")
if vn, dn := pciName(vid, did); dn != "" {
return lspciName(vn + " " + dn)
}
if out, ok := runOut("lspci", "-s", bus); ok {
if _, desc, found := strings.Cut(out, ": "); found {
return lspciName(desc)
+18
View File
@@ -152,3 +152,21 @@ func TestLiveARC(t *testing.T) {
t.Logf("%s = %v %s", x.ID, x.Value, x.Unit)
}
}
// The embedded table names the lab's four GPUs the way lspci would, so a machine without
// pciutils (the router) agrees with one that has it.
func TestPCIName(t *testing.T) {
for id, want := range map[string]string{"8086:3185": "Intel UHD Graphics 600", "8086:56a5": "Intel Arc A380", "8086:46d1": "Intel UHD Graphics", "8086:4c8a": "Intel UHD Graphics 750", "10de:2684": "NVIDIA GeForce RTX 4090"} {
v, d := pciName(id[:4], id[5:])
if d == "" {
t.Errorf("%s: not in the table", id)
continue
}
if got := lspciName(v + " " + d); got != want {
t.Errorf("%s → %q, want %q", id, got, want)
}
}
if _, d := pciName("8086", "ffff"); d != "" {
t.Errorf("an unknown device should be empty, got %q", d)
}
}
+47
View File
@@ -0,0 +1,47 @@
package metrics
// A trimmed PCI ID database, embedded, so a GPU is named the same on every machine —
// "Intel UHD Graphics 600" — whether or not that machine has pciutils. OpenWrt has neither
// lspci nor pci.ids, and the router's GPU came up as "Intel GPU 3185" beside its twin's
// "Intel UHD Graphics 600" until this. scripts/pci-ids-gpu.sh regenerates the file.
import (
_ "embed"
"strings"
"sync"
)
//go:embed pciids_gpu.txt
var pciIDsGPU string
var pciTable struct {
once sync.Once
vendors map[string]string // "8086" → "Intel Corporation"
devices map[string]string // "8086:3185" → "GeminiLake [UHD Graphics 600]"
}
// pciName looks a vendor and device ID up (hex, lower case, no 0x): the vendor's name and
// the device's; empty where unknown.
func pciName(vendor, device string) (string, string) {
pciTable.once.Do(func() {
pciTable.vendors, pciTable.devices = map[string]string{}, map[string]string{}
cur := ""
for _, line := range strings.Split(pciIDsGPU, "\n") {
switch {
case line == "" || line[0] == '#':
case line[0] == '\t':
if cur == "" || len(line) < 7 || line[1] == '\t' {
continue // a subsystem, or a device before any vendor
}
pciTable.devices[cur+":"+line[1:5]] = strings.TrimSpace(line[5:])
default:
if len(line) >= 6 {
cur = line[:4]
pciTable.vendors[cur] = strings.TrimSpace(line[4:])
}
}
}
})
vendor, device = strings.ToLower(vendor), strings.ToLower(device)
return pciTable.vendors[vendor], pciTable.devices[vendor+":"+device]
}
File diff suppressed because it is too large Load Diff
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
# Trim the PCI ID database to the graphics devices of the GPU vendors, for embedding in
# the binary (internal/metrics/pciids_gpu.txt): a machine without pciutils — OpenWrt has
# neither lspci nor pci.ids — then names its GPU exactly as every other machine does.
#
# scripts/pci-ids-gpu.sh [/path/to/pci.ids] default: /usr/share/misc/pci.ids
#
# Kept: the vendor lines and device lines (never subsystems) of Intel 8086, AMD 1002 and
# NVIDIA 10de whose name is bracketed (how pci.ids writes a marketing name) or mentions
# graphics, and everything of the three virtual-GPU vendors (virtio 1af4, VMware 15ad,
# Bochs 1234). A non-GPU device slipping through costs a line; it is only ever consulted
# for a card the kernel already drives as a GPU.
set -eu
src=${1:-/usr/share/misc/pci.ids}
out=$(dirname "$0")/../internal/metrics/pciids_gpu.txt
{
echo "# Trimmed from pci.ids ($(grep -m1 -i 'version:' "$src" | sed 's/^#[[:space:]]*//')) by scripts/pci-ids-gpu.sh."
echo "# Vendor lines and the graphics device lines of Intel, AMD and NVIDIA, plus the virtual GPUs."
echo "# pci.ids is distributed under the 3-clause BSD license (https://pci-ids.ucw.cz/)."
awk '
/^[0-9a-f][0-9a-f][0-9a-f][0-9a-f] / {
vendor = substr($0, 1, 4)
keep = (vendor == "8086" || vendor == "1002" || vendor == "10de")
all = (vendor == "1af4" || vendor == "15ad" || vendor == "1234")
if (keep || all) print
next
}
/^\t[0-9a-f][0-9a-f][0-9a-f][0-9a-f] / {
if (all) { print; next }
if (!keep) next
if ($0 ~ /\[/ || $0 ~ /Graphics|Radeon|GeForce|Quadro|Tesla|Arc |Iris|Xe |GPU|Display|VGA|FirePro|Instinct|Data Center/) print
}
' "$src"
} > "$out"
echo "$out: $(wc -l < "$out") lines, $(wc -c < "$out") bytes"