1use crate::cli::Args;
8use serde::Serialize;
9use std::net::SocketAddr;
10
11#[derive(Clone, Serialize)]
15pub struct PingResult {
16 pub success: bool,
17 pub duration_ms: f64,
18 pub jitter_ms: Option<f64>,
19 pub addr: SocketAddr,
20}
21
22#[derive(Serialize)]
24pub struct Summary {
25 pub addr: SocketAddr,
26 pub total_attempts: usize,
27 pub successful_pings: usize,
28 pub packet_loss: f64,
29 pub min_duration_ms: f64,
30 pub avg_duration_ms: f64,
31 pub max_duration_ms: f64,
32 pub resolve_time_ms: f64,
33}
34
35pub struct Stats {
37 addr: SocketAddr,
38 sent: usize,
39 ok: usize,
40 total_rtt: f64,
41 min_rtt: f64,
42 max_rtt: f64,
43 last_rtt: Option<f64>,
44 resolve_ms: f64,
45}
46
47impl Stats {
48 pub fn new(addr: SocketAddr, resolve_ms: f64) -> Self {
50 Self {
51 addr,
52 sent: 0,
53 ok: 0,
54 total_rtt: 0.0,
55 min_rtt: f64::MAX,
56 max_rtt: 0.0,
57 last_rtt: None,
58 resolve_ms,
59 }
60 }
61
62 pub fn feed(&mut self, success: bool, rtt: f64, want_jitter: bool) -> PingResult {
64 self.sent += 1;
65
66 let jitter = if want_jitter {
67 self.last_rtt.map(|prev| (rtt - prev).abs())
68 } else {
69 None
70 };
71
72 if success {
73 self.ok += 1;
74 self.total_rtt += rtt;
75 self.min_rtt = self.min_rtt.min(rtt);
76 self.max_rtt = self.max_rtt.max(rtt);
77 self.last_rtt = Some(rtt);
78 }
79
80 PingResult {
81 success,
82 duration_ms: rtt,
83 jitter_ms: jitter,
84 addr: self.addr,
85 }
86 }
87
88 pub fn should_continue(&self, args: &Args) -> bool {
90 args.continuous || self.sent < args.count
91 }
92
93 pub fn should_break(&self, success: bool, args: &Args) -> bool {
95 success && args.exit_on_success
96 }
97
98 pub fn summary(&self) -> Summary {
100 let packet_loss = if self.sent == 0 {
101 0.0
102 } else {
103 100.0 * (1.0 - self.ok as f64 / self.sent as f64)
104 };
105
106 Summary {
107 addr: self.addr,
108 total_attempts: self.sent,
109 successful_pings: self.ok,
110 packet_loss,
111 min_duration_ms: if self.ok > 0 { self.min_rtt } else { 0.0 },
112 avg_duration_ms: if self.ok > 0 {
113 self.total_rtt / self.ok as f64
114 } else {
115 0.0
116 },
117 max_duration_ms: if self.ok > 0 { self.max_rtt } else { 0.0 },
118 resolve_time_ms: self.resolve_ms,
119 }
120 }
121
122 pub fn exit_code(&self) -> i32 {
124 if self.ok == self.sent && self.sent > 0 {
125 0
126 } else {
127 1
128 }
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use std::net::{IpAddr, Ipv4Addr};
136
137 fn loopback_addr() -> SocketAddr {
138 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 80)
139 }
140
141 #[test]
142 fn summary_handles_zero_probes() {
143 let stats = Stats::new(loopback_addr(), 0.0);
144 let summary = stats.summary();
145 assert_eq!(summary.total_attempts, 0);
146 assert_eq!(summary.packet_loss, 0.0);
147 }
148
149 #[test]
150 fn jitter_is_difference_between_successive_successes() {
151 let mut stats = Stats::new(loopback_addr(), 0.0);
152 let first = stats.feed(true, 10.0, true);
153 assert_eq!(first.jitter_ms, None);
154
155 let second = stats.feed(true, 15.0, true);
156 assert_eq!(second.jitter_ms, Some(5.0));
157 }
158}