1use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36use std::time::{Duration, SystemTime};
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40pub enum ConnectionQuality {
41 Excellent,
43 Good,
45 Fair,
47 Poor,
49 VeryPoor,
51}
52
53impl ConnectionQuality {
54 #[must_use]
56 pub const fn from_latency(latency_ms: u64) -> Self {
57 if latency_ms < 50 {
58 Self::Excellent
59 } else if latency_ms < 150 {
60 Self::Good
61 } else if latency_ms < 300 {
62 Self::Fair
63 } else if latency_ms < 500 {
64 Self::Poor
65 } else {
66 Self::VeryPoor
67 }
68 }
69
70 #[must_use]
72 pub const fn health_score(&self) -> f64 {
73 match self {
74 Self::Excellent => 1.0,
75 Self::Good => 0.8,
76 Self::Fair => 0.6,
77 Self::Poor => 0.4,
78 Self::VeryPoor => 0.2,
79 }
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct NetworkStats {
86 pub peer_id: String,
88
89 pub avg_latency_ms: f64,
91
92 pub min_latency_ms: u64,
94
95 pub max_latency_ms: u64,
97
98 pub latency_stddev: f64,
100
101 pub sample_count: u64,
103
104 pub packet_loss_rate: f64,
106
107 pub estimated_bandwidth_bps: f64,
109
110 pub quality: ConnectionQuality,
112
113 pub last_update: SystemTime,
115}
116
117impl NetworkStats {
118 fn new(peer_id: String) -> Self {
120 Self {
121 peer_id,
122 avg_latency_ms: 0.0,
123 min_latency_ms: u64::MAX,
124 max_latency_ms: 0,
125 latency_stddev: 0.0,
126 sample_count: 0,
127 packet_loss_rate: 0.0,
128 estimated_bandwidth_bps: 0.0,
129 quality: ConnectionQuality::Good,
130 last_update: SystemTime::now(),
131 }
132 }
133
134 #[must_use]
136 #[inline]
137 pub fn health_score(&self) -> f64 {
138 let latency_score = self.quality.health_score();
139 let packet_loss_penalty = self.packet_loss_rate;
140 let stability_score = if self.sample_count > 10 {
141 1.0 - (self.latency_stddev / 100.0).min(1.0)
142 } else {
143 0.5
144 };
145
146 (latency_score * 0.5 + (1.0 - packet_loss_penalty) * 0.3 + stability_score * 0.2)
147 .clamp(0.0, 1.0)
148 }
149
150 #[must_use]
152 #[inline]
153 pub const fn is_stable(&self) -> bool {
154 self.latency_stddev < 50.0 && self.packet_loss_rate < 0.05
155 }
156
157 #[must_use]
159 #[inline]
160 pub fn is_healthy(&self) -> bool {
161 self.health_score() >= 0.6
162 }
163}
164
165pub struct NetworkMonitor {
167 stats: HashMap<String, NetworkStats>,
169
170 max_history: usize,
172
173 latency_history: HashMap<String, Vec<u64>>,
175}
176
177impl NetworkMonitor {
178 #[must_use]
180 #[inline]
181 pub fn new() -> Self {
182 Self::with_history_size(100)
183 }
184
185 #[must_use]
187 #[inline]
188 pub fn with_history_size(max_history: usize) -> Self {
189 Self {
190 stats: HashMap::new(),
191 max_history,
192 latency_history: HashMap::new(),
193 }
194 }
195
196 pub fn record_latency(&mut self, peer_id: String, latency_ms: u64) {
198 let stats = self
199 .stats
200 .entry(peer_id.clone())
201 .or_insert_with(|| NetworkStats::new(peer_id.clone()));
202
203 stats.sample_count += 1;
205 stats.min_latency_ms = stats.min_latency_ms.min(latency_ms);
206 stats.max_latency_ms = stats.max_latency_ms.max(latency_ms);
207 stats.last_update = SystemTime::now();
208
209 let alpha = 0.3;
211 if stats.sample_count == 1 {
212 stats.avg_latency_ms = latency_ms as f64;
213 } else {
214 stats.avg_latency_ms = alpha * latency_ms as f64 + (1.0 - alpha) * stats.avg_latency_ms;
215 }
216
217 stats.quality = ConnectionQuality::from_latency(stats.avg_latency_ms as u64);
219
220 let history = self.latency_history.entry(peer_id).or_default();
222 history.push(latency_ms);
223 if history.len() > self.max_history {
224 history.remove(0);
225 }
226
227 if history.len() > 1 {
229 let mean = history.iter().sum::<u64>() as f64 / history.len() as f64;
230 let variance = history
231 .iter()
232 .map(|&x| {
233 let diff = x as f64 - mean;
234 diff * diff
235 })
236 .sum::<f64>()
237 / history.len() as f64;
238 stats.latency_stddev = variance.sqrt();
239 }
240 }
241
242 pub fn record_bandwidth(&mut self, peer_id: &str, bytes_per_sec: f64) {
244 if let Some(stats) = self.stats.get_mut(peer_id) {
245 let alpha = 0.2;
247 if stats.estimated_bandwidth_bps == 0.0 {
248 stats.estimated_bandwidth_bps = bytes_per_sec;
249 } else {
250 stats.estimated_bandwidth_bps =
251 alpha * bytes_per_sec + (1.0 - alpha) * stats.estimated_bandwidth_bps;
252 }
253 }
254 }
255
256 pub fn record_packet_loss(&mut self, peer_id: &str, lost_packets: u64, total_packets: u64) {
258 if let Some(stats) = self.stats.get_mut(peer_id) {
259 let loss_rate = lost_packets as f64 / total_packets as f64;
260
261 let alpha = 0.3;
263 stats.packet_loss_rate = alpha * loss_rate + (1.0 - alpha) * stats.packet_loss_rate;
264 }
265 }
266
267 #[must_use]
269 #[inline]
270 pub fn get_stats(&self, peer_id: &str) -> Option<&NetworkStats> {
271 self.stats.get(peer_id)
272 }
273
274 #[must_use]
276 #[inline]
277 pub fn get_quality(&self, peer_id: &str) -> ConnectionQuality {
278 self.stats
279 .get(peer_id)
280 .map(|s| s.quality)
281 .unwrap_or(ConnectionQuality::Good)
282 }
283
284 #[must_use]
286 #[inline]
287 pub fn health_score(&self, peer_id: &str) -> f64 {
288 self.stats
289 .get(peer_id)
290 .map(|s| s.health_score())
291 .unwrap_or(0.5)
292 }
293
294 #[must_use]
296 #[inline]
297 pub fn get_healthy_peers(&self) -> Vec<String> {
298 self.stats
299 .values()
300 .filter(|s| s.is_healthy())
301 .map(|s| s.peer_id.clone())
302 .collect()
303 }
304
305 #[must_use]
307 #[inline]
308 pub fn get_excellent_peers(&self) -> Vec<String> {
309 self.stats
310 .values()
311 .filter(|s| s.quality == ConnectionQuality::Excellent)
312 .map(|s| s.peer_id.clone())
313 .collect()
314 }
315
316 #[must_use]
318 #[inline]
319 pub fn average_health(&self) -> f64 {
320 if self.stats.is_empty() {
321 return 0.5;
322 }
323
324 let sum: f64 = self.stats.values().map(|s| s.health_score()).sum();
325 sum / self.stats.len() as f64
326 }
327
328 pub fn cleanup_old_peers(&mut self, max_age_secs: u64) {
330 let now = SystemTime::now();
331 self.stats.retain(|peer_id, stats| {
332 let age = now
333 .duration_since(stats.last_update)
334 .unwrap_or(Duration::from_secs(0))
335 .as_secs();
336
337 if age >= max_age_secs {
338 self.latency_history.remove(peer_id);
339 false
340 } else {
341 true
342 }
343 });
344 }
345
346 #[must_use]
348 #[inline]
349 pub fn peer_count(&self) -> usize {
350 self.stats.len()
351 }
352
353 #[must_use]
355 #[inline]
356 pub fn get_all_peer_ids(&self) -> Vec<String> {
357 self.stats.keys().cloned().collect()
358 }
359}
360
361impl Default for NetworkMonitor {
362 fn default() -> Self {
363 Self::new()
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 #[test]
372 fn test_connection_quality_from_latency() {
373 assert_eq!(
374 ConnectionQuality::from_latency(30),
375 ConnectionQuality::Excellent
376 );
377 assert_eq!(
378 ConnectionQuality::from_latency(100),
379 ConnectionQuality::Good
380 );
381 assert_eq!(
382 ConnectionQuality::from_latency(200),
383 ConnectionQuality::Fair
384 );
385 assert_eq!(
386 ConnectionQuality::from_latency(400),
387 ConnectionQuality::Poor
388 );
389 assert_eq!(
390 ConnectionQuality::from_latency(600),
391 ConnectionQuality::VeryPoor
392 );
393 }
394
395 #[test]
396 fn test_connection_quality_health_score() {
397 assert_eq!(ConnectionQuality::Excellent.health_score(), 1.0);
398 assert_eq!(ConnectionQuality::Good.health_score(), 0.8);
399 assert_eq!(ConnectionQuality::Fair.health_score(), 0.6);
400 assert_eq!(ConnectionQuality::Poor.health_score(), 0.4);
401 assert_eq!(ConnectionQuality::VeryPoor.health_score(), 0.2);
402 }
403
404 #[test]
405 fn test_network_monitor_basic() {
406 let mut monitor = NetworkMonitor::new();
407
408 monitor.record_latency("peer1".to_string(), 50);
409 monitor.record_latency("peer1".to_string(), 55);
410 monitor.record_latency("peer1".to_string(), 45);
411
412 let stats = monitor.get_stats("peer1").unwrap();
413 assert_eq!(stats.sample_count, 3);
414 assert_eq!(stats.min_latency_ms, 45);
415 assert_eq!(stats.max_latency_ms, 55);
416 }
417
418 #[test]
419 fn test_network_monitor_quality() {
420 let mut monitor = NetworkMonitor::new();
421
422 monitor.record_latency("peer1".to_string(), 30);
423 assert_eq!(monitor.get_quality("peer1"), ConnectionQuality::Excellent);
424
425 monitor.record_latency("peer2".to_string(), 200);
426 assert_eq!(monitor.get_quality("peer2"), ConnectionQuality::Fair);
427 }
428
429 #[test]
430 fn test_bandwidth_tracking() {
431 let mut monitor = NetworkMonitor::new();
432
433 monitor.record_latency("peer1".to_string(), 50);
434 monitor.record_bandwidth("peer1", 1_000_000.0);
435 monitor.record_bandwidth("peer1", 1_500_000.0);
436
437 let stats = monitor.get_stats("peer1").unwrap();
438 assert!(stats.estimated_bandwidth_bps > 0.0);
439 }
440
441 #[test]
442 fn test_packet_loss() {
443 let mut monitor = NetworkMonitor::new();
444
445 monitor.record_latency("peer1".to_string(), 50);
446 monitor.record_packet_loss("peer1", 5, 100); let stats = monitor.get_stats("peer1").unwrap();
449 assert!(stats.packet_loss_rate > 0.0);
450 }
451
452 #[test]
453 fn test_health_score() {
454 let mut monitor = NetworkMonitor::new();
455
456 for _ in 0..10 {
458 monitor.record_latency("peer1".to_string(), 40);
459 }
460
461 let score = monitor.health_score("peer1");
462 assert!(score > 0.8);
463 }
464
465 #[test]
466 fn test_get_healthy_peers() {
467 let mut monitor = NetworkMonitor::new();
468
469 monitor.record_latency("peer1".to_string(), 40);
470 monitor.record_latency("peer2".to_string(), 600);
471 monitor.record_latency("peer3".to_string(), 80);
472
473 let healthy = monitor.get_healthy_peers();
474 assert!(healthy.contains(&"peer1".to_string()));
475 assert!(healthy.contains(&"peer3".to_string()));
476 }
477
478 #[test]
479 fn test_get_excellent_peers() {
480 let mut monitor = NetworkMonitor::new();
481
482 monitor.record_latency("peer1".to_string(), 30);
483 monitor.record_latency("peer2".to_string(), 100);
484
485 let excellent = monitor.get_excellent_peers();
486 assert_eq!(excellent.len(), 1);
487 assert_eq!(excellent[0], "peer1");
488 }
489
490 #[test]
491 fn test_average_health() {
492 let mut monitor = NetworkMonitor::new();
493
494 monitor.record_latency("peer1".to_string(), 40);
495 monitor.record_latency("peer2".to_string(), 150);
496
497 let avg = monitor.average_health();
498 assert!(avg > 0.5 && avg < 1.0);
499 }
500
501 #[test]
502 fn test_cleanup_old_peers() {
503 let mut monitor = NetworkMonitor::new();
504
505 monitor.record_latency("peer1".to_string(), 50);
506 assert_eq!(monitor.peer_count(), 1);
507
508 monitor.cleanup_old_peers(0);
509 assert_eq!(monitor.peer_count(), 0);
510 }
511
512 #[test]
513 fn test_network_stats_stability() {
514 let mut monitor = NetworkMonitor::new();
515
516 for _ in 0..20 {
518 monitor.record_latency("peer1".to_string(), 50);
519 }
520
521 let stats = monitor.get_stats("peer1").unwrap();
522 assert!(stats.is_stable());
523
524 for i in 0..20 {
526 monitor.record_latency("peer2".to_string(), 50 + (i * 20));
527 }
528
529 let stats2 = monitor.get_stats("peer2").unwrap();
530 assert!(!stats2.is_stable());
531 }
532}