use std::collections::BTreeMap;
use std::time::{Duration, Instant};
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::RwLock;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::collections::HashMap;
use std::hash::Hash;
std::sync::{Arc, Mutex, RwLock}, atomic::{AtomicUsize, Ordering}};
pub struct MemoryLeakDetector {
active_objects: Arc<RwLock<HashMap<usize, ObjectTrackingInfo>>>,
stats: Arc<LeakDetectionStats>,
config: LeakDetectorConfig,
detection_thread: Option<std::thread::JoinHandle<()>>,
stop_flag: Arc<AtomicUsize>,
}
struct ObjectTrackingInfo {
address: usize,
size: usize,
allocated_at: Instant,
last_accessed: Instant,
access_count: AtomicUsize,
allocation_location: Option<String>,
object_type: ObjectType,
expected_lifetime: Option<Duration>,
leak_candidate: bool,
}
impl Clone for ObjectTrackingInfo {
fn clone(&self) -> Self {
Self {
address: self.address,
size: self.size,
allocated_at: self.allocated_at,
last_accessed: self.last_accessed,
access_count: AtomicUsize::new(self.access_count.load(Ordering::Relaxed)),
allocation_location: self.allocation_location.clone(),
object_type: self.object_type,
expected_lifetime: self.expected_lifetime,
leak_candidate: self.leak_candidate,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ObjectType {
Normal,
Cache,
Temporary,
LongLived,
Cyclic,
}
pub struct LeakDetectionStats {
pub total_allocated: AtomicU64,
pub total_freed: AtomicU64,
pub active_objects: AtomicUsize,
pub leaked_objects: AtomicU64,
pub leaked_bytes: AtomicU64,
pub false_positives: AtomicU64,
pub auto_cleaned_leaks: AtomicU64,
pub detection_runs: AtomicU64,
pub detection_time_ns: AtomicU64,
pub max_object_age_seconds: AtomicU64,
pub avg_object_age_seconds: AtomicU64,
}
#[derive(Debug, Clone)]
pub struct LeakDetectorConfig {
pub detection_interval: Duration,
pub leak_threshold_bytes: usize,
pub leak_threshold_objects: usize,
pub object_age_threshold: u64,
pub access_count_threshold: usize,
pub enable_auto_cleanup: bool,
pub auto_cleanup_threshold: usize,
pub detect_cyclic_references: bool,
pub history_retention_count: usize,
pub detailed_tracking: bool,
}
impl Default for LeakDetectorConfig {
fn default() -> Self {
Self {
detection_interval: Duration::from_secs(60), leak_threshold_bytes: 1024 * 1024, leak_threshold_objects: 100, object_age_threshold: 3600, access_count_threshold: 5, enable_auto_cleanup: true,
auto_cleanup_threshold: 10 * 1024 * 1024, detect_cyclic_references: true,
history_retention_count: 1000,
detailed_tracking: true,
}
}
}
#[derive(Debug, Clone)]
pub struct LeakReport {
detected_at: Instant,
leaked_object_count: usize,
leaked_memory_bytes: usize,
oldest_leak_age_seconds: u64,
leak_type_distribution: HashMap<ObjectType, usize>,
leak_details: Vec<LeakDetail>,
}
#[derive(Debug, Clone)]
pub struct LeakDetail {
address: usize,
size: usize,
allocated_at: Instant,
last_accessed: Instant,
access_count: usize,
object_type: ObjectType,
allocation_location: Option<String>,
severity: LeakSeverity,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LeakSeverity {
Low,
Medium,
High,
Critical,
}
impl MemoryLeakDetector {
pub fn new(config: LeakDetectorConfig) -> Self {
let stats: _ = Arc::new(Mutex::new(LeakDetectionStats {)),
total_allocated: AtomicU64::new(0))
total_freed: AtomicU64::new(0),
active_objects: AtomicUsize::new(0),
leaked_objects: AtomicU64::new(0),
leaked_bytes: AtomicU64::new(0),
false_positives: AtomicU64::new(0),
auto_cleaned_leaks: AtomicU64::new(0),
detection_runs: AtomicU64::new(0),
detection_time_ns: AtomicU64::new(0),
max_object_age_seconds: AtomicU64::new(0),
avg_object_age_seconds: AtomicU64::new(0),
});
let stop_flag: _ = Arc::new(Mutex::new(AtomicUsize::new(0)),;
let detection_thread: _ = Some(Self::start_detection_thread(
Arc::clone(stats),
Arc::clone(stop_flag),
config.clone(),
));
Self {
active_objects: Arc::new(Mutex::new(HashMap::new()))
stats,
config,
detection_thread,
stop_flag,
}
}
pub fn track_allocation(
&self,
address: usize,
size: usize,
object_type: ObjectType,
allocation_location: Option<String>,
) {
let mut objects = self.active_objects.write().unwrap();
let now: _ = Instant::now();
objects.insert(address, ObjectTrackingInfo {
address,
size,
allocated_at: now,
last_accessed: now,
access_count: AtomicUsize::new(0),
allocation_location,
object_type,
expected_lifetime: None,
leak_candidate: false,
});
self.stats.total_allocated.fetch_add(1, Ordering::Relaxed);
self.stats.active_objects.fetch_add(1, Ordering::Relaxed);
}
pub fn track_access(&self, address: usize) {
let mut objects = self.active_objects.write().unwrap();
if let Some(info) = objects.get_mut(&address) {
info.last_accessed = Instant::now();
info.access_count.fetch_add(1, Ordering::Relaxed);
}
}
pub fn track_deallocation(&self, address: usize) {
let mut objects = self.active_objects.write().unwrap();
if objects.remove(&address).is_some() {
self.stats.total_freed.fetch_add(1, Ordering::Relaxed);
self.stats.active_objects.fetch_sub(1, Ordering::Relaxed);
}
}
pub fn detect_leaks(&self) -> LeakReport {
let start_time: _ = Instant::now();
self.stats.detection_runs.fetch_add(1, Ordering::Relaxed);
let mut objects = self.active_objects.write().unwrap();
let now: _ = Instant::now();
let mut leak_details = Vec::new();
let mut total_leaked_bytes = 0usize;
let mut oldest_leak_age = 0u64;
let mut leak_type_distribution = HashMap::new();
for (address, info) in objects.iter_mut() {
let age_seconds: _ = now.duration_since(info.allocated_at).as_secs();
let access_count: _ = info.access_count.load(Ordering::Relaxed);
let is_leak: _ = self.is_potential_leak(info, age_seconds, access_count);
if is_leak {
info.leak_candidate = true;
let severity: _ = self.calculate_leak_severity(info, age_seconds, access_count);
let leak_detail: _ = LeakDetail {
address: *address,
size: info.size,
allocated_at: info.allocated_at,
last_accessed: info.last_accessed,
access_count,
object_type: info.object_type,
allocation_location: info.allocation_location.clone(),
severity,
};
leak_details.push(leak_detail);
total_leaked_bytes += info.size;
oldest_leak_age = oldest_leak_age.max(age_seconds);
*leak_type_distribution.entry(info.object_type).or_insert(0) += 1;
if self.config.enable_auto_cleanup
&& total_leaked_bytes >= self.config.auto_cleanup_threshold
{
self.auto_cleanup_leak(*address);
}
} else {
info.leak_candidate = false;
}
}
let detection_time: _ = start_time.elapsed();
self.stats.detection_time_ns.fetch_add(
detection_time.as_nanos() as u64,
Ordering::Relaxed
);
if !leak_details.is_empty() {
self.stats.leaked_objects.fetch_add(leak_details.len() as u64, Ordering::Relaxed);
self.stats.leaked_bytes.fetch_add(total_leaked_bytes as u64, Ordering::Relaxed);
let current_max: _ = self.stats.max_object_age_seconds.load(Ordering::Relaxed);
if oldest_leak_age > current_max {
self.stats.max_object_age_seconds.store(oldest_leak_age, Ordering::Relaxed);
}
let total_objects: _ = self.stats.active_objects.load(Ordering::Relaxed);
let current_avg: _ = self.stats.avg_object_age_seconds.load(Ordering::Relaxed);
let new_avg: _ = (current_avg * (total_objects as u64 - 1) + oldest_leak_age) / total_objects as u64;
self.stats.avg_object_age_seconds.store(new_avg, Ordering::Relaxed);
}
LeakReport {
detected_at: now,
leaked_object_count: leak_details.len(),
leaked_memory_bytes: total_leaked_bytes,
oldest_leak_age_seconds: oldest_leak_age,
leak_type_distribution,
leak_details,
}
}
fn is_potential_leak(&self, info: &ObjectTrackingInfo, age_seconds: u64, access_count: usize) -> bool {
if age_seconds > self.config.object_age_threshold {
if access_count < self.config.access_count_threshold {
match info.object_type {
ObjectType::Temporary => age_seconds > 300, ObjectType::Cache => access_count < 3,
ObjectType::Normal => age_seconds > 1800 && access_count < 5, ObjectType::LongLived => false, ObjectType::Cyclic => age_seconds > 600, }
} else {
false
}
} else {
false
}
}
fn calculate_leak_severity(&self, info: &ObjectTrackingInfo, age_seconds: u64, _access_count: usize) -> LeakSeverity {
let size_mb: _ = info.size as f64 / (1024.0 * 1024.0);
let age_hours: _ = age_seconds as f64 / 3600.0;
if size_mb > 100.0 || age_hours > 24.0 {
LeakSeverity::Critical
} else if size_mb > 10.0 || age_hours > 6.0 {
LeakSeverity::High
} else if size_mb > 1.0 || age_hours > 1.0 {
LeakSeverity::Medium
} else {
LeakSeverity::Low
}
}
fn auto_cleanup_leak(&self, address: usize) {
let mut objects = self.active_objects.write().unwrap();
if let Some(info) = objects.get(&address) {
if info.leak_candidate && info.size >= self.config.auto_cleanup_threshold {
objects.remove(&address);
self.stats.auto_cleaned_leaks.fetch_add(1, Ordering::Relaxed);
self.stats.active_objects.fetch_sub(1, Ordering::Relaxed);
self.stats.total_freed.fetch_add(1, Ordering::Relaxed);
}
}
}
fn start_detection_thread(
_stats: Arc<LeakDetectionStats>,
stop_flag: Arc<AtomicUsize>,
config: LeakDetectorConfig,
) -> std::thread::JoinHandle<()> {
std::thread::spawn(move || {
while stop_flag.load(Ordering::Relaxed) == 0 {
std::thread::sleep(config.detection_interval);
if stop_flag.load(Ordering::Relaxed) == 0 {
}
}
})
}
pub fn get_stats(&self) -> LeakDetectionStatsSnapshot {
LeakDetectionStatsSnapshot {
total_allocated: self.stats.total_allocated.load(Ordering::Relaxed),
total_freed: self.stats.total_freed.load(Ordering::Relaxed),
active_objects: self.stats.active_objects.load(Ordering::Relaxed),
leaked_objects: self.stats.leaked_objects.load(Ordering::Relaxed),
leaked_bytes: self.stats.leaked_bytes.load(Ordering::Relaxed),
false_positives: self.stats.false_positives.load(Ordering::Relaxed),
auto_cleaned_leaks: self.stats.auto_cleaned_leaks.load(Ordering::Relaxed),
detection_runs: self.stats.detection_runs.load(Ordering::Relaxed),
avg_detection_time_ms: if self.stats.detection_runs.load(Ordering::Relaxed) > 0 {
self.stats.detection_time_ns.load(Ordering::Relaxed) as f64
/ self.stats.detection_runs.load(Ordering::Relaxed) as f64
/ 1_000_000.0
} else {
0.0
},
max_object_age_seconds: self.stats.max_object_age_seconds.load(Ordering::Relaxed),
avg_object_age_seconds: self.stats.avg_object_age_seconds.load(Ordering::Relaxed),
leak_detection_rate: if self.stats.total_allocated.load(Ordering::Relaxed) > 0 {
self.stats.leaked_objects.load(Ordering::Relaxed) as f64
/ self.stats.total_allocated.load(Ordering::Relaxed) as f64
* 100.0
} else {
0.0
},
}
}
pub fn get_object_info(&self, address: usize) -> Option<ObjectTrackingInfo> {
let objects: _ = self.active_objects.read().unwrap();
objects.get(&address).cloned()
}
pub fn get_all_active_objects(&self) -> HashMap<usize, ObjectTrackingInfo> {
let objects: _ = self.active_objects.read().unwrap();
objects.clone()
}
pub fn stop(&mut self) {
self.stop_flag.store(1, Ordering::Relaxed);
if let Some(handle) = self.detection_thread.take() {
handle.join().unwrap();
}
}
}
#[derive(Debug, Clone)]
pub struct LeakDetectionStatsSnapshot {
pub total_allocated: u64,
pub total_freed: u64,
pub active_objects: usize,
pub leaked_objects: u64,
pub leaked_bytes: u64,
pub false_positives: u64,
pub auto_cleaned_leaks: u64,
pub detection_runs: u64,
pub avg_detection_time_ms: f64,
pub max_object_age_seconds: u64,
pub avg_object_age_seconds: u64,
pub leak_detection_rate: f64,
}
impl Drop for MemoryLeakDetector {
fn drop(&mut self) {
self.stop();
}
}