use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Debug)]
pub struct MemoryProfiler {
current_usage: AtomicUsize,
peak_usage: AtomicUsize,
limit_bytes: usize,
allocation_count: AtomicUsize,
deallocation_count: AtomicUsize,
}
#[derive(Debug, Clone)]
pub struct MemorySnapshot {
pub current_bytes: usize,
pub peak_bytes: usize,
pub limit_bytes: usize,
pub usage_percent: f64,
pub allocations: usize,
pub deallocations: usize,
pub warning: bool,
pub exceeded: bool,
}
#[derive(Debug, Clone)]
pub struct AllocationEvent {
pub size: usize,
pub category: AllocationCategory,
pub sequence: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllocationCategory {
Node,
Relationship,
Property,
Index,
Cache,
Temporary,
Other,
}
impl Default for MemoryProfiler {
fn default() -> Self {
Self::new(1024 * 1024 * 1024) }
}
impl MemoryProfiler {
pub fn new(limit_bytes: usize) -> Self {
Self {
current_usage: AtomicUsize::new(0),
peak_usage: AtomicUsize::new(0),
limit_bytes,
allocation_count: AtomicUsize::new(0),
deallocation_count: AtomicUsize::new(0),
}
}
pub fn with_default_limit() -> Self {
Self::default()
}
pub fn record_alloc(&self, size: usize) -> bool {
let current = self.current_usage.fetch_add(size, Ordering::SeqCst) + size;
self.allocation_count.fetch_add(1, Ordering::Relaxed);
let mut peak = self.peak_usage.load(Ordering::SeqCst);
while current > peak {
match self.peak_usage.compare_exchange_weak(
peak,
current,
Ordering::SeqCst,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(p) => peak = p,
}
}
current <= self.limit_bytes
}
pub fn record_dealloc(&self, size: usize) {
self.current_usage.fetch_sub(size, Ordering::SeqCst);
self.deallocation_count.fetch_add(1, Ordering::Relaxed);
}
pub fn current_usage(&self) -> usize {
self.current_usage.load(Ordering::SeqCst)
}
pub fn peak_usage(&self) -> usize {
self.peak_usage.load(Ordering::SeqCst)
}
pub fn limit(&self) -> usize {
self.limit_bytes
}
pub fn is_within_limits(&self) -> bool {
self.current_usage() <= self.limit_bytes
}
pub fn is_warning(&self) -> bool {
let usage = self.current_usage();
let threshold = (self.limit_bytes as f64 * 0.8) as usize;
usage > threshold
}
pub fn usage_percent(&self) -> f64 {
let usage = self.current_usage() as f64;
let limit = self.limit_bytes as f64;
(usage / limit) * 100.0
}
pub fn snapshot(&self) -> MemorySnapshot {
let current = self.current_usage();
let peak = self.peak_usage();
let usage_percent = (current as f64 / self.limit_bytes as f64) * 100.0;
MemorySnapshot {
current_bytes: current,
peak_bytes: peak,
limit_bytes: self.limit_bytes,
usage_percent,
allocations: self.allocation_count.load(Ordering::Relaxed),
deallocations: self.deallocation_count.load(Ordering::Relaxed),
warning: usage_percent > 80.0,
exceeded: current > self.limit_bytes,
}
}
pub fn reset(&self) {
self.current_usage.store(0, Ordering::SeqCst);
self.peak_usage.store(0, Ordering::SeqCst);
self.allocation_count.store(0, Ordering::Relaxed);
self.deallocation_count.store(0, Ordering::Relaxed);
}
pub fn format_report(&self) -> String {
let snapshot = self.snapshot();
format!(
"Memory Report:\n\
├── Current: {:.2} MB ({:.1}%)\n\
├── Peak: {:.2} MB\n\
├── Limit: {:.2} MB\n\
├── Allocations: {}\n\
├── Deallocations: {}\n\
└── Status: {}",
snapshot.current_bytes as f64 / 1024.0 / 1024.0,
snapshot.usage_percent,
snapshot.peak_bytes as f64 / 1024.0 / 1024.0,
snapshot.limit_bytes as f64 / 1024.0 / 1024.0,
snapshot.allocations,
snapshot.deallocations,
if snapshot.exceeded {
"⚠️ EXCEEDED"
} else if snapshot.warning {
"⚠️ WARNING (>80%)"
} else {
"✅ OK"
}
)
}
}
pub type SharedMemoryProfiler = Arc<MemoryProfiler>;
pub fn create_shared_profiler(limit_bytes: usize) -> SharedMemoryProfiler {
Arc::new(MemoryProfiler::new(limit_bytes))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_profiler_creation() {
let profiler = MemoryProfiler::default();
assert_eq!(profiler.limit(), 1024 * 1024 * 1024); assert_eq!(profiler.current_usage(), 0);
assert!(profiler.is_within_limits());
}
#[test]
fn test_allocation_tracking() {
let profiler = MemoryProfiler::new(1024 * 1024);
assert!(profiler.record_alloc(500 * 1024));
assert_eq!(profiler.current_usage(), 500 * 1024);
assert!(profiler.is_within_limits());
assert!(profiler.record_alloc(400 * 1024));
assert_eq!(profiler.current_usage(), 900 * 1024);
assert!(profiler.is_warning());
assert!(!profiler.record_alloc(200 * 1024)); assert!(!profiler.is_within_limits());
}
#[test]
fn test_deallocation_tracking() {
let profiler = MemoryProfiler::new(1024 * 1024);
profiler.record_alloc(500 * 1024);
assert_eq!(profiler.current_usage(), 500 * 1024);
profiler.record_dealloc(200 * 1024);
assert_eq!(profiler.current_usage(), 300 * 1024);
}
#[test]
fn test_peak_tracking() {
let profiler = MemoryProfiler::new(1024 * 1024);
profiler.record_alloc(500 * 1024);
profiler.record_alloc(300 * 1024);
assert_eq!(profiler.peak_usage(), 800 * 1024);
profiler.record_dealloc(500 * 1024);
assert_eq!(profiler.current_usage(), 300 * 1024);
assert_eq!(profiler.peak_usage(), 800 * 1024); }
#[test]
fn test_snapshot() {
let profiler = MemoryProfiler::new(1024 * 1024);
profiler.record_alloc(512 * 1024);
let snapshot = profiler.snapshot();
assert_eq!(snapshot.current_bytes, 512 * 1024);
assert_eq!(snapshot.limit_bytes, 1024 * 1024);
assert!((snapshot.usage_percent - 50.0).abs() < 0.1);
assert!(!snapshot.warning);
assert!(!snapshot.exceeded);
}
#[test]
fn test_format_report() {
let profiler = MemoryProfiler::new(1024 * 1024);
profiler.record_alloc(100 * 1024);
let report = profiler.format_report();
assert!(report.contains("Memory Report"));
assert!(report.contains("OK"));
}
}