Repository navigation
The rest of the slowdown fix, plus a correct picture of bridged virtual machines. Soaked for
eight days across six builds before the cut; the final one ran three and a half days clean.
The traffic LED no longer piles up animations. 1.9.5 removed the picker leak, and five
days of production uptime confirmed that part stayed fixed — yet the main thread was back at a
full core, this time inside SwiftUI's animation evaluation. Each interface LED blinked with a
repeatForever animation that was restarted every time traffic resumed on that interface. Such
an animation never finishes, so SwiftUI kept every one of them (7,681 after five days) and
evaluated the whole list on every frame. The LED's pulse is now computed from the clock on
each update, with no SwiftUI animation behind it at all: idle LEDs cost nothing and a pulsing
one never accumulates anything. Same look as before.
The port query no longer walks the whole I/O Registry every three seconds. Reading the
registry tree is how NetLights maps USB and Thunderbolt devices to ports. It used to serialize
every property of every registry node on every third poll, and a single pass had grown to
about two seconds of kernel time on an M-series MacBook Pro — most of a core, continuously,
for data that only changes when something is plugged in. The walk now runs when IOKit reports
that a USB device, Thunderbolt switch, disk, or HID device appeared or went away, when you
press Refresh, or once a minute as a fallback, and it skips the handful of driver-hosted
HID and display nodes whose property reads were 90 % of the cost and that nothing reads.
The topology shown is unchanged; what changes is that an idle NetLights idles.
Less work per refresh on the main thread. With a virtual machine bridged, the layout engine
was re-deriving the band geometry and the Virtual band's rows dozens of times per tick and
re-hashing every interface, including a string-splitting parse of Thunderbolt port names, on
every geometry lookup. Those are now computed once per layout and cached; on an M-series
MacBook Pro the main thread's busy share with a bridged VM dropped from 88 % to 47 %.
A bridged virtual machine's bridge now sits under the port it bridges. With a UTM virtual
machine in bridged mode, bridge100 floated in the Data Link band with no wire to en7, the USB
Ethernet it bridges, while ifconfig bridge100 plainly listed both members. The graph had been
inferring membership from MAC addresses, which holds for the Thunderbolt bridge0 — it adopts
en1's address — and for nothing else; UTM's and Internet Sharing's bridges get a
locally-administered MAC of their own. NetLights now asks the bridge driver the way ifconfig
does (unprivileged, and it works in the sandboxed App Store build), and the Linux collector
reads /sys/class/net/<bridge>/brif. Each bridge in --dump-json gains an optional
bridgeMembers; the schema stays 3. A bridge is centred over its physical members and wired
to every visible one, including the VM side (vmenet0), which "Hide inactive" now keeps on
screen whenever its bridge is. Two bridges whose members sit close together keep the Physical
row's minimum spacing instead of overlapping, and the terminal graph, which has no wires, names
the members on the bridge's row. The VM-side port of such a bridge (vmenet0) is listed
under Virtual Adapters as a Virtual Machine port instead of under System.
Install: brew install --cask willowhawk-k/tap/netlights (or brew upgrade --cask netlights), the notarized zip below, or the Mac App Store once the review lands. Requires macOS 13 or later.