pub mod messages;
pub mod scaling;
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{sync::Arc, vec::Vec};
#[cfg(feature = "std")]
use std::sync::Arc;
use core::sync::atomic::{AtomicU64, Ordering};
use crate::constants::LCG_MULTIPLIER;
use crate::utils::BasisPoints;
pub use messages::*;
pub use scaling::*;
#[derive(Debug, Clone)]
pub struct InstanceMetrics {
pub servlet_id: Vec<u8>,
pub utilization: BasisPoints,
pub active_requests: u32,
}
pub trait LoadBalancer: Send + Sync + Default + Clone {
fn select(&self, candidates: &[InstanceMetrics]) -> Option<usize>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct LeastLoaded;
impl LoadBalancer for LeastLoaded {
fn select(&self, candidates: &[InstanceMetrics]) -> Option<usize> {
if candidates.is_empty() {
return None;
}
candidates
.iter()
.enumerate()
.min_by_key(|(_, m)| m.utilization.get())
.map(|(i, _)| i)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct PowerOfTwoChoices;
impl LoadBalancer for PowerOfTwoChoices {
fn select(&self, candidates: &[InstanceMetrics]) -> Option<usize> {
match candidates.len() {
0 => None,
1 => Some(0),
2 => {
if candidates[0].utilization <= candidates[1].utilization {
Some(0)
} else {
Some(1)
}
}
n => {
let seed = current_timestamp_ms();
let i1 = (seed as usize) % n;
let i2 = ((seed.wrapping_mul(LCG_MULTIPLIER).wrapping_add(1)) as usize) % n;
let i2 = if i1 == i2 {
(i2 + 1) % n
} else {
i2
};
if candidates[i1].utilization <= candidates[i2].utilization {
Some(i1)
} else {
Some(i2)
}
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RoundRobin {
counter: Arc<AtomicU64>,
}
impl LoadBalancer for RoundRobin {
fn select(&self, candidates: &[InstanceMetrics]) -> Option<usize> {
if candidates.is_empty() {
return None;
}
let count = self.counter.fetch_add(1, Ordering::Relaxed);
Some((count as usize) % candidates.len())
}
}
pub trait ScoringPolicy: Send + Sync {
fn score(&self, pheromone: u64, utilization: BasisPoints) -> BasisPoints;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct PheromoneScoring;
impl ScoringPolicy for PheromoneScoring {
fn score(&self, pheromone: u64, _utilization: BasisPoints) -> BasisPoints {
let inverted = 10000u64.saturating_sub(pheromone);
BasisPoints::new(inverted as u16)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct UtilizationScoring;
impl ScoringPolicy for UtilizationScoring {
fn score(&self, _pheromone: u64, utilization: BasisPoints) -> BasisPoints {
utilization
}
}
#[derive(Debug, Clone, Copy)]
pub struct CombinedScoring {
pub pheromone_weight: u16,
}
impl Default for CombinedScoring {
fn default() -> Self {
Self { pheromone_weight: 5000 } }
}
impl ScoringPolicy for CombinedScoring {
fn score(&self, pheromone: u64, utilization: BasisPoints) -> BasisPoints {
let pheromone_score = 10000u64.saturating_sub(pheromone);
let util_score = utilization.get() as u64;
let pw = self.pheromone_weight as u64;
let uw = 10000u64.saturating_sub(pw);
let combined = (pheromone_score * pw + util_score * uw) / 10000;
BasisPoints::new(combined as u16)
}
}
pub type MessageValidator = fn(&[u8]) -> bool;
pub trait MessageRouter: Send + Sync + Default + Clone {
fn route<'a>(&self, message: &[u8], registered_types: &'a [(&'static [u8], MessageValidator)]) -> Option<&'a [u8]>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct TypeBasedRouter;
impl MessageRouter for TypeBasedRouter {
fn route<'a>(&self, message: &[u8], registered_types: &'a [(&'static [u8], MessageValidator)]) -> Option<&'a [u8]> {
registered_types
.iter()
.find(|(_, validator)| validator(message))
.map(|(name, _)| *name)
}
}
#[cfg(feature = "std")]
pub fn current_timestamp_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
#[cfg(not(feature = "std"))]
pub fn current_timestamp_ms() -> u64 {
0 }