use crate::Device;
use super::core::{MemoryType, PressureLevel};
use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant};
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
#[derive(Debug, Clone)]
pub struct MemorySnapshot {
pub timestamp: Instant,
pub device_usage: HashMap<Device, DeviceMemoryUsage>,
pub host_usage: HostMemoryUsage,
pub memory_pressure: f64,
pub active_allocations: usize,
pub total_allocated: usize,
pub fragmentation_level: f64,
pub bandwidth_utilization: BandwidthUtilization,
pub efficiency_score: f64,
}
impl MemorySnapshot {
pub fn new() -> Self {
Self {
timestamp: Instant::now(),
device_usage: HashMap::new(),
host_usage: HostMemoryUsage::default(),
memory_pressure: 0.0,
active_allocations: 0,
total_allocated: 0,
fragmentation_level: 0.0,
bandwidth_utilization: BandwidthUtilization::default(),
efficiency_score: 1.0,
}
}
pub fn health_score(&self) -> f64 {
let pressure_health = 1.0 - self.memory_pressure;
let fragmentation_health = 1.0 - self.fragmentation_level;
let bandwidth_health = self.bandwidth_utilization.efficiency;
(pressure_health * 0.4) + (fragmentation_health * 0.3) + (bandwidth_health * 0.3)
}
pub fn is_under_pressure(&self) -> bool {
self.memory_pressure > 0.7
}
pub fn most_constrained_device(&self) -> Option<(&Device, &DeviceMemoryUsage)> {
self.device_usage
.iter()
.max_by(|(_, a), (_, b)| a.utilization_percent.partial_cmp(&b.utilization_percent).unwrap_or(std::cmp::Ordering::Equal))
}
pub fn total_utilization(&self) -> f64 {
let host_util = if self.host_usage.total_memory > 0 {
self.host_usage.process_memory as f64 / self.host_usage.total_memory as f64
} else {
0.0
};
let device_util: f64 = self.device_usage
.values()
.map(|usage| usage.utilization_percent / 100.0)
.sum::<f64>() / self.device_usage.len().max(1) as f64;
(host_util * 0.6) + (device_util * 0.4)
}
}
impl Default for MemorySnapshot {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct DeviceMemoryUsage {
pub total_memory: usize,
pub used_memory: usize,
pub free_memory: usize,
pub reserved_memory: usize,
pub utilization_percent: f64,
pub bandwidth_usage: f64,
pub active_transfers: usize,
pub memory_breakdown: HashMap<MemoryType, usize>,
pub pressure_indicators: DevicePressureIndicators,
}
impl DeviceMemoryUsage {
pub fn new(total_memory: usize) -> Self {
Self {
total_memory,
used_memory: 0,
free_memory: total_memory,
reserved_memory: 0,
utilization_percent: 0.0,
bandwidth_usage: 0.0,
active_transfers: 0,
memory_breakdown: HashMap::new(),
pressure_indicators: DevicePressureIndicators::default(),
}
}
pub fn update_usage(&mut self, used: usize, reserved: usize) {
self.used_memory = used;
self.reserved_memory = reserved;
self.free_memory = self.total_memory.saturating_sub(used + reserved);
self.utilization_percent = if self.total_memory > 0 {
(used as f64 / self.total_memory as f64) * 100.0
} else {
0.0
};
}
pub fn available_memory(&self) -> usize {
let fragmentation_factor = 1.0 - (self.pressure_indicators.fragmentation_level * 0.3);
(self.free_memory as f64 * fragmentation_factor) as usize
}
pub fn is_critically_low(&self) -> bool {
self.utilization_percent > 95.0 || self.pressure_indicators.pressure_level >= PressureLevel::Critical
}
pub fn effective_pressure(&self) -> PressureLevel {
let util_pressure = PressureLevel::from_f64(self.utilization_percent / 100.0);
let bandwidth_pressure = PressureLevel::from_f64(self.bandwidth_usage);
let fragmentation_pressure = PressureLevel::from_f64(self.pressure_indicators.fragmentation_level);
[util_pressure, bandwidth_pressure, fragmentation_pressure]
.iter()
.max()
.copied()
.unwrap_or(PressureLevel::None)
}
}
#[derive(Debug, Clone, Default)]
pub struct DevicePressureIndicators {
pub pressure_level: PressureLevel,
pub fragmentation_level: f64,
pub allocation_failure_rate: f64,
pub oom_events: u32,
pub transfer_congestion: f64,
}
#[derive(Debug, Clone, Default)]
pub struct HostMemoryUsage {
pub total_memory: usize,
pub available_memory: usize,
pub process_memory: usize,
pub pinned_memory: usize,
pub virtual_memory: usize,
pub cached_memory: usize,
pub buffered_memory: usize,
pub pressure_indicators: MemoryPressureIndicators,
}
impl HostMemoryUsage {
pub fn utilization_percent(&self) -> f64 {
if self.total_memory > 0 {
let used = self.total_memory - self.available_memory;
(used as f64 / self.total_memory as f64) * 100.0
} else {
0.0
}
}
pub fn process_utilization_percent(&self) -> f64 {
if self.total_memory > 0 {
(self.process_memory as f64 / self.total_memory as f64) * 100.0
} else {
0.0
}
}
pub fn is_under_pressure(&self) -> bool {
self.pressure_indicators.system_pressure >= PressureLevel::Medium ||
self.utilization_percent() > 80.0
}
pub fn effective_available(&self) -> usize {
let pressure_factor = match self.pressure_indicators.system_pressure {
PressureLevel::None => 1.0,
PressureLevel::Low => 0.9,
PressureLevel::Medium => 0.7,
PressureLevel::High => 0.5,
PressureLevel::Critical => 0.2,
};
(self.available_memory as f64 * pressure_factor) as usize
}
}
#[derive(Debug, Clone, Default)]
pub struct MemoryPressureIndicators {
pub system_pressure: PressureLevel,
pub process_pressure: PressureLevel,
pub swap_usage: usize,
pub page_fault_rate: f64,
pub allocation_failure_rate: f64,
pub compaction_events: u32,
pub oom_kills: u32,
pub reclaim_pressure: f64,
}
impl MemoryPressureIndicators {
pub fn pressure_score(&self) -> f64 {
let system_score = self.system_pressure.as_f64();
let process_score = self.process_pressure.as_f64();
let failure_score = self.allocation_failure_rate;
let reclaim_score = self.reclaim_pressure;
(system_score * 0.3) + (process_score * 0.3) + (failure_score * 0.2) + (reclaim_score * 0.2)
}
pub fn requires_immediate_action(&self) -> bool {
self.system_pressure >= PressureLevel::Critical ||
self.allocation_failure_rate > 0.1 ||
self.oom_kills > 0
}
}
#[derive(Debug, Clone)]
pub struct MemoryPressureEvent {
pub timestamp: Instant,
pub pressure_level: PressureLevel,
pub device: Option<Device>,
pub memory_type: MemoryType,
pub total_memory: usize,
pub available_memory: usize,
pub utilization_percent: f64,
pub actions_taken: Vec<PressureAction>,
pub resolution_time: Option<Duration>,
pub severity_score: f64,
pub caused_instability: bool,
}
impl MemoryPressureEvent {
pub fn new(
pressure_level: PressureLevel,
device: Option<Device>,
memory_type: MemoryType,
total_memory: usize,
available_memory: usize,
) -> Self {
let utilization_percent = if total_memory > 0 {
((total_memory - available_memory) as f64 / total_memory as f64) * 100.0
} else {
0.0
};
let severity_score = pressure_level.as_f64() * (utilization_percent / 100.0);
Self {
timestamp: Instant::now(),
pressure_level,
device,
memory_type,
total_memory,
available_memory,
utilization_percent,
actions_taken: Vec::new(),
resolution_time: None,
severity_score,
caused_instability: false,
}
}
pub fn add_action(&mut self, action: PressureAction) {
self.actions_taken.push(action);
}
pub fn resolve(&mut self) {
self.resolution_time = Some(self.timestamp.elapsed());
}
pub fn response_effectiveness(&self) -> f64 {
if self.actions_taken.is_empty() {
return 0.0;
}
let time_factor = if let Some(resolution_time) = self.resolution_time {
let seconds = resolution_time.as_secs_f64();
if seconds < 1.0 {
1.0
} else if seconds < 10.0 {
0.8
} else if seconds < 60.0 {
0.6
} else {
0.3
}
} else {
0.0 };
let action_factor = (self.actions_taken.len() as f64).min(5.0) / 5.0;
time_factor * action_factor
}
pub fn is_critical(&self) -> bool {
self.pressure_level >= PressureLevel::Critical ||
self.utilization_percent > 95.0 ||
self.caused_instability
}
}
#[derive(Debug, Clone)]
pub enum PressureAction {
FreedUnusedMemory { amount: usize },
CompactedPools { pools_affected: usize },
TriggeredGarbageCollection,
ReducedCaches { cache_reduction: usize },
SwappedToDisk { amount: usize },
KilledAllocations { count: usize },
RequestedMoreMemory { amount: usize },
DefragmentedMemory { recovered_space: usize },
FlushedBuffers { buffer_count: usize },
PausedOperations { operation_count: usize },
MovedToSlowerStorage { amount: usize },
ReducedAllocationRate { factor: f64 },
}
impl PressureAction {
pub fn memory_impact(&self) -> usize {
match self {
PressureAction::FreedUnusedMemory { amount } => *amount,
PressureAction::CompactedPools { pools_affected } => pools_affected * 1024 * 1024, PressureAction::TriggeredGarbageCollection => 0, PressureAction::ReducedCaches { cache_reduction } => *cache_reduction,
PressureAction::SwappedToDisk { amount } => *amount,
PressureAction::KilledAllocations { count } => count * 1024, PressureAction::RequestedMoreMemory { amount } => *amount,
PressureAction::DefragmentedMemory { recovered_space } => *recovered_space,
PressureAction::FlushedBuffers { buffer_count } => buffer_count * 4096, PressureAction::PausedOperations { .. } => 0,
PressureAction::MovedToSlowerStorage { amount } => *amount,
PressureAction::ReducedAllocationRate { .. } => 0, }
}
pub fn urgency_score(&self) -> f64 {
match self {
PressureAction::FreedUnusedMemory { .. } => 0.6,
PressureAction::CompactedPools { .. } => 0.7,
PressureAction::TriggeredGarbageCollection => 0.5,
PressureAction::ReducedCaches { .. } => 0.4,
PressureAction::SwappedToDisk { .. } => 0.8,
PressureAction::KilledAllocations { .. } => 0.9,
PressureAction::RequestedMoreMemory { .. } => 0.3,
PressureAction::DefragmentedMemory { .. } => 0.5,
PressureAction::FlushedBuffers { .. } => 0.3,
PressureAction::PausedOperations { .. } => 0.7,
PressureAction::MovedToSlowerStorage { .. } => 0.6,
PressureAction::ReducedAllocationRate { .. } => 0.2,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct BandwidthUtilization {
pub total_bandwidth: f64,
pub current_usage: f64,
pub peak_usage: f64,
pub efficiency: f64,
pub device_breakdown: HashMap<Device, f64>,
pub history: VecDeque<BandwidthSample>,
pub saturation_events: u32,
}
impl BandwidthUtilization {
pub fn new(total_bandwidth: f64) -> Self {
Self {
total_bandwidth,
current_usage: 0.0,
peak_usage: 0.0,
efficiency: 0.0,
device_breakdown: HashMap::new(),
history: VecDeque::new(),
saturation_events: 0,
}
}
pub fn update_usage(&mut self, current_usage: f64) {
self.current_usage = current_usage;
self.peak_usage = self.peak_usage.max(current_usage);
self.efficiency = if self.total_bandwidth > 0.0 {
(current_usage / self.total_bandwidth).min(1.0)
} else {
0.0
};
self.history.push_back(BandwidthSample {
timestamp: Instant::now(),
usage: current_usage,
});
while self.history.len() > 1000 {
self.history.pop_front();
}
if self.efficiency > 0.95 {
self.saturation_events += 1;
}
}
pub fn average_usage(&self, duration: Duration) -> f64 {
let cutoff = Instant::now() - duration;
let recent_samples: Vec<_> = self.history
.iter()
.filter(|sample| sample.timestamp > cutoff)
.collect();
if recent_samples.is_empty() {
self.current_usage
} else {
recent_samples.iter().map(|s| s.usage).sum::<f64>() / recent_samples.len() as f64
}
}
pub fn is_saturated(&self) -> bool {
self.efficiency > 0.9
}
pub fn saturation_risk(&self) -> f64 {
if self.history.len() < 10 {
return 0.0;
}
let recent: Vec<_> = self.history.iter().rev().take(10).collect();
let trend = if recent.len() >= 2 {
let first = recent.last().expect("recent should not be empty after len check").usage;
let last = recent.first().expect("recent should not be empty after len check").usage;
last - first
} else {
0.0
};
let current_risk = self.efficiency;
let trend_risk = if trend > 0.0 {
(trend / self.total_bandwidth).min(0.5)
} else {
0.0
};
(current_risk + trend_risk).min(1.0)
}
}
#[derive(Debug, Clone)]
pub struct BandwidthSample {
pub timestamp: Instant,
pub usage: f64,
}
#[derive(Debug, Clone)]
pub struct MemoryPoolStats {
pub name: String,
pub total_capacity: usize,
pub allocated_from_pool: usize,
pub peak_allocated: usize,
pub allocations_served: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub avg_allocation_size: f64,
pub efficiency_score: f64,
pub fragmentation_ratio: f64,
pub utilization_history: Vec<(Instant, f64)>,
pub pressure_level: PressureLevel,
}
impl MemoryPoolStats {
pub fn new(name: String, total_capacity: usize) -> Self {
Self {
name,
total_capacity,
allocated_from_pool: 0,
peak_allocated: 0,
allocations_served: 0,
cache_hits: 0,
cache_misses: 0,
avg_allocation_size: 0.0,
efficiency_score: 1.0,
fragmentation_ratio: 0.0,
utilization_history: Vec::new(),
pressure_level: PressureLevel::None,
}
}
pub fn hit_rate(&self) -> f64 {
let total_requests = self.cache_hits + self.cache_misses;
if total_requests > 0 {
self.cache_hits as f64 / total_requests as f64
} else {
0.0
}
}
pub fn utilization_percent(&self) -> f64 {
if self.total_capacity > 0 {
(self.allocated_from_pool as f64 / self.total_capacity as f64) * 100.0
} else {
0.0
}
}
pub fn update(&mut self, allocated: usize, allocations: u64, hits: u64, misses: u64) {
self.allocated_from_pool = allocated;
self.peak_allocated = self.peak_allocated.max(allocated);
self.allocations_served = allocations;
self.cache_hits = hits;
self.cache_misses = misses;
if allocations > 0 {
self.avg_allocation_size = allocated as f64 / allocations as f64;
}
self.utilization_history.push((Instant::now(), self.utilization_percent()));
if self.utilization_history.len() > 1000 {
self.utilization_history.remove(0);
}
self.pressure_level = PressureLevel::from_f64(self.utilization_percent() / 100.0);
}
pub fn needs_attention(&self) -> bool {
self.utilization_percent() > 90.0 ||
self.hit_rate() < 0.8 ||
self.fragmentation_ratio > 0.5
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_snapshot_creation() {
let snapshot = MemorySnapshot::new();
assert!(snapshot.health_score() > 0.0);
assert!(!snapshot.is_under_pressure());
}
#[test]
fn test_device_memory_usage() {
let mut usage = DeviceMemoryUsage::new(1024 * 1024 * 1024); usage.update_usage(512 * 1024 * 1024, 0); assert_eq!(usage.utilization_percent, 50.0);
assert!(!usage.is_critically_low());
}
#[test]
fn test_pressure_level_conversion() {
assert_eq!(PressureLevel::from_f64(0.95), PressureLevel::Critical);
assert_eq!(PressureLevel::from_f64(0.5), PressureLevel::Medium);
assert_eq!(PressureLevel::from_f64(0.1), PressureLevel::None);
}
#[test]
fn test_pressure_event() {
let mut event = MemoryPressureEvent::new(
PressureLevel::High,
None,
MemoryType::Host,
1024 * 1024 * 1024,
100 * 1024 * 1024,
);
event.add_action(PressureAction::FreedUnusedMemory { amount: 50 * 1024 * 1024 });
event.resolve();
assert!(event.response_effectiveness() > 0.0);
assert!(event.is_critical());
}
#[test]
fn test_bandwidth_utilization() {
let mut bandwidth = BandwidthUtilization::new(100.0); bandwidth.update_usage(75.0);
assert_eq!(bandwidth.efficiency, 0.75);
assert!(!bandwidth.is_saturated());
bandwidth.update_usage(95.0);
assert!(bandwidth.is_saturated());
}
#[test]
fn test_memory_pool_stats() {
let mut pool = MemoryPoolStats::new("test_pool".to_string(), 1024 * 1024);
pool.update(512 * 1024, 100, 80, 20);
assert_eq!(pool.utilization_percent(), 50.0);
assert_eq!(pool.hit_rate(), 0.8);
assert!(!pool.needs_attention());
}
#[test]
fn test_pressure_indicators() {
let mut indicators = MemoryPressureIndicators::default();
indicators.system_pressure = PressureLevel::High;
indicators.allocation_failure_rate = 0.15;
assert!(indicators.pressure_score() > 0.5);
assert!(indicators.requires_immediate_action());
}
}