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.iter().map(|s| s.rss_bytes).max().unwrap_or(0)
185 }
186
187 pub fn avg_rss(&self) -> usize {
189 if self.samples.is_empty() {
190 return 0;
191 }
192 self.samples.iter().map(|s| s.rss_bytes).sum::<usize>() / self.samples.len()
193 }
194
195 pub fn has_growth_trend(&self) -> bool {
197 if self.samples.len() < 3 {
198 return false;
199 }
200
201 let third = self.samples.len() / 3;
203 let first_avg: usize = self.samples[..third]
204 .iter()
205 .map(|s| s.rss_bytes)
206 .sum::<usize>()
207 / third;
208 let last_avg: usize = self.samples[self.samples.len() - third..]
209 .iter()
210 .map(|s| s.rss_bytes)
211 .sum::<usize>()
212 / third;
213
214 last_avg > first_avg + (first_avg / 5)
216 }
217
218 pub fn summary(&self) -> String {
220 let mut s = String::new();
221 s.push_str(&format!("Heap Report (duration: {:?})\n", self.duration));
222 s.push_str(&format!(
223 " Current RSS: {} bytes ({:.2} MB)\n",
224 self.current_stats.rss_bytes,
225 self.current_stats.rss_bytes as f64 / 1_048_576.0
226 ));
227 s.push_str(&format!(
228 " Peak RSS: {} bytes ({:.2} MB)\n",
229 self.peak_rss(),
230 self.peak_rss() as f64 / 1_048_576.0
231 ));
232 s.push_str(&format!(
233 " Fragmentation: {:.1}%\n",
234 self.current_stats.fragmentation_ratio() * 100.0
235 ));
236
237 if self.has_growth_trend() {
238 s.push_str(" ⚠️ Memory growth trend detected\n");
239 }
240
241 s
242 }
243}
244
245#[cfg(target_os = "linux")]
251fn get_memory_usage() -> (usize, usize) {
252 use std::fs;
253
254 if let Ok(statm) = fs::read_to_string("/proc/self/statm") {
256 let parts: Vec<&str> = statm.split_whitespace().collect();
257 if parts.len() >= 2 {
258 let page_size = 4096; let virtual_pages: usize = parts[0].parse().unwrap_or(0);
260 let rss_pages: usize = parts[1].parse().unwrap_or(0);
261 return (rss_pages * page_size, virtual_pages * page_size);
262 }
263 }
264
265 (0, 0)
266}
267
268#[cfg(target_os = "macos")]
269fn get_memory_usage() -> (usize, usize) {
270 #[cfg(feature = "profiling")]
273 {
274 if let Some(usage) = memory_stats::memory_stats() {
275 return (usage.physical_mem, usage.virtual_mem);
276 }
277 }
278 (0, 0)
279}
280
281#[cfg(target_os = "windows")]
282fn get_memory_usage() -> (usize, usize) {
283 #[cfg(feature = "profiling")]
284 {
285 if let Some(usage) = memory_stats::memory_stats() {
286 return (usage.physical_mem, usage.virtual_mem);
287 }
288 }
289 (0, 0)
290}
291
292#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
293fn get_memory_usage() -> (usize, usize) {
294 (0, 0)
295}
296
297#[cfg(feature = "dhat-heap")]
303pub mod dhat_profiler {
304 use dhat::{Profiler, ProfilerBuilder};
305
306 pub struct DhatGuard {
308 #[allow(dead_code)]
309 profiler: Profiler,
310 }
311
312 pub fn start_dhat() -> DhatGuard {
314 let profiler = dhat::Profiler::builder().build();
315 DhatGuard { profiler }
316 }
317
318 pub fn start_dhat_with_file(path: &str) -> DhatGuard {
320 let profiler = dhat::Profiler::builder().file_name(path).build();
321 DhatGuard { profiler }
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn test_heap_sample() {
331 let sample = HeapSample::capture();
332 assert!(sample.timestamp_ms > 0);
333 }
334
335 #[test]
336 fn test_heap_stats() {
337 let stats = HeapStats::capture();
338 assert!(stats.fragmentation_ratio() >= 0.0);
340 assert!(stats.efficiency() >= 0.0 && stats.efficiency() <= 1.0);
341 }
342
343 #[test]
344 fn test_heap_profiler() {
345 let profiler = HeapProfiler::new();
346
347 profiler.sample();
349 profiler.sample();
350 profiler.sample();
351
352 let report = profiler.report();
353 assert_eq!(report.samples.len(), 3);
354 }
355
356 #[test]
357 fn test_growth_detection() {
358 let report = HeapReport {
359 duration: std::time::Duration::from_secs(10),
360 samples: vec![
361 HeapSample {
362 timestamp_ms: 0,
363 rss_bytes: 1000,
364 virtual_bytes: 2000,
365 heap_used: None,
366 },
367 HeapSample {
368 timestamp_ms: 100,
369 rss_bytes: 1100,
370 virtual_bytes: 2100,
371 heap_used: None,
372 },
373 HeapSample {
374 timestamp_ms: 200,
375 rss_bytes: 1200,
376 virtual_bytes: 2200,
377 heap_used: None,
378 },
379 HeapSample {
380 timestamp_ms: 300,
381 rss_bytes: 2000,
382 virtual_bytes: 3000,
383 heap_used: None,
384 },
385 HeapSample {
386 timestamp_ms: 400,
387 rss_bytes: 2500,
388 virtual_bytes: 3500,
389 heap_used: None,
390 },
391 HeapSample {
392 timestamp_ms: 500,
393 rss_bytes: 3000,
394 virtual_bytes: 4000,
395 heap_used: None,
396 },
397 ],
398 current_stats: HeapStats::default(),
399 };
400
401 assert!(report.has_growth_trend());
402 }
403}