use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Instant;
pub struct HeapProfiler {
start_time: Instant,
samples: parking_lot::Mutex<Vec<HeapSample>>,
sample_interval_ms: AtomicUsize,
}
impl HeapProfiler {
pub fn new() -> Self {
Self {
start_time: Instant::now(),
samples: parking_lot::Mutex::new(Vec::new()),
sample_interval_ms: AtomicUsize::new(100),
}
}
pub fn set_sample_interval(&self, ms: usize) {
self.sample_interval_ms.store(ms, Ordering::Relaxed);
}
pub fn sample(&self) -> HeapSample {
let sample = HeapSample::capture();
self.samples.lock().push(sample.clone());
sample
}
pub fn stats(&self) -> HeapStats {
HeapStats::capture()
}
pub fn samples(&self) -> Vec<HeapSample> {
self.samples.lock().clone()
}
pub fn report(&self) -> HeapReport {
let samples = self.samples.lock().clone();
let current = HeapStats::capture();
HeapReport {
duration: self.start_time.elapsed(),
samples,
current_stats: current,
}
}
pub fn clear(&self) {
self.samples.lock().clear();
}
}
impl Default for HeapProfiler {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct HeapSample {
pub timestamp_ms: u64,
pub rss_bytes: usize,
pub virtual_bytes: usize,
pub heap_used: Option<usize>,
}
impl HeapSample {
pub fn capture() -> Self {
let (rss, virtual_mem) = get_memory_usage();
Self {
timestamp_ms: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0),
rss_bytes: rss,
virtual_bytes: virtual_mem,
heap_used: None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct HeapStats {
pub used_bytes: usize,
pub allocated_bytes: usize,
pub rss_bytes: usize,
pub virtual_bytes: usize,
pub peak_rss_bytes: usize,
pub segments: usize,
}
impl HeapStats {
pub fn capture() -> Self {
let (rss, virtual_mem) = get_memory_usage();
Self {
used_bytes: rss, allocated_bytes: virtual_mem,
rss_bytes: rss,
virtual_bytes: virtual_mem,
peak_rss_bytes: rss, segments: 0,
}
}
pub fn fragmentation_ratio(&self) -> f64 {
if self.allocated_bytes == 0 {
return 0.0;
}
let unused = self.allocated_bytes.saturating_sub(self.used_bytes);
unused as f64 / self.allocated_bytes as f64
}
pub fn efficiency(&self) -> f64 {
if self.allocated_bytes == 0 {
return 1.0;
}
self.used_bytes as f64 / self.allocated_bytes as f64
}
}
#[derive(Debug, Clone)]
pub struct HeapReport {
pub duration: std::time::Duration,
pub samples: Vec<HeapSample>,
pub current_stats: HeapStats,
}
impl HeapReport {
pub fn peak_rss(&self) -> usize {
self.samples.iter().map(|s| s.rss_bytes).max().unwrap_or(0)
}
pub fn avg_rss(&self) -> usize {
if self.samples.is_empty() {
return 0;
}
self.samples.iter().map(|s| s.rss_bytes).sum::<usize>() / self.samples.len()
}
pub fn has_growth_trend(&self) -> bool {
if self.samples.len() < 3 {
return false;
}
let third = self.samples.len() / 3;
let first_avg: usize = self.samples[..third]
.iter()
.map(|s| s.rss_bytes)
.sum::<usize>()
/ third;
let last_avg: usize = self.samples[self.samples.len() - third..]
.iter()
.map(|s| s.rss_bytes)
.sum::<usize>()
/ third;
last_avg > first_avg + (first_avg / 5)
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str(&format!("Heap Report (duration: {:?})\n", self.duration));
s.push_str(&format!(
" Current RSS: {} bytes ({:.2} MB)\n",
self.current_stats.rss_bytes,
self.current_stats.rss_bytes as f64 / 1_048_576.0
));
s.push_str(&format!(
" Peak RSS: {} bytes ({:.2} MB)\n",
self.peak_rss(),
self.peak_rss() as f64 / 1_048_576.0
));
s.push_str(&format!(
" Fragmentation: {:.1}%\n",
self.current_stats.fragmentation_ratio() * 100.0
));
if self.has_growth_trend() {
s.push_str(" ⚠️ Memory growth trend detected\n");
}
s
}
}
#[cfg(target_os = "linux")]
fn get_memory_usage() -> (usize, usize) {
use std::fs;
if let Ok(statm) = fs::read_to_string("/proc/self/statm") {
let parts: Vec<&str> = statm.split_whitespace().collect();
if parts.len() >= 2 {
let page_size = 4096; let virtual_pages: usize = parts[0].parse().unwrap_or(0);
let rss_pages: usize = parts[1].parse().unwrap_or(0);
return (rss_pages * page_size, virtual_pages * page_size);
}
}
(0, 0)
}
#[cfg(target_os = "macos")]
fn get_memory_usage() -> (usize, usize) {
#[cfg(feature = "profiling")]
{
if let Some(usage) = memory_stats::memory_stats() {
return (usage.physical_mem, usage.virtual_mem);
}
}
(0, 0)
}
#[cfg(target_os = "windows")]
fn get_memory_usage() -> (usize, usize) {
#[cfg(feature = "profiling")]
{
if let Some(usage) = memory_stats::memory_stats() {
return (usage.physical_mem, usage.virtual_mem);
}
}
(0, 0)
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
fn get_memory_usage() -> (usize, usize) {
(0, 0)
}
#[cfg(feature = "dhat-heap")]
pub mod dhat_profiler {
use dhat::Profiler;
pub struct DhatGuard {
#[allow(dead_code)]
profiler: Profiler,
}
pub fn start_dhat() -> DhatGuard {
let profiler = dhat::Profiler::builder().build();
DhatGuard { profiler }
}
pub fn start_dhat_with_file(path: &str) -> DhatGuard {
let profiler = dhat::Profiler::builder().file_name(path).build();
DhatGuard { profiler }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_heap_sample() {
let sample = HeapSample::capture();
assert!(sample.timestamp_ms > 0);
}
#[test]
fn test_heap_stats() {
let stats = HeapStats::capture();
assert!(stats.fragmentation_ratio() >= 0.0);
assert!(stats.efficiency() >= 0.0 && stats.efficiency() <= 1.0);
}
#[test]
fn test_heap_profiler() {
let profiler = HeapProfiler::new();
profiler.sample();
profiler.sample();
profiler.sample();
let report = profiler.report();
assert_eq!(report.samples.len(), 3);
}
#[test]
fn test_growth_detection() {
let report = HeapReport {
duration: std::time::Duration::from_secs(10),
samples: vec![
HeapSample {
timestamp_ms: 0,
rss_bytes: 1000,
virtual_bytes: 2000,
heap_used: None,
},
HeapSample {
timestamp_ms: 100,
rss_bytes: 1100,
virtual_bytes: 2100,
heap_used: None,
},
HeapSample {
timestamp_ms: 200,
rss_bytes: 1200,
virtual_bytes: 2200,
heap_used: None,
},
HeapSample {
timestamp_ms: 300,
rss_bytes: 2000,
virtual_bytes: 3000,
heap_used: None,
},
HeapSample {
timestamp_ms: 400,
rss_bytes: 2500,
virtual_bytes: 3500,
heap_used: None,
},
HeapSample {
timestamp_ms: 500,
rss_bytes: 3000,
virtual_bytes: 4000,
heap_used: None,
},
],
current_stats: HeapStats::default(),
};
assert!(report.has_growth_trend());
}
}