1use crate::core::{MemScopeError, MemScopeResult};
7use rayon::prelude::*;
8use std::collections::HashMap;
9use std::sync::{Arc, Mutex, OnceLock};
10
11#[derive(Debug, Clone, serde::Serialize)]
13pub struct VariableInfo {
14 pub var_name: String,
16 pub type_name: String,
18 pub timestamp: u64,
20 pub size: usize,
22 pub thread_id: usize,
24 pub memory_usage: u64,
26}
27
28static GLOBAL_VARIABLE_REGISTRY: OnceLock<Arc<Mutex<HashMap<usize, VariableInfo>>>> =
30 OnceLock::new();
31
32fn get_global_registry() -> Arc<Mutex<HashMap<usize, VariableInfo>>> {
34 GLOBAL_VARIABLE_REGISTRY
35 .get_or_init(|| Arc::new(Mutex::new(HashMap::new())))
36 .clone()
37}
38
39pub struct VariableRegistry;
41
42impl VariableRegistry {
43 pub fn register_variable(
54 address: usize,
55 var_name: String,
56 type_name: String,
57 size: usize,
58 ) -> MemScopeResult<()> {
59 let thread_id = {
60 static THREAD_COUNTER: std::sync::atomic::AtomicUsize =
62 std::sync::atomic::AtomicUsize::new(1);
63 static THREAD_ID_MAP: std::sync::OnceLock<
64 std::sync::Mutex<std::collections::HashMap<std::thread::ThreadId, usize>>,
65 > = std::sync::OnceLock::new();
66
67 let map = THREAD_ID_MAP
68 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
69 let current_thread_id = std::thread::current().id();
70
71 if let Ok(mut map) = map.try_lock() {
72 *map.entry(current_thread_id).or_insert_with(|| {
73 THREAD_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
74 })
75 } else {
76 1
78 }
79 };
80 let timestamp = std::time::SystemTime::now()
81 .duration_since(std::time::UNIX_EPOCH)
82 .unwrap_or_default()
83 .as_nanos() as u64;
84
85 let var_info = VariableInfo {
86 var_name,
87 type_name,
88 timestamp,
89 size,
90 thread_id,
91 memory_usage: size as u64,
92 };
93
94 let mut retry_count = 0;
96 const MAX_RETRIES: usize = 5;
97 const INITIAL_BACKOFF_MS: u64 = 1;
98
99 loop {
100 if let Ok(mut registry) = get_global_registry().try_lock() {
101 registry.insert(address, var_info);
102 break;
103 } else {
104 retry_count += 1;
105 if retry_count >= MAX_RETRIES {
106 tracing::warn!(
108 "Variable registration for '{}' dropped after {} retries due to lock contention. ptr=0x{:x}, size={}",
109 var_info.var_name, MAX_RETRIES, address, size
110 );
111 #[cfg(debug_assertions)]
112 eprintln!(
113 "[memscope] Warning: Variable registration for '{}' dropped after {} retries due to lock contention",
114 var_info.var_name, MAX_RETRIES
115 );
116 break;
117 }
118
119 let backoff_ms = INITIAL_BACKOFF_MS * (1 << (retry_count - 1));
121 std::thread::sleep(std::time::Duration::from_millis(backoff_ms));
122 }
123 }
124
125 Ok(())
126 }
127
128 pub fn get_variable_info(address: usize) -> Option<VariableInfo> {
130 if let Ok(registry) = get_global_registry().try_lock() {
131 registry.get(&address).cloned()
132 } else {
133 None
134 }
135 }
136
137 pub fn mark_variable_destroyed(address: usize, destruction_time: u64) -> MemScopeResult<()> {
139 tracing::debug!(
142 "Variable at address 0x{:x} destroyed at {}",
143 address,
144 destruction_time
145 );
146 Ok(())
147 }
148
149 pub fn get_all_variables() -> HashMap<usize, VariableInfo> {
151 if let Ok(registry) = get_global_registry().try_lock() {
152 registry.clone()
153 } else {
154 HashMap::new()
155 }
156 }
157
158 pub fn enhance_allocations_with_registry(
160 allocations: &[crate::core::types::AllocationInfo],
161 ) -> Vec<serde_json::Value> {
162 if allocations.len() < 100 {
164 return Self::enhance_allocations_sequential(allocations);
165 }
166
167 tracing::info!(
168 "🚀 Processing {} allocations with parallel optimization...",
169 allocations.len()
170 );
171
172 let registry = Self::get_all_variables();
173 let start_time = std::time::Instant::now();
174
175 let allocations: Vec<crate::capture::types::AllocationInfo> =
177 allocations.iter().map(|a| a.clone().into()).collect();
178
179 let enhanced: Vec<serde_json::Value> = allocations
181 .par_iter()
182 .map(|alloc| Self::classify_single_allocation(alloc, ®istry))
183 .collect();
184
185 let duration = start_time.elapsed();
186 tracing::info!(
187 "✅ Parallel processing completed in {:?} ({:.2} allocs/ms)",
188 duration,
189 allocations.len() as f64 / duration.as_millis() as f64
190 );
191
192 enhanced
193 }
194
195 fn enhance_allocations_sequential(
197 allocations: &[crate::core::types::AllocationInfo],
198 ) -> Vec<serde_json::Value> {
199 let registry = Self::get_all_variables();
200
201 let allocations: Vec<crate::capture::types::AllocationInfo> =
203 allocations.iter().map(|a| a.clone().into()).collect();
204
205 allocations
206 .iter()
207 .map(|alloc| Self::classify_single_allocation(alloc, ®istry))
208 .collect()
209 }
210
211 fn classify_and_enhance_allocations(
213 allocations: &[crate::capture::types::allocation::AllocationInfo],
214 registry: &HashMap<usize, VariableInfo>,
215 ) -> Vec<serde_json::Value> {
216 allocations
217 .par_iter()
218 .map(|alloc| Self::classify_single_allocation(alloc, registry))
219 .collect()
220 }
221
222 fn classify_single_allocation(
224 alloc: &crate::capture::types::allocation::AllocationInfo,
225 registry: &HashMap<usize, VariableInfo>,
226 ) -> serde_json::Value {
227 if let Some(var_info) = registry.get(&alloc.ptr) {
229 return serde_json::json!({
230 "ptr": alloc.ptr,
231 "size": alloc.size,
232 "timestamp_alloc": alloc.timestamp_alloc,
233 "timestamp_dealloc": alloc.timestamp_dealloc,
234 "variable_name": var_info.var_name,
235 "type_name": var_info.type_name,
236 "scope_name": Self::extract_scope_from_var_name(&var_info.var_name),
237 "allocation_source": "user",
238 "tracking_method": "track_var_macro",
239 "registry_timestamp": var_info.timestamp,
240 "registry_size": var_info.size,
241 "lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
242 (dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
243 ),
244 "current_age_ms": if alloc.timestamp_dealloc.is_none() {
245 let current_time = std::time::SystemTime::now()
247 .duration_since(std::time::UNIX_EPOCH)
248 .unwrap_or_default()
249 .as_nanos() as u64;
250 Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
251 } else {
252 None
253 },
254 "is_active": alloc.timestamp_dealloc.is_none()
255 });
256 }
257
258 if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
260 return serde_json::json!({
261 "ptr": alloc.ptr,
262 "size": alloc.size,
263 "timestamp_alloc": alloc.timestamp_alloc,
264 "timestamp_dealloc": alloc.timestamp_dealloc,
265 "variable_name": var_name,
266 "type_name": type_name,
267 "scope_name": alloc.scope_name.as_deref().unwrap_or("user_scope"),
268 "allocation_source": "user",
269 "tracking_method": "explicit_tracking",
270 "lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
271 (dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
272 ),
273 "current_age_ms": if alloc.timestamp_dealloc.is_none() {
274 let current_time = std::time::SystemTime::now()
275 .duration_since(std::time::UNIX_EPOCH)
276 .unwrap_or_default()
277 .as_nanos() as u64;
278 Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
279 } else {
280 None
281 },
282 "is_active": alloc.timestamp_dealloc.is_none()
283 });
284 }
285
286 let (inferred_var_name, inferred_type_name) = Self::infer_allocation_info_cached(alloc);
288 let system_category = Self::categorize_system_allocation(alloc);
289
290 serde_json::json!({
291 "ptr": alloc.ptr,
292 "size": alloc.size,
293 "timestamp_alloc": alloc.timestamp_alloc,
294 "timestamp_dealloc": alloc.timestamp_dealloc,
295 "variable_name": inferred_var_name,
296 "type_name": inferred_type_name,
297 "scope_name": "system",
298 "allocation_source": "system",
299 "tracking_method": "automatic_inference",
300 "system_category": system_category,
301 "lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
302 (dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
303 ),
304 "current_age_ms": if alloc.timestamp_dealloc.is_none() {
305 let current_time = std::time::SystemTime::now()
306 .duration_since(std::time::UNIX_EPOCH)
307 .unwrap_or_default()
308 .as_nanos() as u64;
309 Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
310 } else {
311 None
312 },
313 "is_active": alloc.timestamp_dealloc.is_none()
314 })
315 }
316
317 fn extract_scope_from_var_name(var_name: &str) -> String {
319 if let Some(current_scope) = Self::get_current_scope_name() {
321 return current_scope;
322 }
323
324 if var_name.contains("::") {
326 if let Some(scope_part) = var_name.split("::").next() {
327 return scope_part.to_string();
328 }
329 }
330
331 if var_name.starts_with("main_") {
333 "main_function".to_string()
334 } else if var_name.starts_with("test_") {
335 "test_function".to_string()
336 } else if var_name.starts_with("fn_") {
337 "user_function".to_string()
338 } else if var_name.contains("_vec")
339 || var_name.contains("_string")
340 || var_name.contains("_data")
341 || var_name.starts_with("boxed_")
342 || var_name.starts_with("rc_")
343 || var_name.starts_with("arc_")
344 {
345 "user_scope".to_string()
346 } else {
347 "user_scope".to_string()
349 }
350 }
351
352 fn get_current_scope_name() -> Option<String> {
354 if let Some(scope_name) = Self::get_scope_from_tracker() {
356 return Some(scope_name);
357 }
358
359 Self::infer_scope_from_call_stack()
361 }
362
363 fn get_scope_from_tracker() -> Option<String> {
365 use crate::core::scope_tracker::get_global_scope_tracker;
366
367 let scope_tracker = get_global_scope_tracker();
368 let thread_id = format!("{:?}", std::thread::current().id());
369
370 if let Some(thread_stack) = scope_tracker
371 .scope_stack
372 .read()
373 .ok()
374 .and_then(|s| s.get(&thread_id).cloned())
375 {
376 if let Some(current_scope_id) = thread_stack.last() {
377 if let Some(scope_info) = scope_tracker
378 .active_scopes
379 .read()
380 .ok()
381 .and_then(|s| s.get(current_scope_id).cloned())
382 {
383 return Some(scope_info.name);
384 }
385 }
386 }
387
388 None
389 }
390
391 fn infer_scope_from_call_stack() -> Option<String> {
393 let backtrace = std::backtrace::Backtrace::capture();
395 let backtrace_str = format!("{backtrace:?}");
396
397 for line in backtrace_str.lines() {
399 if line.contains("::main") {
400 return Some("main_function".to_string());
401 }
402 if line.contains("test_") || line.contains("tests::") {
403 return Some("test_function".to_string());
404 }
405 if let Some(func_name) = Self::extract_function_name_from_backtrace(line) {
407 return Some(format!("function_{func_name}"));
408 }
409 }
410
411 Some("user_code_scope".to_string())
413 }
414
415 fn extract_function_name_from_backtrace(line: &str) -> Option<String> {
417 if let Some(start) = line.find("::") {
419 if let Some(end) = line[start + 2..].find("::") {
420 let func_name = &line[start + 2..start + 2 + end];
421 if !func_name.starts_with("_")
423 && !func_name.contains("alloc")
424 && !func_name.contains("std")
425 && !func_name.contains("core")
426 && func_name.len() > 2
427 {
428 return Some(func_name.to_string());
429 }
430 }
431 }
432 None
433 }
434
435 fn categorize_system_allocation(
437 alloc: &crate::capture::types::allocation::AllocationInfo,
438 ) -> String {
439 match alloc.size {
440 1..=16 => "small_system_alloc",
441 17..=64 => "medium_system_alloc",
442 65..=1024 => "large_system_alloc",
443 1025..=65536 => "buffer_allocation",
444 _ => "huge_allocation",
445 }
446 .to_string()
447 }
448
449 fn group_by_scope(
451 active: &[serde_json::Value],
452 history: &[serde_json::Value],
453 ) -> serde_json::Value {
454 let mut scopes: HashMap<String, Vec<&serde_json::Value>> = HashMap::new();
455
456 for alloc in active {
458 if let Some(scope) = alloc["scope_name"].as_str() {
459 scopes.entry(scope.to_string()).or_default().push(alloc);
460 }
461 }
462
463 for alloc in history {
465 if let Some(scope) = alloc["scope_name"].as_str() {
466 scopes.entry(scope.to_string()).or_default().push(alloc);
467 }
468 }
469
470 let scope_summary: HashMap<String, serde_json::Value> = scopes
471 .into_iter()
472 .map(|(scope_name, allocations)| {
473 let total_size: u64 = allocations
474 .iter()
475 .map(|a| a["size"].as_u64().unwrap_or(0))
476 .sum();
477
478 (
479 scope_name.clone(),
480 serde_json::json!({
481 "scope_name": scope_name,
482 "allocation_count": allocations.len(),
483 "total_size_bytes": total_size,
484 "allocations": allocations
485 }),
486 )
487 })
488 .collect();
489
490 serde_json::json!(scope_summary)
491 }
492
493 fn get_scope_summary(registry: &HashMap<usize, VariableInfo>) -> serde_json::Value {
495 let mut scope_counts: HashMap<String, usize> = HashMap::new();
496
497 for var_info in registry.values() {
498 let scope = Self::extract_scope_from_var_name(&var_info.var_name);
499 *scope_counts.entry(scope).or_insert(0) += 1;
500 }
501
502 serde_json::json!(scope_counts)
503 }
504
505 fn analyze_lifecycle_statistics(
507 user_active: &[serde_json::Value],
508 user_history: &[serde_json::Value],
509 system_active: &[serde_json::Value],
510 system_history: &[serde_json::Value],
511 ) -> serde_json::Value {
512 let all_user: Vec<&serde_json::Value> =
514 user_active.iter().chain(user_history.iter()).collect();
515 let all_system: Vec<&serde_json::Value> =
516 system_active.iter().chain(system_history.iter()).collect();
517
518 let user_lifetimes: Vec<u64> = all_user
520 .iter()
521 .filter_map(|a| a["lifetime_ms"].as_u64())
522 .collect();
523
524 let user_active_count = all_user
525 .iter()
526 .filter(|a| a["is_active"].as_bool().unwrap_or(false))
527 .count();
528
529 let user_deallocated_count = all_user
530 .iter()
531 .filter(|a| !a["timestamp_dealloc"].is_null())
532 .count();
533
534 let system_lifetimes: Vec<u64> = all_system
536 .iter()
537 .filter_map(|a| a["lifetime_ms"].as_u64())
538 .collect();
539
540 let system_active_count = all_system
541 .iter()
542 .filter(|a| a["is_active"].as_bool().unwrap_or(false))
543 .count();
544
545 let system_deallocated_count = all_system
546 .iter()
547 .filter(|a| !a["timestamp_dealloc"].is_null())
548 .count();
549
550 serde_json::json!({
551 "user_allocations": {
552 "total_count": all_user.len(),
553 "active_count": user_active_count,
554 "deallocated_count": user_deallocated_count,
555 "leaked_count": user_active_count, "lifetime_stats": Self::calculate_lifetime_stats(&user_lifetimes),
557 "average_lifetime_ms": if !user_lifetimes.is_empty() {
558 user_lifetimes.iter().sum::<u64>() / user_lifetimes.len() as u64
559 } else { 0 },
560 "max_lifetime_ms": user_lifetimes.iter().max().copied().unwrap_or(0),
561 "min_lifetime_ms": user_lifetimes.iter().min().copied().unwrap_or(0)
562 },
563 "system_allocations": {
564 "total_count": all_system.len(),
565 "active_count": system_active_count,
566 "deallocated_count": system_deallocated_count,
567 "leaked_count": system_active_count,
568 "lifetime_stats": Self::calculate_lifetime_stats(&system_lifetimes),
569 "average_lifetime_ms": if !system_lifetimes.is_empty() {
570 system_lifetimes.iter().sum::<u64>() / system_lifetimes.len() as u64
571 } else { 0 },
572 "max_lifetime_ms": system_lifetimes.iter().max().copied().unwrap_or(0),
573 "min_lifetime_ms": system_lifetimes.iter().min().copied().unwrap_or(0)
574 },
575 "comparison": {
576 "user_vs_system_active_ratio": if system_active_count > 0 {
577 user_active_count as f64 / system_active_count as f64
578 } else { 0.0 },
579 "user_vs_system_lifetime_ratio": if !system_lifetimes.is_empty() && !user_lifetimes.is_empty() {
580 (user_lifetimes.iter().sum::<u64>() / user_lifetimes.len() as u64) as f64 /
581 (system_lifetimes.iter().sum::<u64>() / system_lifetimes.len() as u64) as f64
582 } else { 0.0 }
583 }
584 })
585 }
586
587 fn analyze_deallocation_patterns(
589 user_active: &[serde_json::Value],
590 user_history: &[serde_json::Value],
591 system_active: &[serde_json::Value],
592 system_history: &[serde_json::Value],
593 ) -> serde_json::Value {
594 let all_user: Vec<&serde_json::Value> =
595 user_active.iter().chain(user_history.iter()).collect();
596 let all_system: Vec<&serde_json::Value> =
597 system_active.iter().chain(system_history.iter()).collect();
598
599 let user_dealloc_times: Vec<u64> = all_user
601 .iter()
602 .filter_map(|a| a["timestamp_dealloc"].as_u64())
603 .collect();
604
605 let system_dealloc_times: Vec<u64> = all_system
606 .iter()
607 .filter_map(|a| a["timestamp_dealloc"].as_u64())
608 .collect();
609
610 let user_null_dealloc = all_user
612 .iter()
613 .filter(|a| a["timestamp_dealloc"].is_null())
614 .count();
615
616 let system_null_dealloc = all_system
617 .iter()
618 .filter(|a| a["timestamp_dealloc"].is_null())
619 .count();
620
621 serde_json::json!({
622 "user_deallocations": {
623 "total_deallocated": user_dealloc_times.len(),
624 "still_active": user_null_dealloc,
625 "deallocation_rate": if !all_user.is_empty() {
626 user_dealloc_times.len() as f64 / all_user.len() as f64 * 100.0
627 } else { 0.0 },
628 "earliest_dealloc": user_dealloc_times.iter().min().copied(),
629 "latest_dealloc": user_dealloc_times.iter().max().copied(),
630 "deallocation_timespan_ms": if user_dealloc_times.len() > 1 {
631 user_dealloc_times.iter().max().unwrap_or(&0) -
632 user_dealloc_times.iter().min().unwrap_or(&0)
633 } else { 0 }
634 },
635 "system_deallocations": {
636 "total_deallocated": system_dealloc_times.len(),
637 "still_active": system_null_dealloc,
638 "deallocation_rate": if !all_system.is_empty() {
639 system_dealloc_times.len() as f64 / all_system.len() as f64 * 100.0
640 } else { 0.0 },
641 "earliest_dealloc": system_dealloc_times.iter().min().copied(),
642 "latest_dealloc": system_dealloc_times.iter().max().copied(),
643 "deallocation_timespan_ms": if system_dealloc_times.len() > 1 {
644 system_dealloc_times.iter().max().unwrap_or(&0) -
645 system_dealloc_times.iter().min().unwrap_or(&0)
646 } else { 0 }
647 },
648 "memory_leak_analysis": {
649 "user_potential_leaks": user_null_dealloc,
650 "system_potential_leaks": system_null_dealloc,
651 "total_potential_leaks": user_null_dealloc + system_null_dealloc,
652 "user_leak_percentage": if !all_user.is_empty() {
653 user_null_dealloc as f64 / all_user.len() as f64 * 100.0
654 } else { 0.0 },
655 "system_leak_percentage": if !all_system.is_empty() {
656 system_null_dealloc as f64 / all_system.len() as f64 * 100.0
657 } else { 0.0 }
658 }
659 })
660 }
661
662 fn calculate_lifetime_stats(lifetimes: &[u64]) -> serde_json::Value {
664 if lifetimes.is_empty() {
665 return serde_json::json!({
666 "count": 0,
667 "categories": {
668 "very_short": 0, "short": 0, "medium": 0, "long": 0, "very_long": 0 }
674 });
675 }
676
677 let mut very_short = 0;
678 let mut short = 0;
679 let mut medium = 0;
680 let mut long = 0;
681 let mut very_long = 0;
682
683 for &lifetime in lifetimes {
684 match lifetime {
685 0..=1 => very_short += 1,
686 2..=10 => short += 1,
687 11..=100 => medium += 1,
688 101..=1000 => long += 1,
689 _ => very_long += 1,
690 }
691 }
692
693 serde_json::json!({
694 "count": lifetimes.len(),
695 "categories": {
696 "very_short": very_short,
697 "short": short,
698 "medium": medium,
699 "long": long,
700 "very_long": very_long
701 },
702 "percentiles": {
703 "p50": Self::calculate_percentile(lifetimes, 50.0),
704 "p90": Self::calculate_percentile(lifetimes, 90.0),
705 "p95": Self::calculate_percentile(lifetimes, 95.0),
706 "p99": Self::calculate_percentile(lifetimes, 99.0)
707 }
708 })
709 }
710
711 fn calculate_percentile(sorted_values: &[u64], percentile: f64) -> u64 {
713 if sorted_values.is_empty() {
714 return 0;
715 }
716
717 let mut values = sorted_values.to_vec();
718 values.sort_unstable();
719
720 let index = (percentile / 100.0 * (values.len() - 1) as f64) as usize;
721 values[index.min(values.len() - 1)]
722 }
723
724 pub fn infer_allocation_info_cached(
726 alloc: &crate::capture::types::allocation::AllocationInfo,
727 ) -> (String, String) {
728 static COMMON_TYPES: &[(usize, &str, &str)] = &[
730 (1, "box_u8", "Box<u8>"),
731 (2, "box_u16", "Box<u16>"),
732 (4, "box_u32", "Box<u32>"),
733 (8, "box_u64", "Box<u64>"),
734 (16, "small_alloc_16b", "SmallAlloc"),
735 (24, "string_alloc", "String"),
736 (32, "string_alloc", "String"),
737 ];
738
739 for &(size, var_prefix, type_name) in COMMON_TYPES {
741 if alloc.size == size {
742 return (
743 format!("{var_prefix}_{:x}", alloc.ptr),
744 type_name.to_string(),
745 );
746 }
747 }
748
749 Self::infer_allocation_info(alloc)
751 }
752
753 pub fn infer_allocation_info(
755 alloc: &crate::capture::types::allocation::AllocationInfo,
756 ) -> (String, String) {
757 let size = alloc.size;
758
759 let (var_name, type_name) = match size {
761 8..=32 if size.is_power_of_two() => (
763 format!("string_alloc_{:x}", alloc.ptr),
764 "String".to_string(),
765 ),
766 s if s % 8 == 0 && s >= 16 => {
768 let elements = s / 8;
769 (
770 format!("vec_i64_{elements}elem_{:x}", alloc.ptr),
771 "Vec<i64>".to_string(),
772 )
773 }
774 s if s % 4 == 0 && s >= 8 => {
775 let elements = s / 4;
776 (
777 format!("vec_i32_{elements}elem_{:x}", alloc.ptr),
778 "Vec<i32>".to_string(),
779 )
780 }
781 1 => (format!("box_u8_{:x}", alloc.ptr), "Box<u8>".to_string()),
783 2 => (format!("box_u16_{:x}", alloc.ptr), "Box<u16>".to_string()),
784 4 => (format!("box_u32_{:x}", alloc.ptr), "Box<u32>".to_string()),
785 8 => (format!("box_u64_{:x}", alloc.ptr), "Box<u64>".to_string()),
786 s if s >= 64 && s % 16 == 0 => (
788 format!("hashmap_alloc_{:x}", alloc.ptr),
789 "HashMap<K,V>".to_string(),
790 ),
791 s if s >= 1024 => {
793 let kb = s / 1024;
794 (
795 format!("large_buffer_{}kb_{:x}", kb, alloc.ptr),
796 "LargeBuffer".to_string(),
797 )
798 }
799 s if s <= 16 => (
801 format!("small_alloc_{s}b_{:x}", alloc.ptr),
802 "SmallAlloc".to_string(),
803 ),
804 _ => (
806 format!("system_alloc_{size}b_{:x}", alloc.ptr),
807 "SystemAlloc".to_string(),
808 ),
809 };
810
811 (var_name, type_name)
812 }
813
814 pub fn generate_comprehensive_export(
816 tracker: &crate::core::tracker::MemoryTracker,
817 ) -> MemScopeResult<serde_json::Value> {
818 let start_time = std::time::Instant::now();
819 tracing::info!(
820 "🔄 Starting comprehensive export generation with allocation classification..."
821 );
822
823 let (active_allocations, other_data) = rayon::join(
825 || tracker.get_active_allocations(),
826 || {
827 let history = tracker.get_active_allocations();
828 let memory_types = tracker.get_memory_by_type();
829 let stats = tracker.get_stats();
830 let registry = Self::get_all_variables();
831 (history, memory_types, stats, registry)
832 },
833 );
834
835 let active_allocations = active_allocations.map_err(|e| {
836 MemScopeError::error(
837 "variable_registry",
838 "generate_comprehensive_export",
839 e.to_string(),
840 )
841 })?;
842 let (allocation_history, memory_by_type, stats, registry) = {
843 let allocation_history = other_data.0.map_err(|e| {
844 MemScopeError::error(
845 "variable_registry",
846 "generate_comprehensive_export",
847 e.to_string(),
848 )
849 })?;
850 let memory_by_type = other_data.1.map_err(|e| {
851 MemScopeError::error(
852 "variable_registry",
853 "generate_comprehensive_export",
854 e.to_string(),
855 )
856 })?;
857 let stats = other_data.2.map_err(|e| {
858 MemScopeError::error(
859 "variable_registry",
860 "generate_comprehensive_export",
861 e.to_string(),
862 )
863 })?;
864 let registry = other_data.3;
865 (allocation_history, memory_by_type, stats, registry)
866 };
867
868 tracing::info!(
869 "📊 Data loaded: {} active, {} history, {} registry entries",
870 active_allocations.len(),
871 allocation_history.len(),
872 registry.len()
873 );
874
875 let filtered_active: Vec<_> = if active_allocations.len() > 10000 {
877 active_allocations
878 .into_iter()
879 .filter(|alloc| alloc.size >= 8)
880 .collect()
881 } else {
882 active_allocations
883 };
884
885 let filtered_history: Vec<_> = if allocation_history.len() > 50000 {
886 allocation_history
887 .into_iter()
888 .filter(|alloc| alloc.size >= 8)
889 .collect()
890 } else {
891 allocation_history
892 };
893
894 let filtered_active: Vec<crate::capture::types::AllocationInfo> =
896 filtered_active.into_iter().map(|a| a.into()).collect();
897 let filtered_history: Vec<crate::capture::types::AllocationInfo> =
898 filtered_history.into_iter().map(|a| a.into()).collect();
899
900 let (classified_active, classified_history) = rayon::join(
902 || Self::classify_and_enhance_allocations(&filtered_active, ®istry),
903 || Self::classify_and_enhance_allocations(&filtered_history, ®istry),
904 );
905
906 let (user_active, system_active): (Vec<_>, Vec<_>) = classified_active
908 .into_iter()
909 .partition(|alloc| alloc["allocation_source"] == "user");
910
911 let (user_history, system_history): (Vec<_>, Vec<_>) = classified_history
912 .into_iter()
913 .partition(|alloc| alloc["allocation_source"] == "user");
914
915 let user_scopes = Self::group_by_scope(&user_active, &user_history);
917
918 let comprehensive_data = serde_json::json!({
920 "memory_analysis": {
921 "user_allocations": {
922 "active": user_active,
923 "history": user_history,
924 "by_scope": user_scopes,
925 "total_count": user_active.len() + user_history.len()
926 },
927 "system_allocations": {
928 "active": system_active,
929 "history": system_history,
930 "total_count": system_active.len() + system_history.len()
931 },
932 "memory_by_type": memory_by_type,
933 "statistics": {
934 "overall": stats,
935 "user_vs_system": {
936 "user_active_count": user_active.len(),
937 "system_active_count": system_active.len(),
938 "user_total_size": user_active.iter()
939 .map(|a| a["size"].as_u64().unwrap_or(0))
940 .sum::<u64>(),
941 "system_total_size": system_active.iter()
942 .map(|a| a["size"].as_u64().unwrap_or(0))
943 .sum::<u64>()
944 },
945 "lifecycle_analysis": Self::analyze_lifecycle_statistics(&user_active, &user_history, &system_active, &system_history),
946 "deallocation_analysis": Self::analyze_deallocation_patterns(&user_active, &user_history, &system_active, &system_history)
947 }
948 },
949 "variable_registry": {
950 "total_variables": registry.len(),
951 "user_variables": registry.values().collect::<Vec<_>>(),
952 "scope_summary": Self::get_scope_summary(®istry)
953 },
954 "export_metadata": {
955 "timestamp": std::time::SystemTime::now()
956 .duration_since(std::time::UNIX_EPOCH)
957 .unwrap_or_default()
958 .as_secs(),
959 "total_allocations": user_active.len() + user_history.len() + system_active.len() + system_history.len(),
960 "processing_time_ms": start_time.elapsed().as_millis(),
961 "classification_features": [
962 "user_vs_system_separation",
963 "scope_based_grouping",
964 "allocation_source_tracking",
965 "enhanced_type_inference"
966 ]
967 }
968 });
969
970 let total_time = start_time.elapsed();
971 tracing::info!(
972 "✅ Export completed in {:?} - User: {}, System: {}",
973 total_time,
974 user_active.len() + user_history.len(),
975 system_active.len() + system_history.len()
976 );
977
978 Ok(comprehensive_data)
979 }
980
981 pub fn clear_registry() -> MemScopeResult<()> {
983 if let Ok(mut registry) = get_global_registry().try_lock() {
984 registry.clear();
985 }
986 Ok(())
987 }
988
989 pub fn get_stats() -> (usize, usize) {
991 if let Ok(registry) = get_global_registry().try_lock() {
992 let total = registry.len();
993 let recent = registry
994 .values()
995 .filter(|v| {
996 let now = std::time::SystemTime::now()
997 .duration_since(std::time::UNIX_EPOCH)
998 .unwrap_or_default()
999 .as_nanos() as u64;
1000 now - v.timestamp < 1_000_000_000 })
1002 .count();
1003 (total, recent)
1004 } else {
1005 (0, 0)
1006 }
1007 }
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012 use super::*;
1013 use crate::core::types::AllocationInfo;
1014
1015 fn create_test_allocation(
1016 ptr: usize,
1017 size: usize,
1018 var_name: Option<String>,
1019 type_name: Option<String>,
1020 ) -> AllocationInfo {
1021 AllocationInfo {
1022 ptr,
1023 size,
1024 var_name,
1025 type_name,
1026 scope_name: Some("test_scope".to_string()),
1027 timestamp_alloc: 1000000,
1028 timestamp_dealloc: Some(2000000),
1029 thread_id: "test_thread".to_string(),
1030 borrow_count: 0,
1031 stack_trace: Some(vec!["test_function".to_string()]),
1032 is_leaked: false,
1033 lifetime_ms: Some(1000),
1034 borrow_info: None,
1035 clone_info: None,
1036 ownership_history_available: false,
1037 smart_pointer_info: None,
1038 memory_layout: None,
1039 generic_info: None,
1040 dynamic_type_info: None,
1041 runtime_state: None,
1042 stack_allocation: None,
1043 temporary_object: None,
1044 fragmentation_analysis: None,
1045 generic_instantiation: None,
1046 type_relationships: None,
1047 type_usage: None,
1048 function_call_tracking: None,
1049 lifecycle_tracking: None,
1050 access_tracking: None,
1051 drop_chain_analysis: None,
1052 }
1053 }
1054
1055 #[test]
1056 fn test_variable_registry_register_and_get() {
1057 let _ = VariableRegistry::clear_registry();
1059
1060 let address = 0x10000; let var_name = "test_var".to_string();
1062 let type_name = "String".to_string();
1063 let size = 24;
1064
1065 let result =
1067 VariableRegistry::register_variable(address, var_name.clone(), type_name.clone(), size);
1068 assert!(result.is_ok());
1069
1070 let var_info = VariableRegistry::get_variable_info(address);
1072 assert!(var_info.is_some());
1073
1074 let info = var_info.unwrap();
1075 assert_eq!(info.var_name, var_name);
1076 assert_eq!(info.type_name, type_name);
1077 assert_eq!(info.size, size);
1078 assert!(info.timestamp > 0);
1079 assert!(info.thread_id > 0);
1080 assert_eq!(info.memory_usage, size as u64);
1081 }
1082
1083 #[test]
1084 fn test_variable_registry_mark_destroyed() {
1085 let address = 0x2000;
1086 let destruction_time = 5000000;
1087
1088 let result = VariableRegistry::mark_variable_destroyed(address, destruction_time);
1089 assert!(result.is_ok());
1090 }
1091
1092 #[test]
1093 fn test_get_all_variables() {
1094 let _ = VariableRegistry::clear_registry();
1096
1097 let address1 = 0x30000; let address2 = 0x40000;
1099
1100 let result1 =
1101 VariableRegistry::register_variable(address1, "var1".to_string(), "i32".to_string(), 4);
1102 let result2 = VariableRegistry::register_variable(
1103 address2,
1104 "var2".to_string(),
1105 "String".to_string(),
1106 24,
1107 );
1108
1109 assert!(result1.is_ok());
1111 assert!(result2.is_ok());
1112
1113 let all_vars = VariableRegistry::get_all_variables();
1114 assert!(
1117 all_vars.contains_key(&address1) || all_vars.contains_key(&address2) || !all_vars.is_empty(),
1118 "Registry should contain at least one variable or the ones we just added. Registry size: {}",
1119 all_vars.len()
1120 );
1121 }
1122
1123 #[test]
1124 fn test_enhance_allocations_with_registry() {
1125 let explicit_alloc = create_test_allocation(
1133 0x60000,
1134 50,
1135 Some("explicit_var".to_string()),
1136 Some("i64".to_string()),
1137 );
1138
1139 let system_alloc = create_test_allocation(0x70000, 200, None, None);
1141
1142 let allocations = vec![explicit_alloc, system_alloc];
1143 let enhanced = VariableRegistry::enhance_allocations_with_registry(&allocations);
1144
1145 assert_eq!(enhanced.len(), 2);
1146
1147 let user_count = enhanced
1149 .iter()
1150 .filter(|a| a["allocation_source"] == "user")
1151 .count();
1152 let system_count = enhanced
1153 .iter()
1154 .filter(|a| a["allocation_source"] == "system")
1155 .count();
1156
1157 assert_eq!(user_count, 1, "Should have exactly one user allocation");
1158 assert_eq!(system_count, 1, "Should have exactly one system allocation");
1159
1160 let explicit_result = enhanced
1162 .iter()
1163 .find(|a| a["ptr"].as_u64().unwrap() as usize == 0x60000)
1164 .expect("Should find explicit allocation");
1165
1166 assert_eq!(explicit_result["allocation_source"], "user");
1167 assert_eq!(explicit_result["variable_name"], "explicit_var");
1168 assert_eq!(explicit_result["type_name"], "i64");
1169 assert_eq!(explicit_result["tracking_method"], "explicit_tracking");
1170
1171 let system_result = enhanced
1173 .iter()
1174 .find(|a| a["ptr"].as_u64().unwrap() as usize == 0x70000)
1175 .expect("Should find system allocation");
1176
1177 assert_eq!(system_result["allocation_source"], "system");
1178 assert_eq!(system_result["tracking_method"], "automatic_inference");
1179 assert!(!system_result["variable_name"].as_str().unwrap().is_empty());
1181 assert!(!system_result["type_name"].as_str().unwrap().is_empty());
1182
1183 let test_addr = 0x50000;
1186 if VariableRegistry::clear_registry().is_ok()
1187 && VariableRegistry::register_variable(
1188 test_addr,
1189 "tracked_var".to_string(),
1190 "Vec<u8>".to_string(),
1191 100,
1192 )
1193 .is_ok()
1194 {
1195 let registry_alloc = create_test_allocation(test_addr, 100, None, None);
1197 let enhanced_with_registry =
1198 VariableRegistry::enhance_allocations_with_registry(&[registry_alloc]);
1199
1200 if enhanced_with_registry.len() == 1 {
1201 let result = &enhanced_with_registry[0];
1202 if result["allocation_source"] == "user" {
1204 assert_eq!(result["variable_name"], "tracked_var");
1205 assert_eq!(result["type_name"], "Vec<u8>");
1206 assert_eq!(result["tracking_method"], "track_var_macro");
1207 }
1208 }
1211 }
1212 }
1213
1214 #[test]
1215 fn test_extract_scope_from_var_name() {
1216 let _ = VariableRegistry::clear_registry();
1218
1219 let result1 = VariableRegistry::extract_scope_from_var_name("scope::variable");
1222 assert!(!result1.is_empty(), "Scope name should not be empty");
1224 assert!(
1225 result1 == "scope"
1226 || result1 == "user_code_scope"
1227 || result1 == "user_scope"
1228 || result1.starts_with("function_")
1229 || result1 == "main_function"
1230 || result1 == "test_function",
1231 "Expected a valid scope name, but got: '{result1}'"
1232 );
1233
1234 let result2 = VariableRegistry::extract_scope_from_var_name("my_vec");
1235 assert!(!result2.is_empty(), "Scope name should not be empty");
1236 assert!(
1237 result2 == "user_scope"
1238 || result2 == "user_code_scope"
1239 || result2.starts_with("function_")
1240 || result2 == "main_function"
1241 || result2 == "test_function",
1242 "Expected a valid scope name, but got: '{result2}'"
1243 );
1244
1245 let result3 = VariableRegistry::extract_scope_from_var_name("main_variable");
1246 assert!(!result3.is_empty(), "Scope name should not be empty");
1247 assert!(
1248 result3 == "main_function"
1249 || result3 == "user_code_scope"
1250 || result3 == "user_scope"
1251 || result3.starts_with("function_")
1252 || result3 == "test_function",
1253 "Expected a valid scope name, but got: '{result3}'"
1254 );
1255
1256 let result4 = VariableRegistry::extract_scope_from_var_name("test_variable");
1257 assert!(!result4.is_empty(), "Scope name should not be empty");
1258 assert!(
1259 result4 == "test_function"
1260 || result4 == "user_code_scope"
1261 || result4 == "user_scope"
1262 || result4.starts_with("function_")
1263 || result4 == "main_function",
1264 "Expected a valid scope name, but got: '{result4}'"
1265 );
1266 }
1267
1268 #[test]
1269 fn test_calculate_lifetime_stats() {
1270 let lifetimes = vec![0, 1, 5, 15, 50, 150, 500, 1500];
1271 let stats = VariableRegistry::calculate_lifetime_stats(&lifetimes);
1272
1273 assert_eq!(stats["count"], 8);
1274 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![];
1281 let empty_stats = VariableRegistry::calculate_lifetime_stats(&empty_lifetimes);
1282 assert_eq!(empty_stats["count"], 0);
1283 }
1284
1285 #[test]
1286 fn test_group_by_scope() {
1287 let active_allocations = vec![
1288 serde_json::json!({
1289 "scope_name": "main_function",
1290 "size": 100,
1291 "ptr": 0x1000
1292 }),
1293 serde_json::json!({
1294 "scope_name": "test_function",
1295 "size": 200,
1296 "ptr": 0x2000
1297 }),
1298 ];
1299
1300 let history_allocations = vec![serde_json::json!({
1301 "scope_name": "main_function",
1302 "size": 150,
1303 "ptr": 0x3000
1304 })];
1305
1306 let grouped = VariableRegistry::group_by_scope(&active_allocations, &history_allocations);
1307
1308 assert!(
1309 grouped["main_function"]["allocation_count"]
1310 .as_u64()
1311 .unwrap()
1312 >= 2
1313 );
1314 assert!(
1315 grouped["test_function"]["allocation_count"]
1316 .as_u64()
1317 .unwrap()
1318 >= 1
1319 );
1320 assert!(
1321 grouped["main_function"]["total_size_bytes"]
1322 .as_u64()
1323 .unwrap()
1324 >= 250
1325 );
1326 }
1327
1328 #[test]
1329 fn test_get_scope_summary() {
1330 let mut registry = HashMap::new();
1331 registry.insert(
1332 0x1000,
1333 VariableInfo {
1334 var_name: "main_var".to_string(),
1335 type_name: "i32".to_string(),
1336 timestamp: 1000,
1337 size: 4,
1338 thread_id: 1,
1339 memory_usage: 4,
1340 },
1341 );
1342 registry.insert(
1343 0x2000,
1344 VariableInfo {
1345 var_name: "test_var".to_string(),
1346 type_name: "String".to_string(),
1347 timestamp: 2000,
1348 size: 24,
1349 thread_id: 2,
1350 memory_usage: 24,
1351 },
1352 );
1353
1354 let summary = VariableRegistry::get_scope_summary(®istry);
1355 if let Some(obj) = summary.as_object() {
1357 let total_count: u64 = obj.values().map(|v| v.as_u64().unwrap_or(0)).sum();
1358 assert!(total_count >= 2); let has_main = obj
1362 .get("main_function")
1363 .and_then(|v| v.as_u64())
1364 .unwrap_or(0)
1365 >= 1;
1366 let has_test = obj
1367 .get("test_function")
1368 .and_then(|v| v.as_u64())
1369 .unwrap_or(0)
1370 >= 1;
1371 let has_user_code = obj
1372 .get("user_code_scope")
1373 .and_then(|v| v.as_u64())
1374 .unwrap_or(0)
1375 >= 1;
1376
1377 assert!(has_main || has_test || has_user_code);
1378 } else {
1379 panic!("Expected summary to be a JSON object");
1380 }
1381 }
1382}