use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use tokio::sync::Mutex;
pub(crate) const PARK_AFTER_FAILURES: u64 = 3;
const MIN_PARK_DURATION: Duration = Duration::from_secs(5);
const MAX_PARK_DURATION: Duration = Duration::from_secs(60);
fn park_duration(consecutive_failures: u64) -> Duration {
let doublings = consecutive_failures
.saturating_sub(PARK_AFTER_FAILURES)
.min(16) as u32;
MIN_PARK_DURATION
.saturating_mul(1 << doublings)
.min(MAX_PARK_DURATION)
}
#[derive(Debug, Clone)]
pub struct ResolverStats {
pub avg_response_time_ms: f64,
pub success_count: u64,
pub failure_count: u64,
pub timeout_count: u64,
pub consecutive_failures: u64,
pub last_used: SystemTime,
weight_factor: f64,
parked_until: Option<Instant>,
}
impl ResolverStats {
pub fn new() -> Self {
Self {
avg_response_time_ms: 0.0,
success_count: 0,
failure_count: 0,
timeout_count: 0,
consecutive_failures: 0,
last_used: SystemTime::now(),
weight_factor: 0.2, parked_until: None,
}
}
pub fn update_response_time(&mut self, response_time: Duration) {
let response_time_ms = response_time.as_nanos() as f64 / 1_000_000.0;
if self.avg_response_time_ms == 0.0 {
self.avg_response_time_ms = response_time_ms;
} else {
self.avg_response_time_ms = (1.0 - self.weight_factor) * self.avg_response_time_ms
+ self.weight_factor * response_time_ms;
}
}
pub fn record_success(&mut self, response_time: Duration) {
self.success_count = self.success_count.saturating_add(1);
self.clear_failures();
self.update_response_time(response_time);
}
pub fn record_failure(&mut self) {
self.failure_count = self.failure_count.saturating_add(1);
self.add_failure();
}
pub fn record_timeout(&mut self) {
self.timeout_count = self.timeout_count.saturating_add(1);
self.add_failure();
}
pub fn clear_failures(&mut self) {
self.consecutive_failures = 0;
self.parked_until = None;
self.last_used = SystemTime::now();
}
fn add_failure(&mut self) {
self.consecutive_failures = self.consecutive_failures.saturating_add(1);
self.last_used = SystemTime::now();
if self.consecutive_failures >= PARK_AFTER_FAILURES {
self.parked_until = Some(Instant::now() + park_duration(self.consecutive_failures));
}
}
fn is_parked(&self) -> bool {
self.parked_until
.is_some_and(|until| Instant::now() < until)
}
fn record_query_sent(&mut self) {
if self.parked_until.is_some() && !self.is_parked() {
self.parked_until = Some(Instant::now() + park_duration(self.consecutive_failures));
}
}
}
impl Default for ResolverStats {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GlobalStats {
pub total_queries: u64,
pub total_successful: u64,
pub total_failed: u64,
pub total_timeouts: u64,
pub client_queries: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub udp_receive_errors: u64,
pub tcp_accept_errors: u64,
pub resolver_stats: HashMap<SocketAddr, ResolverStats>,
}
impl GlobalStats {
pub fn new() -> Self {
Self {
total_queries: 0,
total_successful: 0,
total_failed: 0,
total_timeouts: 0,
client_queries: 0,
cache_hits: 0,
cache_misses: 0,
udp_receive_errors: 0,
tcp_accept_errors: 0,
resolver_stats: HashMap::new(),
}
}
pub fn record_success(&mut self, resolver: SocketAddr, response_time: Duration) {
self.total_queries = self.total_queries.saturating_add(1);
self.total_successful = self.total_successful.saturating_add(1);
let stats = self.resolver_stats.entry(resolver).or_default();
stats.record_success(response_time);
}
pub fn record_failure(&mut self, resolver: SocketAddr) {
self.total_queries = self.total_queries.saturating_add(1);
self.total_failed = self.total_failed.saturating_add(1);
let stats = self.resolver_stats.entry(resolver).or_default();
stats.record_failure();
}
pub fn record_timeout(&mut self, resolver: SocketAddr) {
self.total_queries = self.total_queries.saturating_add(1);
self.total_timeouts = self.total_timeouts.saturating_add(1);
let stats = self.resolver_stats.entry(resolver).or_default();
stats.record_timeout();
}
pub fn clear_failures(&mut self, resolver: SocketAddr) {
if let Some(stats) = self.resolver_stats.get_mut(&resolver) {
stats.clear_failures();
}
}
pub fn record_query_sent(&mut self, resolver: SocketAddr) {
if let Some(stats) = self.resolver_stats.get_mut(&resolver) {
stats.record_query_sent();
}
}
pub fn get_resolver_stats(&self, resolver: &SocketAddr) -> Option<&ResolverStats> {
self.resolver_stats.get(resolver)
}
pub fn record_client_query(&mut self) {
self.client_queries = self.client_queries.saturating_add(1);
}
pub fn record_cache_hit(&mut self) {
self.cache_hits = self.cache_hits.saturating_add(1);
}
pub fn record_cache_miss(&mut self) {
self.cache_misses = self.cache_misses.saturating_add(1);
}
pub fn increment_udp_receive_errors(&mut self) {
self.udp_receive_errors = self.udp_receive_errors.saturating_add(1);
}
pub fn increment_tcp_accept_errors(&mut self) {
self.tcp_accept_errors = self.tcp_accept_errors.saturating_add(1);
}
pub fn get_resolvers_by_speed(&self) -> Vec<(SocketAddr, f64)> {
let mut resolvers: Vec<(SocketAddr, f64)> = self
.resolver_stats
.iter()
.filter(|(_, stats)| stats.success_count > 0 && !stats.is_parked())
.map(|(addr, stats)| (*addr, stats.avg_response_time_ms))
.collect();
resolvers.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
resolvers
}
}
impl Default for GlobalStats {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SharedStats {
inner: Arc<Mutex<GlobalStats>>,
}
impl SharedStats {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(GlobalStats::new())),
}
}
pub async fn record_success(&self, resolver: SocketAddr, response_time: Duration) {
let mut stats = self.inner.lock().await;
stats.record_success(resolver, response_time);
}
pub async fn record_failure(&self, resolver: SocketAddr) {
let mut stats = self.inner.lock().await;
stats.record_failure(resolver);
}
pub async fn record_timeout(&self, resolver: SocketAddr) {
let mut stats = self.inner.lock().await;
stats.record_timeout(resolver);
}
pub async fn clear_failures(&self, resolver: SocketAddr) {
let mut stats = self.inner.lock().await;
stats.clear_failures(resolver);
}
pub async fn record_query_sent(&self, resolver: SocketAddr) {
let mut stats = self.inner.lock().await;
stats.record_query_sent(resolver);
}
pub async fn get_stats(&self) -> GlobalStats {
let stats = self.inner.lock().await;
stats.clone()
}
pub async fn record_client_query(&self) {
let mut stats = self.inner.lock().await;
stats.record_client_query();
}
pub async fn record_cache_hit(&self) {
let mut stats = self.inner.lock().await;
stats.record_cache_hit();
}
pub async fn record_cache_miss(&self) {
let mut stats = self.inner.lock().await;
stats.record_cache_miss();
}
pub async fn get_resolvers_by_speed(&self) -> Vec<(SocketAddr, f64)> {
let stats = self.inner.lock().await;
stats.get_resolvers_by_speed()
}
pub async fn increment_udp_receive_errors(&self) {
let mut stats = self.inner.lock().await;
stats.increment_udp_receive_errors();
}
pub async fn increment_tcp_accept_errors(&self) {
let mut stats = self.inner.lock().await;
stats.increment_tcp_accept_errors();
}
}
impl Default for SharedStats {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stats_counter_saturation() {
let mut stats = GlobalStats::new();
stats.total_queries = u64::MAX - 1;
stats.cache_hits = u64::MAX - 1;
stats.record_client_query();
stats.record_cache_hit();
assert_eq!(stats.client_queries, 1);
assert_eq!(stats.cache_hits, u64::MAX);
stats.record_cache_hit();
assert_eq!(stats.cache_hits, u64::MAX);
}
#[test]
fn test_resolver_stats_saturation() {
let mut stats = ResolverStats::new();
stats.success_count = u64::MAX - 1;
stats.failure_count = u64::MAX - 1;
stats.timeout_count = u64::MAX - 1;
stats.record_success(Duration::from_secs(1));
stats.record_failure();
stats.record_timeout();
assert_eq!(stats.success_count, u64::MAX);
assert_eq!(stats.failure_count, u64::MAX);
assert_eq!(stats.timeout_count, u64::MAX);
}
#[test]
fn test_sub_millisecond_samples_are_averaged() {
let mut stats = ResolverStats::new();
for _ in 0..100 {
stats.record_success(Duration::from_micros(400));
}
stats.record_success(Duration::from_millis(100));
assert!((stats.avg_response_time_ms - 20.32).abs() < 0.01);
}
#[test]
fn test_failing_resolver_is_parked_for_a_while() {
let mut stats = GlobalStats::new();
let fast: SocketAddr = "192.0.2.1:53".parse().unwrap();
let slow: SocketAddr = "192.0.2.2:53".parse().unwrap();
stats.record_success(fast, Duration::from_millis(10));
stats.record_success(slow, Duration::from_millis(50));
let without_fast = vec![(slow, 50.0)];
for _ in 1..PARK_AFTER_FAILURES {
stats.record_timeout(fast);
}
assert_eq!(stats.get_resolvers_by_speed().len(), 2);
stats.record_timeout(fast);
assert_eq!(stats.get_resolvers_by_speed(), without_fast);
stats.resolver_stats.get_mut(&fast).unwrap().parked_until = Some(Instant::now());
assert_eq!(stats.get_resolvers_by_speed().len(), 2);
stats.record_query_sent(slow);
assert_eq!(stats.get_resolvers_by_speed().len(), 2);
stats.record_query_sent(fast);
assert_eq!(stats.get_resolvers_by_speed(), without_fast);
stats.record_success(fast, Duration::from_millis(10));
assert_eq!(stats.get_resolvers_by_speed().len(), 2);
assert_eq!(park_duration(PARK_AFTER_FAILURES), MIN_PARK_DURATION);
assert_eq!(
park_duration(PARK_AFTER_FAILURES + 1),
MIN_PARK_DURATION * 2
);
assert_eq!(park_duration(u64::MAX), MAX_PARK_DURATION);
}
}