// IFTOPRS — ENGINEERING REPORT

Real-time bandwidth monitor in Rust · ratatui + crossterm + libpcap · per-flow process attribution · 31 cyberpunk themes · NDJSON streaming · auto-restart capture

>_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.

27,924
Total Rust Lines
21,812
Production Rust
2,256
Test Functions
31
Color Themes
17
CLI Flags
14
Direct Crates
249
Transitive Crates
20
Production .rs Files
205
Git Commits
v2.22.8
Released

Source Distribution — 27,924 lines

21,812 production / 6,112 tests · 78.1% production

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.

SubsystemKey FilesLines%ShareDescription
UIui/app.rs, ui/render.rs6,38223.6%
TUI state, input dispatch, mouse handling, popups, theme chooser, interface chooser, render pipeline for header + flow table + sparkline overlays + tooltips
Capturecapture/parser.rs, capture/sniffer.rs3,31512.2%
libpcap loop, exponential-backoff restart, Ethernet/IPv4/IPv6/TCP/UDP/ICMP decode, 5-tuple folding
Configconfig/cli.rs, config/theme.rs, config/prefs.rs4,55516.8%
clap derive (17 flags + 5 completion shells), 31 themes with display names + swatches, TOML auto-save
Datadata/flow.rs, data/tracker.rs, data/history.rs3,59013.3%
5-tuple flow records, peak / cumulative counters, 40-sample sparkline ring buffer, sliding-window rate calculation
Utilutil/format.rs, util/resolver.rs, util/procinfo.rs2,82210.4%
byte/bit formatters, async reverse-DNS + port-name cache, background lsof poller for PID+name attribution
Main / Entrymain.rs, lib.rs1,1484.2%
argv handoff, JSON mode dispatch, capture spawn, TUI bootstrap, signal handling
Integration teststests/integration.rs5,25819.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
TOTAL27,070100%

$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.

FileLinesRole
tests/integration.rs5,258479 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.rs3,768TUI 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.rs3,074Packet 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.rs2,610ratatui 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.rs2,344clap derive: 17 flags, 5 completion shells, --list-colors swatch dump, --list-interfaces table, version string. Holds every clap_complete generator
src/util/resolver.rs1,732Async reverse-DNS with cache + TTL; port-to-service-name table (/etc/services-style); never blocks the UI thread on lookup misses
src/data/flow.rs1,6735-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.rs1,17731 themes — ThemeName enum, ThemeName::ALL table, display-name resolver, color palette per theme, swatch row for chooser preview
src/main.rs1,135Entry point — argv handoff, JSON-mode short-circuit, capture thread spawn, TUI bootstrap, Ctrl+C handling, prefs save on exit
src/data/tracker.rs1,018Flow store: insertion / update / expiration, packet→flow folding, per-direction byte accumulation, sliding-window state, alert evaluation
src/config/prefs.rs948TOML serializer for ~/.iftoprs.conf — load, default-on-missing, auto-save on toggle, schema-stable across upgrades
src/data/history.rs854Per-flow sparkline ring buffer (40 samples), sliding-window rate math, peak tracking, display string builder (▁▂▃▅▇█)
src/util/format.rs673Human-readable byte/bit formatters — 10b, 1.2Kb, 340Mb, 1Gb; log-scale bar fill computation; rate column color picker
src/util/procinfo.rs408Background lsof -i -n -P -F pcn poller; (local_addr, local_port) → (pid, name) cache in Arc<Mutex<_>>; non-blocking spawn
src/capture/sniffer.rs184pcap capture loop, BPF filter compile, snaplen / promiscuous setup, exponential-backoff restart on transient errors
src/util/mod.rs5re-exports for the util submodules
src/lib.rs5library entry — re-exports for the integration test harness
src/data/mod.rs3re-exports for the data submodules
src/config/mod.rs3re-exports for the config submodules
src/ui/mod.rs2re-exports for ui::app + ui::render
src/capture/mod.rs2re-exports for capture::sniffer + capture::parser
TOTAL (PRODUCTION + TESTS)26,63321 .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 variantDisplay nameVibe
1NeonSprawlNeon SprawlDefault — saturated cyan + magenta city grid
2AcidRainAcid RainToxic green over dark navy
3IceBreakerIce BreakerFrost cyan + arctic blue
4SynthWaveSynth WaveSunset magenta / orange gradient
5RustBeltRust BeltBurnt orange + steel grey
6GhostWireGhost WirePale monochrome ghost-net
7RedSectorRed SectorAggressive red alarm palette
8SakuraDenSakura DenPink + lavender + soft greys
9DataStreamData StreamMatrix-green falling code
10SolarFlareSolar FlareHot yellow + orange flare
11NeonNoirNeon NoirHigh-contrast pink-on-black
12ChromeHeartChrome HeartPolished chrome + cool blue
13BladeRunnerBlade RunnerTyrell amber + LA-2049 magenta
14VoidWalkerVoid WalkerDeep purple over jet black
15ToxicWasteToxic WasteHazmat green + warning yellow
16CyberFrostCyber FrostGlacier blue + chrome whites
17PlasmaCorePlasma CoreReactor-hot magenta + violet
18SteelNerveSteel NerveGunmetal grey + arctic accents
19DarkSignalDark SignalLow-light blue + faint cyan
20GlitchPopGlitch PopSaturated pop palette — pink, green, cyan
21HoloShiftHolo ShiftIridescent shift — teal + pink
22NightCityNight CityYellow signage + cobalt skyline
23DeepNetDeep NetUltra-deep blue + faint accent
24LaserGridLaser GridTron magenta lines on black
25QuantumFluxQuantum FluxShifting cyan/violet quantum palette
26BioHazardBio HazardContainment-orange + black
27DarkwaveDarkwaveMoody synth-wave dark
28OverlockOverlockAnime-styled neon
29MegacorpMegacorpCorporate cyan + sterile white
30ZaibatsuZaibatsuCrimson + gold corporate signage
31IftopcolorIftopcolorClassic iftop palette for muscle-memory
31 THEMES1,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.

2,256
Total #[test] fns
1,729
Unit Tests (src/)
513
Integration Tests
6,112
Integration LOC

// TEST DISTRIBUTION (UNIT)

File#[test] fnsCoverage
src/config/cli.rs320flag parse, validator, completion dump, --list-colors swatch dump
src/capture/parser.rs261Ethernet / VLAN / IPv4 / IPv6 / TCP / UDP / ICMP / ARP decode; corrupt input tolerance
src/data/flow.rs2015-tuple equality, sort comparators, display variants, peak / cumulative math
src/util/resolver.rs191DNS cache TTL, port-name map, multicast CIDR edges, Greek tonos / Tai Xuan Jing port tokens
src/ui/app.rs182state transitions, keybind dispatch, mouse hit boxes, tooltip controller
src/config/theme.rs14231-theme round-trip, display-name resolver, palette assertions
src/util/format.rs97byte/bit formatters across edge magnitudes, log-scale bar fill
src/config/prefs.rs75TOML round-trip, default-on-missing, auto-save
src/data/history.rs7240-sample ring math, sparkline string, peak tracking
src/data/tracker.rs68flow insertion / update / expiration, direction inference, alert eval
src/ui/render.rs64header layout, hit-box arithmetic, tooltip positioning, popup geometry
src/util/procinfo.rs27lsof output parse, cache invalidation, missing-socket handling
src/main.rs14argv handoff, JSON-mode short-circuit, prefs save on exit
UNIT SUBTOTAL1,714across 13 source files

// CI MATRIX

JobRunnerlibpcap-devCommand
FormatUbuntunot neededcargo --locked fmt --all --check
ClippyUbuntuapt installcargo clippy --all-targets --locked -- -D warnings
Test (Linux)Ubuntuapt installcargo build --locked && cargo test --locked
Test (macOS)macOSsystem libpcapcargo 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.

CrateVersionRoleTier
ratatui0.30TUI rendering framework — widgets, layout, stylingdirect
crossterm0.29Terminal events + manipulation, mouse capture, raw modedirect
pcap2.4Packet capture via libpcap (system library)direct
tokio1.51Async runtime, mpsc channels, signal handlingdirect
clap4.6CLI argument parsing (derive)direct
clap_complete4Shell completion generation (zsh/bash/fish/elvish/powershell)direct
dns-lookup3.0Reverse DNS resolutiondirect
regex1.12Pattern matching for filtersdirect
chrono0.4Clock segment, capture start time, ISO timestampsdirect
anyhow1.0Error wrapping at process boundariesdirect
serde1.0Config + JSON serialization (derive)direct
serde_json1.0NDJSON streaming outputdirect
toml1.1Config file formatdirect
dirs6.0Home-directory detection (~/.iftoprs.conf, export path)direct
DIRECT14249 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.

DecisionMechanismRationale
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.