Skip to main content

memscope_rs/capture/backends/
mod.rs

1//! Capture backends for different tracking strategies.
2//!
3//! This module provides the CaptureBackend trait and implementations
4//! for different tracking strategies (core, lockfree, async, unified).
5//!
6//! All files are maintained under 1000 lines per coding standards.
7
8// Core tracking modules
9pub mod core_tracker;
10pub mod core_types;
11pub mod export_options;
12
13// Lockfree tracking modules
14pub mod lockfree_tracker;
15pub mod lockfree_types;
16
17// Async tracking modules
18pub mod async_tracker;
19pub mod async_types;
20
21// Task profile modules (task-level memory profiling)
22pub mod profile_replay;
23pub mod task_profile;
24
25// Efficiency scoring modules
26pub mod efficiency_scoring;
27
28// Bottleneck analysis modules
29pub mod bottleneck_analysis;
30
31// Hotspot analysis modules
32pub mod hotspot_analysis;
33
34// Resource ranking modules
35pub mod resource_ranking;
36
37// Unsafe/FFI tracking modules
38pub mod unsafe_tracking;
39
40// Unified tracking modules
41pub mod unified_tracker;
42
43// Global tracking module (lazy init, CLI-friendly)
44pub mod global_tracking;
45
46use crate::event_store::{MemoryEvent, MemoryEventType};
47
48// Re-export core tracker types
49pub use core_tracker::{
50    collect_all_trackers_local, configure_tracking_strategy, get_registry_stats_local, get_tracker,
51    MemoryTracker,
52};
53
54// Re-export export options
55pub use export_options::{ExportMode, ExportOptions};
56
57// Re-export async tracker types
58pub use async_tracker::{
59    create_tracked, get_memory_snapshot, initialize, is_tracking_active, shutdown, spawn_tracked,
60    track_current_allocation, track_current_deallocation, AsyncTracker,
61};
62pub use async_types::{
63    AsyncAllocation, AsyncError, AsyncMemorySnapshot, AsyncResult, AsyncSnapshot, AsyncStats,
64    ExtendedTaskInfo, TaskId, TaskInfo, TrackedFuture,
65};
66
67// Re-export task profile types
68pub use profile_replay::{
69    finalize_metrics, rebuild_async_profiles_from_events, rebuild_with_live_tracker,
70    RebuiltProfiles,
71};
72pub use task_profile::{AggregatedTaskStats, TaskMemoryProfile, TaskProfileManager, TaskType};
73
74// Re-export efficiency scoring types
75pub use efficiency_scoring::{
76    ComponentScores, EfficiencyConfig, EfficiencyScorer, EfficiencyWeights,
77};
78
79// Re-export bottleneck analysis types
80pub use bottleneck_analysis::{
81    BottleneckAnalyzer, BottleneckConfig, BottleneckKind, BottleneckMetrics, PerformanceIssue,
82    TaskMetrics,
83};
84
85// Re-export hotspot analysis types
86pub use hotspot_analysis::{
87    AllocationFrequencyPattern, CallStackHotspot, FrequencyAnalysis, HotspotAnalyzer,
88    HotspotConfig, HotspotStatistics, MemoryUsagePeak,
89};
90
91// Re-export resource ranking types
92pub use resource_ranking::{
93    EfficiencyScores, RankingConfig, RankingStatistics, ResourceRanking, ResourceRankingAnalyzer,
94    TaskResourceMetrics,
95};
96
97// Re-export unsafe/FFI tracking types
98pub use unsafe_tracking::{
99    AllocationInfo, AllocationOrigin, AllocationSource, MemoryPassport, OwnershipInfo,
100    PassportStamp, SafetyViolation, SecurityClearance, UnsafeTracker, UnsafeTrackingConfig,
101    UnsafeTrackingStats, ValidityStatus, ViolationSeverity,
102};
103
104// Re-export lockfree tracker types
105pub use lockfree_tracker::{
106    finalize_thread_tracker, get_current_tracker, init_thread_tracker, is_tracking,
107    memory_snapshot, quick_trace, stop_tracing, trace_all, trace_thread, track_allocation_lockfree,
108    track_deallocation_lockfree, ThreadLocalTracker,
109};
110pub use lockfree_types::{
111    AllocationCategory, AnalysisSummary, Event, EventType, FrequencyData, FrequencyPattern,
112    InteractionType, LockfreeAnalysis, MemorySnapshot, MemoryStats, SamplingConfig, SystemMetrics,
113    ThreadInteraction, ThreadStats,
114};
115
116// Re-export unified tracker types
117pub use unified_tracker::{
118    detect_environment, get_backend, initialize as initialize_unified, AsyncRuntimeType,
119    BackendConfig, DetectionConfig, DispatcherConfig, DispatcherMetrics, EnvironmentDetection,
120    EnvironmentDetector, MemoryAnalysisData, MemoryStatistics,
121    MemoryTracker as UnifiedMemoryTracker, RuntimeEnvironment, SessionMetadata, TrackerConfig,
122    TrackerStatistics, TrackerType, TrackingDispatcher, TrackingOperation, TrackingSession,
123    TrackingStrategy, UnifiedBackend,
124};
125
126// Re-export global tracking types
127pub use global_tracking::{
128    global_tracker, init_global_tracking, init_global_tracking_with_config, is_initialized,
129    GlobalTracker, GlobalTrackerConfig, GlobalTrackerStats,
130};
131
132/// Capture Backend trait
133///
134/// All capture backends must implement this trait to provide
135/// a unified interface for capturing memory events.
136pub trait CaptureBackend: Send + Sync {
137    /// Capture an allocation event
138    fn capture_alloc(&self, ptr: usize, size: usize, thread_id: u64) -> MemoryEvent;
139
140    /// Capture a deallocation event
141    fn capture_dealloc(&self, ptr: usize, size: usize, thread_id: u64) -> MemoryEvent;
142
143    /// Capture a reallocation event
144    fn capture_realloc(
145        &self,
146        ptr: usize,
147        old_size: usize,
148        new_size: usize,
149        thread_id: u64,
150    ) -> MemoryEvent;
151
152    /// Capture a move event
153    fn capture_move(
154        &self,
155        _from_ptr: usize,
156        to_ptr: usize,
157        size: usize,
158        thread_id: u64,
159    ) -> MemoryEvent;
160}
161
162/// Type of capture backend
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum CaptureBackendType {
165    /// Core tracking backend (original implementation)
166    Core,
167    /// Lockfree tracking backend (lock-free multi-threaded)
168    Lockfree,
169    /// Async tracking backend (async task tracking)
170    Async,
171    /// Unified tracking backend (auto-detects best strategy)
172    Unified,
173}
174
175impl CaptureBackendType {
176    /// Create a capture backend instance
177    pub fn create_backend(&self) -> Box<dyn CaptureBackend> {
178        match self {
179            CaptureBackendType::Core => Box::new(CoreBackend),
180            CaptureBackendType::Lockfree => Box::new(LockfreeBackend),
181            CaptureBackendType::Async => Box::new(AsyncBackend),
182            CaptureBackendType::Unified => Box::new(UnifiedCaptureBackend::new()),
183        }
184    }
185}
186
187/// Core tracking backend
188///
189/// This is the original tracking backend implementation.
190#[derive(Debug)]
191pub struct CoreBackend;
192
193impl CaptureBackend for CoreBackend {
194    fn capture_alloc(&self, ptr: usize, size: usize, thread_id: u64) -> MemoryEvent {
195        MemoryEvent::allocate(ptr, size, thread_id)
196    }
197
198    fn capture_dealloc(&self, ptr: usize, size: usize, thread_id: u64) -> MemoryEvent {
199        MemoryEvent::deallocate(ptr, size, thread_id)
200    }
201
202    fn capture_realloc(
203        &self,
204        ptr: usize,
205        old_size: usize,
206        new_size: usize,
207        thread_id: u64,
208    ) -> MemoryEvent {
209        MemoryEvent::reallocate(ptr, old_size, new_size, thread_id)
210    }
211
212    fn capture_move(
213        &self,
214        _from_ptr: usize,
215        to_ptr: usize,
216        size: usize,
217        thread_id: u64,
218    ) -> MemoryEvent {
219        MemoryEvent {
220            timestamp: MemoryEvent::now(),
221            event_type: MemoryEventType::Move,
222            ptr: to_ptr,
223            size,
224            old_size: None,
225            new_ptr: None,
226            binding_serial: None,
227            degraded_release: false,
228            charge_outcome: None,
229            thread_id,
230            var_name: None,
231            type_name: None,
232            call_stack_hash: None,
233            thread_name: None,
234            source_file: None,
235            source_line: None,
236            module_path: None,
237            clone_source_ptr: None,
238            clone_target_ptr: None,
239            stack_ptr: None,
240            task_id: None,
241        }
242    }
243}
244
245/// Lockfree tracking backend
246///
247/// This backend uses lock-free data structures for high-performance
248/// multi-threaded tracking.
249#[derive(Debug)]
250pub struct LockfreeBackend;
251
252impl CaptureBackend for LockfreeBackend {
253    fn capture_alloc(&self, ptr: usize, size: usize, thread_id: u64) -> MemoryEvent {
254        MemoryEvent::allocate(ptr, size, thread_id).with_call_stack_hash(self.hash_call_stack())
255    }
256
257    fn capture_dealloc(&self, ptr: usize, size: usize, thread_id: u64) -> MemoryEvent {
258        MemoryEvent::deallocate(ptr, size, thread_id).with_call_stack_hash(self.hash_call_stack())
259    }
260
261    fn capture_realloc(
262        &self,
263        ptr: usize,
264        old_size: usize,
265        new_size: usize,
266        thread_id: u64,
267    ) -> MemoryEvent {
268        MemoryEvent::reallocate(ptr, old_size, new_size, thread_id)
269            .with_call_stack_hash(self.hash_call_stack())
270    }
271
272    fn capture_move(
273        &self,
274        _from_ptr: usize,
275        to_ptr: usize,
276        size: usize,
277        thread_id: u64,
278    ) -> MemoryEvent {
279        MemoryEvent {
280            timestamp: MemoryEvent::now(),
281            event_type: MemoryEventType::Move,
282            ptr: to_ptr,
283            size,
284            old_size: None,
285            new_ptr: None,
286            binding_serial: None,
287            degraded_release: false,
288            charge_outcome: None,
289            thread_id,
290            var_name: None,
291            type_name: None,
292            call_stack_hash: Some(self.hash_call_stack()),
293            thread_name: None,
294            source_file: None,
295            source_line: None,
296            module_path: None,
297            clone_source_ptr: None,
298            clone_target_ptr: None,
299            stack_ptr: None,
300            task_id: None,
301        }
302    }
303}
304
305impl LockfreeBackend {
306    /// Generate a hash of the current call context.
307    ///
308    /// Note: This is a lightweight hash based on thread ID and a counter,
309    /// not a full call stack capture. For full call stack tracking,
310    /// enable the `backtrace` feature.
311    #[inline]
312    fn hash_call_stack(&self) -> u64 {
313        use std::collections::hash_map::DefaultHasher;
314        use std::hash::{Hash, Hasher};
315
316        let mut hasher = DefaultHasher::new();
317
318        // Use thread ID for basic grouping
319        std::thread::current().id().hash(&mut hasher);
320
321        // Add a counter for uniqueness within the same thread
322        // Using a thread-local counter would be better but adds overhead
323        static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
324        let count = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
325        count.hash(&mut hasher);
326
327        hasher.finish()
328    }
329}
330
331/// Async tracking backend
332///
333/// This backend is optimized for async task tracking with task ID support.
334#[derive(Debug)]
335pub struct AsyncBackend;
336
337impl CaptureBackend for AsyncBackend {
338    fn capture_alloc(&self, ptr: usize, size: usize, thread_id: u64) -> MemoryEvent {
339        MemoryEvent::allocate(ptr, size, thread_id)
340    }
341
342    fn capture_dealloc(&self, ptr: usize, size: usize, thread_id: u64) -> MemoryEvent {
343        MemoryEvent::deallocate(ptr, size, thread_id)
344    }
345
346    fn capture_realloc(
347        &self,
348        ptr: usize,
349        old_size: usize,
350        new_size: usize,
351        thread_id: u64,
352    ) -> MemoryEvent {
353        MemoryEvent::reallocate(ptr, old_size, new_size, thread_id)
354    }
355
356    fn capture_move(
357        &self,
358        _from_ptr: usize,
359        to_ptr: usize,
360        size: usize,
361        thread_id: u64,
362    ) -> MemoryEvent {
363        MemoryEvent {
364            timestamp: MemoryEvent::now(),
365            event_type: MemoryEventType::Move,
366            ptr: to_ptr,
367            size,
368            old_size: None,
369            new_ptr: None,
370            binding_serial: None,
371            degraded_release: false,
372            charge_outcome: None,
373            thread_id,
374            var_name: None,
375            type_name: None,
376            call_stack_hash: None,
377            thread_name: None,
378            source_file: None,
379            source_line: None,
380            module_path: None,
381            clone_source_ptr: None,
382            clone_target_ptr: None,
383            stack_ptr: None,
384            task_id: None,
385        }
386    }
387}
388
389/// Unified capture backend
390///
391/// This backend automatically detects the best tracking strategy
392/// based on the runtime environment for capturing events.
393pub struct UnifiedCaptureBackend {
394    /// The actual backend being used
395    inner: Box<dyn CaptureBackend>,
396    /// Which backend was selected
397    backend_type: CaptureBackendType,
398}
399
400impl UnifiedCaptureBackend {
401    /// Detect the best capture backend for the current runtime environment.
402    ///
403    /// Selection logic:
404    /// - Single CPU core or unavailable parallelism → CoreBackend (simple, lowest overhead)
405    /// - Multiple CPU cores → LockfreeBackend (concurrent, high throughput)
406    ///
407    /// Note: AsyncBackend detection is not currently implemented.
408    /// The backend selection is made once at creation time and can be
409    /// refreshed using `refresh_backend()` if runtime conditions change.
410    fn detect_best_backend() -> (Box<dyn CaptureBackend>, CaptureBackendType) {
411        let thread_count = std::thread::available_parallelism()
412            .map(|p| p.get())
413            .unwrap_or(1);
414
415        if thread_count <= 1 {
416            (Box::new(CoreBackend), CaptureBackendType::Core)
417        } else {
418            (Box::new(LockfreeBackend), CaptureBackendType::Lockfree)
419        }
420    }
421
422    /// Create a new unified capture backend with auto-detection.
423    pub fn new() -> Self {
424        let (inner, backend_type) = Self::detect_best_backend();
425        Self {
426            inner,
427            backend_type,
428        }
429    }
430
431    /// Get which backend type was selected.
432    pub fn backend_type(&self) -> CaptureBackendType {
433        self.backend_type
434    }
435
436    /// Refresh the backend selection based on current runtime environment.
437    ///
438    /// This allows switching to a more appropriate backend if the
439    /// runtime conditions have changed (e.g., from single-threaded
440    /// to multi-threaded).
441    ///
442    /// Note: This replaces the inner backend with a new instance,
443    /// so any internal state is lost.
444    pub fn refresh_backend(&mut self) {
445        let (new_inner, new_type) = Self::detect_best_backend();
446        self.inner = new_inner;
447        self.backend_type = new_type;
448    }
449}
450
451impl Default for UnifiedCaptureBackend {
452    fn default() -> Self {
453        Self::new()
454    }
455}
456
457impl CaptureBackend for UnifiedCaptureBackend {
458    fn capture_alloc(&self, ptr: usize, size: usize, thread_id: u64) -> MemoryEvent {
459        self.inner.capture_alloc(ptr, size, thread_id)
460    }
461
462    fn capture_dealloc(&self, ptr: usize, size: usize, thread_id: u64) -> MemoryEvent {
463        self.inner.capture_dealloc(ptr, size, thread_id)
464    }
465
466    fn capture_realloc(
467        &self,
468        ptr: usize,
469        old_size: usize,
470        new_size: usize,
471        thread_id: u64,
472    ) -> MemoryEvent {
473        self.inner
474            .capture_realloc(ptr, old_size, new_size, thread_id)
475    }
476
477    fn capture_move(
478        &self,
479        from_ptr: usize,
480        to_ptr: usize,
481        size: usize,
482        thread_id: u64,
483    ) -> MemoryEvent {
484        self.inner.capture_move(from_ptr, to_ptr, size, thread_id)
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::*;
491
492    #[test]
493    fn test_core_backend() {
494        let backend = CoreBackend;
495        let event = backend.capture_alloc(0x1000, 1024, 1);
496        assert_eq!(event.ptr, 0x1000);
497        assert_eq!(event.size, 1024);
498        assert_eq!(event.thread_id, 1);
499        assert!(event.is_allocation());
500    }
501
502    #[test]
503    fn test_lockfree_backend() {
504        let backend = LockfreeBackend;
505        let event = backend.capture_alloc(0x1000, 1024, 1);
506        assert_eq!(event.ptr, 0x1000);
507        assert_eq!(event.size, 1024);
508        assert!(event.call_stack_hash.is_some());
509    }
510
511    #[test]
512    fn test_async_backend() {
513        let backend = AsyncBackend;
514        let event = backend.capture_alloc(0x1000, 1024, 1);
515        assert_eq!(event.ptr, 0x1000);
516        assert_eq!(event.size, 1024);
517    }
518
519    #[test]
520    fn test_unified_backend() {
521        let backend = UnifiedCaptureBackend::default();
522        let event = backend.capture_alloc(0x1000, 1024, 1);
523        assert_eq!(event.ptr, 0x1000);
524        assert_eq!(event.size, 1024);
525    }
526
527    #[test]
528    fn test_backend_type_creation() {
529        let core_backend = CaptureBackendType::Core.create_backend();
530        let lockfree_backend = CaptureBackendType::Lockfree.create_backend();
531        let async_backend = CaptureBackendType::Async.create_backend();
532        let unified_backend = CaptureBackendType::Unified.create_backend();
533
534        // Test that all backends can capture events
535        let event1 = core_backend.capture_alloc(0x1000, 1024, 1);
536        let event2 = lockfree_backend.capture_alloc(0x2000, 2048, 2);
537        let event3 = async_backend.capture_alloc(0x3000, 3072, 3);
538        let event4 = unified_backend.capture_alloc(0x4000, 4096, 4);
539
540        assert_eq!(event1.ptr, 0x1000);
541        assert_eq!(event2.ptr, 0x2000);
542        assert_eq!(event3.ptr, 0x3000);
543        assert_eq!(event4.ptr, 0x4000);
544    }
545}