prax_query/profiling/
mod.rs

1//! Memory profiling and leak detection utilities.
2//!
3//! This module provides comprehensive memory profiling tools for detecting
4//! memory leaks, tracking allocations, and analyzing memory usage patterns.
5//!
6//! # Features
7//!
8//! - **Allocation Tracking**: Track every allocation and deallocation
9//! - **Leak Detection**: Identify memory that wasn't freed
10//! - **Memory Snapshots**: Capture and compare memory state
11//! - **Heap Profiling**: Integration with DHAT for detailed heap analysis
12//! - **Pool Monitoring**: Track string/buffer pool usage
13//!
14//! # Quick Start
15//!
16//! ```rust,ignore
17//! use prax_query::profiling::{MemoryProfiler, AllocationTracker};
18//!
19//! // Start profiling
20//! let profiler = MemoryProfiler::new();
21//!
22//! // Take initial snapshot
23//! let before = profiler.snapshot();
24//!
25//! // ... do some work ...
26//!
27//! // Take final snapshot
28//! let after = profiler.snapshot();
29//!
30//! // Analyze difference
31//! let diff = after.diff(&before);
32//! if diff.has_leaks() {
33//!     eprintln!("Potential memory leak detected!");
34//!     eprintln!("{}", diff.report());
35//! }
36//! ```
37//!
38//! # Enabling Profiling
39//!
40//! Add the `profiling` feature to enable runtime profiling:
41//! ```toml
42//! [dependencies]
43//! prax-query = { version = "0.3", features = ["profiling"] }
44//! ```
45//!
46//! For DHAT heap profiling (slower but more detailed):
47//! ```toml
48//! [dependencies]
49//! prax-query = { version = "0.3", features = ["dhat-heap"] }
50//! ```
51
52pub mod allocation;
53pub mod heap;
54pub mod leak_detector;
55pub mod snapshot;
56
57pub use allocation::{
58    AllocationRecord, AllocationTracker, AllocationStats, TrackedAllocator,
59    GLOBAL_TRACKER,
60};
61pub use heap::{HeapProfiler, HeapStats, HeapReport};
62pub use leak_detector::{LeakDetector, LeakReport, PotentialLeak, LeakSeverity};
63pub use snapshot::{MemorySnapshot, SnapshotDiff, PoolSnapshot};
64
65use std::sync::atomic::{AtomicBool, Ordering};
66
67/// Global flag to enable/disable profiling at runtime.
68static PROFILING_ENABLED: AtomicBool = AtomicBool::new(false);
69
70/// Enable memory profiling globally.
71pub fn enable_profiling() {
72    PROFILING_ENABLED.store(true, Ordering::SeqCst);
73    tracing::info!("Memory profiling enabled");
74}
75
76/// Disable memory profiling globally.
77pub fn disable_profiling() {
78    PROFILING_ENABLED.store(false, Ordering::SeqCst);
79    tracing::info!("Memory profiling disabled");
80}
81
82/// Check if profiling is enabled.
83#[inline]
84pub fn is_profiling_enabled() -> bool {
85    PROFILING_ENABLED.load(Ordering::Relaxed)
86}
87
88/// Run a closure with profiling enabled, returning the result and a memory report.
89pub fn with_profiling<F, R>(f: F) -> (R, LeakReport)
90where
91    F: FnOnce() -> R,
92{
93    let detector = LeakDetector::new();
94    let _guard = detector.start();
95
96    let result = f();
97
98    let report = detector.finish();
99    (result, report)
100}
101
102/// Async version of `with_profiling`.
103pub async fn with_profiling_async<F, Fut, R>(f: F) -> (R, LeakReport)
104where
105    F: FnOnce() -> Fut,
106    Fut: std::future::Future<Output = R>,
107{
108    let detector = LeakDetector::new();
109    let _guard = detector.start();
110
111    let result = f().await;
112
113    let report = detector.finish();
114    (result, report)
115}
116
117/// Memory profiler combining all profiling capabilities.
118pub struct MemoryProfiler {
119    tracker: AllocationTracker,
120    heap_profiler: HeapProfiler,
121    leak_detector: LeakDetector,
122}
123
124impl MemoryProfiler {
125    /// Create a new memory profiler.
126    pub fn new() -> Self {
127        Self {
128            tracker: AllocationTracker::new(),
129            heap_profiler: HeapProfiler::new(),
130            leak_detector: LeakDetector::new(),
131        }
132    }
133
134    /// Take a memory snapshot.
135    pub fn snapshot(&self) -> MemorySnapshot {
136        MemorySnapshot::capture(&self.tracker)
137    }
138
139    /// Get current allocation statistics.
140    pub fn stats(&self) -> AllocationStats {
141        self.tracker.stats()
142    }
143
144    /// Get heap statistics.
145    pub fn heap_stats(&self) -> HeapStats {
146        self.heap_profiler.stats()
147    }
148
149    /// Run leak detection and generate a report.
150    pub fn detect_leaks(&self) -> LeakReport {
151        self.leak_detector.analyze(&self.tracker)
152    }
153
154    /// Generate a comprehensive memory report.
155    pub fn report(&self) -> MemoryReport {
156        MemoryReport {
157            allocation_stats: self.stats(),
158            heap_stats: self.heap_stats(),
159            leak_report: self.detect_leaks(),
160            pool_stats: self.pool_stats(),
161        }
162    }
163
164    /// Get pool statistics (string pool, buffer pool, etc.).
165    pub fn pool_stats(&self) -> PoolStats {
166        use crate::memory::{GLOBAL_STRING_POOL, GLOBAL_BUFFER_POOL};
167
168        PoolStats {
169            string_pool: GLOBAL_STRING_POOL.stats(),
170            buffer_pool_available: GLOBAL_BUFFER_POOL.available(),
171        }
172    }
173
174    /// Reset all tracking state.
175    pub fn reset(&self) {
176        self.tracker.reset();
177    }
178}
179
180impl Default for MemoryProfiler {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186/// Comprehensive memory report.
187#[derive(Debug)]
188pub struct MemoryReport {
189    /// Allocation statistics.
190    pub allocation_stats: AllocationStats,
191    /// Heap statistics.
192    pub heap_stats: HeapStats,
193    /// Leak detection report.
194    pub leak_report: LeakReport,
195    /// Pool statistics.
196    pub pool_stats: PoolStats,
197}
198
199impl MemoryReport {
200    /// Check if the report indicates potential issues.
201    pub fn has_issues(&self) -> bool {
202        self.leak_report.has_leaks()
203            || self.allocation_stats.net_allocations() > 1000
204            || self.heap_stats.fragmentation_ratio() > 0.3
205    }
206
207    /// Generate a human-readable summary.
208    pub fn summary(&self) -> String {
209        let mut s = String::new();
210        s.push_str("=== Memory Profile Report ===\n\n");
211
212        s.push_str("Allocations:\n");
213        s.push_str(&format!(
214            "  Total: {} ({} bytes)\n",
215            self.allocation_stats.total_allocations,
216            self.allocation_stats.total_bytes_allocated
217        ));
218        s.push_str(&format!(
219            "  Current: {} ({} bytes)\n",
220            self.allocation_stats.current_allocations,
221            self.allocation_stats.current_bytes
222        ));
223        s.push_str(&format!(
224            "  Peak: {} bytes\n",
225            self.allocation_stats.peak_bytes
226        ));
227
228        s.push_str("\nHeap:\n");
229        s.push_str(&format!(
230            "  Used: {} bytes\n",
231            self.heap_stats.used_bytes
232        ));
233        s.push_str(&format!(
234            "  RSS: {} bytes\n",
235            self.heap_stats.rss_bytes
236        ));
237        s.push_str(&format!(
238            "  Fragmentation: {:.1}%\n",
239            self.heap_stats.fragmentation_ratio() * 100.0
240        ));
241
242        s.push_str("\nPools:\n");
243        s.push_str(&format!(
244            "  String pool: {} strings ({} bytes)\n",
245            self.pool_stats.string_pool.count,
246            self.pool_stats.string_pool.total_bytes
247        ));
248        s.push_str(&format!(
249            "  Buffer pool: {} available\n",
250            self.pool_stats.buffer_pool_available
251        ));
252
253        if self.leak_report.has_leaks() {
254            s.push_str("\n⚠️  POTENTIAL LEAKS DETECTED:\n");
255            s.push_str(&self.leak_report.summary());
256        } else {
257            s.push_str("\n✅ No memory leaks detected\n");
258        }
259
260        s
261    }
262}
263
264impl std::fmt::Display for MemoryReport {
265    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266        write!(f, "{}", self.summary())
267    }
268}
269
270/// Pool statistics.
271#[derive(Debug, Clone)]
272pub struct PoolStats {
273    /// String pool statistics.
274    pub string_pool: crate::memory::PoolStats,
275    /// Number of available buffers in buffer pool.
276    pub buffer_pool_available: usize,
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn test_profiling_toggle() {
285        disable_profiling();
286        assert!(!is_profiling_enabled());
287
288        enable_profiling();
289        assert!(is_profiling_enabled());
290
291        disable_profiling();
292        assert!(!is_profiling_enabled());
293    }
294
295    #[test]
296    fn test_memory_profiler() {
297        let profiler = MemoryProfiler::new();
298
299        // Take snapshot
300        let snapshot = profiler.snapshot();
301        assert!(snapshot.timestamp > 0);
302
303        // Get stats
304        let stats = profiler.stats();
305        assert!(stats.total_allocations >= 0);
306
307        // Generate report
308        let report = profiler.report();
309        assert!(!report.summary().is_empty());
310    }
311
312    #[test]
313    fn test_with_profiling() {
314        let (result, report) = with_profiling(|| {
315            // Allocate some memory
316            let v: Vec<i32> = (0..100).collect();
317            v.len()
318        });
319
320        assert_eq!(result, 100);
321        // Report should be valid
322        assert!(report.potential_leaks.is_empty() || !report.potential_leaks.is_empty());
323    }
324}
325