use anyhow::Result;
use std::time::Instant;
use super::PingResult;
use crate::utils::calculate_jitter;
const PING_SAMPLES: usize = 5;
pub async fn single_ping(url: &str) -> Result<f64> {
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, ORIGIN, REFERER, USER_AGENT};
let mut headers = HeaderMap::new();
headers.insert(USER_AGENT, HeaderValue::from_static("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"));
headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
if url.contains("cloudflare") {
headers.insert(
ORIGIN,
HeaderValue::from_static("https://speed.cloudflare.com"),
);
headers.insert(
REFERER,
HeaderValue::from_static("https://speed.cloudflare.com/"),
);
}
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.default_headers(headers)
.build()?;
let start = Instant::now();
let _ = client.head(url).send().await?;
Ok(start.elapsed().as_secs_f64() * 1000.0)
}
pub async fn measure_ping(url: &str) -> Result<PingResult> {
let mut samples = Vec::with_capacity(PING_SAMPLES);
let mut failures = 0;
for _ in 0..PING_SAMPLES {
match single_ping(url).await {
Ok(ms) => samples.push(ms),
Err(_) => failures += 1,
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
if samples.is_empty() {
anyhow::bail!("All ping attempts failed");
}
let min_ms = samples.iter().cloned().fold(f64::INFINITY, f64::min);
let max_ms = samples.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let avg_ms = samples.iter().sum::<f64>() / samples.len() as f64;
let jitter_ms = calculate_jitter(&samples);
let packet_loss = (failures as f64 / PING_SAMPLES as f64) * 100.0;
Ok(PingResult {
latency_ms: avg_ms,
jitter_ms,
min_ms,
max_ms,
packet_loss_percent: packet_loss,
samples,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_ping_google() {
let result = measure_ping("https://www.google.com").await;
assert!(result.is_ok());
}
}