#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MetricMode {
Max { ceiling: f32 },
Target { setpoint: f32 },
Spread { per_worker: f32 },
Unlimited,
}
impl MetricMode {
#[inline]
pub fn is_within(self, current: f32) -> bool {
match self {
Self::Max { ceiling } => current <= ceiling,
Self::Target { setpoint } => (current - setpoint).abs() <= 0.05,
Self::Spread { per_worker } => current <= per_worker * 1.1,
Self::Unlimited => true,
}
}
#[inline]
pub fn headroom(self, current: f32) -> f32 {
match self {
Self::Max { ceiling } => (ceiling - current).max(0.0),
Self::Target { setpoint } => (setpoint - current).max(0.0),
Self::Spread { per_worker } => (per_worker - current).max(0.0),
Self::Unlimited => 1.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MetricPolicy {
pub mode: MetricMode,
}
impl MetricPolicy {
#[inline]
pub const fn new(mode: MetricMode) -> Self {
Self { mode }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RebalanceStrategy {
#[default]
ThrottleAdmission,
ShedLoad,
ScaleOut,
ScaleIn,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ResourcePolicy {
pub cpu: MetricPolicy,
pub memory: MetricPolicy,
pub rebalance: RebalanceStrategy,
}
impl ResourcePolicy {
pub fn memory_cap_max_cpu(mem_ceiling: f32) -> Self {
Self {
memory: MetricPolicy::new(MetricMode::Max {
ceiling: mem_ceiling,
}),
cpu: MetricPolicy::new(MetricMode::Max { ceiling: 1.0 }),
rebalance: RebalanceStrategy::ThrottleAdmission,
}
}
pub fn unlimited_memory_cpu_spread(per_worker: f32) -> Self {
Self {
memory: MetricPolicy::new(MetricMode::Unlimited),
cpu: MetricPolicy::new(MetricMode::Spread { per_worker }),
rebalance: RebalanceStrategy::ThrottleAdmission,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WorkProfile {
CpuIntensive,
IoBound,
MemoryHeavy,
Remote,
#[default]
Mixed,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn max_headroom_when_below_ceiling() {
let mode = MetricMode::Max { ceiling: 0.85 };
assert!(mode.is_within(0.61));
assert!((mode.headroom(0.61) - 0.24).abs() < 0.001);
}
#[test]
fn max_breach_when_over_ceiling() {
let mode = MetricMode::Max { ceiling: 0.85 };
assert!(!mode.is_within(0.90));
assert_eq!(mode.headroom(0.90), 0.0);
}
#[test]
fn target_within_setpoint_band() {
let mode = MetricMode::Target { setpoint: 0.5 };
assert!(mode.is_within(0.52));
assert!(!mode.is_within(0.60));
assert!((mode.headroom(0.4) - 0.1).abs() < 0.001);
}
#[test]
fn spread_respects_per_worker_share() {
let mode = MetricMode::Spread { per_worker: 0.25 };
assert!(mode.is_within(0.20));
assert!(!mode.is_within(0.30));
assert!((mode.headroom(0.10) - 0.15).abs() < 0.001);
}
#[test]
fn unlimited_always_within() {
let mode = MetricMode::Unlimited;
assert!(mode.is_within(1.0));
assert_eq!(mode.headroom(0.9), 1.0);
}
#[test]
fn cpu_spread_policy_constructor() {
let policy = ResourcePolicy::unlimited_memory_cpu_spread(0.25);
assert_eq!(policy.rebalance, RebalanceStrategy::ThrottleAdmission);
assert!(matches!(policy.cpu.mode, MetricMode::Spread { .. }));
assert!(matches!(policy.memory.mode, MetricMode::Unlimited));
}
#[test]
fn work_profile_default_is_mixed() {
assert_eq!(WorkProfile::default(), WorkProfile::Mixed);
}
#[test]
fn metric_policy_new_stores_mode() {
let policy = MetricPolicy::new(MetricMode::Unlimited);
assert_eq!(policy.mode, MetricMode::Unlimited);
}
#[test]
fn rebalance_strategy_default_throttles_admission() {
assert_eq!(
RebalanceStrategy::default(),
RebalanceStrategy::ThrottleAdmission
);
}
#[test]
fn memory_cap_policy_sets_cpu_unbounded() {
let policy = ResourcePolicy::memory_cap_max_cpu(0.75);
assert!(
matches!(policy.memory.mode, MetricMode::Max { ceiling } if (ceiling - 0.75).abs() < 0.001)
);
assert!(matches!(policy.cpu.mode, MetricMode::Max { ceiling: 1.0 }));
}
}