use std::time::Duration;
use crate::adapter::net::behavior::placement::{ColocationPolicy, IntentMatchPolicy, ScopeLabel};
pub const DEFAULT_PER_CHANNEL_CAP_BYTES: u64 = 100 * 1024 * 1024;
pub const MIN_PER_CHANNEL_CAP_BYTES: u64 = 1024 * 1024;
pub const DEFAULT_TOTAL_CAP_BYTES: u64 = 10 * 1024 * 1024 * 1024;
pub const DEFAULT_PROXIMITY_MAX_RTT_MS: u64 = 200;
pub const DEFAULT_BANDWIDTH_BUDGET_FRACTION: f32 = 0.25;
pub const DEFAULT_NIC_PEAK_BYTES_PER_S: u64 = 125_000_000;
pub const DEFAULT_OBSERVER_INFLIGHT_CAP: usize = 1024;
#[derive(Debug, Clone)]
pub struct GreedyConfig {
pub scopes: Vec<ScopeLabel>,
pub proximity_max_rtt: Duration,
pub per_channel_cap_bytes: u64,
pub total_cap_bytes: u64,
pub bandwidth_budget_fraction: f32,
pub nic_peak_bytes_per_s: Option<u64>,
pub intent_match: IntentMatchPolicy,
pub colocation_policy: ColocationPolicy,
pub observer_inflight_cap: usize,
}
impl Default for GreedyConfig {
fn default() -> Self {
Self {
scopes: Vec::new(),
proximity_max_rtt: Duration::from_millis(DEFAULT_PROXIMITY_MAX_RTT_MS),
per_channel_cap_bytes: DEFAULT_PER_CHANNEL_CAP_BYTES,
total_cap_bytes: DEFAULT_TOTAL_CAP_BYTES,
bandwidth_budget_fraction: DEFAULT_BANDWIDTH_BUDGET_FRACTION,
nic_peak_bytes_per_s: None,
intent_match: IntentMatchPolicy::AnyOfLocalCapabilities,
colocation_policy: ColocationPolicy::SoftPreference,
observer_inflight_cap: DEFAULT_OBSERVER_INFLIGHT_CAP,
}
}
}
impl GreedyConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_scopes(mut self, scopes: Vec<ScopeLabel>) -> Self {
self.scopes = scopes;
self
}
pub fn with_proximity_max_rtt(mut self, rtt: Duration) -> Self {
self.proximity_max_rtt = rtt;
self
}
pub fn with_per_channel_cap_bytes(mut self, cap: u64) -> Self {
self.per_channel_cap_bytes = cap;
self
}
pub fn with_total_cap_bytes(mut self, cap: u64) -> Self {
self.total_cap_bytes = cap;
self
}
pub fn with_bandwidth_budget_fraction(mut self, fraction: f32) -> Self {
self.bandwidth_budget_fraction = fraction;
self
}
pub fn with_nic_peak_bytes_per_s(mut self, peak: Option<u64>) -> Self {
self.nic_peak_bytes_per_s = peak;
self
}
pub fn with_observer_inflight_cap(mut self, cap: usize) -> Self {
self.observer_inflight_cap = cap.max(1);
self
}
pub fn effective_nic_peak_bytes_per_s(&self) -> u64 {
match self.nic_peak_bytes_per_s {
Some(v) if v > 0 => v,
_ => DEFAULT_NIC_PEAK_BYTES_PER_S,
}
}
pub fn with_intent_match(mut self, policy: IntentMatchPolicy) -> Self {
self.intent_match = policy;
self
}
pub fn with_colocation_policy(mut self, policy: ColocationPolicy) -> Self {
self.colocation_policy = policy;
self
}
pub fn validate(&self) -> Result<(), GreedyConfigError> {
if self.per_channel_cap_bytes < MIN_PER_CHANNEL_CAP_BYTES {
return Err(GreedyConfigError::PerChannelCapTooLow {
got: self.per_channel_cap_bytes,
min: MIN_PER_CHANNEL_CAP_BYTES,
});
}
if self.total_cap_bytes < self.per_channel_cap_bytes {
return Err(GreedyConfigError::TotalCapBelowPerChannel {
total: self.total_cap_bytes,
per_channel: self.per_channel_cap_bytes,
});
}
if !self.bandwidth_budget_fraction.is_finite()
|| self.bandwidth_budget_fraction <= 0.0
|| self.bandwidth_budget_fraction > 1.0
{
return Err(GreedyConfigError::BudgetFractionOutOfRange {
got: self.bandwidth_budget_fraction,
});
}
if self.proximity_max_rtt.is_zero() {
return Err(GreedyConfigError::ProximityRttZero);
}
Ok(())
}
}
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum GreedyConfigError {
#[error("greedy per_channel_cap_bytes {got} below minimum {min}")]
PerChannelCapTooLow {
got: u64,
min: u64,
},
#[error("greedy total_cap_bytes {total} must be ≥ per_channel_cap_bytes {per_channel}")]
TotalCapBelowPerChannel {
total: u64,
per_channel: u64,
},
#[error("greedy bandwidth_budget_fraction {got} outside (0.0, 1.0] or non-finite")]
BudgetFractionOutOfRange {
got: f32,
},
#[error("greedy proximity_max_rtt must be non-zero")]
ProximityRttZero,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_valid() {
GreedyConfig::default()
.validate()
.expect("defaults must validate");
}
#[test]
fn per_channel_cap_below_floor_rejected() {
let cfg = GreedyConfig::default().with_per_channel_cap_bytes(1024);
let err = cfg.validate().expect_err("1 KiB cap must reject");
assert!(matches!(
err,
GreedyConfigError::PerChannelCapTooLow { got: 1024, .. }
));
}
#[test]
fn total_cap_below_per_channel_rejected() {
let cfg = GreedyConfig::default()
.with_per_channel_cap_bytes(200 * 1024 * 1024)
.with_total_cap_bytes(100 * 1024 * 1024);
let err = cfg
.validate()
.expect_err("total below per-channel must reject");
assert!(matches!(
err,
GreedyConfigError::TotalCapBelowPerChannel { .. }
));
}
#[test]
fn budget_fraction_zero_rejected() {
let cfg = GreedyConfig::default().with_bandwidth_budget_fraction(0.0);
let err = cfg.validate().expect_err("zero fraction must reject");
assert!(matches!(
err,
GreedyConfigError::BudgetFractionOutOfRange { .. }
));
}
#[test]
fn budget_fraction_above_one_rejected() {
let cfg = GreedyConfig::default().with_bandwidth_budget_fraction(1.5);
let err = cfg.validate().expect_err("fraction above 1.0 must reject");
assert!(matches!(
err,
GreedyConfigError::BudgetFractionOutOfRange { .. }
));
}
#[test]
fn budget_fraction_nan_rejected() {
let cfg = GreedyConfig::default().with_bandwidth_budget_fraction(f32::NAN);
let err = cfg.validate().expect_err("NaN fraction must reject");
assert!(matches!(
err,
GreedyConfigError::BudgetFractionOutOfRange { .. }
));
}
#[test]
fn budget_fraction_inf_rejected() {
let cfg = GreedyConfig::default().with_bandwidth_budget_fraction(f32::INFINITY);
let err = cfg.validate().expect_err("inf fraction must reject");
assert!(matches!(
err,
GreedyConfigError::BudgetFractionOutOfRange { .. }
));
}
#[test]
fn proximity_rtt_zero_rejected() {
let cfg = GreedyConfig::default().with_proximity_max_rtt(Duration::ZERO);
let err = cfg.validate().expect_err("zero RTT must reject");
assert!(matches!(err, GreedyConfigError::ProximityRttZero));
}
#[test]
fn boundary_values_admitted() {
let cfg = GreedyConfig::default()
.with_per_channel_cap_bytes(MIN_PER_CHANNEL_CAP_BYTES)
.with_total_cap_bytes(MIN_PER_CHANNEL_CAP_BYTES)
.with_bandwidth_budget_fraction(1.0)
.with_proximity_max_rtt(Duration::from_nanos(1));
cfg.validate().expect("boundary values are admissible");
}
}