From c65ba48e1031e1450bc399122886b06c559a77bf Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Tue, 28 Apr 2026 23:06:33 +0700 Subject: [PATCH] feat(cli): implement main execution loop and network I/O logic --- crates/irtt-cli/src/main.rs | 118 +++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/crates/irtt-cli/src/main.rs b/crates/irtt-cli/src/main.rs index f328e4d..73b0c35 100644 --- a/crates/irtt-cli/src/main.rs +++ b/crates/irtt-cli/src/main.rs @@ -1 +1,117 @@ -fn main() {} +use std::{ + io::{self, Write}, + process::ExitCode, + thread, + time::{Duration, Instant}, +}; + +use clap::Parser; +use irtt_cli::{format_event, CliArgs}; +use irtt_client::{Client, ClientEvent, ClientTimestamp, OpenOutcome, RecvBudget}; + +const RECV_BUDGET: RecvBudget = RecvBudget { max_packets: 16 }; +const FINAL_DRAIN: Duration = Duration::from_millis(100); +const MAX_SLEEP: Duration = Duration::from_millis(20); + +fn main() -> ExitCode { + let args = CliArgs::parse(); + match run(args) { + Ok(()) => ExitCode::SUCCESS, + Err(err) => { + eprintln!("irtt-rs: {err}"); + ExitCode::FAILURE + } + } +} + +fn run(args: CliArgs) -> Result<(), Box> { + let mode = args.output; + let mut stdout = io::BufWriter::new(io::stdout().lock()); + let mut client = Client::connect(args.to_client_config())?; + + let open = client.open(ClientTimestamp::now())?; + print_event(open_event(&open), mode, &mut stdout)?; + + while let Some(deadline) = client.next_send_deadline() { + let now = Instant::now(); + if deadline <= now { + let events = client.send_probe()?; + print_events(&events, mode, &mut stdout)?; + } + + let events = client.recv_available(RECV_BUDGET)?; + print_events(&events, mode, &mut stdout)?; + + let events = client.poll_timeouts(ClientTimestamp::now())?; + print_events(&events, mode, &mut stdout)?; + + sleep_until_next_send(client.next_send_deadline()); + } + + drain_final_replies(&mut client, mode, &mut stdout)?; + + let events = client.poll_timeouts(ClientTimestamp::now())?; + print_events(&events, mode, &mut stdout)?; + + let events = client.close(ClientTimestamp::now())?; + print_events(&events, mode, &mut stdout)?; + stdout.flush()?; + Ok(()) +} + +fn open_event(outcome: &OpenOutcome) -> &ClientEvent { + match outcome { + OpenOutcome::Started { event, .. } | OpenOutcome::NoTestCompleted { event, .. } => event, + } +} + +fn drain_final_replies( + client: &mut Client, + mode: irtt_cli::OutputMode, + out: &mut W, +) -> Result<(), Box> { + let deadline = Instant::now() + FINAL_DRAIN; + while Instant::now() < deadline { + let events = client.recv_available(RECV_BUDGET)?; + if events.is_empty() { + thread::sleep(Duration::from_millis(5)); + } else { + print_events(&events, mode, out)?; + } + } + Ok(()) +} + +fn sleep_until_next_send(deadline: Option) { + let sleep_for = match deadline { + Some(deadline) => deadline + .saturating_duration_since(Instant::now()) + .min(MAX_SLEEP), + None => Duration::from_millis(1), + }; + if !sleep_for.is_zero() { + thread::sleep(sleep_for); + } +} + +fn print_events( + events: &[ClientEvent], + mode: irtt_cli::OutputMode, + out: &mut W, +) -> io::Result<()> { + for event in events { + print_event(event, mode, out)?; + } + Ok(()) +} + +fn print_event( + event: &ClientEvent, + mode: irtt_cli::OutputMode, + out: &mut W, +) -> io::Result<()> { + if let Some(line) = format_event(event, mode) { + writeln!(out, "{line}")?; + } + Ok(()) +}