mumble_server_runtime_gateway/
metrics.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2
3/// Allocation-free counters for the Mumble voice packet path.
4#[derive(Debug, Default)]
5pub struct VoiceMetrics {
6    ingress_packets: AtomicU64,
7    ingress_bytes: AtomicU64,
8    egress_packets: AtomicU64,
9    egress_bytes: AtomicU64,
10    dropped_packets: AtomicU64,
11}
12
13impl VoiceMetrics {
14    pub(crate) fn ingress(&self, bytes: usize) {
15        self.ingress_packets.fetch_add(1, Ordering::Relaxed);
16        self.ingress_bytes
17            .fetch_add(u64::try_from(bytes).unwrap_or(u64::MAX), Ordering::Relaxed);
18    }
19
20    pub(crate) fn egress(&self, bytes: usize) {
21        self.egress_packets.fetch_add(1, Ordering::Relaxed);
22        self.egress_bytes
23            .fetch_add(u64::try_from(bytes).unwrap_or(u64::MAX), Ordering::Relaxed);
24    }
25
26    pub(crate) fn dropped(&self) {
27        self.dropped_packets.fetch_add(1, Ordering::Relaxed);
28    }
29
30    #[must_use]
31    pub fn snapshot(&self) -> VoiceMetricsSnapshot {
32        VoiceMetricsSnapshot {
33            ingress_packets: self.ingress_packets.load(Ordering::Relaxed),
34            ingress_bytes: self.ingress_bytes.load(Ordering::Relaxed),
35            egress_packets: self.egress_packets.load(Ordering::Relaxed),
36            egress_bytes: self.egress_bytes.load(Ordering::Relaxed),
37            dropped_packets: self.dropped_packets.load(Ordering::Relaxed),
38        }
39    }
40}
41
42#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
43pub struct VoiceMetricsSnapshot {
44    pub ingress_packets: u64,
45    pub ingress_bytes: u64,
46    pub egress_packets: u64,
47    pub egress_bytes: u64,
48    pub dropped_packets: u64,
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn counters_are_monotonic_and_saturating_at_conversion_boundaries() {
57        let metrics = VoiceMetrics::default();
58        metrics.ingress(30);
59        metrics.egress(42);
60        metrics.dropped();
61
62        assert_eq!(
63            metrics.snapshot(),
64            VoiceMetricsSnapshot {
65                ingress_packets: 1,
66                ingress_bytes: 30,
67                egress_packets: 1,
68                egress_bytes: 42,
69                dropped_packets: 1,
70            }
71        );
72    }
73}