>_EXECUTIVE SUMMARY
iftoprs is a single-binary real-time bandwidth monitor written in Rust. Live libpcap capture feeds an async tokio pipeline; a per-flow tracker computes 2 s / 10 s / 40 s sliding-window averages, cumulative byte counters, and 40-sample sparkline history; a ratatui TUI renders the result with mouse + keyboard navigation, hover tooltips, 31 cyberpunk color themes, configurable bandwidth alerts, and live filter / pin / drill-down. Background lsof polling joins each flow to its owning (PID, process_name). A headless --json mode streams NDJSON snapshots for pipelines. Auto-restart with exponential backoff keeps the capture loop alive across transient kernel / driver hiccups. 21,812 production Rust lines + 6,112 integration-test lines + 2,256 test functions + 31 themes + 17 CLI flags + 14 direct (249 transitive) crates — macOS & Linux on x86_64 + aarch64.
Source Distribution — 27,924 lines
Production: 20 .rs files under src/ (capture, data, ui, config, util, main). Tests: tests/integration.rs drives the built binary via CARGO_BIN_EXE_iftoprs — no cargo run, so CI output is read directly from the process.
~SCALE & POSITION
Where iftoprs sits in the bandwidth-monitor landscape. iftoprs is single-author, single-binary, single-language Rust; the prior-art set spans different languages, different scopes, and different UX trade-offs.
| Tool | Language | UI | Process attribution | Themes | JSON output | Mouse + tooltips |
|---|---|---|---|---|---|---|
| iftoprs | Rust | ratatui + crossterm | per-flow PID + name (lsof) | 31 | --json NDJSON stream |
full — hover, right-click, sparkline tooltips |
| iftop | C | ncurses | none | built-in palette | none | none |
| nethogs | C++ | ncurses | per-process aggregate | none | tracemode text | none |
| bandwhich | Rust | tui-rs | per-process | none | raw mode text | none |
| vnstat | C | CLI / text | none (interface-level) | none | --json |
n/a (no TUI) |
| nload | C++ | ncurses | none | none | none | none |
Comparison rows reflect each tool's public feature set as observed from project READMEs and man pages. iftoprs targets the iftop UX with strict feature-superset goals: every iftop feature has an equivalent or stronger replacement (live filter, mouse, themes, sparkline, process column, JSON), and every iftoprs setting auto-persists to ~/.iftoprs.conf in TOML.
By UX axis
Most prior-art tools commit to one of: ncurses + hotkeys, or text-mode dumps. iftoprs combines both — full mouse + keyboard parity in the TUI, plus a --json headless mode that is the same capture engine without the renderer. The TUI carries 14 segment tooltips (hover + right-click) plus per-flow sparkline tooltips driven by 40-sample ring buffers.
By theming axis
31 themes — pressed-key live preview, swatch chooser, side-by-side compare, persisted to ~/.iftoprs.conf. Prior-art tools in this category typically ship one palette or rely on terminal-wide color schemes. iftoprs treats colors as a first-class config knob.
By resilience axis
v2.22.2 wired in auto-restart on transient capture errors with exponential backoff (commit 76f42105bd). The capture thread can lose its pcap handle to a kernel driver hiccup or interface flap and reopen without dropping the user-visible UI.
By integration axis
--json emits NDJSON snapshots compatible with jq, log shippers, and dashboards. --completions {zsh,bash,fish,elvish,powershell} generates first-class completions for any shell. e exports a full flow snapshot to ~/.iftoprs.export.txt for offline analysis.
#SUBSYSTEM BREAKDOWN
Source partitioned by role. UI rendering and capture parsing dominate — ratatui draw paths and the per-protocol packet decoder are the two heaviest subsystems. The CLI is wide because every flag, completion shell, and color swatch list is generated from one clap derive surface.
| Subsystem | Key Files | Lines | % | Share | Description |
|---|---|---|---|---|---|
| UI | ui/app.rs, ui/render.rs | 6,382 | 23.6% | TUI state, input dispatch, mouse handling, popups, theme chooser, interface chooser, render pipeline for header + flow table + sparkline overlays + tooltips | |
| Capture | capture/parser.rs, capture/sniffer.rs | 3,315 | 12.2% | libpcap loop, exponential-backoff restart, Ethernet/IPv4/IPv6/TCP/UDP/ICMP decode, 5-tuple folding | |
| Config | config/cli.rs, config/theme.rs, config/prefs.rs | 4,555 | 16.8% | clap derive (17 flags + 5 completion shells), 31 themes with display names + swatches, TOML auto-save | |
| Data | data/flow.rs, data/tracker.rs, data/history.rs | 3,590 | 13.3% | 5-tuple flow records, peak / cumulative counters, 40-sample sparkline ring buffer, sliding-window rate calculation | |
| Util | util/format.rs, util/resolver.rs, util/procinfo.rs | 2,822 | 10.4% | byte/bit formatters, async reverse-DNS + port-name cache, background lsof poller for PID+name attribution | |
| Main / Entry | main.rs, lib.rs | 1,148 | 4.2% | argv handoff, JSON mode dispatch, capture spawn, TUI bootstrap, signal handling | |
| Integration tests | tests/integration.rs | 5,258 | 19.4% | 479 integration tests driving the built binary via CARGO_BIN_EXE_iftoprs; covers CLI parse, JSON mode, completions, theme list, BPF filter pass-through | |
| TOTAL | 27,070 | 100% | |||
$FILES BY SIZE
Every .rs file in the crate. ui/app.rs + capture/parser.rs + ui/render.rs together account for ~35% of total source. The integration test file alone is 1.2× larger than the largest production file — behavior is pinned heavily for a CLI tool.
| File | Lines | Role |
|---|---|---|
| tests/integration.rs | 5,258 | 479 integration tests driving the built iftoprs binary — CLI parse, --help / --version, --list-interfaces, --list-colors, --completions {zsh,bash,fish,elvish,powershell}, JSON mode, BPF filter compile, CIDR parse, theme name resolution, prefs round-trip, sparkline math, port-name resolution, multicast CIDR edges |
| src/ui/app.rs | 3,768 | TUI app state machine: every keybind, every popup, every chooser, hover-tooltip controller, drill-down filter, pin/unpin state, pause / resume, theme + interface choosers, help HUD, mouse-event dispatch, scroll math |
| src/capture/parser.rs | 3,074 | Packet decoder — Ethernet II, VLAN, IPv4, IPv6, TCP, UDP, ICMP, ICMPv6, ARP, raw IP. Per-packet 5-tuple extraction, protocol classification, direction inference vs interface IP / net filter |
| src/ui/render.rs | 2,610 | ratatui paint paths — header bar with 14 hover-segment hit boxes, flow table with TX/RX columns and gradient bars, sparkline row, footer status, alert flash, popup choosers, help HUD, pause overlay |
| src/config/cli.rs | 2,344 | clap derive: 17 flags, 5 completion shells, --list-colors swatch dump, --list-interfaces table, version string. Holds every clap_complete generator |
| src/util/resolver.rs | 1,732 | Async reverse-DNS with cache + TTL; port-to-service-name table (/etc/services-style); never blocks the UI thread on lookup misses |
| src/data/flow.rs | 1,673 | 5-tuple Flow record, TX/RX counters, sort comparators, format helpers, display variants for one-line / two-line / sent-only / recv-only modes |
| src/config/theme.rs | 1,177 | 31 themes — ThemeName enum, ThemeName::ALL table, display-name resolver, color palette per theme, swatch row for chooser preview |
| src/main.rs | 1,135 | Entry point — argv handoff, JSON-mode short-circuit, capture thread spawn, TUI bootstrap, Ctrl+C handling, prefs save on exit |
| src/data/tracker.rs | 1,018 | Flow store: insertion / update / expiration, packet→flow folding, per-direction byte accumulation, sliding-window state, alert evaluation |
| src/config/prefs.rs | 948 | TOML serializer for ~/.iftoprs.conf — load, default-on-missing, auto-save on toggle, schema-stable across upgrades |
| src/data/history.rs | 854 | Per-flow sparkline ring buffer (40 samples), sliding-window rate math, peak tracking, display string builder (▁▂▃▅▇█) |
| src/util/format.rs | 673 | Human-readable byte/bit formatters — 10b, 1.2Kb, 340Mb, 1Gb; log-scale bar fill computation; rate column color picker |
| src/util/procinfo.rs | 408 | Background lsof -i -n -P -F pcn poller; (local_addr, local_port) → (pid, name) cache in Arc<Mutex<_>>; non-blocking spawn |
| src/capture/sniffer.rs | 184 | pcap capture loop, BPF filter compile, snaplen / promiscuous setup, exponential-backoff restart on transient errors |
| src/util/mod.rs | 5 | re-exports for the util submodules |
| src/lib.rs | 5 | library entry — re-exports for the integration test harness |
| src/data/mod.rs | 3 | re-exports for the data submodules |
| src/config/mod.rs | 3 | re-exports for the config submodules |
| src/ui/mod.rs | 2 | re-exports for ui::app + ui::render |
| src/capture/mod.rs | 2 | re-exports for capture::sniffer + capture::parser |
| TOTAL (PRODUCTION + TESTS) | 26,633 | 21 .rs files — single binary crate |
@CAPTURE PIPELINE
Packets flow through five stages between the wire and the screen. The async hand-off between capture and parser keeps the libpcap thread tight; the parser runs on a dedicated tokio task; the UI thread reads only the resulting flow table. Auto-restart re-arms the capture handle on transient errors so a driver hiccup never tears down the visible UI.
Wire (NIC / VLAN / WiFi / loopback)
│
▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ sniffer.rs │────▶│ parser.rs │────▶│ tracker.rs │
│ (184) │ │ (2,950) │ │ (1,018) │
│ pcap_loop │ │ Eth / VLAN │ │ 5-tuple store │
│ BPF compile │ │ IPv4 / IPv6 │ │ TX / RX split │
│ exp-backoff │ │ TCP / UDP / │ │ window math │
│ restart │ │ ICMP / Other │ │ alert eval │
└────────────────┘ └────────────────┘ └───────┬────────┘
│
┌─────────────────────────┤
│ │
▼ ▼
┌────────────────┐ ┌────────────────┐
│ resolver.rs │ │ history.rs │
│ (1,732) │ │ (854) │
│ async DNS │ │ 40-sample ring │
│ port names │ │ sparkline │
│ TTL cache │ │ peak tracking │
└────────┬───────┘ └────────┬───────┘
│ │
└────────────┬─────────────┘
│
▼
┌────────────────┐
│ procinfo.rs │
│ (408) │
│ lsof poll │
│ PID + name │
│ cache (Arc/Mut)│
└────────┬───────┘
│
▼
┌──────────────────┐
│ ui/app.rs │
│ (3,654) │
│ state + input │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ ui/render.rs │
│ (2,610) │
│ ratatui paint │
└──────────────────┘
A second egress path skips the renderer entirely: when --json is set, main.rs writes one {"flows":[...]} object per refresh interval to stdout and never starts a terminal.
&FEATURE INVENTORY
Every user-visible capability, partitioned by axis. Anything keyed off ~/.iftoprs.conf auto-persists when toggled in the TUI — users never edit the file by hand unless they want to.
Capture engine
libpcap loop, BPF filter compile, snaplen + promiscuous, exponential-backoff auto-restart on transient errors, async mpsc hand-off, kernel-drop count exposed in status bar.
Flow model
5-tuple (src/dst/sport/dport/proto) keys, TCP / UDP / ICMP / Other classes, TX/RX counters split by interface IP or -F CIDR, peak + cumulative totals, three sliding-window averages (2 s yellow, 10 s green, 40 s cyan).
DNS + port names
Async reverse-DNS cache, /etc/services-style port-to-service map, never blocks the UI thread on lookup misses, toggled with n / N.
Process attribution
Background lsof -i -n -P -F pcn poller, Arc<Mutex<HashMap>> cache, per-flow PID + name column, per-process aggregate tab (Tab), drill-down filter (Enter).
Themes
31 cyberpunk themes with display names, swatch chooser (c), live preview, persisted to ~/.iftoprs.conf, full swatch dump with --list-colors.
Mouse
Left click selects, right click on flow shows TX/RX tooltip with sparkline, right click on header shows segment tooltip, middle click pins/unpins, scroll navigates, hover triggers 1 s tooltip with 3 s auto-hide.
Sparkline
40-sample per-flow ring buffer, inline row below selected flow, also rendered inside right-click tooltip, ▁▂▃▅▇█ block characters, window = 40 × refresh_rate seconds.
Live filter
/ substring match on hostname / IP, 0 clears, Ctrl+W delete word, Ctrl+K kill to end, Esc cancels.
Pinned flows
F toggles pin, ★ marker floats to top of list across sort changes, persisted to ~/.iftoprs.conf pinned array.
Bandwidth alerts
Configurable threshold in bytes/sec, border flash + bell (\x07) + status bar message ⚠ ALERT: hostname rate/s when any flow crosses.
JSON streaming
--json emits NDJSON snapshots to stdout, one object per refresh interval, no TUI started, no terminal control bytes.
Shell completions
--completions {zsh,bash,fish,elvish,powershell}; ship a static _iftoprs for zsh under completions/.
Export
e writes a full flow snapshot to ~/.iftoprs.export.txt with per-flow rates + TX/RX totals + process info.
Clipboard
y copies the selected flow's row to clipboard (via system clipboard pipe).
Display modes
Two-line / one-line / sent-only / recv-only line modes (t); bar styles gradient / solid / thin / ascii (b); bytes vs bits (B); cumulative row (U); border (x); header (g); refresh rate 1/2/5/10 s (f).
Tooltips
14 hover segment tooltips on header bar with 1 s delay + 3 s auto-hide; right-click variants are instant and persistent until dismissed; toggle hover with T (right-click still works).
%THEME CATALOG
31 cyberpunk color themes defined in src/config/theme.rs. The ThemeName enum and ThemeName::ALL table are the single source of truth for the chooser, the --list-colors swatch dump, and the TOML config validator. Default is NeonSprawl.
| # | Enum variant | Display name | Vibe |
|---|---|---|---|
| 1 | NeonSprawl | Neon Sprawl | Default — saturated cyan + magenta city grid |
| 2 | AcidRain | Acid Rain | Toxic green over dark navy |
| 3 | IceBreaker | Ice Breaker | Frost cyan + arctic blue |
| 4 | SynthWave | Synth Wave | Sunset magenta / orange gradient |
| 5 | RustBelt | Rust Belt | Burnt orange + steel grey |
| 6 | GhostWire | Ghost Wire | Pale monochrome ghost-net |
| 7 | RedSector | Red Sector | Aggressive red alarm palette |
| 8 | SakuraDen | Sakura Den | Pink + lavender + soft greys |
| 9 | DataStream | Data Stream | Matrix-green falling code |
| 10 | SolarFlare | Solar Flare | Hot yellow + orange flare |
| 11 | NeonNoir | Neon Noir | High-contrast pink-on-black |
| 12 | ChromeHeart | Chrome Heart | Polished chrome + cool blue |
| 13 | BladeRunner | Blade Runner | Tyrell amber + LA-2049 magenta |
| 14 | VoidWalker | Void Walker | Deep purple over jet black |
| 15 | ToxicWaste | Toxic Waste | Hazmat green + warning yellow |
| 16 | CyberFrost | Cyber Frost | Glacier blue + chrome whites |
| 17 | PlasmaCore | Plasma Core | Reactor-hot magenta + violet |
| 18 | SteelNerve | Steel Nerve | Gunmetal grey + arctic accents |
| 19 | DarkSignal | Dark Signal | Low-light blue + faint cyan |
| 20 | GlitchPop | Glitch Pop | Saturated pop palette — pink, green, cyan |
| 21 | HoloShift | Holo Shift | Iridescent shift — teal + pink |
| 22 | NightCity | Night City | Yellow signage + cobalt skyline |
| 23 | DeepNet | Deep Net | Ultra-deep blue + faint accent |
| 24 | LaserGrid | Laser Grid | Tron magenta lines on black |
| 25 | QuantumFlux | Quantum Flux | Shifting cyan/violet quantum palette |
| 26 | BioHazard | Bio Hazard | Containment-orange + black |
| 27 | Darkwave | Darkwave | Moody synth-wave dark |
| 28 | Overlock | Overlock | Anime-styled neon |
| 29 | Megacorp | Megacorp | Corporate cyan + sterile white |
| 30 | Zaibatsu | Zaibatsu | Crimson + gold corporate signage |
| 31 | Iftopcolor | Iftopcolor | Classic iftop palette for muscle-memory |
| 31 THEMES | 1,177 lines in src/config/theme.rs | ||
*TEST SURFACE
2,256 test functions across the production crate and integration suite. CI runs format + clippy + test on Ubuntu + macOS on every push and pull request. Integration tests drive the built binary via CARGO_BIN_EXE_iftoprs — not cargo run — so the assertions hit the real binary.
// TEST DISTRIBUTION (UNIT)
| File | #[test] fns | Coverage |
|---|---|---|
| src/config/cli.rs | 320 | flag parse, validator, completion dump, --list-colors swatch dump |
| src/capture/parser.rs | 261 | Ethernet / VLAN / IPv4 / IPv6 / TCP / UDP / ICMP / ARP decode; corrupt input tolerance |
| src/data/flow.rs | 201 | 5-tuple equality, sort comparators, display variants, peak / cumulative math |
| src/util/resolver.rs | 191 | DNS cache TTL, port-name map, multicast CIDR edges, Greek tonos / Tai Xuan Jing port tokens |
| src/ui/app.rs | 182 | state transitions, keybind dispatch, mouse hit boxes, tooltip controller |
| src/config/theme.rs | 142 | 31-theme round-trip, display-name resolver, palette assertions |
| src/util/format.rs | 97 | byte/bit formatters across edge magnitudes, log-scale bar fill |
| src/config/prefs.rs | 75 | TOML round-trip, default-on-missing, auto-save |
| src/data/history.rs | 72 | 40-sample ring math, sparkline string, peak tracking |
| src/data/tracker.rs | 68 | flow insertion / update / expiration, direction inference, alert eval |
| src/ui/render.rs | 64 | header layout, hit-box arithmetic, tooltip positioning, popup geometry |
| src/util/procinfo.rs | 27 | lsof output parse, cache invalidation, missing-socket handling |
| src/main.rs | 14 | argv handoff, JSON-mode short-circuit, prefs save on exit |
| UNIT SUBTOTAL | 1,714 | across 13 source files |
// CI MATRIX
| Job | Runner | libpcap-dev | Command |
|---|---|---|---|
| Format | Ubuntu | not needed | cargo --locked fmt --all --check |
| Clippy | Ubuntu | apt install | cargo clippy --all-targets --locked -- -D warnings |
| Test (Linux) | Ubuntu | apt install | cargo build --locked && cargo test --locked |
| Test (macOS) | macOS | system libpcap | cargo build --locked && cargo test --locked |
Workflow uses least-privilege contents: read permissions, cancels in-progress runs on the same branch when a newer commit arrives, sets fail-fast: false so both OSes finish even when one fails, and pins per-job timeouts so a hung runner can't burn the queue.
+DEPENDENCIES
14 direct crates declared in Cargo.toml; 249 total in Cargo.lock once tokio / ratatui / regex pull in their full transitive graph. Single binary out of cargo install iftoprs — LTO + strip + opt-level 3 in the release profile.
| Crate | Version | Role | Tier |
|---|---|---|---|
| ratatui | 0.30 | TUI rendering framework — widgets, layout, styling | direct |
| crossterm | 0.29 | Terminal events + manipulation, mouse capture, raw mode | direct |
| pcap | 2.4 | Packet capture via libpcap (system library) | direct |
| tokio | 1.51 | Async runtime, mpsc channels, signal handling | direct |
| clap | 4.6 | CLI argument parsing (derive) | direct |
| clap_complete | 4 | Shell completion generation (zsh/bash/fish/elvish/powershell) | direct |
| dns-lookup | 3.0 | Reverse DNS resolution | direct |
| regex | 1.12 | Pattern matching for filters | direct |
| chrono | 0.4 | Clock segment, capture start time, ISO timestamps | direct |
| anyhow | 1.0 | Error wrapping at process boundaries | direct |
| serde | 1.0 | Config + JSON serialization (derive) | direct |
| serde_json | 1.0 | NDJSON streaming output | direct |
| toml | 1.1 | Config file format | direct |
| dirs | 6.0 | Home-directory detection (~/.iftoprs.conf, export path) | direct |
| DIRECT | 14 | 249 transitive in Cargo.lock | |
System-level dependency: libpcap for raw packet capture. macOS ships libpcap system-wide. Linux requires libpcap-dev (Debian / Ubuntu) or libpcap-devel (Fedora / RHEL).
?KEY DESIGN DECISIONS
Decisions that shape the codebase but aren't obvious from the source.
| Decision | Mechanism | Rationale |
|---|---|---|
| Async capture, sync UI | tokio + mpsc — capture and parser run on tokio tasks, UI on the main thread |
The libpcap loop is blocking by nature; isolating it on a dedicated task means a busy capture cannot stall mouse events or paint. UI thread reads parsed flows from a channel and never touches pcap directly. |
| Auto-restart on transient errors | Exponential backoff in capture/sniffer.rs; v2.22.2 commit 76f42105bd |
Driver hiccups, interface flaps, and brief permission losses should not crash the visible UI. The capture handle is reopened with the same BPF filter behind the scenes; the user sees an ephemeral status line and continues. |
| TOML config with auto-save | config/prefs.rs writes through to ~/.iftoprs.conf on every TUI toggle |
Users discover preferences by pressing keys, not by editing files. Auto-save means the next launch already remembers the chosen theme, refresh rate, line mode, bar style, and pinned flows. |
| Process attribution via lsof | Background poll in util/procinfo.rs at refresh-rate cadence, Arc<Mutex<HashMap>> cache, non-blocking join on render |
Per-packet PID lookup would be expensive. Polling lsof at the refresh interval gives accurate-enough attribution without measurable overhead on busy hosts. Cache miss = blank process column for one frame, not a crash. |
| JSON mode skips the TUI entirely | main.rs short-circuits before crossterm raw-mode entry when --json is set |
NDJSON pipelines must not emit terminal control bytes. The capture engine, parser, and tracker are identical to TUI mode; only the renderer changes. |
| 31 themes as an enum, not data | ThemeName enum + ThemeName::ALL table in config/theme.rs |
Compile-time exhaustiveness for the chooser, the swatch dump, and the TOML validator. Adding a theme is one variant + one row — the compiler then catches every site that needs to handle it. |
| 14 segment tooltips, hover + right-click | Hit-box arithmetic in ui/render.rs, controller in ui/app.rs |
Hover = 1 s delay, 3 s auto-hide, can be disabled with T. Right-click = instant, persistent until dismissed, always enabled. Two interaction models against one renderer. |
| Sparkline as a ring buffer per flow | data/history.rs — 40-sample ring, folded at refresh-rate cadence |
Drawing the sparkline requires the last N samples regardless of when they were taken. A ring buffer makes the read O(N) without copying; visible window scales with refresh_rate (40 s at 1 s refresh, 400 s at 10 s). |
| Integration tests drive the real binary | CARGO_BIN_EXE_iftoprs env var instead of cargo run |
cargo run reuses the parent cargo state and can rebuild mid-test, polluting stderr. The exe-env path locks in the binary at test-harness start, so CI output is deterministic and reads come from a single fixed process. |
| Five completion shells | clap_complete — --completions {zsh,bash,fish,elvish,powershell} |
Every shell ships completion the same day a flag lands — no separate scripts to keep in sync. The repo also ships a static _iftoprs under completions/ for zsh package install. |
| Pinned flows persist | pinned: Vec<String> in ~/.iftoprs.conf; floated to top across sort changes |
The same hosts come back across sessions (work VPN, home NAS, monitoring endpoints). Persisting pin survives restarts so muscle memory builds up. |
| Single-binary install | cargo install iftoprs — LTO + strip + opt-level 3 in release profile |
One binary, no node_modules, no pip install, no Docker. Drop it on a server, set capabilities once, run. |