diff --git a/README.md b/README.md index 9fc35b9..72388a7 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,9 @@ Probe a server: ```sh irtt-client netperf-eu.bufferbloat.net:2112 --duration 30s --interval 100ms + +# Negotiate parameters without sending measurement probes +irtt-client --no-test netperf-eu.bufferbloat.net:2112 ``` Probe continuously until interrupted (`Ctrl-C`): diff --git a/crates/irtt-app/src/cmd/client/args.rs b/crates/irtt-app/src/cmd/client/args.rs index 5813580..0ece53b 100644 --- a/crates/irtt-app/src/cmd/client/args.rs +++ b/crates/irtt-app/src/cmd/client/args.rs @@ -31,6 +31,15 @@ pub struct ClientArgs { )] pub targets_stdin: bool, + /// Negotiate parameters and exit without sending measurement probes. + #[arg( + short = 'n', + long, + conflicts_with = "targets_stdin", + long_help = "Negotiate parameters with each target and exit without sending measurement probes. Duration options still set the requested duration, but do not keep this mode running. Reports negotiated parameters using the selected output format, without a measurement summary." + )] + pub no_test: bool, + /// Managed group pacing for multi-target mode. #[arg( long, @@ -106,7 +115,7 @@ impl ClientArgs { if self.targets_stdin && !self.is_continuous() { return Err("--targets-stdin requires --continuous or --duration 0".to_owned()); } - prepare_managed_run( + let mut setup = prepare_managed_run( &self.common, if self.continuous { Duration::ZERO @@ -119,7 +128,11 @@ impl ClientArgs { stdin_controlled: self.targets_stdin, }, ) - .await + .await?; + if self.no_test { + setup.client.request.run_mode = irtt_client::RunMode::NoTest; + } + Ok(setup) } pub fn is_continuous(&self) -> bool { diff --git a/crates/irtt-app/src/cmd/client/run.rs b/crates/irtt-app/src/cmd/client/run.rs index 19bf3fa..37282f5 100644 --- a/crates/irtt-app/src/cmd/client/run.rs +++ b/crates/irtt-app/src/cmd/client/run.rs @@ -167,7 +167,8 @@ pub async fn run_stream( }) }), }; - stream_output.print_final_summary = should_print_final_summary(continuous, interrupted); + stream_output.print_final_summary = + !args.no_test && should_print_final_summary(continuous, interrupted); stream_output.show_running_only_summary_note = continuous && interrupted && stream_output.print_final_summary; for target in &setup.targets { @@ -733,7 +734,7 @@ fn stats_config(continuous: bool) -> StatsConfig { } } fn finite_stats_memory_warning(args: &ClientArgs, target_count: usize) -> Option { - if args.is_continuous() || args.duration.is_zero() { + if args.no_test || args.is_continuous() || args.duration.is_zero() { return None; } let target_count = u64::try_from(target_count).unwrap_or(u64::MAX); diff --git a/docs/irtt-client.md b/docs/irtt-client.md index 1a15d47..9d7dfae 100644 --- a/docs/irtt-client.md +++ b/docs/irtt-client.md @@ -172,6 +172,25 @@ desired set to 128 targets and retains at most 256 live target generations while replacements drain. The 128-target limit applies after dual-stack expansion: a hostname producing both families consumes two target slots. +## NEGOTIATION ONLY + +`-n` / `--no-test` connects to each target, negotiates the requested parameters, +and exits without sending measurement probes. For example: + +```sh +irtt-client --no-test host.example:2112 --duration 30s --interval 100ms +``` + +The `no_test_completed` event reports successful negotiation, including the +negotiated duration, interval, and packet length. Output format and column +selection work as usual; no measurement summary or statistics-memory warning +is printed. Negotiation failures use the normal target diagnostics and exit +policy: an uninterrupted run exits nonzero if no target succeeds. + +Duration options still specify the duration to negotiate; even with +`--continuous` or `--duration 0`, the client exits after negotiation. +`--no-test` cannot be combined with `--targets-stdin`. + ## FINITE VERSUS CONTINUOUS OPERATION `--duration` and `--interval` accept fractional and composite durations, such as