use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
use std::time::Instant;
pub type ClientId = u64;
#[derive(Debug)]
pub struct ClientResources {
client_id: ClientId,
total_requests: AtomicU64,
requests_in_window: AtomicU32,
pending_requests: AtomicUsize,
rejected_requests: AtomicU64,
last_request: AtomicU64,
priority: AtomicU32,
}
impl ClientResources {
pub fn new(client_id: ClientId) -> Self {
let now = Instant::now().duration_since(Instant::now()).as_secs();
Self {
client_id,
total_requests: AtomicU64::new(0),
requests_in_window: AtomicU32::new(0),
pending_requests: AtomicUsize::new(0),
rejected_requests: AtomicU64::new(0),
last_request: AtomicU64::new(now),
priority: AtomicU32::new(0),
}
}
pub fn record_request(&self) {
self.total_requests.fetch_add(1, Ordering::Relaxed);
self.requests_in_window.fetch_add(1, Ordering::Relaxed);
self.pending_requests.fetch_add(1, Ordering::AcqRel);
let now = Instant::now().duration_since(Instant::now()).as_secs();
self.last_request.store(now, Ordering::Relaxed);
}
pub fn record_response(&self) {
self.pending_requests.fetch_sub(1, Ordering::AcqRel);
}
pub fn record_rejection(&self) {
self.rejected_requests.fetch_add(1, Ordering::Relaxed);
}
pub fn reset_window(&self) {
self.requests_in_window.store(0, Ordering::Relaxed);
}
pub fn client_id(&self) -> ClientId {
self.client_id
}
pub fn total_requests(&self) -> u64 {
self.total_requests.load(Ordering::Acquire)
}
pub fn requests_in_window(&self) -> u32 {
self.requests_in_window.load(Ordering::Acquire)
}
pub fn pending_requests(&self) -> usize {
self.pending_requests.load(Ordering::Acquire)
}
pub fn rejected_requests(&self) -> u64 {
self.rejected_requests.load(Ordering::Acquire)
}
pub fn priority(&self) -> u32 {
self.priority.load(Ordering::Acquire)
}
pub fn set_priority(&self, priority: u32) {
self.priority.store(priority, Ordering::Release);
}
pub fn load_factor(&self) -> f32 {
let pending = self.pending_requests.load(Ordering::Acquire) as f32;
let window = self.requests_in_window.load(Ordering::Acquire) as f32;
(pending / 100.0).min(1.0) * 0.6 + (window / 1000.0).min(1.0) * 0.4
}
}
#[derive(Debug, Clone)]
pub struct ClientQuota {
pub max_requests_per_sec: u32,
pub max_pending: usize,
pub max_rejection_rate: f32,
}
impl Default for ClientQuota {
fn default() -> Self {
Self {
max_requests_per_sec: 500,
max_pending: 50,
max_rejection_rate: 0.1, }
}
}
impl ClientQuota {
pub fn new(max_requests_per_sec: u32, max_pending: usize) -> Self {
Self {
max_requests_per_sec,
max_pending,
max_rejection_rate: 0.1,
}
}
pub fn is_exceeded(&self, client: &ClientResources) -> bool {
if client.pending_requests() >= self.max_pending {
return true;
}
if client.requests_in_window() >= self.max_requests_per_sec {
return true;
}
let total = client.total_requests();
if total > 0 {
let rejected = client.rejected_requests();
let rejection_rate = rejected as f32 / total as f32;
if rejection_rate > self.max_rejection_rate {
return true;
}
}
false
}
}
#[derive(Debug)]
pub struct ClientRegistry {
clients: dashmap::DashMap<ClientId, Arc<ClientResources>>,
default_quota: ClientQuota,
client_quotas: dashmap::DashMap<ClientId, ClientQuota>,
}
impl ClientRegistry {
pub fn new() -> Self {
Self {
clients: dashmap::DashMap::new(),
default_quota: ClientQuota::default(),
client_quotas: dashmap::DashMap::new(),
}
}
pub fn with_default_quota(quota: ClientQuota) -> Self {
Self {
clients: dashmap::DashMap::new(),
default_quota: quota,
client_quotas: dashmap::DashMap::new(),
}
}
pub fn get_or_create(&self, client_id: ClientId) -> Arc<ClientResources> {
self.clients
.entry(client_id)
.or_insert_with(|| Arc::new(ClientResources::new(client_id)))
.clone()
}
pub fn register_client(&self, client_id: ClientId, quota: ClientQuota) {
self.get_or_create(client_id);
self.client_quotas.insert(client_id, quota);
}
pub fn get_quota(&self, client_id: ClientId) -> ClientQuota {
self.client_quotas
.get(&client_id)
.map(|q| q.clone())
.unwrap_or_else(|| self.default_quota.clone())
}
pub fn can_admit(&self, client_id: ClientId) -> bool {
let client = self.get_or_create(client_id);
let quota = self.get_quota(client_id);
!quota.is_exceeded(&client)
}
pub fn record_request(&self, client_id: ClientId) -> bool {
if !self.can_admit(client_id) {
let client = self.get_or_create(client_id);
client.record_rejection();
return false;
}
let client = self.get_or_create(client_id);
client.record_request();
true
}
pub fn record_response(&self, client_id: ClientId) {
let client = self.get_or_create(client_id);
client.record_response();
}
pub fn client_stats(&self, client_id: ClientId) -> Option<ClientStats> {
self.clients.get(&client_id).map(|c| ClientStats {
client_id: c.client_id(),
total_requests: c.total_requests(),
pending_requests: c.pending_requests(),
rejected_requests: c.rejected_requests(),
load_factor: c.load_factor(),
priority: c.priority(),
})
}
pub fn all_client_ids(&self) -> Vec<ClientId> {
self.clients.iter().map(|e| *e.key()).collect()
}
pub fn client_count(&self) -> usize {
self.clients.len()
}
pub fn reset_all_windows(&self) {
for entry in self.clients.iter() {
entry.value().reset_window();
}
}
pub fn aggregate_stats(&self) -> AggregateClientStats {
let mut total_requests = 0;
let mut total_pending = 0;
let mut total_rejected = 0;
let mut max_load: f32 = 0.0;
for entry in self.clients.iter() {
let client = entry.value();
total_requests += client.total_requests();
total_pending += client.pending_requests();
total_rejected += client.rejected_requests();
max_load = max_load.max(client.load_factor());
}
AggregateClientStats {
client_count: self.clients.len(),
total_requests,
total_pending,
total_rejected,
max_load_factor: max_load,
}
}
}
impl Default for ClientRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ClientStats {
pub client_id: ClientId,
pub total_requests: u64,
pub pending_requests: usize,
pub rejected_requests: u64,
pub load_factor: f32,
pub priority: u32,
}
#[derive(Debug, Clone)]
pub struct AggregateClientStats {
pub client_count: usize,
pub total_requests: u64,
pub total_pending: usize,
pub total_rejected: u64,
pub max_load_factor: f32,
}
use std::sync::Arc;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_resources() {
let client = ClientResources::new(1);
assert_eq!(client.total_requests(), 0);
assert_eq!(client.pending_requests(), 0);
client.record_request();
client.record_request();
assert_eq!(client.total_requests(), 2);
assert_eq!(client.pending_requests(), 2);
assert_eq!(client.requests_in_window(), 2);
client.record_response();
client.record_response();
assert_eq!(client.pending_requests(), 0);
}
#[test]
fn test_client_quota() {
let quota = ClientQuota::new(10, 5);
let client = ClientResources::new(1);
assert!(!quota.is_exceeded(&client));
for _ in 0..5 {
client.record_request();
}
assert!(quota.is_exceeded(&client));
}
#[test]
fn test_client_registry() {
let registry = ClientRegistry::new();
let client = registry.get_or_create(1);
assert_eq!(client.client_id(), 1);
assert!(registry.record_request(1));
assert!(registry.record_request(1));
let stats = registry.client_stats(1).unwrap();
assert_eq!(stats.total_requests, 2);
assert_eq!(stats.pending_requests, 2);
}
#[test]
fn test_client_rejection() {
let quota = ClientQuota::new(2, 10); let registry = ClientRegistry::with_default_quota(quota);
assert!(registry.record_request(1));
assert!(registry.record_request(1));
assert!(!registry.record_request(1));
let stats = registry.client_stats(1).unwrap();
assert_eq!(stats.rejected_requests, 1);
}
#[test]
fn test_load_factor() {
let client = ClientResources::new(1);
assert_eq!(client.load_factor(), 0.0);
for _ in 0..50 {
client.record_request();
}
let load = client.load_factor();
assert!(load > 0.0);
assert!(load < 1.0);
for _ in 0..100 {
client.record_request();
}
let load = client.load_factor();
assert!(load > 0.5);
}
}