use crate::core::error::PlottingError;
use std::fs;
use std::io::Read;
pub fn get_total_memory() -> Result<u64, PlottingError> {
let mut contents = String::new();
fs::File::open("/proc/meminfo")
.and_then(|mut f| f.read_to_string(&mut contents))
.map_err(|e| PlottingError::SystemError(format!("Failed to read /proc/meminfo: {}", e)))?;
for line in contents.lines() {
if line.starts_with("MemTotal:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let kb = parts[1].parse::<u64>().map_err(|e| {
PlottingError::SystemError(format!("Failed to parse memory size: {}", e))
})?;
return Ok(kb * 1024); }
}
}
Err(PlottingError::SystemError(
"Could not find MemTotal in /proc/meminfo".to_string(),
))
}
pub fn get_available_memory() -> Result<u64, PlottingError> {
let mut contents = String::new();
fs::File::open("/proc/meminfo")
.and_then(|mut f| f.read_to_string(&mut contents))
.map_err(|e| PlottingError::SystemError(format!("Failed to read /proc/meminfo: {}", e)))?;
for line in contents.lines() {
if line.starts_with("MemAvailable:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let kb = parts[1].parse::<u64>().map_err(|e| {
PlottingError::SystemError(format!("Failed to parse memory size: {}", e))
})?;
return Ok(kb * 1024); }
}
}
let mut mem_free = 0u64;
let mut buffers = 0u64;
let mut cached = 0u64;
for line in contents.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let value = parts[1].parse::<u64>().unwrap_or(0);
match parts[0] {
"MemFree:" => mem_free = value,
"Buffers:" => buffers = value,
"Cached:" => cached = value,
_ => {}
}
}
}
Ok((mem_free + buffers + cached) * 1024) }
pub fn get_numa_nodes() -> usize {
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")
&& entry
.file_name()
.to_string_lossy()
.chars()
.skip(4)
.all(|c| c.is_ascii_digit())
})
.count();
if node_count > 0 {
return node_count;
}
}
if let Ok(contents) = fs::read_to_string("/proc/cpuinfo") {
let physical_ids: std::collections::HashSet<_> = contents
.lines()
.filter(|line| line.starts_with("physical id"))
.map(|line| line.split(':').nth(1).unwrap_or("").trim())
.collect();
if physical_ids.len() > 1 {
return physical_ids.len();
}
}
1 }
pub fn check_hugepage_support() -> bool {
if let Ok(contents) = fs::read_to_string("/proc/meminfo") {
for line in contents.lines() {
if line.starts_with("HugePages_Total:") {
return true;
}
}
}
if let Ok(contents) = fs::read_to_string("/proc/mounts") {
for line in contents.lines() {
if line.contains("hugetlbfs") {
return true;
}
}
}
if fs::metadata("/sys/kernel/mm/hugepages").is_ok() {
if let Ok(entries) = fs::read_dir("/sys/kernel/mm/hugepages") {
return entries.count() > 0;
}
}
false
}
pub fn check_memory_mapping_support() -> Result<bool, PlottingError> {
Ok(true)
}
pub fn get_hugepage_info() -> Option<HugepageInfo> {
let mut info = HugepageInfo::default();
if let Ok(contents) = fs::read_to_string("/proc/meminfo") {
for line in contents.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let value = parts[1].parse::<u64>().unwrap_or(0);
match parts[0] {
"HugePages_Total:" => info.total_pages = value,
"HugePages_Free:" => info.free_pages = value,
"Hugepagesize:" => info.page_size_kb = value,
_ => {}
}
}
}
}
if info.total_pages > 0 {
Some(info)
} else {
None
}
}
pub fn configure_transparent_hugepages(enabled: bool) -> Result<(), PlottingError> {
let thp_path = "/sys/kernel/mm/transparent_hugepage/enabled";
if fs::metadata(thp_path).is_err() {
return Err(PlottingError::SystemError(
"Transparent huge pages not supported".to_string(),
));
}
let value = if enabled { "always" } else { "never" };
fs::write(thp_path, value).map_err(|e| {
PlottingError::SystemError(format!("Failed to configure transparent huge pages: {}", e))
})
}
pub fn get_memory_pressure() -> Option<MemoryPressure> {
if let Ok(contents) = fs::read_to_string("/proc/pressure/memory") {
return parse_pressure_stats(&contents);
}
None
}
fn parse_pressure_stats(contents: &str) -> Option<MemoryPressure> {
let mut pressure = MemoryPressure::default();
for line in contents.lines() {
if line.starts_with("some ") {
if let Some(avg10) = extract_avg_value(line, "avg10=") {
pressure.some_avg10 = avg10;
}
if let Some(avg60) = extract_avg_value(line, "avg60=") {
pressure.some_avg60 = avg60;
}
} else if line.starts_with("full ") {
if let Some(avg10) = extract_avg_value(line, "avg10=") {
pressure.full_avg10 = avg10;
}
if let Some(avg60) = extract_avg_value(line, "avg60=") {
pressure.full_avg60 = avg60;
}
}
}
Some(pressure)
}
fn extract_avg_value(line: &str, prefix: &str) -> Option<f32> {
if let Some(start) = line.find(prefix) {
let start_idx = start + prefix.len();
if let Some(end) = line[start_idx..].find(' ') {
line[start_idx..start_idx + end].parse().ok()
} else {
line[start_idx..].parse().ok()
}
} else {
None
}
}
pub fn configure_swappiness(value: u32) -> Result<(), PlottingError> {
if value > 100 {
return Err(PlottingError::InvalidInput(
"Swappiness must be between 0 and 100".to_string(),
));
}
fs::write("/proc/sys/vm/swappiness", value.to_string())
.map_err(|e| PlottingError::SystemError(format!("Failed to configure swappiness: {}", e)))
}
pub fn get_swappiness() -> Result<u32, PlottingError> {
let contents = fs::read_to_string("/proc/sys/vm/swappiness")
.map_err(|e| PlottingError::SystemError(format!("Failed to read swappiness: {}", e)))?;
contents
.trim()
.parse()
.map_err(|e| PlottingError::SystemError(format!("Failed to parse swappiness: {}", e)))
}
#[derive(Debug, Clone, Default)]
pub struct HugepageInfo {
pub total_pages: u64,
pub free_pages: u64,
pub page_size_kb: u64,
}
#[derive(Debug, Clone, Default)]
pub struct MemoryPressure {
pub some_avg10: f32, pub some_avg60: f32, pub full_avg10: f32, pub full_avg60: f32, }
pub fn get_detailed_memory_stats() -> Result<DetailedMemoryStats, PlottingError> {
let mut contents = String::new();
fs::File::open("/proc/meminfo")
.and_then(|mut f| f.read_to_string(&mut contents))
.map_err(|e| PlottingError::SystemError(format!("Failed to read /proc/meminfo: {}", e)))?;
let mut stats = DetailedMemoryStats::default();
for line in contents.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let value_kb = parts[1].parse::<u64>().unwrap_or(0);
let value_bytes = value_kb * 1024;
match parts[0] {
"MemTotal:" => stats.total = value_bytes,
"MemFree:" => stats.free = value_bytes,
"MemAvailable:" => stats.available = value_bytes,
"Buffers:" => stats.buffers = value_bytes,
"Cached:" => stats.cached = value_bytes,
"SwapTotal:" => stats.swap_total = value_bytes,
"SwapFree:" => stats.swap_free = value_bytes,
"Slab:" => stats.slab = value_bytes,
"SReclaimable:" => stats.slab_reclaimable = value_bytes,
"SUnreclaim:" => stats.slab_unreclaimable = value_bytes,
"PageTables:" => stats.page_tables = value_bytes,
"Mapped:" => stats.mapped = value_bytes,
_ => {}
}
}
}
Ok(stats)
}
#[derive(Debug, Clone, Default)]
pub struct DetailedMemoryStats {
pub total: u64,
pub free: u64,
pub available: u64,
pub buffers: u64,
pub cached: u64,
pub swap_total: u64,
pub swap_free: u64,
pub slab: u64,
pub slab_reclaimable: u64,
pub slab_unreclaimable: u64,
pub page_tables: u64,
pub mapped: u64,
}