use crate::error::CoreResult;
#[cfg(target_os = "linux")]
use crate::CoreError;
#[cfg(target_os = "linux")]
use std::fs;
#[derive(Debug, Clone)]
pub struct MemoryInfo {
pub total_memory: usize,
pub available_memory: usize,
pub page_size: usize,
pub bandwidth_gbps: f64,
pub latency_ns: f64,
pub numa_nodes: usize,
pub swap_info: SwapInfo,
pub pressure: MemoryPressure,
}
impl Default for MemoryInfo {
fn default() -> Self {
Self {
total_memory: (8u64 * 1024 * 1024 * 1024) as usize, available_memory: (4u64 * 1024 * 1024 * 1024) as usize, page_size: 4096,
bandwidth_gbps: 20.0,
latency_ns: 100.0,
numa_nodes: 1,
swap_info: SwapInfo::default(),
pressure: MemoryPressure::default(),
}
}
}
impl MemoryInfo {
pub fn detect() -> CoreResult<Self> {
#[cfg(target_os = "linux")]
return Self::detect_linux();
#[cfg(target_os = "windows")]
return Self::detect_windows();
#[cfg(target_os = "macos")]
return Self::detect_macos();
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
return Ok(Self::default());
}
fn parse_meminfo_value(line: &str) -> Option<u64> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 2 {
return None;
}
parts[1].parse::<u64>().ok()
}
#[cfg(target_os = "linux")]
fn detect_linux() -> CoreResult<Self> {
let meminfo = fs::read_to_string("/proc/meminfo").map_err(|e| {
CoreError::IoError(crate::error::ErrorContext::new(format!(
"Failed to read /proc/meminfo: {e}"
)))
})?;
let mut total_memory = 8 * 1024 * 1024 * 1024; let mut available_memory = 4 * 1024 * 1024 * 1024; let mut swap_total = 0;
let mut swap_free = 0;
for line in meminfo.lines() {
if line.starts_with("MemTotal:") {
if let Some(value) = Self::parse_meminfo_value(line) {
total_memory = value * 1024; }
} else if line.starts_with("MemAvailable:") {
if let Some(value) = Self::parse_meminfo_value(line) {
available_memory = value * 1024; }
} else if line.starts_with("SwapTotal:") {
if let Some(value) = Self::parse_meminfo_value(line) {
swap_total = value * 1024; }
} else if line.starts_with("SwapFree:") {
if let Some(value) = Self::parse_meminfo_value(line) {
swap_free = value * 1024; }
}
}
let page_size = Self::get_page_size();
let numa_nodes = Self::detect_numa_nodes();
let bandwidth_gbps = Self::estimate_memorybandwidth();
let latency_ns = Self::estimate_memory_latency();
let pressure = Self::detect_memory_pressure();
let swap_info = SwapInfo {
total: swap_total as usize,
free: swap_free as usize,
used: (swap_total - swap_free) as usize,
};
Ok(Self {
total_memory: total_memory as usize,
available_memory: available_memory as usize,
page_size,
bandwidth_gbps,
latency_ns,
numa_nodes,
swap_info,
pressure,
})
}
#[allow(dead_code)]
fn parse_meminfo_line(line: &str) -> Option<usize> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
parts[1].parse().ok()
} else {
None
}
}
#[allow(dead_code)]
fn get_page_size() -> usize {
4096 }
#[allow(dead_code)]
fn detect_numa_nodes() -> usize {
#[cfg(target_os = "linux")]
{
if let Ok(entries) = fs::read_dir("/sys/devices/system/node") {
let node_count = entries
.filter_map(|entry| entry.ok())
.filter(|entry| entry.file_name().to_string_lossy().starts_with("node"))
.count();
if node_count > 0 {
return node_count;
}
}
}
1 }
#[allow(dead_code)]
fn estimate_memorybandwidth() -> f64 {
#[cfg(target_os = "linux")]
{
if let Ok(content) = fs::read_to_string("/proc/meminfo") {
if content.contains("MemTotal:") {
return 25.0; }
}
}
20.0 }
#[allow(dead_code)]
fn estimate_memory_latency() -> f64 {
80.0 }
#[allow(dead_code)]
fn detect_memory_pressure() -> MemoryPressure {
#[cfg(target_os = "linux")]
{
if let Ok(content) = fs::read_to_string("/proc/pressure/memory") {
for line in content.lines() {
if line.starts_with("some avg10=") {
if let Some(pressure_str) = line.split('=').nth(1) {
if let Some(pressure_val) = pressure_str.split_whitespace().next() {
if let Ok(pressure) = pressure_val.parse::<f64>() {
return if pressure > 50.0 {
MemoryPressure::High
} else if pressure > 20.0 {
MemoryPressure::Medium
} else {
MemoryPressure::Low
};
}
}
}
}
}
}
}
MemoryPressure::Low
}
#[cfg(target_os = "windows")]
fn detect_windows() -> CoreResult<Self> {
Ok(Self::default())
}
#[cfg(target_os = "macos")]
fn detect_macos() -> CoreResult<Self> {
Ok(Self::default())
}
pub fn performance_score(&self) -> f64 {
let capacity_score =
(self.total_memory as f64 / (64.0 * 1024.0 * 1024.0 * 1024.0)).min(1.0); let bandwidth_score = (self.bandwidth_gbps / 100.0).min(1.0); let latency_score = (200.0 / self.latency_ns).min(1.0); let availability_score = self.available_memory as f64 / self.total_memory as f64;
(capacity_score + bandwidth_score + latency_score + availability_score) / 4.0
}
pub fn optimal_chunk_size(&self) -> usize {
let available_mb = self.available_memory / (1024 * 1024);
let chunk_mb = if available_mb > 1024 {
64 } else if available_mb > 256 {
16 } else {
4 };
(chunk_mb * 1024 * 1024).max(self.page_size * 16) }
pub fn is_under_pressure(&self) -> bool {
matches!(self.pressure, MemoryPressure::High)
|| (self.available_memory * 100 / self.total_memory) < 10 }
pub fn allocation_strategy(&self) -> AllocationStrategy {
if self.numa_nodes > 1 {
AllocationStrategy::NumaAware
} else if self.is_under_pressure() {
AllocationStrategy::Conservative
} else {
AllocationStrategy::Aggressive
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SwapInfo {
pub total: usize,
pub free: usize,
pub used: usize,
}
impl SwapInfo {
pub fn is_swap_active(&self) -> bool {
self.used > 0
}
pub fn usage_percentage(&self) -> f64 {
if self.total == 0 {
0.0
} else {
(self.used as f64 / self.total as f64) * 100.0
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MemoryPressure {
#[default]
Low,
Medium,
High,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllocationStrategy {
Aggressive,
Conservative,
NumaAware,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_detection() {
let memoryinfo = MemoryInfo::detect();
assert!(memoryinfo.is_ok());
let memory = memoryinfo.expect("Operation failed");
assert!(memory.total_memory > 0);
assert!(memory.available_memory > 0);
assert!(memory.available_memory <= memory.total_memory);
assert!(memory.page_size > 0);
}
#[test]
fn test_performance_score() {
let memory = MemoryInfo::default();
let score = memory.performance_score();
assert!((0.0..=1.0).contains(&score));
}
#[test]
fn test_optimal_chunk_size() {
let memory = MemoryInfo::default();
let chunk_size = memory.optimal_chunk_size();
assert!(chunk_size >= memory.page_size * 16);
}
#[test]
fn test_swap_info() {
let swap = SwapInfo {
total: 1024 * 1024 * 1024, free: 512 * 1024 * 1024, used: 512 * 1024 * 1024, };
assert!(swap.is_swap_active());
assert_eq!(swap.usage_percentage(), 50.0);
}
#[test]
fn test_allocation_strategy() {
let mut memory = MemoryInfo::default();
memory.available_memory = memory.total_memory / 20; assert_eq!(
memory.allocation_strategy(),
AllocationStrategy::Conservative
);
memory.numa_nodes = 2;
memory.available_memory = memory.total_memory / 2; assert_eq!(memory.allocation_strategy(), AllocationStrategy::NumaAware);
}
#[test]
fn test_memory_pressure() {
assert_eq!(MemoryPressure::Low, MemoryPressure::Low);
assert_ne!(MemoryPressure::Low, MemoryPressure::High);
}
}