#[cfg(feature = "stats")] use std::collections::BTreeMap; use std::{ collections::HashSet, io::{self, Write}, sync::atomic::AtomicBool, thread, time::{Duration, Instant}, }; use irtt_client::{ ClientEvent, EventSubscriptionError, ManagedClient, ManagedClientGroup, ManagedClientGroupConfig, ManagedGroupCompletionPolicy, ManagedGroupEndReason, ManagedGroupEvent, ManagedTargetOutcome, SessionEndReason, SubscriberConfig, SubscriberOverflow, TargetEvent, }; #[cfg(all(test, feature = "stats"))] use irtt_client::{ClientTimestamp, PacketMeta, RttSample, SignedDuration}; use super::{ args::{ClientArgs, ResolvedCliTarget}, output::{EventRenderStats, OutputConfig}, }; #[cfg(feature = "stats")] use crate::shared::client::expected_probe_count; use crate::shared::client::{ is_shutdown_requested, session::{ peer_close_run_error, request_group_stop_once, should_print_final_summary, should_stop_group_on_peer_close, }, }; #[cfg(feature = "stats")] use irtt_stats::{StatsCollector, StatsConfig}; #[cfg(feature = "stats")] const FINITE_STATS_BYTES_PER_PROBE: u64 = 500; #[cfg(feature = "stats")] const MIB: u64 = 1024 * 1024; #[cfg(feature = "stats")] const GIB: u64 = 1024 * MIB; #[cfg(feature = "stats")] const FINITE_STATS_MEMORY_WARNING_BYTES: u64 = 128 * MIB; #[cfg(feature = "stats")] const FINITE_STATS_MEMORY_STRONG_WARNING_BYTES: u64 = 512 * MIB; #[cfg(feature = "stats")] const FINITE_STATS_MEMORY_VERY_STRONG_WARNING_BYTES: u64 = GIB; const GROUP_IDLE_SLEEP: Duration = Duration::from_millis(5); const GROUP_COMPLETION_GRACE: Duration = Duration::from_secs(1); const MANAGED_EVENT_WAIT_SLICE: Duration = Duration::from_millis(20); const MANAGED_EVENT_CAPACITY: usize = 16_384; pub fn run_stream( args: ClientArgs, shutdown_requested: &AtomicBool, ) -> Result<(), Box> { if args.list_columns { print!("{}", OutputConfig::list_columns()); return Ok(()); } let output_config = OutputConfig::new( args.format, args.columns.as_deref(), args.header, args.verbose, args.target_specs() .map(|targets| targets.len() > 1) .unwrap_or(false), ) .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; let targets = args .resolved_managed_targets() .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; let multi_target = targets.len() > 1; let continuous = args.is_continuous(); #[cfg(feature = "stats")] if let Some(warning) = finite_stats_memory_warning(&args) { eprintln!("{warning}"); } if multi_target { return run_group_stream(args, targets, output_config, continuous, shutdown_requested); } let target_label = targets .first() .map(|target| target.label.as_str()) .expect("at least one target was validated"); let mut stdout = io::LineWriter::new(io::stdout().lock()); #[cfg(feature = "stats")] let mut stats = StatsCollector::new(stats_config(continuous)); let mut stream_output = StreamOutput { config: output_config, header_printed: false, print_final_summary: false, show_running_only_summary_note: false, out: &mut stdout, }; if is_shutdown_requested(shutdown_requested) { return Ok(()); } let (session, events) = ManagedClient::start_with_subscription( args.to_client_config(), managed_event_subscriber_config(), )?; let mut interrupted = false; loop { if is_shutdown_requested(shutdown_requested) { interrupted = true; session.stop(); break; } match events.recv_timeout(MANAGED_EVENT_WAIT_SLICE) { Ok(Some(event)) => { #[cfg(feature = "stats")] print_events_with_stats( &mut stream_output, std::slice::from_ref(&event), Some(target_label), &mut stats, )?; #[cfg(not(feature = "stats"))] print_events_with_stats( &mut stream_output, std::slice::from_ref(&event), Some(target_label), )?; } Ok(None) => {} Err(EventSubscriptionError::Disconnected) => break, } } interrupted |= is_shutdown_requested(shutdown_requested); if interrupted { eprintln!("interrupted, closing session..."); drain_single_client_events( &events, &mut stream_output, target_label, #[cfg(feature = "stats")] &mut stats, )?; } let outcome = session.join(); drain_single_client_events( &events, &mut stream_output, target_label, #[cfg(feature = "stats")] &mut stats, )?; if let Some(warning) = dropped_event_warning(events.dropped_events(), "managed client") { eprintln!("{warning}"); } interrupted |= is_shutdown_requested(shutdown_requested); let peer_close_error = outcome.as_ref().ok().and_then(|outcome| { peer_close_run_error( continuous, interrupted, u64::from(outcome.end_reason == SessionEndReason::PeerClosed), ) }); let print_final_summary = should_print_final_summary(continuous, interrupted); stream_output.print_final_summary = print_final_summary; stream_output.show_running_only_summary_note = continuous && interrupted && print_final_summary; #[cfg(feature = "stats")] stream_output.print_summary(&stats)?; #[cfg(not(feature = "stats"))] stream_output.print_summary()?; stream_output.out.flush()?; if let Err(error) = outcome { return Err(error.into()); } if let Some(error) = peer_close_error { return Err(error.into()); } Ok(()) } fn managed_event_subscriber_config() -> SubscriberConfig { SubscriberConfig { capacity: MANAGED_EVENT_CAPACITY, // CLI output is best-effort under sustained backpressure. Dropping // stale rows keeps the managed worker independent from output speed, // while dropped_events() makes incomplete output visible. overflow: SubscriberOverflow::DropOldest, } } #[cfg(feature = "stats")] fn drain_single_client_events( events: &irtt_client::EventSubscription, stream_output: &mut StreamOutput<'_, W>, target_label: &str, stats: &mut StatsCollector, ) -> io::Result<()> { while let Ok(Some(event)) = events.try_recv() { print_events_with_stats( stream_output, std::slice::from_ref(&event), Some(target_label), stats, )?; } Ok(()) } #[cfg(not(feature = "stats"))] fn drain_single_client_events( events: &irtt_client::EventSubscription, stream_output: &mut StreamOutput<'_, W>, target_label: &str, ) -> io::Result<()> { while let Ok(Some(event)) = events.try_recv() { print_events_with_stats( stream_output, std::slice::from_ref(&event), Some(target_label), )?; } Ok(()) } struct StreamOutput<'a, W: Write> { config: OutputConfig, header_printed: bool, print_final_summary: bool, show_running_only_summary_note: bool, out: &'a mut W, } impl StreamOutput<'_, W> { fn print_events( &mut self, events: &[ClientEvent], target: Option<&str>, stats_updates: &[EventRenderStats], ) -> io::Result<()> { self.print_header()?; for (event, stats_update) in events.iter().zip(stats_updates) { self.print_event(event, target, stats_update)?; } Ok(()) } fn print_event( &mut self, event: &ClientEvent, target: Option<&str>, stats_update: &EventRenderStats, ) -> io::Result<()> { if let Some(line) = self.config.render_event(event, target, Some(stats_update)) { writeln!(self.out, "{line}")?; } Ok(()) } fn print_header(&mut self) -> io::Result<()> { if self.header_printed { return Ok(()); } self.header_printed = true; if let Some(header) = self.config.render_header() { writeln!(self.out, "{header}")?; } Ok(()) } #[cfg(feature = "stats")] fn print_summary(&mut self, stats: &StatsCollector) -> io::Result<()> { if !self.print_final_summary || !self.config.prints_summary() { return Ok(()); } write!( self.out, "{}", crate::cmd::client::summary::format_summary_with_options( &stats.snapshot(), crate::cmd::client::summary::SummaryFormatOptions { verbose: self.config.summary_verbose(), show_running_only_note: self.show_running_only_summary_note, }, ) )?; Ok(()) } #[cfg(not(feature = "stats"))] fn print_summary(&mut self) -> io::Result<()> { Ok(()) } } #[cfg(feature = "stats")] fn print_events_with_stats( stream_output: &mut StreamOutput<'_, W>, events: &[ClientEvent], target: Option<&str>, stats: &mut StatsCollector, ) -> io::Result<()> { let stats_updates = events .iter() .map(|event| EventRenderStats::from(stats.process(event))) .collect::>(); stream_output.print_events(events, target, &stats_updates) } #[cfg(not(feature = "stats"))] fn print_events_with_stats( stream_output: &mut StreamOutput<'_, W>, events: &[ClientEvent], target: Option<&str>, ) -> io::Result<()> { let stats_updates = vec![EventRenderStats::default(); events.len()]; stream_output.print_events(events, target, &stats_updates) } fn run_group_stream( args: ClientArgs, targets: Vec, output_config: OutputConfig, continuous: bool, shutdown_requested: &AtomicBool, ) -> Result<(), Box> { let mut config = args.to_client_config(); let managed_targets = targets .iter() .map(|target| target.managed.clone()) .collect::>(); let expected_target_count = managed_targets.len(); if let Some(first) = managed_targets.first() { config.server_addr = first.remote.to_string(); } let group_config = ManagedClientGroupConfig { client: config, pacing: args.pacing.into(), completion: ManagedGroupCompletionPolicy::AllTargetsComplete, }; let (session, events) = ManagedClientGroup::start_with_subscription( group_config, managed_targets, SubscriberConfig { capacity: MANAGED_EVENT_CAPACITY, // CLI output is best-effort under sustained backpressure. Dropping // stale rows keeps continuous runs attached to the running group // instead of turning a full output queue into a disconnected // subscriber and a potentially blocking join. overflow: SubscriberOverflow::DropOldest, }, )?; let mut stdout = io::LineWriter::new(io::stdout().lock()); #[cfg(feature = "stats")] let mut stats = targets .iter() .map(|target| { ( target.label.clone(), StatsCollector::new(stats_config(continuous)), ) }) .collect::>(); let mut stream_output = StreamOutput { config: output_config, header_printed: false, print_final_summary: false, show_running_only_summary_note: false, out: &mut stdout, }; let mut interrupted = false; let mut stop_requested = false; let mut peer_close_requested_stop = false; let mut terminal_targets = HashSet::new(); let mut last_event_at = Instant::now(); let mut saw_target_event = false; loop { if is_shutdown_requested(shutdown_requested) { interrupted = true; if request_group_stop_once(&mut stop_requested) { session.stop(); } } match events.try_recv() { Ok(Some(group_event)) => { last_event_at = Instant::now(); saw_target_event = true; match group_event { ManagedGroupEvent::Client(target_event) => { #[cfg(feature = "stats")] { let label = target_event.target.as_str(); let stats = stats .entry(label.to_owned()) .or_insert_with(|| StatsCollector::new(stats_config(continuous))); print_target_event_with_stats( &mut stream_output, &target_event, stats, )?; } #[cfg(not(feature = "stats"))] { print_target_event_with_stats(&mut stream_output, &target_event)?; } } ManagedGroupEvent::TargetFinished(target) => { report_target_failure(&target); terminal_targets.insert(target.id.as_str().to_owned()); if should_stop_group_on_peer_close( continuous, interrupted, &target.end_reason, ) { peer_close_requested_stop = true; if request_group_stop_once(&mut stop_requested) { session.stop(); } } } } } Ok(None) => { if interrupted { break; } if terminal_targets.len() >= expected_target_count { break; } if should_join_group_after_idle(&args, continuous, saw_target_event, last_event_at) { break; } thread::sleep(GROUP_IDLE_SLEEP); } Err(EventSubscriptionError::Disconnected) => break, } } if interrupted { eprintln!("interrupted, closing group..."); } let outcome = session.join()?; while let Ok(Some(group_event)) = events.try_recv() { match group_event { ManagedGroupEvent::Client(target_event) => { #[cfg(feature = "stats")] { let label = target_event.target.as_str(); let stats = stats .entry(label.to_owned()) .or_insert_with(|| StatsCollector::new(stats_config(continuous))); print_target_event_with_stats(&mut stream_output, &target_event, stats)?; } #[cfg(not(feature = "stats"))] { print_target_event_with_stats(&mut stream_output, &target_event)?; } } ManagedGroupEvent::TargetFinished(target) => { if terminal_targets.insert(target.id.as_str().to_owned()) { report_target_failure(&target); } } } } if let Some(warning) = dropped_event_warning(events.dropped_events(), "managed group") { eprintln!("{warning}"); } interrupted |= is_shutdown_requested(shutdown_requested); for target in &outcome.targets { if terminal_targets.insert(target.id.as_str().to_owned()) { report_target_failure(target); } } let successful_targets = outcome.successful_target_outcomes; let failed_targets = outcome.failed_target_outcomes; let peer_closed_target_outcomes = outcome .peer_closed_target_outcomes .max(u64::from(peer_close_requested_stop)); let terminal_error = if let Some(error) = peer_close_run_error(continuous, interrupted, peer_closed_target_outcomes) { Some(error) } else if outcome.end_reason == ManagedGroupEndReason::Cancelled && !interrupted && !peer_close_requested_stop { Some("managed client group was cancelled".to_owned()) } else if !interrupted && successful_targets == 0 && failed_targets > 0 { Some(format!( "no managed target completed successfully ({failed_targets} failed)" )) } else { None }; let print_final_summary = should_print_final_summary(continuous, interrupted); stream_output.print_final_summary = print_final_summary; stream_output.show_running_only_summary_note = continuous && interrupted && print_final_summary; #[cfg(feature = "stats")] { for (label, stats) in &stats { if stream_output.print_final_summary && stream_output.config.prints_summary() { writeln!(stream_output.out)?; writeln!(stream_output.out, "target: {label}")?; } stream_output.print_summary(stats)?; } } #[cfg(not(feature = "stats"))] { stream_output.print_summary()?; } stream_output.out.flush()?; if let Some(error) = terminal_error { return Err(error.into()); } Ok(()) } fn report_target_failure(target: &ManagedTargetOutcome) { if let Some(failure) = target.end_reason.failure() { eprintln!( "irtt-rs: target {} failed ({}): {}", target.id, failure.kind, failure.message ); } } fn dropped_event_warning(dropped_events: u64, source: &str) -> Option { (dropped_events > 0).then(|| { let event_word = if dropped_events == 1 { "event" } else { "events" }; format!( "irtt-rs: warning: dropped {dropped_events} {source} {event_word}; output and statistics may be incomplete" ) }) } fn estimated_group_completion_grace(args: &ClientArgs) -> Duration { let open_timeout: Duration = args.to_client_config().open_timeouts.iter().sum(); open_timeout .saturating_add(args.duration) .saturating_add(GROUP_COMPLETION_GRACE) } fn should_join_group_after_idle( args: &ClientArgs, continuous: bool, saw_target_event: bool, last_event_at: Instant, ) -> bool { if continuous && saw_target_event { return false; } last_event_at.elapsed() > estimated_group_completion_grace(args) } #[cfg(feature = "stats")] fn print_target_event_with_stats( stream_output: &mut StreamOutput<'_, W>, target_event: &TargetEvent, stats: &mut StatsCollector, ) -> io::Result<()> { print_events_with_stats( stream_output, std::slice::from_ref(&target_event.event), Some(target_event.target.as_str()), stats, ) } #[cfg(not(feature = "stats"))] fn print_target_event_with_stats( stream_output: &mut StreamOutput<'_, W>, target_event: &TargetEvent, ) -> io::Result<()> { print_events_with_stats( stream_output, std::slice::from_ref(&target_event.event), Some(target_event.target.as_str()), ) } #[cfg(feature = "stats")] fn stats_config(continuous: bool) -> StatsConfig { if continuous { StatsConfig::continuous() } else { StatsConfig::finite() } } #[cfg(feature = "stats")] fn estimate_finite_stats_memory_bytes(expected_probes: u64) -> u64 { expected_probes.saturating_mul(FINITE_STATS_BYTES_PER_PROBE) } #[cfg(feature = "stats")] fn finite_stats_memory_warning(args: &ClientArgs) -> Option { if args.is_continuous() || args.duration.is_zero() { return None; } let expected_probes = expected_probe_count(args.duration, args.interval); let estimated_bytes = estimate_finite_stats_memory_bytes(expected_probes); if estimated_bytes < FINITE_STATS_MEMORY_WARNING_BYTES { return None; } let formatted = format_bytes_for_warning(estimated_bytes); let guidance = if estimated_bytes >= FINITE_STATS_MEMORY_VERY_STRONG_WARNING_BYTES { "this may be unsuitable on memory-constrained systems" } else if estimated_bytes >= FINITE_STATS_MEMORY_STRONG_WARNING_BYTES { "consider shortening the run, increasing the interval, or using continuous mode" } else { "use continuous mode for bounded-memory long-running tests" }; Some(format!( "irtt-rs: warning: finite exact statistics may retain about {formatted} for this run; {guidance}" )) } #[cfg(feature = "stats")] fn format_bytes_for_warning(bytes: u64) -> String { if bytes >= GIB { format!("{} GiB", bytes.saturating_add(GIB / 2) / GIB) } else { format!("{} MiB", bytes.saturating_add(MIB / 2) / MIB) } } #[cfg(all(test, feature = "stats"))] mod tests { use super::*; use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, time::{Duration, Instant, UNIX_EPOCH}, }; fn test_timestamp(offset: Duration) -> ClientTimestamp { ClientTimestamp { wall: UNIX_EPOCH + offset, mono: Instant::now() + offset, } } fn test_remote() -> SocketAddr { SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 2112) } fn reply_event(seq: u32, rtt_us: u64) -> ClientEvent { let sent_at = test_timestamp(Duration::from_millis(seq as u64)); let received_at = ClientTimestamp { wall: sent_at.wall + Duration::from_micros(rtt_us), mono: sent_at.mono + Duration::from_micros(rtt_us), }; ClientEvent::EchoReply { seq, remote: test_remote(), sent_at, received_at, rtt: RttSample { raw: Duration::from_micros(rtt_us), adjusted: None, effective: SignedDuration::from_nanos(i128::from(rtt_us) * 1_000), }, server_timing: None, one_way: None, received_stats: None, bytes: 64, packet_meta: PacketMeta::default(), } } fn cli_args(args: &[&str]) -> ClientArgs { let mut argv = vec!["irtt-rs"]; argv.extend_from_slice(args); ::try_parse_from(argv).unwrap() } fn output_config( format: crate::cmd::client::OutputFormat, columns: Option<&str>, header: crate::cmd::client::HeaderMode, verbose: bool, ) -> OutputConfig { OutputConfig::new(format, columns, header, verbose, false).unwrap() } #[test] fn dropped_event_warning_marks_statistics_incomplete() { assert_eq!(dropped_event_warning(0, "managed client"), None); let warning = dropped_event_warning(7, "managed group").unwrap(); assert!(warning.contains("dropped 7 managed group events")); assert!(warning.contains("statistics may be incomplete")); } #[test] fn single_client_drain_preserves_final_queue_and_surfaces_overflow() { let hub = irtt_client::EventHub::new(); let events = hub .subscribe(SubscriberConfig { capacity: 1, overflow: SubscriberOverflow::DropOldest, }) .unwrap(); hub.publish(reply_event(1, 1_000)); hub.publish(reply_event(2, 2_000)); hub.disconnect_all(); let mut stats = StatsCollector::new(StatsConfig::finite()); let mut out = Vec::new(); { let mut stream_output = StreamOutput { config: output_config( crate::cmd::client::OutputFormat::Csv, Some("event,seq"), crate::cmd::client::HeaderMode::Never, false, ), header_printed: false, print_final_summary: false, show_running_only_summary_note: false, out: &mut out, }; drain_single_client_events(&events, &mut stream_output, "target", &mut stats).unwrap(); } let rendered = String::from_utf8(out).unwrap(); assert_eq!(rendered.trim(), "echo_reply,2"); assert_eq!(events.dropped_events(), 1); assert!( dropped_event_warning(events.dropped_events(), "managed client") .unwrap() .contains("dropped 1 managed client event") ); assert_eq!( events.try_recv().unwrap_err(), EventSubscriptionError::Disconnected ); } #[test] fn output_helper_streams_and_collects_events() { let sent_at = test_timestamp(Duration::from_secs(1)); let received_at = test_timestamp(Duration::from_secs(1) + Duration::from_micros(1200)); let events = [ ClientEvent::EchoSent { seq: 1, remote: test_remote(), scheduled_at: sent_at.mono, sent_at, bytes: 64, send_call: Duration::from_micros(10), timer_error: Duration::ZERO, }, ClientEvent::EchoReply { seq: 1, remote: test_remote(), sent_at, received_at, rtt: RttSample { raw: Duration::from_micros(1200), adjusted: None, effective: SignedDuration::from_nanos(1_200_000), }, server_timing: None, one_way: None, received_stats: None, bytes: 64, packet_meta: PacketMeta::default(), }, ]; let mut stats = StatsCollector::new(StatsConfig::finite()); let mut out = Vec::new(); { let mut stream_output = StreamOutput { config: output_config( crate::cmd::client::OutputFormat::Tsv, Some("effective_rtt_us"), crate::cmd::client::HeaderMode::Never, false, ), header_printed: false, print_final_summary: true, show_running_only_summary_note: false, out: &mut out, }; print_events_with_stats(&mut stream_output, &events, None, &mut stats).unwrap(); stream_output.print_summary(&stats).unwrap(); } let rendered = String::from_utf8(out).unwrap(); let summary = stats.snapshot(); assert!(!rendered.is_empty()); assert_eq!(summary.packets.packets_sent, 1); assert_eq!(summary.packets.unique_replies, 1); } #[test] fn continuous_summary_prints_when_enabled_and_suppresses_when_disabled() { let mut stats = StatsCollector::new(StatsConfig::continuous()); let mut out = Vec::new(); { let mut stream_output = StreamOutput { config: output_config( crate::cmd::client::OutputFormat::Table, None, crate::cmd::client::HeaderMode::Auto, false, ), header_printed: false, print_final_summary: true, show_running_only_summary_note: true, out: &mut out, }; print_events_with_stats( &mut stream_output, &[reply_event(1, 1200)], None, &mut stats, ) .unwrap(); stream_output.print_summary(&stats).unwrap(); } let rendered = String::from_utf8(out).unwrap(); assert!(rendered.contains("irtt-rs summary")); assert!(rendered.contains("medians unavailable")); assert!(rendered.contains("continuous mode")); assert!(rendered.contains("packets:")); assert!(rendered.contains("received=1")); let stats = StatsCollector::new(StatsConfig::continuous()); let mut out = Vec::new(); { let mut stream_output = StreamOutput { config: output_config( crate::cmd::client::OutputFormat::Table, None, crate::cmd::client::HeaderMode::Auto, false, ), header_printed: false, print_final_summary: false, show_running_only_summary_note: true, out: &mut out, }; stream_output.print_summary(&stats).unwrap(); } assert!(out.is_empty()); } #[test] fn continuous_mode_uses_continuous_stats_config() { let mut collector = StatsCollector::new(stats_config(true)); for seq in 0..5000_u32 { let sent_at = test_timestamp(Duration::from_micros(seq as u64)); let received_at = test_timestamp(Duration::from_micros(seq as u64 + 1)); collector.process(&ClientEvent::EchoReply { seq, remote: test_remote(), sent_at, received_at, rtt: RttSample { raw: Duration::from_micros(1), adjusted: None, effective: SignedDuration::from_nanos(1_000), }, server_timing: None, one_way: None, received_stats: None, bytes: 64, packet_meta: PacketMeta::default(), }); } assert_eq!(collector.snapshot().rtt.primary.median_ns, None); } #[test] fn finite_stats_memory_warning_reports_only_large_finite_runs() { for args in [ cli_args(&["--duration", "0", "--interval", "1ms", "127.0.0.1:2112"]), cli_args(&[ "--duration", "1000ms", "--interval", "1ms", "127.0.0.1:2112", ]), ] { assert_eq!(finite_stats_memory_warning(&args), None); } for (threshold, size, guidance) in [ ( FINITE_STATS_MEMORY_WARNING_BYTES, "about 128 MiB", "bounded-memory long-running tests", ), ( FINITE_STATS_MEMORY_STRONG_WARNING_BYTES, "about 512 MiB", "shortening the run", ), ( FINITE_STATS_MEMORY_VERY_STRONG_WARNING_BYTES, "about 1 GiB", "memory-constrained systems", ), ] { let expected_probes = threshold.div_ceil(FINITE_STATS_BYTES_PER_PROBE); let args = cli_args(&[ "--duration", &format!("{expected_probes}ms"), "--interval", "1ms", "127.0.0.1:2112", ]); let warning = finite_stats_memory_warning(&args).unwrap(); assert!(warning.contains(size)); assert!(warning.contains(guidance)); } } #[test] fn expected_probe_count_rounds_up() { assert_eq!( expected_probe_count(Duration::from_millis(1001), Duration::from_secs(1)), 2 ); } #[test] fn estimate_finite_stats_memory_bytes_saturates() { assert_eq!(estimate_finite_stats_memory_bytes(u64::MAX), u64::MAX); } }