1use crate::compute_block::{CpuGovernor, FrequencyScalingState};
14use crate::theme::Gradient;
15use presentar_core::{
16 Brick, BrickAssertion, BrickBudget, BrickVerification, Canvas, Constraints, Event,
17 LayoutResult, Point, Rect, Size, TextStyle, TypeId, Widget,
18};
19use std::any::Any;
20use std::time::Duration;
21
22const METER_CHARS: [char; 9] = [' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
24
25const FREQ_INDICATORS: [char; 5] = ['⚡', '↑', '→', '↓', '·'];
27
28#[derive(Debug, Clone)]
30#[allow(clippy::struct_excessive_bools)]
31pub struct CpuGrid {
32 pub core_usage: Vec<f64>,
34 gradient: Gradient,
36 columns: Option<usize>,
38 show_labels: bool,
40 show_percentages: bool,
42 compact: bool,
44 bounds: Rect,
46 frequencies: Option<Vec<u32>>,
48 max_frequencies: Option<Vec<u32>>,
50 governor: Option<CpuGovernor>,
52 show_freq_indicators: bool,
54}
55
56impl Default for CpuGrid {
57 fn default() -> Self {
58 Self::new(vec![])
59 }
60}
61
62impl CpuGrid {
63 #[must_use]
65 pub fn new(core_usage: Vec<f64>) -> Self {
66 Self {
67 core_usage,
68 gradient: Gradient::from_hex(&["#7aa2f7", "#e0af68", "#f7768e"]), columns: None,
70 show_labels: true,
71 show_percentages: false,
72 compact: false,
73 bounds: Rect::default(),
74 frequencies: None,
75 max_frequencies: None,
76 governor: None,
77 show_freq_indicators: false,
78 }
79 }
80
81 #[must_use]
83 pub fn with_gradient(mut self, gradient: Gradient) -> Self {
84 self.gradient = gradient;
85 self
86 }
87
88 #[must_use]
90 pub fn with_columns(mut self, cols: usize) -> Self {
91 debug_assert!(cols > 0, "column count must be positive");
92 self.columns = Some(cols);
93 self
94 }
95
96 #[must_use]
98 pub fn compact(mut self) -> Self {
99 self.compact = true;
100 self
101 }
102
103 #[must_use]
105 pub fn without_labels(mut self) -> Self {
106 self.show_labels = false;
107 self
108 }
109
110 #[must_use]
113 pub fn with_percentages(mut self) -> Self {
114 self.show_percentages = true;
115 self
116 }
117
118 #[must_use]
128 pub fn with_freq_indicators(mut self) -> Self {
129 self.show_freq_indicators = true;
130 self
131 }
132
133 #[must_use]
137 pub fn with_frequencies(mut self, current: Vec<u32>, max: Vec<u32>) -> Self {
138 debug_assert_eq!(
140 current.len(),
141 max.len(),
142 "current and max frequencies must have same length"
143 );
144 self.frequencies = Some(current);
145 self.max_frequencies = Some(max);
146 self
147 }
148
149 #[must_use]
151 pub fn with_governor(mut self, governor: CpuGovernor) -> Self {
152 self.governor = Some(governor);
153 self
154 }
155
156 pub fn set_usage(&mut self, usage: Vec<f64>) {
158 self.core_usage = usage;
159 }
160
161 pub fn set_frequencies(&mut self, current: Vec<u32>, max: Vec<u32>) {
163 self.frequencies = Some(current);
164 self.max_frequencies = Some(max);
165 }
166
167 pub fn set_governor(&mut self, governor: CpuGovernor) {
169 self.governor = Some(governor);
170 }
171
172 #[must_use]
174 pub fn governor(&self) -> Option<&CpuGovernor> {
175 self.governor.as_ref()
176 }
177
178 #[must_use]
180 pub fn governor_display(&self) -> &'static str {
181 self.governor.as_ref().map_or("", CpuGovernor::as_str)
182 }
183
184 #[must_use]
186 pub fn core_count(&self) -> usize {
187 self.core_usage.len()
188 }
189
190 fn optimal_grid(&self, max_width: usize) -> (usize, usize) {
192 let count = self.core_usage.len();
193 if count == 0 {
194 return (0, 0);
195 }
196
197 let cell_width = self.cell_width();
198
199 let max_cols = (max_width / cell_width).max(1);
200 let cols = self.columns.unwrap_or_else(|| {
201 let sqrt = (count as f64).sqrt().ceil() as usize;
203 sqrt.min(max_cols).max(1)
204 });
205
206 let rows = count.div_ceil(cols);
207 (cols, rows)
208 }
209
210 fn cell_width(&self) -> usize {
212 let freq_extra = usize::from(self.show_freq_indicators);
213
214 if self.show_percentages {
215 if self.compact {
217 7 + freq_extra
218 } else {
219 8 + freq_extra
220 }
221 } else if self.show_labels {
222 if self.compact {
224 4 + freq_extra
225 } else {
226 5 + freq_extra
227 }
228 } else if self.compact {
229 2 + freq_extra
230 } else {
231 3 + freq_extra
232 }
233 }
234
235 fn meter_char(pct: f64) -> char {
237 let idx = ((pct / 100.0) * 8.0).round() as usize;
238 METER_CHARS[idx.min(8)]
239 }
240
241 fn freq_scaling_state(&self, core_idx: usize) -> Option<FrequencyScalingState> {
243 let frequencies = self.frequencies.as_ref()?;
244 let max_frequencies = self.max_frequencies.as_ref()?;
245
246 let current = *frequencies.get(core_idx)?;
247 let max = *max_frequencies.get(core_idx)?;
248
249 if max == 0 {
250 return Some(FrequencyScalingState::Idle);
251 }
252
253 let ratio = current as f64 / max as f64;
254
255 Some(if ratio > 1.0 {
256 FrequencyScalingState::Turbo
257 } else if ratio >= 0.8 {
258 FrequencyScalingState::High
259 } else if ratio >= 0.5 {
260 FrequencyScalingState::Normal
261 } else if ratio >= 0.2 {
262 FrequencyScalingState::Scaled
263 } else {
264 FrequencyScalingState::Idle
265 })
266 }
267
268 fn freq_indicator(&self, core_idx: usize) -> Option<char> {
270 if !self.show_freq_indicators {
271 return None;
272 }
273
274 self.freq_scaling_state(core_idx).map(|state| match state {
275 FrequencyScalingState::Turbo => FREQ_INDICATORS[0], FrequencyScalingState::High => FREQ_INDICATORS[1], FrequencyScalingState::Normal => FREQ_INDICATORS[2], FrequencyScalingState::Scaled => FREQ_INDICATORS[3], FrequencyScalingState::Idle => FREQ_INDICATORS[4], })
281 }
282
283 #[must_use]
285 pub fn avg_frequency_ghz(&self) -> Option<f64> {
286 let frequencies = self.frequencies.as_ref()?;
287 if frequencies.is_empty() {
288 return None;
289 }
290 let sum: u64 = frequencies.iter().map(|&f| f as u64).sum();
291 Some(sum as f64 / frequencies.len() as f64 / 1000.0)
292 }
293}
294
295impl Brick for CpuGrid {
296 fn brick_name(&self) -> &'static str {
297 "cpu_grid"
298 }
299
300 fn assertions(&self) -> &[BrickAssertion] {
301 static ASSERTIONS: &[BrickAssertion] = &[BrickAssertion::max_latency_ms(16)];
302 ASSERTIONS
303 }
304
305 fn budget(&self) -> BrickBudget {
306 BrickBudget::uniform(16)
307 }
308
309 fn verify(&self) -> BrickVerification {
310 BrickVerification {
311 passed: self.assertions().to_vec(),
312 failed: vec![],
313 verification_time: Duration::from_micros(10),
314 }
315 }
316
317 fn to_html(&self) -> String {
318 String::new()
319 }
320
321 fn to_css(&self) -> String {
322 String::new()
323 }
324}
325
326impl Widget for CpuGrid {
327 fn type_id(&self) -> TypeId {
328 TypeId::of::<Self>()
329 }
330
331 fn measure(&self, constraints: Constraints) -> Size {
332 let max_width = constraints.max_width as usize;
333 let (cols, rows) = self.optimal_grid(max_width);
334
335 let cell_width = self.cell_width() as f32;
336
337 let width = (cols as f32 * cell_width).min(constraints.max_width);
338 let height = (rows as f32).min(constraints.max_height);
339
340 constraints.constrain(Size::new(width, height))
341 }
342
343 fn layout(&mut self, bounds: Rect) -> LayoutResult {
344 self.bounds = bounds;
345 LayoutResult {
346 size: Size::new(bounds.width, bounds.height),
347 }
348 }
349
350 fn paint(&self, canvas: &mut dyn Canvas) {
351 if self.core_usage.is_empty() {
352 return;
353 }
354
355 let (cols, _rows) = self.optimal_grid(self.bounds.width as usize);
356 if cols == 0 {
357 return;
358 }
359
360 let cell_width = self.cell_width() as f32;
361
362 for (i, &usage) in self.core_usage.iter().enumerate() {
363 let col = i % cols;
364 let row = i / cols;
365
366 let x = self.bounds.x + col as f32 * cell_width;
367 let y = self.bounds.y + row as f32;
368
369 if y >= self.bounds.y + self.bounds.height {
371 break;
372 }
373
374 let usage_clamped = usage.clamp(0.0, 100.0);
375 let color = self.gradient.for_percent(usage_clamped);
376
377 let style = TextStyle {
378 color,
379 ..Default::default()
380 };
381
382 let freq_char = self.freq_indicator(i).unwrap_or(' ');
384 let freq_suffix = if self.show_freq_indicators {
385 freq_char.to_string()
386 } else {
387 String::new()
388 };
389
390 if self.show_percentages {
391 let label = if self.compact {
393 format!("{i:2}{usage_clamped:3.0}%{freq_suffix}")
394 } else {
395 format!("{i:2} {usage_clamped:3.0}%{freq_suffix}")
396 };
397 canvas.draw_text(&label, Point::new(x, y), &style);
398 } else if self.show_labels {
399 let meter = Self::meter_char(usage_clamped);
401 let label = if self.compact {
402 format!("{i:2}{meter}{freq_suffix}")
403 } else {
404 format!("{i:2} {meter}{freq_suffix}")
405 };
406 canvas.draw_text(&label, Point::new(x, y), &style);
407 } else {
408 let meter = Self::meter_char(usage_clamped);
409 let label = format!("{meter}{freq_suffix}");
410 canvas.draw_text(&label, Point::new(x, y), &style);
411 }
412 }
413 }
414
415 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
416 None
417 }
418
419 fn children(&self) -> &[Box<dyn Widget>] {
420 &[]
421 }
422
423 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
424 &mut []
425 }
426}
427
428#[cfg(test)]
429#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
430mod tests {
431 use super::*;
432
433 #[test]
434 fn test_cpu_grid_new() {
435 let grid = CpuGrid::new(vec![10.0, 50.0, 90.0]);
436 assert_eq!(grid.core_count(), 3);
437 }
438
439 #[test]
440 fn test_cpu_grid_empty() {
441 let grid = CpuGrid::new(vec![]);
442 assert_eq!(grid.core_count(), 0);
443 }
444
445 #[test]
446 fn test_cpu_grid_with_columns() {
447 let grid = CpuGrid::new(vec![10.0; 48]).with_columns(8);
448 assert_eq!(grid.columns, Some(8));
449 }
450
451 #[test]
452 fn test_cpu_grid_compact() {
453 let grid = CpuGrid::new(vec![10.0; 48]).compact();
454 assert!(grid.compact);
455 }
456
457 #[test]
458 fn test_cpu_grid_without_labels() {
459 let grid = CpuGrid::new(vec![10.0; 48]).without_labels();
460 assert!(!grid.show_labels);
461 }
462
463 #[test]
464 fn test_meter_char() {
465 assert_eq!(CpuGrid::meter_char(0.0), ' ');
466 assert_eq!(CpuGrid::meter_char(50.0), '▄');
467 assert_eq!(CpuGrid::meter_char(100.0), '█');
468 }
469
470 #[test]
471 fn test_optimal_grid_48_cores() {
472 let grid = CpuGrid::new(vec![10.0; 48]);
473 let (cols, rows) = grid.optimal_grid(80);
474 assert!(cols > 0);
475 assert!(rows > 0);
476 assert!(cols * rows >= 48);
477 }
478
479 #[test]
480 fn test_optimal_grid_explicit_columns() {
481 let grid = CpuGrid::new(vec![10.0; 48]).with_columns(8);
482 let (cols, rows) = grid.optimal_grid(80);
483 assert_eq!(cols, 8);
484 assert_eq!(rows, 6);
485 }
486
487 #[test]
488 fn test_cpu_grid_measure() {
489 let grid = CpuGrid::new(vec![10.0; 8]);
490 let size = grid.measure(Constraints::new(0.0, 80.0, 0.0, 20.0));
491 assert!(size.width > 0.0);
492 assert!(size.height > 0.0);
493 }
494
495 #[test]
496 fn test_cpu_grid_set_usage() {
497 let mut grid = CpuGrid::new(vec![]);
498 assert_eq!(grid.core_count(), 0);
499 grid.set_usage(vec![10.0, 20.0, 30.0]);
500 assert_eq!(grid.core_count(), 3);
501 }
502
503 #[test]
504 fn test_cpu_grid_verify() {
505 let grid = CpuGrid::new(vec![50.0; 8]);
506 let v = grid.verify();
507 assert!(v.is_valid());
508 }
509
510 #[test]
511 fn test_cpu_grid_type_id() {
512 let grid = CpuGrid::new(vec![]);
513 let _ = Widget::type_id(&grid);
514 }
515
516 #[test]
517 fn test_cpu_grid_default() {
518 let grid = CpuGrid::default();
519 assert_eq!(grid.core_count(), 0);
520 }
521
522 #[test]
523 fn test_cpu_grid_brick_name() {
524 let grid = CpuGrid::new(vec![]);
525 assert_eq!(grid.brick_name(), "cpu_grid");
526 }
527
528 #[test]
529 fn test_cpu_grid_layout() {
530 let mut grid = CpuGrid::new(vec![50.0; 8]);
531 let result = grid.layout(Rect::new(0.0, 0.0, 80.0, 10.0));
532 assert_eq!(result.size.width, 80.0);
533 assert_eq!(result.size.height, 10.0);
534 }
535
536 #[test]
537 fn test_cpu_grid_event() {
538 let mut grid = CpuGrid::new(vec![50.0]);
539 let event = Event::Resize {
540 width: 80.0,
541 height: 24.0,
542 };
543 assert!(grid.event(&event).is_none());
544 }
545
546 #[test]
547 fn test_cpu_grid_children() {
548 let grid = CpuGrid::new(vec![50.0]);
549 assert!(grid.children().is_empty());
550 }
551
552 #[test]
553 fn test_cpu_grid_children_mut() {
554 let mut grid = CpuGrid::new(vec![50.0]);
555 assert!(grid.children_mut().is_empty());
556 }
557
558 #[test]
559 fn test_cpu_grid_assertions() {
560 let grid = CpuGrid::new(vec![50.0]);
561 assert!(!grid.assertions().is_empty());
562 }
563
564 #[test]
565 fn test_cpu_grid_budget() {
566 let grid = CpuGrid::new(vec![50.0]);
567 let budget = grid.budget();
568 assert!(budget.layout_ms > 0);
569 }
570
571 #[test]
572 fn test_cpu_grid_to_html() {
573 let grid = CpuGrid::new(vec![50.0]);
574 assert!(grid.to_html().is_empty());
575 }
576
577 #[test]
578 fn test_cpu_grid_to_css() {
579 let grid = CpuGrid::new(vec![50.0]);
580 assert!(grid.to_css().is_empty());
581 }
582
583 #[test]
584 fn test_cpu_grid_clone() {
585 let grid = CpuGrid::new(vec![50.0, 75.0]).with_columns(4);
586 let cloned = grid.clone();
587 assert_eq!(cloned.core_usage.len(), grid.core_usage.len());
588 assert_eq!(cloned.columns, grid.columns);
589 }
590
591 #[test]
592 fn test_cpu_grid_debug() {
593 let grid = CpuGrid::new(vec![50.0]);
594 let debug = format!("{grid:?}");
595 assert!(debug.contains("CpuGrid"));
596 }
597
598 #[test]
599 fn test_cpu_grid_with_gradient() {
600 let gradient = Gradient::from_hex(&["#00FF00", "#FF0000"]);
601 let grid = CpuGrid::new(vec![50.0]).with_gradient(gradient);
602 assert_eq!(grid.core_count(), 1);
604 }
605
606 #[test]
607 fn test_cpu_grid_paint() {
608 use crate::{CellBuffer, DirectTerminalCanvas};
609
610 let mut grid = CpuGrid::new(vec![10.0, 50.0, 90.0]);
611 let mut buffer = CellBuffer::new(40, 10);
612 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
613
614 grid.layout(Rect::new(0.0, 0.0, 40.0, 10.0));
615 grid.paint(&mut canvas);
616 }
617
618 #[test]
619 fn test_cpu_grid_paint_empty() {
620 use crate::{CellBuffer, DirectTerminalCanvas};
621
622 let grid = CpuGrid::new(vec![]);
623 let mut buffer = CellBuffer::new(40, 10);
624 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
625
626 grid.paint(&mut canvas);
627 }
628
629 #[test]
630 fn test_cpu_grid_paint_compact() {
631 use crate::{CellBuffer, DirectTerminalCanvas};
632
633 let mut grid = CpuGrid::new(vec![10.0, 50.0, 90.0]).compact();
634 let mut buffer = CellBuffer::new(40, 10);
635 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
636
637 grid.layout(Rect::new(0.0, 0.0, 40.0, 10.0));
638 grid.paint(&mut canvas);
639 }
640
641 #[test]
642 fn test_cpu_grid_paint_without_labels() {
643 use crate::{CellBuffer, DirectTerminalCanvas};
644
645 let mut grid = CpuGrid::new(vec![10.0, 50.0, 90.0]).without_labels();
646 let mut buffer = CellBuffer::new(40, 10);
647 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
648
649 grid.layout(Rect::new(0.0, 0.0, 40.0, 10.0));
650 grid.paint(&mut canvas);
651 }
652
653 #[test]
654 fn test_cpu_grid_paint_compact_without_labels() {
655 use crate::{CellBuffer, DirectTerminalCanvas};
656
657 let mut grid = CpuGrid::new(vec![10.0, 50.0, 90.0])
658 .compact()
659 .without_labels();
660 let mut buffer = CellBuffer::new(40, 10);
661 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
662
663 grid.layout(Rect::new(0.0, 0.0, 40.0, 10.0));
664 grid.paint(&mut canvas);
665 }
666
667 #[test]
668 fn test_cpu_grid_optimal_grid_empty() {
669 let grid = CpuGrid::new(vec![]);
670 let (cols, rows) = grid.optimal_grid(80);
671 assert_eq!(cols, 0);
672 assert_eq!(rows, 0);
673 }
674
675 #[test]
676 fn test_cpu_grid_measure_compact() {
677 let grid = CpuGrid::new(vec![10.0; 8]).compact();
678 let size = grid.measure(Constraints::new(0.0, 80.0, 0.0, 20.0));
679 assert!(size.width > 0.0);
680 }
681
682 #[test]
683 fn test_cpu_grid_measure_without_labels() {
684 let grid = CpuGrid::new(vec![10.0; 8]).without_labels();
685 let size = grid.measure(Constraints::new(0.0, 80.0, 0.0, 20.0));
686 assert!(size.width > 0.0);
687 }
688
689 #[test]
690 fn test_cpu_grid_measure_compact_without_labels() {
691 let grid = CpuGrid::new(vec![10.0; 8]).compact().without_labels();
692 let size = grid.measure(Constraints::new(0.0, 80.0, 0.0, 20.0));
693 assert!(size.width > 0.0);
694 }
695
696 #[test]
697 fn test_cpu_grid_meter_char_edge_cases() {
698 assert_eq!(CpuGrid::meter_char(-10.0), ' ');
699 assert_eq!(CpuGrid::meter_char(150.0), '█');
700 assert_eq!(CpuGrid::meter_char(12.5), '▁');
701 assert_eq!(CpuGrid::meter_char(25.0), '▂');
702 assert_eq!(CpuGrid::meter_char(37.5), '▃');
703 assert_eq!(CpuGrid::meter_char(62.5), '▅');
704 assert_eq!(CpuGrid::meter_char(75.0), '▆');
705 assert_eq!(CpuGrid::meter_char(87.5), '▇');
706 }
707
708 #[test]
711 fn test_cpu_grid_with_frequencies() {
712 let grid = CpuGrid::new(vec![50.0; 4])
713 .with_frequencies(vec![3600, 3200, 2800, 1000], vec![4000, 4000, 4000, 4000]);
714
715 assert!(grid.frequencies.is_some());
716 assert!(grid.max_frequencies.is_some());
717 assert_eq!(grid.frequencies.as_ref().unwrap().len(), 4);
718 }
719
720 #[test]
721 fn test_cpu_grid_with_freq_indicators() {
722 let grid = CpuGrid::new(vec![50.0; 4])
723 .with_frequencies(vec![3600, 3200, 2800, 1000], vec![4000, 4000, 4000, 4000])
724 .with_freq_indicators();
725
726 assert!(grid.show_freq_indicators);
727 }
728
729 #[test]
730 fn test_cpu_grid_freq_scaling_state() {
731 let grid = CpuGrid::new(vec![50.0; 4]).with_frequencies(
732 vec![4200, 3500, 2500, 500], vec![4000, 4000, 4000, 4000], );
735
736 assert_eq!(
738 grid.freq_scaling_state(0),
739 Some(FrequencyScalingState::Turbo)
740 );
741 assert_eq!(
743 grid.freq_scaling_state(1),
744 Some(FrequencyScalingState::High)
745 );
746 assert_eq!(
748 grid.freq_scaling_state(2),
749 Some(FrequencyScalingState::Normal)
750 );
751 assert_eq!(
753 grid.freq_scaling_state(3),
754 Some(FrequencyScalingState::Idle)
755 );
756 }
757
758 #[test]
759 fn test_cpu_grid_freq_scaling_state_scaled() {
760 let grid = CpuGrid::new(vec![50.0; 1]).with_frequencies(vec![1200], vec![4000]);
761 assert_eq!(
763 grid.freq_scaling_state(0),
764 Some(FrequencyScalingState::Scaled)
765 );
766 }
767
768 #[test]
769 fn test_cpu_grid_freq_scaling_state_no_frequencies() {
770 let grid = CpuGrid::new(vec![50.0; 4]);
771 assert_eq!(grid.freq_scaling_state(0), None);
772 }
773
774 #[test]
775 fn test_cpu_grid_freq_scaling_state_zero_max() {
776 let grid = CpuGrid::new(vec![50.0; 1]).with_frequencies(vec![3000], vec![0]);
777 assert_eq!(
778 grid.freq_scaling_state(0),
779 Some(FrequencyScalingState::Idle)
780 );
781 }
782
783 #[test]
784 fn test_cpu_grid_freq_indicator() {
785 let grid = CpuGrid::new(vec![50.0; 4])
786 .with_frequencies(vec![4200, 3500, 2500, 500], vec![4000, 4000, 4000, 4000])
787 .with_freq_indicators();
788
789 assert_eq!(grid.freq_indicator(0), Some('⚡')); assert_eq!(grid.freq_indicator(1), Some('↑')); assert_eq!(grid.freq_indicator(2), Some('→')); assert_eq!(grid.freq_indicator(3), Some('·')); }
794
795 #[test]
796 fn test_cpu_grid_freq_indicator_disabled() {
797 let grid = CpuGrid::new(vec![50.0; 4])
798 .with_frequencies(vec![4200, 3500, 2500, 500], vec![4000, 4000, 4000, 4000]);
799 assert_eq!(grid.freq_indicator(0), None);
801 }
802
803 #[test]
804 fn test_cpu_grid_with_governor() {
805 let grid = CpuGrid::new(vec![50.0; 4]).with_governor(CpuGovernor::Performance);
806 assert_eq!(grid.governor(), Some(&CpuGovernor::Performance));
807 assert_eq!(grid.governor_display(), "performance");
808 }
809
810 #[test]
811 fn test_cpu_grid_governor_powersave() {
812 let grid = CpuGrid::new(vec![50.0; 4]).with_governor(CpuGovernor::Powersave);
813 assert_eq!(grid.governor(), Some(&CpuGovernor::Powersave));
814 assert_eq!(grid.governor_display(), "powersave");
815 }
816
817 #[test]
818 fn test_cpu_grid_governor_schedutil() {
819 let grid = CpuGrid::new(vec![50.0; 4]).with_governor(CpuGovernor::Schedutil);
820 assert_eq!(grid.governor(), Some(&CpuGovernor::Schedutil));
821 assert_eq!(grid.governor_display(), "schedutil");
822 }
823
824 #[test]
825 fn test_cpu_grid_no_governor() {
826 let grid = CpuGrid::new(vec![50.0; 4]);
827 assert_eq!(grid.governor(), None);
828 assert_eq!(grid.governor_display(), "");
829 }
830
831 #[test]
832 fn test_cpu_grid_set_frequencies() {
833 let mut grid = CpuGrid::new(vec![50.0; 4]);
834 assert!(grid.frequencies.is_none());
835
836 grid.set_frequencies(vec![3600, 3200, 2800, 1000], vec![4000, 4000, 4000, 4000]);
837 assert!(grid.frequencies.is_some());
838 assert!(grid.max_frequencies.is_some());
839 }
840
841 #[test]
842 fn test_cpu_grid_set_governor() {
843 let mut grid = CpuGrid::new(vec![50.0; 4]);
844 assert!(grid.governor().is_none());
845
846 grid.set_governor(CpuGovernor::Ondemand);
847 assert_eq!(grid.governor(), Some(&CpuGovernor::Ondemand));
848 }
849
850 #[test]
851 fn test_cpu_grid_avg_frequency_ghz() {
852 let grid = CpuGrid::new(vec![50.0; 4])
853 .with_frequencies(vec![3600, 3200, 2800, 2400], vec![4000, 4000, 4000, 4000]);
854
855 let avg = grid.avg_frequency_ghz().unwrap();
856 assert!((avg - 3.0).abs() < 0.01); }
858
859 #[test]
860 fn test_cpu_grid_avg_frequency_ghz_none() {
861 let grid = CpuGrid::new(vec![50.0; 4]);
862 assert!(grid.avg_frequency_ghz().is_none());
863 }
864
865 #[test]
866 fn test_cpu_grid_avg_frequency_ghz_empty() {
867 let grid = CpuGrid::new(vec![]).with_frequencies(vec![], vec![]);
868 assert!(grid.avg_frequency_ghz().is_none());
869 }
870
871 #[test]
872 fn test_cpu_grid_paint_with_freq_indicators() {
873 use crate::{CellBuffer, DirectTerminalCanvas};
874
875 let mut grid = CpuGrid::new(vec![50.0, 80.0, 20.0, 10.0])
876 .with_frequencies(vec![4200, 3500, 2500, 500], vec![4000, 4000, 4000, 4000])
877 .with_freq_indicators();
878
879 let mut buffer = CellBuffer::new(60, 10);
880 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
881
882 grid.layout(Rect::new(0.0, 0.0, 60.0, 10.0));
883 grid.paint(&mut canvas);
884 }
885
886 #[test]
887 fn test_cpu_grid_paint_with_percentages_and_freq() {
888 use crate::{CellBuffer, DirectTerminalCanvas};
889
890 let mut grid = CpuGrid::new(vec![50.0, 80.0])
891 .with_percentages()
892 .with_frequencies(vec![4200, 3500], vec![4000, 4000])
893 .with_freq_indicators();
894
895 let mut buffer = CellBuffer::new(60, 10);
896 let mut canvas = DirectTerminalCanvas::new(&mut buffer);
897
898 grid.layout(Rect::new(0.0, 0.0, 60.0, 10.0));
899 grid.paint(&mut canvas);
900 }
901
902 #[test]
903 fn test_cpu_grid_cell_width_with_freq_indicators() {
904 let grid_no_freq = CpuGrid::new(vec![50.0; 4]);
905 let grid_with_freq = CpuGrid::new(vec![50.0; 4]).with_freq_indicators();
906
907 assert_eq!(grid_with_freq.cell_width(), grid_no_freq.cell_width() + 1);
909 }
910
911 #[test]
912 fn test_cpu_grid_cell_width_percentages_with_freq() {
913 let grid = CpuGrid::new(vec![50.0; 4])
914 .with_percentages()
915 .with_freq_indicators();
916
917 assert_eq!(grid.cell_width(), 9);
919 }
920
921 #[test]
922 fn test_cpu_grid_governor_all_variants() {
923 assert_eq!(
925 CpuGrid::new(vec![])
926 .with_governor(CpuGovernor::Performance)
927 .governor_display(),
928 "performance"
929 );
930 assert_eq!(
931 CpuGrid::new(vec![])
932 .with_governor(CpuGovernor::Powersave)
933 .governor_display(),
934 "powersave"
935 );
936 assert_eq!(
937 CpuGrid::new(vec![])
938 .with_governor(CpuGovernor::Ondemand)
939 .governor_display(),
940 "ondemand"
941 );
942 assert_eq!(
943 CpuGrid::new(vec![])
944 .with_governor(CpuGovernor::Conservative)
945 .governor_display(),
946 "conservative"
947 );
948 assert_eq!(
949 CpuGrid::new(vec![])
950 .with_governor(CpuGovernor::Schedutil)
951 .governor_display(),
952 "schedutil"
953 );
954 assert_eq!(
955 CpuGrid::new(vec![])
956 .with_governor(CpuGovernor::Userspace)
957 .governor_display(),
958 "userspace"
959 );
960 assert_eq!(
961 CpuGrid::new(vec![])
962 .with_governor(CpuGovernor::Unknown)
963 .governor_display(),
964 "unknown"
965 );
966 }
967}