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