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 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 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
67pub 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 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 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 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 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 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 #[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}