Skip to main content

memscope_rs/facade/
implementation.rs

1//! MemScope - Unified facade for all engines
2//!
3//! This module provides the MemScope facade which unifies all engines
4//! into a simple, easy-to-use interface.
5
6use crate::analysis::detectors::Detector;
7use crate::analysis_engine::{AnalysisEngine, Analyzer};
8use crate::capture::{CaptureBackendType, CaptureEngine};
9use crate::event_store::EventStore;
10use crate::metadata::MetadataEngine;
11use crate::query::QueryEngine;
12use crate::render_engine::RenderEngine;
13use crate::snapshot::SnapshotEngine;
14use crate::timeline::TimelineEngine;
15use std::sync::{Arc, Mutex};
16
17/// MemScope - Unified facade for all engines
18///
19/// MemScope provides a simple, unified interface for memory tracking,
20/// analysis, and visualization. It integrates all 9 engines into a
21/// single, easy-to-use API.
22///
23/// Key properties:
24/// - Simple: One-stop interface for all functionality
25/// - Powerful: Access to all engines when needed
26/// - Type-safe: Strong typing throughout
27/// - Thread-safe: All operations are thread-safe
28pub struct MemScope {
29    /// Event Store - Centralized event storage
30    pub event_store: Arc<EventStore>,
31    /// Capture Engine - Event capture backend
32    pub capture: Arc<CaptureEngine>,
33    /// Metadata Engine - Centralized metadata management
34    pub metadata: Arc<MetadataEngine>,
35    /// Snapshot Engine - Snapshot construction and aggregation
36    pub snapshot: Arc<SnapshotEngine>,
37    /// Query Engine - Unified query interface
38    pub query: Arc<QueryEngine>,
39    /// Analysis Engine - Memory analysis logic (wrapped in Mutex for interior mutability)
40    pub analysis: Arc<Mutex<AnalysisEngine>>,
41    /// Timeline Engine - Time-based memory analysis
42    pub timeline: Arc<TimelineEngine>,
43    /// Render Engine - Output rendering
44    pub render: Arc<RenderEngine>,
45}
46
47impl MemScope {
48    /// Convert ActiveAllocation to AllocationInfo (helper function)
49    fn to_allocation_info(
50        active: &crate::snapshot::ActiveAllocation,
51    ) -> crate::capture::types::AllocationInfo {
52        let thread_id_u64 = active.thread_id;
53        // Note: Rust's ThreadId type cannot be reconstructed from u64.
54        // We use the current thread's ID as a placeholder.
55        // The actual thread ID value is available in thread_id_u64 field.
56        // This is a known limitation that requires tracking ThreadId at allocation time.
57        let thread_id = std::thread::current().id();
58
59        // For Container/Value types, ptr is None (no real heap allocation).
60        // We use 0 as a sentinel value, which indicates "not a real pointer".
61        // Callers should check TrackKind to determine if this is a real allocation.
62        let ptr = match active.kind {
63            crate::core::types::TrackKind::HeapOwner { ptr, .. } => ptr,
64            crate::core::types::TrackKind::StackOwner { heap_ptr, .. } => heap_ptr,
65            crate::core::types::TrackKind::Container | crate::core::types::TrackKind::Value => 0,
66        };
67
68        crate::capture::types::AllocationInfo {
69            ptr,
70            size: active.size,
71            var_name: active.var_name.clone(),
72            type_name: active.type_name.clone(),
73            scope_name: None,
74            timestamp_alloc: active.allocated_at,
75            timestamp_dealloc: None,
76            thread_id,
77            thread_id_u64,
78            borrow_count: 0,
79            stack_trace: None,
80            is_leaked: false,
81            lifetime_ms: None,
82            module_path: None,
83            borrow_info: None,
84            clone_info: None,
85            ownership_history_available: false,
86            smart_pointer_info: None,
87            memory_layout: None,
88            generic_info: None,
89            dynamic_type_info: None,
90            runtime_state: None,
91            stack_allocation: None,
92            temporary_object: None,
93            fragmentation_analysis: None,
94            generic_instantiation: None,
95            type_relationships: None,
96            type_usage: None,
97            function_call_tracking: None,
98            lifecycle_tracking: None,
99            access_tracking: None,
100            drop_chain_analysis: None,
101            stack_ptr: active.stack_ptr,
102            task_id: None,
103            generation_id: 0,
104        }
105    }
106
107    /// Create a new MemScope instance
108    ///
109    /// This creates all engines with default settings and connects them
110    /// together in the correct configuration.
111    pub fn new() -> Self {
112        // Create EventStore (the foundation)
113        let event_store = Arc::new(EventStore::new());
114
115        // Create Capture Engine
116        let capture = Arc::new(CaptureEngine::new(
117            CaptureBackendType::Unified,
118            event_store.clone(),
119        ));
120
121        // Create Metadata Engine
122        let metadata = Arc::new(MetadataEngine::new());
123
124        // Create Snapshot Engine
125        let snapshot = Arc::new(SnapshotEngine::new(event_store.clone()));
126
127        // Create Query Engine
128        let query = Arc::new(QueryEngine::new(snapshot.clone()));
129
130        // Create Analysis Engine
131        let analysis = Arc::new(Mutex::new(AnalysisEngine::new(snapshot.clone())));
132
133        // Create Timeline Engine
134        let timeline = Arc::new(TimelineEngine::new(event_store.clone()));
135
136        // Create Render Engine
137        let render = Arc::new(RenderEngine::new(snapshot.clone()));
138
139        Self {
140            event_store,
141            capture,
142            metadata,
143            snapshot,
144            query,
145            analysis,
146            timeline,
147            render,
148        }
149    }
150
151    /// Create a new MemScope instance with a specific capture backend
152    ///
153    /// # Arguments
154    /// * `backend_type` - The type of capture backend to use
155    pub fn with_backend(backend_type: CaptureBackendType) -> Self {
156        // Create EventStore (the foundation)
157        let event_store = Arc::new(EventStore::new());
158
159        // Create Capture Engine with specified backend
160        let capture = Arc::new(CaptureEngine::new(backend_type, event_store.clone()));
161
162        // Create Metadata Engine
163        let metadata = Arc::new(MetadataEngine::new());
164
165        // Create Snapshot Engine
166        let snapshot = Arc::new(SnapshotEngine::new(event_store.clone()));
167
168        // Create Query Engine
169        let query = Arc::new(QueryEngine::new(snapshot.clone()));
170
171        // Create Analysis Engine
172        let analysis = Arc::new(Mutex::new(AnalysisEngine::new(snapshot.clone())));
173
174        // Create Timeline Engine
175        let timeline = Arc::new(TimelineEngine::new(event_store.clone()));
176
177        // Create Render Engine
178        let render = Arc::new(RenderEngine::new(snapshot.clone()));
179
180        Self {
181            event_store,
182            capture,
183            metadata,
184            snapshot,
185            query,
186            analysis,
187            timeline,
188            render,
189        }
190    }
191
192    /// Register an analyzer with the analysis engine
193    ///
194    /// # Arguments
195    /// * `analyzer` - The analyzer to register
196    pub fn register_analyzer(&self, analyzer: Box<dyn Analyzer>) {
197        if let Ok(mut analysis) = self.analysis.lock() {
198            analysis.register_analyzer(analyzer);
199            tracing::info!("Analyzer registered successfully");
200        } else {
201            tracing::error!("Failed to acquire analysis engine lock for registration");
202        }
203    }
204
205    /// Get a summary of current memory usage
206    pub fn summary(&self) -> crate::query::QueryResult {
207        self.query.summary()
208    }
209
210    /// Get top allocations by size
211    ///
212    /// # Arguments
213    /// * `limit` - Maximum number of allocations to return
214    pub fn top_allocations(&self, limit: usize) -> crate::query::QueryResult {
215        self.query.top_allocations(limit)
216    }
217
218    /// Render current snapshot to JSON
219    ///
220    /// # Arguments
221    /// * `verbose` - Whether to include verbose output
222    pub fn render_json(&self, verbose: bool) -> Result<crate::render_engine::RenderResult, String> {
223        let snapshot = self.snapshot.build_snapshot();
224        self.render.render_json(&snapshot, verbose)
225    }
226
227    /// Clear all events and reset state
228    pub fn clear(&self) {
229        self.event_store.clear();
230    }
231
232    /// Get the total number of events
233    pub fn event_count(&self) -> usize {
234        self.event_store.len()
235    }
236
237    // ===== Detector Methods =====
238
239    /// Register a detector with the analysis engine
240    ///
241    /// This method automatically wraps the detector in an adapter
242    /// and registers it as an analyzer.
243    ///
244    /// # Arguments
245    /// * `detector` - The detector to register
246    pub fn register_detector<D>(&self, detector: D)
247    where
248        D: crate::analysis::detectors::Detector + Send + Sync + 'static,
249    {
250        use crate::analysis_engine::DetectorToAnalyzer;
251        let adapter = Box::new(DetectorToAnalyzer::new(detector));
252        self.register_analyzer(adapter);
253    }
254
255    /// Run all registered detectors and return the results
256    ///
257    /// # Returns
258    /// A vector of analysis results from all detectors
259    pub fn run_detectors(&self) -> Vec<crate::analysis_engine::analyzer::AnalysisResult> {
260        if let Ok(analysis) = self.analysis.lock() {
261            analysis.analyze()
262        } else {
263            tracing::error!("Failed to acquire analysis engine lock");
264            vec![]
265        }
266    }
267
268    /// Run the leak detector on the current snapshot
269    ///
270    /// # Returns
271    /// Detection results from the leak detector
272    pub fn run_leak_detector(&self) -> crate::analysis::detectors::DetectionResult {
273        use crate::analysis::detectors::{LeakDetector, LeakDetectorConfig};
274        let detector = LeakDetector::new(LeakDetectorConfig::default());
275        let snapshot = self.snapshot.build_snapshot();
276        let allocations: Vec<crate::capture::types::AllocationInfo> = snapshot
277            .active_allocations
278            .values()
279            .map(Self::to_allocation_info)
280            .collect();
281        detector.detect(&allocations)
282    }
283
284    /// Run the UAF (Use-After-Free) detector on the current snapshot
285    ///
286    /// # Returns
287    /// Detection results from the UAF detector
288    pub fn run_uaf_detector(&self) -> crate::analysis::detectors::DetectionResult {
289        use crate::analysis::detectors::{UafDetector, UafDetectorConfig};
290        let detector = UafDetector::new(UafDetectorConfig::default());
291        let snapshot = self.snapshot.build_snapshot();
292        let allocations: Vec<crate::capture::types::AllocationInfo> = snapshot
293            .active_allocations
294            .values()
295            .map(Self::to_allocation_info)
296            .collect();
297        detector.detect(&allocations)
298    }
299
300    /// Run the overflow detector on the current snapshot
301    ///
302    /// # Returns
303    /// Detection results from the overflow detector
304    pub fn run_overflow_detector(&self) -> crate::analysis::detectors::DetectionResult {
305        use crate::analysis::detectors::{OverflowDetector, OverflowDetectorConfig};
306        let detector = OverflowDetector::new(OverflowDetectorConfig::default());
307        let snapshot = self.snapshot.build_snapshot();
308        let allocations: Vec<crate::capture::types::AllocationInfo> = snapshot
309            .active_allocations
310            .values()
311            .map(Self::to_allocation_info)
312            .collect();
313        detector.detect(&allocations)
314    }
315
316    /// Run the safety detector on the current snapshot
317    ///
318    /// # Returns
319    /// Detection results from the safety detector
320    pub fn run_safety_detector(&self) -> crate::analysis::detectors::DetectionResult {
321        use crate::analysis::detectors::{SafetyDetector, SafetyDetectorConfig};
322        let detector = SafetyDetector::new(SafetyDetectorConfig::default());
323        let snapshot = self.snapshot.build_snapshot();
324        let allocations: Vec<crate::capture::types::AllocationInfo> = snapshot
325            .active_allocations
326            .values()
327            .map(Self::to_allocation_info)
328            .collect();
329        detector.detect(&allocations)
330    }
331
332    /// Run the lifecycle detector on the current snapshot
333    ///
334    /// # Returns
335    /// Detection results from the lifecycle detector
336    pub fn run_lifecycle_detector(&self) -> crate::analysis::detectors::DetectionResult {
337        use crate::analysis::detectors::{LifecycleDetector, LifecycleDetectorConfig};
338        let detector = LifecycleDetector::new(LifecycleDetectorConfig::default());
339        let snapshot = self.snapshot.build_snapshot();
340        let allocations: Vec<crate::capture::types::AllocationInfo> = snapshot
341            .active_allocations
342            .values()
343            .map(Self::to_allocation_info)
344            .collect();
345        detector.detect(&allocations)
346    }
347
348    // ===== Export Methods =====
349
350    /// Export the current memory snapshot as an HTML dashboard with a specific template
351    ///
352    /// # Arguments
353    /// * `path` - Directory path where the HTML file will be saved
354    /// * `template` - The dashboard template to use
355    ///
356    /// # Returns
357    /// Result indicating success or failure
358    pub fn export_html_with_template<P: AsRef<std::path::Path>>(
359        &self,
360        path: P,
361        template: crate::render_engine::export::DashboardTemplate,
362    ) -> Result<(), String> {
363        use crate::render_engine::export::export_dashboard_html_with_template;
364        use crate::tracker::Tracker;
365        use std::sync::Arc;
366
367        // Create a new tracker instance for export
368        let tracker = Tracker::new();
369        let passport_tracker =
370            Arc::new(crate::analysis::memory_passport_tracker::get_global_passport_tracker());
371
372        export_dashboard_html_with_template(path, &tracker, &passport_tracker, template, None)
373            .map_err(|e| format!("Failed to export HTML: {}", e))
374    }
375
376    /// Export the current memory snapshot as an HTML dashboard
377    ///
378    /// This method automatically detects program characteristics and selects
379    /// the most appropriate template:
380    /// - Multithread: If the program uses multiple threads
381    /// - Binary: Default template for single-threaded programs
382    ///
383    /// # Arguments
384    /// * `path` - Directory path where the HTML file will be saved
385    ///
386    /// # Returns
387    /// Result indicating success or failure
388    pub fn export_html<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), String> {
389        use crate::render_engine::export::DashboardTemplate;
390
391        let _snapshot = self.snapshot.build_snapshot();
392        self.export_html_with_template(path, DashboardTemplate::Unified)
393    }
394
395    /// Export all JSON files
396    ///
397    /// This method exports 9 JSON files containing comprehensive memory analysis:
398    /// - memory_analysis.json: Complete memory allocation analysis
399    /// - lifetime.json: Ownership and lifetime tracking
400    /// - ownership_graph.json: Ownership graph analysis with cycle detection
401    /// - system_resources.json: System resource monitoring
402    /// - thread_analysis.json: Thread-specific memory stats
403    /// - unsafe_ffi.json: Unsafe FFI boundary tracking
404    /// - memory_passports.json: Memory lifecycle passports
405    /// - leak_detection.json: Potential memory leaks
406    /// - async_analysis.json: Async task analysis
407    ///
408    /// # Arguments
409    /// * `path` - Directory path where JSON files will be saved
410    ///
411    /// # Returns
412    /// Result indicating success or failure
413    pub fn export_json<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), String> {
414        use crate::render_engine::export::export_all_json;
415        use crate::tracker::Tracker;
416        use std::sync::Arc;
417
418        let tracker = Tracker::new();
419        let passport_tracker =
420            Arc::new(crate::analysis::memory_passport_tracker::get_global_passport_tracker());
421        let async_tracker = Arc::new(crate::capture::backends::async_tracker::AsyncTracker::new());
422
423        export_all_json(path, &tracker, &passport_tracker, &async_tracker)
424            .map_err(|e| format!("Failed to export JSON files: {}", e))
425    }
426}
427
428impl Default for MemScope {
429    fn default() -> Self {
430        Self::new()
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    #[test]
439    fn test_memscope_creation() {
440        let memscope = MemScope::new();
441        assert_eq!(memscope.event_count(), 0);
442    }
443
444    #[test]
445    fn test_memscope_default() {
446        let memscope = MemScope::default();
447        assert_eq!(memscope.event_count(), 0);
448    }
449
450    #[test]
451    fn test_memscope_with_backend() {
452        let memscope = MemScope::with_backend(CaptureBackendType::Core);
453        assert_eq!(memscope.event_count(), 0);
454    }
455
456    #[test]
457    fn test_summary() {
458        let memscope = MemScope::new();
459        let result = memscope.summary();
460        match result {
461            crate::query::QueryResult::Summary(_) => (),
462            _ => panic!("Expected summary result"),
463        }
464    }
465
466    #[test]
467    fn test_top_allocations() {
468        let memscope = MemScope::new();
469        let result = memscope.top_allocations(10);
470        match result {
471            crate::query::QueryResult::Allocations(_) => (),
472            _ => panic!("Expected allocations result"),
473        }
474    }
475
476    #[test]
477    fn test_render_json() {
478        let memscope = MemScope::new();
479        let result = memscope.render_json(false);
480        assert!(result.is_ok());
481    }
482
483    #[test]
484    fn test_clear() {
485        let memscope = MemScope::new();
486        // Simulate some events
487        memscope.capture.capture_alloc(0x1000, 1024, 1);
488        assert_eq!(memscope.event_count(), 1);
489
490        memscope.clear();
491        assert_eq!(memscope.event_count(), 0);
492    }
493}