use crate::debug::debug_trace::{DebugSection, DebugSectionBuilder, DebugTrace, Priority};
use std::sync::Arc;
use std::sync::RwLock;
#[derive(Clone)]
pub struct MemoryTracker {
history: Arc<RwLock<Vec<MemorySnapshot>>>,
max_history: usize,
}
#[derive(Clone, Debug)]
struct MemorySnapshot {
_timestamp: std::time::Instant,
memory_kb: usize,
}
impl MemoryTracker {
#[must_use]
pub fn new(max_history: usize) -> Self {
Self {
history: Arc::new(RwLock::new(Vec::new())),
max_history,
}
}
pub fn record_snapshot(&self) {
if let Some(memory_kb) = crate::utils::memory_tracker::get_process_memory_kb() {
let snapshot = MemorySnapshot {
_timestamp: std::time::Instant::now(),
memory_kb,
};
if let Ok(mut history) = self.history.write() {
history.push(snapshot);
if history.len() > self.max_history {
let drain_count = history.len() - self.max_history;
history.drain(0..drain_count);
}
}
}
}
#[must_use]
pub fn get_current_memory_mb(&self) -> Option<f64> {
crate::utils::memory_tracker::get_process_memory_kb().map(|kb| kb as f64 / 1024.0)
}
#[must_use]
pub fn get_history(&self) -> Vec<(usize, f64)> {
if let Ok(history) = self.history.read() {
history
.iter()
.enumerate()
.map(|(idx, snapshot)| (idx, snapshot.memory_kb as f64 / 1024.0))
.collect()
} else {
Vec::new()
}
}
}
impl Default for MemoryTracker {
fn default() -> Self {
Self::new(100)
}
}
pub struct MemoryDebugProvider {
tracker: MemoryTracker,
}
impl MemoryDebugProvider {
#[must_use]
pub fn new(tracker: MemoryTracker) -> Self {
Self { tracker }
}
}
impl DebugTrace for MemoryDebugProvider {
fn name(&self) -> &'static str {
"Memory"
}
fn debug_sections(&self) -> Vec<DebugSection> {
let mut builder = DebugSectionBuilder::new();
builder.add_section("MEMORY USAGE", "", Priority::MEMORY);
if let Some(memory_mb) = self.tracker.get_current_memory_mb() {
builder.add_field("Current Memory", format!("{memory_mb:.2} MB"));
} else {
builder.add_field("Current Memory", "Unable to read");
}
let history = self.tracker.get_history();
if history.is_empty() {
builder.add_line("No memory history available");
} else {
builder.add_line("");
builder.add_line("Memory History (last readings):");
let values: Vec<f64> = history.iter().map(|(_, mb)| *mb).collect();
let min = values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let avg = values.iter().sum::<f64>() / values.len() as f64;
builder.add_field(" Min", format!("{min:.2} MB"));
builder.add_field(" Max", format!("{max:.2} MB"));
builder.add_field(" Avg", format!("{avg:.2} MB"));
builder.add_line("");
builder.add_line(" Recent readings:");
for (_, mb) in history.iter().rev().take(5) {
builder.add_line(format!(" {mb:.2} MB"));
}
if history.len() >= 2 {
let first = history.first().map_or(0.0, |(_, mb)| *mb);
let last = history.last().map_or(0.0, |(_, mb)| *mb);
let growth = last - first;
let growth_pct = if first > 0.0 {
(growth / first) * 100.0
} else {
0.0
};
builder.add_line("");
builder.add_field(" Growth", format!("{growth:+.2} MB ({growth_pct:+.1}%)"));
}
}
#[cfg(target_os = "linux")]
{
if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
let mut total_mb = 0.0;
let mut available_mb = 0.0;
for line in meminfo.lines() {
if line.starts_with("MemTotal:") {
if let Some(kb) = line.split_whitespace().nth(1) {
if let Ok(kb_val) = kb.parse::<f64>() {
total_mb = kb_val / 1024.0;
}
}
} else if line.starts_with("MemAvailable:") {
if let Some(kb) = line.split_whitespace().nth(1) {
if let Ok(kb_val) = kb.parse::<f64>() {
available_mb = kb_val / 1024.0;
}
}
}
}
if total_mb > 0.0 {
builder.add_line("");
builder.add_line("System Memory:");
builder.add_field(" Total", format!("{total_mb:.2} MB"));
builder.add_field(" Available", format!("{available_mb:.2} MB"));
let used_pct = ((total_mb - available_mb) / total_mb) * 100.0;
builder.add_field(" Used", format!("{used_pct:.1}%"));
}
}
}
builder.build()
}
fn debug_summary(&self) -> Option<String> {
self.tracker
.get_current_memory_mb()
.map(|mb| format!("{mb:.2} MB"))
}
}