use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use dashmap::DashMap;
use crate::instance::ModuleInstance;
#[derive(Debug, Clone)]
pub struct PoolConfig {
pub min_instances: usize,
pub max_instances: usize,
pub target_load: f32,
pub scale_up_threshold: f32,
pub scale_down_threshold: f32,
pub scale_cooldown: Duration,
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
min_instances: 1,
max_instances: 4,
target_load: 0.6,
scale_up_threshold: 0.8,
scale_down_threshold: 0.3,
scale_cooldown: Duration::from_secs(30),
}
}
}
#[derive(Debug, Clone)]
pub struct InstanceStats {
pub instance_id: usize,
pub total_requests: u64,
pub pending_requests: usize,
pub avg_latency_us: f64,
pub last_request: Option<Instant>,
pub load_factor: f32,
}
#[derive(Debug)]
pub struct PooledInstance {
id: usize,
instance: ModuleInstance,
total_requests: AtomicU64,
pending_requests: AtomicUsize,
total_latency_us: AtomicU64,
latency_count: AtomicU64,
last_request: AtomicU64,
}
impl PooledInstance {
pub fn new(id: usize, instance: ModuleInstance) -> Self {
let now = Instant::now().duration_since(Instant::now()).as_secs();
Self {
id,
instance,
total_requests: AtomicU64::new(0),
pending_requests: AtomicUsize::new(0),
total_latency_us: AtomicU64::new(0),
latency_count: AtomicU64::new(0),
last_request: AtomicU64::new(now),
}
}
pub fn record_request_start(&self) {
self.pending_requests.fetch_add(1, Ordering::AcqRel);
self.total_requests.fetch_add(1, Ordering::Relaxed);
let now = Instant::now().duration_since(Instant::now()).as_secs();
self.last_request.store(now, Ordering::Relaxed);
}
pub fn record_request_end(&self, latency_us: u64) {
self.pending_requests.fetch_sub(1, Ordering::AcqRel);
self.total_latency_us.fetch_add(latency_us, Ordering::Relaxed);
self.latency_count.fetch_add(1, Ordering::Relaxed);
}
pub fn id(&self) -> usize {
self.id
}
pub fn instance(&self) -> &ModuleInstance {
&self.instance
}
pub fn load_factor(&self) -> f32 {
let pending = self.pending_requests.load(Ordering::Acquire) as f32;
(pending / 100.0).min(1.0)
}
pub fn stats(&self) -> InstanceStats {
let total_latency = self.total_latency_us.load(Ordering::Acquire);
let count = self.latency_count.load(Ordering::Acquire);
InstanceStats {
instance_id: self.id,
total_requests: self.total_requests.load(Ordering::Acquire),
pending_requests: self.pending_requests.load(Ordering::Acquire),
avg_latency_us: if count > 0 { total_latency as f64 / count as f64 } else { 0.0 },
last_request: None, load_factor: self.load_factor(),
}
}
pub fn instance_mut(&mut self) -> &mut ModuleInstance {
&mut self.instance
}
}
#[derive(Debug, Clone, Copy, Default)]
pub enum LoadBalanceStrategy {
#[default]
LeastLoaded,
RoundRobin,
Random,
}
#[derive(Debug)]
pub struct ModulePool {
module_name: String,
instances: DashMap<usize, Arc<PooledInstance>>,
config: PoolConfig,
strategy: LoadBalanceStrategy,
rr_counter: AtomicUsize,
}
impl ModulePool {
pub fn new(module_name: String, config: PoolConfig) -> Self {
Self {
module_name,
instances: DashMap::new(),
config,
strategy: LoadBalanceStrategy::default(),
rr_counter: AtomicUsize::new(0),
}
}
pub fn with_instance(module_name: String, instance: ModuleInstance) -> Self {
let pool = Self::new(module_name, PoolConfig::default());
pool.add_instance(instance);
pool
}
pub fn add_instance(&self, instance: ModuleInstance) -> usize {
let id = self.instances.len();
let pooled = Arc::new(PooledInstance::new(id, instance));
self.instances.insert(id, pooled);
id
}
pub fn remove_instance(&self, id: usize) -> Option<Arc<PooledInstance>> {
if self.instances.len() <= self.config.min_instances {
return None; }
self.instances.remove(&id).map(|(_, v)| v)
}
pub fn select_instance(&self) -> Option<Arc<PooledInstance>> {
if self.instances.is_empty() {
return None;
}
match self.strategy {
LoadBalanceStrategy::LeastLoaded => self.select_least_loaded(),
LoadBalanceStrategy::RoundRobin => self.select_round_robin(),
LoadBalanceStrategy::Random => self.select_random(),
}
}
fn select_least_loaded(&self) -> Option<Arc<PooledInstance>> {
let mut best: Option<(usize, f32)> = None;
for entry in self.instances.iter() {
let load = entry.value().load_factor();
if best.is_none() || load < best.unwrap().1 {
best = Some((*entry.key(), load));
}
}
best.and_then(|(id, _)| self.instances.get(&id).map(|e| e.clone()))
}
fn select_round_robin(&self) -> Option<Arc<PooledInstance>> {
let count = self.instances.len();
if count == 0 {
return None;
}
let idx = self.rr_counter.fetch_add(1, Ordering::Relaxed) % count;
self.instances.get(&idx).map(|e| e.clone())
}
fn select_random(&self) -> Option<Arc<PooledInstance>> {
let count = self.instances.len();
if count == 0 {
return None;
}
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
let hasher = RandomState::new().build_hasher();
let idx = hasher.finish() as usize % count;
self.instances.get(&idx).map(|e| e.clone())
}
pub fn avg_load(&self) -> f32 {
let mut total = 0.0;
let count = self.instances.len();
if count == 0 {
return 0.0;
}
for entry in self.instances.iter() {
total += entry.value().load_factor();
}
total / count as f32
}
pub fn should_scale_up(&self) -> bool {
if self.instances.len() >= self.config.max_instances {
return false;
}
let avg_load = self.avg_load();
avg_load >= self.config.scale_up_threshold
}
pub fn should_scale_down(&self) -> bool {
if self.instances.len() <= self.config.min_instances {
return false;
}
let avg_load = self.avg_load();
avg_load <= self.config.scale_down_threshold
}
pub fn stats(&self) -> PoolStats {
let mut total_requests = 0;
let mut total_pending = 0;
let mut instance_stats = Vec::new();
for entry in self.instances.iter() {
let stats = entry.value().stats();
total_requests += stats.total_requests;
total_pending += stats.pending_requests;
instance_stats.push(stats);
}
PoolStats {
module_name: self.module_name.clone(),
instance_count: self.instances.len(),
total_requests,
total_pending,
avg_load: self.avg_load(),
instances: instance_stats,
}
}
pub fn module_name(&self) -> &str {
&self.module_name
}
pub fn instance_count(&self) -> usize {
self.instances.len()
}
pub fn all_instances(&self) -> Vec<Arc<PooledInstance>> {
self.instances.iter().map(|e| e.value().clone()).collect()
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub module_name: String,
pub instance_count: usize,
pub total_requests: u64,
pub total_pending: usize,
pub avg_load: f32,
pub instances: Vec<InstanceStats>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_creation() {
let pool = ModulePool::new("test".to_string(), PoolConfig::default());
assert_eq!(pool.instance_count(), 0);
assert_eq!(pool.module_name(), "test");
}
#[test]
fn test_pool_add_instance() {
let pool = ModulePool::new("test".to_string(), PoolConfig::default());
assert_eq!(pool.instance_count(), 0);
}
#[test]
fn test_pool_config() {
let config = PoolConfig {
min_instances: 2,
max_instances: 8,
target_load: 0.5,
scale_up_threshold: 0.7,
scale_down_threshold: 0.2,
scale_cooldown: Duration::from_secs(60),
};
assert_eq!(config.min_instances, 2);
assert_eq!(config.max_instances, 8);
assert_eq!(config.target_load, 0.5);
}
}