feat(cli): quiet default human output
This commit is contained in:
1 parent
1d6310ec22
commit
8cdd5c63e8
3 files changed
+111
-8
No files matched your search
@@ -87,6 +87,10 @@ pub struct CliArgs {
|
|||||||
/// Output format: human, simple, machine, or rtt-us.
|
/// Output format: human, simple, machine, or rtt-us.
|
||||||
#[arg(long, value_enum, default_value_t = OutputMode::Human)]
|
#[arg(long, value_enum, default_value_t = OutputMode::Human)]
|
||||||
pub output: OutputMode,
|
pub output: OutputMode,
|
||||||
|
|
||||||
|
/// Include extra fields in human output.
|
||||||
|
#[arg(long)]
|
||||||
|
pub verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliArgs {
|
impl CliArgs {
|
||||||
@@ -322,6 +326,11 @@ pub struct HumanEventStats {
|
|||||||
pub receive_ipdv: Option<Duration>,
|
pub receive_ipdv: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub struct HumanOutputOptions {
|
||||||
|
pub verbose: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "stats")]
|
#[cfg(feature = "stats")]
|
||||||
impl From<irtt_stats::EventStatsUpdate> for HumanEventStats {
|
impl From<irtt_stats::EventStatsUpdate> for HumanEventStats {
|
||||||
fn from(value: irtt_stats::EventStatsUpdate) -> Self {
|
fn from(value: irtt_stats::EventStatsUpdate) -> Self {
|
||||||
@@ -335,6 +344,14 @@ impl From<irtt_stats::EventStatsUpdate> for HumanEventStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_human_event(event: &ClientEvent, stats: Option<HumanEventStats>) -> String {
|
pub fn format_human_event(event: &ClientEvent, stats: Option<HumanEventStats>) -> String {
|
||||||
|
format_human_event_with_options(event, stats, HumanOutputOptions::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_human_event_with_options(
|
||||||
|
event: &ClientEvent,
|
||||||
|
stats: Option<HumanEventStats>,
|
||||||
|
options: HumanOutputOptions,
|
||||||
|
) -> String {
|
||||||
match event {
|
match event {
|
||||||
ClientEvent::SessionStarted { remote, token, .. } => {
|
ClientEvent::SessionStarted { remote, token, .. } => {
|
||||||
format!("session started remote={remote} token={token:#x}")
|
format!("session started remote={remote} token={token:#x}")
|
||||||
@@ -355,16 +372,19 @@ pub fn format_human_event(event: &ClientEvent, stats: Option<HumanEventStats>) -
|
|||||||
received_stats,
|
received_stats,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let mut out = format!(
|
let mut out = format!("seq={seq}");
|
||||||
"seq={seq} logical_seq={logical_seq} rtt={}",
|
if options.verbose {
|
||||||
format_duration(rtt.effective)
|
write!(out, " logical_seq={logical_seq}").unwrap();
|
||||||
);
|
}
|
||||||
|
write!(out, " rtt={}", format_duration(rtt.effective)).unwrap();
|
||||||
write_human_one_way(&mut out, *one_way);
|
write_human_one_way(&mut out, *one_way);
|
||||||
write!(out, " ipdv={}", format_human_ipdv(stats)).unwrap();
|
write!(out, " ipdv={}", format_human_ipdv(stats)).unwrap();
|
||||||
if let Some(processing) = server_timing.and_then(|timing| timing.processing) {
|
if let Some(processing) = server_timing.and_then(|timing| timing.processing) {
|
||||||
write!(out, " proc={}", format_duration(processing)).unwrap();
|
write!(out, " proc={}", format_duration(processing)).unwrap();
|
||||||
}
|
}
|
||||||
|
if options.verbose {
|
||||||
write_human_received_stats(&mut out, *received_stats);
|
write_human_received_stats(&mut out, *received_stats);
|
||||||
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
ClientEvent::EchoLoss {
|
ClientEvent::EchoLoss {
|
||||||
@@ -1277,11 +1297,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn human_uses_readable_per_event_lines() {
|
fn human_uses_readable_per_event_lines() {
|
||||||
let line = format_event(&reply_event(), OutputMode::Human).unwrap();
|
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("rd=500.0µs"));
|
||||||
assert!(line.contains("sd=400.0µs"));
|
assert!(line.contains("sd=400.0µs"));
|
||||||
assert!(line.contains("ipdv=n/a"));
|
assert!(line.contains("ipdv=n/a"));
|
||||||
assert!(line.contains("proc=300.0µs"));
|
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!(!line.contains("rtt_us="));
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
line,
|
line,
|
||||||
@@ -1293,6 +1316,19 @@ mod tests {
|
|||||||
assert!(!OutputMode::RttUs.prints_summary());
|
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]
|
#[test]
|
||||||
fn human_reply_uses_supplied_ipdv_update() {
|
fn human_reply_uses_supplied_ipdv_update() {
|
||||||
let line = format_human_event(
|
let line = format_human_event(
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use clap::Parser;
|
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};
|
use irtt_client::{Client, ClientEvent, ClientTimestamp, OpenOutcome, RecvBudget};
|
||||||
#[cfg(feature = "stats")]
|
#[cfg(feature = "stats")]
|
||||||
use irtt_stats::{StatsCollector, StatsConfig};
|
use irtt_stats::{StatsCollector, StatsConfig};
|
||||||
@@ -51,6 +53,9 @@ fn run(args: CliArgs, shutdown_requested: &AtomicBool) -> Result<(), Box<dyn std
|
|||||||
let mut stats = StatsCollector::new(stats_config(continuous));
|
let mut stats = StatsCollector::new(stats_config(continuous));
|
||||||
let mut output = EventOutput {
|
let mut output = EventOutput {
|
||||||
mode,
|
mode,
|
||||||
|
human_options: HumanOutputOptions {
|
||||||
|
verbose: args.verbose,
|
||||||
|
},
|
||||||
print_finite_summary: !continuous,
|
print_finite_summary: !continuous,
|
||||||
out: &mut stdout,
|
out: &mut stdout,
|
||||||
#[cfg(feature = "stats")]
|
#[cfg(feature = "stats")]
|
||||||
@@ -178,6 +183,7 @@ fn sleep_until_next_send(deadline: Option<Instant>) {
|
|||||||
|
|
||||||
struct EventOutput<'a, W: Write> {
|
struct EventOutput<'a, W: Write> {
|
||||||
mode: irtt_cli::OutputMode,
|
mode: irtt_cli::OutputMode,
|
||||||
|
human_options: HumanOutputOptions,
|
||||||
print_finite_summary: bool,
|
print_finite_summary: bool,
|
||||||
out: &'a mut W,
|
out: &'a mut W,
|
||||||
#[cfg(feature = "stats")]
|
#[cfg(feature = "stats")]
|
||||||
@@ -201,7 +207,11 @@ impl<W: Write> EventOutput<'_, W> {
|
|||||||
|
|
||||||
let line =
|
let line =
|
||||||
if self.mode == OutputMode::Human && !matches!(event, ClientEvent::EchoSent { .. }) {
|
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 {
|
} else {
|
||||||
format_event(event, self.mode)
|
format_event(event, self.mode)
|
||||||
};
|
};
|
||||||
@@ -222,7 +232,12 @@ impl<W: Write> EventOutput<'_, W> {
|
|||||||
write!(
|
write!(
|
||||||
self.out,
|
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 {
|
let mut output = EventOutput {
|
||||||
mode: irtt_cli::OutputMode::RttUs,
|
mode: irtt_cli::OutputMode::RttUs,
|
||||||
|
human_options: HumanOutputOptions::default(),
|
||||||
print_finite_summary: true,
|
print_finite_summary: true,
|
||||||
out: &mut out,
|
out: &mut out,
|
||||||
stats: &mut stats,
|
stats: &mut stats,
|
||||||
@@ -386,6 +402,7 @@ mod tests {
|
|||||||
{
|
{
|
||||||
let mut output = EventOutput {
|
let mut output = EventOutput {
|
||||||
mode: irtt_cli::OutputMode::Human,
|
mode: irtt_cli::OutputMode::Human,
|
||||||
|
human_options: HumanOutputOptions::default(),
|
||||||
print_finite_summary: false,
|
print_finite_summary: false,
|
||||||
out: &mut out,
|
out: &mut out,
|
||||||
stats: &mut stats,
|
stats: &mut stats,
|
||||||
@@ -404,6 +421,7 @@ mod tests {
|
|||||||
{
|
{
|
||||||
let mut output = EventOutput {
|
let mut output = EventOutput {
|
||||||
mode: irtt_cli::OutputMode::Human,
|
mode: irtt_cli::OutputMode::Human,
|
||||||
|
human_options: HumanOutputOptions::default(),
|
||||||
print_finite_summary: false,
|
print_finite_summary: false,
|
||||||
out: &mut out,
|
out: &mut out,
|
||||||
stats: &mut stats,
|
stats: &mut stats,
|
||||||
|
|||||||
@@ -5,6 +5,18 @@ use irtt_stats::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn format_summary(summary: &FiniteSummary) -> String {
|
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 mut out = String::new();
|
||||||
let packets = summary.packets;
|
let packets = summary.packets;
|
||||||
let loss = summary.loss;
|
let loss = summary.loss;
|
||||||
@@ -21,8 +33,10 @@ pub fn format_summary(summary: &FiniteSummary) -> String {
|
|||||||
writeln!(out, " {}", "-".repeat(82)).unwrap();
|
writeln!(out, " {}", "-".repeat(82)).unwrap();
|
||||||
|
|
||||||
write_signed_duration_row(&mut out, "RTT", &summary.rtt.primary);
|
write_signed_duration_row(&mut out, "RTT", &summary.rtt.primary);
|
||||||
|
if options.verbose {
|
||||||
write_duration_row(&mut out, "raw RTT", &summary.rtt.raw);
|
write_duration_row(&mut out, "raw RTT", &summary.rtt.raw);
|
||||||
write_signed_duration_row(&mut out, "adjusted RTT", &summary.rtt.adjusted);
|
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, "IPDV/jitter", &summary.ipdv.round_trip);
|
||||||
write_duration_row(&mut out, "send IPDV", &summary.ipdv.send);
|
write_duration_row(&mut out, "send IPDV", &summary.ipdv.send);
|
||||||
write_duration_row(&mut out, "receive IPDV", &summary.ipdv.receive);
|
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("bytes: sent=64 received=64"));
|
||||||
assert!(output.contains("RTT"));
|
assert!(output.contains("RTT"));
|
||||||
assert!(output.contains("1.2ms"));
|
assert!(output.contains("1.2ms"));
|
||||||
|
assert!(!output.contains("raw RTT"));
|
||||||
|
assert!(!output.contains("adjusted RTT"));
|
||||||
assert!(output.contains("server processing"));
|
assert!(output.contains("server processing"));
|
||||||
assert!(output.contains("300.0µs"));
|
assert!(output.contains("300.0µs"));
|
||||||
assert!(output.contains("send call"));
|
assert!(output.contains("send call"));
|
||||||
@@ -265,6 +281,39 @@ mod tests {
|
|||||||
assert!(output.contains("2.0µs"));
|
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]
|
#[test]
|
||||||
fn signed_stats_can_format_negative_values() {
|
fn signed_stats_can_format_negative_values() {
|
||||||
let stats = SignedDurationStatsWithMedian {
|
let stats = SignedDurationStatsWithMedian {
|
||||||
|
|||||||
Reference in new issue
Block a user