use super::config::{AllocationStrategy, DeviceInfo, LoadBalancingStrategy, MemoryUsageStats};
use super::traits::{MemoryType, OperationParameter};
use super::HardwareResult;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::SystemTime;
#[derive(Debug, Clone)]
pub struct ResourceAllocator {
pub strategy: AllocationStrategy,
pub reservations: HashMap<String, ResourceReservation>,
pub history: Vec<AllocationRecord>,
pub limits: HashMap<String, ResourceLimits>,
round_robin_cursor: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResourceReservation {
pub device_id: String,
pub resources: HashMap<String, f64>,
pub timestamp: SystemTime,
pub expiration: Option<SystemTime>,
pub id: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AllocationRecord {
pub device_id: String,
pub timestamp: SystemTime,
pub duration: std::time::Duration,
pub resources: HashMap<String, f64>,
pub operation_params: Vec<OperationParameter>,
pub success: bool,
pub performance_metrics: HashMap<String, f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResourceLimits {
pub max_cpu: f64,
pub max_memory: f64,
pub max_gpu: f64,
pub max_power: f64,
pub max_bandwidth: f64,
pub custom_limits: HashMap<String, f64>,
}
#[derive(Debug, Clone)]
pub struct LoadBalancer {
pub strategy: LoadBalancingStrategy,
pub weights: HashMap<String, f64>,
pub connections: HashMap<String, u64>,
pub load_history: HashMap<String, Vec<(SystemTime, f64)>>,
pub adaptive_thresholds: HashMap<String, f64>,
round_robin_cursor: usize,
weighted_credits: HashMap<String, f64>,
}
#[derive(Debug, Clone)]
pub struct MemoryManager {
pub pools: HashMap<String, MemoryPool>,
pub usage_tracking: HashMap<String, MemoryUsageStats>,
pub gc_schedule: HashMap<String, SystemTime>,
pub pressure_monitor: MemoryPressureMonitor,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MemoryPool {
pub id: String,
pub device_id: String,
pub total_size: usize,
pub used_size: usize,
pub available_size: usize,
pub allocated_blocks: Vec<MemoryBlock>,
pub free_blocks: Vec<MemoryBlock>,
pub fragmentation_ratio: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MemoryBlock {
pub id: String,
pub offset: usize,
pub size: usize,
pub memory_type: MemoryType,
pub allocated_at: SystemTime,
pub tags: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct MemoryPressureMonitor {
pub pressure_levels: HashMap<String, MemoryPressureLevel>,
pub pressure_history: HashMap<String, Vec<(SystemTime, f64)>>,
pub thresholds: HashMap<String, MemoryPressureThresholds>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MemoryPressureLevel {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MemoryPressureThresholds {
pub low: f64,
pub medium: f64,
pub high: f64,
pub critical: f64,
}
impl ResourceAllocator {
pub fn new(strategy: AllocationStrategy) -> Self {
Self {
strategy,
reservations: HashMap::new(),
history: Vec::new(),
limits: HashMap::new(),
round_robin_cursor: 0,
}
}
pub fn allocate(
&mut self,
requirements: &HashMap<String, f64>,
available_devices: &[DeviceInfo],
) -> HardwareResult<String> {
let selected = match self.strategy {
AllocationStrategy::FirstAvailable => available_devices.first(),
AllocationStrategy::BestFit => {
self.find_best_fit_device(requirements, available_devices)
},
AllocationStrategy::RoundRobin => self.next_round_robin_device(available_devices),
AllocationStrategy::LoadAware => self.find_least_loaded_device(available_devices),
AllocationStrategy::PerformanceOptimized => {
self.find_highest_performance_device(available_devices)
},
AllocationStrategy::PowerEfficient => {
self.find_most_power_efficient_device(available_devices)
},
};
let Some(device) = selected else {
let record = AllocationRecord {
device_id: String::new(),
timestamp: SystemTime::now(),
duration: std::time::Duration::from_secs(0),
resources: requirements.clone(),
operation_params: vec![],
success: false,
performance_metrics: HashMap::new(),
};
self.history.push(record);
return Err(super::TrustformersError::hardware_error(
"No available device satisfies the allocation requirements",
"allocate",
));
};
let device_id = device.id.clone();
let record = AllocationRecord {
device_id: device_id.clone(),
timestamp: SystemTime::now(),
duration: std::time::Duration::from_secs(0), resources: requirements.clone(),
operation_params: vec![],
success: true,
performance_metrics: HashMap::new(),
};
self.history.push(record);
Ok(device_id)
}
fn find_best_fit_device<'a>(
&self,
requirements: &HashMap<String, f64>,
available_devices: &'a [DeviceInfo],
) -> Option<&'a DeviceInfo> {
match requirements.get("memory").copied() {
Some(needed) => available_devices
.iter()
.filter(|d| d.status.memory_usage.free as f64 >= needed)
.min_by(|a, b| a.status.memory_usage.free.cmp(&b.status.memory_usage.free)),
None => available_devices.iter().max_by_key(|d| d.status.memory_usage.free),
}
}
fn next_round_robin_device<'a>(
&mut self,
available_devices: &'a [DeviceInfo],
) -> Option<&'a DeviceInfo> {
if available_devices.is_empty() {
return None;
}
let idx = self.round_robin_cursor % available_devices.len();
self.round_robin_cursor = self.round_robin_cursor.wrapping_add(1);
available_devices.get(idx)
}
fn find_least_loaded_device<'a>(
&self,
available_devices: &'a [DeviceInfo],
) -> Option<&'a DeviceInfo> {
available_devices
.iter()
.min_by(|a, b| a.status.utilization.total_cmp(&b.status.utilization))
}
fn find_highest_performance_device<'a>(
&self,
available_devices: &'a [DeviceInfo],
) -> Option<&'a DeviceInfo> {
available_devices
.iter()
.max_by_key(|d| d.capabilities.compute_units.unwrap_or(0))
}
fn find_most_power_efficient_device<'a>(
&self,
available_devices: &'a [DeviceInfo],
) -> Option<&'a DeviceInfo> {
available_devices.iter().min_by(|a, b| {
let pa = a.capabilities.power_consumption.unwrap_or(f64::INFINITY);
let pb = b.capabilities.power_consumption.unwrap_or(f64::INFINITY);
pa.total_cmp(&pb)
})
}
pub fn set_limits(&mut self, device_id: &str, limits: ResourceLimits) {
self.limits.insert(device_id.to_string(), limits);
}
pub fn get_history(&self) -> &[AllocationRecord] {
&self.history
}
}
impl LoadBalancer {
pub fn new(strategy: LoadBalancingStrategy) -> Self {
Self {
strategy,
weights: HashMap::new(),
connections: HashMap::new(),
load_history: HashMap::new(),
adaptive_thresholds: HashMap::new(),
round_robin_cursor: 0,
weighted_credits: HashMap::new(),
}
}
pub fn select_device(&mut self, available_devices: &[String]) -> HardwareResult<String> {
if available_devices.is_empty() {
return Err(super::TrustformersError::hardware_error(
"No devices available",
"allocate",
));
}
let selected = match self.strategy {
LoadBalancingStrategy::RoundRobin => self.round_robin_select(available_devices),
LoadBalancingStrategy::LeastConnections => {
self.least_connections_select(available_devices)
},
LoadBalancingStrategy::LeastUtilization => {
self.least_utilization_select(available_devices)
},
LoadBalancingStrategy::WeightedRoundRobin => {
self.weighted_round_robin_select(available_devices)
},
LoadBalancingStrategy::PerformanceBased => {
self.performance_based_select(available_devices)
},
LoadBalancingStrategy::Adaptive => self.adaptive_select(available_devices),
};
*self.connections.entry(selected.clone()).or_insert(0) += 1;
Ok(selected)
}
fn round_robin_select(&mut self, devices: &[String]) -> String {
let idx = self.round_robin_cursor % devices.len();
self.round_robin_cursor = self.round_robin_cursor.wrapping_add(1);
devices[idx].clone()
}
fn least_connections_select(&self, devices: &[String]) -> String {
devices
.iter()
.min_by_key(|device| self.connections.get(*device).unwrap_or(&0))
.cloned()
.unwrap_or_default()
}
fn least_utilization_select(&self, devices: &[String]) -> String {
devices
.iter()
.min_by(|a, b| self.latest_utilization(a).total_cmp(&self.latest_utilization(b)))
.cloned()
.unwrap_or_default()
}
fn latest_utilization(&self, device: &str) -> f64 {
self.load_history
.get(device)
.and_then(|history| history.last())
.map(|(_, utilization)| *utilization)
.unwrap_or(f64::INFINITY)
}
fn weighted_round_robin_select(&mut self, devices: &[String]) -> String {
let total_weight: f64 =
devices.iter().map(|d| self.weights.get(d).copied().unwrap_or(1.0)).sum();
for device in devices {
let weight = self.weights.get(device).copied().unwrap_or(1.0);
*self.weighted_credits.entry(device.clone()).or_insert(0.0) += weight;
}
let selected = devices
.iter()
.max_by(|a, b| {
let ca = self.weighted_credits.get(*a).copied().unwrap_or(0.0);
let cb = self.weighted_credits.get(*b).copied().unwrap_or(0.0);
ca.total_cmp(&cb)
})
.cloned()
.unwrap_or_default();
if let Some(credit) = self.weighted_credits.get_mut(&selected) {
*credit -= total_weight.max(f64::MIN_POSITIVE);
}
selected
}
fn performance_based_select(&self, devices: &[String]) -> String {
devices
.iter()
.max_by(|a, b| {
let wa = self.weights.get(*a).copied().unwrap_or(0.0);
let wb = self.weights.get(*b).copied().unwrap_or(0.0);
wa.total_cmp(&wb)
})
.cloned()
.unwrap_or_default()
}
fn adaptive_select(&self, devices: &[String]) -> String {
devices
.iter()
.max_by(|a, b| self.adaptive_headroom(a).total_cmp(&self.adaptive_headroom(b)))
.cloned()
.unwrap_or_default()
}
fn adaptive_headroom(&self, device: &str) -> f64 {
let threshold = self.adaptive_thresholds.get(device).copied().unwrap_or(1.0);
let utilization = self.latest_utilization(device);
if utilization.is_infinite() {
f64::NEG_INFINITY
} else {
threshold - utilization
}
}
pub fn set_weight(&mut self, device_id: &str, weight: f64) {
self.weights.insert(device_id.to_string(), weight);
}
}
impl MemoryManager {
pub fn new() -> Self {
Self {
pools: HashMap::new(),
usage_tracking: HashMap::new(),
gc_schedule: HashMap::new(),
pressure_monitor: MemoryPressureMonitor::new(),
}
}
pub fn allocate_memory(
&mut self,
device_id: &str,
size: usize,
memory_type: MemoryType,
) -> HardwareResult<MemoryBlock> {
let pool = self
.pools
.entry(device_id.to_string())
.or_insert_with(|| MemoryPool::new(device_id));
pool.allocate(size, memory_type)
}
pub fn deallocate_memory(&mut self, device_id: &str, block_id: &str) -> HardwareResult<()> {
if let Some(pool) = self.pools.get_mut(device_id) {
pool.deallocate(block_id)
} else {
Err(super::TrustformersError::hardware_error(
"Device not found",
"deallocate",
))
}
}
pub fn trigger_gc(&mut self, device_id: &str) -> HardwareResult<()> {
if let Some(pool) = self.pools.get_mut(device_id) {
pool.garbage_collect()?;
self.gc_schedule.insert(device_id.to_string(), SystemTime::now());
}
Ok(())
}
pub fn get_usage_stats(&self, device_id: &str) -> Option<&MemoryUsageStats> {
self.usage_tracking.get(device_id)
}
}
impl MemoryPool {
pub fn new(device_id: &str) -> Self {
Self {
id: format!("pool_{}", device_id),
device_id: device_id.to_string(),
total_size: 1024 * 1024 * 1024, used_size: 0,
available_size: 1024 * 1024 * 1024,
allocated_blocks: Vec::new(),
free_blocks: Vec::new(),
fragmentation_ratio: 0.0,
}
}
pub fn allocate(
&mut self,
size: usize,
memory_type: MemoryType,
) -> HardwareResult<MemoryBlock> {
if self.available_size < size {
return Err(super::TrustformersError::hardware_error(
"Insufficient memory",
"allocate",
));
}
let block = MemoryBlock {
id: format!(
"block_{}_{}",
self.allocated_blocks.len(),
chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
),
offset: self.used_size,
size,
memory_type,
allocated_at: SystemTime::now(),
tags: vec![],
};
self.allocated_blocks.push(block.clone());
self.used_size += size;
self.available_size -= size;
Ok(block)
}
pub fn deallocate(&mut self, block_id: &str) -> HardwareResult<()> {
if let Some(pos) = self.allocated_blocks.iter().position(|b| b.id == block_id) {
let block = self.allocated_blocks.remove(pos);
self.used_size -= block.size;
self.available_size += block.size;
self.free_blocks.push(block);
Ok(())
} else {
Err(super::TrustformersError::hardware_error(
"Block not found",
"deallocate",
))
}
}
pub fn garbage_collect(&mut self) -> HardwareResult<()> {
self.free_blocks.sort_by_key(|b| b.offset);
self.fragmentation_ratio = self.calculate_fragmentation();
Ok(())
}
fn calculate_fragmentation(&self) -> f64 {
if self.free_blocks.is_empty() {
return 0.0;
}
self.free_blocks.len() as f64 / (self.total_size / 1024) as f64
}
}
impl MemoryPressureMonitor {
pub fn new() -> Self {
Self {
pressure_levels: HashMap::new(),
pressure_history: HashMap::new(),
thresholds: HashMap::new(),
}
}
pub fn update_pressure(&mut self, device_id: &str, utilization: f64) {
let default_thresholds = MemoryPressureThresholds::default();
let thresholds = self.thresholds.get(device_id).unwrap_or(&default_thresholds);
let level = if utilization < thresholds.low {
MemoryPressureLevel::Low
} else if utilization < thresholds.medium {
MemoryPressureLevel::Medium
} else if utilization < thresholds.high {
MemoryPressureLevel::High
} else {
MemoryPressureLevel::Critical
};
self.pressure_levels.insert(device_id.to_string(), level);
let entry = self.pressure_history.entry(device_id.to_string()).or_default();
entry.push((SystemTime::now(), utilization));
if entry.len() > 1000 {
entry.drain(..500);
}
}
pub fn get_pressure_level(&self, device_id: &str) -> Option<MemoryPressureLevel> {
self.pressure_levels.get(device_id).copied()
}
pub fn set_thresholds(&mut self, device_id: &str, thresholds: MemoryPressureThresholds) {
self.thresholds.insert(device_id.to_string(), thresholds);
}
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
max_cpu: 0.8,
max_memory: 0.9,
max_gpu: 0.95,
max_power: 300.0,
max_bandwidth: 10_000_000_000.0, custom_limits: HashMap::new(),
}
}
}
impl Default for MemoryPressureThresholds {
fn default() -> Self {
Self {
low: 0.5,
medium: 0.7,
high: 0.85,
critical: 0.95,
}
}
}
impl Default for MemoryManager {
fn default() -> Self {
Self::new()
}
}
impl Default for MemoryPressureMonitor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::super::traits::{DeviceStatus, MemoryUsage};
use super::super::{DataType, HardwareCapabilities, HardwareType};
use super::*;
fn device(
id: &str,
free_memory: usize,
utilization: f64,
compute_units: Option<u32>,
power: Option<f64>,
) -> DeviceInfo {
DeviceInfo {
id: id.to_string(),
hardware_type: HardwareType::CPU,
capabilities: HardwareCapabilities {
data_types: vec![DataType::F32],
max_dimensions: 4,
memory_size: Some(free_memory),
clock_frequency: None,
compute_units,
operations: vec![],
power_consumption: power,
thermal_design_power: None,
},
status: DeviceStatus {
online: true,
busy: false,
error: None,
memory_usage: MemoryUsage {
used: 0,
total: free_memory,
free: free_memory,
fragmentation: 0.0,
},
temperature: None,
power_consumption: power,
utilization,
},
last_seen: SystemTime::now(),
weight: 1.0,
priority: 0,
tags: vec![],
}
}
#[test]
fn test_allocate_first_available_returns_real_device_id() {
let mut allocator = ResourceAllocator::new(AllocationStrategy::FirstAvailable);
let devices = vec![device("real-device-7", 1024, 0.1, Some(4), Some(50.0))];
let id = allocator
.allocate(&HashMap::new(), &devices)
.expect("allocation should succeed");
assert_eq!(id, "real-device-7");
}
#[test]
fn test_allocate_errors_when_no_devices_available() {
let mut allocator = ResourceAllocator::new(AllocationStrategy::FirstAvailable);
let result = allocator.allocate(&HashMap::new(), &[]);
assert!(
result.is_err(),
"must error rather than fabricate a device id"
);
}
#[test]
fn test_allocate_load_aware_picks_least_utilized_device() {
let mut allocator = ResourceAllocator::new(AllocationStrategy::LoadAware);
let devices = vec![
device("busy", 1024, 0.9, None, None),
device("idle", 1024, 0.05, None, None),
device("medium", 1024, 0.5, None, None),
];
let id = allocator
.allocate(&HashMap::new(), &devices)
.expect("allocation should succeed");
assert_eq!(id, "idle");
}
#[test]
fn test_allocate_performance_optimized_picks_most_compute_units() {
let mut allocator = ResourceAllocator::new(AllocationStrategy::PerformanceOptimized);
let devices = vec![
device("small", 1024, 0.0, Some(4), None),
device("big", 1024, 0.0, Some(64), None),
];
let id = allocator
.allocate(&HashMap::new(), &devices)
.expect("allocation should succeed");
assert_eq!(id, "big");
}
#[test]
fn test_allocate_power_efficient_picks_lowest_power() {
let mut allocator = ResourceAllocator::new(AllocationStrategy::PowerEfficient);
let devices = vec![
device("hungry", 1024, 0.0, None, Some(300.0)),
device("thrifty", 1024, 0.0, None, Some(15.0)),
];
let id = allocator
.allocate(&HashMap::new(), &devices)
.expect("allocation should succeed");
assert_eq!(id, "thrifty");
}
#[test]
fn test_allocate_best_fit_picks_tightest_sufficient_device() {
let mut allocator = ResourceAllocator::new(AllocationStrategy::BestFit);
let devices = vec![
device("huge", 1_000_000, 0.0, None, None),
device("snug", 200, 0.0, None, None),
device("too_small", 50, 0.0, None, None),
];
let mut requirements = HashMap::new();
requirements.insert("memory".to_string(), 100.0);
let id = allocator.allocate(&requirements, &devices).expect("allocation should succeed");
assert_eq!(
id, "snug",
"best-fit must pick the smallest device that still satisfies the requirement"
);
}
#[test]
fn test_load_balancer_round_robin_actually_cycles() {
let mut lb = LoadBalancer::new(LoadBalancingStrategy::RoundRobin);
let devices = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let first = lb.select_device(&devices).expect("select should succeed");
let second = lb.select_device(&devices).expect("select should succeed");
let third = lb.select_device(&devices).expect("select should succeed");
assert_ne!(
first, second,
"round robin must not pick the same device twice in a row"
);
assert_ne!(second, third);
}
#[test]
fn test_load_balancer_least_utilization_uses_load_history() {
let mut lb = LoadBalancer::new(LoadBalancingStrategy::LeastUtilization);
lb.load_history.insert("busy".to_string(), vec![(SystemTime::now(), 0.95)]);
lb.load_history.insert("idle".to_string(), vec![(SystemTime::now(), 0.02)]);
let devices = vec!["busy".to_string(), "idle".to_string()];
let selected = lb.select_device(&devices).expect("select should succeed");
assert_eq!(selected, "idle");
}
#[test]
fn test_load_balancer_performance_based_uses_weights() {
let mut lb = LoadBalancer::new(LoadBalancingStrategy::PerformanceBased);
lb.set_weight("weak", 1.0);
lb.set_weight("strong", 10.0);
let devices = vec!["weak".to_string(), "strong".to_string()];
let selected = lb.select_device(&devices).expect("select should succeed");
assert_eq!(selected, "strong");
}
#[test]
fn test_load_balancer_weighted_round_robin_favors_higher_weight() {
let mut lb = LoadBalancer::new(LoadBalancingStrategy::WeightedRoundRobin);
lb.set_weight("light", 1.0);
lb.set_weight("heavy", 3.0);
let devices = vec!["light".to_string(), "heavy".to_string()];
let mut heavy_count = 0;
for _ in 0..8 {
if lb.select_device(&devices).expect("select should succeed") == "heavy" {
heavy_count += 1;
}
}
assert!(
heavy_count >= 5,
"expected heavy (weight 3) to be selected more often, got {heavy_count}/8"
);
}
}