Skip to main content

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, AllocationStats, AllocationTracker, GLOBAL_TRACKER, TrackedAllocator,
59};
60pub use heap::{HeapProfiler, HeapReport, HeapStats};
61pub use leak_detector::{LeakDetector, LeakReport, LeakSeverity, PotentialLeak};
62pub use snapshot::{MemorySnapshot, PoolSnapshot, SnapshotDiff};
63
64use std::sync::atomic::{AtomicBool, Ordering};
65
66/// Global flag to enable/disable profiling at runtime.
67static PROFILING_ENABLED: AtomicBool = AtomicBool::new(false);
68
69/// Enable memory profiling globally.
70pub fn enable_profiling() {
71    PROFILING_ENABLED.store(true, Ordering::SeqCst);
72    tracing::info!("Memory profiling enabled");
73}
74
75/// Disable memory profiling globally.
76pub fn disable_profiling() {
77    PROFILING_ENABLED.store(false, Ordering::SeqCst);
78    tracing::info!("Memory profiling disabled");
79}
80
81/// Check if profiling is enabled.
82#[inline]
83pub fn is_profiling_enabled() -> bool {
84    PROFILING_ENABLED.load(Ordering::Relaxed)
85}
86
87/// Run a closure with profiling enabled, returning the result and a memory report.
88pub fn with_profiling<F, R>(f: F) -> (R, LeakReport)
89where
90    F: FnOnce() -> R,
91{
92    let detector = LeakDetector::new();
93    let _guard = detector.start();
94
95    let result = f();
96
97    let report = detector.finish();
98    (result, report)
99}
100
101/// Async version of `with_profiling`.
102pub async fn with_profiling_async<F, Fut, R>(f: F) -> (R, LeakReport)
103where
104    F: FnOnce() -> Fut,
105    Fut: std::future::Future<Output = R>,
106{
107    let detector = LeakDetector::new();
108    let _guard = detector.start();
109
110    let result = f().await;
111
112    let report = detector.finish();
113    (result, report)
114}
115
116/// Memory profiler combining all profiling capabilities.
117pub struct MemoryProfiler {
118    tracker: AllocationTracker,
119    heap_profiler: HeapProfiler,
120    leak_detector: LeakDetector,
121}
122
123impl MemoryProfiler {
124    /// Create a new memory profiler.
125    pub fn new() -> Self {
126        Self {
127            tracker: AllocationTracker::new(),
128            heap_profiler: HeapProfiler::new(),
129            leak_detector: LeakDetector::new(),
130        }
131    }
132
133    /// Take a memory snapshot.
134    pub fn snapshot(&self) -> MemorySnapshot {
135        MemorySnapshot::capture(&self.tracker)
136    }
137
138    /// Get current allocation statistics.
139    pub fn stats(&self) -> AllocationStats {
140        self.tracker.stats()
141    }
142
143    /// Get heap statistics.
144    pub fn heap_stats(&self) -> HeapStats {
145        self.heap_profiler.stats()
146    }
147
148    /// Run leak detection and generate a report.
149    pub fn detect_leaks(&self) -> LeakReport {
150        self.leak_detector.analyze(&self.tracker)
151    }
152
153    /// Generate a comprehensive memory report.
154    pub fn report(&self) -> MemoryReport {
155        MemoryReport {
156            allocation_stats: self.stats(),
157            heap_stats: self.heap_stats(),
158            leak_report: self.detect_leaks(),
159            pool_stats: self.pool_stats(),
160        }
161    }
162
163    /// Get pool statistics (string pool, buffer pool, etc.).
164    pub fn pool_stats(&self) -> PoolStats {
165        use crate::memory::{GLOBAL_BUFFER_POOL, GLOBAL_STRING_POOL};
166
167        PoolStats {
168            string_pool: GLOBAL_STRING_POOL.stats(),
169            buffer_pool_available: GLOBAL_BUFFER_POOL.available(),
170        }
171    }
172
173    /// Reset all tracking state.
174    pub fn reset(&self) {
175        self.tracker.reset();
176    }
177}
178
179impl Default for MemoryProfiler {
180    fn default() -> Self {
181        Self::new()
182    }
183}
184
185/// Comprehensive memory report.
186#[derive(Debug)]
187pub struct MemoryReport {
188    /// Allocation statistics.
189    pub allocation_stats: AllocationStats,
190    /// Heap statistics.
191    pub heap_stats: HeapStats,
192    /// Leak detection report.
193    pub leak_report: LeakReport,
194    /// Pool statistics.
195    pub pool_stats: PoolStats,
196}
197
198impl MemoryReport {
199    /// Check if the report indicates potential issues.
200    pub fn has_issues(&self) -> bool {
201        self.leak_report.has_leaks()
202            || self.allocation_stats.net_allocations() > 1000
203            || self.heap_stats.fragmentation_ratio() > 0.3
204    }
205
206    /// Generate a human-readable summary.
207    pub fn summary(&self) -> String {
208        let mut s = String::new();
209        s.push_str("=== Memory Profile Report ===\n\n");
210
211        s.push_str("Allocations:\n");
212        s.push_str(&format!(
213            "  Total: {} ({} bytes)\n",
214            self.allocation_stats.total_allocations, self.allocation_stats.total_bytes_allocated
215        ));
216        s.push_str(&format!(
217            "  Current: {} ({} bytes)\n",
218            self.allocation_stats.current_allocations, self.allocation_stats.current_bytes
219        ));
220        s.push_str(&format!(
221            "  Peak: {} bytes\n",
222            self.allocation_stats.peak_bytes
223        ));
224
225        s.push_str("\nHeap:\n");
226        s.push_str(&format!("  Used: {} bytes\n", self.heap_stats.used_bytes));
227        s.push_str(&format!("  RSS: {} bytes\n", self.heap_stats.rss_bytes));
228        s.push_str(&format!(
229            "  Fragmentation: {:.1}%\n",
230            self.heap_stats.fragmentation_ratio() * 100.0
231        ));
232
233        s.push_str("\nPools:\n");
234        s.push_str(&format!(
235            "  String pool: {} strings ({} bytes)\n",
236            self.pool_stats.string_pool.count, self.pool_stats.string_pool.total_bytes
237        ));
238        s.push_str(&format!(
239            "  Buffer pool: {} available\n",
240            self.pool_stats.buffer_pool_available
241        ));
242
243        if self.leak_report.has_leaks() {
244            s.push_str("\n⚠️  POTENTIAL LEAKS DETECTED:\n");
245            s.push_str(&self.leak_report.summary());
246        } else {
247            s.push_str("\n✅ No memory leaks detected\n");
248        }
249
250        s
251    }
252}
253
254impl std::fmt::Display for MemoryReport {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        write!(f, "{}", self.summary())
257    }
258}
259
260/// Pool statistics.
261#[derive(Debug, Clone)]
262pub struct PoolStats {
263    /// String pool statistics.
264    pub string_pool: crate::memory::PoolStats,
265    /// Number of available buffers in buffer pool.
266    pub buffer_pool_available: usize,
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_profiling_toggle() {
275        disable_profiling();
276        assert!(!is_profiling_enabled());
277
278        enable_profiling();
279        assert!(is_profiling_enabled());
280
281        disable_profiling();
282        assert!(!is_profiling_enabled());
283    }
284
285    #[test]
286    fn test_memory_profiler() {
287        let profiler = MemoryProfiler::new();
288
289        // Take snapshot
290        let snapshot = profiler.snapshot();
291        assert!(snapshot.timestamp > 0);
292
293        // Get stats
294        let stats = profiler.stats();
295        assert!(stats.total_allocations >= 0);
296
297        // Generate report
298        let report = profiler.report();
299        assert!(!report.summary().is_empty());
300    }
301
302    #[test]
303    fn test_with_profiling() {
304        let (result, report) = with_profiling(|| {
305            // Allocate some memory
306            let v: Vec<i32> = (0..100).collect();
307            v.len()
308        });
309
310        assert_eq!(result, 100);
311        // Report should be valid
312        assert!(report.potential_leaks.is_empty() || !report.potential_leaks.is_empty());
313    }
314}