1use std::time::Instant;
4use std::collections::{
5 VecDeque,
6 HashMap,
7};
8use std::sync::{
9 Arc,
10 Mutex,
11};
12
13use crossbeam_channel::Receiver;
14
15use ratatui::symbols::line::*;
16use ratatui::{
17 Frame,
18 layout::{
19 Alignment,
20 Constraint,
21 Direction,
22 Layout,
23 Rect
24 },
25 style::{Modifier, Color, Style},
27 widgets::{
29 Block, BorderType, Borders, LineGauge,
30 Paragraph},
32};
33
34use gondola_core::prelude::*;
35
36use crate::colors::ColorTheme;
45use crate::widgets::timeseries;
46
47pub const LG_LINE_HORIZONTAL : &str = "░";
50pub const LG_LINE: Set = Set {
51 vertical : THICK_VERTICAL,
52 horizontal : LG_LINE_HORIZONTAL,
54 top_right : THICK_TOP_RIGHT,
55 top_left : THICK_TOP_LEFT,
56 bottom_right : THICK_BOTTOM_RIGHT,
57 bottom_left : THICK_BOTTOM_LEFT,
58 vertical_left : THICK_VERTICAL_LEFT,
59 vertical_right : THICK_VERTICAL_RIGHT,
60 horizontal_down : THICK_HORIZONTAL_DOWN,
61 horizontal_up : THICK_HORIZONTAL_UP,
62 cross : THICK_CROSS,
63};
64
65
66#[derive(Debug, Clone)]
67pub struct CPUTab<'a> {
68 pub theme : ColorTheme,
69 pub freq_queue : Vec<VecDeque<(f64,f64)>>,
70 pub temp_queue : Vec<VecDeque<(f64,f64)>>,
71 pub disk_usage : u8, pub tp_recv : Receiver<TofPacket>,
73 timer : Instant,
74 queue_size : usize,
75 pub last_moni : CPUMoniData,
76 alerts : Arc<Mutex<HashMap<&'a str, TofAlert<'a>>>>,
77 alerts_active : bool,
78 moni_old_check : Instant,
79}
80
81impl CPUTab<'_> {
82
83 pub fn new<'a>(tp_recv : Receiver<TofPacket>,
84 alerts : Arc<Mutex<HashMap<&'a str, TofAlert<'a>>>>,
85 theme : ColorTheme) -> CPUTab<'a> {
86 let queue_size = 1000usize;
87 let mut freq_queue = Vec::<VecDeque::<(f64,f64)>>::with_capacity(4);
88 let mut temp_queue = Vec::<VecDeque::<(f64,f64)>>::with_capacity(4);
89 let mut alerts_active = false;
91 match alerts.lock() {
92 Ok(al) => {
93 if al.len() > 0 {
94 alerts_active = true;
95 info!("Found {} active alerts!", al.len());
96 }
97 }
98 Err(err) => {
99 error!("Unable to lock alert mutex! {err}");
100 }
101 }
102
103 for _core in 0..4 {
104 let core_queue = VecDeque::<(f64,f64)>::with_capacity(queue_size);
105 freq_queue.push(core_queue);
106 }
107 for _core in 0..4 {
108 let core_queue = VecDeque::<(f64,f64)>::with_capacity(queue_size);
109 temp_queue.push(core_queue);
110 }
111
112
113 CPUTab {
114 theme : theme,
115 timer : Instant::now(),
116 freq_queue : freq_queue,
117 temp_queue : temp_queue,
118 disk_usage : 0u8,
119 tp_recv : tp_recv,
120 queue_size : 1000usize,
121 last_moni : CPUMoniData::new(),
122 alerts : alerts,
123 alerts_active : alerts_active,
124 moni_old_check : Instant::now(),
125 }
126 }
127
128 pub fn receive_packet(&mut self) -> Result<(), SerializationError> {
129 let moni : CPUMoniData;let met = self.timer.elapsed().as_secs_f64();
131 match self.tp_recv.try_recv() {
132 Err(err) => {
133 trace!("Can't receive packet! {err}");
134 return Ok(())
135 }
136 Ok(pack) => {
137 trace!("Got next packet {}!", pack);
138 match pack.packet_type {
139 TofPacketType::CPUMoniData => {
140 moni = pack.unpack()?;
141 }
142 _ => {
143 return Ok(());
144 },
145 }
146 }
147 }
148 if moni.disk_usage == u8::MAX {
149 error!("CPUInfo packet only contains error vals!");
150 return Ok(());
151 }
152
153 let temps = moni.get_temps();
154 for core in 0..4 {
155 self.freq_queue[core].push_back((met, moni.cpu_freq[core] as f64));
156 if self.freq_queue[core].len() > self.queue_size {
157 self.freq_queue[core].pop_front();
158 }
159
160 self.temp_queue[core].push_back((met, temps[core] as f64));
161 if self.temp_queue[core].len() > self.queue_size {
162 self.temp_queue[core].pop_front();
163 }
164 }
165 self.disk_usage = moni.disk_usage;
166 self.last_moni = moni;
167 if self.alerts_active {
169 match self.alerts.lock() {
170 Ok(mut al) => {
171 al.get_mut("cpu_core0_temp").unwrap().trigger(temps[0] as f32);
173 al.get_mut("cpu_core1_temp").unwrap().trigger(temps[1] as f32);
174 al.get_mut("cpu_disk").unwrap() .trigger(moni.disk_usage as f32);
175 al.get_mut("cpu_hk_too_old").unwrap().trigger(self.moni_old_check.elapsed().as_secs() as f32);
176 },
177 Err(err) => error!("Unable to lock global alerts! {err}"),
178 }
179 }
180 self.moni_old_check = Instant::now();
181 Ok(())
182 }
183
184 pub fn render(&mut self, main_window : &Rect, frame : &mut Frame) {
185 let main_chunks = Layout::default()
186 .direction(Direction::Horizontal)
187 .constraints(
188 [Constraint::Percentage(30), Constraint::Percentage(70)].as_ref(),
189 )
190 .split(*main_window);
191 let main_cols0 = Layout::default()
192 .direction(Direction::Vertical)
193 .constraints(
194 [Constraint::Percentage(90),
195 Constraint::Percentage(10)].as_ref(),
196 )
197 .split(main_chunks[0]);
198
199 let graph_chunks = Layout::default()
200 .direction(Direction::Horizontal)
201 .constraints(
202 [Constraint::Percentage(50),
203 Constraint::Percentage(50)].as_ref(),
204 )
205 .split(main_chunks[1]).to_vec();
206
207
208 let freq_chunks = Layout::default()
209 .direction(Direction::Vertical)
210 .constraints(
211 [Constraint::Percentage(25),
212 Constraint::Percentage(25),
213 Constraint::Percentage(26),
214 Constraint::Percentage(25)].as_ref(),
215 )
216 .split(graph_chunks[0]).to_vec();
217
218 let temp_chunks = Layout::default()
219 .direction(Direction::Vertical)
220 .constraints(
221 [Constraint::Percentage(25),
222 Constraint::Percentage(25),
223 Constraint::Percentage(26),
224 Constraint::Percentage(25)].as_ref(),
225 )
226 .split(graph_chunks[1]).to_vec();
227
228
229 let info_view_str = format!("{}", self.last_moni);
230
231 let info_view = Paragraph::new(info_view_str)
232 .style(self.theme.style())
233 .alignment(Alignment::Left)
234 .block(
235 Block::default()
236 .borders(Borders::ALL)
237 .style(self.theme.style())
238 .title("Info")
239 .border_type(BorderType::Rounded),
240 );
241 let mut ratio = self.disk_usage as f64/100.0;
242 if ratio > 1.00 {
243 error!("TOF CPU disk filled to more than 100%");
244 ratio = 0.0;
245 }
246 let fg_color : Color;
247 if self.disk_usage > 80 {
248 fg_color = Color::Red; } else {
251 fg_color = self.theme.hc;
252 }
253
254 let label_str = format!("Disc usage {} %", self.disk_usage);
255 let du_gauge = LineGauge::default()
256 .block(
257 Block::default()
258 .borders(Borders::ALL)
259 .style(self.theme.style())
260 .title("Disk usage (/tpool)")
261 .border_type(BorderType::Rounded)
262 )
263 .filled_style(
264 Style::default()
265 .fg(fg_color)
266 .bg(self.theme.bg1)
267 .add_modifier(Modifier::BOLD)
268 )
269 .label(label_str)
271 .line_set(LG_LINE)
273 .ratio(ratio);
275 for core in 0..4 {
276 let label = format!("Core{} freq. [GHz]", core);
277 let core_theme = self.theme.clone();
278 let mut freq_ts_data = VecDeque::from(self.freq_queue[core].clone());
279 let freq_ts = timeseries(&mut freq_ts_data,
280 label.clone(),
281 label.clone(),
282 &core_theme );
283 frame.render_widget(freq_ts,freq_chunks[core]);
284 }
285
286 let temp_labels = vec!["Core0 T [\u{00B0}C]",
287 "Core1 T [\u{00B0}C]",
288 "CPU T [\u{00B0}C]",
289 "MB T [\u{00B0}C]"];
290
291 for core in 0..4 {
292 let label = temp_labels[core].to_string();
293 let core_theme = self.theme.clone();
294 let mut temp_ts_data = VecDeque::from(self.temp_queue[core].clone());
295 let temp_ts = timeseries(&mut temp_ts_data,
296 label.clone(),
297 label.clone(),
298 &core_theme );
299 frame.render_widget(temp_ts,temp_chunks[core]);
300 }
301 frame.render_widget(info_view, main_cols0[0]);
302 frame.render_widget(du_gauge, main_cols0[1]);
303 }
304}