sorting_race/lib/
memory_graph.rs

1//! Memory graph widget for visualizing memory usage per algorithm
2
3use ratatui::{
4    buffer::Buffer,
5    layout::Rect,
6    style::{Color, Style},
7    widgets::{Block, Widget},
8};
9use std::collections::HashMap;
10
11/// Memory usage data for a single algorithm
12#[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/// Widget for displaying memory usage graphs
48#[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    /// Create a new memory graph
62    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    /// Set the maximum history length
76    pub fn max_history(mut self, max: usize) -> Self {
77        self.max_history = max.max(1);
78        self
79    }
80
81    /// Set the base style
82    pub fn style(mut self, style: Style) -> Self {
83        self.style = style;
84        self
85    }
86
87    /// Set the style for current memory values
88    pub fn current_style(mut self, style: Style) -> Self {
89        self.current_style = style;
90        self
91    }
92
93    /// Set the style for peak memory values
94    pub fn peak_style(mut self, style: Style) -> Self {
95        self.peak_style = style;
96        self
97    }
98
99    /// Set the block
100    pub fn block(mut self, block: Block<'static>) -> Self {
101        self.block = Some(block);
102        self
103    }
104
105    /// Set the title
106    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    /// Set whether to show values
115    pub fn show_values(mut self, show: bool) -> Self {
116        self.show_values = show;
117        self
118    }
119
120    /// Update memory usage for an algorithm
121    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    /// Get memory data for an algorithm
127    pub fn get_algorithm_data(&self, name: &str) -> Option<&MemoryData> {
128        self.data.get(name)
129    }
130
131    /// Get all algorithm names
132    pub fn algorithm_names(&self) -> Vec<&String> {
133        self.data.keys().collect()
134    }
135
136    /// Clear all data
137    pub fn clear(&mut self) {
138        self.data.clear();
139    }
140
141    /// Format bytes into human readable string
142    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    /// Render the memory graph to a buffer
160    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        // Find maximum memory usage for scaling
191        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                // Render algorithm name
202                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                // Render memory usage bars
221                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); // Leave space for values
224                    
225                    // Current memory bar
226                    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                        // Peak memory indicator (show as different character)
238                        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                    // Show values if enabled
252                    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        // Should not panic and should have some content
371        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}