# 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 binary
- `irtt-cli`, a stream/text IRTT-compatible client applet
- `irtt-tui`, an optional terminal UI applet when built with the `tui` feature
- `irtt-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:

```sh
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:

```sh
cargo install --path crates/irtt-cli --features tui
```

Or run it directly without installing:

```sh
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:

```sh
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:

```sh
irtt-cli <server>
```

Set duration and interval:

```sh
irtt-cli <server> --duration 30s --interval 100ms
```

Run continuously:

```sh
irtt-cli <server> --duration 0
```

Probe multiple targets concurrently:

```sh
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:

```sh
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.

```sh
irtt-tui <server>
irtt-tui <server> --duration 30s
irtt-tui host-a:2112 host-b:2112 --pacing burst
irtt-tui --target ams=ams.example.com:2112 --target sg=sg.example.com:2112
cargo run -p irtt-cli --features tui --bin irtt-tui -- <server>
```

For available options:

```sh
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 header
- `csv`: comma-separated rows, intended for scripts
- `tsv`: tab-separated rows, useful for one-value streams and shell pipelines
- `jsonl`: 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:

```text
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:

```sh
irtt-cli <server> --duration 0 --interval 250ms --format jsonl
```

Each line is one client event object, for example:

```json
{"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:

```sh
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:

```toml
[dependencies]
irtt-client = { path = "crates/irtt-client" }
```

Minimal managed-client example:

```rust
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(())
}
```
