Skip to main content

hopr_transport/
stats.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2
3/// Atomic counters for tracking packet statistics per peer.
4///
5/// Uses atomic operations for thread-safe, lock-free updates.
6/// Stats are automatically reset when the peer disconnects (the entry is dropped).
7#[derive(Debug, Default)]
8pub struct PeerPacketStats {
9    packets_out: AtomicU64,
10    packets_in: AtomicU64,
11    bytes_out: AtomicU64,
12    bytes_in: AtomicU64,
13}
14
15impl PeerPacketStats {
16    /// Record an outgoing packet with the given size in bytes.
17    #[inline]
18    pub fn record_packet_out(&self, bytes: usize) {
19        self.packets_out.fetch_add(1, Ordering::Relaxed);
20        self.bytes_out.fetch_add(bytes as u64, Ordering::Relaxed);
21    }
22
23    /// Record an incoming packet with the given size in bytes.
24    #[inline]
25    pub fn record_packet_in(&self, bytes: usize) {
26        self.packets_in.fetch_add(1, Ordering::Relaxed);
27        self.bytes_in.fetch_add(bytes as u64, Ordering::Relaxed);
28    }
29
30    /// Create a snapshot of the current stats.
31    pub fn snapshot(&self) -> PeerPacketStatsSnapshot {
32        PeerPacketStatsSnapshot {
33            packets_out: self.packets_out.load(Ordering::Relaxed),
34            packets_in: self.packets_in.load(Ordering::Relaxed),
35            bytes_out: self.bytes_out.load(Ordering::Relaxed),
36            bytes_in: self.bytes_in.load(Ordering::Relaxed),
37        }
38    }
39}
40
41/// A point-in-time snapshot of peer packet statistics.
42///
43/// This is a non-atomic, serializable copy of the stats for API responses.
44#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct PeerPacketStatsSnapshot {
47    pub packets_out: u64,
48    pub packets_in: u64,
49    pub bytes_out: u64,
50    pub bytes_in: u64,
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn peer_packet_stats_should_start_at_zero() {
59        let stats = PeerPacketStats::default();
60        let snapshot = stats.snapshot();
61
62        insta::assert_yaml_snapshot!(snapshot);
63    }
64
65    #[test]
66    fn peer_packet_stats_should_record_outgoing_packets() {
67        let stats = PeerPacketStats::default();
68
69        stats.record_packet_out(100);
70        stats.record_packet_out(200);
71
72        let snapshot = stats.snapshot();
73        insta::assert_yaml_snapshot!(snapshot);
74    }
75
76    #[test]
77    fn peer_packet_stats_should_record_incoming_packets() {
78        let stats = PeerPacketStats::default();
79
80        stats.record_packet_in(50);
81        stats.record_packet_in(150);
82        stats.record_packet_in(100);
83
84        let snapshot = stats.snapshot();
85        insta::assert_yaml_snapshot!(snapshot);
86    }
87
88    #[test]
89    fn peer_packet_stats_should_record_bidirectional_traffic() {
90        let stats = PeerPacketStats::default();
91
92        stats.record_packet_out(1000);
93        stats.record_packet_in(500);
94        stats.record_packet_out(2000);
95        stats.record_packet_in(1500);
96
97        let snapshot = stats.snapshot();
98        insta::assert_yaml_snapshot!(snapshot);
99    }
100}