1use crate::core::types::TrackingResult;
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}
23
24static GLOBAL_VARIABLE_REGISTRY: OnceLock<Arc<Mutex<HashMap<usize, VariableInfo>>>> =
26 OnceLock::new();
27
28fn get_global_registry() -> Arc<Mutex<HashMap<usize, VariableInfo>>> {
30 GLOBAL_VARIABLE_REGISTRY
31 .get_or_init(|| Arc::new(Mutex::new(HashMap::new())))
32 .clone()
33}
34
35pub struct VariableRegistry;
37
38impl VariableRegistry {
39 pub fn register_variable(
41 address: usize,
42 var_name: String,
43 type_name: String,
44 size: usize,
45 ) -> TrackingResult<()> {
46 let timestamp = std::time::SystemTime::now()
47 .duration_since(std::time::UNIX_EPOCH)
48 .unwrap_or_default()
49 .as_nanos() as u64;
50
51 let var_info = VariableInfo {
52 var_name,
53 type_name,
54 timestamp,
55 size,
56 };
57
58 if let Ok(mut registry) = get_global_registry().try_lock() {
59 registry.insert(address, var_info);
60 }
61
62 Ok(())
63 }
64
65 pub fn get_variable_info(address: usize) -> Option<VariableInfo> {
67 if let Ok(registry) = get_global_registry().try_lock() {
68 registry.get(&address).cloned()
69 } else {
70 None
71 }
72 }
73
74 pub fn mark_variable_destroyed(address: usize, destruction_time: u64) -> TrackingResult<()> {
76 tracing::debug!(
79 "Variable at address 0x{:x} destroyed at {}",
80 address,
81 destruction_time
82 );
83 Ok(())
84 }
85
86 pub fn get_all_variables() -> HashMap<usize, VariableInfo> {
88 if let Ok(registry) = get_global_registry().try_lock() {
89 registry.clone()
90 } else {
91 HashMap::new()
92 }
93 }
94
95 pub fn enhance_allocations_with_registry(
97 allocations: &[crate::core::types::AllocationInfo],
98 ) -> Vec<serde_json::Value> {
99 if allocations.len() < 100 {
101 return Self::enhance_allocations_sequential(allocations);
102 }
103
104 tracing::info!(
105 "🚀 Processing {} allocations with parallel optimization...",
106 allocations.len()
107 );
108
109 let registry = Self::get_all_variables();
110 let start_time = std::time::Instant::now();
111
112 let enhanced: Vec<serde_json::Value> = allocations
114 .par_iter()
115 .map(|alloc| Self::classify_single_allocation(alloc, ®istry))
116 .collect();
117
118 let duration = start_time.elapsed();
119 tracing::info!(
120 "✅ Parallel processing completed in {:?} ({:.2} allocs/ms)",
121 duration,
122 allocations.len() as f64 / duration.as_millis() as f64
123 );
124
125 enhanced
126 }
127
128 fn enhance_allocations_sequential(
130 allocations: &[crate::core::types::AllocationInfo],
131 ) -> Vec<serde_json::Value> {
132 let registry = Self::get_all_variables();
133
134 allocations
135 .iter()
136 .map(|alloc| Self::classify_single_allocation(alloc, ®istry))
137 .collect()
138 }
139
140 fn classify_and_enhance_allocations(
142 allocations: &[crate::core::types::AllocationInfo],
143 registry: &HashMap<usize, VariableInfo>,
144 ) -> Vec<serde_json::Value> {
145 allocations
146 .par_iter()
147 .map(|alloc| Self::classify_single_allocation(alloc, registry))
148 .collect()
149 }
150
151 fn classify_single_allocation(
153 alloc: &crate::core::types::AllocationInfo,
154 registry: &HashMap<usize, VariableInfo>,
155 ) -> serde_json::Value {
156 if let Some(var_info) = registry.get(&alloc.ptr) {
158 return serde_json::json!({
159 "ptr": alloc.ptr,
160 "size": alloc.size,
161 "timestamp_alloc": alloc.timestamp_alloc,
162 "timestamp_dealloc": alloc.timestamp_dealloc,
163 "variable_name": var_info.var_name,
164 "type_name": var_info.type_name,
165 "scope_name": Self::extract_scope_from_var_name(&var_info.var_name),
166 "allocation_source": "user",
167 "tracking_method": "track_var_macro",
168 "registry_timestamp": var_info.timestamp,
169 "registry_size": var_info.size,
170 "lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
171 (dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
172 ),
173 "current_age_ms": if alloc.timestamp_dealloc.is_none() {
174 let current_time = std::time::SystemTime::now()
176 .duration_since(std::time::UNIX_EPOCH)
177 .unwrap_or_default()
178 .as_nanos() as u64;
179 Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
180 } else {
181 None
182 },
183 "is_active": alloc.timestamp_dealloc.is_none()
184 });
185 }
186
187 if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
189 return serde_json::json!({
190 "ptr": alloc.ptr,
191 "size": alloc.size,
192 "timestamp_alloc": alloc.timestamp_alloc,
193 "timestamp_dealloc": alloc.timestamp_dealloc,
194 "variable_name": var_name,
195 "type_name": type_name,
196 "scope_name": alloc.scope_name.as_deref().unwrap_or("user_scope"),
197 "allocation_source": "user",
198 "tracking_method": "explicit_tracking",
199 "lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
200 (dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
201 ),
202 "current_age_ms": if alloc.timestamp_dealloc.is_none() {
203 let current_time = std::time::SystemTime::now()
204 .duration_since(std::time::UNIX_EPOCH)
205 .unwrap_or_default()
206 .as_nanos() as u64;
207 Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
208 } else {
209 None
210 },
211 "is_active": alloc.timestamp_dealloc.is_none()
212 });
213 }
214
215 let (inferred_var_name, inferred_type_name) = Self::infer_allocation_info_cached(alloc);
217 let system_category = Self::categorize_system_allocation(alloc);
218
219 serde_json::json!({
220 "ptr": alloc.ptr,
221 "size": alloc.size,
222 "timestamp_alloc": alloc.timestamp_alloc,
223 "timestamp_dealloc": alloc.timestamp_dealloc,
224 "variable_name": inferred_var_name,
225 "type_name": inferred_type_name,
226 "scope_name": "system",
227 "allocation_source": "system",
228 "tracking_method": "automatic_inference",
229 "system_category": system_category,
230 "lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
231 (dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
232 ),
233 "current_age_ms": if alloc.timestamp_dealloc.is_none() {
234 let current_time = std::time::SystemTime::now()
235 .duration_since(std::time::UNIX_EPOCH)
236 .unwrap_or_default()
237 .as_nanos() as u64;
238 Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
239 } else {
240 None
241 },
242 "is_active": alloc.timestamp_dealloc.is_none()
243 })
244 }
245
246 fn extract_scope_from_var_name(var_name: &str) -> String {
248 if let Some(current_scope) = Self::get_current_scope_name() {
250 return current_scope;
251 }
252
253 if var_name.contains("::") {
255 if let Some(scope_part) = var_name.split("::").next() {
256 return scope_part.to_string();
257 }
258 }
259
260 if var_name.starts_with("main_") {
262 "main_function".to_string()
263 } else if var_name.starts_with("test_") {
264 "test_function".to_string()
265 } else if var_name.starts_with("fn_") {
266 "user_function".to_string()
267 } else if var_name.contains("_vec")
268 || var_name.contains("_string")
269 || var_name.contains("_data")
270 || var_name.starts_with("boxed_")
271 || var_name.starts_with("rc_")
272 || var_name.starts_with("arc_")
273 {
274 "user_scope".to_string()
275 } else {
276 "user_scope".to_string()
278 }
279 }
280
281 fn get_current_scope_name() -> Option<String> {
283 if let Some(scope_name) = Self::get_scope_from_tracker() {
285 return Some(scope_name);
286 }
287
288 Self::infer_scope_from_call_stack()
290 }
291
292 fn get_scope_from_tracker() -> Option<String> {
294 use crate::core::scope_tracker::get_global_scope_tracker;
295
296 let scope_tracker = get_global_scope_tracker();
297 let thread_id = format!("{:?}", std::thread::current().id());
298
299 if let Some(thread_stack) = scope_tracker.scope_stack.get(&thread_id) {
301 if let Some(¤t_scope_id) = thread_stack.last() {
302 if let Some(scope_info) = scope_tracker.active_scopes.get(¤t_scope_id) {
304 return Some(scope_info.name.clone());
305 }
306 }
307 }
308
309 None
310 }
311
312 fn infer_scope_from_call_stack() -> Option<String> {
314 let backtrace = std::backtrace::Backtrace::capture();
316 let backtrace_str = format!("{backtrace:?}");
317
318 for line in backtrace_str.lines() {
320 if line.contains("::main") {
321 return Some("main_function".to_string());
322 }
323 if line.contains("test_") || line.contains("tests::") {
324 return Some("test_function".to_string());
325 }
326 if let Some(func_name) = Self::extract_function_name_from_backtrace(line) {
328 return Some(format!("function_{func_name}"));
329 }
330 }
331
332 Some("user_code_scope".to_string())
334 }
335
336 fn extract_function_name_from_backtrace(line: &str) -> Option<String> {
338 if let Some(start) = line.find("::") {
340 if let Some(end) = line[start + 2..].find("::") {
341 let func_name = &line[start + 2..start + 2 + end];
342 if !func_name.starts_with("_")
344 && !func_name.contains("alloc")
345 && !func_name.contains("std")
346 && !func_name.contains("core")
347 && func_name.len() > 2
348 {
349 return Some(func_name.to_string());
350 }
351 }
352 }
353 None
354 }
355
356 fn categorize_system_allocation(alloc: &crate::core::types::AllocationInfo) -> String {
358 match alloc.size {
359 1..=16 => "small_system_alloc",
360 17..=64 => "medium_system_alloc",
361 65..=1024 => "large_system_alloc",
362 1025..=65536 => "buffer_allocation",
363 _ => "huge_allocation",
364 }
365 .to_string()
366 }
367
368 fn group_by_scope(
370 active: &[serde_json::Value],
371 history: &[serde_json::Value],
372 ) -> serde_json::Value {
373 let mut scopes: HashMap<String, Vec<&serde_json::Value>> = HashMap::new();
374
375 for alloc in active {
377 if let Some(scope) = alloc["scope_name"].as_str() {
378 scopes.entry(scope.to_string()).or_default().push(alloc);
379 }
380 }
381
382 for alloc in history {
384 if let Some(scope) = alloc["scope_name"].as_str() {
385 scopes.entry(scope.to_string()).or_default().push(alloc);
386 }
387 }
388
389 let scope_summary: HashMap<String, serde_json::Value> = scopes
390 .into_iter()
391 .map(|(scope_name, allocations)| {
392 let total_size: u64 = allocations
393 .iter()
394 .map(|a| a["size"].as_u64().unwrap_or(0))
395 .sum();
396
397 (
398 scope_name.clone(),
399 serde_json::json!({
400 "scope_name": scope_name,
401 "allocation_count": allocations.len(),
402 "total_size_bytes": total_size,
403 "allocations": allocations
404 }),
405 )
406 })
407 .collect();
408
409 serde_json::json!(scope_summary)
410 }
411
412 fn get_scope_summary(registry: &HashMap<usize, VariableInfo>) -> serde_json::Value {
414 let mut scope_counts: HashMap<String, usize> = HashMap::new();
415
416 for var_info in registry.values() {
417 let scope = Self::extract_scope_from_var_name(&var_info.var_name);
418 *scope_counts.entry(scope).or_insert(0) += 1;
419 }
420
421 serde_json::json!(scope_counts)
422 }
423
424 fn analyze_lifecycle_statistics(
426 user_active: &[serde_json::Value],
427 user_history: &[serde_json::Value],
428 system_active: &[serde_json::Value],
429 system_history: &[serde_json::Value],
430 ) -> serde_json::Value {
431 let all_user: Vec<&serde_json::Value> =
433 user_active.iter().chain(user_history.iter()).collect();
434 let all_system: Vec<&serde_json::Value> =
435 system_active.iter().chain(system_history.iter()).collect();
436
437 let user_lifetimes: Vec<u64> = all_user
439 .iter()
440 .filter_map(|a| a["lifetime_ms"].as_u64())
441 .collect();
442
443 let user_active_count = all_user
444 .iter()
445 .filter(|a| a["is_active"].as_bool().unwrap_or(false))
446 .count();
447
448 let user_deallocated_count = all_user
449 .iter()
450 .filter(|a| !a["timestamp_dealloc"].is_null())
451 .count();
452
453 let system_lifetimes: Vec<u64> = all_system
455 .iter()
456 .filter_map(|a| a["lifetime_ms"].as_u64())
457 .collect();
458
459 let system_active_count = all_system
460 .iter()
461 .filter(|a| a["is_active"].as_bool().unwrap_or(false))
462 .count();
463
464 let system_deallocated_count = all_system
465 .iter()
466 .filter(|a| !a["timestamp_dealloc"].is_null())
467 .count();
468
469 serde_json::json!({
470 "user_allocations": {
471 "total_count": all_user.len(),
472 "active_count": user_active_count,
473 "deallocated_count": user_deallocated_count,
474 "leaked_count": user_active_count, "lifetime_stats": Self::calculate_lifetime_stats(&user_lifetimes),
476 "average_lifetime_ms": if !user_lifetimes.is_empty() {
477 user_lifetimes.iter().sum::<u64>() / user_lifetimes.len() as u64
478 } else { 0 },
479 "max_lifetime_ms": user_lifetimes.iter().max().copied().unwrap_or(0),
480 "min_lifetime_ms": user_lifetimes.iter().min().copied().unwrap_or(0)
481 },
482 "system_allocations": {
483 "total_count": all_system.len(),
484 "active_count": system_active_count,
485 "deallocated_count": system_deallocated_count,
486 "leaked_count": system_active_count,
487 "lifetime_stats": Self::calculate_lifetime_stats(&system_lifetimes),
488 "average_lifetime_ms": if !system_lifetimes.is_empty() {
489 system_lifetimes.iter().sum::<u64>() / system_lifetimes.len() as u64
490 } else { 0 },
491 "max_lifetime_ms": system_lifetimes.iter().max().copied().unwrap_or(0),
492 "min_lifetime_ms": system_lifetimes.iter().min().copied().unwrap_or(0)
493 },
494 "comparison": {
495 "user_vs_system_active_ratio": if system_active_count > 0 {
496 user_active_count as f64 / system_active_count as f64
497 } else { 0.0 },
498 "user_vs_system_lifetime_ratio": if !system_lifetimes.is_empty() && !user_lifetimes.is_empty() {
499 (user_lifetimes.iter().sum::<u64>() / user_lifetimes.len() as u64) as f64 /
500 (system_lifetimes.iter().sum::<u64>() / system_lifetimes.len() as u64) as f64
501 } else { 0.0 }
502 }
503 })
504 }
505
506 fn analyze_deallocation_patterns(
508 user_active: &[serde_json::Value],
509 user_history: &[serde_json::Value],
510 system_active: &[serde_json::Value],
511 system_history: &[serde_json::Value],
512 ) -> serde_json::Value {
513 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_dealloc_times: Vec<u64> = all_user
520 .iter()
521 .filter_map(|a| a["timestamp_dealloc"].as_u64())
522 .collect();
523
524 let system_dealloc_times: Vec<u64> = all_system
525 .iter()
526 .filter_map(|a| a["timestamp_dealloc"].as_u64())
527 .collect();
528
529 let user_null_dealloc = all_user
531 .iter()
532 .filter(|a| a["timestamp_dealloc"].is_null())
533 .count();
534
535 let system_null_dealloc = all_system
536 .iter()
537 .filter(|a| a["timestamp_dealloc"].is_null())
538 .count();
539
540 serde_json::json!({
541 "user_deallocations": {
542 "total_deallocated": user_dealloc_times.len(),
543 "still_active": user_null_dealloc,
544 "deallocation_rate": if !all_user.is_empty() {
545 user_dealloc_times.len() as f64 / all_user.len() as f64 * 100.0
546 } else { 0.0 },
547 "earliest_dealloc": user_dealloc_times.iter().min().copied(),
548 "latest_dealloc": user_dealloc_times.iter().max().copied(),
549 "deallocation_timespan_ms": if user_dealloc_times.len() > 1 {
550 user_dealloc_times.iter().max().unwrap_or(&0) -
551 user_dealloc_times.iter().min().unwrap_or(&0)
552 } else { 0 }
553 },
554 "system_deallocations": {
555 "total_deallocated": system_dealloc_times.len(),
556 "still_active": system_null_dealloc,
557 "deallocation_rate": if !all_system.is_empty() {
558 system_dealloc_times.len() as f64 / all_system.len() as f64 * 100.0
559 } else { 0.0 },
560 "earliest_dealloc": system_dealloc_times.iter().min().copied(),
561 "latest_dealloc": system_dealloc_times.iter().max().copied(),
562 "deallocation_timespan_ms": if system_dealloc_times.len() > 1 {
563 system_dealloc_times.iter().max().unwrap_or(&0) -
564 system_dealloc_times.iter().min().unwrap_or(&0)
565 } else { 0 }
566 },
567 "memory_leak_analysis": {
568 "user_potential_leaks": user_null_dealloc,
569 "system_potential_leaks": system_null_dealloc,
570 "total_potential_leaks": user_null_dealloc + system_null_dealloc,
571 "user_leak_percentage": if !all_user.is_empty() {
572 user_null_dealloc as f64 / all_user.len() as f64 * 100.0
573 } else { 0.0 },
574 "system_leak_percentage": if !all_system.is_empty() {
575 system_null_dealloc as f64 / all_system.len() as f64 * 100.0
576 } else { 0.0 }
577 }
578 })
579 }
580
581 fn calculate_lifetime_stats(lifetimes: &[u64]) -> serde_json::Value {
583 if lifetimes.is_empty() {
584 return serde_json::json!({
585 "count": 0,
586 "categories": {
587 "very_short": 0, "short": 0, "medium": 0, "long": 0, "very_long": 0 }
593 });
594 }
595
596 let mut very_short = 0;
597 let mut short = 0;
598 let mut medium = 0;
599 let mut long = 0;
600 let mut very_long = 0;
601
602 for &lifetime in lifetimes {
603 match lifetime {
604 0..=1 => very_short += 1,
605 2..=10 => short += 1,
606 11..=100 => medium += 1,
607 101..=1000 => long += 1,
608 _ => very_long += 1,
609 }
610 }
611
612 serde_json::json!({
613 "count": lifetimes.len(),
614 "categories": {
615 "very_short": very_short,
616 "short": short,
617 "medium": medium,
618 "long": long,
619 "very_long": very_long
620 },
621 "percentiles": {
622 "p50": Self::calculate_percentile(lifetimes, 50.0),
623 "p90": Self::calculate_percentile(lifetimes, 90.0),
624 "p95": Self::calculate_percentile(lifetimes, 95.0),
625 "p99": Self::calculate_percentile(lifetimes, 99.0)
626 }
627 })
628 }
629
630 fn calculate_percentile(sorted_values: &[u64], percentile: f64) -> u64 {
632 if sorted_values.is_empty() {
633 return 0;
634 }
635
636 let mut values = sorted_values.to_vec();
637 values.sort_unstable();
638
639 let index = (percentile / 100.0 * (values.len() - 1) as f64) as usize;
640 values[index.min(values.len() - 1)]
641 }
642
643 pub fn infer_allocation_info_cached(
645 alloc: &crate::core::types::AllocationInfo,
646 ) -> (String, String) {
647 static COMMON_TYPES: &[(usize, &str, &str)] = &[
649 (1, "box_u8", "Box<u8>"),
650 (2, "box_u16", "Box<u16>"),
651 (4, "box_u32", "Box<u32>"),
652 (8, "box_u64", "Box<u64>"),
653 (16, "small_alloc_16b", "SmallAlloc"),
654 (24, "string_alloc", "String"),
655 (32, "string_alloc", "String"),
656 ];
657
658 for &(size, var_prefix, type_name) in COMMON_TYPES {
660 if alloc.size == size {
661 return (
662 format!("{var_prefix}_{:x}", alloc.ptr),
663 type_name.to_string(),
664 );
665 }
666 }
667
668 Self::infer_allocation_info(alloc)
670 }
671
672 pub fn infer_allocation_info(alloc: &crate::core::types::AllocationInfo) -> (String, String) {
674 let size = alloc.size;
675
676 let (var_name, type_name) = match size {
678 8..=32 if size.is_power_of_two() => (
680 format!("string_alloc_{:x}", alloc.ptr),
681 "String".to_string(),
682 ),
683 s if s % 8 == 0 && s >= 16 => {
685 let elements = s / 8;
686 (
687 format!("vec_i64_{elements}elem_{:x}", alloc.ptr),
688 "Vec<i64>".to_string(),
689 )
690 }
691 s if s % 4 == 0 && s >= 8 => {
692 let elements = s / 4;
693 (
694 format!("vec_i32_{elements}elem_{:x}", alloc.ptr),
695 "Vec<i32>".to_string(),
696 )
697 }
698 1 => (format!("box_u8_{:x}", alloc.ptr), "Box<u8>".to_string()),
700 2 => (format!("box_u16_{:x}", alloc.ptr), "Box<u16>".to_string()),
701 4 => (format!("box_u32_{:x}", alloc.ptr), "Box<u32>".to_string()),
702 8 => (format!("box_u64_{:x}", alloc.ptr), "Box<u64>".to_string()),
703 s if s >= 64 && s % 16 == 0 => (
705 format!("hashmap_alloc_{:x}", alloc.ptr),
706 "HashMap<K,V>".to_string(),
707 ),
708 s if s >= 1024 => {
710 let kb = s / 1024;
711 (
712 format!("large_buffer_{}kb_{:x}", kb, alloc.ptr),
713 "LargeBuffer".to_string(),
714 )
715 }
716 s if s <= 16 => (
718 format!("small_alloc_{s}b_{:x}", alloc.ptr),
719 "SmallAlloc".to_string(),
720 ),
721 _ => (
723 format!("system_alloc_{size}b_{:x}", alloc.ptr),
724 "SystemAlloc".to_string(),
725 ),
726 };
727
728 (var_name, type_name)
729 }
730
731 pub fn generate_comprehensive_export(
733 tracker: &crate::core::tracker::MemoryTracker,
734 ) -> TrackingResult<serde_json::Value> {
735 let start_time = std::time::Instant::now();
736 tracing::info!(
737 "🔄 Starting comprehensive export generation with allocation classification..."
738 );
739
740 let (active_allocations, other_data) = rayon::join(
742 || tracker.get_active_allocations(),
743 || {
744 let history = tracker.get_allocation_history();
745 let memory_types = tracker.get_memory_by_type();
746 let stats = tracker.get_stats();
747 let registry = Self::get_all_variables();
748 (history, memory_types, stats, registry)
749 },
750 );
751
752 let active_allocations = active_allocations?;
753 let (allocation_history, memory_by_type, stats, registry) = {
754 let allocation_history = other_data.0?;
755 let memory_by_type = other_data.1?;
756 let stats = other_data.2?;
757 let registry = other_data.3;
758 (allocation_history, memory_by_type, stats, registry)
759 };
760
761 tracing::info!(
762 "📊 Data loaded: {} active, {} history, {} registry entries",
763 active_allocations.len(),
764 allocation_history.len(),
765 registry.len()
766 );
767
768 let filtered_active: Vec<_> = if active_allocations.len() > 10000 {
770 active_allocations
771 .into_iter()
772 .filter(|alloc| alloc.size >= 8)
773 .collect()
774 } else {
775 active_allocations
776 };
777
778 let filtered_history: Vec<_> = if allocation_history.len() > 50000 {
779 allocation_history
780 .into_iter()
781 .filter(|alloc| alloc.size >= 8)
782 .collect()
783 } else {
784 allocation_history
785 };
786
787 let (classified_active, classified_history) = rayon::join(
789 || Self::classify_and_enhance_allocations(&filtered_active, ®istry),
790 || Self::classify_and_enhance_allocations(&filtered_history, ®istry),
791 );
792
793 let (user_active, system_active): (Vec<_>, Vec<_>) = classified_active
795 .into_iter()
796 .partition(|alloc| alloc["allocation_source"] == "user");
797
798 let (user_history, system_history): (Vec<_>, Vec<_>) = classified_history
799 .into_iter()
800 .partition(|alloc| alloc["allocation_source"] == "user");
801
802 let user_scopes = Self::group_by_scope(&user_active, &user_history);
804
805 let comprehensive_data = serde_json::json!({
807 "memory_analysis": {
808 "user_allocations": {
809 "active": user_active,
810 "history": user_history,
811 "by_scope": user_scopes,
812 "total_count": user_active.len() + user_history.len()
813 },
814 "system_allocations": {
815 "active": system_active,
816 "history": system_history,
817 "total_count": system_active.len() + system_history.len()
818 },
819 "memory_by_type": memory_by_type,
820 "statistics": {
821 "overall": stats,
822 "user_vs_system": {
823 "user_active_count": user_active.len(),
824 "system_active_count": system_active.len(),
825 "user_total_size": user_active.iter()
826 .map(|a| a["size"].as_u64().unwrap_or(0))
827 .sum::<u64>(),
828 "system_total_size": system_active.iter()
829 .map(|a| a["size"].as_u64().unwrap_or(0))
830 .sum::<u64>()
831 },
832 "lifecycle_analysis": Self::analyze_lifecycle_statistics(&user_active, &user_history, &system_active, &system_history),
833 "deallocation_analysis": Self::analyze_deallocation_patterns(&user_active, &user_history, &system_active, &system_history)
834 }
835 },
836 "variable_registry": {
837 "total_variables": registry.len(),
838 "user_variables": registry.values().collect::<Vec<_>>(),
839 "scope_summary": Self::get_scope_summary(®istry)
840 },
841 "export_metadata": {
842 "timestamp": std::time::SystemTime::now()
843 .duration_since(std::time::UNIX_EPOCH)
844 .unwrap_or_default()
845 .as_secs(),
846 "total_allocations": user_active.len() + user_history.len() + system_active.len() + system_history.len(),
847 "processing_time_ms": start_time.elapsed().as_millis(),
848 "classification_features": [
849 "user_vs_system_separation",
850 "scope_based_grouping",
851 "allocation_source_tracking",
852 "enhanced_type_inference"
853 ]
854 }
855 });
856
857 let total_time = start_time.elapsed();
858 tracing::info!(
859 "✅ Export completed in {:?} - User: {}, System: {}",
860 total_time,
861 user_active.len() + user_history.len(),
862 system_active.len() + system_history.len()
863 );
864
865 Ok(comprehensive_data)
866 }
867
868 pub fn clear_registry() -> TrackingResult<()> {
870 if let Ok(mut registry) = get_global_registry().try_lock() {
871 registry.clear();
872 }
873 Ok(())
874 }
875
876 pub fn get_stats() -> (usize, usize) {
878 if let Ok(registry) = get_global_registry().try_lock() {
879 let total = registry.len();
880 let recent = registry
881 .values()
882 .filter(|v| {
883 let now = std::time::SystemTime::now()
884 .duration_since(std::time::UNIX_EPOCH)
885 .unwrap_or_default()
886 .as_nanos() as u64;
887 now - v.timestamp < 1_000_000_000 })
889 .count();
890 (total, recent)
891 } else {
892 (0, 0)
893 }
894 }
895}
896
897#[cfg(test)]
898mod tests {
899 use super::*;
900 use crate::core::types::AllocationInfo;
901
902 fn create_test_allocation(
903 ptr: usize,
904 size: usize,
905 var_name: Option<String>,
906 type_name: Option<String>,
907 ) -> AllocationInfo {
908 AllocationInfo {
909 ptr,
910 size,
911 var_name,
912 type_name,
913 scope_name: Some("test_scope".to_string()),
914 timestamp_alloc: 1000000,
915 timestamp_dealloc: Some(2000000),
916 thread_id: "test_thread".to_string(),
917 borrow_count: 0,
918 stack_trace: Some(vec!["test_function".to_string()]),
919 is_leaked: false,
920 lifetime_ms: Some(1000),
921 borrow_info: None,
922 clone_info: None,
923 ownership_history_available: false,
924 smart_pointer_info: None,
925 memory_layout: None,
926 generic_info: None,
927 dynamic_type_info: None,
928 runtime_state: None,
929 stack_allocation: None,
930 temporary_object: None,
931 fragmentation_analysis: None,
932 generic_instantiation: None,
933 type_relationships: None,
934 type_usage: None,
935 function_call_tracking: None,
936 lifecycle_tracking: None,
937 access_tracking: None,
938 drop_chain_analysis: None,
939 }
940 }
941
942 #[test]
943 fn test_variable_registry_register_and_get() {
944 let _ = VariableRegistry::clear_registry();
946
947 let address = 0x10000; let var_name = "test_var".to_string();
949 let type_name = "String".to_string();
950 let size = 24;
951
952 let result =
954 VariableRegistry::register_variable(address, var_name.clone(), type_name.clone(), size);
955 assert!(result.is_ok());
956
957 let var_info = VariableRegistry::get_variable_info(address);
959 assert!(var_info.is_some());
960
961 let info = var_info.unwrap();
962 assert_eq!(info.var_name, var_name);
963 assert_eq!(info.type_name, type_name);
964 assert_eq!(info.size, size);
965 assert!(info.timestamp > 0);
966 }
967
968 #[test]
969 fn test_variable_registry_mark_destroyed() {
970 let address = 0x2000;
971 let destruction_time = 5000000;
972
973 let result = VariableRegistry::mark_variable_destroyed(address, destruction_time);
974 assert!(result.is_ok());
975 }
976
977 #[test]
978 fn test_get_all_variables() {
979 let _ = VariableRegistry::clear_registry();
981
982 let address1 = 0x30000; let address2 = 0x40000;
984
985 let result1 =
986 VariableRegistry::register_variable(address1, "var1".to_string(), "i32".to_string(), 4);
987 let result2 = VariableRegistry::register_variable(
988 address2,
989 "var2".to_string(),
990 "String".to_string(),
991 24,
992 );
993
994 assert!(result1.is_ok());
996 assert!(result2.is_ok());
997
998 let all_vars = VariableRegistry::get_all_variables();
999 assert!(
1002 all_vars.contains_key(&address1) || all_vars.contains_key(&address2) || !all_vars.is_empty(),
1003 "Registry should contain at least one variable or the ones we just added. Registry size: {}",
1004 all_vars.len()
1005 );
1006 }
1007
1008 #[test]
1009 fn test_enhance_allocations_with_registry() {
1010 let explicit_alloc = create_test_allocation(
1018 0x60000,
1019 50,
1020 Some("explicit_var".to_string()),
1021 Some("i64".to_string()),
1022 );
1023
1024 let system_alloc = create_test_allocation(0x70000, 200, None, None);
1026
1027 let allocations = vec![explicit_alloc, system_alloc];
1028 let enhanced = VariableRegistry::enhance_allocations_with_registry(&allocations);
1029
1030 assert_eq!(enhanced.len(), 2);
1031
1032 let user_count = enhanced
1034 .iter()
1035 .filter(|a| a["allocation_source"] == "user")
1036 .count();
1037 let system_count = enhanced
1038 .iter()
1039 .filter(|a| a["allocation_source"] == "system")
1040 .count();
1041
1042 assert_eq!(user_count, 1, "Should have exactly one user allocation");
1043 assert_eq!(system_count, 1, "Should have exactly one system allocation");
1044
1045 let explicit_result = enhanced
1047 .iter()
1048 .find(|a| a["ptr"].as_u64().unwrap() as usize == 0x60000)
1049 .expect("Should find explicit allocation");
1050
1051 assert_eq!(explicit_result["allocation_source"], "user");
1052 assert_eq!(explicit_result["variable_name"], "explicit_var");
1053 assert_eq!(explicit_result["type_name"], "i64");
1054 assert_eq!(explicit_result["tracking_method"], "explicit_tracking");
1055
1056 let system_result = enhanced
1058 .iter()
1059 .find(|a| a["ptr"].as_u64().unwrap() as usize == 0x70000)
1060 .expect("Should find system allocation");
1061
1062 assert_eq!(system_result["allocation_source"], "system");
1063 assert_eq!(system_result["tracking_method"], "automatic_inference");
1064 assert!(!system_result["variable_name"].as_str().unwrap().is_empty());
1066 assert!(!system_result["type_name"].as_str().unwrap().is_empty());
1067
1068 let test_addr = 0x50000;
1071 if VariableRegistry::clear_registry().is_ok()
1072 && VariableRegistry::register_variable(
1073 test_addr,
1074 "tracked_var".to_string(),
1075 "Vec<u8>".to_string(),
1076 100,
1077 )
1078 .is_ok()
1079 {
1080 let registry_alloc = create_test_allocation(test_addr, 100, None, None);
1082 let enhanced_with_registry =
1083 VariableRegistry::enhance_allocations_with_registry(&[registry_alloc]);
1084
1085 if enhanced_with_registry.len() == 1 {
1086 let result = &enhanced_with_registry[0];
1087 if result["allocation_source"] == "user" {
1089 assert_eq!(result["variable_name"], "tracked_var");
1090 assert_eq!(result["type_name"], "Vec<u8>");
1091 assert_eq!(result["tracking_method"], "track_var_macro");
1092 }
1093 }
1096 }
1097 }
1098
1099 #[test]
1100 fn test_extract_scope_from_var_name() {
1101 let _ = VariableRegistry::clear_registry();
1103
1104 let result1 = VariableRegistry::extract_scope_from_var_name("scope::variable");
1107 assert!(!result1.is_empty(), "Scope name should not be empty");
1109 assert!(
1110 result1 == "scope"
1111 || result1 == "user_code_scope"
1112 || result1 == "user_scope"
1113 || result1.starts_with("function_")
1114 || result1 == "main_function"
1115 || result1 == "test_function",
1116 "Expected a valid scope name, but got: '{result1}'"
1117 );
1118
1119 let result2 = VariableRegistry::extract_scope_from_var_name("my_vec");
1120 assert!(!result2.is_empty(), "Scope name should not be empty");
1121 assert!(
1122 result2 == "user_scope"
1123 || result2 == "user_code_scope"
1124 || result2.starts_with("function_")
1125 || result2 == "main_function"
1126 || result2 == "test_function",
1127 "Expected a valid scope name, but got: '{result2}'"
1128 );
1129
1130 let result3 = VariableRegistry::extract_scope_from_var_name("main_variable");
1131 assert!(!result3.is_empty(), "Scope name should not be empty");
1132 assert!(
1133 result3 == "main_function"
1134 || result3 == "user_code_scope"
1135 || result3 == "user_scope"
1136 || result3.starts_with("function_")
1137 || result3 == "test_function",
1138 "Expected a valid scope name, but got: '{result3}'"
1139 );
1140
1141 let result4 = VariableRegistry::extract_scope_from_var_name("test_variable");
1142 assert!(!result4.is_empty(), "Scope name should not be empty");
1143 assert!(
1144 result4 == "test_function"
1145 || result4 == "user_code_scope"
1146 || result4 == "user_scope"
1147 || result4.starts_with("function_")
1148 || result4 == "main_function",
1149 "Expected a valid scope name, but got: '{result4}'"
1150 );
1151 }
1152
1153 #[test]
1154 fn test_categorize_system_allocation() {
1155 let small_alloc = create_test_allocation(0x1000, 8, None, None);
1156 let medium_alloc = create_test_allocation(0x2000, 32, None, None);
1157 let large_alloc = create_test_allocation(0x3000, 512, None, None);
1158 let buffer_alloc = create_test_allocation(0x4000, 4096, None, None);
1159 let huge_alloc = create_test_allocation(0x5000, 100000, None, None);
1160
1161 assert_eq!(
1162 VariableRegistry::categorize_system_allocation(&small_alloc),
1163 "small_system_alloc"
1164 );
1165 assert_eq!(
1166 VariableRegistry::categorize_system_allocation(&medium_alloc),
1167 "medium_system_alloc"
1168 );
1169 assert_eq!(
1170 VariableRegistry::categorize_system_allocation(&large_alloc),
1171 "large_system_alloc"
1172 );
1173 assert_eq!(
1174 VariableRegistry::categorize_system_allocation(&buffer_alloc),
1175 "buffer_allocation"
1176 );
1177 assert_eq!(
1178 VariableRegistry::categorize_system_allocation(&huge_alloc),
1179 "huge_allocation"
1180 );
1181 }
1182
1183 #[test]
1184 fn test_infer_allocation_info() {
1185 let string_alloc = create_test_allocation(0x1000, 8, None, None);
1188 let (var_name, type_name) = VariableRegistry::infer_allocation_info(&string_alloc);
1189 assert!(!var_name.is_empty());
1190 assert_eq!(type_name, "String");
1191 assert!(var_name.starts_with("string_alloc_"));
1192
1193 let vec_alloc = create_test_allocation(0x2000, 24, None, None);
1195 let (var_name, type_name) = VariableRegistry::infer_allocation_info(&vec_alloc);
1196 assert!(!var_name.is_empty());
1197 assert_eq!(type_name, "Vec<i64>");
1198 assert!(var_name.starts_with("vec_i64_"));
1199 assert!(var_name.contains("3elem")); let vec_i32_alloc = create_test_allocation(0x3000, 12, None, None);
1203 let (var_name, type_name) = VariableRegistry::infer_allocation_info(&vec_i32_alloc);
1204 assert!(!var_name.is_empty());
1205 assert_eq!(type_name, "Vec<i32>");
1206 assert!(var_name.starts_with("vec_i32_"));
1207 assert!(var_name.contains("3elem")); let vec_i32_alloc = create_test_allocation(0x3000, 12, None, None);
1211 let (var_name, type_name) = VariableRegistry::infer_allocation_info(&vec_i32_alloc);
1212 assert!(!var_name.is_empty());
1213 assert_eq!(type_name, "Vec<i32>");
1214 assert!(var_name.starts_with("vec_i32_"));
1215 assert!(var_name.contains("3elem")); let large_vec_alloc = create_test_allocation(0x4000, 2048, None, None);
1219 let (var_name, type_name) = VariableRegistry::infer_allocation_info(&large_vec_alloc);
1220 assert!(!var_name.is_empty());
1221 assert_eq!(type_name, "Vec<i64>");
1222 assert!(var_name.starts_with("vec_i64_"));
1223 assert!(var_name.contains("256elem")); let large_alloc = create_test_allocation(0x5000, 1030, None, None);
1227 let (var_name, type_name) = VariableRegistry::infer_allocation_info(&large_alloc);
1228 assert!(!var_name.is_empty());
1229 assert_eq!(type_name, "LargeBuffer");
1230 assert!(var_name.starts_with("large_buffer_"));
1231 assert!(var_name.contains("1kb")); }
1233
1234 #[test]
1235 fn test_infer_allocation_info_cached() {
1236 let common_alloc = create_test_allocation(0x1000, 24, None, None);
1237 let (var_name, type_name) = VariableRegistry::infer_allocation_info_cached(&common_alloc);
1238 assert!(var_name.contains("string_alloc"));
1239 assert_eq!(type_name, "String");
1240
1241 let uncommon_alloc = create_test_allocation(0x2000, 123, None, None);
1242 let (var_name, type_name) = VariableRegistry::infer_allocation_info_cached(&uncommon_alloc);
1243 assert!(var_name.contains("system_alloc"));
1244 assert_eq!(type_name, "SystemAlloc");
1245 }
1246
1247 #[test]
1248 fn test_calculate_percentile() {
1249 let values = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
1250
1251 assert_eq!(VariableRegistry::calculate_percentile(&values, 50.0), 5);
1252 assert_eq!(VariableRegistry::calculate_percentile(&values, 90.0), 9);
1253 assert_eq!(VariableRegistry::calculate_percentile(&values, 100.0), 10);
1254
1255 let empty_values: Vec<u64> = vec![];
1256 assert_eq!(
1257 VariableRegistry::calculate_percentile(&empty_values, 50.0),
1258 0
1259 );
1260 }
1261
1262 #[test]
1263 fn test_calculate_lifetime_stats() {
1264 let lifetimes = vec![0, 1, 5, 15, 50, 150, 500, 1500];
1265 let stats = VariableRegistry::calculate_lifetime_stats(&lifetimes);
1266
1267 assert_eq!(stats["count"], 8);
1268 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![];
1275 let empty_stats = VariableRegistry::calculate_lifetime_stats(&empty_lifetimes);
1276 assert_eq!(empty_stats["count"], 0);
1277 }
1278
1279 #[test]
1280 fn test_get_stats() {
1281 let _ = VariableRegistry::clear_registry();
1283
1284 let (total_before, recent_before) = VariableRegistry::get_stats();
1285
1286 let result1 = VariableRegistry::register_variable(
1288 0x8000,
1289 "stat_var1".to_string(),
1290 "i32".to_string(),
1291 4,
1292 );
1293 let result2 = VariableRegistry::register_variable(
1294 0x9000,
1295 "stat_var2".to_string(),
1296 "String".to_string(),
1297 24,
1298 );
1299
1300 assert!(result1.is_ok());
1302 assert!(result2.is_ok());
1303
1304 let (total_after, recent_after) = VariableRegistry::get_stats();
1305 assert!(
1307 total_after >= total_before,
1308 "Total should not decrease: before={}, after={}",
1309 total_before,
1310 total_after
1311 );
1312 assert!(
1313 recent_after >= recent_before,
1314 "Recent should not decrease: before={}, after={}",
1315 recent_before,
1316 recent_after
1317 );
1318 }
1319
1320 #[test]
1321 fn test_clear_registry() {
1322 let _ = VariableRegistry::register_variable(
1324 0xa000,
1325 "clear_test1".to_string(),
1326 "i32".to_string(),
1327 4,
1328 );
1329 let _ = VariableRegistry::register_variable(
1330 0xb000,
1331 "clear_test2".to_string(),
1332 "String".to_string(),
1333 24,
1334 );
1335
1336 let result = VariableRegistry::clear_registry();
1338 assert!(result.is_ok());
1339
1340 let var_info1 = VariableRegistry::get_variable_info(0xa000);
1342 let var_info2 = VariableRegistry::get_variable_info(0xb000);
1343
1344 assert!(
1347 var_info1.is_none()
1348 || var_info2.is_none()
1349 || VariableRegistry::get_all_variables().is_empty()
1350 );
1351 }
1352
1353 #[test]
1354 fn test_sequential_vs_parallel_processing() {
1355 let _ = VariableRegistry::clear_registry();
1357
1358 let small_allocations = vec![
1360 create_test_allocation(0x1000, 100, None, None),
1361 create_test_allocation(0x2000, 200, None, None),
1362 ];
1363
1364 let enhanced_small =
1365 VariableRegistry::enhance_allocations_with_registry(&small_allocations);
1366 assert_eq!(enhanced_small.len(), 2);
1367
1368 let large_allocations: Vec<_> = (0..150)
1370 .map(|i| create_test_allocation(0x10000 + i, 100, None, None))
1371 .collect();
1372
1373 let enhanced_large =
1374 VariableRegistry::enhance_allocations_with_registry(&large_allocations);
1375 assert_eq!(enhanced_large.len(), 150);
1376 }
1377
1378 #[test]
1379 fn test_extract_function_name_from_backtrace() {
1380 let backtrace_line = " 10: my_crate::my_module::my_function::h1234567890abcdef";
1381 let func_name = VariableRegistry::extract_function_name_from_backtrace(backtrace_line);
1382 assert_eq!(func_name, Some("my_module".to_string()));
1383
1384 let system_line = " 10: std::alloc::alloc::h1234567890abcdef";
1385 let system_func = VariableRegistry::extract_function_name_from_backtrace(system_line);
1386 assert!(system_func.is_none());
1387
1388 let invalid_line = "invalid backtrace line";
1389 let invalid_func = VariableRegistry::extract_function_name_from_backtrace(invalid_line);
1390 assert!(invalid_func.is_none());
1391 }
1392
1393 #[test]
1394 fn test_group_by_scope() {
1395 let active_allocations = vec![
1396 serde_json::json!({
1397 "scope_name": "main_function",
1398 "size": 100,
1399 "ptr": 0x1000
1400 }),
1401 serde_json::json!({
1402 "scope_name": "test_function",
1403 "size": 200,
1404 "ptr": 0x2000
1405 }),
1406 ];
1407
1408 let history_allocations = vec![serde_json::json!({
1409 "scope_name": "main_function",
1410 "size": 150,
1411 "ptr": 0x3000
1412 })];
1413
1414 let grouped = VariableRegistry::group_by_scope(&active_allocations, &history_allocations);
1415
1416 assert!(
1417 grouped["main_function"]["allocation_count"]
1418 .as_u64()
1419 .unwrap()
1420 >= 2
1421 );
1422 assert!(
1423 grouped["test_function"]["allocation_count"]
1424 .as_u64()
1425 .unwrap()
1426 >= 1
1427 );
1428 assert!(
1429 grouped["main_function"]["total_size_bytes"]
1430 .as_u64()
1431 .unwrap()
1432 >= 250
1433 );
1434 }
1435
1436 #[test]
1437 fn test_get_scope_summary() {
1438 let mut registry = HashMap::new();
1439 registry.insert(
1440 0x1000,
1441 VariableInfo {
1442 var_name: "main_var".to_string(),
1443 type_name: "i32".to_string(),
1444 timestamp: 1000,
1445 size: 4,
1446 },
1447 );
1448 registry.insert(
1449 0x2000,
1450 VariableInfo {
1451 var_name: "test_var".to_string(),
1452 type_name: "String".to_string(),
1453 timestamp: 2000,
1454 size: 24,
1455 },
1456 );
1457
1458 let summary = VariableRegistry::get_scope_summary(®istry);
1459 if let Some(obj) = summary.as_object() {
1461 let total_count: u64 = obj.values().map(|v| v.as_u64().unwrap_or(0)).sum();
1462 assert!(total_count >= 2); let has_main = obj
1466 .get("main_function")
1467 .and_then(|v| v.as_u64())
1468 .unwrap_or(0)
1469 >= 1;
1470 let has_test = obj
1471 .get("test_function")
1472 .and_then(|v| v.as_u64())
1473 .unwrap_or(0)
1474 >= 1;
1475 let has_user_code = obj
1476 .get("user_code_scope")
1477 .and_then(|v| v.as_u64())
1478 .unwrap_or(0)
1479 >= 1;
1480
1481 assert!(has_main || has_test || has_user_code);
1482 } else {
1483 panic!("Expected summary to be a JSON object");
1484 }
1485 }
1486}