sql_cli/debug/
memory_debug.rs1use crate::debug::debug_trace::{DebugSection, DebugSectionBuilder, DebugTrace, Priority};
2use std::sync::Arc;
3use std::sync::RwLock;
4
5#[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 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
70pub 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 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 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 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 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 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 #[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}