use portable_atomic::{AtomicU64, Ordering};
use serde::Serialize;
pub struct UdpStats {
send: UdpSendStats,
recv: UdpRecvStats,
}
#[repr(align(64))]
struct UdpSendStats {
packets: AtomicU64,
bytes: AtomicU64,
errors: AtomicU64,
mtu_exceeded: AtomicU64,
}
#[repr(align(64))]
struct UdpRecvStats {
packets: AtomicU64,
bytes: AtomicU64,
errors: AtomicU64,
kernel_drops: AtomicU64,
}
impl UdpStats {
pub fn new() -> Self {
Self {
send: UdpSendStats {
packets: AtomicU64::new(0),
bytes: AtomicU64::new(0),
errors: AtomicU64::new(0),
mtu_exceeded: AtomicU64::new(0),
},
recv: UdpRecvStats {
packets: AtomicU64::new(0),
bytes: AtomicU64::new(0),
errors: AtomicU64::new(0),
kernel_drops: AtomicU64::new(0),
},
}
}
pub fn record_send(&self, bytes: usize) {
self.record_send_batch(1, bytes);
}
pub fn record_send_batch(&self, packets: usize, bytes: usize) {
self.send
.packets
.fetch_add(packets as u64, Ordering::Relaxed);
self.send.bytes.fetch_add(bytes as u64, Ordering::Relaxed);
}
pub fn record_recv(&self, bytes: usize) {
self.recv.packets.fetch_add(1, Ordering::Relaxed);
self.recv.bytes.fetch_add(bytes as u64, Ordering::Relaxed);
}
pub fn record_send_error(&self) {
self.send.errors.fetch_add(1, Ordering::Relaxed);
}
pub fn record_recv_error(&self) {
self.recv.errors.fetch_add(1, Ordering::Relaxed);
}
pub fn record_mtu_exceeded(&self) {
self.send.mtu_exceeded.fetch_add(1, Ordering::Relaxed);
}
pub fn set_kernel_drops(&self, drops: u64) {
self.recv.kernel_drops.store(drops, Ordering::Relaxed);
}
pub fn kernel_drops(&self) -> u64 {
self.recv.kernel_drops.load(Ordering::Relaxed)
}
pub fn snapshot(&self) -> UdpStatsSnapshot {
UdpStatsSnapshot {
packets_sent: self.send.packets.load(Ordering::Relaxed),
bytes_sent: self.send.bytes.load(Ordering::Relaxed),
packets_recv: self.recv.packets.load(Ordering::Relaxed),
bytes_recv: self.recv.bytes.load(Ordering::Relaxed),
send_errors: self.send.errors.load(Ordering::Relaxed),
recv_errors: self.recv.errors.load(Ordering::Relaxed),
mtu_exceeded: self.send.mtu_exceeded.load(Ordering::Relaxed),
kernel_drops: self.recv.kernel_drops.load(Ordering::Relaxed),
}
}
}
impl Default for UdpStats {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, Default, Serialize)]
pub struct UdpStatsSnapshot {
pub packets_sent: u64,
pub bytes_sent: u64,
pub packets_recv: u64,
pub bytes_recv: u64,
pub send_errors: u64,
pub recv_errors: u64,
pub mtu_exceeded: u64,
pub kernel_drops: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn batch_and_scalar_updates_preserve_udp_stats_snapshot() {
let stats = UdpStats::new();
stats.record_send_batch(32, 45_120);
stats.record_send(1410);
stats.record_recv(1400);
stats.record_send_error();
stats.record_recv_error();
stats.record_mtu_exceeded();
stats.set_kernel_drops(7);
let snapshot = stats.snapshot();
assert_eq!(snapshot.packets_sent, 33);
assert_eq!(snapshot.bytes_sent, 46_530);
assert_eq!(snapshot.packets_recv, 1);
assert_eq!(snapshot.bytes_recv, 1400);
assert_eq!(snapshot.send_errors, 1);
assert_eq!(snapshot.recv_errors, 1);
assert_eq!(snapshot.mtu_exceeded, 1);
assert_eq!(snapshot.kernel_drops, 7);
}
#[test]
fn send_and_receive_writers_do_not_share_a_cache_line() {
let stats = UdpStats::new();
let send = std::ptr::addr_of!(stats.send) as usize;
let recv = std::ptr::addr_of!(stats.recv) as usize;
assert_eq!(send % 64, 0);
assert_eq!(recv % 64, 0);
assert!(send.abs_diff(recv) >= 64);
}
}