use crate::core::{MemScopeError, MemScopeResult};
use rayon::prelude::*;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
#[derive(Debug, Clone, serde::Serialize)]
pub struct VariableInfo {
pub var_name: String,
pub type_name: String,
pub timestamp: u64,
pub size: usize,
pub thread_id: usize,
pub memory_usage: u64,
}
static GLOBAL_VARIABLE_REGISTRY: OnceLock<Arc<Mutex<HashMap<usize, VariableInfo>>>> =
OnceLock::new();
fn get_global_registry() -> Arc<Mutex<HashMap<usize, VariableInfo>>> {
GLOBAL_VARIABLE_REGISTRY
.get_or_init(|| Arc::new(Mutex::new(HashMap::new())))
.clone()
}
pub struct VariableRegistry;
impl VariableRegistry {
pub fn register_variable(
address: usize,
var_name: String,
type_name: String,
size: usize,
) -> MemScopeResult<()> {
let thread_id = {
static THREAD_COUNTER: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(1);
static THREAD_ID_MAP: std::sync::OnceLock<
std::sync::Mutex<std::collections::HashMap<std::thread::ThreadId, usize>>,
> = std::sync::OnceLock::new();
let map = THREAD_ID_MAP
.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
let current_thread_id = std::thread::current().id();
if let Ok(mut map) = map.try_lock() {
*map.entry(current_thread_id).or_insert_with(|| {
THREAD_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
})
} else {
1
}
};
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
let var_info = VariableInfo {
var_name,
type_name,
timestamp,
size,
thread_id,
memory_usage: size as u64,
};
let mut retry_count = 0;
const MAX_RETRIES: usize = 5;
const INITIAL_BACKOFF_MS: u64 = 1;
loop {
if let Ok(mut registry) = get_global_registry().try_lock() {
registry.insert(address, var_info);
break;
} else {
retry_count += 1;
if retry_count >= MAX_RETRIES {
tracing::warn!(
"Variable registration for '{}' dropped after {} retries due to lock contention. ptr=0x{:x}, size={}",
var_info.var_name, MAX_RETRIES, address, size
);
#[cfg(debug_assertions)]
eprintln!(
"[memscope] Warning: Variable registration for '{}' dropped after {} retries due to lock contention",
var_info.var_name, MAX_RETRIES
);
break;
}
let backoff_ms = INITIAL_BACKOFF_MS * (1 << (retry_count - 1));
std::thread::sleep(std::time::Duration::from_millis(backoff_ms));
}
}
Ok(())
}
pub fn get_variable_info(address: usize) -> Option<VariableInfo> {
if let Ok(registry) = get_global_registry().try_lock() {
registry.get(&address).cloned()
} else {
None
}
}
pub fn mark_variable_destroyed(address: usize, destruction_time: u64) -> MemScopeResult<()> {
tracing::debug!(
"Variable at address 0x{:x} destroyed at {}",
address,
destruction_time
);
Ok(())
}
pub fn get_all_variables() -> HashMap<usize, VariableInfo> {
if let Ok(registry) = get_global_registry().try_lock() {
registry.clone()
} else {
HashMap::new()
}
}
pub fn enhance_allocations_with_registry(
allocations: &[crate::core::types::AllocationInfo],
) -> Vec<serde_json::Value> {
if allocations.len() < 100 {
return Self::enhance_allocations_sequential(allocations);
}
tracing::info!(
"🚀 Processing {} allocations with parallel optimization...",
allocations.len()
);
let registry = Self::get_all_variables();
let start_time = std::time::Instant::now();
let allocations: Vec<crate::capture::types::AllocationInfo> =
allocations.iter().map(|a| a.clone().into()).collect();
let enhanced: Vec<serde_json::Value> = allocations
.par_iter()
.map(|alloc| Self::classify_single_allocation(alloc, ®istry))
.collect();
let duration = start_time.elapsed();
tracing::info!(
"✅ Parallel processing completed in {:?} ({:.2} allocs/ms)",
duration,
allocations.len() as f64 / duration.as_millis() as f64
);
enhanced
}
fn enhance_allocations_sequential(
allocations: &[crate::core::types::AllocationInfo],
) -> Vec<serde_json::Value> {
let registry = Self::get_all_variables();
let allocations: Vec<crate::capture::types::AllocationInfo> =
allocations.iter().map(|a| a.clone().into()).collect();
allocations
.iter()
.map(|alloc| Self::classify_single_allocation(alloc, ®istry))
.collect()
}
fn classify_and_enhance_allocations(
allocations: &[crate::capture::types::allocation::AllocationInfo],
registry: &HashMap<usize, VariableInfo>,
) -> Vec<serde_json::Value> {
allocations
.par_iter()
.map(|alloc| Self::classify_single_allocation(alloc, registry))
.collect()
}
fn classify_single_allocation(
alloc: &crate::capture::types::allocation::AllocationInfo,
registry: &HashMap<usize, VariableInfo>,
) -> serde_json::Value {
if let Some(var_info) = registry.get(&alloc.ptr) {
return serde_json::json!({
"ptr": alloc.ptr,
"size": alloc.size,
"timestamp_alloc": alloc.timestamp_alloc,
"timestamp_dealloc": alloc.timestamp_dealloc,
"variable_name": var_info.var_name,
"type_name": var_info.type_name,
"scope_name": Self::extract_scope_from_var_name(&var_info.var_name),
"allocation_source": "user",
"tracking_method": "track_var_macro",
"registry_timestamp": var_info.timestamp,
"registry_size": var_info.size,
"lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
(dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
),
"current_age_ms": if alloc.timestamp_dealloc.is_none() {
let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
} else {
None
},
"is_active": alloc.timestamp_dealloc.is_none()
});
}
if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
return serde_json::json!({
"ptr": alloc.ptr,
"size": alloc.size,
"timestamp_alloc": alloc.timestamp_alloc,
"timestamp_dealloc": alloc.timestamp_dealloc,
"variable_name": var_name,
"type_name": type_name,
"scope_name": alloc.scope_name.as_deref().unwrap_or("user_scope"),
"allocation_source": "user",
"tracking_method": "explicit_tracking",
"lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
(dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
),
"current_age_ms": if alloc.timestamp_dealloc.is_none() {
let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
} else {
None
},
"is_active": alloc.timestamp_dealloc.is_none()
});
}
let (inferred_var_name, inferred_type_name) = Self::infer_allocation_info_cached(alloc);
let system_category = Self::categorize_system_allocation(alloc);
serde_json::json!({
"ptr": alloc.ptr,
"size": alloc.size,
"timestamp_alloc": alloc.timestamp_alloc,
"timestamp_dealloc": alloc.timestamp_dealloc,
"variable_name": inferred_var_name,
"type_name": inferred_type_name,
"scope_name": "system",
"allocation_source": "system",
"tracking_method": "automatic_inference",
"system_category": system_category,
"lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
(dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
),
"current_age_ms": if alloc.timestamp_dealloc.is_none() {
let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
} else {
None
},
"is_active": alloc.timestamp_dealloc.is_none()
})
}
fn extract_scope_from_var_name(var_name: &str) -> String {
if let Some(current_scope) = Self::get_current_scope_name() {
return current_scope;
}
if var_name.contains("::") {
if let Some(scope_part) = var_name.split("::").next() {
return scope_part.to_string();
}
}
if var_name.starts_with("main_") {
"main_function".to_string()
} else if var_name.starts_with("test_") {
"test_function".to_string()
} else if var_name.starts_with("fn_") {
"user_function".to_string()
} else if var_name.contains("_vec")
|| var_name.contains("_string")
|| var_name.contains("_data")
|| var_name.starts_with("boxed_")
|| var_name.starts_with("rc_")
|| var_name.starts_with("arc_")
{
"user_scope".to_string()
} else {
"user_scope".to_string()
}
}
fn get_current_scope_name() -> Option<String> {
if let Some(scope_name) = Self::get_scope_from_tracker() {
return Some(scope_name);
}
Self::infer_scope_from_call_stack()
}
fn get_scope_from_tracker() -> Option<String> {
use crate::core::scope_tracker::get_global_scope_tracker;
let scope_tracker = get_global_scope_tracker();
let thread_id = format!("{:?}", std::thread::current().id());
if let Some(thread_stack) = scope_tracker
.scope_stack
.read()
.ok()
.and_then(|s| s.get(&thread_id).cloned())
{
if let Some(current_scope_id) = thread_stack.last() {
if let Some(scope_info) = scope_tracker
.active_scopes
.read()
.ok()
.and_then(|s| s.get(current_scope_id).cloned())
{
return Some(scope_info.name);
}
}
}
None
}
fn infer_scope_from_call_stack() -> Option<String> {
let backtrace = std::backtrace::Backtrace::capture();
let backtrace_str = format!("{backtrace:?}");
for line in backtrace_str.lines() {
if line.contains("::main") {
return Some("main_function".to_string());
}
if line.contains("test_") || line.contains("tests::") {
return Some("test_function".to_string());
}
if let Some(func_name) = Self::extract_function_name_from_backtrace(line) {
return Some(format!("function_{func_name}"));
}
}
Some("user_code_scope".to_string())
}
fn extract_function_name_from_backtrace(line: &str) -> Option<String> {
if let Some(start) = line.find("::") {
if let Some(end) = line[start + 2..].find("::") {
let func_name = &line[start + 2..start + 2 + end];
if !func_name.starts_with("_")
&& !func_name.contains("alloc")
&& !func_name.contains("std")
&& !func_name.contains("core")
&& func_name.len() > 2
{
return Some(func_name.to_string());
}
}
}
None
}
fn categorize_system_allocation(
alloc: &crate::capture::types::allocation::AllocationInfo,
) -> String {
match alloc.size {
1..=16 => "small_system_alloc",
17..=64 => "medium_system_alloc",
65..=1024 => "large_system_alloc",
1025..=65536 => "buffer_allocation",
_ => "huge_allocation",
}
.to_string()
}
fn group_by_scope(
active: &[serde_json::Value],
history: &[serde_json::Value],
) -> serde_json::Value {
let mut scopes: HashMap<String, Vec<&serde_json::Value>> = HashMap::new();
for alloc in active {
if let Some(scope) = alloc["scope_name"].as_str() {
scopes.entry(scope.to_string()).or_default().push(alloc);
}
}
for alloc in history {
if let Some(scope) = alloc["scope_name"].as_str() {
scopes.entry(scope.to_string()).or_default().push(alloc);
}
}
let scope_summary: HashMap<String, serde_json::Value> = scopes
.into_iter()
.map(|(scope_name, allocations)| {
let total_size: u64 = allocations
.iter()
.map(|a| a["size"].as_u64().unwrap_or(0))
.sum();
(
scope_name.clone(),
serde_json::json!({
"scope_name": scope_name,
"allocation_count": allocations.len(),
"total_size_bytes": total_size,
"allocations": allocations
}),
)
})
.collect();
serde_json::json!(scope_summary)
}
fn get_scope_summary(registry: &HashMap<usize, VariableInfo>) -> serde_json::Value {
let mut scope_counts: HashMap<String, usize> = HashMap::new();
for var_info in registry.values() {
let scope = Self::extract_scope_from_var_name(&var_info.var_name);
*scope_counts.entry(scope).or_insert(0) += 1;
}
serde_json::json!(scope_counts)
}
fn analyze_lifecycle_statistics(
user_active: &[serde_json::Value],
user_history: &[serde_json::Value],
system_active: &[serde_json::Value],
system_history: &[serde_json::Value],
) -> serde_json::Value {
let all_user: Vec<&serde_json::Value> =
user_active.iter().chain(user_history.iter()).collect();
let all_system: Vec<&serde_json::Value> =
system_active.iter().chain(system_history.iter()).collect();
let user_lifetimes: Vec<u64> = all_user
.iter()
.filter_map(|a| a["lifetime_ms"].as_u64())
.collect();
let user_active_count = all_user
.iter()
.filter(|a| a["is_active"].as_bool().unwrap_or(false))
.count();
let user_deallocated_count = all_user
.iter()
.filter(|a| !a["timestamp_dealloc"].is_null())
.count();
let system_lifetimes: Vec<u64> = all_system
.iter()
.filter_map(|a| a["lifetime_ms"].as_u64())
.collect();
let system_active_count = all_system
.iter()
.filter(|a| a["is_active"].as_bool().unwrap_or(false))
.count();
let system_deallocated_count = all_system
.iter()
.filter(|a| !a["timestamp_dealloc"].is_null())
.count();
serde_json::json!({
"user_allocations": {
"total_count": all_user.len(),
"active_count": user_active_count,
"deallocated_count": user_deallocated_count,
"leaked_count": user_active_count, "lifetime_stats": Self::calculate_lifetime_stats(&user_lifetimes),
"average_lifetime_ms": if !user_lifetimes.is_empty() {
user_lifetimes.iter().sum::<u64>() / user_lifetimes.len() as u64
} else { 0 },
"max_lifetime_ms": user_lifetimes.iter().max().copied().unwrap_or(0),
"min_lifetime_ms": user_lifetimes.iter().min().copied().unwrap_or(0)
},
"system_allocations": {
"total_count": all_system.len(),
"active_count": system_active_count,
"deallocated_count": system_deallocated_count,
"leaked_count": system_active_count,
"lifetime_stats": Self::calculate_lifetime_stats(&system_lifetimes),
"average_lifetime_ms": if !system_lifetimes.is_empty() {
system_lifetimes.iter().sum::<u64>() / system_lifetimes.len() as u64
} else { 0 },
"max_lifetime_ms": system_lifetimes.iter().max().copied().unwrap_or(0),
"min_lifetime_ms": system_lifetimes.iter().min().copied().unwrap_or(0)
},
"comparison": {
"user_vs_system_active_ratio": if system_active_count > 0 {
user_active_count as f64 / system_active_count as f64
} else { 0.0 },
"user_vs_system_lifetime_ratio": if !system_lifetimes.is_empty() && !user_lifetimes.is_empty() {
(user_lifetimes.iter().sum::<u64>() / user_lifetimes.len() as u64) as f64 /
(system_lifetimes.iter().sum::<u64>() / system_lifetimes.len() as u64) as f64
} else { 0.0 }
}
})
}
fn analyze_deallocation_patterns(
user_active: &[serde_json::Value],
user_history: &[serde_json::Value],
system_active: &[serde_json::Value],
system_history: &[serde_json::Value],
) -> serde_json::Value {
let all_user: Vec<&serde_json::Value> =
user_active.iter().chain(user_history.iter()).collect();
let all_system: Vec<&serde_json::Value> =
system_active.iter().chain(system_history.iter()).collect();
let user_dealloc_times: Vec<u64> = all_user
.iter()
.filter_map(|a| a["timestamp_dealloc"].as_u64())
.collect();
let system_dealloc_times: Vec<u64> = all_system
.iter()
.filter_map(|a| a["timestamp_dealloc"].as_u64())
.collect();
let user_null_dealloc = all_user
.iter()
.filter(|a| a["timestamp_dealloc"].is_null())
.count();
let system_null_dealloc = all_system
.iter()
.filter(|a| a["timestamp_dealloc"].is_null())
.count();
serde_json::json!({
"user_deallocations": {
"total_deallocated": user_dealloc_times.len(),
"still_active": user_null_dealloc,
"deallocation_rate": if !all_user.is_empty() {
user_dealloc_times.len() as f64 / all_user.len() as f64 * 100.0
} else { 0.0 },
"earliest_dealloc": user_dealloc_times.iter().min().copied(),
"latest_dealloc": user_dealloc_times.iter().max().copied(),
"deallocation_timespan_ms": if user_dealloc_times.len() > 1 {
user_dealloc_times.iter().max().unwrap_or(&0) -
user_dealloc_times.iter().min().unwrap_or(&0)
} else { 0 }
},
"system_deallocations": {
"total_deallocated": system_dealloc_times.len(),
"still_active": system_null_dealloc,
"deallocation_rate": if !all_system.is_empty() {
system_dealloc_times.len() as f64 / all_system.len() as f64 * 100.0
} else { 0.0 },
"earliest_dealloc": system_dealloc_times.iter().min().copied(),
"latest_dealloc": system_dealloc_times.iter().max().copied(),
"deallocation_timespan_ms": if system_dealloc_times.len() > 1 {
system_dealloc_times.iter().max().unwrap_or(&0) -
system_dealloc_times.iter().min().unwrap_or(&0)
} else { 0 }
},
"memory_leak_analysis": {
"user_potential_leaks": user_null_dealloc,
"system_potential_leaks": system_null_dealloc,
"total_potential_leaks": user_null_dealloc + system_null_dealloc,
"user_leak_percentage": if !all_user.is_empty() {
user_null_dealloc as f64 / all_user.len() as f64 * 100.0
} else { 0.0 },
"system_leak_percentage": if !all_system.is_empty() {
system_null_dealloc as f64 / all_system.len() as f64 * 100.0
} else { 0.0 }
}
})
}
fn calculate_lifetime_stats(lifetimes: &[u64]) -> serde_json::Value {
if lifetimes.is_empty() {
return serde_json::json!({
"count": 0,
"categories": {
"very_short": 0, "short": 0, "medium": 0, "long": 0, "very_long": 0 }
});
}
let mut very_short = 0;
let mut short = 0;
let mut medium = 0;
let mut long = 0;
let mut very_long = 0;
for &lifetime in lifetimes {
match lifetime {
0..=1 => very_short += 1,
2..=10 => short += 1,
11..=100 => medium += 1,
101..=1000 => long += 1,
_ => very_long += 1,
}
}
serde_json::json!({
"count": lifetimes.len(),
"categories": {
"very_short": very_short,
"short": short,
"medium": medium,
"long": long,
"very_long": very_long
},
"percentiles": {
"p50": Self::calculate_percentile(lifetimes, 50.0),
"p90": Self::calculate_percentile(lifetimes, 90.0),
"p95": Self::calculate_percentile(lifetimes, 95.0),
"p99": Self::calculate_percentile(lifetimes, 99.0)
}
})
}
fn calculate_percentile(sorted_values: &[u64], percentile: f64) -> u64 {
if sorted_values.is_empty() {
return 0;
}
let mut values = sorted_values.to_vec();
values.sort_unstable();
let index = (percentile / 100.0 * (values.len() - 1) as f64) as usize;
values[index.min(values.len() - 1)]
}
pub fn infer_allocation_info_cached(
alloc: &crate::capture::types::allocation::AllocationInfo,
) -> (String, String) {
static COMMON_TYPES: &[(usize, &str, &str)] = &[
(1, "box_u8", "Box<u8>"),
(2, "box_u16", "Box<u16>"),
(4, "box_u32", "Box<u32>"),
(8, "box_u64", "Box<u64>"),
(16, "small_alloc_16b", "SmallAlloc"),
(24, "string_alloc", "String"),
(32, "string_alloc", "String"),
];
for &(size, var_prefix, type_name) in COMMON_TYPES {
if alloc.size == size {
return (
format!("{var_prefix}_{:x}", alloc.ptr),
type_name.to_string(),
);
}
}
Self::infer_allocation_info(alloc)
}
pub fn infer_allocation_info(
alloc: &crate::capture::types::allocation::AllocationInfo,
) -> (String, String) {
let size = alloc.size;
let (var_name, type_name) = match size {
8..=32 if size.is_power_of_two() => (
format!("string_alloc_{:x}", alloc.ptr),
"String".to_string(),
),
s if s % 8 == 0 && s >= 16 => {
let elements = s / 8;
(
format!("vec_i64_{elements}elem_{:x}", alloc.ptr),
"Vec<i64>".to_string(),
)
}
s if s % 4 == 0 && s >= 8 => {
let elements = s / 4;
(
format!("vec_i32_{elements}elem_{:x}", alloc.ptr),
"Vec<i32>".to_string(),
)
}
1 => (format!("box_u8_{:x}", alloc.ptr), "Box<u8>".to_string()),
2 => (format!("box_u16_{:x}", alloc.ptr), "Box<u16>".to_string()),
4 => (format!("box_u32_{:x}", alloc.ptr), "Box<u32>".to_string()),
8 => (format!("box_u64_{:x}", alloc.ptr), "Box<u64>".to_string()),
s if s >= 64 && s % 16 == 0 => (
format!("hashmap_alloc_{:x}", alloc.ptr),
"HashMap<K,V>".to_string(),
),
s if s >= 1024 => {
let kb = s / 1024;
(
format!("large_buffer_{}kb_{:x}", kb, alloc.ptr),
"LargeBuffer".to_string(),
)
}
s if s <= 16 => (
format!("small_alloc_{s}b_{:x}", alloc.ptr),
"SmallAlloc".to_string(),
),
_ => (
format!("system_alloc_{size}b_{:x}", alloc.ptr),
"SystemAlloc".to_string(),
),
};
(var_name, type_name)
}
pub fn generate_comprehensive_export(
tracker: &crate::core::tracker::MemoryTracker,
) -> MemScopeResult<serde_json::Value> {
let start_time = std::time::Instant::now();
tracing::info!(
"🔄 Starting comprehensive export generation with allocation classification..."
);
let (active_allocations, other_data) = rayon::join(
|| tracker.get_active_allocations(),
|| {
let history = tracker.get_active_allocations();
let memory_types = tracker.get_memory_by_type();
let stats = tracker.get_stats();
let registry = Self::get_all_variables();
(history, memory_types, stats, registry)
},
);
let active_allocations = active_allocations.map_err(|e| {
MemScopeError::error(
"variable_registry",
"generate_comprehensive_export",
e.to_string(),
)
})?;
let (allocation_history, memory_by_type, stats, registry) = {
let allocation_history = other_data.0.map_err(|e| {
MemScopeError::error(
"variable_registry",
"generate_comprehensive_export",
e.to_string(),
)
})?;
let memory_by_type = other_data.1.map_err(|e| {
MemScopeError::error(
"variable_registry",
"generate_comprehensive_export",
e.to_string(),
)
})?;
let stats = other_data.2.map_err(|e| {
MemScopeError::error(
"variable_registry",
"generate_comprehensive_export",
e.to_string(),
)
})?;
let registry = other_data.3;
(allocation_history, memory_by_type, stats, registry)
};
tracing::info!(
"📊 Data loaded: {} active, {} history, {} registry entries",
active_allocations.len(),
allocation_history.len(),
registry.len()
);
let filtered_active: Vec<_> = if active_allocations.len() > 10000 {
active_allocations
.into_iter()
.filter(|alloc| alloc.size >= 8)
.collect()
} else {
active_allocations
};
let filtered_history: Vec<_> = if allocation_history.len() > 50000 {
allocation_history
.into_iter()
.filter(|alloc| alloc.size >= 8)
.collect()
} else {
allocation_history
};
let filtered_active: Vec<crate::capture::types::AllocationInfo> =
filtered_active.into_iter().map(|a| a.into()).collect();
let filtered_history: Vec<crate::capture::types::AllocationInfo> =
filtered_history.into_iter().map(|a| a.into()).collect();
let (classified_active, classified_history) = rayon::join(
|| Self::classify_and_enhance_allocations(&filtered_active, ®istry),
|| Self::classify_and_enhance_allocations(&filtered_history, ®istry),
);
let (user_active, system_active): (Vec<_>, Vec<_>) = classified_active
.into_iter()
.partition(|alloc| alloc["allocation_source"] == "user");
let (user_history, system_history): (Vec<_>, Vec<_>) = classified_history
.into_iter()
.partition(|alloc| alloc["allocation_source"] == "user");
let user_scopes = Self::group_by_scope(&user_active, &user_history);
let comprehensive_data = serde_json::json!({
"memory_analysis": {
"user_allocations": {
"active": user_active,
"history": user_history,
"by_scope": user_scopes,
"total_count": user_active.len() + user_history.len()
},
"system_allocations": {
"active": system_active,
"history": system_history,
"total_count": system_active.len() + system_history.len()
},
"memory_by_type": memory_by_type,
"statistics": {
"overall": stats,
"user_vs_system": {
"user_active_count": user_active.len(),
"system_active_count": system_active.len(),
"user_total_size": user_active.iter()
.map(|a| a["size"].as_u64().unwrap_or(0))
.sum::<u64>(),
"system_total_size": system_active.iter()
.map(|a| a["size"].as_u64().unwrap_or(0))
.sum::<u64>()
},
"lifecycle_analysis": Self::analyze_lifecycle_statistics(&user_active, &user_history, &system_active, &system_history),
"deallocation_analysis": Self::analyze_deallocation_patterns(&user_active, &user_history, &system_active, &system_history)
}
},
"variable_registry": {
"total_variables": registry.len(),
"user_variables": registry.values().collect::<Vec<_>>(),
"scope_summary": Self::get_scope_summary(®istry)
},
"export_metadata": {
"timestamp": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
"total_allocations": user_active.len() + user_history.len() + system_active.len() + system_history.len(),
"processing_time_ms": start_time.elapsed().as_millis(),
"classification_features": [
"user_vs_system_separation",
"scope_based_grouping",
"allocation_source_tracking",
"enhanced_type_inference"
]
}
});
let total_time = start_time.elapsed();
tracing::info!(
"✅ Export completed in {:?} - User: {}, System: {}",
total_time,
user_active.len() + user_history.len(),
system_active.len() + system_history.len()
);
Ok(comprehensive_data)
}
pub fn clear_registry() -> MemScopeResult<()> {
if let Ok(mut registry) = get_global_registry().try_lock() {
registry.clear();
}
Ok(())
}
pub fn get_stats() -> (usize, usize) {
if let Ok(registry) = get_global_registry().try_lock() {
let total = registry.len();
let recent = registry
.values()
.filter(|v| {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
now - v.timestamp < 1_000_000_000 })
.count();
(total, recent)
} else {
(0, 0)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::types::AllocationInfo;
fn create_test_allocation(
ptr: usize,
size: usize,
var_name: Option<String>,
type_name: Option<String>,
) -> AllocationInfo {
AllocationInfo {
ptr,
size,
var_name,
type_name,
scope_name: Some("test_scope".to_string()),
timestamp_alloc: 1000000,
timestamp_dealloc: Some(2000000),
thread_id: "test_thread".to_string(),
borrow_count: 0,
stack_trace: Some(vec!["test_function".to_string()]),
is_leaked: false,
lifetime_ms: Some(1000),
borrow_info: None,
clone_info: None,
ownership_history_available: false,
smart_pointer_info: None,
memory_layout: None,
generic_info: None,
dynamic_type_info: None,
runtime_state: None,
stack_allocation: None,
temporary_object: None,
fragmentation_analysis: None,
generic_instantiation: None,
type_relationships: None,
type_usage: None,
function_call_tracking: None,
lifecycle_tracking: None,
access_tracking: None,
drop_chain_analysis: None,
}
}
#[test]
fn test_variable_registry_register_and_get() {
let _ = VariableRegistry::clear_registry();
let address = 0x10000; let var_name = "test_var".to_string();
let type_name = "String".to_string();
let size = 24;
let result =
VariableRegistry::register_variable(address, var_name.clone(), type_name.clone(), size);
assert!(result.is_ok());
let var_info = VariableRegistry::get_variable_info(address);
assert!(var_info.is_some());
let info = var_info.unwrap();
assert_eq!(info.var_name, var_name);
assert_eq!(info.type_name, type_name);
assert_eq!(info.size, size);
assert!(info.timestamp > 0);
assert!(info.thread_id > 0);
assert_eq!(info.memory_usage, size as u64);
}
#[test]
fn test_variable_registry_mark_destroyed() {
let address = 0x2000;
let destruction_time = 5000000;
let result = VariableRegistry::mark_variable_destroyed(address, destruction_time);
assert!(result.is_ok());
}
#[test]
fn test_get_all_variables() {
let _ = VariableRegistry::clear_registry();
let address1 = 0x30000; let address2 = 0x40000;
let result1 =
VariableRegistry::register_variable(address1, "var1".to_string(), "i32".to_string(), 4);
let result2 = VariableRegistry::register_variable(
address2,
"var2".to_string(),
"String".to_string(),
24,
);
assert!(result1.is_ok());
assert!(result2.is_ok());
let all_vars = VariableRegistry::get_all_variables();
assert!(
all_vars.contains_key(&address1) || all_vars.contains_key(&address2) || !all_vars.is_empty(),
"Registry should contain at least one variable or the ones we just added. Registry size: {}",
all_vars.len()
);
}
#[test]
fn test_enhance_allocations_with_registry() {
let explicit_alloc = create_test_allocation(
0x60000,
50,
Some("explicit_var".to_string()),
Some("i64".to_string()),
);
let system_alloc = create_test_allocation(0x70000, 200, None, None);
let allocations = vec![explicit_alloc, system_alloc];
let enhanced = VariableRegistry::enhance_allocations_with_registry(&allocations);
assert_eq!(enhanced.len(), 2);
let user_count = enhanced
.iter()
.filter(|a| a["allocation_source"] == "user")
.count();
let system_count = enhanced
.iter()
.filter(|a| a["allocation_source"] == "system")
.count();
assert_eq!(user_count, 1, "Should have exactly one user allocation");
assert_eq!(system_count, 1, "Should have exactly one system allocation");
let explicit_result = enhanced
.iter()
.find(|a| a["ptr"].as_u64().unwrap() as usize == 0x60000)
.expect("Should find explicit allocation");
assert_eq!(explicit_result["allocation_source"], "user");
assert_eq!(explicit_result["variable_name"], "explicit_var");
assert_eq!(explicit_result["type_name"], "i64");
assert_eq!(explicit_result["tracking_method"], "explicit_tracking");
let system_result = enhanced
.iter()
.find(|a| a["ptr"].as_u64().unwrap() as usize == 0x70000)
.expect("Should find system allocation");
assert_eq!(system_result["allocation_source"], "system");
assert_eq!(system_result["tracking_method"], "automatic_inference");
assert!(!system_result["variable_name"].as_str().unwrap().is_empty());
assert!(!system_result["type_name"].as_str().unwrap().is_empty());
let test_addr = 0x50000;
if VariableRegistry::clear_registry().is_ok()
&& VariableRegistry::register_variable(
test_addr,
"tracked_var".to_string(),
"Vec<u8>".to_string(),
100,
)
.is_ok()
{
let registry_alloc = create_test_allocation(test_addr, 100, None, None);
let enhanced_with_registry =
VariableRegistry::enhance_allocations_with_registry(&[registry_alloc]);
if enhanced_with_registry.len() == 1 {
let result = &enhanced_with_registry[0];
if result["allocation_source"] == "user" {
assert_eq!(result["variable_name"], "tracked_var");
assert_eq!(result["type_name"], "Vec<u8>");
assert_eq!(result["tracking_method"], "track_var_macro");
}
}
}
}
#[test]
fn test_extract_scope_from_var_name() {
let _ = VariableRegistry::clear_registry();
let result1 = VariableRegistry::extract_scope_from_var_name("scope::variable");
assert!(!result1.is_empty(), "Scope name should not be empty");
assert!(
result1 == "scope"
|| result1 == "user_code_scope"
|| result1 == "user_scope"
|| result1.starts_with("function_")
|| result1 == "main_function"
|| result1 == "test_function",
"Expected a valid scope name, but got: '{result1}'"
);
let result2 = VariableRegistry::extract_scope_from_var_name("my_vec");
assert!(!result2.is_empty(), "Scope name should not be empty");
assert!(
result2 == "user_scope"
|| result2 == "user_code_scope"
|| result2.starts_with("function_")
|| result2 == "main_function"
|| result2 == "test_function",
"Expected a valid scope name, but got: '{result2}'"
);
let result3 = VariableRegistry::extract_scope_from_var_name("main_variable");
assert!(!result3.is_empty(), "Scope name should not be empty");
assert!(
result3 == "main_function"
|| result3 == "user_code_scope"
|| result3 == "user_scope"
|| result3.starts_with("function_")
|| result3 == "test_function",
"Expected a valid scope name, but got: '{result3}'"
);
let result4 = VariableRegistry::extract_scope_from_var_name("test_variable");
assert!(!result4.is_empty(), "Scope name should not be empty");
assert!(
result4 == "test_function"
|| result4 == "user_code_scope"
|| result4 == "user_scope"
|| result4.starts_with("function_")
|| result4 == "main_function",
"Expected a valid scope name, but got: '{result4}'"
);
}
#[test]
fn test_calculate_lifetime_stats() {
let lifetimes = vec![0, 1, 5, 15, 50, 150, 500, 1500];
let stats = VariableRegistry::calculate_lifetime_stats(&lifetimes);
assert_eq!(stats["count"], 8);
assert_eq!(stats["categories"]["very_short"], 2); assert_eq!(stats["categories"]["short"], 1); assert_eq!(stats["categories"]["medium"], 2); assert_eq!(stats["categories"]["long"], 2); assert_eq!(stats["categories"]["very_long"], 1);
let empty_lifetimes: Vec<u64> = vec![];
let empty_stats = VariableRegistry::calculate_lifetime_stats(&empty_lifetimes);
assert_eq!(empty_stats["count"], 0);
}
#[test]
fn test_group_by_scope() {
let active_allocations = vec![
serde_json::json!({
"scope_name": "main_function",
"size": 100,
"ptr": 0x1000
}),
serde_json::json!({
"scope_name": "test_function",
"size": 200,
"ptr": 0x2000
}),
];
let history_allocations = vec![serde_json::json!({
"scope_name": "main_function",
"size": 150,
"ptr": 0x3000
})];
let grouped = VariableRegistry::group_by_scope(&active_allocations, &history_allocations);
assert!(
grouped["main_function"]["allocation_count"]
.as_u64()
.unwrap()
>= 2
);
assert!(
grouped["test_function"]["allocation_count"]
.as_u64()
.unwrap()
>= 1
);
assert!(
grouped["main_function"]["total_size_bytes"]
.as_u64()
.unwrap()
>= 250
);
}
#[test]
fn test_get_scope_summary() {
let mut registry = HashMap::new();
registry.insert(
0x1000,
VariableInfo {
var_name: "main_var".to_string(),
type_name: "i32".to_string(),
timestamp: 1000,
size: 4,
thread_id: 1,
memory_usage: 4,
},
);
registry.insert(
0x2000,
VariableInfo {
var_name: "test_var".to_string(),
type_name: "String".to_string(),
timestamp: 2000,
size: 24,
thread_id: 2,
memory_usage: 24,
},
);
let summary = VariableRegistry::get_scope_summary(®istry);
if let Some(obj) = summary.as_object() {
let total_count: u64 = obj.values().map(|v| v.as_u64().unwrap_or(0)).sum();
assert!(total_count >= 2);
let has_main = obj
.get("main_function")
.and_then(|v| v.as_u64())
.unwrap_or(0)
>= 1;
let has_test = obj
.get("test_function")
.and_then(|v| v.as_u64())
.unwrap_or(0)
>= 1;
let has_user_code = obj
.get("user_code_scope")
.and_then(|v| v.as_u64())
.unwrap_or(0)
>= 1;
assert!(has_main || has_test || has_user_code);
} else {
panic!("Expected summary to be a JSON object");
}
}
}