cbtop/bricks/panels/
memory.rs1use crate::brick::{Brick, BrickAssertion, BrickBudget, BrickVerification};
6use presentar_core::{Canvas, Point, Rect, TextStyle, Widget};
7use presentar_terminal::{Meter, Theme};
8use std::any::Any;
9
10#[derive(Debug, Clone, Default)]
12pub struct MemoryMetrics {
13 pub total_ram: u64,
15 pub used_ram: u64,
17 pub total_swap: u64,
19 pub used_swap: u64,
21 pub cached: u64,
23 pub buffers: u64,
25}
26
27impl MemoryMetrics {
28 pub fn ram_percent(&self) -> f64 {
30 if self.total_ram > 0 {
31 (self.used_ram as f64 / self.total_ram as f64) * 100.0
32 } else {
33 0.0
34 }
35 }
36
37 pub fn swap_percent(&self) -> f64 {
39 if self.total_swap > 0 {
40 (self.used_swap as f64 / self.total_swap as f64) * 100.0
41 } else {
42 0.0
43 }
44 }
45
46 pub fn format_bytes(bytes: u64) -> String {
48 batuta_common::fmt::format_bytes(bytes)
49 }
50}
51
52pub struct MemoryPanelBrick {
54 pub metrics: MemoryMetrics,
56 pub theme: Theme,
58}
59
60impl MemoryPanelBrick {
61 pub fn new() -> Self {
63 Self {
64 metrics: MemoryMetrics::default(),
65 theme: Theme::tokyo_night(),
66 }
67 }
68
69 pub fn paint(&self, canvas: &mut dyn Canvas, width: f32, _height: f32) {
71 let label_style = TextStyle {
72 color: self.theme.foreground,
73 ..Default::default()
74 };
75 let dim_style = TextStyle {
76 color: self.theme.dim,
77 ..Default::default()
78 };
79
80 canvas.draw_text("Memory Monitor", Point::new(2.0, 2.0), &label_style);
81
82 let ram_pct = self.metrics.ram_percent();
84 canvas.draw_text("RAM:", Point::new(2.0, 4.0), &dim_style);
85
86 let mut ram_meter = Meter::new(ram_pct, 100.0).with_color(self.theme.memory_color(ram_pct));
87 ram_meter.layout(Rect::new(10.0, 4.0, width - 30.0, 1.0));
88 ram_meter.paint(canvas);
89
90 let ram_info = format!(
91 "{} / {} ({:.1}%)",
92 MemoryMetrics::format_bytes(self.metrics.used_ram),
93 MemoryMetrics::format_bytes(self.metrics.total_ram),
94 ram_pct
95 );
96 let ram_style = TextStyle {
97 color: self.theme.memory_color(ram_pct),
98 ..Default::default()
99 };
100 canvas.draw_text(&ram_info, Point::new(2.0, 5.0), &ram_style);
101
102 let swap_pct = self.metrics.swap_percent();
104 canvas.draw_text("Swap:", Point::new(2.0, 7.0), &dim_style);
105
106 let mut swap_meter =
107 Meter::new(swap_pct, 100.0).with_color(self.theme.memory_color(swap_pct));
108 swap_meter.layout(Rect::new(10.0, 7.0, width - 30.0, 1.0));
109 swap_meter.paint(canvas);
110
111 let swap_info = format!(
112 "{} / {} ({:.1}%)",
113 MemoryMetrics::format_bytes(self.metrics.used_swap),
114 MemoryMetrics::format_bytes(self.metrics.total_swap),
115 swap_pct
116 );
117 let swap_style = TextStyle {
118 color: self.theme.memory_color(swap_pct),
119 ..Default::default()
120 };
121 canvas.draw_text(&swap_info, Point::new(2.0, 8.0), &swap_style);
122
123 canvas.draw_text("Cached:", Point::new(2.0, 10.0), &dim_style);
125 canvas.draw_text(
126 &MemoryMetrics::format_bytes(self.metrics.cached),
127 Point::new(12.0, 10.0),
128 &label_style,
129 );
130
131 canvas.draw_text("Buffers:", Point::new(2.0, 11.0), &dim_style);
132 canvas.draw_text(
133 &MemoryMetrics::format_bytes(self.metrics.buffers),
134 Point::new(12.0, 11.0),
135 &label_style,
136 );
137 }
138}
139
140impl Default for MemoryPanelBrick {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146impl Brick for MemoryPanelBrick {
147 fn brick_name(&self) -> &'static str {
148 "memory_panel"
149 }
150
151 fn assertions(&self) -> Vec<BrickAssertion> {
152 vec![
153 BrickAssertion::MinWidth(40),
154 BrickAssertion::MinHeight(12),
155 BrickAssertion::max_latency_ms(8),
156 ]
157 }
158
159 fn budget(&self) -> BrickBudget {
160 BrickBudget::FRAME_60FPS
161 }
162
163 fn verify(&self) -> BrickVerification {
164 let mut v = BrickVerification::new();
165 for assertion in self.assertions() {
166 v.check(&assertion);
167 }
168 v
169 }
170
171 fn as_any(&self) -> &dyn Any {
172 self
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_memory_panel_brick_name() {
182 let panel = MemoryPanelBrick::new();
183 assert_eq!(panel.brick_name(), "memory_panel");
184 }
185
186 #[test]
187 fn test_memory_panel_has_assertions() {
188 let panel = MemoryPanelBrick::new();
189 assert!(!panel.assertions().is_empty());
190 }
191
192 #[test]
193 fn test_memory_metrics_percent() {
194 let metrics = MemoryMetrics {
195 total_ram: 16_000_000_000,
196 used_ram: 8_000_000_000,
197 total_swap: 4_000_000_000,
198 used_swap: 1_000_000_000,
199 cached: 2_000_000_000,
200 buffers: 500_000_000,
201 };
202 assert!((metrics.ram_percent() - 50.0).abs() < 0.01);
203 assert!((metrics.swap_percent() - 25.0).abs() < 0.01);
204 }
205
206 #[test]
207 fn test_format_bytes() {
208 assert_eq!(MemoryMetrics::format_bytes(500), "500 B");
209 assert_eq!(MemoryMetrics::format_bytes(1024), "1.0 KB");
210 assert_eq!(MemoryMetrics::format_bytes(1_048_576), "1.0 MB");
211 assert_eq!(MemoryMetrics::format_bytes(1_073_741_824), "1.0 GB");
212 }
213}