1use presentar_core::{
14 Brick, BrickAssertion, BrickBudget, BrickVerification, Canvas, Color, Constraints, Event,
15 LayoutResult, Point, Rect, Size, TextStyle, TypeId, Widget,
16};
17use std::any::Any;
18use std::time::Duration;
19
20#[derive(Debug, Clone, Default)]
22pub struct HugePages {
23 pub total: u64,
25 pub free: u64,
27 pub reserved: u64,
29 pub page_size_kb: u64,
31}
32
33impl HugePages {
34 #[must_use]
36 pub fn new(total: u64, free: u64, reserved: u64, page_size_kb: u64) -> Self {
37 Self {
38 total,
39 free,
40 reserved,
41 page_size_kb,
42 }
43 }
44
45 #[must_use]
47 pub fn used(&self) -> u64 {
48 self.total.saturating_sub(self.free)
49 }
50
51 #[must_use]
53 pub fn used_bytes(&self) -> u64 {
54 self.used() * self.page_size_kb * 1024
55 }
56
57 #[must_use]
59 pub fn total_bytes(&self) -> u64 {
60 self.total * self.page_size_kb * 1024
61 }
62
63 #[must_use]
65 pub fn usage_percent(&self) -> f64 {
66 if self.total == 0 {
67 0.0
68 } else {
69 let pct = (self.used() as f64 / self.total as f64) * 100.0;
70 debug_assert!((0.0..=100.0).contains(&pct), "usage_percent must be 0-100");
72 pct
73 }
74 }
75
76 #[must_use]
78 pub fn is_configured(&self) -> bool {
79 self.total > 0
80 }
81
82 #[must_use]
84 pub fn to_display_string(&self) -> String {
85 if !self.is_configured() {
86 return String::from("HugePages: not configured");
87 }
88
89 let size_str = if self.page_size_kb >= 1024 * 1024 {
90 format!("{}G", self.page_size_kb / (1024 * 1024))
91 } else if self.page_size_kb >= 1024 {
92 format!("{}M", self.page_size_kb / 1024)
93 } else {
94 format!("{}K", self.page_size_kb)
95 };
96
97 format!("{}/{} {}", self.used(), self.total, size_str)
98 }
99}
100
101#[derive(Debug, Clone)]
103pub struct MemorySegment {
104 pub name: String,
106 pub bytes: u64,
108 pub color: Color,
110}
111
112impl MemorySegment {
113 #[must_use]
115 pub fn new(name: impl Into<String>, bytes: u64, color: Color) -> Self {
116 Self {
117 name: name.into(),
118 bytes,
119 color,
120 }
121 }
122}
123
124#[derive(Debug, Clone)]
126pub struct MemoryBar {
127 segments: Vec<MemorySegment>,
129 total_bytes: u64,
131 show_labels: bool,
133 show_values: bool,
135 bar_width: usize,
137 bounds: Rect,
139 huge_pages: Option<HugePages>,
141 show_huge_pages: bool,
143}
144
145impl Default for MemoryBar {
146 fn default() -> Self {
147 Self::new(0)
148 }
149}
150
151impl MemoryBar {
152 #[must_use]
154 pub fn new(total_bytes: u64) -> Self {
155 Self {
156 segments: Vec::new(),
157 total_bytes,
158 show_labels: true,
159 show_values: true,
160 bar_width: 30,
161 bounds: Rect::default(),
162 huge_pages: None,
163 show_huge_pages: false,
164 }
165 }
166
167 #[must_use]
169 pub fn from_usage(
170 used_bytes: u64,
171 cached_bytes: u64,
172 swap_used: u64,
173 swap_total: u64,
174 total_bytes: u64,
175 ) -> Self {
176 let mut bar = Self::new(total_bytes);
177
178 bar.add_segment(MemorySegment::new(
180 "Used",
181 used_bytes,
182 Color::new(0.98, 0.47, 0.56, 1.0), ));
184
185 bar.add_segment(MemorySegment::new(
187 "Cached",
188 cached_bytes,
189 Color::new(0.88, 0.69, 0.41, 1.0), ));
191
192 if swap_total > 0 {
194 bar.add_segment(MemorySegment::new(
195 "Swap",
196 swap_used,
197 Color::new(0.73, 0.60, 0.97, 1.0), ));
199 }
200
201 bar
202 }
203
204 pub fn add_segment(&mut self, segment: MemorySegment) {
206 self.segments.push(segment);
207 }
208
209 #[must_use]
211 pub fn segment(mut self, name: impl Into<String>, bytes: u64, color: Color) -> Self {
212 self.segments.push(MemorySegment::new(name, bytes, color));
213 if self.total_bytes == 0 {
215 self.total_bytes = self.segments.iter().map(|s| s.bytes).sum();
216 }
217 self
218 }
219
220 #[must_use]
222 pub fn with_bar_width(mut self, width: usize) -> Self {
223 self.bar_width = width;
224 self
225 }
226
227 #[must_use]
229 pub fn without_labels(mut self) -> Self {
230 self.show_labels = false;
231 self
232 }
233
234 #[must_use]
236 pub fn without_values(mut self) -> Self {
237 self.show_values = false;
238 self
239 }
240
241 #[must_use]
245 pub fn with_huge_pages(mut self, huge_pages: HugePages) -> Self {
246 self.huge_pages = Some(huge_pages);
247 self.show_huge_pages = true;
248 self
249 }
250
251 #[must_use]
253 pub fn show_huge_pages(mut self, show: bool) -> Self {
254 self.show_huge_pages = show;
255 self
256 }
257
258 pub fn set_huge_pages(&mut self, huge_pages: HugePages) {
260 self.huge_pages = Some(huge_pages);
261 }
262
263 #[must_use]
265 pub fn huge_pages(&self) -> Option<&HugePages> {
266 self.huge_pages.as_ref()
267 }
268
269 #[must_use]
271 pub fn has_huge_pages(&self) -> bool {
272 self.huge_pages
273 .as_ref()
274 .is_some_and(HugePages::is_configured)
275 }
276
277 pub fn set_total(&mut self, total: u64) {
279 self.total_bytes = total;
280 }
281
282 #[must_use]
284 pub fn total(&self) -> u64 {
285 self.total_bytes
286 }
287
288 #[must_use]
290 pub fn used(&self) -> u64 {
291 self.segments.iter().map(|s| s.bytes).sum()
292 }
293
294 #[must_use]
296 pub fn usage_percent(&self) -> f64 {
297 if self.total_bytes == 0 {
298 0.0
299 } else {
300 (self.used() as f64 / self.total_bytes as f64) * 100.0
301 }
302 }
303
304 fn format_bytes(bytes: u64) -> String {
306 const KB: u64 = 1024;
307 const MB: u64 = KB * 1024;
308 const GB: u64 = MB * 1024;
309 const TB: u64 = GB * 1024;
310
311 if bytes >= TB {
312 format!("{:.1}T", bytes as f64 / TB as f64)
313 } else if bytes >= GB {
314 format!("{:.1}G", bytes as f64 / GB as f64)
315 } else if bytes >= MB {
316 format!("{:.1}M", bytes as f64 / MB as f64)
317 } else if bytes >= KB {
318 format!("{:.1}K", bytes as f64 / KB as f64)
319 } else {
320 format!("{bytes}B")
321 }
322 }
323}
324
325impl Widget for MemoryBar {
326 fn type_id(&self) -> TypeId {
327 TypeId::of::<Self>()
328 }
329
330 fn measure(&self, constraints: Constraints) -> Size {
331 let mut height = if self.show_labels {
333 self.segments.len().max(1) as f32
334 } else {
335 1.0
336 };
337
338 if self.show_huge_pages && self.has_huge_pages() {
340 height += 1.0;
341 }
342
343 let width = constraints.max_width.min(80.0);
344 constraints.constrain(Size::new(width, height))
345 }
346
347 fn layout(&mut self, bounds: Rect) -> LayoutResult {
348 self.bounds = bounds;
349 LayoutResult {
350 size: Size::new(bounds.width, bounds.height),
351 }
352 }
353
354 #[allow(clippy::too_many_lines)]
355 fn paint(&self, canvas: &mut dyn Canvas) {
356 if self.bounds.width < 1.0 || self.bounds.height < 1.0 {
357 return;
358 }
359
360 let bar_chars = self
361 .bar_width
362 .min(self.bounds.width as usize)
363 .saturating_sub(20);
364 if bar_chars == 0 {
365 return;
366 }
367
368 if self.show_labels {
369 for (i, segment) in self.segments.iter().enumerate() {
371 let y = self.bounds.y + i as f32;
372 let pct = (segment.bytes as f64 / self.total_bytes as f64) * 100.0;
373 let filled = ((pct / 100.0) * bar_chars as f64).round() as usize;
374
375 let label = format!("{:>6}:", segment.name);
377 let label_style = TextStyle {
378 color: Color::new(0.5, 0.5, 0.6, 1.0),
379 ..Default::default()
380 };
381 canvas.draw_text(&label, Point::new(self.bounds.x, y), &label_style);
382
383 if self.show_values {
385 let value = Self::format_bytes(segment.bytes);
386 canvas.draw_text(
387 &format!("{value:>6}"),
388 Point::new(self.bounds.x + 8.0, y),
389 &TextStyle {
390 color: segment.color,
391 ..Default::default()
392 },
393 );
394 }
395
396 let bar_x = if self.show_values { 15.0 } else { 8.0 };
398 let mut bar = String::with_capacity(bar_chars + 2);
399 for j in 0..bar_chars {
400 if j < filled {
401 bar.push('█');
402 } else {
403 bar.push('░');
404 }
405 }
406 canvas.draw_text(
407 &bar,
408 Point::new(self.bounds.x + bar_x, y),
409 &TextStyle {
410 color: segment.color,
411 ..Default::default()
412 },
413 );
414
415 let pct_x = self.bounds.x + bar_x + bar_chars as f32 + 1.0;
417 canvas.draw_text(
418 &format!("{pct:3.0}%"),
419 Point::new(pct_x, y),
420 &TextStyle {
421 color: segment.color,
422 ..Default::default()
423 },
424 );
425 }
426 } else {
427 let mut x = self.bounds.x;
429 let y = self.bounds.y;
430 let mut pos = 0.0;
431
432 for segment in &self.segments {
433 let segment_width =
434 (segment.bytes as f64 / self.total_bytes as f64) * bar_chars as f64;
435 let chars = (pos + segment_width).round() as usize - pos.round() as usize;
436
437 let segment_bar: String = (0..chars).map(|_| '█').collect();
438 canvas.draw_text(
439 &segment_bar,
440 Point::new(x, y),
441 &TextStyle {
442 color: segment.color,
443 ..Default::default()
444 },
445 );
446
447 x += chars as f32;
448 pos += segment_width;
449 }
450
451 let remaining = bar_chars.saturating_sub(pos.round() as usize);
453 if remaining > 0 {
454 let empty: String = (0..remaining).map(|_| '░').collect();
455 canvas.draw_text(
456 &empty,
457 Point::new(x, y),
458 &TextStyle {
459 color: Color::new(0.3, 0.3, 0.3, 1.0),
460 ..Default::default()
461 },
462 );
463 }
464 }
465
466 if self.show_huge_pages {
468 if let Some(hp) = &self.huge_pages {
469 if hp.is_configured() {
470 let y = if self.show_labels {
471 self.bounds.y + self.segments.len() as f32
472 } else {
473 self.bounds.y + 1.0
474 };
475
476 let hp_color = Color::new(0.39, 0.82, 0.75, 1.0); let label = "HPages:";
481 canvas.draw_text(
482 label,
483 Point::new(self.bounds.x, y),
484 &TextStyle {
485 color: Color::new(0.5, 0.5, 0.6, 1.0),
486 ..Default::default()
487 },
488 );
489
490 let value = hp.to_display_string();
492 canvas.draw_text(
493 &format!("{value:>12}"),
494 Point::new(self.bounds.x + 8.0, y),
495 &TextStyle {
496 color: hp_color,
497 ..Default::default()
498 },
499 );
500
501 let bar_x = if self.show_values { 21.0 } else { 8.0 };
503 let pct = hp.usage_percent();
504 let filled = ((pct / 100.0) * bar_chars as f64).round() as usize;
505
506 let mut bar = String::with_capacity(bar_chars);
507 for j in 0..bar_chars {
508 if j < filled {
509 bar.push('█');
510 } else {
511 bar.push('░');
512 }
513 }
514 canvas.draw_text(
515 &bar,
516 Point::new(self.bounds.x + bar_x, y),
517 &TextStyle {
518 color: hp_color,
519 ..Default::default()
520 },
521 );
522
523 let pct_x = self.bounds.x + bar_x + bar_chars as f32 + 1.0;
525 canvas.draw_text(
526 &format!("{pct:3.0}%"),
527 Point::new(pct_x, y),
528 &TextStyle {
529 color: hp_color,
530 ..Default::default()
531 },
532 );
533 }
534 }
535 }
536 }
537
538 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
539 None
540 }
541
542 fn children(&self) -> &[Box<dyn Widget>] {
543 &[]
544 }
545
546 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
547 &mut []
548 }
549}
550
551impl Brick for MemoryBar {
552 fn brick_name(&self) -> &'static str {
553 "memory_bar"
554 }
555
556 fn assertions(&self) -> &[BrickAssertion] {
557 static ASSERTIONS: &[BrickAssertion] = &[BrickAssertion::max_latency_ms(8)];
558 ASSERTIONS
559 }
560
561 fn budget(&self) -> BrickBudget {
562 BrickBudget::uniform(8)
563 }
564
565 fn verify(&self) -> BrickVerification {
566 BrickVerification {
567 passed: vec![BrickAssertion::max_latency_ms(8)],
568 failed: vec![],
569 verification_time: Duration::from_micros(5),
570 }
571 }
572
573 fn to_html(&self) -> String {
574 String::new()
575 }
576
577 fn to_css(&self) -> String {
578 String::new()
579 }
580}
581
582#[cfg(test)]
583mod tests {
584 use super::*;
585
586 #[test]
587 fn test_memory_bar_new() {
588 let bar = MemoryBar::new(1024 * 1024 * 1024);
589 assert_eq!(bar.total(), 1024 * 1024 * 1024);
590 }
591
592 #[test]
593 #[allow(clippy::identity_op)]
594 fn test_memory_bar_from_usage() {
595 let bar = MemoryBar::from_usage(
596 50 * 1024 * 1024 * 1024, 20 * 1024 * 1024 * 1024, 1 * 1024 * 1024 * 1024, 8 * 1024 * 1024 * 1024, 128 * 1024 * 1024 * 1024, );
602 assert_eq!(bar.segments.len(), 3);
603 }
604
605 #[test]
606 fn test_memory_bar_usage_percent() {
607 let mut bar = MemoryBar::new(100);
608 bar.add_segment(MemorySegment::new("Used", 75, Color::RED));
609 assert!((bar.usage_percent() - 75.0).abs() < 0.01);
610 }
611
612 #[test]
613 fn test_format_bytes() {
614 assert_eq!(MemoryBar::format_bytes(500), "500B");
615 assert_eq!(MemoryBar::format_bytes(1024), "1.0K");
616 assert_eq!(MemoryBar::format_bytes(1024 * 1024), "1.0M");
617 assert_eq!(MemoryBar::format_bytes(1024 * 1024 * 1024), "1.0G");
618 assert_eq!(
619 MemoryBar::format_bytes(1024u64 * 1024 * 1024 * 1024),
620 "1.0T"
621 );
622 }
623
624 #[test]
625 fn test_memory_bar_add_segment() {
626 let mut bar = MemoryBar::new(1000);
627 bar.add_segment(MemorySegment::new("Test", 500, Color::BLUE));
628 assert_eq!(bar.segments.len(), 1);
629 assert_eq!(bar.used(), 500);
630 }
631
632 #[test]
633 fn test_memory_bar_set_total() {
634 let mut bar = MemoryBar::new(100);
635 bar.set_total(200);
636 assert_eq!(bar.total(), 200);
637 }
638
639 #[test]
640 fn test_memory_bar_without_labels() {
641 let bar = MemoryBar::new(100).without_labels();
642 assert!(!bar.show_labels);
643 }
644
645 #[test]
646 fn test_memory_bar_without_values() {
647 let bar = MemoryBar::new(100).without_values();
648 assert!(!bar.show_values);
649 }
650
651 #[test]
652 fn test_memory_bar_with_bar_width() {
653 let bar = MemoryBar::new(100).with_bar_width(50);
654 assert_eq!(bar.bar_width, 50);
655 }
656
657 #[test]
658 fn test_memory_bar_layout() {
659 let mut bar = MemoryBar::new(1000);
660 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
661 let result = bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
662 assert!(result.size.width > 0.0);
663 assert!(result.size.height > 0.0);
664 }
665
666 #[test]
667 fn test_memory_bar_verify() {
668 let bar = MemoryBar::new(1000);
669 let v = bar.verify();
670 assert!(v.is_valid());
671 }
672
673 #[test]
674 fn test_memory_bar_default() {
675 let bar = MemoryBar::default();
676 assert_eq!(bar.total(), 0);
677 }
678
679 #[test]
680 fn test_memory_segment_new() {
681 let seg = MemorySegment::new("Test", 1000, Color::GREEN);
682 assert_eq!(seg.name, "Test");
683 assert_eq!(seg.bytes, 1000);
684 }
685
686 #[test]
687 fn test_memory_bar_from_usage_no_swap() {
688 let bar = MemoryBar::from_usage(
689 50 * 1024 * 1024 * 1024, 20 * 1024 * 1024 * 1024, 0, 0, 128 * 1024 * 1024 * 1024,
694 );
695 assert_eq!(bar.segments.len(), 2);
697 }
698
699 #[test]
700 fn test_memory_bar_usage_percent_zero_total() {
701 let bar = MemoryBar::new(0);
702 assert_eq!(bar.usage_percent(), 0.0);
703 }
704
705 #[test]
706 fn test_memory_bar_measure() {
707 let mut bar = MemoryBar::new(1000);
708 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
709 bar.add_segment(MemorySegment::new("Cached", 300, Color::YELLOW));
710
711 let constraints = Constraints {
712 min_width: 0.0,
713 max_width: 100.0,
714 min_height: 0.0,
715 max_height: 50.0,
716 };
717 let size = bar.measure(constraints);
718 assert!(size.width > 0.0);
719 assert_eq!(size.height, 2.0);
721 }
722
723 #[test]
724 fn test_memory_bar_measure_no_labels() {
725 let bar = MemoryBar::new(1000).without_labels();
726 let constraints = Constraints {
727 min_width: 0.0,
728 max_width: 100.0,
729 min_height: 0.0,
730 max_height: 50.0,
731 };
732 let size = bar.measure(constraints);
733 assert_eq!(size.height, 1.0);
735 }
736
737 #[test]
738 fn test_memory_bar_paint_with_labels() {
739 use crate::{CellBuffer, DirectTerminalCanvas};
740
741 let mut bar = MemoryBar::new(1000);
742 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
743 bar.add_segment(MemorySegment::new("Cached", 300, Color::YELLOW));
744
745 let mut buffer = CellBuffer::new(80, 10);
746 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
747
748 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
749 bar.paint(&mut canvas);
750
751 assert!(bar.show_labels);
753 }
754
755 #[test]
756 fn test_memory_bar_paint_without_labels() {
757 use crate::{CellBuffer, DirectTerminalCanvas};
758
759 let mut bar = MemoryBar::new(1000).without_labels();
760 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
761 bar.add_segment(MemorySegment::new("Cached", 300, Color::YELLOW));
762
763 let mut buffer = CellBuffer::new(80, 10);
764 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
765
766 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
767 bar.paint(&mut canvas);
768
769 assert!(!bar.show_labels);
771 }
772
773 #[test]
774 fn test_memory_bar_paint_zero_total() {
775 use crate::{CellBuffer, DirectTerminalCanvas};
776
777 let bar = MemoryBar::new(0);
778 let mut buffer = CellBuffer::new(80, 10);
779 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
780
781 bar.paint(&mut canvas);
783 }
784
785 #[test]
786 fn test_memory_bar_paint_small_bounds() {
787 use crate::{CellBuffer, DirectTerminalCanvas};
788
789 let mut bar = MemoryBar::new(1000);
790 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
791
792 let mut buffer = CellBuffer::new(10, 2);
793 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
794
795 bar.layout(Rect::new(0.0, 0.0, 10.0, 2.0));
797 bar.paint(&mut canvas);
798 }
799
800 #[test]
801 fn test_memory_bar_paint_without_values() {
802 use crate::{CellBuffer, DirectTerminalCanvas};
803
804 let mut bar = MemoryBar::new(1000).without_values();
805 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
806
807 let mut buffer = CellBuffer::new(80, 10);
808 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
809
810 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
811 bar.paint(&mut canvas);
812
813 assert!(!bar.show_values);
814 }
815
816 #[test]
817 fn test_memory_bar_paint_stacked_with_empty() {
818 use crate::{CellBuffer, DirectTerminalCanvas};
819
820 let mut bar = MemoryBar::new(1000).without_labels();
822 bar.add_segment(MemorySegment::new("Used", 200, Color::RED));
823
824 let mut buffer = CellBuffer::new(80, 10);
825 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
826
827 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
828 bar.paint(&mut canvas);
829
830 assert_eq!(bar.used(), 200);
832 }
833
834 #[test]
835 fn test_memory_bar_event() {
836 let mut bar = MemoryBar::new(1000);
837 let event = Event::Resize {
838 width: 80.0,
839 height: 24.0,
840 };
841 assert!(bar.event(&event).is_none());
842 }
843
844 #[test]
845 fn test_memory_bar_children() {
846 let bar = MemoryBar::new(1000);
847 assert!(bar.children().is_empty());
848 }
849
850 #[test]
851 fn test_memory_bar_children_mut() {
852 let mut bar = MemoryBar::new(1000);
853 assert!(bar.children_mut().is_empty());
854 }
855
856 #[test]
857 fn test_memory_bar_type_id() {
858 let bar = MemoryBar::new(1000);
859 let tid = Widget::type_id(&bar);
860 assert_eq!(tid, TypeId::of::<MemoryBar>());
861 }
862
863 #[test]
864 fn test_memory_bar_brick_name() {
865 let bar = MemoryBar::new(1000);
866 assert_eq!(bar.brick_name(), "memory_bar");
867 }
868
869 #[test]
870 fn test_memory_bar_assertions() {
871 let bar = MemoryBar::new(1000);
872 let assertions = bar.assertions();
873 assert!(!assertions.is_empty());
874 }
875
876 #[test]
877 fn test_memory_bar_budget() {
878 let bar = MemoryBar::new(1000);
879 let budget = bar.budget();
880 assert!(budget.layout_ms > 0);
881 }
882
883 #[test]
884 fn test_memory_bar_to_html() {
885 let bar = MemoryBar::new(1000);
886 assert!(bar.to_html().is_empty());
887 }
888
889 #[test]
890 fn test_memory_bar_to_css() {
891 let bar = MemoryBar::new(1000);
892 assert!(bar.to_css().is_empty());
893 }
894
895 #[test]
896 fn test_memory_segment_clone() {
897 let seg = MemorySegment::new("Test", 1000, Color::GREEN);
898 let cloned = seg.clone();
899 assert_eq!(cloned.name, seg.name);
900 assert_eq!(cloned.bytes, seg.bytes);
901 }
902
903 #[test]
904 fn test_memory_bar_clone() {
905 let mut bar = MemoryBar::new(1000);
906 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
907 let cloned = bar.clone();
908 assert_eq!(cloned.total(), bar.total());
909 assert_eq!(cloned.segments.len(), bar.segments.len());
910 }
911
912 #[test]
913 fn test_memory_segment_debug() {
914 let seg = MemorySegment::new("Test", 1000, Color::GREEN);
915 let debug = format!("{seg:?}");
916 assert!(debug.contains("Test"));
917 assert!(debug.contains("1000"));
918 }
919
920 #[test]
921 fn test_memory_bar_debug() {
922 let bar = MemoryBar::new(1000);
923 let debug = format!("{bar:?}");
924 assert!(debug.contains("1000"));
925 }
926
927 #[test]
930 fn test_huge_pages_new() {
931 let hp = HugePages::new(512, 256, 0, 2048); assert_eq!(hp.total, 512);
933 assert_eq!(hp.free, 256);
934 assert_eq!(hp.reserved, 0);
935 assert_eq!(hp.page_size_kb, 2048);
936 }
937
938 #[test]
939 fn test_huge_pages_used() {
940 let hp = HugePages::new(512, 256, 0, 2048);
941 assert_eq!(hp.used(), 256); }
943
944 #[test]
945 fn test_huge_pages_used_bytes() {
946 let hp = HugePages::new(512, 256, 0, 2048); assert_eq!(hp.used_bytes(), 256 * 2048 * 1024);
949 }
950
951 #[test]
952 fn test_huge_pages_total_bytes() {
953 let hp = HugePages::new(512, 256, 0, 2048);
954 assert_eq!(hp.total_bytes(), 512 * 2048 * 1024);
955 }
956
957 #[test]
958 fn test_huge_pages_usage_percent() {
959 let hp = HugePages::new(100, 50, 0, 2048);
960 assert!((hp.usage_percent() - 50.0).abs() < 0.01);
961 }
962
963 #[test]
964 fn test_huge_pages_usage_percent_zero_total() {
965 let hp = HugePages::new(0, 0, 0, 2048);
966 assert_eq!(hp.usage_percent(), 0.0);
967 }
968
969 #[test]
970 fn test_huge_pages_is_configured() {
971 let configured = HugePages::new(512, 256, 0, 2048);
972 let not_configured = HugePages::new(0, 0, 0, 2048);
973
974 assert!(configured.is_configured());
975 assert!(!not_configured.is_configured());
976 }
977
978 #[test]
979 fn test_huge_pages_to_display_string() {
980 let hp = HugePages::new(512, 256, 0, 2048); assert_eq!(hp.to_display_string(), "256/512 2M");
982 }
983
984 #[test]
985 fn test_huge_pages_to_display_string_1g_pages() {
986 let hp = HugePages::new(8, 4, 0, 1024 * 1024); assert_eq!(hp.to_display_string(), "4/8 1G");
988 }
989
990 #[test]
991 fn test_huge_pages_to_display_string_not_configured() {
992 let hp = HugePages::new(0, 0, 0, 2048);
993 assert_eq!(hp.to_display_string(), "HugePages: not configured");
994 }
995
996 #[test]
997 fn test_huge_pages_default() {
998 let hp = HugePages::default();
999 assert_eq!(hp.total, 0);
1000 assert_eq!(hp.free, 0);
1001 assert!(!hp.is_configured());
1002 }
1003
1004 #[test]
1005 fn test_huge_pages_clone() {
1006 let hp = HugePages::new(512, 256, 32, 2048);
1007 let cloned = hp.clone();
1008 assert_eq!(cloned.total, hp.total);
1009 assert_eq!(cloned.free, hp.free);
1010 assert_eq!(cloned.reserved, hp.reserved);
1011 }
1012
1013 #[test]
1014 fn test_huge_pages_debug() {
1015 let hp = HugePages::new(512, 256, 0, 2048);
1016 let debug = format!("{hp:?}");
1017 assert!(debug.contains("512"));
1018 assert!(debug.contains("256"));
1019 }
1020
1021 #[test]
1022 fn test_memory_bar_with_huge_pages() {
1023 let hp = HugePages::new(512, 256, 0, 2048);
1024 let bar = MemoryBar::new(1024 * 1024 * 1024).with_huge_pages(hp);
1025
1026 assert!(bar.has_huge_pages());
1027 assert!(bar.show_huge_pages);
1028 assert!(bar.huge_pages().is_some());
1029 }
1030
1031 #[test]
1032 fn test_memory_bar_set_huge_pages() {
1033 let mut bar = MemoryBar::new(1024 * 1024 * 1024);
1034 assert!(!bar.has_huge_pages());
1035
1036 bar.set_huge_pages(HugePages::new(512, 256, 0, 2048));
1037 assert!(bar.huge_pages().is_some());
1038 }
1039
1040 #[test]
1041 fn test_memory_bar_huge_pages_show_toggle() {
1042 let hp = HugePages::new(512, 256, 0, 2048);
1043 let bar = MemoryBar::new(1024 * 1024 * 1024)
1044 .with_huge_pages(hp)
1045 .show_huge_pages(false);
1046
1047 assert!(!bar.show_huge_pages);
1048 }
1049
1050 #[test]
1051 fn test_memory_bar_measure_with_huge_pages() {
1052 let hp = HugePages::new(512, 256, 0, 2048);
1053 let mut bar = MemoryBar::new(1000).with_huge_pages(hp);
1054 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
1055 bar.add_segment(MemorySegment::new("Cached", 300, Color::YELLOW));
1056
1057 let constraints = Constraints::new(0.0, 100.0, 0.0, 50.0);
1058 let size = bar.measure(constraints);
1059
1060 assert_eq!(size.height, 3.0);
1062 }
1063
1064 #[test]
1065 fn test_memory_bar_measure_huge_pages_disabled() {
1066 let hp = HugePages::new(512, 256, 0, 2048);
1067 let mut bar = MemoryBar::new(1000)
1068 .with_huge_pages(hp)
1069 .show_huge_pages(false);
1070 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
1071
1072 let constraints = Constraints::new(0.0, 100.0, 0.0, 50.0);
1073 let size = bar.measure(constraints);
1074
1075 assert_eq!(size.height, 1.0);
1077 }
1078
1079 #[test]
1080 fn test_memory_bar_paint_with_huge_pages() {
1081 use crate::{CellBuffer, DirectTerminalCanvas};
1082
1083 let hp = HugePages::new(512, 256, 0, 2048);
1084 let mut bar = MemoryBar::new(1000).with_huge_pages(hp);
1085 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
1086
1087 let mut buffer = CellBuffer::new(80, 10);
1088 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1089
1090 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
1091 bar.paint(&mut canvas);
1092
1093 assert!(bar.has_huge_pages());
1095 }
1096
1097 #[test]
1098 fn test_memory_bar_paint_huge_pages_no_labels() {
1099 use crate::{CellBuffer, DirectTerminalCanvas};
1100
1101 let hp = HugePages::new(512, 256, 0, 2048);
1102 let mut bar = MemoryBar::new(1000).with_huge_pages(hp).without_labels();
1103 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
1104
1105 let mut buffer = CellBuffer::new(80, 10);
1106 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1107
1108 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
1109 bar.paint(&mut canvas);
1110 }
1111
1112 #[test]
1113 fn test_memory_bar_has_huge_pages_not_configured() {
1114 let hp = HugePages::new(0, 0, 0, 2048); let bar = MemoryBar::new(1000).with_huge_pages(hp);
1116
1117 assert!(!bar.has_huge_pages());
1119 }
1120
1121 #[test]
1122 fn test_huge_pages_small_page_size() {
1123 let hp = HugePages::new(1000, 500, 0, 64); assert_eq!(hp.to_display_string(), "500/1000 64K");
1125 }
1126}