Skip to main content

memscope_rs/
variable_registry.rs

1//! Variable Registry - Simple HashMap-based variable name tracking
2//!
3//! This module provides a lightweight alternative to log-based tracking,
4//! using a global HashMap to store variable address -> variable info mappings.
5
6use crate::core::{MemScopeError, MemScopeResult};
7use rayon::prelude::*;
8use std::collections::HashMap;
9use std::sync::{Arc, Mutex, OnceLock};
10
11/// Variable information stored in registry
12#[derive(Debug, Clone, serde::Serialize)]
13pub struct VariableInfo {
14    /// User-defined variable name
15    pub var_name: String,
16    /// Type name of the variable
17    pub type_name: String,
18    /// Timestamp when variable was registered
19    pub timestamp: u64,
20    /// Estimated size of the variable
21    pub size: usize,
22    /// Thread ID that created this variable
23    pub thread_id: usize,
24    /// Memory usage of this variable
25    pub memory_usage: u64,
26}
27
28/// Global variable registry using HashMap for fast lookups
29static GLOBAL_VARIABLE_REGISTRY: OnceLock<Arc<Mutex<HashMap<usize, VariableInfo>>>> =
30    OnceLock::new();
31
32/// Get or initialize the global variable registry
33fn 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
39/// Variable Registry - manages variable address to name mappings
40pub struct VariableRegistry;
41
42mod inference;
43
44impl VariableRegistry {
45    /// Register a variable with its address and information.
46    ///
47    /// # Thread Safety
48    /// This function uses a try-lock on the global registry. If the lock is
49    /// unavailable (high contention), registration is silently skipped.
50    /// In debug builds, a warning is printed to stderr in this case.
51    ///
52    /// # Return Value
53    /// Returns `Ok(())` on success. Note: due to lock-free design,
54    /// registration failures are silently ignored to avoid blocking.
55    pub fn register_variable(
56        address: usize,
57        var_name: String,
58        type_name: String,
59        size: usize,
60    ) -> MemScopeResult<()> {
61        let thread_id = {
62            // Use a simple atomic counter for thread IDs instead of hash
63            static THREAD_COUNTER: std::sync::atomic::AtomicUsize =
64                std::sync::atomic::AtomicUsize::new(1);
65            static THREAD_ID_MAP: std::sync::OnceLock<
66                std::sync::Mutex<std::collections::HashMap<std::thread::ThreadId, usize>>,
67            > = std::sync::OnceLock::new();
68
69            let map = THREAD_ID_MAP
70                .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
71            let current_thread_id = std::thread::current().id();
72
73            if let Ok(mut map) = map.try_lock() {
74                *map.entry(current_thread_id).or_insert_with(|| {
75                    THREAD_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
76                })
77            } else {
78                // Fallback if we can't get the lock
79                1
80            }
81        };
82        let timestamp = std::time::SystemTime::now()
83            .duration_since(std::time::UNIX_EPOCH)
84            .unwrap_or_default()
85            .as_nanos() as u64;
86
87        let var_info = VariableInfo {
88            var_name,
89            type_name,
90            timestamp,
91            size,
92            thread_id,
93            memory_usage: size as u64,
94        };
95
96        // Retry mechanism to prevent data loss under high contention
97        let mut retry_count = 0;
98        const MAX_RETRIES: usize = 5;
99        const INITIAL_BACKOFF_MS: u64 = 1;
100
101        loop {
102            if let Ok(mut registry) = get_global_registry().try_lock() {
103                registry.insert(address, var_info);
104                break;
105            } else {
106                retry_count += 1;
107                if retry_count >= MAX_RETRIES {
108                    tracing::warn!(
109                        "Variable registration for '{}' dropped after {} retries due to lock contention. ptr=0x{:x}, size={}",
110                        var_info.var_name, MAX_RETRIES, address, size
111                    );
112                    break;
113                }
114
115                // Exponential backoff: 1ms, 2ms, 4ms, 8ms, 16ms
116                let backoff_ms = INITIAL_BACKOFF_MS * (1 << (retry_count - 1));
117                std::thread::sleep(std::time::Duration::from_millis(backoff_ms));
118            }
119        }
120
121        Ok(())
122    }
123
124    /// Get variable information by address
125    pub fn get_variable_info(address: usize) -> Option<VariableInfo> {
126        if let Ok(registry) = get_global_registry().try_lock() {
127            registry.get(&address).cloned()
128        } else {
129            None
130        }
131    }
132
133    /// Mark a variable as destroyed with destruction timestamp
134    pub fn mark_variable_destroyed(address: usize, destruction_time: u64) -> MemScopeResult<()> {
135        // For now, we keep the variable in registry but could add destruction_time field
136        // This method ensures the variable registry is aware of destruction events
137        tracing::debug!(
138            "Variable at address 0x{:x} destroyed at {}",
139            address,
140            destruction_time
141        );
142        Ok(())
143    }
144
145    /// Get all variable mappings
146    pub fn get_all_variables() -> HashMap<usize, VariableInfo> {
147        if let Ok(registry) = get_global_registry().try_lock() {
148            registry.clone()
149        } else {
150            HashMap::new()
151        }
152    }
153
154    /// Enhance tracker allocations with variable names from registry (optimized with parallel processing)
155    pub fn enhance_allocations_with_registry(
156        allocations: &[crate::core::types::AllocationInfo],
157    ) -> Vec<serde_json::Value> {
158        // Early return for small datasets
159        if allocations.len() < 100 {
160            return Self::enhance_allocations_sequential(allocations);
161        }
162
163        tracing::info!(
164            "🚀 Processing {} allocations with parallel optimization...",
165            allocations.len()
166        );
167
168        let registry = Self::get_all_variables();
169        let start_time = std::time::Instant::now();
170
171        // Convert to new system allocation types
172        let allocations: Vec<crate::capture::types::AllocationInfo> =
173            allocations.iter().map(|a| a.clone().into()).collect();
174
175        // Use parallel processing for large datasets
176        let enhanced: Vec<serde_json::Value> = allocations
177            .par_iter()
178            .map(|alloc| Self::classify_single_allocation(alloc, &registry))
179            .collect();
180
181        let duration = start_time.elapsed();
182        tracing::info!(
183            "✅ Parallel processing completed in {:?} ({:.2} allocs/ms)",
184            duration,
185            allocations.len() as f64 / duration.as_millis() as f64
186        );
187
188        enhanced
189    }
190
191    /// Sequential processing for small datasets
192    fn enhance_allocations_sequential(
193        allocations: &[crate::core::types::AllocationInfo],
194    ) -> Vec<serde_json::Value> {
195        let registry = Self::get_all_variables();
196
197        // Convert to new system allocation types
198        let allocations: Vec<crate::capture::types::AllocationInfo> =
199            allocations.iter().map(|a| a.clone().into()).collect();
200
201        allocations
202            .iter()
203            .map(|alloc| Self::classify_single_allocation(alloc, &registry))
204            .collect()
205    }
206
207    /// Classify and enhance allocations with user/system distinction and scope information
208    fn classify_and_enhance_allocations(
209        allocations: &[crate::capture::types::allocation::AllocationInfo],
210        registry: &HashMap<usize, VariableInfo>,
211    ) -> Vec<serde_json::Value> {
212        allocations
213            .par_iter()
214            .map(|alloc| Self::classify_single_allocation(alloc, registry))
215            .collect()
216    }
217
218    /// Classify a single allocation as user or system with full context
219    fn classify_single_allocation(
220        alloc: &crate::capture::types::allocation::AllocationInfo,
221        registry: &HashMap<usize, VariableInfo>,
222    ) -> serde_json::Value {
223        // Check if this is a user-tracked variable (highest priority)
224        if let Some(var_info) = registry.get(&alloc.ptr) {
225            return serde_json::json!({
226                "ptr": alloc.ptr,
227                "size": alloc.size,
228                "timestamp_alloc": alloc.timestamp_alloc,
229                "timestamp_dealloc": alloc.timestamp_dealloc,
230                "variable_name": var_info.var_name,
231                "type_name": var_info.type_name,
232                "scope_name": Self::extract_scope_from_var_name(&var_info.var_name),
233                "allocation_source": "user",
234                "tracking_method": "track_var_macro",
235                "registry_timestamp": var_info.timestamp,
236                "registry_size": var_info.size,
237                "lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
238                    (dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
239                ),
240                "current_age_ms": if alloc.timestamp_dealloc.is_none() {
241                    // For active allocations, calculate how long they've been alive
242                    let current_time = std::time::SystemTime::now()
243                        .duration_since(std::time::UNIX_EPOCH)
244                        .unwrap_or_default()
245                        .as_nanos() as u64;
246                    Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
247                } else {
248                    None
249                },
250                "is_active": alloc.timestamp_dealloc.is_none(),
251                "is_leaked": alloc.is_leaked
252            });
253        }
254
255        // Check if allocation has explicit variable information (user allocation)
256        if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
257            return serde_json::json!({
258                "ptr": alloc.ptr,
259                "size": alloc.size,
260                "timestamp_alloc": alloc.timestamp_alloc,
261                "timestamp_dealloc": alloc.timestamp_dealloc,
262                "variable_name": var_name,
263                "type_name": type_name,
264                "scope_name": alloc.scope_name.as_deref().unwrap_or("user_scope"),
265                "allocation_source": "user",
266                "tracking_method": "explicit_tracking",
267                "lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
268                    (dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
269                ),
270                "current_age_ms": if alloc.timestamp_dealloc.is_none() {
271                    let current_time = std::time::SystemTime::now()
272                        .duration_since(std::time::UNIX_EPOCH)
273                        .unwrap_or_default()
274                        .as_nanos() as u64;
275                    Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
276                } else {
277                    None
278                },
279                "is_active": alloc.timestamp_dealloc.is_none(),
280                "is_leaked": alloc.is_leaked
281            });
282        }
283
284        // This is a system allocation - apply smart inference
285        let (inferred_var_name, inferred_type_name) = Self::infer_allocation_info_cached(alloc);
286        let system_category = Self::categorize_system_allocation(alloc);
287
288        serde_json::json!({
289            "ptr": alloc.ptr,
290            "size": alloc.size,
291            "timestamp_alloc": alloc.timestamp_alloc,
292            "timestamp_dealloc": alloc.timestamp_dealloc,
293            "variable_name": inferred_var_name,
294            "type_name": inferred_type_name,
295            "scope_name": "system",
296            "allocation_source": "system",
297            "tracking_method": "automatic_inference",
298            "system_category": system_category,
299            "lifetime_ms": alloc.timestamp_dealloc.map(|dealloc|
300                (dealloc.saturating_sub(alloc.timestamp_alloc)) / 1_000_000
301            ),
302            "current_age_ms": if alloc.timestamp_dealloc.is_none() {
303                let current_time = std::time::SystemTime::now()
304                    .duration_since(std::time::UNIX_EPOCH)
305                    .unwrap_or_default()
306                    .as_nanos() as u64;
307                Some((current_time.saturating_sub(alloc.timestamp_alloc)) / 1_000_000)
308            } else {
309                None
310            },
311            "is_active": alloc.timestamp_dealloc.is_none(),
312            "is_leaked": alloc.is_leaked
313        })
314    }
315
316    /// Extract scope information from variable name and current scope context
317    fn extract_scope_from_var_name(var_name: &str) -> String {
318        // First, try to get the current scope from the scope tracker
319        if let Some(current_scope) = Self::get_current_scope_name() {
320            return current_scope;
321        }
322
323        // Fallback: Try to extract scope from variable name patterns
324        if var_name.contains("::") {
325            if let Some(scope_part) = var_name.split("::").next() {
326                return scope_part.to_string();
327            }
328        }
329
330        // Check for common scope patterns
331        if var_name.starts_with("main_") {
332            "main_function".to_string()
333        } else if var_name.starts_with("test_") {
334            "test_function".to_string()
335        } else if var_name.starts_with("fn_") {
336            "user_function".to_string()
337        } else if var_name.contains("_vec")
338            || var_name.contains("_string")
339            || var_name.contains("_data")
340            || var_name.starts_with("boxed_")
341            || var_name.starts_with("rc_")
342            || var_name.starts_with("arc_")
343        {
344            "user_scope".to_string()
345        } else {
346            // For user variables, default to user_scope instead of global
347            "user_scope".to_string()
348        }
349    }
350
351    /// Get the current scope name from the scope tracker or infer from call stack
352    fn get_current_scope_name() -> Option<String> {
353        // First try to get from scope tracker
354        if let Some(scope_name) = Self::get_scope_from_tracker() {
355            return Some(scope_name);
356        }
357
358        // Fallback: Try to infer scope from call stack
359        Self::infer_scope_from_call_stack()
360    }
361
362    /// Get scope from the scope tracker
363    fn get_scope_from_tracker() -> Option<String> {
364        use crate::core::scope_tracker::get_global_scope_tracker;
365
366        let scope_tracker = get_global_scope_tracker();
367        let thread_id = format!("{:?}", std::thread::current().id());
368
369        if let Some(thread_stack) = scope_tracker
370            .scope_stack
371            .read()
372            .ok()
373            .and_then(|s| s.get(&thread_id).cloned())
374        {
375            if let Some(current_scope_id) = thread_stack.last() {
376                if let Some(scope_info) = scope_tracker
377                    .active_scopes
378                    .read()
379                    .ok()
380                    .and_then(|s| s.get(current_scope_id).cloned())
381                {
382                    return Some(scope_info.name);
383                }
384            }
385        }
386
387        None
388    }
389
390    /// Infer scope from call stack information
391    fn infer_scope_from_call_stack() -> Option<String> {
392        // Try to get function name from backtrace
393        let backtrace = std::backtrace::Backtrace::capture();
394        let backtrace_str = format!("{backtrace:?}");
395
396        // Look for function names in the backtrace
397        for line in backtrace_str.lines() {
398            if line.contains("::main") {
399                return Some("main_function".to_string());
400            }
401            if line.contains("test_") || line.contains("tests::") {
402                return Some("test_function".to_string());
403            }
404            // Look for user-defined function patterns
405            if let Some(func_name) = Self::extract_function_name_from_backtrace(line) {
406                return Some(format!("function_{func_name}"));
407            }
408        }
409
410        // If we can't determine the scope, use a more descriptive default
411        Some("user_code_scope".to_string())
412    }
413
414    /// Extract function name from backtrace line
415    fn extract_function_name_from_backtrace(line: &str) -> Option<String> {
416        // Try to extract function names from common patterns
417        if let Some(start) = line.find("::") {
418            if let Some(end) = line[start + 2..].find("::") {
419                let func_name = &line[start + 2..start + 2 + end];
420                // Filter out common system functions
421                if !func_name.starts_with("_")
422                    && !func_name.contains("alloc")
423                    && !func_name.contains("std")
424                    && !func_name.contains("core")
425                    && func_name.len() > 2
426                {
427                    return Some(func_name.to_string());
428                }
429            }
430        }
431        None
432    }
433
434    /// Categorize system allocations for better understanding
435    fn categorize_system_allocation(
436        alloc: &crate::capture::types::allocation::AllocationInfo,
437    ) -> String {
438        match alloc.size {
439            1..=16 => "small_system_alloc",
440            17..=64 => "medium_system_alloc",
441            65..=1024 => "large_system_alloc",
442            1025..=65536 => "buffer_allocation",
443            _ => "huge_allocation",
444        }
445        .to_string()
446    }
447
448    /// Group allocations by scope for better organization
449    fn group_by_scope(
450        active: &[serde_json::Value],
451        history: &[serde_json::Value],
452    ) -> serde_json::Value {
453        let mut scopes: HashMap<String, Vec<&serde_json::Value>> = HashMap::new();
454
455        // Group active allocations
456        for alloc in active {
457            if let Some(scope) = alloc["scope_name"].as_str() {
458                scopes.entry(scope.to_string()).or_default().push(alloc);
459            }
460        }
461
462        // Group history allocations
463        for alloc in history {
464            if let Some(scope) = alloc["scope_name"].as_str() {
465                scopes.entry(scope.to_string()).or_default().push(alloc);
466            }
467        }
468
469        let scope_summary: HashMap<String, serde_json::Value> = scopes
470            .into_iter()
471            .map(|(scope_name, allocations)| {
472                let total_size: u64 = allocations
473                    .iter()
474                    .map(|a| a["size"].as_u64().unwrap_or(0))
475                    .sum();
476
477                (
478                    scope_name.clone(),
479                    serde_json::json!({
480                        "scope_name": scope_name,
481                        "allocation_count": allocations.len(),
482                        "total_size_bytes": total_size,
483                        "allocations": allocations
484                    }),
485                )
486            })
487            .collect();
488
489        serde_json::json!(scope_summary)
490    }
491
492    /// Get scope summary from registry
493    fn get_scope_summary(registry: &HashMap<usize, VariableInfo>) -> serde_json::Value {
494        let mut scope_counts: HashMap<String, usize> = HashMap::new();
495
496        for var_info in registry.values() {
497            let scope = Self::extract_scope_from_var_name(&var_info.var_name);
498            *scope_counts.entry(scope).or_insert(0) += 1;
499        }
500
501        serde_json::json!(scope_counts)
502    }
503
504    /// Analyze lifecycle statistics for lifetime_ms patterns
505    fn analyze_lifecycle_statistics(
506        user_active: &[serde_json::Value],
507        user_history: &[serde_json::Value],
508        system_active: &[serde_json::Value],
509        system_history: &[serde_json::Value],
510    ) -> serde_json::Value {
511        // Combine all allocations for analysis
512        let all_user: Vec<&serde_json::Value> =
513            user_active.iter().chain(user_history.iter()).collect();
514        let all_system: Vec<&serde_json::Value> =
515            system_active.iter().chain(system_history.iter()).collect();
516
517        // Analyze user allocations - now all should have lifetime_ms values
518        let user_lifetimes: Vec<u64> = all_user
519            .iter()
520            .filter_map(|a| a["lifetime_ms"].as_u64())
521            .collect();
522
523        let user_active_count = all_user
524            .iter()
525            .filter(|a| a["is_active"].as_bool().unwrap_or(false))
526            .count();
527
528        let user_deallocated_count = all_user
529            .iter()
530            .filter(|a| !a["timestamp_dealloc"].is_null())
531            .count();
532
533        // Analyze system allocations - now all should have lifetime_ms values
534        let system_lifetimes: Vec<u64> = all_system
535            .iter()
536            .filter_map(|a| a["lifetime_ms"].as_u64())
537            .collect();
538
539        let system_active_count = all_system
540            .iter()
541            .filter(|a| a["is_active"].as_bool().unwrap_or(false))
542            .count();
543
544        let system_deallocated_count = all_system
545            .iter()
546            .filter(|a| !a["timestamp_dealloc"].is_null())
547            .count();
548
549        // A live allocation is not a leak: "no deallocation timestamp" only says the
550        // block was still present when the snapshot was taken. Leak counts follow the
551        // explicit `is_leaked` flag produced by the leak rules (plan A02).
552        let user_leaked_count = all_user
553            .iter()
554            .filter(|a| a["is_leaked"].as_bool().unwrap_or(false))
555            .count();
556        let system_leaked_count = all_system
557            .iter()
558            .filter(|a| a["is_leaked"].as_bool().unwrap_or(false))
559            .count();
560
561        serde_json::json!({
562            "user_allocations": {
563                "total_count": all_user.len(),
564                "active_count": user_active_count,
565                "deallocated_count": user_deallocated_count,
566                "leaked_count": user_leaked_count,
567                "lifetime_stats": Self::calculate_lifetime_stats(&user_lifetimes),
568                "average_lifetime_ms": if !user_lifetimes.is_empty() {
569                    user_lifetimes.iter().sum::<u64>() / user_lifetimes.len() as u64
570                } else { 0 },
571                "max_lifetime_ms": user_lifetimes.iter().max().copied().unwrap_or(0),
572                "min_lifetime_ms": user_lifetimes.iter().min().copied().unwrap_or(0)
573            },
574            "system_allocations": {
575                "total_count": all_system.len(),
576                "active_count": system_active_count,
577                "deallocated_count": system_deallocated_count,
578                "leaked_count": system_leaked_count,
579                "lifetime_stats": Self::calculate_lifetime_stats(&system_lifetimes),
580                "average_lifetime_ms": if !system_lifetimes.is_empty() {
581                    system_lifetimes.iter().sum::<u64>() / system_lifetimes.len() as u64
582                } else { 0 },
583                "max_lifetime_ms": system_lifetimes.iter().max().copied().unwrap_or(0),
584                "min_lifetime_ms": system_lifetimes.iter().min().copied().unwrap_or(0)
585            },
586            "comparison": {
587                "user_vs_system_active_ratio": if system_active_count > 0 {
588                    user_active_count as f64 / system_active_count as f64
589                } else { 0.0 },
590                "user_vs_system_lifetime_ratio": if !system_lifetimes.is_empty() && !user_lifetimes.is_empty() {
591                    (user_lifetimes.iter().sum::<u64>() / user_lifetimes.len() as u64) as f64 /
592                    (system_lifetimes.iter().sum::<u64>() / system_lifetimes.len() as u64) as f64
593                } else { 0.0 }
594            }
595        })
596    }
597
598    /// Analyze deallocation patterns for timestamp_dealloc
599    fn analyze_deallocation_patterns(
600        user_active: &[serde_json::Value],
601        user_history: &[serde_json::Value],
602        system_active: &[serde_json::Value],
603        system_history: &[serde_json::Value],
604    ) -> serde_json::Value {
605        let all_user: Vec<&serde_json::Value> =
606            user_active.iter().chain(user_history.iter()).collect();
607        let all_system: Vec<&serde_json::Value> =
608            system_active.iter().chain(system_history.iter()).collect();
609
610        // Analyze deallocation timestamps
611        let user_dealloc_times: Vec<u64> = all_user
612            .iter()
613            .filter_map(|a| a["timestamp_dealloc"].as_u64())
614            .collect();
615
616        let system_dealloc_times: Vec<u64> = all_system
617            .iter()
618            .filter_map(|a| a["timestamp_dealloc"].as_u64())
619            .collect();
620
621        // Count null deallocations (active/leaked allocations)
622        let user_null_dealloc = all_user
623            .iter()
624            .filter(|a| a["timestamp_dealloc"].is_null())
625            .count();
626
627        let system_null_dealloc = all_system
628            .iter()
629            .filter(|a| a["timestamp_dealloc"].is_null())
630            .count();
631
632        serde_json::json!({
633            "user_deallocations": {
634                "total_deallocated": user_dealloc_times.len(),
635                "still_active": user_null_dealloc,
636                "deallocation_rate": if !all_user.is_empty() {
637                    user_dealloc_times.len() as f64 / all_user.len() as f64 * 100.0
638                } else { 0.0 },
639                "earliest_dealloc": user_dealloc_times.iter().min().copied(),
640                "latest_dealloc": user_dealloc_times.iter().max().copied(),
641                "deallocation_timespan_ms": if user_dealloc_times.len() > 1 {
642                    user_dealloc_times.iter().max().unwrap_or(&0) -
643                    user_dealloc_times.iter().min().unwrap_or(&0)
644                } else { 0 }
645            },
646            "system_deallocations": {
647                "total_deallocated": system_dealloc_times.len(),
648                "still_active": system_null_dealloc,
649                "deallocation_rate": if !all_system.is_empty() {
650                    system_dealloc_times.len() as f64 / all_system.len() as f64 * 100.0
651                } else { 0.0 },
652                "earliest_dealloc": system_dealloc_times.iter().min().copied(),
653                "latest_dealloc": system_dealloc_times.iter().max().copied(),
654                "deallocation_timespan_ms": if system_dealloc_times.len() > 1 {
655                    system_dealloc_times.iter().max().unwrap_or(&0) -
656                    system_dealloc_times.iter().min().unwrap_or(&0)
657                } else { 0 }
658            },
659            "memory_leak_analysis": {
660                "user_potential_leaks": user_null_dealloc,
661                "system_potential_leaks": system_null_dealloc,
662                "total_potential_leaks": user_null_dealloc + system_null_dealloc,
663                "user_leak_percentage": if !all_user.is_empty() {
664                    user_null_dealloc as f64 / all_user.len() as f64 * 100.0
665                } else { 0.0 },
666                "system_leak_percentage": if !all_system.is_empty() {
667                    system_null_dealloc as f64 / all_system.len() as f64 * 100.0
668                } else { 0.0 }
669            }
670        })
671    }
672
673    /// Calculate detailed lifetime statistics
674    fn calculate_lifetime_stats(lifetimes: &[u64]) -> serde_json::Value {
675        if lifetimes.is_empty() {
676            return serde_json::json!({
677                "count": 0,
678                "categories": {
679                    "very_short": 0,    // < 1ms
680                    "short": 0,         // 1-10ms
681                    "medium": 0,        // 10-100ms
682                    "long": 0,          // 100-1000ms
683                    "very_long": 0      // > 1000ms
684                }
685            });
686        }
687
688        let mut very_short = 0;
689        let mut short = 0;
690        let mut medium = 0;
691        let mut long = 0;
692        let mut very_long = 0;
693
694        for &lifetime in lifetimes {
695            match lifetime {
696                0..=1 => very_short += 1,
697                2..=10 => short += 1,
698                11..=100 => medium += 1,
699                101..=1000 => long += 1,
700                _ => very_long += 1,
701            }
702        }
703
704        serde_json::json!({
705            "count": lifetimes.len(),
706            "categories": {
707                "very_short": very_short,
708                "short": short,
709                "medium": medium,
710                "long": long,
711                "very_long": very_long
712            },
713            "percentiles": {
714                "p50": Self::calculate_percentile(lifetimes, 50.0),
715                "p90": Self::calculate_percentile(lifetimes, 90.0),
716                "p95": Self::calculate_percentile(lifetimes, 95.0),
717                "p99": Self::calculate_percentile(lifetimes, 99.0)
718            }
719        })
720    }
721
722    /// Calculate percentile for lifetime analysis
723    fn calculate_percentile(sorted_values: &[u64], percentile: f64) -> u64 {
724        if sorted_values.is_empty() {
725            return 0;
726        }
727
728        let mut values = sorted_values.to_vec();
729        values.sort_unstable();
730
731        let index = (percentile / 100.0 * (values.len() - 1) as f64) as usize;
732        values[index.min(values.len() - 1)]
733    }
734
735    /// Generate comprehensive export data with clear separation of system vs user allocations
736    pub fn generate_comprehensive_export(
737        tracker: &crate::core::tracker::MemoryTracker,
738    ) -> MemScopeResult<serde_json::Value> {
739        let start_time = std::time::Instant::now();
740        tracing::info!(
741            "🔄 Starting comprehensive export generation with allocation classification..."
742        );
743
744        // Get tracker data
745        let active_allocations = tracker.get_active_allocations().map_err(|e| {
746            MemScopeError::error(
747                "variable_registry",
748                "generate_comprehensive_export",
749                e.to_string(),
750            )
751        })?;
752        let allocation_history = tracker.get_active_allocations().map_err(|e| {
753            MemScopeError::error(
754                "variable_registry",
755                "generate_comprehensive_export",
756                e.to_string(),
757            )
758        })?;
759        let memory_by_type = tracker.get_memory_by_type().map_err(|e| {
760            MemScopeError::error(
761                "variable_registry",
762                "generate_comprehensive_export",
763                e.to_string(),
764            )
765        })?;
766        let stats = tracker.get_stats().map_err(|e| {
767            MemScopeError::error(
768                "variable_registry",
769                "generate_comprehensive_export",
770                e.to_string(),
771            )
772        })?;
773        let registry = Self::get_all_variables();
774
775        tracing::info!(
776            "📊 Data loaded: {} active, {} history, {} registry entries",
777            active_allocations.len(),
778            allocation_history.len(),
779            registry.len()
780        );
781
782        // Filter out very small allocations to reduce processing overhead
783        let filtered_active: Vec<_> = if active_allocations.len() > 10000 {
784            active_allocations
785                .into_iter()
786                .filter(|alloc| alloc.size >= 8)
787                .collect()
788        } else {
789            active_allocations
790        };
791
792        let filtered_history: Vec<_> = if allocation_history.len() > 50000 {
793            allocation_history
794                .into_iter()
795                .filter(|alloc| alloc.size >= 8)
796                .collect()
797        } else {
798            allocation_history
799        };
800
801        // Convert to new system allocation types
802        let filtered_active: Vec<crate::capture::types::AllocationInfo> = filtered_active
803            .into_iter()
804            .map(|a: crate::capture::backends::core_types::AllocationInfo| a.into())
805            .collect();
806        let filtered_history: Vec<crate::capture::types::AllocationInfo> = filtered_history
807            .into_iter()
808            .map(|a: crate::capture::backends::core_types::AllocationInfo| a.into())
809            .collect();
810
811        // Classify and enhance allocations in parallel
812        let (classified_active, classified_history) = rayon::join(
813            || Self::classify_and_enhance_allocations(&filtered_active, &registry),
814            || Self::classify_and_enhance_allocations(&filtered_history, &registry),
815        );
816
817        // Separate user and system allocations
818        let (user_active, system_active): (Vec<_>, Vec<_>) = classified_active
819            .into_iter()
820            .partition(|alloc| alloc["allocation_source"] == "user");
821
822        let (user_history, system_history): (Vec<_>, Vec<_>) = classified_history
823            .into_iter()
824            .partition(|alloc| alloc["allocation_source"] == "user");
825
826        // Group user variables by scope
827        let user_scopes = Self::group_by_scope(&user_active, &user_history);
828
829        // Build comprehensive result with clear separation
830        let comprehensive_data = serde_json::json!({
831            "memory_analysis": {
832                "user_allocations": {
833                    "active": user_active,
834                    "history": user_history,
835                    "by_scope": user_scopes,
836                    "total_count": user_active.len() + user_history.len()
837                },
838                "system_allocations": {
839                    "active": system_active,
840                    "history": system_history,
841                    "total_count": system_active.len() + system_history.len()
842                },
843                "memory_by_type": memory_by_type,
844                "statistics": {
845                    "overall": stats,
846                    "user_vs_system": {
847                        "user_active_count": user_active.len(),
848                        "system_active_count": system_active.len(),
849                        "user_total_size": user_active.iter()
850                            .map(|a| a["size"].as_u64().unwrap_or(0))
851                            .sum::<u64>(),
852                        "system_total_size": system_active.iter()
853                            .map(|a| a["size"].as_u64().unwrap_or(0))
854                            .sum::<u64>()
855                    },
856                    "lifecycle_analysis": Self::analyze_lifecycle_statistics(&user_active, &user_history, &system_active, &system_history),
857                    "deallocation_analysis": Self::analyze_deallocation_patterns(&user_active, &user_history, &system_active, &system_history)
858                }
859            },
860            "variable_registry": {
861                "total_variables": registry.len(),
862                "user_variables": registry.values().collect::<Vec<_>>(),
863                "scope_summary": Self::get_scope_summary(&registry)
864            },
865            "export_metadata": {
866                "timestamp": std::time::SystemTime::now()
867                    .duration_since(std::time::UNIX_EPOCH)
868                    .unwrap_or_default()
869                    .as_secs(),
870                "total_allocations": user_active.len() + user_history.len() + system_active.len() + system_history.len(),
871                "processing_time_ms": start_time.elapsed().as_millis(),
872                "classification_features": [
873                    "user_vs_system_separation",
874                    "scope_based_grouping",
875                    "allocation_source_tracking",
876                    "enhanced_type_inference"
877                ]
878            }
879        });
880
881        let total_time = start_time.elapsed();
882        tracing::info!(
883            "✅ Export completed in {:?} - User: {}, System: {}",
884            total_time,
885            user_active.len() + user_history.len(),
886            system_active.len() + system_history.len()
887        );
888
889        Ok(comprehensive_data)
890    }
891
892    /// Clear all variable registrations
893    pub fn clear_registry() -> MemScopeResult<()> {
894        if let Ok(mut registry) = get_global_registry().try_lock() {
895            registry.clear();
896        }
897        Ok(())
898    }
899
900    /// Get registry statistics
901    pub fn get_stats() -> (usize, usize) {
902        if let Ok(registry) = get_global_registry().try_lock() {
903            let total = registry.len();
904            let recent = registry
905                .values()
906                .filter(|v| {
907                    let now = std::time::SystemTime::now()
908                        .duration_since(std::time::UNIX_EPOCH)
909                        .unwrap_or_default()
910                        .as_nanos() as u64;
911                    now - v.timestamp < 1_000_000_000 // Last 1 second
912                })
913                .count();
914            (total, recent)
915        } else {
916            (0, 0)
917        }
918    }
919}
920
921#[cfg(test)]
922mod tests;