use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use tokio::sync::Mutex;
const MAX_CONSECUTIVE_FAILURES: u64 = 2;
#[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,
}
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, }
}
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.consecutive_failures = 0;
self.last_used = SystemTime::now();
self.update_response_time(response_time);
}
pub fn record_failure(&mut self) {
self.failure_count = self.failure_count.saturating_add(1);
self.consecutive_failures = self.consecutive_failures.saturating_add(1);
self.last_used = SystemTime::now();
}
pub fn record_timeout(&mut self) {
self.timeout_count = self.timeout_count.saturating_add(1);
self.consecutive_failures = self.consecutive_failures.saturating_add(1);
self.last_used = SystemTime::now();
}
pub fn clear_failures(&mut self) {
self.consecutive_failures = 0;
self.last_used = SystemTime::now();
}
}
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 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.consecutive_failures < MAX_CONSECUTIVE_FAILURES
})
.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 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_not_ranked_by_speed() {
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));
stats.record_timeout(fast);
assert_eq!(stats.get_resolvers_by_speed().len(), 2);
stats.record_timeout(fast);
assert_eq!(stats.get_resolvers_by_speed(), vec![(slow, 50.0)]);
stats.record_failure(slow);
stats.record_failure(slow);
assert!(stats.get_resolvers_by_speed().is_empty());
stats.record_success(fast, Duration::from_millis(10));
assert_eq!(stats.get_resolvers_by_speed(), vec![(fast, 10.0)]);
}
}