#[cfg(feature = "profiling_memory")]
use crate::CoreResult;
#[cfg(feature = "profiling_memory")]
use std::collections::HashMap;
#[cfg(feature = "profiling_memory")]
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "profiling_memory")]
static TRACKED_ALLOCATED: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "profiling_memory")]
static TRACKED_PEAK: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "profiling_memory")]
fn record_allocation(size: usize) {
let prev = TRACKED_ALLOCATED.fetch_add(size, Ordering::Relaxed);
let new_total = prev + size;
let mut current_peak = TRACKED_PEAK.load(Ordering::Relaxed);
while new_total > current_peak {
match TRACKED_PEAK.compare_exchange_weak(
current_peak,
new_total,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(actual) => current_peak = actual,
}
}
}
#[cfg(feature = "profiling_memory")]
fn record_deallocation(size: usize) {
TRACKED_ALLOCATED.fetch_sub(size, Ordering::Relaxed);
}
#[cfg(feature = "profiling_memory")]
fn get_tracked_allocated() -> usize {
TRACKED_ALLOCATED.load(Ordering::Relaxed)
}
#[cfg(feature = "profiling_memory")]
#[derive(Debug, Clone, Default)]
struct OsMemoryInfo {
resident: usize,
virtual_size: usize,
}
#[cfg(all(feature = "profiling_memory", target_os = "macos"))]
fn read_os_memory_info() -> CoreResult<OsMemoryInfo> {
use std::mem;
#[repr(C)]
#[derive(Default)]
struct MachTaskBasicInfo {
virtual_size: u64, resident_size: u64, resident_size_max: u64, user_time: [u32; 2], system_time: [u32; 2], policy: i32, suspend_count: i32, }
const MACH_TASK_BASIC_INFO: u32 = 20;
const MACH_TASK_BASIC_INFO_COUNT: u32 =
(mem::size_of::<MachTaskBasicInfo>() / mem::size_of::<u32>()) as u32;
extern "C" {
fn mach_task_self() -> u32;
fn task_info(
target_task: u32,
flavor: u32,
task_info_out: *mut MachTaskBasicInfo,
task_info_count: *mut u32,
) -> i32;
}
let mut info = MachTaskBasicInfo::default();
let mut count = MACH_TASK_BASIC_INFO_COUNT;
let kr = unsafe {
task_info(
mach_task_self(),
MACH_TASK_BASIC_INFO,
&mut info as *mut MachTaskBasicInfo,
&mut count,
)
};
if kr != 0 {
return Err(crate::CoreError::ConfigError(
crate::error::ErrorContext::new(format!("task_info failed with kern_return: {}", kr)),
));
}
Ok(OsMemoryInfo {
resident: info.resident_size as usize,
virtual_size: info.virtual_size as usize,
})
}
#[cfg(all(feature = "profiling_memory", target_os = "linux"))]
fn read_os_memory_info() -> CoreResult<OsMemoryInfo> {
use std::fs;
let statm = fs::read_to_string("/proc/self/statm").map_err(|e| {
crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
"Failed to read /proc/self/statm: {}",
e
)))
})?;
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
let page_size = if page_size <= 0 {
4096
} else {
page_size as usize
};
let parts: Vec<&str> = statm.trim().split_whitespace().collect();
if parts.len() < 2 {
return Err(crate::CoreError::ConfigError(
crate::error::ErrorContext::new("Invalid /proc/self/statm format".to_string()),
));
}
let virtual_pages: usize = parts[0].parse().map_err(|e| {
crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
"Failed to parse virtual size from /proc/self/statm: {}",
e
)))
})?;
let resident_pages: usize = parts[1].parse().map_err(|e| {
crate::CoreError::ConfigError(crate::error::ErrorContext::new(format!(
"Failed to parse resident size from /proc/self/statm: {}",
e
)))
})?;
Ok(OsMemoryInfo {
resident: resident_pages * page_size,
virtual_size: virtual_pages * page_size,
})
}
#[cfg(all(
feature = "profiling_memory",
not(target_os = "macos"),
not(target_os = "linux")
))]
fn read_os_memory_info() -> CoreResult<OsMemoryInfo> {
let allocated = get_tracked_allocated();
Ok(OsMemoryInfo {
resident: allocated,
virtual_size: allocated,
})
}
#[cfg(feature = "profiling_memory")]
#[derive(Debug, Clone)]
pub struct MemoryStats {
pub allocated: usize,
pub resident: usize,
pub mapped: usize,
pub metadata: usize,
pub retained: usize,
}
#[cfg(feature = "profiling_memory")]
impl MemoryStats {
pub fn current() -> CoreResult<Self> {
let os_info = read_os_memory_info()?;
let tracked = get_tracked_allocated();
let allocated = if tracked > 0 {
tracked
} else {
os_info.resident
};
let metadata = allocated / 50;
let retained = os_info.resident.saturating_sub(allocated);
Ok(Self {
allocated,
resident: os_info.resident,
mapped: os_info.virtual_size,
metadata,
retained,
})
}
pub fn overhead_ratio(&self) -> f64 {
if self.allocated == 0 {
0.0
} else {
self.metadata as f64 / self.allocated as f64
}
}
pub fn utilization_ratio(&self) -> f64 {
if self.resident == 0 {
0.0
} else {
self.allocated as f64 / self.resident as f64
}
}
pub fn format(&self) -> String {
format!(
"Memory Stats:\n\
- Allocated: {} MB\n\
- Resident: {} MB\n\
- Mapped: {} MB\n\
- Metadata: {} MB\n\
- Retained: {} MB\n\
- Overhead: {:.2}%\n\
- Utilization: {:.2}%",
self.allocated / 1_048_576,
self.resident / 1_048_576,
self.mapped / 1_048_576,
self.metadata / 1_048_576,
self.retained / 1_048_576,
self.overhead_ratio() * 100.0,
self.utilization_ratio() * 100.0
)
}
}
#[cfg(feature = "profiling_memory")]
pub struct MemoryProfiler {
baseline: Option<MemoryStats>,
}
#[cfg(feature = "profiling_memory")]
impl MemoryProfiler {
pub fn new() -> Self {
Self { baseline: None }
}
pub fn set_baseline(&mut self) -> CoreResult<()> {
self.baseline = Some(MemoryStats::current()?);
Ok(())
}
pub fn get_stats() -> CoreResult<MemoryStats> {
MemoryStats::current()
}
pub fn get_delta(&self) -> CoreResult<Option<MemoryDelta>> {
if let Some(ref baseline) = self.baseline {
let current = MemoryStats::current()?;
Ok(Some(MemoryDelta {
allocated_delta: current.allocated as i64 - baseline.allocated as i64,
resident_delta: current.resident as i64 - baseline.resident as i64,
mapped_delta: current.mapped as i64 - baseline.mapped as i64,
metadata_delta: current.metadata as i64 - baseline.metadata as i64,
retained_delta: current.retained as i64 - baseline.retained as i64,
}))
} else {
Ok(None)
}
}
pub fn print_stats() -> CoreResult<()> {
let stats = Self::get_stats()?;
println!("{}", stats.format());
Ok(())
}
pub fn track_allocation(size: usize) {
record_allocation(size);
}
pub fn track_deallocation(size: usize) {
record_deallocation(size);
}
}
#[cfg(feature = "profiling_memory")]
impl Default for MemoryProfiler {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "profiling_memory")]
#[derive(Debug, Clone)]
pub struct MemoryDelta {
pub allocated_delta: i64,
pub resident_delta: i64,
pub mapped_delta: i64,
pub metadata_delta: i64,
pub retained_delta: i64,
}
#[cfg(feature = "profiling_memory")]
impl MemoryDelta {
pub fn format(&self) -> String {
format!(
"Memory Delta:\n\
- Allocated: {:+} MB\n\
- Resident: {:+} MB\n\
- Mapped: {:+} MB\n\
- Metadata: {:+} MB\n\
- Retained: {:+} MB",
self.allocated_delta / 1_048_576,
self.resident_delta / 1_048_576,
self.mapped_delta / 1_048_576,
self.metadata_delta / 1_048_576,
self.retained_delta / 1_048_576
)
}
}
#[cfg(feature = "profiling_memory")]
pub struct AllocationTracker {
snapshots: Vec<(String, MemoryStats)>,
}
#[cfg(feature = "profiling_memory")]
impl AllocationTracker {
pub fn new() -> Self {
Self {
snapshots: Vec::new(),
}
}
pub fn snapshot(&mut self, label: impl Into<String>) -> CoreResult<()> {
let stats = MemoryStats::current()?;
self.snapshots.push((label.into(), stats));
Ok(())
}
pub fn snapshots(&self) -> &[(String, MemoryStats)] {
&self.snapshots
}
pub fn analyze(&self) -> AllocationAnalysis {
if self.snapshots.is_empty() {
return AllocationAnalysis {
total_allocated: 0,
peak_allocated: 0,
total_snapshots: 0,
largest_increase: None,
patterns: HashMap::new(),
};
}
let mut peak_allocated = 0;
let mut largest_increase: Option<(String, i64)> = None;
for i in 0..self.snapshots.len() {
let (ref label, ref stats) = self.snapshots[i];
if stats.allocated > peak_allocated {
peak_allocated = stats.allocated;
}
if i > 0 {
let prev_stats = &self.snapshots[i - 1].1;
let increase = stats.allocated as i64 - prev_stats.allocated as i64;
if let Some((_, max_increase)) = largest_increase {
if increase > max_increase {
largest_increase = Some((label.clone(), increase));
}
} else {
largest_increase = Some((label.clone(), increase));
}
}
}
let last_allocated = self.snapshots.last().map(|(_, s)| s.allocated).unwrap_or(0);
AllocationAnalysis {
total_allocated: last_allocated,
peak_allocated,
total_snapshots: self.snapshots.len(),
largest_increase,
patterns: HashMap::new(),
}
}
pub fn clear(&mut self) {
self.snapshots.clear();
}
}
#[cfg(feature = "profiling_memory")]
impl Default for AllocationTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "profiling_memory")]
#[derive(Debug, Clone)]
pub struct AllocationAnalysis {
pub total_allocated: usize,
pub peak_allocated: usize,
pub total_snapshots: usize,
pub largest_increase: Option<(String, i64)>,
pub patterns: HashMap<String, usize>,
}
#[cfg(feature = "profiling_memory")]
pub fn enable_profiling() -> CoreResult<()> {
Ok(())
}
#[cfg(feature = "profiling_memory")]
pub fn disable_profiling() -> CoreResult<()> {
TRACKED_ALLOCATED.store(0, Ordering::Relaxed);
TRACKED_PEAK.store(0, Ordering::Relaxed);
Ok(())
}
#[cfg(not(feature = "profiling_memory"))]
use crate::CoreResult;
#[cfg(not(feature = "profiling_memory"))]
#[derive(Debug, Clone)]
pub struct MemoryStats {
pub allocated: usize,
pub resident: usize,
pub mapped: usize,
pub metadata: usize,
pub retained: usize,
}
#[cfg(not(feature = "profiling_memory"))]
impl MemoryStats {
pub fn current() -> CoreResult<Self> {
Ok(Self {
allocated: 0,
resident: 0,
mapped: 0,
metadata: 0,
retained: 0,
})
}
pub fn format(&self) -> String {
"Memory profiling not enabled".to_string()
}
}
#[cfg(not(feature = "profiling_memory"))]
pub struct MemoryProfiler;
#[cfg(not(feature = "profiling_memory"))]
impl MemoryProfiler {
pub fn new() -> Self {
Self
}
pub fn get_stats() -> CoreResult<MemoryStats> {
MemoryStats::current()
}
pub fn print_stats() -> CoreResult<()> {
Ok(())
}
}
#[cfg(not(feature = "profiling_memory"))]
pub fn enable_profiling() -> CoreResult<()> {
Ok(())
}
#[cfg(test)]
#[cfg(feature = "profiling_memory")]
mod tests {
use super::*;
#[test]
fn test_memory_stats() {
let stats = MemoryStats::current();
assert!(stats.is_ok());
if let Ok(s) = stats {
println!("{}", s.format());
assert!(s.resident > 0, "Resident memory should be > 0");
}
}
#[test]
fn test_memory_profiler() {
let mut profiler = MemoryProfiler::new();
assert!(profiler.set_baseline().is_ok());
let _vec: Vec<u8> = vec![0; 1_000_000];
let delta = profiler.get_delta();
assert!(delta.is_ok());
}
#[test]
fn test_allocation_tracker() {
let mut tracker = AllocationTracker::new();
assert!(tracker.snapshot("baseline").is_ok());
let _vec: Vec<u8> = vec![0; 1_000_000];
assert!(tracker.snapshot("after_alloc").is_ok());
let analysis = tracker.analyze();
assert_eq!(analysis.total_snapshots, 2);
}
#[test]
fn test_memory_delta() {
let delta = MemoryDelta {
allocated_delta: 1_048_576,
resident_delta: 2_097_152,
mapped_delta: 0,
metadata_delta: 0,
retained_delta: 0,
};
let formatted = delta.format();
assert!(formatted.contains("Allocated"));
}
#[test]
fn test_enable_disable_profiling() {
assert!(enable_profiling().is_ok());
assert!(disable_profiling().is_ok());
}
#[test]
fn test_manual_tracking() {
TRACKED_ALLOCATED.store(0, Ordering::Relaxed);
TRACKED_PEAK.store(0, Ordering::Relaxed);
MemoryProfiler::track_allocation(1024);
assert_eq!(get_tracked_allocated(), 1024);
MemoryProfiler::track_allocation(2048);
assert_eq!(get_tracked_allocated(), 3072);
MemoryProfiler::track_deallocation(1024);
assert_eq!(get_tracked_allocated(), 2048);
assert_eq!(TRACKED_PEAK.load(Ordering::Relaxed), 3072);
}
#[test]
fn test_overhead_and_utilization_ratios() {
let stats = MemoryStats {
allocated: 1_000_000,
resident: 2_000_000,
mapped: 4_000_000,
metadata: 20_000,
retained: 1_000_000,
};
let overhead = stats.overhead_ratio();
assert!((overhead - 0.02).abs() < 1e-6);
let utilization = stats.utilization_ratio();
assert!((utilization - 0.5).abs() < 1e-6);
}
#[test]
fn test_zero_stats_ratios() {
let stats = MemoryStats {
allocated: 0,
resident: 0,
mapped: 0,
metadata: 0,
retained: 0,
};
assert_eq!(stats.overhead_ratio(), 0.0);
assert_eq!(stats.utilization_ratio(), 0.0);
}
}