use dashmap::{DashMap, DashSet};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tracing::info;
const AGGREGATION_WINDOW: Duration = Duration::from_secs(30);
use crate::constants::user::ANONYMOUS;
use crate::types::ClientId;
#[derive(Debug)]
struct UserConnectionStats {
count: AtomicU64,
routing_mode: &'static str,
first_seen: Instant,
last_seen: Mutex<Instant>,
}
impl UserConnectionStats {
const fn new(routing_mode: &'static str, timestamp: Instant) -> Self {
Self {
count: AtomicU64::new(1),
routing_mode,
first_seen: timestamp,
last_seen: Mutex::new(timestamp),
}
}
fn record_event(&self, timestamp: Instant) {
self.count.fetch_add(1, Ordering::Relaxed);
if let Ok(mut last) = self.last_seen.lock() {
*last = timestamp;
}
}
#[inline]
fn duration_secs(&self) -> f64 {
self.last_seen.lock().ok().map_or(0.0, |last| {
last.duration_since(self.first_seen).as_secs_f64()
})
}
fn get_count(&self) -> u64 {
self.count.load(Ordering::Relaxed)
}
fn log_connection(&self, username: &str) {
let duration = self.duration_secs();
let count = self.get_count();
let noun = if count == 1 {
"connection"
} else {
"connections"
};
info!(
username = %username,
count = count,
routing_mode = %self.routing_mode,
duration_secs = duration,
"User {} created {} {} in {} in {:.1}s",
username, count, noun, self.routing_mode, duration
);
}
fn log_disconnection(&self, username: &str) {
let duration = self.duration_secs();
let count = self.get_count();
let noun = if count == 1 { "session" } else { "sessions" };
info!(
username = %username,
count = count,
routing_mode = %self.routing_mode,
duration_secs = duration,
"{} {} closed for {} in {} over {:.1}s",
count, noun, username, self.routing_mode, duration
);
}
}
#[derive(Clone, Debug)]
pub struct ConnectionStatsAggregator {
connection_stats: Arc<DashMap<String, UserConnectionStats>>,
disconnection_stats: Arc<DashMap<String, UserConnectionStats>>,
seen_connections: Arc<DashSet<ClientId>>,
seen_disconnections: Arc<DashSet<ClientId>>,
last_flush: Arc<Mutex<Instant>>,
}
impl ConnectionStatsAggregator {
#[must_use]
pub fn new() -> Self {
Self {
connection_stats: Arc::new(DashMap::new()),
disconnection_stats: Arc::new(DashMap::new()),
seen_connections: Arc::new(DashSet::new()),
seen_disconnections: Arc::new(DashSet::new()),
last_flush: Arc::new(Mutex::new(Instant::now())),
}
}
fn maybe_flush(&self, now: Instant, force: bool) {
if let Ok(mut last_flush) = self.last_flush.lock() {
let should_flush = force
|| (now.duration_since(*last_flush) >= AGGREGATION_WINDOW
&& (!self.connection_stats.is_empty() || !self.disconnection_stats.is_empty()));
if should_flush {
let log_and_drain =
|stats: &DashMap<String, UserConnectionStats>,
log_fn: fn(&UserConnectionStats, &str)| {
stats
.iter()
.for_each(|entry| log_fn(entry.value(), entry.key()));
stats.clear();
};
log_and_drain(&self.connection_stats, UserConnectionStats::log_connection);
log_and_drain(
&self.disconnection_stats,
UserConnectionStats::log_disconnection,
);
self.seen_disconnections.clear();
*last_flush = now;
}
}
}
fn record_event(
&self,
username: Option<&str>,
routing_mode: &'static str,
is_connection: bool,
) {
let now = Instant::now();
if !self.connection_stats.is_empty() || !self.disconnection_stats.is_empty() {
self.maybe_flush(now, false);
}
let stats = if is_connection {
&self.connection_stats
} else {
&self.disconnection_stats
};
let username = username.unwrap_or(ANONYMOUS).to_string();
stats
.entry(username)
.and_modify(|s| s.record_event(now))
.or_insert_with(|| UserConnectionStats::new(routing_mode, now));
}
pub fn record_connection(&self, username: Option<&str>, routing_mode: &'static str) {
self.record_event(username, routing_mode, true);
}
pub fn record_session_connection(
&self,
client_id: ClientId,
username: Option<&str>,
routing_mode: &'static str,
) {
if !self.seen_connections.insert(client_id) {
return;
}
self.record_event(username, routing_mode, true);
}
pub fn record_disconnection(&self, username: Option<&str>, routing_mode: &'static str) {
self.record_event(username, routing_mode, false);
}
pub fn record_session_disconnection(
&self,
client_id: ClientId,
username: Option<&str>,
routing_mode: &'static str,
) {
if !self.seen_disconnections.insert(client_id) {
return;
}
self.seen_connections.remove(&client_id);
self.record_event(username, routing_mode, false);
}
pub fn flush(&self) {
self.maybe_flush(Instant::now(), true);
}
}
impl Default for ConnectionStatsAggregator {
fn default() -> Self {
Self::new()
}
}
impl ConnectionStatsAggregator {
#[must_use]
pub fn connection_count(&self, username: &str) -> Option<u64> {
self.connection_stats
.get(username)
.map(|stats| stats.get_count())
}
#[must_use]
pub fn user_count(&self) -> usize {
self.connection_stats.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_connection_stats_new() {
let now = Instant::now();
let stats = UserConnectionStats::new("per-command", now);
assert_eq!(stats.get_count(), 1);
assert_eq!(stats.routing_mode, "per-command");
assert_eq!(stats.first_seen, now);
}
#[test]
fn test_user_connection_stats_record_event() {
let now = Instant::now();
let stats = UserConnectionStats::new("hybrid", now);
assert_eq!(stats.get_count(), 1);
let later = now + Duration::from_secs(5);
stats.record_event(later);
assert_eq!(stats.get_count(), 2);
assert!(stats.duration_secs() >= 5.0);
}
#[test]
fn test_user_connection_stats_duration_secs() {
let now = Instant::now();
let stats = UserConnectionStats::new("stateful", now);
assert!(stats.duration_secs() < 0.1);
let later = now + Duration::from_secs(3);
stats.record_event(later);
let duration = stats.duration_secs();
assert!((3.0..3.1).contains(&duration));
}
#[test]
fn test_connection_stats_aggregator_new() {
let aggregator = ConnectionStatsAggregator::new();
assert_eq!(aggregator.user_count(), 0);
assert_eq!(aggregator.connection_count("test"), None);
}
#[test]
fn test_connection_stats_aggregator_default() {
let aggregator = ConnectionStatsAggregator::default();
assert_eq!(aggregator.user_count(), 0);
}
#[test]
fn test_record_connection_single_user() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(Some("alice"), "per-command");
assert_eq!(aggregator.user_count(), 1);
assert_eq!(aggregator.connection_count("alice"), Some(1));
}
#[test]
fn test_record_connection_multiple_events_same_user() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(Some("bob"), "hybrid");
aggregator.record_connection(Some("bob"), "hybrid");
aggregator.record_connection(Some("bob"), "hybrid");
assert_eq!(aggregator.user_count(), 1);
assert_eq!(aggregator.connection_count("bob"), Some(3));
}
#[test]
fn test_record_connection_multiple_users() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(Some("alice"), "per-command");
aggregator.record_connection(Some("bob"), "hybrid");
aggregator.record_connection(Some("charlie"), "stateful");
assert_eq!(aggregator.user_count(), 3);
assert_eq!(aggregator.connection_count("alice"), Some(1));
assert_eq!(aggregator.connection_count("bob"), Some(1));
assert_eq!(aggregator.connection_count("charlie"), Some(1));
}
#[test]
fn test_record_connection_anonymous() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(None, "per-command");
assert_eq!(aggregator.user_count(), 1);
assert_eq!(aggregator.connection_count(ANONYMOUS), Some(1));
}
#[test]
fn test_record_disconnection() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_disconnection(Some("alice"), "per-command");
assert_eq!(aggregator.user_count(), 0); }
#[test]
fn test_flush_clears_stats() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(Some("alice"), "per-command");
aggregator.record_connection(Some("bob"), "hybrid");
assert_eq!(aggregator.user_count(), 2);
aggregator.flush();
assert_eq!(aggregator.user_count(), 0);
assert_eq!(aggregator.connection_count("alice"), None);
assert_eq!(aggregator.connection_count("bob"), None);
}
#[test]
fn test_aggregator_clone() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(Some("alice"), "per-command");
let cloned = aggregator;
assert_eq!(cloned.user_count(), 1);
assert_eq!(cloned.connection_count("alice"), Some(1));
}
#[test]
fn test_connection_count_nonexistent_user() {
let aggregator = ConnectionStatsAggregator::new();
assert_eq!(aggregator.connection_count("nonexistent"), None);
}
#[test]
fn test_user_connection_stats_log_connection_single() {
let now = Instant::now();
let stats = UserConnectionStats::new("hybrid", now);
stats.log_connection("testuser");
}
#[test]
fn test_user_connection_stats_log_connection_plural() {
let now = Instant::now();
let stats = UserConnectionStats::new("per-command", now);
stats.record_event(now + Duration::from_secs(1));
stats.record_event(now + Duration::from_secs(2));
stats.log_connection("testuser");
}
#[test]
fn test_user_connection_stats_log_disconnection_single() {
let now = Instant::now();
let stats = UserConnectionStats::new("stateful", now);
stats.log_disconnection("testuser");
}
#[test]
fn test_user_connection_stats_log_disconnection_plural() {
let now = Instant::now();
let stats = UserConnectionStats::new("hybrid", now);
stats.record_event(now + Duration::from_secs(1));
stats.record_event(now + Duration::from_secs(2));
stats.log_disconnection("testuser");
}
#[test]
fn test_record_connection_with_empty_username() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(Some(""), "hybrid");
assert_eq!(aggregator.user_count(), 1);
assert_eq!(aggregator.connection_count(""), Some(1));
}
#[test]
fn test_multiple_disconnections_same_user() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_disconnection(Some("alice"), "hybrid");
aggregator.record_disconnection(Some("alice"), "hybrid");
aggregator.record_disconnection(Some("alice"), "hybrid");
assert_eq!(aggregator.user_count(), 0); }
#[test]
fn test_anonymous_user_constant() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(None, "per-command");
aggregator.record_connection(None, "per-command");
assert_eq!(aggregator.connection_count(ANONYMOUS), Some(2));
}
#[test]
fn test_routing_mode_preserved() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(Some("user1"), "hybrid");
aggregator.record_connection(Some("user2"), "stateful");
aggregator.record_connection(Some("user3"), "per-command");
assert_eq!(aggregator.user_count(), 3);
}
#[test]
fn test_duration_zero_for_single_event() {
let now = Instant::now();
let stats = UserConnectionStats::new("hybrid", now);
let duration = stats.duration_secs();
assert!(duration < 0.01);
}
#[test]
fn test_get_count_after_multiple_records() {
let now = Instant::now();
let stats = UserConnectionStats::new("stateful", now);
for i in 1..=10 {
stats.record_event(now + Duration::from_millis(i * 100));
}
assert_eq!(stats.get_count(), 11); }
#[test]
fn test_aggregator_flush_with_no_stats() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.flush();
assert_eq!(aggregator.user_count(), 0);
}
#[test]
fn test_aggregator_clone_independence() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(Some("alice"), "hybrid");
let cloned = aggregator.clone();
assert_eq!(aggregator.connection_count("alice"), Some(1));
assert_eq!(cloned.connection_count("alice"), Some(1));
aggregator.record_connection(Some("alice"), "hybrid");
assert_eq!(aggregator.connection_count("alice"), Some(2));
assert_eq!(cloned.connection_count("alice"), Some(2));
}
#[test]
fn test_flush_after_connection_and_disconnection() {
let aggregator = ConnectionStatsAggregator::new();
aggregator.record_connection(Some("alice"), "hybrid");
aggregator.record_disconnection(Some("bob"), "stateful");
assert_eq!(aggregator.user_count(), 1);
aggregator.flush();
assert_eq!(aggregator.user_count(), 0);
assert_eq!(aggregator.connection_count("alice"), None);
}
#[test]
fn test_record_session_connection_deduplicates_same_client() {
let aggregator = ConnectionStatsAggregator::new();
let client_id = ClientId::new();
aggregator.record_session_connection(client_id, Some("alice"), "hybrid");
aggregator.record_session_connection(client_id, Some("alice"), "hybrid");
assert_eq!(aggregator.connection_count("alice"), Some(1));
}
#[test]
fn test_record_session_disconnection_deduplicates_same_client() {
let aggregator = ConnectionStatsAggregator::new();
let client_id = ClientId::new();
aggregator.record_session_disconnection(client_id, Some("alice"), "hybrid");
aggregator.record_session_disconnection(client_id, Some("alice"), "hybrid");
assert_eq!(aggregator.user_count(), 0);
}
}