use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy)]
pub struct ProbeResult {
pub avg_latency: Duration,
pub samples: u32,
}
impl ProbeResult {
pub fn required_vus(&self, target_rps: u32, num_operations: u32) -> u32 {
let lat_secs = self.avg_latency.as_secs_f64().max(0.001);
let ops = num_operations.max(1) as f64;
let raw = (target_rps as f64 * ops * lat_secs).ceil() as u32;
raw.saturating_add(1).max(1)
}
}
pub async fn probe_target_latency(
target: &str,
samples: u32,
skip_tls_verify: bool,
) -> Option<ProbeResult> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(5))
.danger_accept_invalid_certs(skip_tls_verify)
.build()
.ok()?;
let mut total = Duration::ZERO;
let mut count: u32 = 0;
for _ in 0..samples {
let start = Instant::now();
let head = client.head(target).send().await;
let elapsed = match head {
Ok(_) => start.elapsed(),
Err(_) => {
let start = Instant::now();
match client.get(target).send().await {
Ok(_) => start.elapsed(),
Err(_) => continue,
}
}
};
total += elapsed;
count = count.saturating_add(1);
}
if count == 0 {
return None;
}
Some(ProbeResult {
avg_latency: total / count,
samples: count,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn required_vus_scales_with_latency() {
let fast = ProbeResult {
avg_latency: Duration::from_millis(2),
samples: 3,
};
assert_eq!(fast.required_vus(1000, 1), 3);
let slow = ProbeResult {
avg_latency: Duration::from_millis(100),
samples: 3,
};
assert_eq!(slow.required_vus(100, 1), 11);
}
#[test]
fn required_vus_scales_with_operation_count() {
let probe = ProbeResult {
avg_latency: Duration::from_millis(15),
samples: 3,
};
assert_eq!(probe.required_vus(100, 1), 3); assert_eq!(probe.required_vus(100, 12), 19); }
#[test]
fn required_vus_clamps_to_one() {
let fast = ProbeResult {
avg_latency: Duration::from_micros(50),
samples: 1,
};
assert!(fast.required_vus(1, 1) >= 1);
}
#[test]
fn required_vus_treats_zero_operations_as_one() {
let probe = ProbeResult {
avg_latency: Duration::from_millis(10),
samples: 1,
};
assert_eq!(probe.required_vus(100, 0), probe.required_vus(100, 1));
}
#[tokio::test]
async fn probe_returns_none_for_unreachable() {
let result = probe_target_latency("http://127.0.0.1:1/", 1, false).await;
assert!(result.is_none());
}
}