feat(tui): topology view tab ('T') — NvLink/NUMA/PCIe graph and matrix (#200)
* feat(tui): add topology view tab ('T') for NvLink/NUMA/PCIe visualization
Implements the per-host Topology tab requested in issue #190. Adds a
new reserved tab that ships with remote and replay modes, accessible
via 'T' and toggled between graph and matrix modes with 'M'.
Graph mode renders NUMA zones as ASCII boxes with GPUs inside and
NvLink/NvSwitch edges between them; NUMA boxes stack side-by-side on
wide terminals and fall back to vertical stacking on narrower ones.
Matrix mode mirrors `nvidia-smi topo -m` with CPU affinity + NUMA
columns.
Graceful-degradation paths cover hosts without NvLink (PCIe only),
non-NVIDIA hosts (NUMA groups only), hosts without NUMA (single
synthetic "NUMA ?" box), and terminals narrower than 100 columns
(automatic matrix fallback so nothing overflows 80-col sessions).
Extends `NvLinkRemoteDevice` with `bandwidth_mb_s: Option<u32>` so the
NVn generation classifier can derive labels like "NV5" from the hint.
The new label serialises through the Prometheus exporter and round-
trips via the network parser with backward compatibility: pre-#190
exporters that omit `bandwidth_mb_s` continue to parse cleanly.
Mock clusters can exercise the tab via `ALL_SMI_MOCK_TOPOLOGY=1`,
which emits a DGX-style 8-GPU, 2-NUMA, 64-link topology from every
synthetic NVIDIA node.
Closes #190
* fix(topology): remember operator-selected host, correct docs
C1: Topology tab now tracks the operator's host selection. Previously
pressing `T` unconditionally overwrote `state.current_tab`, so the
renderer always fell through to the first host-shaped tab. Stash the
previously-selected host tab in `topology_last_host_tab` (both on `T`
and on Left/Right arrow navigation), propagate it through the render
snapshot, and have `topology_target_host` honour it when still present
in the tab strip. Remote and replay tab updaters clear the cached name
when the stashed host disappears (disconnect, switched recording) so
the renderer falls back to the first host instead of displaying stale
data.
H1: README claimed `Tab`/`Shift-Tab` cycles Topology hosts, but only the
arrow keys are wired up. Correct the wording and note that the tab now
remembers the last-selected host.
M1: Hide the matrix `CPU Affinity` column until NVML
`nvmlDeviceGetCpuAffinity` plumbing lands — shipping a column that
always says `-` is just noise. Drop the dead `cpu_affinity` helper and
shrink the `pick_cell_width` overhead accordingly (27 → 13 cells) so
narrower terminals can now render the matrix.
M2: Clarify `bandwidth_to_generation` doc. NvLink Gen 2/3/4 all share
the ~25 GB/s per-link ceiling and are collapsed into the `Some(4)`
bucket; `Some(2)` and `Some(3)` are never returned by design.
M3: Simplify `pick_cell_width` tail. The trailing
`if MIN_CELL * gpu_count <= usable` branch is dead because the
preceding `for cw in (MIN_CELL..=MAX_CELL).rev()` already covers the
MIN_CELL case. Return 0 directly after the loop.
Tests: add three `topology_target_host_*` cases covering the remembered
host, the empty fallback, and the stale-host fallback paths. Refit the
`falls_back_to_summary_under_80_col` and matrix legend tests to the
new overhead / column layout.
* test(topology): add key-handler and snapshot round-trip tests
Add 9 tests covering the Topology tab hardening from issue #190:
- t_key_jumps_to_topology_tab_and_remembers_host: T hotkey jumps to
the Topology tab and stashes the previously-selected host tab in
topology_last_host_tab so the renderer honours the operator's host
selection on return.
- t_key_is_noop_when_topology_tab_absent: silent no-op in local mode
where the Topology tab is never inserted.
- remember_current_host_tab_skips_reserved_tabs: All / Users / Topology
reserved tabs are never stashed.
- remember_current_host_tab_stashes_host_tab: host tabs are stashed
correctly.
- m_key_toggles_topology_view_mode_when_topology_active: uppercase M
cycles Graph → Matrix → Graph.
- lowercase_m_also_toggles_topology_view_mode: lowercase m accepted
to reduce muscle-memory friction.
- m_key_does_not_toggle_topology_mode_outside_topology_tab: M outside
the Topology tab hits the global GPU-sort binding, not the mode
toggle.
- test_snapshot_capture_preserves_topology_state: topology_view_mode
and topology_last_host_tab survive RenderSnapshot::capture.
- test_topology_state_roundtrips_through_as_app_state: both fields
survive the full capture → as_app_state round-trip. J
Jeongkyu Shin committed
93d1e5ea2aee64205af23585e56ee9f22d2b33ff
Parent: cede28e
Committed by GitHub <noreply@github.com>
on 4/20/2026, 5:31:05 PM