1#![allow(dead_code)]
4
5use std::time::Instant;
6
7#[derive(Debug, Clone, Default)]
9pub struct DirectionStats {
10 pub packets_sent: u64,
12 pub packets_received: u64,
14 pub packets_lost: u64,
16 pub packets_retransmitted: u64,
18 pub bytes_sent: u64,
20 pub bytes_received: u64,
22 pub packet_loss_rate: f64,
24 pub retransmit_rate: f64,
26 pub bandwidth_bps: f64,
28}
29
30impl DirectionStats {
31 pub fn update_loss_rate(&mut self) {
33 let total = self.packets_sent;
34 if total == 0 {
35 self.packet_loss_rate = 0.0;
36 } else {
37 self.packet_loss_rate = self.packets_lost as f64 / total as f64;
38 }
39 }
40
41 pub fn update_retransmit_rate(&mut self) {
43 let total = self.packets_sent;
44 if total == 0 {
45 self.retransmit_rate = 0.0;
46 } else {
47 self.retransmit_rate = self.packets_retransmitted as f64 / total as f64;
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
54pub struct RttStats {
55 pub current_ms: f64,
57 pub min_ms: f64,
59 pub max_ms: f64,
61 pub mean_ms: f64,
63 pub variance_ms: f64,
65 pub sample_count: u64,
67}
68
69impl Default for RttStats {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl RttStats {
76 pub fn new() -> Self {
78 Self {
79 current_ms: 0.0,
80 min_ms: f64::MAX,
81 max_ms: 0.0,
82 mean_ms: 0.0,
83 variance_ms: 0.0,
84 sample_count: 0,
85 }
86 }
87
88 pub fn update(&mut self, sample_ms: f64) {
90 self.current_ms = sample_ms;
91 self.sample_count += 1;
92
93 if sample_ms < self.min_ms {
95 self.min_ms = sample_ms;
96 }
97 if sample_ms > self.max_ms {
98 self.max_ms = sample_ms;
99 }
100
101 let n = self.sample_count as f64;
103 let delta = sample_ms - self.mean_ms;
104 self.mean_ms += delta / n;
105 let delta2 = sample_ms - self.mean_ms;
106 self.variance_ms += delta * delta2;
108 }
109
110 pub fn population_variance(&self) -> f64 {
114 if self.sample_count < 2 {
115 return 0.0;
116 }
117 self.variance_ms / self.sample_count as f64
118 }
119}
120
121#[derive(Debug, Clone, Default)]
123pub struct BufferStats {
124 pub send_buffer_level: usize,
126 pub recv_buffer_level: usize,
128 pub send_buffer_capacity: usize,
130 pub recv_buffer_capacity: usize,
132 pub send_buffer_utilization: f64,
134 pub recv_buffer_utilization: f64,
136}
137
138impl BufferStats {
139 pub fn update_utilization(&mut self) {
141 self.send_buffer_utilization = if self.send_buffer_capacity == 0 {
142 0.0
143 } else {
144 self.send_buffer_level as f64 / self.send_buffer_capacity as f64
145 };
146
147 self.recv_buffer_utilization = if self.recv_buffer_capacity == 0 {
148 0.0
149 } else {
150 self.recv_buffer_level as f64 / self.recv_buffer_capacity as f64
151 };
152 }
153}
154
155#[derive(Debug, Clone)]
157pub struct SrtStreamStats {
158 pub send: DirectionStats,
160 pub recv: DirectionStats,
162 pub rtt: RttStats,
164 pub buffer: BufferStats,
166 pub uptime_ms: u64,
168 pub negotiated_latency_ms: u32,
170 pub encryption_enabled: bool,
172 pub fec_enabled: bool,
174 pub connected_at: Instant,
176}
177
178impl SrtStreamStats {
179 pub fn new(latency_ms: u32) -> Self {
181 Self {
182 send: DirectionStats::default(),
183 recv: DirectionStats::default(),
184 rtt: RttStats::new(),
185 buffer: BufferStats::default(),
186 uptime_ms: 0,
187 negotiated_latency_ms: latency_ms,
188 encryption_enabled: false,
189 fec_enabled: false,
190 connected_at: Instant::now(),
191 }
192 }
193
194 pub fn uptime(&self) -> std::time::Duration {
196 self.connected_at.elapsed()
197 }
198
199 pub fn is_healthy(&self) -> bool {
202 self.send.packet_loss_rate < 0.01
203 && self.recv.packet_loss_rate < 0.01
204 && self.rtt.current_ms < 200.0
205 }
206
207 pub fn quality_score(&self) -> f32 {
212 let loss = self
214 .send
215 .packet_loss_rate
216 .max(self.recv.packet_loss_rate)
217 .min(0.1);
218 let loss_score = 1.0 - (loss / 0.1);
219
220 let rtt_score = (1.0 - (self.rtt.current_ms / 500.0).min(1.0)).max(0.0);
222
223 let buf_util = self
225 .buffer
226 .send_buffer_utilization
227 .max(self.buffer.recv_buffer_utilization)
228 .min(1.0);
229 let buf_score = 1.0 - buf_util;
230
231 let score = 0.5 * loss_score + 0.35 * rtt_score + 0.15 * buf_score;
233 score.clamp(0.0, 1.0) as f32
234 }
235
236 pub fn report(&self) -> String {
238 let quality = StreamQuality::from_stats(self);
239 format!(
240 "SRT Stream Report\n\
241 Quality: {}\n\
242 RTT: {:.1} ms (min={:.1}, max={:.1})\n\
243 Loss (tx): {:.2}%\n\
244 Loss (rx): {:.2}%\n\
245 Latency: {} ms (negotiated)\n\
246 Encrypted: {}\n\
247 FEC: {}\n\
248 Uptime: {:.1}s",
249 quality.name(),
250 self.rtt.current_ms,
251 self.rtt.min_ms.min(self.rtt.max_ms), self.rtt.max_ms,
253 self.send.packet_loss_rate * 100.0,
254 self.recv.packet_loss_rate * 100.0,
255 self.negotiated_latency_ms,
256 self.encryption_enabled,
257 self.fec_enabled,
258 self.uptime().as_secs_f64(),
259 )
260 }
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265pub enum StreamQuality {
266 Excellent,
268 Good,
270 Fair,
272 Poor,
274 Critical,
276}
277
278impl StreamQuality {
279 pub fn from_stats(stats: &SrtStreamStats) -> Self {
281 let loss = stats.send.packet_loss_rate.max(stats.recv.packet_loss_rate);
282 let rtt = stats.rtt.current_ms;
283
284 if loss < 0.001 && rtt < 20.0 {
285 Self::Excellent
286 } else if loss < 0.01 && rtt < 50.0 {
287 Self::Good
288 } else if loss < 0.05 && rtt < 100.0 {
289 Self::Fair
290 } else if loss < 0.1 && rtt < 200.0 {
291 Self::Poor
292 } else {
293 Self::Critical
294 }
295 }
296
297 pub fn name(&self) -> &'static str {
299 match self {
300 Self::Excellent => "Excellent",
301 Self::Good => "Good",
302 Self::Fair => "Fair",
303 Self::Poor => "Poor",
304 Self::Critical => "Critical",
305 }
306 }
307
308 pub fn is_usable(&self) -> bool {
311 !matches!(self, Self::Critical)
312 }
313}
314
315#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn test_rtt_stats_update() {
325 let mut rtt = RttStats::new();
326 rtt.update(10.0);
327 rtt.update(20.0);
328 rtt.update(30.0);
329 assert!((rtt.mean_ms - 20.0).abs() < 1e-9, "mean={}", rtt.mean_ms);
331 assert_eq!(rtt.sample_count, 3);
332 assert_eq!(rtt.current_ms, 30.0);
333 }
334
335 #[test]
336 fn test_rtt_stats_min_max() {
337 let mut rtt = RttStats::new();
338 rtt.update(50.0);
339 rtt.update(10.0);
340 rtt.update(100.0);
341 assert_eq!(rtt.min_ms, 10.0);
342 assert_eq!(rtt.max_ms, 100.0);
343 }
344
345 #[test]
346 fn test_direction_stats_loss_rate() {
347 let mut d = DirectionStats::default();
348 d.packets_sent = 100;
349 d.packets_lost = 10;
350 d.update_loss_rate();
351 assert!((d.packet_loss_rate - 0.1).abs() < 1e-9);
352 }
353
354 #[test]
355 fn test_buffer_stats_utilization() {
356 let mut b = BufferStats::default();
357 b.send_buffer_level = 512;
358 b.send_buffer_capacity = 1024;
359 b.recv_buffer_level = 0;
360 b.recv_buffer_capacity = 1024;
361 b.update_utilization();
362 assert!((b.send_buffer_utilization - 0.5).abs() < 1e-9);
363 assert!((b.recv_buffer_utilization - 0.0).abs() < 1e-9);
364 }
365
366 #[test]
367 fn test_stream_stats_healthy() {
368 let stats = SrtStreamStats::new(120);
369 assert!(stats.is_healthy());
371 }
372
373 #[test]
374 fn test_stream_quality_from_stats() {
375 let mut stats = SrtStreamStats::new(120);
376 stats.send.packet_loss_rate = 0.005; stats.rtt.current_ms = 30.0;
378 let quality = StreamQuality::from_stats(&stats);
379 assert_eq!(quality, StreamQuality::Good);
380 }
381
382 #[test]
383 fn test_stream_quality_is_usable() {
384 assert!(StreamQuality::Excellent.is_usable());
385 assert!(StreamQuality::Good.is_usable());
386 assert!(StreamQuality::Fair.is_usable());
387 assert!(StreamQuality::Poor.is_usable());
388 assert!(!StreamQuality::Critical.is_usable());
389 }
390
391 #[test]
392 fn test_quality_score_range() {
393 let mut stats = SrtStreamStats::new(120);
395 let score = stats.quality_score();
396 assert!((0.0..=1.0).contains(&score));
397
398 stats.send.packet_loss_rate = 1.0;
400 stats.recv.packet_loss_rate = 1.0;
401 stats.rtt.current_ms = 1000.0;
402 stats.buffer.send_buffer_utilization = 1.0;
403 stats.buffer.recv_buffer_utilization = 1.0;
404 let score_bad = stats.quality_score();
405 assert!((0.0..=1.0).contains(&score_bad));
406 assert!(score > score_bad);
407 }
408}