1use ratatui::{
4 buffer::Buffer,
5 layout::Rect,
6 style::{Color, Style},
7 widgets::{Block, Widget},
8};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone)]
13pub struct MemoryData {
14 pub current: usize,
15 pub peak: usize,
16 pub history: Vec<usize>,
17}
18
19impl MemoryData {
20 pub fn new() -> Self {
21 Self {
22 current: 0,
23 peak: 0,
24 history: Vec::new(),
25 }
26 }
27
28 pub fn update(&mut self, current: usize, max_history: usize) {
29 self.current = current;
30 if current > self.peak {
31 self.peak = current;
32 }
33
34 self.history.push(current);
35 if self.history.len() > max_history {
36 self.history.remove(0);
37 }
38 }
39}
40
41impl Default for MemoryData {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct MemoryGraph {
50 data: HashMap<String, MemoryData>,
51 max_history: usize,
52 style: Style,
53 current_style: Style,
54 peak_style: Style,
55 block: Option<Block<'static>>,
56 title: Option<String>,
57 show_values: bool,
58}
59
60impl MemoryGraph {
61 pub fn new() -> Self {
63 Self {
64 data: HashMap::new(),
65 max_history: 100,
66 style: Style::default(),
67 current_style: Style::default().fg(Color::Green),
68 peak_style: Style::default().fg(Color::Red),
69 block: None,
70 title: None,
71 show_values: true,
72 }
73 }
74
75 pub fn max_history(mut self, max: usize) -> Self {
77 self.max_history = max.max(1);
78 self
79 }
80
81 pub fn style(mut self, style: Style) -> Self {
83 self.style = style;
84 self
85 }
86
87 pub fn current_style(mut self, style: Style) -> Self {
89 self.current_style = style;
90 self
91 }
92
93 pub fn peak_style(mut self, style: Style) -> Self {
95 self.peak_style = style;
96 self
97 }
98
99 pub fn block(mut self, block: Block<'static>) -> Self {
101 self.block = Some(block);
102 self
103 }
104
105 pub fn title<T>(mut self, title: T) -> Self
107 where
108 T: Into<String>,
109 {
110 self.title = Some(title.into());
111 self
112 }
113
114 pub fn show_values(mut self, show: bool) -> Self {
116 self.show_values = show;
117 self
118 }
119
120 pub fn update_algorithm(&mut self, name: &str, current_bytes: usize) {
122 let entry = self.data.entry(name.to_string()).or_default();
123 entry.update(current_bytes, self.max_history);
124 }
125
126 pub fn get_algorithm_data(&self, name: &str) -> Option<&MemoryData> {
128 self.data.get(name)
129 }
130
131 pub fn algorithm_names(&self) -> Vec<&String> {
133 self.data.keys().collect()
134 }
135
136 pub fn clear(&mut self) {
138 self.data.clear();
139 }
140
141 fn format_bytes(bytes: usize) -> String {
143 const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
144 let mut size = bytes as f64;
145 let mut unit_index = 0;
146
147 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
148 size /= 1024.0;
149 unit_index += 1;
150 }
151
152 if unit_index == 0 {
153 format!("{}B", bytes)
154 } else {
155 format!("{:.1}{}", size, UNITS[unit_index])
156 }
157 }
158
159 pub fn render_widget(&self, area: Rect, buf: &mut Buffer) {
161 if area.width < 3 || area.height < 3 {
162 return;
163 }
164
165 let inner_area = if let Some(ref block) = self.block {
166 let inner = block.inner(area);
167 block.render(area, buf);
168 inner
169 } else {
170 area
171 };
172
173 if self.data.is_empty() {
174 return;
175 }
176
177 let algorithms: Vec<_> = self.data.keys().cloned().collect();
178 let algorithm_count = algorithms.len();
179
180 if algorithm_count == 0 {
181 return;
182 }
183
184 let line_height = if inner_area.height > algorithm_count as u16 {
185 inner_area.height / algorithm_count as u16
186 } else {
187 1
188 };
189
190 let max_memory = self.data.values()
192 .map(|data| data.peak.max(data.current))
193 .max()
194 .unwrap_or(1);
195
196 for (i, algorithm) in algorithms.iter().enumerate() {
197 if let Some(memory_data) = self.data.get(algorithm) {
198 let y_start = inner_area.top() + (i as u16 * line_height);
199 let _y_end = (y_start + line_height).min(inner_area.bottom());
200
201 let name_y = y_start;
203 if name_y < inner_area.bottom() {
204 let display_name = if algorithm.len() > 10 {
205 &algorithm[..10]
206 } else {
207 algorithm
208 };
209
210 for (char_idx, ch) in display_name.chars().enumerate() {
211 let char_x = inner_area.left() + char_idx as u16;
212 if char_x < inner_area.right() {
213 buf[(char_x, name_y)]
214 .set_symbol(&ch.to_string())
215 .set_style(self.style);
216 }
217 }
218 }
219
220 if line_height > 1 && y_start + 1 < inner_area.bottom() {
222 let bar_y = y_start + 1;
223 let available_width = inner_area.width.saturating_sub(15); if max_memory > 0 {
227 let current_width = ((memory_data.current as f64 / max_memory as f64) * available_width as f64) as u16;
228 for x in 0..current_width {
229 let char_x = inner_area.left() + x;
230 if char_x < inner_area.right() && bar_y < inner_area.bottom() {
231 buf[(char_x, bar_y)]
232 .set_symbol("█")
233 .set_style(self.current_style);
234 }
235 }
236
237 let peak_width = ((memory_data.peak as f64 / max_memory as f64) * available_width as f64) as u16;
239 if peak_width > current_width {
240 for x in current_width..peak_width {
241 let char_x = inner_area.left() + x;
242 if char_x < inner_area.right() && bar_y < inner_area.bottom() {
243 buf[(char_x, bar_y)]
244 .set_symbol("░")
245 .set_style(self.peak_style);
246 }
247 }
248 }
249 }
250
251 if self.show_values {
253 let values_x = inner_area.right().saturating_sub(14);
254 if values_x > inner_area.left() {
255 let current_str = Self::format_bytes(memory_data.current);
256 let peak_str = Self::format_bytes(memory_data.peak);
257 let value_text = format!("{}/{}", current_str, peak_str);
258
259 for (char_idx, ch) in value_text.chars().enumerate() {
260 let char_x = values_x + char_idx as u16;
261 if char_x < inner_area.right() && bar_y < inner_area.bottom() {
262 buf[(char_x, bar_y)]
263 .set_symbol(&ch.to_string())
264 .set_style(self.style);
265 }
266 }
267 }
268 }
269 }
270 }
271 }
272 }
273}
274
275impl Default for MemoryGraph {
276 fn default() -> Self {
277 Self::new()
278 }
279}
280
281impl Widget for MemoryGraph {
282 fn render(self, area: Rect, buf: &mut Buffer) {
283 self.render_widget(area, buf);
284 }
285}
286
287impl Widget for &MemoryGraph {
288 fn render(self, area: Rect, buf: &mut Buffer) {
289 self.render_widget(area, buf);
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use ratatui::{
297 buffer::Buffer,
298 layout::Rect,
299 };
300
301 #[test]
302 fn test_memory_graph_creation() {
303 let graph = MemoryGraph::new();
304 assert_eq!(graph.data.len(), 0);
305 assert_eq!(graph.max_history, 100);
306 }
307
308 #[test]
309 fn test_update_algorithm() {
310 let mut graph = MemoryGraph::new();
311 graph.update_algorithm("QuickSort", 1024);
312 graph.update_algorithm("MergeSort", 2048);
313
314 assert_eq!(graph.data.len(), 2);
315
316 let quick_data = graph.get_algorithm_data("QuickSort").unwrap();
317 assert_eq!(quick_data.current, 1024);
318 assert_eq!(quick_data.peak, 1024);
319
320 let merge_data = graph.get_algorithm_data("MergeSort").unwrap();
321 assert_eq!(merge_data.current, 2048);
322 assert_eq!(merge_data.peak, 2048);
323 }
324
325 #[test]
326 fn test_peak_memory_tracking() {
327 let mut graph = MemoryGraph::new();
328 graph.update_algorithm("TestSort", 1024);
329 graph.update_algorithm("TestSort", 2048);
330 graph.update_algorithm("TestSort", 1536);
331
332 let data = graph.get_algorithm_data("TestSort").unwrap();
333 assert_eq!(data.current, 1536);
334 assert_eq!(data.peak, 2048);
335 assert_eq!(data.history.len(), 3);
336 }
337
338 #[test]
339 fn test_history_limit() {
340 let mut graph = MemoryGraph::new().max_history(3);
341 for i in 1..=5 {
342 graph.update_algorithm("TestSort", i * 100);
343 }
344
345 let data = graph.get_algorithm_data("TestSort").unwrap();
346 assert_eq!(data.history.len(), 3);
347 assert_eq!(data.history, vec![300, 400, 500]);
348 }
349
350 #[test]
351 fn test_format_bytes() {
352 assert_eq!(MemoryGraph::format_bytes(512), "512B");
353 assert_eq!(MemoryGraph::format_bytes(1024), "1.0KB");
354 assert_eq!(MemoryGraph::format_bytes(1536), "1.5KB");
355 assert_eq!(MemoryGraph::format_bytes(1048576), "1.0MB");
356 assert_eq!(MemoryGraph::format_bytes(1073741824), "1.0GB");
357 }
358
359 #[test]
360 fn test_render_widget() {
361 let mut graph = MemoryGraph::new();
362 graph.update_algorithm("QuickSort", 1024);
363 graph.update_algorithm("MergeSort", 2048);
364
365 let area = Rect::new(0, 0, 50, 10);
366 let mut buffer = Buffer::empty(area);
367
368 graph.render_widget(area, &mut buffer);
369
370 let content = buffer.content();
372 assert!(!content.is_empty());
373 }
374
375 #[test]
376 fn test_clear() {
377 let mut graph = MemoryGraph::new();
378 graph.update_algorithm("TestSort", 1024);
379 assert_eq!(graph.data.len(), 1);
380
381 graph.clear();
382 assert_eq!(graph.data.len(), 0);
383 }
384}