- Status: done
- Date: 2026-06-10
- Specs touched:
LOCAL_ANALYTICS.md# Real-time system resources andBRAIN_HOST_PROTOCOL.md# Pattern A — one source-file reconciliation each (/sys/block/<dev>/stat→/proc/diskstats, same counters in one read); everything else realized, not changed.
Closes issue #115. The live system-resources panel (the Activity icon in the top bar) was wired end-to-end — UI SSE → brain systemlive hub → host-agent GET /v1/system/resources — but the leaf returned synthesized counters in both binaries, so even a booted box showed invented numbers. This slice adds the real /proc sampler and injects it into the real binary only; the fake's synthetic counters stay as the inner loop's stand-in.
A stateless sampler reading six kernel files under an injectable root (/ in production, a fixture tree in tests) into one protocol.SystemResources of raw cumulative counters — the brain keeps all rate derivation (BRAIN_HOST_PROTOCOL.md # Pattern A):
/proc/stataggregatecpuline →CPUCounters. TotalJiffies sums user..steal (the first eight fields), excluding guest/guest_nice — the kernel already accounts guest time inside user/nice, so the issue's "sum of all fields" would double-count it and understate CPU% on a VM-hosting box. IdleJiffies = idle + iowait, per the issue./proc/meminfo→MemCounters(KiB → bytes; Used = Total − Available, the same meaning the synthetic fields had)./proc/loadavg→LoadAvg [3]float64./proc/net/dev→[]NetCounters, filtered: everything exceptloand the container-side virtual interfaces (docker*,br-*,veth*). Physical NICs and the future mesh interface pass through —LOCAL_ANALYTICS.mdreports "physical LAN NICs + the mesh interface only" (the issue's sketch said to skip mesh; the spec says report it, and the spec wins —DISCOVERY.md's LAN-only rule is about publishing, not throughput reporting). Sorted by name for a stable wire order./proc/diskstats→[]DiskCountersfor whole-disk devices only (sd*/hd*/vd*/xvd*letters-only,nvme<n>n<n>,mmcblk<n>): partitions would double-count their parent disk; loop/dm/md/zram/sr are views over the same physical IO. Sectors × 512 (the kernel's/proc/diskstatssector unit is fixed at 512 regardless of the device's real sector size). Read from/proc/diskstatsrather than per-device/sys/block/<dev>/statfiles — same counters, one read; both specs reconciled./proc/uptime→UptimeS;ts_ns = time.Now().UnixNano().
Any read or parse failure fails the whole sample — a partial snapshot is worth less than none, since the brain's poller logs-and-skips a failed tick while keeping its previous rate baseline (systemlive.poll).
- New consumer-side
hostagent.SystemSamplerinterface (Sample() (protocol.SystemResources, error)) andSystemfield onAgent, following the existingHealth/Disk/Logs/Resourcesseam pattern. When nil (the fake binary, tests), the synthetic monotonically-climbing counters serve unchanged; when set, the handler serves the sampler's snapshot verbatim, and a sampler error is a 500sample-failed— never a silent fall-through to synthetic, which would corrupt the brain's rate diff with a fake baseline. cmd/host-agent-real/main.go:a.System = procsource.New()next to the other reporters.- The stale aspirational comment on the handler ("The real binary reads /proc and /sys") now describes the actual seam.
internal/systemlive, the API layer, and the UI are untouched, per the issue.
- Parser tests (
procsource_test.go, fixture/proctree, never touches the real one): full coherent fixture (jiffy sums incl. guest exclusion, KiB→bytes, loadavg, allowlist filtering with rx/tx column positions, whole-disk filtering with sector conversion, uptime truncation);includeIface/wholeDisktables (tailscale0in,docker_gwbridge/nvme0n1p2/zram0/… out); short pre-2.6.33 cpu line; six loud-error cases. - Real-
/proctests (the package is Linux-only, so/procis always there): double-sample monotonicity (jiffies andts_nsnever go backwards — the property the brain's rate derivation needs) and allowlist enforcement against live interfaces/devices. - Seam tests (
agent_test.go, cross-platform stub): delegation serves the snapshot verbatim; sampler error → 500sample-failed; the two pre-existing synthetic-fallback tests pass unchanged (fake behavior unmodified). - End-to-end smoke (
systemresources_linux_test.go): the exactcmd/host-agent-realwiring (a.System = procsource.New()) served over the real HTTP handler returns this box's actual counters and none of the synthetic signature values — the inner-loop equivalent of the issue's "open the panel on a booted box". - Full non-PAM Go suite + gofmt + vet green locally; the real binary's compile is CI's job (no
libpam0g-devhere).
LOCAL_ANALYTICS.md# Real-time system resources — host-agent owns the host reads, applies the interface/device selection at the source, stays stateless, returns raw counters. # Interface and device selection realized as written (mesh included).BRAIN_HOST_PROTOCOL.md# Pattern A — the documentedGET /v1/system/resourcespayload now carries real data on a real box; cold-start null-rate contract untouched because the brain still owns derivation.CLAUDE.md# Developing —procsourcesits behind the Linux build tag on the host-integrated side of the inner/outer boundary; the cross-platform surface (fake agent, brain, UI) compiles and behaves exactly as before.
ts_nsis wall-clock (time.Now().UnixNano(), as the issue specified), not CLOCK_MONOTONIC — an NTP step between two polls could distort or null one tick's rates. The brain already tolerates a bad tick (skip/derive-next); a monotonic source would need a protocol-comment change and isn't worth it for a 1 Hz gauge.- The interface filter is name-pattern-based, not
/sys/class/net/<if>/device-backed physical detection. Exotic virtual interfaces outside the excluded prefixes (tun*,wg*) would be reported — acceptable, since a WireGuard-style interface is the kind of mesh the spec wants shown. - Jiffies and "sum of all fields" deviation from the issue text — deliberate, documented above and in the package doc.
- The real binary still can't be compile-checked on this dev box (PAM); CI covers it.
- The deeper Settings → System page (full graphs, all interfaces/drives broken out) stays deferred per
LOCAL_ANALYTICS.md# UI surfaces.
- Nothing new from this slice. With #115 done, the live-resources chain is real end-to-end on a booted box; the deferred admin System page and the per-container "Activity Monitor" view remain tracked in
NEXT.md.