prax_query/profiling/
mod.rs1pub 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
67static PROFILING_ENABLED: AtomicBool = AtomicBool::new(false);
69
70pub fn enable_profiling() {
72 PROFILING_ENABLED.store(true, Ordering::SeqCst);
73 tracing::info!("Memory profiling enabled");
74}
75
76pub fn disable_profiling() {
78 PROFILING_ENABLED.store(false, Ordering::SeqCst);
79 tracing::info!("Memory profiling disabled");
80}
81
82#[inline]
84pub fn is_profiling_enabled() -> bool {
85 PROFILING_ENABLED.load(Ordering::Relaxed)
86}
87
88pub 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
102pub 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
117pub struct MemoryProfiler {
119 tracker: AllocationTracker,
120 heap_profiler: HeapProfiler,
121 leak_detector: LeakDetector,
122}
123
124impl MemoryProfiler {
125 pub fn new() -> Self {
127 Self {
128 tracker: AllocationTracker::new(),
129 heap_profiler: HeapProfiler::new(),
130 leak_detector: LeakDetector::new(),
131 }
132 }
133
134 pub fn snapshot(&self) -> MemorySnapshot {
136 MemorySnapshot::capture(&self.tracker)
137 }
138
139 pub fn stats(&self) -> AllocationStats {
141 self.tracker.stats()
142 }
143
144 pub fn heap_stats(&self) -> HeapStats {
146 self.heap_profiler.stats()
147 }
148
149 pub fn detect_leaks(&self) -> LeakReport {
151 self.leak_detector.analyze(&self.tracker)
152 }
153
154 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 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 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#[derive(Debug)]
188pub struct MemoryReport {
189 pub allocation_stats: AllocationStats,
191 pub heap_stats: HeapStats,
193 pub leak_report: LeakReport,
195 pub pool_stats: PoolStats,
197}
198
199impl MemoryReport {
200 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 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#[derive(Debug, Clone)]
272pub struct PoolStats {
273 pub string_pool: crate::memory::PoolStats,
275 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 let snapshot = profiler.snapshot();
301 assert!(snapshot.timestamp > 0);
302
303 let stats = profiler.stats();
305 assert!(stats.total_allocations >= 0);
306
307 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 let v: Vec<i32> = (0..100).collect();
317 v.len()
318 });
319
320 assert_eq!(result, 100);
321 assert!(report.potential_leaks.is_empty() || !report.potential_leaks.is_empty());
323 }
324}
325