sql_cli/debug/
memory_debug.rs

1use crate::debug::debug_trace::{DebugSection, DebugSectionBuilder, DebugTrace, Priority};
2use std::sync::Arc;
3use std::sync::RwLock;
4
5/// Tracks memory usage over time
6#[derive(Clone)]
7pub struct MemoryTracker {
8    history: Arc<RwLock<Vec<MemorySnapshot>>>,
9    max_history: usize,
10}
11
12#[derive(Clone, Debug)]
13struct MemorySnapshot {
14    timestamp: std::time::Instant,
15    memory_kb: usize,
16}
17
18impl MemoryTracker {
19    pub fn new(max_history: usize) -> Self {
20        Self {
21            history: Arc::new(RwLock::new(Vec::new())),
22            max_history,
23        }
24    }
25
26    pub fn record_snapshot(&self) {
27        if let Some(memory_kb) = crate::utils::memory_tracker::get_process_memory_kb() {
28            let snapshot = MemorySnapshot {
29                timestamp: std::time::Instant::now(),
30                memory_kb,
31            };
32
33            if let Ok(mut history) = self.history.write() {
34                history.push(snapshot);
35                // Keep only the last max_history entries
36                if history.len() > self.max_history {
37                    let drain_count = history.len() - self.max_history;
38                    history.drain(0..drain_count);
39                }
40            }
41        }
42    }
43
44    pub fn get_current_memory_mb(&self) -> Option<f64> {
45        crate::utils::memory_tracker::get_process_memory_kb().map(|kb| kb as f64 / 1024.0)
46    }
47
48    pub fn get_history(&self) -> Vec<(usize, f64)> {
49        if let Ok(history) = self.history.read() {
50            history
51                .iter()
52                .enumerate()
53                .map(|(idx, snapshot)| (idx, snapshot.memory_kb as f64 / 1024.0))
54                .collect()
55        } else {
56            Vec::new()
57        }
58    }
59}
60
61impl Default for MemoryTracker {
62    fn default() -> Self {
63        Self::new(100)
64    }
65}
66
67/// Debug trace implementation for memory tracking
68pub struct MemoryDebugProvider {
69    tracker: MemoryTracker,
70}
71
72impl MemoryDebugProvider {
73    pub fn new(tracker: MemoryTracker) -> Self {
74        Self { tracker }
75    }
76}
77
78impl DebugTrace for MemoryDebugProvider {
79    fn name(&self) -> &str {
80        "Memory"
81    }
82
83    fn debug_sections(&self) -> Vec<DebugSection> {
84        let mut builder = DebugSectionBuilder::new();
85
86        builder.add_section("MEMORY USAGE", "", Priority::MEMORY);
87
88        // Current memory usage
89        if let Some(memory_mb) = self.tracker.get_current_memory_mb() {
90            builder.add_field("Current Memory", format!("{:.2} MB", memory_mb));
91        } else {
92            builder.add_field("Current Memory", "Unable to read");
93        }
94
95        // Memory history
96        let history = self.tracker.get_history();
97        if !history.is_empty() {
98            builder.add_line("");
99            builder.add_line("Memory History (last readings):");
100
101            // Calculate statistics
102            let values: Vec<f64> = history.iter().map(|(_, mb)| *mb).collect();
103            let min = values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
104            let max = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
105            let avg = values.iter().sum::<f64>() / values.len() as f64;
106
107            builder.add_field("  Min", format!("{:.2} MB", min));
108            builder.add_field("  Max", format!("{:.2} MB", max));
109            builder.add_field("  Avg", format!("{:.2} MB", avg));
110
111            // Show last few readings
112            builder.add_line("");
113            builder.add_line("  Recent readings:");
114            for (_, mb) in history.iter().rev().take(5) {
115                builder.add_line(format!("    {:.2} MB", mb));
116            }
117
118            // Memory growth
119            if history.len() >= 2 {
120                let first = history.first().map(|(_, mb)| *mb).unwrap_or(0.0);
121                let last = history.last().map(|(_, mb)| *mb).unwrap_or(0.0);
122                let growth = last - first;
123                let growth_pct = if first > 0.0 {
124                    (growth / first) * 100.0
125                } else {
126                    0.0
127                };
128
129                builder.add_line("");
130                builder.add_field(
131                    "  Growth",
132                    format!("{:+.2} MB ({:+.1}%)", growth, growth_pct),
133                );
134            }
135        } else {
136            builder.add_line("No memory history available");
137        }
138
139        // System memory info (if available)
140        #[cfg(target_os = "linux")]
141        {
142            if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
143                let mut total_mb = 0.0;
144                let mut available_mb = 0.0;
145
146                for line in meminfo.lines() {
147                    if line.starts_with("MemTotal:") {
148                        if let Some(kb) = line.split_whitespace().nth(1) {
149                            if let Ok(kb_val) = kb.parse::<f64>() {
150                                total_mb = kb_val / 1024.0;
151                            }
152                        }
153                    } else if line.starts_with("MemAvailable:") {
154                        if let Some(kb) = line.split_whitespace().nth(1) {
155                            if let Ok(kb_val) = kb.parse::<f64>() {
156                                available_mb = kb_val / 1024.0;
157                            }
158                        }
159                    }
160                }
161
162                if total_mb > 0.0 {
163                    builder.add_line("");
164                    builder.add_line("System Memory:");
165                    builder.add_field("  Total", format!("{:.2} MB", total_mb));
166                    builder.add_field("  Available", format!("{:.2} MB", available_mb));
167                    let used_pct = ((total_mb - available_mb) / total_mb) * 100.0;
168                    builder.add_field("  Used", format!("{:.1}%", used_pct));
169                }
170            }
171        }
172
173        builder.build()
174    }
175
176    fn debug_summary(&self) -> Option<String> {
177        self.tracker
178            .get_current_memory_mb()
179            .map(|mb| format!("{:.2} MB", mb))
180    }
181}