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 fn test_memory_bar_from_usage() {
594 let bar = MemoryBar::from_usage(
595 50 * 1024 * 1024 * 1024, 20 * 1024 * 1024 * 1024, 1 * 1024 * 1024 * 1024, 8 * 1024 * 1024 * 1024, 128 * 1024 * 1024 * 1024, );
601 assert_eq!(bar.segments.len(), 3);
602 }
603
604 #[test]
605 fn test_memory_bar_usage_percent() {
606 let mut bar = MemoryBar::new(100);
607 bar.add_segment(MemorySegment::new("Used", 75, Color::RED));
608 assert!((bar.usage_percent() - 75.0).abs() < 0.01);
609 }
610
611 #[test]
612 fn test_format_bytes() {
613 assert_eq!(MemoryBar::format_bytes(500), "500B");
614 assert_eq!(MemoryBar::format_bytes(1024), "1.0K");
615 assert_eq!(MemoryBar::format_bytes(1024 * 1024), "1.0M");
616 assert_eq!(MemoryBar::format_bytes(1024 * 1024 * 1024), "1.0G");
617 assert_eq!(
618 MemoryBar::format_bytes(1024u64 * 1024 * 1024 * 1024),
619 "1.0T"
620 );
621 }
622
623 #[test]
624 fn test_memory_bar_add_segment() {
625 let mut bar = MemoryBar::new(1000);
626 bar.add_segment(MemorySegment::new("Test", 500, Color::BLUE));
627 assert_eq!(bar.segments.len(), 1);
628 assert_eq!(bar.used(), 500);
629 }
630
631 #[test]
632 fn test_memory_bar_set_total() {
633 let mut bar = MemoryBar::new(100);
634 bar.set_total(200);
635 assert_eq!(bar.total(), 200);
636 }
637
638 #[test]
639 fn test_memory_bar_without_labels() {
640 let bar = MemoryBar::new(100).without_labels();
641 assert!(!bar.show_labels);
642 }
643
644 #[test]
645 fn test_memory_bar_without_values() {
646 let bar = MemoryBar::new(100).without_values();
647 assert!(!bar.show_values);
648 }
649
650 #[test]
651 fn test_memory_bar_with_bar_width() {
652 let bar = MemoryBar::new(100).with_bar_width(50);
653 assert_eq!(bar.bar_width, 50);
654 }
655
656 #[test]
657 fn test_memory_bar_layout() {
658 let mut bar = MemoryBar::new(1000);
659 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
660 let result = bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
661 assert!(result.size.width > 0.0);
662 assert!(result.size.height > 0.0);
663 }
664
665 #[test]
666 fn test_memory_bar_verify() {
667 let bar = MemoryBar::new(1000);
668 let v = bar.verify();
669 assert!(v.is_valid());
670 }
671
672 #[test]
673 fn test_memory_bar_default() {
674 let bar = MemoryBar::default();
675 assert_eq!(bar.total(), 0);
676 }
677
678 #[test]
679 fn test_memory_segment_new() {
680 let seg = MemorySegment::new("Test", 1000, Color::GREEN);
681 assert_eq!(seg.name, "Test");
682 assert_eq!(seg.bytes, 1000);
683 }
684
685 #[test]
686 fn test_memory_bar_from_usage_no_swap() {
687 let bar = MemoryBar::from_usage(
688 50 * 1024 * 1024 * 1024, 20 * 1024 * 1024 * 1024, 0, 0, 128 * 1024 * 1024 * 1024,
693 );
694 assert_eq!(bar.segments.len(), 2);
696 }
697
698 #[test]
699 fn test_memory_bar_usage_percent_zero_total() {
700 let bar = MemoryBar::new(0);
701 assert_eq!(bar.usage_percent(), 0.0);
702 }
703
704 #[test]
705 fn test_memory_bar_measure() {
706 let mut bar = MemoryBar::new(1000);
707 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
708 bar.add_segment(MemorySegment::new("Cached", 300, Color::YELLOW));
709
710 let constraints = Constraints {
711 min_width: 0.0,
712 max_width: 100.0,
713 min_height: 0.0,
714 max_height: 50.0,
715 };
716 let size = bar.measure(constraints);
717 assert!(size.width > 0.0);
718 assert_eq!(size.height, 2.0);
720 }
721
722 #[test]
723 fn test_memory_bar_measure_no_labels() {
724 let bar = MemoryBar::new(1000).without_labels();
725 let constraints = Constraints {
726 min_width: 0.0,
727 max_width: 100.0,
728 min_height: 0.0,
729 max_height: 50.0,
730 };
731 let size = bar.measure(constraints);
732 assert_eq!(size.height, 1.0);
734 }
735
736 #[test]
737 fn test_memory_bar_paint_with_labels() {
738 use crate::{CellBuffer, DirectTerminalCanvas};
739
740 let mut bar = MemoryBar::new(1000);
741 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
742 bar.add_segment(MemorySegment::new("Cached", 300, Color::YELLOW));
743
744 let mut buffer = CellBuffer::new(80, 10);
745 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
746
747 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
748 bar.paint(&mut canvas);
749
750 assert!(bar.show_labels);
752 }
753
754 #[test]
755 fn test_memory_bar_paint_without_labels() {
756 use crate::{CellBuffer, DirectTerminalCanvas};
757
758 let mut bar = MemoryBar::new(1000).without_labels();
759 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
760 bar.add_segment(MemorySegment::new("Cached", 300, Color::YELLOW));
761
762 let mut buffer = CellBuffer::new(80, 10);
763 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
764
765 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
766 bar.paint(&mut canvas);
767
768 assert!(!bar.show_labels);
770 }
771
772 #[test]
773 fn test_memory_bar_paint_zero_total() {
774 use crate::{CellBuffer, DirectTerminalCanvas};
775
776 let bar = MemoryBar::new(0);
777 let mut buffer = CellBuffer::new(80, 10);
778 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
779
780 bar.paint(&mut canvas);
782 }
783
784 #[test]
785 fn test_memory_bar_paint_small_bounds() {
786 use crate::{CellBuffer, DirectTerminalCanvas};
787
788 let mut bar = MemoryBar::new(1000);
789 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
790
791 let mut buffer = CellBuffer::new(10, 2);
792 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
793
794 bar.layout(Rect::new(0.0, 0.0, 10.0, 2.0));
796 bar.paint(&mut canvas);
797 }
798
799 #[test]
800 fn test_memory_bar_paint_without_values() {
801 use crate::{CellBuffer, DirectTerminalCanvas};
802
803 let mut bar = MemoryBar::new(1000).without_values();
804 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
805
806 let mut buffer = CellBuffer::new(80, 10);
807 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
808
809 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
810 bar.paint(&mut canvas);
811
812 assert!(!bar.show_values);
813 }
814
815 #[test]
816 fn test_memory_bar_paint_stacked_with_empty() {
817 use crate::{CellBuffer, DirectTerminalCanvas};
818
819 let mut bar = MemoryBar::new(1000).without_labels();
821 bar.add_segment(MemorySegment::new("Used", 200, Color::RED));
822
823 let mut buffer = CellBuffer::new(80, 10);
824 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
825
826 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
827 bar.paint(&mut canvas);
828
829 assert_eq!(bar.used(), 200);
831 }
832
833 #[test]
834 fn test_memory_bar_event() {
835 let mut bar = MemoryBar::new(1000);
836 let event = Event::Resize {
837 width: 80.0,
838 height: 24.0,
839 };
840 assert!(bar.event(&event).is_none());
841 }
842
843 #[test]
844 fn test_memory_bar_children() {
845 let bar = MemoryBar::new(1000);
846 assert!(bar.children().is_empty());
847 }
848
849 #[test]
850 fn test_memory_bar_children_mut() {
851 let mut bar = MemoryBar::new(1000);
852 assert!(bar.children_mut().is_empty());
853 }
854
855 #[test]
856 fn test_memory_bar_type_id() {
857 let bar = MemoryBar::new(1000);
858 let tid = Widget::type_id(&bar);
859 assert_eq!(tid, TypeId::of::<MemoryBar>());
860 }
861
862 #[test]
863 fn test_memory_bar_brick_name() {
864 let bar = MemoryBar::new(1000);
865 assert_eq!(bar.brick_name(), "memory_bar");
866 }
867
868 #[test]
869 fn test_memory_bar_assertions() {
870 let bar = MemoryBar::new(1000);
871 let assertions = bar.assertions();
872 assert!(!assertions.is_empty());
873 }
874
875 #[test]
876 fn test_memory_bar_budget() {
877 let bar = MemoryBar::new(1000);
878 let budget = bar.budget();
879 assert!(budget.layout_ms > 0);
880 }
881
882 #[test]
883 fn test_memory_bar_to_html() {
884 let bar = MemoryBar::new(1000);
885 assert!(bar.to_html().is_empty());
886 }
887
888 #[test]
889 fn test_memory_bar_to_css() {
890 let bar = MemoryBar::new(1000);
891 assert!(bar.to_css().is_empty());
892 }
893
894 #[test]
895 fn test_memory_segment_clone() {
896 let seg = MemorySegment::new("Test", 1000, Color::GREEN);
897 let cloned = seg.clone();
898 assert_eq!(cloned.name, seg.name);
899 assert_eq!(cloned.bytes, seg.bytes);
900 }
901
902 #[test]
903 fn test_memory_bar_clone() {
904 let mut bar = MemoryBar::new(1000);
905 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
906 let cloned = bar.clone();
907 assert_eq!(cloned.total(), bar.total());
908 assert_eq!(cloned.segments.len(), bar.segments.len());
909 }
910
911 #[test]
912 fn test_memory_segment_debug() {
913 let seg = MemorySegment::new("Test", 1000, Color::GREEN);
914 let debug = format!("{seg:?}");
915 assert!(debug.contains("Test"));
916 assert!(debug.contains("1000"));
917 }
918
919 #[test]
920 fn test_memory_bar_debug() {
921 let bar = MemoryBar::new(1000);
922 let debug = format!("{bar:?}");
923 assert!(debug.contains("1000"));
924 }
925
926 #[test]
929 fn test_huge_pages_new() {
930 let hp = HugePages::new(512, 256, 0, 2048); assert_eq!(hp.total, 512);
932 assert_eq!(hp.free, 256);
933 assert_eq!(hp.reserved, 0);
934 assert_eq!(hp.page_size_kb, 2048);
935 }
936
937 #[test]
938 fn test_huge_pages_used() {
939 let hp = HugePages::new(512, 256, 0, 2048);
940 assert_eq!(hp.used(), 256); }
942
943 #[test]
944 fn test_huge_pages_used_bytes() {
945 let hp = HugePages::new(512, 256, 0, 2048); assert_eq!(hp.used_bytes(), 256 * 2048 * 1024);
948 }
949
950 #[test]
951 fn test_huge_pages_total_bytes() {
952 let hp = HugePages::new(512, 256, 0, 2048);
953 assert_eq!(hp.total_bytes(), 512 * 2048 * 1024);
954 }
955
956 #[test]
957 fn test_huge_pages_usage_percent() {
958 let hp = HugePages::new(100, 50, 0, 2048);
959 assert!((hp.usage_percent() - 50.0).abs() < 0.01);
960 }
961
962 #[test]
963 fn test_huge_pages_usage_percent_zero_total() {
964 let hp = HugePages::new(0, 0, 0, 2048);
965 assert_eq!(hp.usage_percent(), 0.0);
966 }
967
968 #[test]
969 fn test_huge_pages_is_configured() {
970 let configured = HugePages::new(512, 256, 0, 2048);
971 let not_configured = HugePages::new(0, 0, 0, 2048);
972
973 assert!(configured.is_configured());
974 assert!(!not_configured.is_configured());
975 }
976
977 #[test]
978 fn test_huge_pages_to_display_string() {
979 let hp = HugePages::new(512, 256, 0, 2048); assert_eq!(hp.to_display_string(), "256/512 2M");
981 }
982
983 #[test]
984 fn test_huge_pages_to_display_string_1g_pages() {
985 let hp = HugePages::new(8, 4, 0, 1024 * 1024); assert_eq!(hp.to_display_string(), "4/8 1G");
987 }
988
989 #[test]
990 fn test_huge_pages_to_display_string_not_configured() {
991 let hp = HugePages::new(0, 0, 0, 2048);
992 assert_eq!(hp.to_display_string(), "HugePages: not configured");
993 }
994
995 #[test]
996 fn test_huge_pages_default() {
997 let hp = HugePages::default();
998 assert_eq!(hp.total, 0);
999 assert_eq!(hp.free, 0);
1000 assert!(!hp.is_configured());
1001 }
1002
1003 #[test]
1004 fn test_huge_pages_clone() {
1005 let hp = HugePages::new(512, 256, 32, 2048);
1006 let cloned = hp.clone();
1007 assert_eq!(cloned.total, hp.total);
1008 assert_eq!(cloned.free, hp.free);
1009 assert_eq!(cloned.reserved, hp.reserved);
1010 }
1011
1012 #[test]
1013 fn test_huge_pages_debug() {
1014 let hp = HugePages::new(512, 256, 0, 2048);
1015 let debug = format!("{hp:?}");
1016 assert!(debug.contains("512"));
1017 assert!(debug.contains("256"));
1018 }
1019
1020 #[test]
1021 fn test_memory_bar_with_huge_pages() {
1022 let hp = HugePages::new(512, 256, 0, 2048);
1023 let bar = MemoryBar::new(1024 * 1024 * 1024).with_huge_pages(hp);
1024
1025 assert!(bar.has_huge_pages());
1026 assert!(bar.show_huge_pages);
1027 assert!(bar.huge_pages().is_some());
1028 }
1029
1030 #[test]
1031 fn test_memory_bar_set_huge_pages() {
1032 let mut bar = MemoryBar::new(1024 * 1024 * 1024);
1033 assert!(!bar.has_huge_pages());
1034
1035 bar.set_huge_pages(HugePages::new(512, 256, 0, 2048));
1036 assert!(bar.huge_pages().is_some());
1037 }
1038
1039 #[test]
1040 fn test_memory_bar_huge_pages_show_toggle() {
1041 let hp = HugePages::new(512, 256, 0, 2048);
1042 let bar = MemoryBar::new(1024 * 1024 * 1024)
1043 .with_huge_pages(hp)
1044 .show_huge_pages(false);
1045
1046 assert!(!bar.show_huge_pages);
1047 }
1048
1049 #[test]
1050 fn test_memory_bar_measure_with_huge_pages() {
1051 let hp = HugePages::new(512, 256, 0, 2048);
1052 let mut bar = MemoryBar::new(1000).with_huge_pages(hp);
1053 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
1054 bar.add_segment(MemorySegment::new("Cached", 300, Color::YELLOW));
1055
1056 let constraints = Constraints::new(0.0, 100.0, 0.0, 50.0);
1057 let size = bar.measure(constraints);
1058
1059 assert_eq!(size.height, 3.0);
1061 }
1062
1063 #[test]
1064 fn test_memory_bar_measure_huge_pages_disabled() {
1065 let hp = HugePages::new(512, 256, 0, 2048);
1066 let mut bar = MemoryBar::new(1000)
1067 .with_huge_pages(hp)
1068 .show_huge_pages(false);
1069 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
1070
1071 let constraints = Constraints::new(0.0, 100.0, 0.0, 50.0);
1072 let size = bar.measure(constraints);
1073
1074 assert_eq!(size.height, 1.0);
1076 }
1077
1078 #[test]
1079 fn test_memory_bar_paint_with_huge_pages() {
1080 use crate::{CellBuffer, DirectTerminalCanvas};
1081
1082 let hp = HugePages::new(512, 256, 0, 2048);
1083 let mut bar = MemoryBar::new(1000).with_huge_pages(hp);
1084 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
1085
1086 let mut buffer = CellBuffer::new(80, 10);
1087 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1088
1089 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
1090 bar.paint(&mut canvas);
1091
1092 assert!(bar.has_huge_pages());
1094 }
1095
1096 #[test]
1097 fn test_memory_bar_paint_huge_pages_no_labels() {
1098 use crate::{CellBuffer, DirectTerminalCanvas};
1099
1100 let hp = HugePages::new(512, 256, 0, 2048);
1101 let mut bar = MemoryBar::new(1000).with_huge_pages(hp).without_labels();
1102 bar.add_segment(MemorySegment::new("Used", 500, Color::RED));
1103
1104 let mut buffer = CellBuffer::new(80, 10);
1105 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
1106
1107 bar.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
1108 bar.paint(&mut canvas);
1109 }
1110
1111 #[test]
1112 fn test_memory_bar_has_huge_pages_not_configured() {
1113 let hp = HugePages::new(0, 0, 0, 2048); let bar = MemoryBar::new(1000).with_huge_pages(hp);
1115
1116 assert!(!bar.has_huge_pages());
1118 }
1119
1120 #[test]
1121 fn test_huge_pages_small_page_size() {
1122 let hp = HugePages::new(1000, 500, 0, 64); assert_eq!(hp.to_display_string(), "500/1000 64K");
1124 }
1125}