use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::OnceLock;
use std::time::{Duration, Instant, SystemTime};
use super::MetricsSnapshot;
#[derive(Debug)]
pub(crate) struct MetricsCollector {
message_count: AtomicU64,
total_processing_nanos: AtomicU64,
max_processing_nanos: AtomicU64,
priority_message_count: AtomicU64,
total_priority_processing_nanos: AtomicU64,
max_priority_processing_nanos: AtomicU64,
idle_event_count: AtomicU64,
total_idle_processing_nanos: AtomicU64,
max_idle_processing_nanos: AtomicU64,
last_activity_millis: AtomicU64,
start_instant: Instant,
started_at: OnceLock<Instant>,
}
impl MetricsCollector {
pub fn new() -> Self {
Self {
message_count: AtomicU64::new(0),
total_processing_nanos: AtomicU64::new(0),
max_processing_nanos: AtomicU64::new(0),
priority_message_count: AtomicU64::new(0),
total_priority_processing_nanos: AtomicU64::new(0),
max_priority_processing_nanos: AtomicU64::new(0),
idle_event_count: AtomicU64::new(0),
total_idle_processing_nanos: AtomicU64::new(0),
max_idle_processing_nanos: AtomicU64::new(0),
last_activity_millis: AtomicU64::new(0),
start_instant: Instant::now(),
started_at: OnceLock::new(),
}
}
#[inline]
pub fn mark_started(&self) {
let _ = self.started_at.set(Instant::now());
}
#[inline]
fn uptime_baseline(&self) -> Instant {
self.started_at.get().copied().unwrap_or(self.start_instant)
}
#[inline]
pub fn record_message(&self, duration: Duration) {
self.message_count.fetch_add(1, Ordering::Relaxed);
let nanos = duration.as_nanos().min(u64::MAX as u128) as u64;
self.total_processing_nanos
.fetch_add(nanos, Ordering::Relaxed);
self.max_processing_nanos
.fetch_max(nanos, Ordering::Relaxed);
self.update_last_activity();
}
#[inline]
pub fn record_priority_message(&self, duration: Duration) {
self.priority_message_count.fetch_add(1, Ordering::Relaxed);
let nanos = duration.as_nanos().min(u64::MAX as u128) as u64;
self.total_priority_processing_nanos
.fetch_add(nanos, Ordering::Relaxed);
self.max_priority_processing_nanos
.fetch_max(nanos, Ordering::Relaxed);
self.update_last_activity();
}
#[inline]
pub fn record_idle_event(&self, duration: Duration) {
self.idle_event_count.fetch_add(1, Ordering::Relaxed);
let nanos = duration.as_nanos().min(u64::MAX as u128) as u64;
self.total_idle_processing_nanos
.fetch_add(nanos, Ordering::Relaxed);
self.max_idle_processing_nanos
.fetch_max(nanos, Ordering::Relaxed);
self.update_last_activity();
}
fn update_last_activity(&self) {
let millis = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_millis()
.min(u64::MAX as u128) as u64;
self.last_activity_millis.store(millis, Ordering::Relaxed);
}
fn get_last_activity(&self) -> Option<SystemTime> {
let millis = self.last_activity_millis.load(Ordering::Relaxed);
if millis == 0 {
None
} else {
SystemTime::UNIX_EPOCH.checked_add(Duration::from_millis(millis))
}
}
pub fn snapshot(&self) -> MetricsSnapshot {
let count = self.message_count.load(Ordering::Relaxed);
let total_nanos = self.total_processing_nanos.load(Ordering::Relaxed);
let priority_count = self.priority_message_count.load(Ordering::Relaxed);
let total_priority_nanos = self.total_priority_processing_nanos.load(Ordering::Relaxed);
let idle_count = self.idle_event_count.load(Ordering::Relaxed);
let total_idle_nanos = self.total_idle_processing_nanos.load(Ordering::Relaxed);
let max_processing_time =
Duration::from_nanos(self.max_processing_nanos.load(Ordering::Relaxed));
let max_priority_processing_time =
Duration::from_nanos(self.max_priority_processing_nanos.load(Ordering::Relaxed));
let max_idle_processing_time =
Duration::from_nanos(self.max_idle_processing_nanos.load(Ordering::Relaxed));
MetricsSnapshot {
message_count: count,
avg_processing_time: total_nanos
.checked_div(count)
.map(Duration::from_nanos)
.unwrap_or(Duration::ZERO)
.min(max_processing_time),
max_processing_time,
priority_message_count: priority_count,
avg_priority_processing_time: total_priority_nanos
.checked_div(priority_count)
.map(Duration::from_nanos)
.unwrap_or(Duration::ZERO)
.min(max_priority_processing_time),
max_priority_processing_time,
idle_event_count: idle_count,
avg_idle_processing_time: total_idle_nanos
.checked_div(idle_count)
.map(Duration::from_nanos)
.unwrap_or(Duration::ZERO)
.min(max_idle_processing_time),
max_idle_processing_time,
uptime: self.uptime_baseline().elapsed(),
last_activity: self.get_last_activity(),
}
}
#[inline]
pub fn message_count(&self) -> u64 {
self.message_count.load(Ordering::Relaxed)
}
#[inline]
pub fn avg_processing_time(&self) -> Duration {
let count = self.message_count.load(Ordering::Relaxed);
let total_nanos = self.total_processing_nanos.load(Ordering::Relaxed);
total_nanos
.checked_div(count)
.map(Duration::from_nanos)
.unwrap_or(Duration::ZERO)
.min(self.max_processing_time())
}
#[inline]
pub fn max_processing_time(&self) -> Duration {
Duration::from_nanos(self.max_processing_nanos.load(Ordering::Relaxed))
}
#[inline]
pub fn priority_message_count(&self) -> u64 {
self.priority_message_count.load(Ordering::Relaxed)
}
#[inline]
pub fn avg_priority_processing_time(&self) -> Duration {
let count = self.priority_message_count.load(Ordering::Relaxed);
let total_nanos = self.total_priority_processing_nanos.load(Ordering::Relaxed);
total_nanos
.checked_div(count)
.map(Duration::from_nanos)
.unwrap_or(Duration::ZERO)
.min(self.max_priority_processing_time())
}
#[inline]
pub fn max_priority_processing_time(&self) -> Duration {
Duration::from_nanos(self.max_priority_processing_nanos.load(Ordering::Relaxed))
}
#[inline]
pub fn idle_event_count(&self) -> u64 {
self.idle_event_count.load(Ordering::Relaxed)
}
#[inline]
pub fn avg_idle_processing_time(&self) -> Duration {
let count = self.idle_event_count.load(Ordering::Relaxed);
let total_nanos = self.total_idle_processing_nanos.load(Ordering::Relaxed);
total_nanos
.checked_div(count)
.map(Duration::from_nanos)
.unwrap_or(Duration::ZERO)
.min(self.max_idle_processing_time())
}
#[inline]
pub fn max_idle_processing_time(&self) -> Duration {
Duration::from_nanos(self.max_idle_processing_nanos.load(Ordering::Relaxed))
}
#[inline]
pub fn uptime(&self) -> Duration {
self.uptime_baseline().elapsed()
}
#[inline]
pub fn last_activity(&self) -> Option<SystemTime> {
self.get_last_activity()
}
}
impl Default for MetricsCollector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_initial_state() {
let collector = MetricsCollector::new();
let snapshot = collector.snapshot();
assert_eq!(snapshot.message_count, 0);
assert_eq!(snapshot.avg_processing_time, Duration::ZERO);
assert_eq!(snapshot.max_processing_time, Duration::ZERO);
assert!(snapshot.last_activity.is_none());
}
#[test]
fn test_record_message() {
let collector = MetricsCollector::new();
collector.record_message(Duration::from_millis(100));
collector.record_message(Duration::from_millis(200));
assert_eq!(collector.message_count(), 2);
assert_eq!(collector.avg_processing_time(), Duration::from_millis(150));
assert_eq!(collector.max_processing_time(), Duration::from_millis(200));
assert!(collector.last_activity().is_some());
}
#[test]
fn test_uptime_increases() {
let collector = MetricsCollector::new();
let uptime1 = collector.uptime();
std::thread::sleep(Duration::from_millis(10));
let uptime2 = collector.uptime();
assert!(uptime2 > uptime1);
}
#[test]
fn test_mark_started_resets_uptime_baseline() {
let collector = MetricsCollector::new();
std::thread::sleep(Duration::from_millis(20));
let before = collector.uptime();
collector.mark_started();
let after = collector.uptime();
assert!(
after < before,
"mark_started should reset the uptime baseline (before={before:?}, after={after:?})"
);
}
}