1use std::sync::atomic::{AtomicUsize, Ordering};
6use std::time::Instant;
7
8pub struct HeapProfiler {
14 start_time: Instant,
16 samples: parking_lot::Mutex<Vec<HeapSample>>,
18 sample_interval_ms: AtomicUsize,
20}
21
22impl HeapProfiler {
23 pub fn new() -> Self {
25 Self {
26 start_time: Instant::now(),
27 samples: parking_lot::Mutex::new(Vec::new()),
28 sample_interval_ms: AtomicUsize::new(100),
29 }
30 }
31
32 pub fn set_sample_interval(&self, ms: usize) {
34 self.sample_interval_ms.store(ms, Ordering::Relaxed);
35 }
36
37 pub fn sample(&self) -> HeapSample {
39 let sample = HeapSample::capture();
40 self.samples.lock().push(sample.clone());
41 sample
42 }
43
44 pub fn stats(&self) -> HeapStats {
46 HeapStats::capture()
47 }
48
49 pub fn samples(&self) -> Vec<HeapSample> {
51 self.samples.lock().clone()
52 }
53
54 pub fn report(&self) -> HeapReport {
56 let samples = self.samples.lock().clone();
57 let current = HeapStats::capture();
58
59 HeapReport {
60 duration: self.start_time.elapsed(),
61 samples,
62 current_stats: current,
63 }
64 }
65
66 pub fn clear(&self) {
68 self.samples.lock().clear();
69 }
70}
71
72impl Default for HeapProfiler {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78#[derive(Debug, Clone)]
84pub struct HeapSample {
85 pub timestamp_ms: u64,
87 pub rss_bytes: usize,
89 pub virtual_bytes: usize,
91 pub heap_used: Option<usize>,
93}
94
95impl HeapSample {
96 pub fn capture() -> Self {
98 let (rss, virtual_mem) = get_memory_usage();
99
100 Self {
101 timestamp_ms: std::time::SystemTime::now()
102 .duration_since(std::time::UNIX_EPOCH)
103 .map(|d| d.as_millis() as u64)
104 .unwrap_or(0),
105 rss_bytes: rss,
106 virtual_bytes: virtual_mem,
107 heap_used: None,
108 }
109 }
110}
111
112#[derive(Debug, Clone, Default)]
118pub struct HeapStats {
119 pub used_bytes: usize,
121 pub allocated_bytes: usize,
123 pub rss_bytes: usize,
125 pub virtual_bytes: usize,
127 pub peak_rss_bytes: usize,
129 pub segments: usize,
131}
132
133impl HeapStats {
134 pub fn capture() -> Self {
136 let (rss, virtual_mem) = get_memory_usage();
137
138 Self {
139 used_bytes: rss, allocated_bytes: virtual_mem,
141 rss_bytes: rss,
142 virtual_bytes: virtual_mem,
143 peak_rss_bytes: rss, segments: 0,
145 }
146 }
147
148 pub fn fragmentation_ratio(&self) -> f64 {
150 if self.allocated_bytes == 0 {
151 return 0.0;
152 }
153 let unused = self.allocated_bytes.saturating_sub(self.used_bytes);
154 unused as f64 / self.allocated_bytes as f64
155 }
156
157 pub fn efficiency(&self) -> f64 {
159 if self.allocated_bytes == 0 {
160 return 1.0;
161 }
162 self.used_bytes as f64 / self.allocated_bytes as f64
163 }
164}
165
166#[derive(Debug, Clone)]
172pub struct HeapReport {
173 pub duration: std::time::Duration,
175 pub samples: Vec<HeapSample>,
177 pub current_stats: HeapStats,
179}
180
181impl HeapReport {
182 pub fn peak_rss(&self) -> usize {
184 self.samples
185 .iter()
186 .map(|s| s.rss_bytes)
187 .max()
188 .unwrap_or(0)
189 }
190
191 pub fn avg_rss(&self) -> usize {
193 if self.samples.is_empty() {
194 return 0;
195 }
196 self.samples.iter().map(|s| s.rss_bytes).sum::<usize>() / self.samples.len()
197 }
198
199 pub fn has_growth_trend(&self) -> bool {
201 if self.samples.len() < 3 {
202 return false;
203 }
204
205 let third = self.samples.len() / 3;
207 let first_avg: usize =
208 self.samples[..third].iter().map(|s| s.rss_bytes).sum::<usize>() / third;
209 let last_avg: usize = self.samples[self.samples.len() - third..]
210 .iter()
211 .map(|s| s.rss_bytes)
212 .sum::<usize>()
213 / third;
214
215 last_avg > first_avg + (first_avg / 5)
217 }
218
219 pub fn summary(&self) -> String {
221 let mut s = String::new();
222 s.push_str(&format!(
223 "Heap Report (duration: {:?})\n",
224 self.duration
225 ));
226 s.push_str(&format!(
227 " Current RSS: {} bytes ({:.2} MB)\n",
228 self.current_stats.rss_bytes,
229 self.current_stats.rss_bytes as f64 / 1_048_576.0
230 ));
231 s.push_str(&format!(
232 " Peak RSS: {} bytes ({:.2} MB)\n",
233 self.peak_rss(),
234 self.peak_rss() as f64 / 1_048_576.0
235 ));
236 s.push_str(&format!(
237 " Fragmentation: {:.1}%\n",
238 self.current_stats.fragmentation_ratio() * 100.0
239 ));
240
241 if self.has_growth_trend() {
242 s.push_str(" ⚠️ Memory growth trend detected\n");
243 }
244
245 s
246 }
247}
248
249#[cfg(target_os = "linux")]
255fn get_memory_usage() -> (usize, usize) {
256 use std::fs;
257
258 if let Ok(statm) = fs::read_to_string("/proc/self/statm") {
260 let parts: Vec<&str> = statm.split_whitespace().collect();
261 if parts.len() >= 2 {
262 let page_size = 4096; let virtual_pages: usize = parts[0].parse().unwrap_or(0);
264 let rss_pages: usize = parts[1].parse().unwrap_or(0);
265 return (rss_pages * page_size, virtual_pages * page_size);
266 }
267 }
268
269 (0, 0)
270}
271
272#[cfg(target_os = "macos")]
273fn get_memory_usage() -> (usize, usize) {
274 #[cfg(feature = "profiling")]
277 {
278 if let Some(usage) = memory_stats::memory_stats() {
279 return (usage.physical_mem, usage.virtual_mem);
280 }
281 }
282 (0, 0)
283}
284
285#[cfg(target_os = "windows")]
286fn get_memory_usage() -> (usize, usize) {
287 #[cfg(feature = "profiling")]
288 {
289 if let Some(usage) = memory_stats::memory_stats() {
290 return (usage.physical_mem, usage.virtual_mem);
291 }
292 }
293 (0, 0)
294}
295
296#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
297fn get_memory_usage() -> (usize, usize) {
298 (0, 0)
299}
300
301#[cfg(feature = "dhat-heap")]
307pub mod dhat_profiler {
308 use dhat::{Profiler, ProfilerBuilder};
309
310 pub struct DhatGuard {
312 #[allow(dead_code)]
313 profiler: Profiler,
314 }
315
316 pub fn start_dhat() -> DhatGuard {
318 let profiler = dhat::Profiler::builder().build();
319 DhatGuard { profiler }
320 }
321
322 pub fn start_dhat_with_file(path: &str) -> DhatGuard {
324 let profiler = dhat::Profiler::builder()
325 .file_name(path)
326 .build();
327 DhatGuard { profiler }
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_heap_sample() {
337 let sample = HeapSample::capture();
338 assert!(sample.timestamp_ms > 0);
339 }
340
341 #[test]
342 fn test_heap_stats() {
343 let stats = HeapStats::capture();
344 assert!(stats.fragmentation_ratio() >= 0.0);
346 assert!(stats.efficiency() >= 0.0 && stats.efficiency() <= 1.0);
347 }
348
349 #[test]
350 fn test_heap_profiler() {
351 let profiler = HeapProfiler::new();
352
353 profiler.sample();
355 profiler.sample();
356 profiler.sample();
357
358 let report = profiler.report();
359 assert_eq!(report.samples.len(), 3);
360 }
361
362 #[test]
363 fn test_growth_detection() {
364 let report = HeapReport {
365 duration: std::time::Duration::from_secs(10),
366 samples: vec![
367 HeapSample {
368 timestamp_ms: 0,
369 rss_bytes: 1000,
370 virtual_bytes: 2000,
371 heap_used: None,
372 },
373 HeapSample {
374 timestamp_ms: 100,
375 rss_bytes: 1100,
376 virtual_bytes: 2100,
377 heap_used: None,
378 },
379 HeapSample {
380 timestamp_ms: 200,
381 rss_bytes: 1200,
382 virtual_bytes: 2200,
383 heap_used: None,
384 },
385 HeapSample {
386 timestamp_ms: 300,
387 rss_bytes: 2000,
388 virtual_bytes: 3000,
389 heap_used: None,
390 },
391 HeapSample {
392 timestamp_ms: 400,
393 rss_bytes: 2500,
394 virtual_bytes: 3500,
395 heap_used: None,
396 },
397 HeapSample {
398 timestamp_ms: 500,
399 rss_bytes: 3000,
400 virtual_bytes: 4000,
401 heap_used: None,
402 },
403 ],
404 current_stats: HeapStats::default(),
405 };
406
407 assert!(report.has_growth_trend());
408 }
409}
410