use crate::error::{ProcessingError, ProcessingResult};
use memmap2::{Mmap, MmapMut, MmapOptions};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub const MMAP_THRESHOLD: u64 = 100 * 1024 * 1024;
pub const DEFAULT_MEMORY_LIMIT: u64 = 1024 * 1024 * 1024;
pub const DEFAULT_PAGE_SIZE: usize = 10000;
#[derive(Debug, Clone)]
pub struct MemoryConfig {
pub memory_limit: u64,
pub mmap_threshold: u64,
pub page_size: usize,
pub adaptive: bool,
pub force_mmap: bool,
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
memory_limit: DEFAULT_MEMORY_LIMIT,
mmap_threshold: MMAP_THRESHOLD,
page_size: DEFAULT_PAGE_SIZE,
adaptive: true,
force_mmap: false,
}
}
}
impl MemoryConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_memory_limit(mut self, limit: u64) -> Self {
self.memory_limit = limit;
self
}
pub fn with_mmap_threshold(mut self, threshold: u64) -> Self {
self.mmap_threshold = threshold;
self
}
pub fn with_page_size(mut self, size: usize) -> Self {
self.page_size = size;
self
}
pub fn with_adaptive(mut self, adaptive: bool) -> Self {
self.adaptive = adaptive;
self
}
pub fn with_force_mmap(mut self, force: bool) -> Self {
self.force_mmap = force;
self
}
}
#[derive(Debug, Clone)]
pub struct MemoryStats {
pub total_used: u64,
pub mapped_size: u64,
pub mapped_files: usize,
pub active_pages: usize,
pub peak_usage: u64,
}
impl MemoryStats {
pub fn new() -> Self {
Self {
total_used: 0,
mapped_size: 0,
mapped_files: 0,
active_pages: 0,
peak_usage: 0,
}
}
pub fn update_peak(&mut self, current_usage: u64) {
if current_usage > self.peak_usage {
self.peak_usage = current_usage;
}
}
pub fn efficiency(&self) -> f64 {
if self.total_used == 0 {
return 0.0;
}
(self.mapped_size as f64 / self.total_used as f64) * 100.0
}
}
#[derive(Debug)]
pub struct MemoryManager {
config: MemoryConfig,
stats: Arc<RwLock<MemoryStats>>,
mapped_files: Arc<RwLock<HashMap<PathBuf, MmapInfo>>>,
}
#[derive(Debug)]
struct MmapInfo {
size: u64,
created_at: std::time::Instant,
}
impl MemoryManager {
pub fn new(config: MemoryConfig) -> Self {
Self {
config,
stats: Arc::new(RwLock::new(MemoryStats::new())),
mapped_files: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn stats(&self) -> MemoryStats {
self.stats.read().clone()
}
pub fn should_mmap(&self, file_size: u64) -> bool {
self.config.force_mmap || file_size >= self.config.mmap_threshold
}
pub fn mmap_file(&self, path: &Path) -> ProcessingResult<Mmap> {
let file = File::open(path)?;
let file_size = file.metadata()?.len();
if !self.should_mmap(file_size) {
return Err(ProcessingError::new("File too small for memory mapping"));
}
let mmap = unsafe { MmapOptions::new().map(&file)? };
let total_used_after;
{
let mut stats = self.stats.write();
stats.total_used += file_size;
stats.mapped_size += file_size;
stats.mapped_files += 1;
total_used_after = stats.total_used;
}
{
let mut stats = self.stats.write();
stats.update_peak(total_used_after);
}
{
let mut mapped = self.mapped_files.write();
mapped.insert(
path.to_path_buf(),
MmapInfo {
size: file_size,
created_at: std::time::Instant::now(),
},
);
}
Ok(mmap)
}
pub fn mmap_file_mut(&self, path: &Path, size: u64) -> ProcessingResult<MmapMut> {
{
let stats = self.stats.read();
if stats.total_used + size > self.config.memory_limit {
return Err(ProcessingError::new("Memory limit exceeded"));
}
}
let file = File::create(path)?;
file.set_len(size)?;
let mmap = unsafe { MmapOptions::new().map_mut(&file)? };
let total_used_after;
{
let mut stats = self.stats.write();
stats.total_used += size;
stats.mapped_size += size;
stats.mapped_files += 1;
total_used_after = stats.total_used;
}
{
let mut stats = self.stats.write();
stats.update_peak(total_used_after);
}
Ok(mmap)
}
pub fn unmap_file(&self, path: &Path) -> ProcessingResult<()> {
let mut mapped = self.mapped_files.write();
if let Some(info) = mapped.remove(path) {
let mut stats = self.stats.write();
stats.total_used = stats.total_used.saturating_sub(info.size);
stats.mapped_size = stats.mapped_size.saturating_sub(info.size);
stats.mapped_files = stats.mapped_files.saturating_sub(1);
}
Ok(())
}
pub fn cleanup_old_mappings(&self, max_age: std::time::Duration) -> ProcessingResult<usize> {
let mut mapped = self.mapped_files.write();
let now = std::time::Instant::now();
let mut removed = 0;
mapped.retain(|_path, info| {
if now.duration_since(info.created_at) > max_age {
let mut stats = self.stats.write();
stats.total_used = stats.total_used.saturating_sub(info.size);
stats.mapped_size = stats.mapped_size.saturating_sub(info.size);
stats.mapped_files = stats.mapped_files.saturating_sub(1);
removed += 1;
false
} else {
true
}
});
Ok(removed)
}
pub fn efficiency_report(&self) -> MemoryEfficiencyReport {
let stats = self.stats.read();
let mapped = self.mapped_files.read();
MemoryEfficiencyReport {
config: self.config.clone(),
current_stats: stats.clone(),
mapped_files_count: mapped.len(),
efficiency_percentage: stats.efficiency(),
overhead_percentage: self.calculate_overhead(),
}
}
fn calculate_overhead(&self) -> f64 {
let stats = self.stats.read();
if stats.mapped_size == 0 {
return 0.0;
}
let estimated_overhead = (stats.mapped_files as u64 * 1024) + (stats.active_pages as u64 * 64);
(estimated_overhead as f64 / stats.mapped_size as f64) * 100.0
}
}
#[derive(Debug, Clone)]
pub struct MemoryEfficiencyReport {
pub config: MemoryConfig,
pub current_stats: MemoryStats,
pub mapped_files_count: usize,
pub efficiency_percentage: f64,
pub overhead_percentage: f64,
}
impl MemoryEfficiencyReport {
pub fn is_efficient(&self) -> bool {
if self.current_stats.total_used == 0 {
return true;
}
self.overhead_percentage < 10.0 && self.efficiency_percentage > 90.0
}
pub fn print(&self) {
println!("=== Memory Efficiency Report ===");
println!(
"Memory limit: {} MB",
self.config.memory_limit / 1024 / 1024
);
println!(
"Memory map threshold: {} MB",
self.config.mmap_threshold / 1024 / 1024
);
println!("Page size: {}", self.config.page_size);
println!("Adaptive mode: {}", self.config.adaptive);
println!();
println!("Current Usage:");
println!(
" Total memory: {} MB",
self.current_stats.total_used / 1024 / 1024
);
println!(
" Mapped memory: {} MB",
self.current_stats.mapped_size / 1024 / 1024
);
println!(" Mapped files: {}", self.current_stats.mapped_files);
println!(" Active pages: {}", self.current_stats.active_pages);
println!(
" Peak usage: {} MB",
self.current_stats.peak_usage / 1024 / 1024
);
println!();
println!("Efficiency Metrics:");
println!(" Memory efficiency: {:.2}%", self.efficiency_percentage);
println!(" Overhead percentage: {:.2}%", self.overhead_percentage);
println!(" Target met: {}", self.is_efficient());
}
}
pub struct PageIterator<T: Clone> {
items: Vec<T>,
page_size: usize,
current_page: usize,
total_pages: usize,
}
impl<T: Clone> PageIterator<T> {
pub fn new(items: Vec<T>, page_size: usize) -> Self {
let total_pages = (items.len() + page_size - 1) / page_size;
Self {
items,
page_size,
current_page: 0,
total_pages,
}
}
pub fn next_page(&mut self) -> Option<Vec<T>> {
if self.current_page >= self.total_pages {
return None;
}
let start = self.current_page * self.page_size;
let end = std::cmp::min(start + self.page_size, self.items.len());
self.current_page += 1;
Some(self.items[start..end].to_vec())
}
pub fn current_page(&self) -> usize {
self.current_page
}
pub fn total_pages(&self) -> usize {
self.total_pages
}
pub fn has_next(&self) -> bool {
self.current_page < self.total_pages
}
pub fn remaining_items(&self) -> usize {
if self.current_page >= self.total_pages {
return 0;
}
let start = self.current_page * self.page_size;
self.items.len() - start
}
}
impl<T: Clone> Iterator for PageIterator<T> {
type Item = Vec<T>;
fn next(&mut self) -> Option<Self::Item> {
self.next_page()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_config() {
let config = MemoryConfig::new()
.with_memory_limit(512 * 1024 * 1024) .with_page_size(5000)
.with_adaptive(false);
assert_eq!(config.memory_limit, 512 * 1024 * 1024);
assert_eq!(config.page_size, 5000);
assert!(!config.adaptive);
}
#[test]
fn test_memory_stats() {
let mut stats = MemoryStats::new();
stats.total_used = 1024 * 1024; stats.mapped_size = 800 * 1024;
stats.update_peak(2 * 1024 * 1024);
assert_eq!(stats.peak_usage, 2 * 1024 * 1024);
assert_eq!(stats.efficiency(), 78.125); }
#[test]
fn test_page_iterator() {
let items: Vec<i32> = (0..25).collect();
let mut iterator = PageIterator::new(items, 10);
assert_eq!(iterator.total_pages(), 3);
assert_eq!(iterator.remaining_items(), 25);
let page1 = iterator.next_page().unwrap();
assert_eq!(page1.len(), 10);
assert_eq!(iterator.current_page(), 1);
assert_eq!(iterator.remaining_items(), 15);
let page2 = iterator.next_page().unwrap();
assert_eq!(page2.len(), 10);
let page3 = iterator.next_page().unwrap();
assert_eq!(page3.len(), 5);
assert!(!iterator.has_next());
assert!(iterator.next_page().is_none());
}
#[test]
fn test_memory_manager_mmap_decision() {
let config = MemoryConfig::new().with_mmap_threshold(50 * 1024 * 1024);
let manager = MemoryManager::new(config);
assert!(!manager.should_mmap(10 * 1024 * 1024)); assert!(manager.should_mmap(100 * 1024 * 1024)); }
#[test]
fn test_efficiency_report() {
let config = MemoryConfig::default();
let manager = MemoryManager::new(config);
let report = manager.efficiency_report();
assert_eq!(report.mapped_files_count, 0);
assert!(report.is_efficient()); }
}