Enable concurrent probing of multiple targets using positional arguments or the `--target` flag with unique labels. Update output formats to include a `target` column for logical attribution in multi-target runs.
9.0 KiB
irtt-rs
irtt-rs is a Rust implementation of an IRTT-compatible client.
It is not the upstream IRTT project. For the original implementation, protocol background, and broader project documentation, see:
https://github.com/heistp/irtt
Status
This repository currently provides:
irtt-rs, the canonical multi-applet binaryirtt-cli, a stream/text IRTT-compatible client appletirtt-tui, an optional terminal UI applet when built with thetuifeatureirtt-client, a Rust library for running client sessions and consuming events- finite and continuous probe runs
- table, CSV, TSV, and JSON Lines event-row output formats with selectable columns
- optional local summary statistics
Server support is not implemented.
The installed binary is intentionally not named irtt to avoid conflicting or
confusing overlap with upstream IRTT.
Install
Requires Rust 1.85 or newer.
From a local checkout:
git clone https://github.com/Lochnair/irtt-rs.git
cd irtt-rs
cargo install --path crates/irtt-cli
The default install provides the normal client-oriented binaries, including
irtt-rs and irtt-cli. To install the TUI applet as a separate binary target:
cargo install --path crates/irtt-cli --features tui
Or run it directly without installing:
cargo run -p irtt-cli --bin irtt-cli -- <server>
For space-sensitive packaging, such as OpenWrt packages, distributors can ship
only irtt-rs and symlink or hardlink enabled applet names such as irtt-cli
and, when built with the tui feature, irtt-tui to it. Applet dispatch is
based on the invoked binary name. Server support is not implemented.
cargo-dist release archives are currently configured with the full feature
set, so target archives bundle the enabled applet binaries: irtt-rs,
irtt-cli, and irtt-tui. That is separate from space-sensitive package
layouts, which may still ship one dispatcher binary plus applet-name symlinks
or hardlinks. cargo-dist is not currently configured to create those links.
Build verification
Useful local release/package sanity checks:
cargo fmt --check
cargo test -p irtt-cli
cargo clippy --workspace --all-targets -- -D warnings
cargo build -p irtt-cli --release
cargo build -p irtt-cli --no-default-features --release
cargo build -p irtt-cli --features tui --release
cargo build -p irtt-cli --all-features --release
The no-default-features build only provides the irtt-rs dispatcher binary;
the default build provides irtt-rs and irtt-cli; tui, full, and
all-features builds also provide irtt-tui.
CLI usage
Basic test:
irtt-cli <server>
Set duration and interval:
irtt-cli <server> --duration 30s --interval 100ms
Run continuously:
irtt-cli <server> --duration 0
Probe multiple targets concurrently:
irtt-cli host-a:2112 host-b:2112
irtt-cli --target ams=ams.example.com:2112 --target sg=sg.example.com:2112
irtt-cli host-a:2112 host-b:2112 --pacing burst
For positional multi-target runs, the original target string is used as the
logical target label. Repeated positional strings get a stable numeric suffix.
For --target label=host:port, label is used in the target output column.
Labels must be unique. --pacing staggered|burst selects managed group pacing
for multi-target runs; the default is staggered.
Use a specific output format or column selection:
irtt-cli <server> --format table
irtt-cli <server> --format jsonl
irtt-cli <server> --format csv --columns event,seq,remote,effective_rtt_us
irtt-cli host-a:2112 host-b:2112 --format csv --columns target,seq,effective_rtt_us
irtt-cli <server> --format tsv --columns effective_rtt_us --header never
irtt-cli --list-columns
With the optional TUI feature, irtt-tui opens a live cumulative dashboard for
interactive probing. It defaults to continuous probing, equivalent to
--duration 0. Quit with q or Ctrl-C; the client will drain and close the
session gracefully.
irtt-tui <server>
irtt-tui <server> --duration 30s
cargo run -p irtt-cli --features tui --bin irtt-tui -- <server>
For available options:
irtt-cli --help
irtt-tui --help # when built or installed with the tui feature
Event row output
The stream client separates event row rendering from final summaries. Event rows
are controlled by --format, --columns, and --header; final summaries remain
separate table-style text and are printed for table output when the run policy
allows a summary.
Formats:
table: default, terminal-readable columns with a headercsv: comma-separated rows, intended for scriptstsv: tab-separated rows, useful for one-value streams and shell pipelinesjsonl: one JSON object per event row, suitable for streaming consumers
Headers are controlled with --header auto|always|never. In auto, table, CSV,
and TSV print one header row; JSON Lines never prints a header.
Columns are selected with -c, --columns <COLUMNS>, where COLUMNS is a
comma-separated list. Use --list-columns to print the available names. Useful
columns include:
target, event, seq, remote, token, rtt, rtt_us, raw_rtt_us, effective_rtt_us,
adjusted_rtt_us, rd, rd_us, sd, sd_us, ipdv, ipdv_us, proc,
server_processing_us, bytes, send_call_us, timer_error_us, highest_seen,
server_received, server_window, dscp, ecn, traffic_class, kernel_rx_ns,
warning_kind, message, event_wall_ns, client_send_wall_ns,
client_receive_wall_ns
Aliases are accepted for readability: receive_delay and receive_delay_us
for rd and rd_us, send_delay and send_delay_us for sd and sd_us,
server_processing for proc, server_received_count for
server_received, and server_received_window for server_window.
The default single-target table output favors readability with compact columns:
event,seq,rtt,rd,sd,ipdv,proc,message. It omits echo_sent rows. Passing
--columns default is the same as omitting --columns, including the default
table row filtering. Multi-target defaults add target as the first column so
rows can be attributed to a logical target label. Custom table column selections
include all event rows. CSV, TSV, and JSONL default to all non-target columns
for single-target export and include target by default for multi-target
export; use --columns all explicitly to request every column in any format.
Missing table values render as -; missing CSV and TSV values render as empty
fields; missing JSONL values render as null.
Use JSON Lines for structured event streaming:
Example:
irtt-cli <server> --duration 0 --interval 250ms --format jsonl
Each line is one client event object, for example:
{"event":"echo_reply","seq":4,"remote":"203.0.113.10:2112","token":null,"rtt":"12.1ms","rtt_us":12100,"raw_rtt_us":12400,"effective_rtt_us":12100,"adjusted_rtt_us":12100,"rd":"6.0ms","rd_us":6000,"sd":"6.1ms","sd_us":6100,"ipdv":"300.0µs","ipdv_us":300,"proc":"300.0µs","server_processing_us":300,"bytes":64,"send_call_us":null,"timer_error_us":null,"highest_seen":null,"server_received":5,"server_window":"0x1f","dscp":0,"ecn":0,"traffic_class":0,"kernel_rx_ns":null,"warning_kind":null,"message":null,"event_wall_ns":1760000000012400000,"client_send_wall_ns":1760000000000000000,"client_receive_wall_ns":1760000000012400000}
Consumers should match on the event property and read the columns they need.
RTT, send-call, and timer-error *_us fields are reported in microseconds.
raw_rtt_us is the measured client send-to-receive RTT. effective_rtt_us and
adjusted_rtt_us are signed and may be negative when server processing exceeds
the raw RTT or timing correction produces a negative adjusted value. When
one-way delay fields are present, sd_us and rd_us are also signed
microseconds and may be negative due to wall-clock skew.
For consumers that only need effective RTT values in microseconds, use:
irtt-cli <server> --format tsv --columns effective_rtt_us --header never
Library usage
irtt-client can be used directly from Rust code.
Example Cargo.toml dependency from a local checkout:
[dependencies]
irtt-client = { path = "crates/irtt-client" }
Minimal managed-client example:
use std::time::Duration;
use irtt_client::{
ClientConfig, ClientEvent, ManagedClient, SubscriberConfig,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig {
server_addr: "netperf-eu.bufferbloat.net:2112".to_owned(),
duration: Some(Duration::from_secs(10)),
interval: Duration::from_secs(1),
..ClientConfig::default()
};
let (session, events) =
ManagedClient::start_with_subscription(config, SubscriberConfig::default())?;
while let Ok(event) = events.recv() {
match event {
ClientEvent::EchoReply { seq, rtt, .. } => {
println!("seq={seq} rtt_us={}", rtt.effective.as_micros());
}
ClientEvent::SessionClosed { .. } => break,
_ => {}
}
}
let outcome = session.join()?;
println!("session ended: {:?}", outcome.end_reason);
Ok(())
}