use std::net::SocketAddr;
use crate::time::DurationNanos;
#[derive(Clone, Debug, clap::Subcommand)]
pub enum Qcmp {
Ping(Ping),
}
#[derive(clap::Args, Clone, Debug)]
pub struct Ping {
pub endpoint: SocketAddr,
#[clap(short, long, default_value_t = 5)]
pub amount: usize,
#[clap(short, long)]
pub interval: Option<crate::cli::Duration>,
#[clap(short, long, default_value = "1s")]
pub timeout: crate::cli::Duration,
}
impl Ping {
pub async fn run(&self) -> crate::Result<()> {
tracing::info!("starting ping task");
let mut results = Vec::new();
let qcmp_transceiver = std::sync::Arc::new(crate::codec::qcmp::QcmpTransceiver::new()?);
let mut ticker = self.interval.map(|d| tokio::time::interval(d.0));
for _ in 0..self.amount {
if let Some(ticker) = ticker.as_mut() {
let _ = ticker.tick().await;
}
let (recv_time, reply) = match qcmp_transceiver
.ping(self.endpoint, std::time::Duration::from_secs(5))
.await
{
Ok((recv_time, reply)) => (recv_time, reply),
Err(error) => {
tracing::error!(endpoint=%self.endpoint, ?error, "ping failed");
continue;
}
};
let delay = reply.round_trip_delay(recv_time).unwrap();
tracing::info!(delay_millis=%format!("{:.2}", delay.duration().as_secs_f64() * 1000.0), "successful ping");
results.push(delay);
}
match median(&mut results) {
Some(median) => {
let median = median.duration();
let average = std::time::Duration::from_nanos(
(results.iter().map(|dn| dn.nanos() as i128).sum::<i128>()
/ results.len() as i128) as u64,
);
tracing::info!(
median_millis=%format!("{:.2}", median.as_secs_f64() * 1000.0),
average_millis=%format!("{:.2}", average.as_secs_f64() * 1000.0),
attempts=%self.amount,
successful_attempts=%results.len(),
"final results"
);
}
None => {
eyre::bail!("no successful results");
}
}
Ok(())
}
}
fn median(numbers: &mut [DurationNanos]) -> Option<DurationNanos> {
let len = numbers.len();
if len == 0 {
return None;
}
numbers.sort();
if len % 2 == 1 {
Some(numbers[len / 2])
} else {
let mid1 = numbers[(len - 1) / 2];
let mid2 = numbers[len / 2];
Some(DurationNanos::from_nanos((mid1.nanos() + mid2.nanos()) / 2))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dn(nanos: i64) -> DurationNanos {
DurationNanos::from_nanos(nanos)
}
#[test]
fn empty() {
assert_eq!(median(&mut []), None);
}
#[test]
fn single() {
let dn = dn(42);
assert_eq!(median(&mut [dn]), Some(dn));
}
#[test]
fn odd() {
assert_eq!(median(&mut [dn(3), dn(1), dn(2)]), Some(dn(2)));
}
#[test]
fn even() {
assert_eq!(median(&mut [dn(4), dn(3), dn(1), dn(2)]), Some(dn(2)));
}
}