From 0c1ee79afcc23612ef89e49d5293a031ca46b1fb Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Fri, 8 May 2026 13:16:07 +0700 Subject: [PATCH] feat(cli): quiet default human output --- crates/irtt-cli/src/lib.rs | 48 ++++++++++++++++++++++++++---- crates/irtt-cli/src/main.rs | 24 +++++++++++++-- crates/irtt-cli/src/summary.rs | 53 ++++++++++++++++++++++++++++++++-- 3 files changed, 114 insertions(+), 11 deletions(-) diff --git a/crates/irtt-cli/src/lib.rs b/crates/irtt-cli/src/lib.rs index ae00d6a..70186d6 100644 --- a/crates/irtt-cli/src/lib.rs +++ b/crates/irtt-cli/src/lib.rs @@ -87,6 +87,10 @@ pub struct CliArgs { /// Output format: human, simple, machine, or rtt-us. #[arg(long, value_enum, default_value_t = OutputMode::Human)] pub output: OutputMode, + + /// Include extra fields in human output. + #[arg(long)] + pub verbose: bool, } impl CliArgs { @@ -322,6 +326,11 @@ pub struct HumanEventStats { pub receive_ipdv: Option, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct HumanOutputOptions { + pub verbose: bool, +} + #[cfg(feature = "stats")] impl From for HumanEventStats { fn from(value: irtt_stats::EventStatsUpdate) -> Self { @@ -335,6 +344,14 @@ impl From for HumanEventStats { } pub fn format_human_event(event: &ClientEvent, stats: Option) -> String { + format_human_event_with_options(event, stats, HumanOutputOptions::default()) +} + +pub fn format_human_event_with_options( + event: &ClientEvent, + stats: Option, + options: HumanOutputOptions, +) -> String { match event { ClientEvent::SessionStarted { remote, token, .. } => { format!("session started remote={remote} token={token:#x}") @@ -355,16 +372,19 @@ pub fn format_human_event(event: &ClientEvent, stats: Option) - received_stats, .. } => { - let mut out = format!( - "seq={seq} logical_seq={logical_seq} rtt={}", - format_duration(rtt.effective) - ); + let mut out = format!("seq={seq}"); + if options.verbose { + write!(out, " logical_seq={logical_seq}").unwrap(); + } + write!(out, " rtt={}", format_duration(rtt.effective)).unwrap(); write_human_one_way(&mut out, *one_way); write!(out, " ipdv={}", format_human_ipdv(stats)).unwrap(); if let Some(processing) = server_timing.and_then(|timing| timing.processing) { write!(out, " proc={}", format_duration(processing)).unwrap(); } - write_human_received_stats(&mut out, *received_stats); + if options.verbose { + write_human_received_stats(&mut out, *received_stats); + } out } ClientEvent::EchoLoss { @@ -1277,11 +1297,14 @@ mod tests { #[test] fn human_uses_readable_per_event_lines() { let line = format_event(&reply_event(), OutputMode::Human).unwrap(); - assert!(line.starts_with("seq=7 logical_seq=8 rtt=1.2ms")); + assert!(line.starts_with("seq=7 rtt=1.2ms")); assert!(line.contains("rd=500.0µs")); assert!(line.contains("sd=400.0µs")); assert!(line.contains("ipdv=n/a")); assert!(line.contains("proc=300.0µs")); + assert!(!line.contains("logical_seq=")); + assert!(!line.contains("server_received=")); + assert!(!line.contains("server_window=")); assert!(!line.contains("rtt_us=")); assert_ne!( line, @@ -1293,6 +1316,19 @@ mod tests { assert!(!OutputMode::RttUs.prints_summary()); } + #[test] + fn verbose_human_reply_includes_extra_fields() { + let line = format_human_event_with_options( + &reply_event(), + None, + HumanOutputOptions { verbose: true }, + ); + + assert!(line.contains("logical_seq=8")); + assert!(line.contains("server_received=9")); + assert!(line.contains("server_window=0x7")); + } + #[test] fn human_reply_uses_supplied_ipdv_update() { let line = format_human_event( diff --git a/crates/irtt-cli/src/main.rs b/crates/irtt-cli/src/main.rs index b290b94..202956b 100644 --- a/crates/irtt-cli/src/main.rs +++ b/crates/irtt-cli/src/main.rs @@ -10,7 +10,9 @@ use std::{ }; use clap::Parser; -use irtt_cli::{format_event, format_human_event, CliArgs, OutputMode}; +use irtt_cli::{ + format_event, format_human_event_with_options, CliArgs, HumanOutputOptions, OutputMode, +}; use irtt_client::{Client, ClientEvent, ClientTimestamp, OpenOutcome, RecvBudget}; #[cfg(feature = "stats")] use irtt_stats::{StatsCollector, StatsConfig}; @@ -51,6 +53,9 @@ fn run(args: CliArgs, shutdown_requested: &AtomicBool) -> Result<(), Box) { struct EventOutput<'a, W: Write> { mode: irtt_cli::OutputMode, + human_options: HumanOutputOptions, print_finite_summary: bool, out: &'a mut W, #[cfg(feature = "stats")] @@ -201,7 +207,11 @@ impl EventOutput<'_, W> { let line = if self.mode == OutputMode::Human && !matches!(event, ClientEvent::EchoSent { .. }) { - Some(format_human_event(event, Some(stats_update.into()))) + Some(format_human_event_with_options( + event, + Some(stats_update.into()), + self.human_options, + )) } else { format_event(event, self.mode) }; @@ -222,7 +232,12 @@ impl EventOutput<'_, W> { write!( self.out, "{}", - irtt_cli::summary::format_summary(&self.stats.summary()) + irtt_cli::summary::format_summary_with_options( + &self.stats.summary(), + irtt_cli::summary::SummaryFormatOptions { + verbose: self.human_options.verbose, + }, + ) )?; } @@ -364,6 +379,7 @@ mod tests { { let mut output = EventOutput { mode: irtt_cli::OutputMode::RttUs, + human_options: HumanOutputOptions::default(), print_finite_summary: true, out: &mut out, stats: &mut stats, @@ -386,6 +402,7 @@ mod tests { { let mut output = EventOutput { mode: irtt_cli::OutputMode::Human, + human_options: HumanOutputOptions::default(), print_finite_summary: false, out: &mut out, stats: &mut stats, @@ -404,6 +421,7 @@ mod tests { { let mut output = EventOutput { mode: irtt_cli::OutputMode::Human, + human_options: HumanOutputOptions::default(), print_finite_summary: false, out: &mut out, stats: &mut stats, diff --git a/crates/irtt-cli/src/summary.rs b/crates/irtt-cli/src/summary.rs index eda5fb5..9107b49 100644 --- a/crates/irtt-cli/src/summary.rs +++ b/crates/irtt-cli/src/summary.rs @@ -5,6 +5,18 @@ use irtt_stats::{ }; pub fn format_summary(summary: &FiniteSummary) -> String { + format_summary_with_options(summary, SummaryFormatOptions::default()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct SummaryFormatOptions { + pub verbose: bool, +} + +pub fn format_summary_with_options( + summary: &FiniteSummary, + options: SummaryFormatOptions, +) -> String { let mut out = String::new(); let packets = summary.packets; let loss = summary.loss; @@ -21,8 +33,10 @@ pub fn format_summary(summary: &FiniteSummary) -> String { writeln!(out, " {}", "-".repeat(82)).unwrap(); write_signed_duration_row(&mut out, "RTT", &summary.rtt.primary); - write_duration_row(&mut out, "raw RTT", &summary.rtt.raw); - write_signed_duration_row(&mut out, "adjusted RTT", &summary.rtt.adjusted); + if options.verbose { + write_duration_row(&mut out, "raw RTT", &summary.rtt.raw); + write_signed_duration_row(&mut out, "adjusted RTT", &summary.rtt.adjusted); + } write_duration_row(&mut out, "IPDV/jitter", &summary.ipdv.round_trip); write_duration_row(&mut out, "send IPDV", &summary.ipdv.send); write_duration_row(&mut out, "receive IPDV", &summary.ipdv.receive); @@ -257,6 +271,8 @@ mod tests { assert!(output.contains("bytes: sent=64 received=64")); assert!(output.contains("RTT")); assert!(output.contains("1.2ms")); + assert!(!output.contains("raw RTT")); + assert!(!output.contains("adjusted RTT")); assert!(output.contains("server processing")); assert!(output.contains("300.0µs")); assert!(output.contains("send call")); @@ -265,6 +281,39 @@ mod tests { assert!(output.contains("2.0µs")); } + #[test] + fn verbose_summary_includes_raw_and_adjusted_rtt_rows() { + let mut collector = StatsCollector::new(StatsConfig::finite()); + let received_at = test_timestamp(Duration::from_secs(1) + Duration::from_micros(1500)); + collector.process(&ClientEvent::EchoReply { + seq: 1, + logical_seq: 1, + remote: test_remote(), + sent_at: test_timestamp(Duration::from_secs(1)), + received_at, + rtt: RttSample { + raw: Duration::from_micros(1500), + adjusted: Some(Duration::from_micros(1200)), + effective: Duration::from_micros(1200), + adjusted_signed: Some(SignedDuration { ns: 1_200_000 }), + effective_signed: SignedDuration { ns: 1_200_000 }, + }, + server_timing: None, + one_way: None, + received_stats: None, + bytes: 64, + packet_meta: PacketMeta::default(), + }); + + let output = format_summary_with_options( + &collector.summary(), + SummaryFormatOptions { verbose: true }, + ); + + assert!(output.contains("raw RTT")); + assert!(output.contains("adjusted RTT")); + } + #[test] fn signed_stats_can_format_negative_values() { let stats = SignedDurationStatsWithMedian {