use backtrace::Backtrace;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::{Duration, Instant, SystemTime};
#[derive(Debug, Clone)]
pub struct ResourceStats {
pub memory_usage: u64,
pub peak_memory_usage: u64,
pub total_allocations: u64,
pub total_deallocations: u64,
pub active_allocations: u64,
pub fragmentation_ratio: f64,
pub avg_allocation_size: u64,
pub allocation_rate: f64,
pub deallocation_rate: f64,
pub last_updated: SystemTime,
}
impl Default for ResourceStats {
fn default() -> Self {
Self {
memory_usage: 0,
peak_memory_usage: 0,
total_allocations: 0,
total_deallocations: 0,
active_allocations: 0,
fragmentation_ratio: 0.0,
avg_allocation_size: 0,
allocation_rate: 0.0,
deallocation_rate: 0.0,
last_updated: SystemTime::now(),
}
}
}
#[derive(Debug, Clone)]
pub struct AllocationInfo {
pub size: usize,
pub timestamp: Instant,
pub stack_trace: Option<Vec<String>>,
pub category: AllocationCategory,
pub thread_id: thread::ThreadId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AllocationCategory {
AudioBuffer,
TensorData,
Config,
Cache,
Temporary,
Other,
}
impl fmt::Display for AllocationCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AudioBuffer => write!(f, "AudioBuffer"),
Self::TensorData => write!(f, "TensorData"),
Self::Config => write!(f, "Config"),
Self::Cache => write!(f, "Cache"),
Self::Temporary => write!(f, "Temporary"),
Self::Other => write!(f, "Other"),
}
}
}
#[derive(Debug, Clone)]
pub struct TrackingConfig {
pub enable_detailed_tracking: bool,
pub enable_stack_traces: bool,
pub max_tracked_allocations: usize,
pub stats_update_interval: Duration,
pub enable_leak_detection: bool,
pub leak_detection_threshold: Duration,
}
impl Default for TrackingConfig {
fn default() -> Self {
Self {
enable_detailed_tracking: true,
enable_stack_traces: false, max_tracked_allocations: 10000,
stats_update_interval: Duration::from_secs(1),
enable_leak_detection: true,
leak_detection_threshold: Duration::from_secs(300), }
}
}
#[derive(Debug, Clone, Copy)]
pub struct SystemMemoryInfo {
pub rss: u64,
pub virtual_memory: u64,
pub peak_rss: u64,
}
pub struct MemoryTracker {
allocations: Arc<RwLock<HashMap<usize, AllocationInfo>>>,
category_stats: Arc<RwLock<HashMap<AllocationCategory, ResourceStats>>>,
#[allow(dead_code)]
global_stats: Arc<RwLock<ResourceStats>>,
current_memory: AtomicU64,
peak_memory: AtomicU64,
total_allocations: AtomicU64,
total_deallocations: AtomicU64,
config: TrackingConfig,
start_time: Instant,
}
impl MemoryTracker {
pub fn new(config: TrackingConfig) -> Self {
Self {
allocations: Arc::new(RwLock::new(HashMap::new())),
category_stats: Arc::new(RwLock::new(HashMap::new())),
global_stats: Arc::new(RwLock::new(ResourceStats::default())),
current_memory: AtomicU64::new(0),
peak_memory: AtomicU64::new(0),
total_allocations: AtomicU64::new(0),
total_deallocations: AtomicU64::new(0),
config,
start_time: Instant::now(),
}
}
pub fn with_default_config() -> Self {
Self::new(TrackingConfig::default())
}
pub fn record_allocation(&self, ptr: usize, size: usize, category: AllocationCategory) {
let new_memory = self
.current_memory
.fetch_add(size as u64, Ordering::Relaxed)
+ size as u64;
self.total_allocations.fetch_add(1, Ordering::Relaxed);
let current_peak = self.peak_memory.load(Ordering::Relaxed);
if new_memory > current_peak {
self.peak_memory.store(new_memory, Ordering::Relaxed);
}
if self.config.enable_detailed_tracking {
let allocation_info = AllocationInfo {
size,
timestamp: Instant::now(),
stack_trace: if self.config.enable_stack_traces {
Some(self.capture_stack_trace())
} else {
None
},
category,
thread_id: thread::current().id(),
};
if let Ok(mut allocations) = self.allocations.write() {
if allocations.len() >= self.config.max_tracked_allocations {
if let Some(oldest_ptr) = allocations
.iter()
.min_by_key(|(_, info)| info.timestamp)
.map(|(ptr, _)| *ptr)
{
allocations.remove(&oldest_ptr);
}
}
allocations.insert(ptr, allocation_info);
}
self.update_category_stats(category, size as i64);
}
}
pub fn record_deallocation(&self, ptr: usize) -> Option<AllocationInfo> {
self.total_deallocations.fetch_add(1, Ordering::Relaxed);
if self.config.enable_detailed_tracking {
if let Ok(mut allocations) = self.allocations.write() {
if let Some(allocation_info) = allocations.remove(&ptr) {
self.current_memory
.fetch_sub(allocation_info.size as u64, Ordering::Relaxed);
self.update_category_stats(
allocation_info.category,
-(allocation_info.size as i64),
);
return Some(allocation_info);
}
}
}
None
}
pub fn current_memory_usage(&self) -> u64 {
self.current_memory.load(Ordering::Relaxed)
}
pub fn peak_memory_usage(&self) -> u64 {
self.peak_memory.load(Ordering::Relaxed)
}
pub fn get_global_stats(&self) -> ResourceStats {
let current_memory = self.current_memory.load(Ordering::Relaxed);
let peak_memory = self.peak_memory.load(Ordering::Relaxed);
let total_allocs = self.total_allocations.load(Ordering::Relaxed);
let total_deallocs = self.total_deallocations.load(Ordering::Relaxed);
let elapsed = self.start_time.elapsed().as_secs_f64();
let allocation_rate = if elapsed > 0.0 {
total_allocs as f64 / elapsed
} else {
0.0
};
let deallocation_rate = if elapsed > 0.0 {
total_deallocs as f64 / elapsed
} else {
0.0
};
let avg_allocation_size = current_memory.checked_div(total_allocs).unwrap_or(0);
let mut stats = ResourceStats {
memory_usage: current_memory,
peak_memory_usage: peak_memory,
total_allocations: total_allocs,
total_deallocations: total_deallocs,
active_allocations: total_allocs - total_deallocs,
fragmentation_ratio: self.calculate_fragmentation_ratio(),
avg_allocation_size,
allocation_rate,
deallocation_rate,
last_updated: SystemTime::now(),
};
if let Some(system_memory) = self.get_system_memory_info() {
stats.memory_usage = std::cmp::max(stats.memory_usage, system_memory.rss);
stats.peak_memory_usage =
std::cmp::max(stats.peak_memory_usage, system_memory.peak_rss);
if system_memory.virtual_memory > 0 && system_memory.rss <= system_memory.virtual_memory
{
let virtual_fragmentation = (system_memory.virtual_memory - system_memory.rss)
as f64
/ system_memory.virtual_memory as f64;
let combined_fragmentation =
stats.fragmentation_ratio * 0.5 + virtual_fragmentation * 0.5;
stats.fragmentation_ratio = combined_fragmentation.clamp(0.0, 1.0);
}
}
stats
}
fn get_system_memory_info(&self) -> Option<SystemMemoryInfo> {
self.collect_system_memory_info()
}
fn collect_system_memory_info(&self) -> Option<SystemMemoryInfo> {
#[cfg(target_os = "linux")]
{
self.collect_linux_memory_info()
}
#[cfg(target_os = "macos")]
{
self.collect_macos_memory_info()
}
#[cfg(target_os = "windows")]
{
self.collect_windows_memory_info()
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
None
}
}
#[cfg(target_os = "linux")]
fn collect_linux_memory_info(&self) -> Option<SystemMemoryInfo> {
use std::fs;
if let Ok(status) = fs::read_to_string("/proc/self/status") {
let mut rss = 0;
let mut virtual_memory = 0;
let mut peak_rss = 0;
for line in status.lines() {
if line.starts_with("VmRSS:") {
if let Some(value) = line.split_whitespace().nth(1) {
rss = value.parse::<u64>().unwrap_or(0) * 1024; }
} else if line.starts_with("VmSize:") {
if let Some(value) = line.split_whitespace().nth(1) {
virtual_memory = value.parse::<u64>().unwrap_or(0) * 1024;
}
} else if line.starts_with("VmHWM:") {
if let Some(value) = line.split_whitespace().nth(1) {
peak_rss = value.parse::<u64>().unwrap_or(0) * 1024; }
}
}
Some(SystemMemoryInfo {
rss,
virtual_memory,
peak_rss,
})
} else {
None
}
}
#[cfg(target_os = "macos")]
fn collect_macos_memory_info(&self) -> Option<SystemMemoryInfo> {
use std::mem;
unsafe {
#[allow(deprecated)]
let task = libc::mach_task_self();
let mut info: libc::mach_task_basic_info = mem::zeroed();
let mut count =
(mem::size_of::<libc::mach_task_basic_info>() / mem::size_of::<u32>()) as u32;
let result = libc::task_info(
task,
libc::MACH_TASK_BASIC_INFO,
&mut info as *mut _ as *mut i32,
&mut count,
);
if result == libc::KERN_SUCCESS {
Some(SystemMemoryInfo {
rss: info.resident_size,
virtual_memory: info.virtual_size,
peak_rss: info.resident_size_max,
})
} else {
None
}
}
}
#[cfg(target_os = "windows")]
fn collect_windows_memory_info(&self) -> Option<SystemMemoryInfo> {
None
}
pub fn get_category_stats(&self) -> HashMap<AllocationCategory, ResourceStats> {
if let Ok(stats) = self.category_stats.read() {
stats.clone()
} else {
HashMap::new()
}
}
pub fn detect_leaks(&self) -> Vec<(usize, AllocationInfo)> {
if !self.config.enable_leak_detection {
return Vec::new();
}
let now = Instant::now();
let mut leaks = Vec::new();
if let Ok(allocations) = self.allocations.read() {
for (ptr, info) in allocations.iter() {
if now.duration_since(info.timestamp) > self.config.leak_detection_threshold {
leaks.push((*ptr, info.clone()));
}
}
}
leaks
}
pub fn get_allocations_by_category(
&self,
category: AllocationCategory,
) -> Vec<(usize, AllocationInfo)> {
if let Ok(allocations) = self.allocations.read() {
allocations
.iter()
.filter(|(_, info)| info.category == category)
.map(|(ptr, info)| (*ptr, info.clone()))
.collect()
} else {
Vec::new()
}
}
pub fn get_memory_timeline(&self) -> Vec<(SystemTime, u64)> {
vec![(SystemTime::now(), self.current_memory_usage())]
}
pub fn hint_gc(&self) {
if self.config.enable_detailed_tracking {
self.cleanup_stale_allocations();
}
}
pub fn clear(&self) {
if let Ok(mut allocations) = self.allocations.write() {
allocations.clear();
}
if let Ok(mut category_stats) = self.category_stats.write() {
category_stats.clear();
}
self.current_memory.store(0, Ordering::Relaxed);
self.total_allocations.store(0, Ordering::Relaxed);
self.total_deallocations.store(0, Ordering::Relaxed);
}
fn update_category_stats(&self, category: AllocationCategory, size_delta: i64) {
if let Ok(mut stats) = self.category_stats.write() {
let category_stat = stats.entry(category).or_insert_with(ResourceStats::default);
if size_delta > 0 {
category_stat.memory_usage += size_delta as u64;
category_stat.total_allocations += 1;
if category_stat.memory_usage > category_stat.peak_memory_usage {
category_stat.peak_memory_usage = category_stat.memory_usage;
}
} else {
category_stat.memory_usage = category_stat
.memory_usage
.saturating_sub((-size_delta) as u64);
category_stat.total_deallocations += 1;
}
category_stat.last_updated = SystemTime::now();
}
}
fn calculate_fragmentation_ratio(&self) -> f64 {
if let Ok(allocations) = self.allocations.read() {
if allocations.is_empty() {
return 0.0;
}
let total_size: usize = allocations.values().map(|info| info.size).sum();
if total_size == 0 {
return 0.0;
}
let avg_size = total_size / allocations.len();
if avg_size == 0 {
return 0.0;
}
let variance: f64 = allocations
.values()
.map(|info| {
let diff = info.size as f64 - avg_size as f64;
diff * diff
})
.sum::<f64>()
/ allocations.len() as f64;
let std_dev = variance.sqrt();
let fragmentation = std_dev / avg_size as f64;
fragmentation.clamp(0.0, 1.0)
} else {
0.0
}
}
fn capture_stack_trace(&self) -> Vec<String> {
let bt = Backtrace::new();
let mut frames = Vec::new();
for frame in bt.frames() {
for symbol in frame.symbols() {
let mut frame_info = String::new();
if let Some(name) = symbol.name() {
frame_info.push_str(&format!("{name}"));
}
if let Some(filename) = symbol.filename() {
let file_name = filename
.file_name()
.map(|f| f.to_string_lossy())
.unwrap_or_else(|| "unknown".into());
frame_info.push_str(&format!(" ({file_name}:"));
if let Some(line) = symbol.lineno() {
frame_info.push_str(&format!("{line})"));
} else {
frame_info.push(')');
}
}
if self.should_include_frame(&frame_info) {
frames.push(frame_info);
}
}
}
const MAX_FRAMES: usize = 20;
if frames.len() > MAX_FRAMES {
frames.truncate(MAX_FRAMES);
frames.push("... (truncated)".to_string());
}
if frames.is_empty() {
frames.push(format!("allocation at thread {:?}", thread::current().id()));
}
self.format_stack_trace(frames)
}
fn should_include_frame(&self, frame_info: &str) -> bool {
!frame_info.contains("::fmt::")
&& !frame_info.contains("std::")
&& !frame_info.contains("core::")
&& !frame_info.contains("rust_begin_unwind")
&& !frame_info.contains("__rust_")
&& !frame_info.contains("backtrace::")
&& !frame_info
.contains("voirs_sdk::memory::tracking::MemoryTracker::capture_stack_trace")
&& !frame_info.contains("voirs_sdk::memory::tracking::MemoryTracker::record_allocation")
&& !frame_info.is_empty()
}
fn format_stack_trace(&self, frames: Vec<String>) -> Vec<String> {
frames
.into_iter()
.enumerate()
.map(|(i, frame)| {
let cleaned = frame.replace("voirs_sdk::", "").replace("voirs_", "");
format!("#{i:02}: {cleaned}")
})
.collect()
}
pub fn get_compact_stack_trace(&self) -> String {
if !self.config.enable_stack_traces {
return "stack traces disabled".to_string();
}
let frames = self.capture_stack_trace();
if frames.is_empty() {
return "no stack trace available".to_string();
}
frames
.iter()
.take(5)
.map(|f| f.replace("voirs_sdk::", ""))
.collect::<Vec<_>>()
.join(" → ")
}
fn cleanup_stale_allocations(&self) {
let now = Instant::now();
let threshold = Duration::from_secs(3600);
if let Ok(mut allocations) = self.allocations.write() {
allocations.retain(|_, info| now.duration_since(info.timestamp) < threshold);
}
}
}
impl Default for MemoryTracker {
fn default() -> Self {
Self::with_default_config()
}
}
pub struct ResourceTracker {
memory_tracker: Arc<MemoryTracker>,
usage_history: Arc<RwLock<BTreeMap<SystemTime, ResourceStats>>>,
monitor_handle: Option<thread::JoinHandle<()>>,
config: TrackingConfig,
}
impl ResourceTracker {
pub fn new(config: TrackingConfig) -> Self {
let memory_tracker = Arc::new(MemoryTracker::new(config.clone()));
let usage_history = Arc::new(RwLock::new(BTreeMap::new()));
let mut tracker = Self {
memory_tracker,
usage_history,
monitor_handle: None,
config,
};
tracker.start_monitoring();
tracker
}
pub fn memory_tracker(&self) -> &Arc<MemoryTracker> {
&self.memory_tracker
}
pub fn get_usage_history(&self) -> BTreeMap<SystemTime, ResourceStats> {
if let Ok(history) = self.usage_history.read() {
history.clone()
} else {
BTreeMap::new()
}
}
pub fn generate_report(&self) -> ResourceReport {
let global_stats = self.memory_tracker.get_global_stats();
let category_stats = self.memory_tracker.get_category_stats();
let potential_leaks = self.memory_tracker.detect_leaks();
let usage_history = self.get_usage_history();
ResourceReport {
global_stats,
category_stats,
potential_leaks,
usage_history,
report_time: SystemTime::now(),
}
}
fn start_monitoring(&mut self) {
let usage_history = Arc::clone(&self.usage_history);
let memory_tracker = Arc::clone(&self.memory_tracker);
let interval = self.config.stats_update_interval;
let handle = thread::spawn(move || {
loop {
thread::sleep(interval);
let stats = memory_tracker.get_global_stats();
let timestamp = SystemTime::now();
if let Ok(mut history) = usage_history.write() {
history.insert(timestamp, stats);
while history.len() > 1000 {
if let Some(oldest) = history.keys().next().cloned() {
history.remove(&oldest);
} else {
break;
}
}
}
}
});
self.monitor_handle = Some(handle);
}
}
#[derive(Debug, Clone)]
pub struct ResourceReport {
pub global_stats: ResourceStats,
pub category_stats: HashMap<AllocationCategory, ResourceStats>,
pub potential_leaks: Vec<(usize, AllocationInfo)>,
pub usage_history: BTreeMap<SystemTime, ResourceStats>,
pub report_time: SystemTime,
}
impl ResourceReport {
pub fn has_concerns(&self) -> bool {
!self.potential_leaks.is_empty()
|| self.global_stats.fragmentation_ratio > 0.5
|| self.global_stats.active_allocations > 10000
}
pub fn efficiency_score(&self) -> f64 {
let fragmentation_penalty = self.global_stats.fragmentation_ratio;
let leak_penalty = (self.potential_leaks.len() as f64 / 100.0).min(1.0);
(1.0 - fragmentation_penalty * 0.5 - leak_penalty * 0.5).max(0.0)
}
}
impl fmt::Display for ResourceReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"Resource Report - {}",
self.report_time
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
)?;
writeln!(f, "==================")?;
writeln!(
f,
"Memory Usage: {:.2} MB",
self.global_stats.memory_usage as f64 / 1024.0 / 1024.0
)?;
writeln!(
f,
"Peak Memory: {:.2} MB",
self.global_stats.peak_memory_usage as f64 / 1024.0 / 1024.0
)?;
writeln!(
f,
"Active Allocations: {}",
self.global_stats.active_allocations
)?;
writeln!(
f,
"Fragmentation: {:.1}%",
self.global_stats.fragmentation_ratio * 100.0
)?;
writeln!(f, "Potential Leaks: {}", self.potential_leaks.len())?;
writeln!(
f,
"Efficiency Score: {:.1}%",
self.efficiency_score() * 100.0
)?;
if !self.category_stats.is_empty() {
writeln!(f, "\nCategory Breakdown:")?;
for (category, stats) in &self.category_stats {
writeln!(
f,
" {}: {:.2} MB ({} allocations)",
category,
stats.memory_usage as f64 / 1024.0 / 1024.0,
stats.active_allocations
)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn test_memory_tracker_basic() {
let config = TrackingConfig {
enable_detailed_tracking: true,
enable_stack_traces: false,
..Default::default()
};
let tracker = MemoryTracker::new(config);
tracker.record_allocation(0x1000, 1024, AllocationCategory::AudioBuffer);
assert_eq!(tracker.current_memory_usage(), 1024);
let info = tracker.record_deallocation(0x1000);
assert!(info.is_some());
assert_eq!(tracker.current_memory_usage(), 0);
let stats = tracker.get_global_stats();
assert_eq!(stats.total_allocations, 1);
assert_eq!(stats.total_deallocations, 1);
}
#[test]
fn test_category_statistics() {
let tracker = MemoryTracker::default();
tracker.record_allocation(0x1000, 512, AllocationCategory::AudioBuffer);
tracker.record_allocation(0x2000, 256, AllocationCategory::TensorData);
tracker.record_allocation(0x3000, 128, AllocationCategory::Cache);
let category_stats = tracker.get_category_stats();
assert_eq!(category_stats.len(), 3);
let audio_stats = &category_stats[&AllocationCategory::AudioBuffer];
assert_eq!(audio_stats.memory_usage, 512);
assert_eq!(audio_stats.total_allocations, 1);
}
#[test]
fn test_leak_detection() {
let config = TrackingConfig {
enable_leak_detection: true,
leak_detection_threshold: Duration::from_millis(50),
..Default::default()
};
let tracker = MemoryTracker::new(config);
tracker.record_allocation(0x1000, 1024, AllocationCategory::Temporary);
thread::sleep(Duration::from_millis(100));
let leaks = tracker.detect_leaks();
assert_eq!(leaks.len(), 1);
assert_eq!(leaks[0].0, 0x1000);
}
#[test]
fn test_resource_report() {
let tracker = ResourceTracker::new(TrackingConfig::default());
tracker
.memory_tracker()
.record_allocation(0x1000, 1024, AllocationCategory::AudioBuffer);
tracker
.memory_tracker()
.record_allocation(0x2000, 512, AllocationCategory::TensorData);
let report = tracker.generate_report();
assert!(report.global_stats.memory_usage > 0);
assert!(!report.category_stats.is_empty());
let efficiency = report.efficiency_score();
assert!((0.0..=1.0).contains(&efficiency));
}
#[test]
fn test_stack_trace_capture() {
let config = TrackingConfig {
enable_stack_traces: true,
enable_detailed_tracking: true,
..Default::default()
};
let tracker = MemoryTracker::new(config);
tracker.record_allocation(0x1000, 1024, AllocationCategory::AudioBuffer);
if let Ok(allocations) = tracker.allocations.read() {
if let Some(allocation_info) = allocations.get(&0x1000) {
assert!(allocation_info.stack_trace.is_some());
let stack_trace = allocation_info.stack_trace.as_ref().unwrap();
assert!(!stack_trace.is_empty());
let has_meaningful_frame = stack_trace.iter().any(|frame| {
frame.contains("test_stack_trace_capture")
|| frame.contains("record_allocation")
});
assert!(
has_meaningful_frame,
"Stack trace should contain meaningful frames: {stack_trace:?}"
);
} else {
panic!("Allocation not found in tracker");
}
} else {
panic!("Failed to read allocations");
};
}
#[test]
fn test_stack_trace_disabled() {
let config = TrackingConfig {
enable_stack_traces: false,
enable_detailed_tracking: true,
..Default::default()
};
let tracker = MemoryTracker::new(config);
tracker.record_allocation(0x1000, 1024, AllocationCategory::AudioBuffer);
if let Ok(allocations) = tracker.allocations.read() {
if let Some(allocation_info) = allocations.get(&0x1000) {
assert!(allocation_info.stack_trace.is_none());
} else {
panic!("Allocation not found in tracker");
}
} else {
panic!("Failed to read allocations");
};
}
#[test]
fn test_compact_stack_trace() {
let config = TrackingConfig {
enable_stack_traces: true,
enable_detailed_tracking: true,
..Default::default()
};
let tracker = MemoryTracker::new(config);
let compact_trace = tracker.get_compact_stack_trace();
assert!(!compact_trace.is_empty());
assert!(!compact_trace.contains("stack traces disabled"));
let config_disabled = TrackingConfig {
enable_stack_traces: false,
..Default::default()
};
let tracker_disabled = MemoryTracker::new(config_disabled);
let compact_trace_disabled = tracker_disabled.get_compact_stack_trace();
assert_eq!(compact_trace_disabled, "stack traces disabled");
}
#[test]
fn test_stack_trace_filtering() {
let config = TrackingConfig {
enable_stack_traces: true,
enable_detailed_tracking: true,
..Default::default()
};
let tracker = MemoryTracker::new(config);
assert!(tracker.should_include_frame("my_function (src/lib.rs:123)"));
assert!(!tracker.should_include_frame("std::alloc::alloc"));
assert!(!tracker.should_include_frame("core::ptr::drop_in_place"));
assert!(!tracker.should_include_frame("rust_begin_unwind"));
assert!(!tracker.should_include_frame("__rust_start_panic"));
assert!(!tracker.should_include_frame("backtrace::backtrace"));
assert!(!tracker.should_include_frame(""));
}
#[test]
fn test_stack_trace_formatting() {
let config = TrackingConfig {
enable_stack_traces: true,
enable_detailed_tracking: true,
..Default::default()
};
let tracker = MemoryTracker::new(config);
let frames = vec![
"my_function (src/lib.rs:123)".to_string(),
"another_function (src/main.rs:456)".to_string(),
];
let formatted = tracker.format_stack_trace(frames);
assert_eq!(formatted.len(), 2);
assert!(formatted[0].starts_with("#00:"));
assert!(formatted[1].starts_with("#01:"));
assert!(formatted[0].contains("my_function"));
assert!(formatted[1].contains("another_function"));
}
#[test]
fn test_system_memory_info() {
let tracker = MemoryTracker::default();
let system_info = tracker.get_system_memory_info();
if let Some(info) = system_info {
assert!(info.rss > 0, "RSS should be greater than 0");
assert!(
info.virtual_memory >= info.rss,
"Virtual memory should be >= RSS"
);
assert!(
info.peak_rss >= info.rss,
"Peak RSS should be >= current RSS"
);
}
}
#[test]
fn test_enhanced_global_stats() {
let tracker = MemoryTracker::default();
tracker.record_allocation(0x1000, 1024, AllocationCategory::AudioBuffer);
tracker.record_allocation(0x2000, 512, AllocationCategory::TensorData);
let stats = tracker.get_global_stats();
assert_eq!(stats.total_allocations, 2);
assert_eq!(stats.total_deallocations, 0);
assert_eq!(stats.active_allocations, 2);
assert!(stats.memory_usage >= 1536);
assert!(stats.allocation_rate >= 0.0);
assert_eq!(stats.deallocation_rate, 0.0);
assert!(stats.fragmentation_ratio >= 0.0);
assert!(stats.fragmentation_ratio <= 1.0);
}
#[test]
fn test_resource_tracker_real_stats() {
let tracker = ResourceTracker::new(TrackingConfig::default());
tracker
.memory_tracker()
.record_allocation(0x1000, 2048, AllocationCategory::AudioBuffer);
tracker
.memory_tracker()
.record_allocation(0x2000, 1024, AllocationCategory::TensorData);
let report = tracker.generate_report();
assert!(report.global_stats.memory_usage > 0);
assert_eq!(report.global_stats.total_allocations, 2);
assert!(!report.category_stats.is_empty());
let efficiency = report.efficiency_score();
assert!((0.0..=1.0).contains(&efficiency));
}
#[test]
fn test_memory_timeline_tracking() {
let tracker = MemoryTracker::default();
let initial_timeline = tracker.get_memory_timeline();
assert_eq!(initial_timeline.len(), 1);
tracker.record_allocation(0x1000, 1024, AllocationCategory::AudioBuffer);
let updated_timeline = tracker.get_memory_timeline();
assert_eq!(updated_timeline.len(), 1); assert!(updated_timeline[0].1 >= 1024); }
#[test]
fn test_system_memory_collection_platforms() {
let tracker = MemoryTracker::default();
let system_info = tracker.collect_system_memory_info();
match system_info {
Some(info) => {
assert!(info.rss > 0);
assert!(info.virtual_memory >= info.rss);
assert!(info.peak_rss >= info.rss);
}
None => {
}
}
}
#[test]
fn test_enhanced_fragmentation_calculation() {
let config = TrackingConfig {
enable_detailed_tracking: true,
..Default::default()
};
let tracker = MemoryTracker::new(config);
tracker.record_allocation(0x1000, 100, AllocationCategory::AudioBuffer);
tracker.record_allocation(0x2000, 1000, AllocationCategory::TensorData);
tracker.record_allocation(0x3000, 10000, AllocationCategory::Cache);
let stats = tracker.get_global_stats();
assert!(stats.fragmentation_ratio >= 0.0);
assert!(stats.fragmentation_ratio <= 1.0);
let base_fragmentation = tracker.calculate_fragmentation_ratio();
assert!(base_fragmentation >= 0.0);
}
}