entrenar/monitor/gpu/
render.rs1use super::GpuMetrics;
4
5pub fn render_progress_bar(value: f64, width: usize) -> String {
7 let filled = ((value / 100.0) * width as f64).round() as usize;
8 let filled = filled.min(width);
9 let empty = width - filled;
10
11 let bar: String = std::iter::repeat_n('\u{2588}', filled).collect();
12 let empty_bar: String = std::iter::repeat_n('\u{2591}', empty).collect();
13
14 format!("{bar}{empty_bar}")
15}
16
17pub fn render_sparkline(values: &[u32], max_val: u32) -> String {
19 const CHARS: &[char] = &[
20 '\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}',
21 '\u{2588}',
22 ];
23
24 if values.is_empty() || max_val == 0 {
25 return String::new();
26 }
27
28 values
29 .iter()
30 .map(|&v| {
31 let idx =
32 ((f64::from(v) / f64::from(max_val)) * (CHARS.len() - 1) as f64).round() as usize;
33 CHARS[idx.min(CHARS.len() - 1)]
34 })
35 .collect()
36}
37
38pub fn format_gpu_panel(metrics: &GpuMetrics, width: usize) -> Vec<String> {
40 let bar_width = width.saturating_sub(25);
41
42 vec![
43 format!(
44 "───── GPU {}: {} ─────",
45 metrics.device_id,
46 metrics.name.chars().take(width - 20).collect::<String>()
47 ),
48 format!(
49 "Util: {} {:>3}% │ Temp: {}°C",
50 render_progress_bar(f64::from(metrics.utilization_percent), bar_width),
51 metrics.utilization_percent,
52 metrics.temperature_celsius
53 ),
54 format!(
55 "VRAM: {} {:.1}/{:.1} GB ({:.0}%)",
56 render_progress_bar(metrics.memory_percent(), bar_width),
57 metrics.memory_used_mb as f64 / 1024.0,
58 metrics.memory_total_mb as f64 / 1024.0,
59 metrics.memory_percent()
60 ),
61 format!(
62 "Pow: {} {:.0}W/{:.0}W",
63 render_progress_bar(metrics.power_percent(), bar_width),
64 metrics.power_watts,
65 metrics.power_limit_watts
66 ),
67 ]
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn test_render_progress_bar() {
76 let bar = render_progress_bar(50.0, 10);
77 assert_eq!(bar.chars().filter(|&c| c == '\u{2588}').count(), 5);
78 assert_eq!(bar.chars().filter(|&c| c == '\u{2591}').count(), 5);
79 }
80
81 #[test]
82 fn test_render_progress_bar_full() {
83 let bar = render_progress_bar(100.0, 10);
84 assert_eq!(bar.chars().filter(|&c| c == '\u{2588}').count(), 10);
85 }
86
87 #[test]
88 fn test_render_progress_bar_empty() {
89 let bar = render_progress_bar(0.0, 10);
90 assert_eq!(bar.chars().filter(|&c| c == '\u{2591}').count(), 10);
91 }
92
93 #[test]
94 fn test_render_sparkline() {
95 let sparkline = render_sparkline(&[0, 50, 100], 100);
96 assert_eq!(sparkline.chars().count(), 3);
97 assert!(sparkline.starts_with('\u{2581}'));
98 assert!(sparkline.ends_with('\u{2588}'));
99 }
100
101 #[test]
102 fn test_render_sparkline_empty() {
103 let sparkline = render_sparkline(&[], 100);
104 assert!(sparkline.is_empty());
105 }
106
107 #[test]
108 fn test_format_gpu_panel() {
109 let metrics = GpuMetrics::mock(0);
110 let lines = format_gpu_panel(&metrics, 60);
111 assert!(!lines.is_empty());
112 assert!(lines[0].contains("GPU 0"));
113 }
114
115 #[test]
116 fn test_render_sparkline_max_val_zero() {
117 let sparkline = render_sparkline(&[1, 2, 3], 0);
118 assert!(sparkline.is_empty());
119 }
120
121 #[test]
122 fn test_render_progress_bar_over_100() {
123 let bar = render_progress_bar(150.0, 10);
125 assert_eq!(bar.chars().filter(|&c| c == '\u{2588}').count(), 10);
126 }
127
128 #[test]
129 fn test_render_progress_bar_negative() {
130 let bar = render_progress_bar(-10.0, 10);
132 assert!(bar.chars().filter(|&c| c == '\u{2588}').count() == 0);
134 }
135}
136
137#[cfg(test)]
138mod property_tests {
139 use super::*;
140 use proptest::prelude::*;
141
142 proptest! {
143 #![proptest_config(ProptestConfig::with_cases(200))]
144
145 #[test]
146 fn prop_sparkline_length(values in prop::collection::vec(0u32..100, 0..50)) {
147 let sparkline = render_sparkline(&values, 100);
148 prop_assert_eq!(sparkline.chars().count(), values.len());
149 }
150
151 #[test]
152 fn prop_progress_bar_length(value in 0.0f64..100.0, width in 1usize..50) {
153 let bar = render_progress_bar(value, width);
154 let char_count: usize = bar.chars().count();
155 prop_assert_eq!(char_count, width);
156 }
157 }
158}