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