use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
pub struct AtomicMetrics {
pub requests_forwarded: AtomicU64,
pub requests_failed: AtomicU64,
pub bytes_sent: AtomicU64,
pub bytes_received: AtomicU64,
pub active_sessions: AtomicU64,
avg_latency_us: AtomicU64,
}
impl AtomicMetrics {
#[must_use]
pub fn new() -> Self {
Self {
requests_forwarded: AtomicU64::new(0),
requests_failed: AtomicU64::new(0),
bytes_sent: AtomicU64::new(0),
bytes_received: AtomicU64::new(0),
active_sessions: AtomicU64::new(0),
avg_latency_us: AtomicU64::new(0),
}
}
pub fn update_latency_us(&self, latency_us: u64) {
let current = self.avg_latency_us.load(Ordering::Relaxed);
let new_avg = if current == 0 {
latency_us
} else {
(current.saturating_mul(9).saturating_add(latency_us)) / 10
};
self.avg_latency_us.store(new_avg, Ordering::Relaxed);
}
pub fn inc_requests_forwarded(&self) {
self.requests_forwarded.fetch_add(1, Ordering::Relaxed);
}
pub fn inc_requests_failed(&self) {
self.requests_failed.fetch_add(1, Ordering::Relaxed);
}
pub fn add_bytes_sent(&self, bytes: u64) {
self.bytes_sent.fetch_add(bytes, Ordering::Relaxed);
}
pub fn add_bytes_received(&self, bytes: u64) {
self.bytes_received.fetch_add(bytes, Ordering::Relaxed);
}
pub fn inc_active_sessions(&self) {
self.active_sessions.fetch_add(1, Ordering::Relaxed);
}
pub fn dec_active_sessions(&self) {
self.active_sessions.fetch_sub(1, Ordering::Relaxed);
}
#[allow(clippy::cast_precision_loss)]
pub fn snapshot(&self) -> ProxyMetrics {
ProxyMetrics {
requests_forwarded: self.requests_forwarded.load(Ordering::Relaxed),
requests_failed: self.requests_failed.load(Ordering::Relaxed),
bytes_sent: self.bytes_sent.load(Ordering::Relaxed),
bytes_received: self.bytes_received.load(Ordering::Relaxed),
active_sessions: self.active_sessions.load(Ordering::Relaxed),
average_latency_ms: self.avg_latency_us.load(Ordering::Relaxed) as f64 / 1000.0,
}
}
pub fn reset(&self) {
self.requests_forwarded.store(0, Ordering::Relaxed);
self.requests_failed.store(0, Ordering::Relaxed);
self.bytes_sent.store(0, Ordering::Relaxed);
self.bytes_received.store(0, Ordering::Relaxed);
self.active_sessions.store(0, Ordering::Relaxed);
self.avg_latency_us.store(0, Ordering::Relaxed);
}
}
impl Default for AtomicMetrics {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProxyMetrics {
pub requests_forwarded: u64,
pub requests_failed: u64,
pub bytes_sent: u64,
pub bytes_received: u64,
pub active_sessions: u64,
pub average_latency_ms: f64,
}
impl ProxyMetrics {
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn success_rate(&self) -> Option<f64> {
let total = self.requests_forwarded + self.requests_failed;
if total == 0 {
None
} else {
Some((self.requests_forwarded as f64 / total as f64) * 100.0)
}
}
#[must_use]
pub fn total_requests(&self) -> u64 {
self.requests_forwarded + self.requests_failed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::float_cmp)]
fn test_atomic_metrics_creation() {
let metrics = AtomicMetrics::new();
let snapshot = metrics.snapshot();
assert_eq!(snapshot.requests_forwarded, 0);
assert_eq!(snapshot.requests_failed, 0);
assert_eq!(snapshot.bytes_sent, 0);
assert_eq!(snapshot.bytes_received, 0);
assert_eq!(snapshot.active_sessions, 0);
assert_eq!(snapshot.average_latency_ms, 0.0);
}
#[test]
fn test_increment_operations() {
let metrics = AtomicMetrics::new();
metrics.inc_requests_forwarded();
metrics.inc_requests_forwarded();
metrics.inc_requests_failed();
let snapshot = metrics.snapshot();
assert_eq!(snapshot.requests_forwarded, 2);
assert_eq!(snapshot.requests_failed, 1);
}
#[test]
fn test_bytes_tracking() {
let metrics = AtomicMetrics::new();
metrics.add_bytes_sent(1024);
metrics.add_bytes_sent(2048);
metrics.add_bytes_received(512);
let snapshot = metrics.snapshot();
assert_eq!(snapshot.bytes_sent, 3072);
assert_eq!(snapshot.bytes_received, 512);
}
#[test]
fn test_active_sessions() {
let metrics = AtomicMetrics::new();
metrics.inc_active_sessions();
metrics.inc_active_sessions();
metrics.inc_active_sessions();
assert_eq!(metrics.snapshot().active_sessions, 3);
metrics.dec_active_sessions();
assert_eq!(metrics.snapshot().active_sessions, 2);
}
#[test]
#[allow(clippy::float_cmp)]
fn test_latency_tracking() {
let metrics = AtomicMetrics::new();
metrics.update_latency_us(1000);
assert_eq!(metrics.snapshot().average_latency_ms, 1.0);
metrics.update_latency_us(2000);
assert_eq!(metrics.snapshot().average_latency_ms, 1.1);
}
#[test]
#[allow(clippy::float_cmp)]
fn test_reset() {
let metrics = AtomicMetrics::new();
metrics.inc_requests_forwarded();
metrics.inc_requests_failed();
metrics.add_bytes_sent(1024);
metrics.update_latency_us(1000);
metrics.reset();
let snapshot = metrics.snapshot();
assert_eq!(snapshot.requests_forwarded, 0);
assert_eq!(snapshot.requests_failed, 0);
assert_eq!(snapshot.bytes_sent, 0);
assert_eq!(snapshot.average_latency_ms, 0.0);
}
#[test]
fn test_proxy_metrics_success_rate() {
let metrics = ProxyMetrics {
requests_forwarded: 90,
requests_failed: 10,
bytes_sent: 0,
bytes_received: 0,
active_sessions: 0,
average_latency_ms: 0.0,
};
assert_eq!(metrics.success_rate(), Some(90.0));
assert_eq!(metrics.total_requests(), 100);
}
#[test]
fn test_proxy_metrics_success_rate_no_requests() {
let metrics = ProxyMetrics {
requests_forwarded: 0,
requests_failed: 0,
bytes_sent: 0,
bytes_received: 0,
active_sessions: 0,
average_latency_ms: 0.0,
};
assert_eq!(metrics.success_rate(), None);
assert_eq!(metrics.total_requests(), 0);
}
#[test]
fn test_serialization() {
let metrics = ProxyMetrics {
requests_forwarded: 100,
requests_failed: 5,
bytes_sent: 1024,
bytes_received: 2048,
active_sessions: 3,
average_latency_ms: 15.5,
};
let json = serde_json::to_string(&metrics).unwrap();
let deserialized: ProxyMetrics = serde_json::from_str(&json).unwrap();
assert_eq!(metrics, deserialized);
}
}