gander / on FreeBSD
FreeBSD is where the newer terminal process viewers thin out — not because the data is hard to get, but because few projects test there.
htop has supported FreeBSD for years and gander has a dedicated sysctl/libkvm backend tested on a real FreeBSD kernel in CI. If you are choosing between the newer tools on FreeBSD, the platform-support row is often the whole decision.
| FreeBSD support | |
|---|---|
| htop | yes, in the ports tree |
| gander | yes, libkvm backend, CI-tested in a VM |
| btop | yes, built in CI (also NetBSD, OpenBSD) |
| bottom | listed as unofficial; "may only receive limited support" |
That is not a ranking of the tools. bottom is a good program, and it says plainly what its FreeBSD support is. The point is narrower: on FreeBSD you should check this row before anything else, because a viewer that compiles but is not exercised on the platform will eventually show you a number that is wrong rather than missing.
The backend uses two interfaces. sysctl supplies the process table and system figures; libkvm (kvm_getswapinfo, devstat_getdevs) fills in what sysctl does not expose.
| Figure | Source |
|---|---|
| Process list | sysctl(KERN_PROC_PROC) → kinfo_proc[] |
| CPU time, start time | ki_runtime (microseconds), ki_start |
| Resident and virtual size | ki_rssize (pages), ki_size (bytes) |
| State letter | ki_stat plus ki_tdflags/ki_slptime |
| Command line, env | sysctl(KERN_PROC_ARGS), KERN_PROC_ENV |
| Executable, cwd | KERN_PROC_PATHNAME, KERN_PROC_CWD |
| Open files | KERN_PROC_FILEDESC |
| Per-core CPU | kern.cp_times |
| Memory | vm.stats.vm.v_* with v_page_count |
| Swap | kvm_getswapinfo(3) |
| Disk throughput | devstat_getdevs(3) |
| Network throughput | sysctl(NET_RT_IFLIST) |
| CPU affinity | cpuset_getaffinity / cpuset_setaffinity |
Every one of those rows produced a bug during development that only a comparison against another tool caught. A sample of the ones worth knowing about:
ki_runtime is microseconds, while every CPU-time field on Linux and macOS is a tick counter. Dividing one by a HZ divisor gives a percentage off by four orders of magnitude.ki_rssize is pages and ki_size is bytes. They look like a matched pair and are not; forgetting the page size reports a 1.2 GB process as 300 MB, which is plausible enough to survive review.ki_stat is not a state letter. SSLEEP splits three ways depending on ki_tdflags & TDF_SINTR and ki_slptime, and mapping it arithmetically shows a brand-new process as I and a locked one as X.kvm_getswapinfo reports pages. htop's own FreeBSD backend multiplies by 1024 instead of the page size, which reports a 1 GiB swap file as 4 MiB while labelling it bytes. gander does it the way swapinfo(8) does.hw.physmem is larger than the memory the VM manages, so using it as the memory bar's denominator leaves the bar permanently short of full — which reads as a rendering bug on a machine that is genuinely out of memory.The same rule as macOS: a column that could only ever render - is removed from the column list, and a meter stays with n/a here so a config file moved between machines still loads.
| on FreeBSD | |
|---|---|
| PSS / EPSS | no smaps_rollup; gated out |
| PSI, zram, zswap, HugePages | meter says n/a here |
| Container columns | jails are not containers; gated out |
| GPU columns | no drm-usage-stats; gated out |
| Delay accounting | TASKSTATS is Linux-only; gated out |
I/O priority (i) | no ioprio_set; panel says so |
File locks panel (x) | no lock table; points at lsof |
| CPU affinity, env, open files | supported |
| Host-wide disk throughput | supported, the only platform with both figures |
Jails are the interesting omission. kinfo_proc.ki_jid is read and available, but nothing renders it: a jail shares the kernel, has no image layers and no identifier beyond a small integer, so listing it as CONTAINER would be a false equivalence. jid 0 is the host, which is the common case, so the column would be a list of zeroes with a few exceptions.
FreeBSD has no GitHub-hosted runner, so it is checked two ways: a cross-compile of the whole crate to x86_64-unknown-freebsd on every push, and a full test run inside a FreeBSD 14 VM on QEMU. The VM job is the one that matters — the live tests compare gander's figures against ps(1), sysctl(8), swapinfo(8) and netstat(1) inside the guest. Most of the bugs listed above are things a cross-compile cannot see.
The full matrix, including every row that is deliberately missing on each platform, is in docs/PORTABILITY.md.
Every release ships a x86_64-unknown-freebsd archive with a .sha256 file beside it:
tar -xzf gander-<version>-x86_64-unknown-freebsd.tar.gz
./gander-<version>-x86_64-unknown-freebsd/gander
With a Rust toolchain:
cargo install gander-bin
See Releases for the current archive, or all install options.