Machine tab: fastfetch-level host facts (D120/D126/D145) #238

Open
opened 2026-10-17 09:06:08 +00:00 by kayg · 3 comments
Owner

Owner (2026-10-17): "I thought we decided to display way more OS / machine info in the machine pill? I only see basic info by default. A lot more info is cheap to get, like what fastfetch/neofetch get."

Decisions: D120 (Machine tab = GUI fastfetch), D125, D126 (default fields + "more"), D145 (native collection, per-source timeouts, hang detection), D156 (cheap facts on by default), D158 (owner review of the in-product prototype).

Audit (origin/release-prod-live == origin/dev for every file below; no unmerged facts branch except the old review-host-facts review archive from 2026-10-09)

Pipeline: crates/plugins/host-facts (agent plugin, action host.facts.read {includeLive}) → the server forwards the action bytes opaquely (browser connection.call) and itself stores only os (remote_inventory.rs, remote_host_bootstrap.os) and checks executionUid/mode at install (bootstrap_remote.rs) → UI decoder apps/tracer/src/canvas/sessions/details-live.on-demand.ts (decodeHostFacts, field whitelist) → rows apps/tracer/src/canvas/windows/details-facts.ts.

D126 field Collected (agent) Sent Shown by default in prod With "Extended machine info" on
OS + version /etc/os-release NAME + VERSION_ID (no ID/ID_LIKE/PRETTY_NAME) static yes ("System") yes
Kernel uname release static no yes
Architecture uname machine static no yes
Model DMI product_name / devicetree model; no vendor; OEM filler strings not filtered static no yes
CPU + cores cpuinfo model name, logical CPUs only (no physical cores, sockets, max clock). Bug: /proc/cpuinfo read capped at 64 KiB and dropped whole when larger → model + hypervisor flag lost on hosts with more than ~40 CPUs static no yes
Memory live MemTotal/MemAvailable; no swap live no yes
Disk live, / only; statvfs inline with no timeout live no yes ("Disk used on /")
Uptime live /proc/uptime live yes yes
Load live /proc/loadavg live no yes
Agent mode mode + executionUid static no yes
Engines Docker/Podman socket exists only; UI decoder drops the whole list if any other value appears static no yes
more: Init /proc/1/comm static no yes
more: Virtualization DMI substrings, cpuinfo hypervisor flag, /run/systemd/container; misses /.dockerenv, /run/.containerenv, WSL, LXC; container and VM collapse into one string static no yes
more: Shell agent process $SHELL only → empty under the root system unit (no User=, no Environment); no passwd fallback static no yes (often empty)
more: Locale env LC_ALL/LC_MESSAGES/LANG only; no /etc/locale.conf fallback static no yes
more: IPs not collected (D156/D157 extended switch, not built) — — —

Cross-cutting:

  • Hang detection / per-source timeouts (D145): none. Every read runs inline on the agent's single node-plugins thread, so a stuck read would stall every plugin action.
  • Static facts "at install and on reconnect": only at agent process start. The plugin registry lives across reconnects, so a reconnect never refreshes them.
  • Live only while the popover is open: yes. The UI polls includeLive:true once a second while open and stops on close; zero work otherwise.

Root cause of what the owner sees: commit ae151cdc9 (2026-10-11, #127 follow-up) added a device-local "Extended machine info" switch (Settings → Appearance, default off). Off shows only Name, System, Uptime, Login, Address. All other D126 default fields (kernel, arch, model, CPU, memory, disk, load, agent mode, engines) moved behind it. The TASTE entry it cites ("section the machine tab output if extended info is enabled") was about the old "More" items. The switch also mixes up a UI display preference with D157's "Gather extended information" (a collection switch for expensive/sensitive facts, not built yet).

Mockup approval: D158 records the owner's review of the in-product prototype (#48, proto-windows). That prototype showed about 10 machine fields by default plus "More". No owner approval exists for the 5-row default. The share card (D126) and OS logos in the sidebar are not audited here.

Plan

Backend gap on branch host-facts (agent + protocol only, no UI). Fix: collect every cheap fact natively, give each source its own timeout and hang detection, refresh static facts on reconnect, keep live counters on request only, and measure on the perf VM. UI: restore the D126 defaults and design the Machine tab (designer job next; message shape will be posted here).

Owner (2026-10-17): "I thought we decided to display way more OS / machine info in the machine pill? I only see basic info by default. A lot more info is cheap to get, like what fastfetch/neofetch get." Decisions: D120 (Machine tab = GUI fastfetch), D125, D126 (default fields + "more"), D145 (native collection, per-source timeouts, hang detection), D156 (cheap facts on by default), D158 (owner review of the in-product prototype). ## Audit (origin/release-prod-live == origin/dev for every file below; no unmerged facts branch except the old `review-host-facts` review archive from 2026-10-09) Pipeline: `crates/plugins/host-facts` (agent plugin, action `host.facts.read {includeLive}`) → the server forwards the action bytes opaquely (browser `connection.call`) and itself stores only `os` (`remote_inventory.rs`, `remote_host_bootstrap.os`) and checks `executionUid`/`mode` at install (`bootstrap_remote.rs`) → UI decoder `apps/tracer/src/canvas/sessions/details-live.on-demand.ts` (`decodeHostFacts`, field whitelist) → rows `apps/tracer/src/canvas/windows/details-facts.ts`. | D126 field | Collected (agent) | Sent | Shown by default in prod | With "Extended machine info" on | |---|---|---|---|---| | OS + version | `/etc/os-release` NAME + VERSION_ID (no ID/ID_LIKE/PRETTY_NAME) | static | yes ("System") | yes | | Kernel | uname release | static | **no** | yes | | Architecture | uname machine | static | **no** | yes | | Model | DMI product_name / devicetree model; no vendor; OEM filler strings not filtered | static | **no** | yes | | CPU + cores | cpuinfo model name, logical CPUs only (no physical cores, sockets, max clock). Bug: `/proc/cpuinfo` read capped at 64 KiB and dropped whole when larger → model + hypervisor flag lost on hosts with more than ~40 CPUs | static | **no** | yes | | Memory | live MemTotal/MemAvailable; no swap | live | **no** | yes | | Disk | live, `/` only; `statvfs` inline with no timeout | live | **no** | yes ("Disk used on /") | | Uptime | live `/proc/uptime` | live | yes | yes | | Load | live `/proc/loadavg` | live | **no** | yes | | Agent mode | mode + executionUid | static | **no** | yes | | Engines | Docker/Podman socket exists only; UI decoder drops the whole list if any other value appears | static | **no** | yes | | more: Init | `/proc/1/comm` | static | no | yes | | more: Virtualization | DMI substrings, cpuinfo `hypervisor` flag, `/run/systemd/container`; misses `/.dockerenv`, `/run/.containerenv`, WSL, LXC; container and VM collapse into one string | static | no | yes | | more: Shell | agent process `$SHELL` only → empty under the root system unit (no `User=`, no Environment); no passwd fallback | static | no | yes (often empty) | | more: Locale | env LC_ALL/LC_MESSAGES/LANG only; no `/etc/locale.conf` fallback | static | no | yes | | more: IPs | not collected (D156/D157 extended switch, not built) | — | — | — | Cross-cutting: - **Hang detection / per-source timeouts (D145): none.** Every read runs inline on the agent's single `node-plugins` thread, so a stuck read would stall every plugin action. - **Static facts "at install and on reconnect": only at agent process start.** The plugin registry lives across reconnects, so a reconnect never refreshes them. - **Live only while the popover is open: yes.** The UI polls `includeLive:true` once a second while open and stops on close; zero work otherwise. **Root cause of what the owner sees:** commit `ae151cdc9` (2026-10-11, #127 follow-up) added a device-local "Extended machine info" switch (Settings → Appearance, **default off**). Off shows only Name, System, Uptime, Login, Address. All other D126 default fields (kernel, arch, model, CPU, memory, disk, load, agent mode, engines) moved behind it. The TASTE entry it cites ("section the machine tab output if extended info is enabled") was about the old "More" items. The switch also mixes up a UI display preference with D157's "Gather extended information" (a collection switch for expensive/sensitive facts, not built yet). **Mockup approval:** D158 records the owner's review of the in-product prototype (#48, `proto-windows`). That prototype showed about 10 machine fields by default plus "More". No owner approval exists for the 5-row default. The share card (D126) and OS logos in the sidebar are not audited here. ## Plan Backend gap on branch `host-facts` (agent + protocol only, no UI). Fix: collect every cheap fact natively, give each source its own timeout and hang detection, refresh static facts on reconnect, keep live counters on request only, and measure on the perf VM. UI: restore the D126 defaults and design the Machine tab (designer job next; message shape will be posted here).
Author
Owner

Starting backend slice on branch host-facts, base 895bcdd23fa7a072978acc3d640d34cc505c33db (origin/dev).

Starting backend slice on branch `host-facts`, base `895bcdd23fa7a072978acc3d640d34cc505c33db` (origin/dev).
Author
Owner

State: paused by the coordinator (build host load 55, production deploy waiting)

Branch host-facts, head 733c3aedfe2f2665b2ca4e920539b6c9452d1aa1 (pushed), base 895bcdd23 (origin/dev).

Commits:

  • d7e2637e7 tracer: the engines decoder keeps known values and drops unknown ones one by one. Before, any new engine name blanked the Engines row. Red then green: the new bun test failed on the old decoder and passes now (3/3).
  • 65ba5dacc host-facts: the full cheap fact set, per-source deadlines with hang detection (stalled), static facts refreshed on reconnect, live counters only on request. cargo test -p infraternal-plugin-host-facts: 32 passed, 0 failed. clippy -D warnings (pedantic) clean. fmt clean.
  • 733c3aedf statfs runs inline on kernel block filesystems; only network and FUSE mounts get a bounded thread. Not yet built or tested (paused).

Perf VM numbers (root@10.69.69.208, 4 vCPU, Debian 13, under /var/lock/perf-bench.lock; 3 runs of 1000 samples each, dev vs 65ba5dacc):

dev p50 / p95 host-facts p50 / p95 target
static collection 90–123 / 148–184 µs 741–1264 / 2083–4590 µs ≤ 20 ms ✓
live refresh 18–27 / 30–39 µs 360–403 / 1200–1791 µs ≤ 2 ms ✓ (tight; 733c3aed targets this, not yet measured)
popover read (whole plugin call) — 353–421 / 1062–1513 µs
static JSON 354 B 685 B < 8 KiB frame (worst case tested)
live JSON 528 B 1117 B
HostFacts / plugin struct — 792 / 912 B

Zero cost when nobody looks: no timers. Static facts are collected at start and lazily on the first read after a link change. Live counters are collected only for includeLive:true. A hung source costs one parked 64 KiB-stack thread until the kernel returns, never more.

Still open: re-measure 733c3aed on the perf VM; scripts/verify fast (binary size vs dev, D243 ≤16 KB, not measured yet). Overlap: mesh-view-truth (#236, 19dbe843f) adds the same hostname field, and this branch matches its filter. Whichever merges second gets a small conflict in facts.rs.

## State: paused by the coordinator (build host load 55, production deploy waiting) Branch `host-facts`, head `733c3aedfe2f2665b2ca4e920539b6c9452d1aa1` (pushed), base `895bcdd23` (origin/dev). Commits: - `d7e2637e7` tracer: the engines decoder keeps known values and drops unknown ones one by one. Before, any new engine name blanked the Engines row. Red then green: the new bun test failed on the old decoder and passes now (3/3). - `65ba5dacc` host-facts: the full cheap fact set, per-source deadlines with hang detection (`stalled`), static facts refreshed on reconnect, live counters only on request. `cargo test -p infraternal-plugin-host-facts`: **32 passed, 0 failed**. clippy `-D warnings` (pedantic) clean. fmt clean. - `733c3aedf` statfs runs inline on kernel block filesystems; only network and FUSE mounts get a bounded thread. **Not yet built or tested** (paused). Perf VM numbers (root@10.69.69.208, 4 vCPU, Debian 13, under `/var/lock/perf-bench.lock`; 3 runs of 1000 samples each, dev vs `65ba5dacc`): | | dev p50 / p95 | host-facts p50 / p95 | target | |---|---|---|---| | static collection | 90–123 / 148–184 µs | 741–1264 / 2083–4590 µs | ≤ 20 ms ✓ | | live refresh | 18–27 / 30–39 µs | 360–403 / 1200–1791 µs | ≤ 2 ms ✓ (tight; `733c3aed` targets this, not yet measured) | | popover read (whole plugin call) | — | 353–421 / 1062–1513 µs | | | static JSON | 354 B | 685 B | < 8 KiB frame (worst case tested) | | live JSON | 528 B | 1117 B | | | HostFacts / plugin struct | — | 792 / 912 B | | Zero cost when nobody looks: no timers. Static facts are collected at start and lazily on the first read after a link change. Live counters are collected only for `includeLive:true`. A hung source costs one parked 64 KiB-stack thread until the kernel returns, never more. Still open: re-measure `733c3aed` on the perf VM; `scripts/verify fast` (binary size vs dev, D243 ≤16 KB, not measured yet). Overlap: `mesh-view-truth` (#236, `19dbe843f`) adds the same `hostname` field, and this branch matches its filter. Whichever merges second gets a small conflict in facts.rs.
Author
Owner

Message shape for the Machine tab designer

Action host.facts.read, input {"includeLive": bool}. Send false once (it costs nothing; the facts are cached). While the popover is open, send true every second; that is what the UI already does. Every field not listed as always present is optional and left out when unknown. The UI must never draw a dash for a missing field.

Static (always present): os, version, kernel, architecture, logicalCpus, mode ("embedded"|"remote"), executionUid, engines (["Docker","Podman","Containerd","Libvirt","LXD","Incus"]).
Static (optional): osId ("ubuntu", the stable logo key), osIdLike ("debian"), osPretty ("Ubuntu 24.04.1 LTS"), osCodename, hostname, vendor, model, modelVersion, firmware ("1.2.3 (01/02/2024)"), cpuModel, physicalCores, cpuSockets, cpuMaxMhz, gpus ([{vendorId, deviceId, driver?}] as PCI ids; the UI maps them to names), init, hypervisor ("QEMU", "WSL", "Virtual machine"…), container ("docker", "lxc"…), virtualization (legacy combined label), shell, locale, timezone, sessionCount, backgroundSessionCount.
Live (only with includeLive): memory {usedBytes,totalBytes}, swap (absent if none), disks ([{mount, fs, usedBytes, totalBytes}], root first, at most 12; fs "nfs4"/"cifs"/"fuse.*" means a network or FUSE mount), rootDisk (legacy), uptimeSeconds, loadAverage [1,5,15], processes, cpuBusyPercent (0–100, absent on the popover's first read).
Hangs: stalled ([{source, path?, fs?}]), for example {"source":"disk","path":"/mnt/nas","fs":"nfs4"}, gives "NFS mount /mnt/nas is unresponsive". A stalled disk is left out of disks.

D126 mapping. Default: OS + version (osPretty or os+version), kernel, architecture, model (vendor + model/modelVersion), CPU (cpuModel, physicalCores/logicalCpus, sockets, max clock, busy %), memory + swap bars, disk bars, uptime, load, infraternal agent mode (mode + executionUid 0 = root), engines. Behind "more": init, hypervisor/container, shell, locale, time zone, firmware, GPUs, hostname. IPs are not collected (D156/D157 extended switch).

UI fix needed separately: ae151cdc9 put every D126 default except uptime behind the device-local "Extended machine info" switch, which is off by default. That is why the owner sees only 5 rows.

## Message shape for the Machine tab designer Action `host.facts.read`, input `{"includeLive": bool}`. Send `false` once (it costs nothing; the facts are cached). While the popover is open, send `true` every second; that is what the UI already does. Every field not listed as always present is optional and left out when unknown. The UI must never draw a dash for a missing field. Static (always present): `os`, `version`, `kernel`, `architecture`, `logicalCpus`, `mode` ("embedded"|"remote"), `executionUid`, `engines` (["Docker","Podman","Containerd","Libvirt","LXD","Incus"]). Static (optional): `osId` ("ubuntu", the stable logo key), `osIdLike` ("debian"), `osPretty` ("Ubuntu 24.04.1 LTS"), `osCodename`, `hostname`, `vendor`, `model`, `modelVersion`, `firmware` ("1.2.3 (01/02/2024)"), `cpuModel`, `physicalCores`, `cpuSockets`, `cpuMaxMhz`, `gpus` ([{vendorId, deviceId, driver?}] as PCI ids; the UI maps them to names), `init`, `hypervisor` ("QEMU", "WSL", "Virtual machine"…), `container` ("docker", "lxc"…), `virtualization` (legacy combined label), `shell`, `locale`, `timezone`, `sessionCount`, `backgroundSessionCount`. Live (only with includeLive): `memory` {usedBytes,totalBytes}, `swap` (absent if none), `disks` ([{mount, fs, usedBytes, totalBytes}], root first, at most 12; fs "nfs4"/"cifs"/"fuse.*" means a network or FUSE mount), `rootDisk` (legacy), `uptimeSeconds`, `loadAverage` [1,5,15], `processes`, `cpuBusyPercent` (0–100, absent on the popover's first read). Hangs: `stalled` ([{source, path?, fs?}]), for example {"source":"disk","path":"/mnt/nas","fs":"nfs4"}, gives "NFS mount /mnt/nas is unresponsive". A stalled disk is left out of `disks`. D126 mapping. Default: OS + version (`osPretty` or os+version), kernel, architecture, model (vendor + model/modelVersion), CPU (cpuModel, physicalCores/logicalCpus, sockets, max clock, busy %), memory + swap bars, disk bars, uptime, load, infraternal agent mode (mode + executionUid 0 = root), engines. Behind "more": init, hypervisor/container, shell, locale, time zone, firmware, GPUs, hostname. IPs are not collected (D156/D157 extended switch). UI fix needed separately: `ae151cdc9` put every D126 default except uptime behind the device-local "Extended machine info" switch, which is off by default. That is why the owner sees only 5 rows.
Sign in to join this conversation.
No labels
launch-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal#238
No description provided.