liftof_tui/tabs/
tab_cpu.rs

1//! Visualize the CPUMoniData packet
2
3use 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    //widgets::Paragraph,
26    style::{Modifier, Color, Style},
27    //text::{Span, Line},
28    widgets::{
29        Block, BorderType, Borders, LineGauge,
30        //List, ListItem, ListState,
31        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
45//pub const LG_LINE_HORIZONTAL : &str = "◉";
46//pub const LG_LINE_HORIZONTAL : &str = "▥";
47pub const LG_LINE_HORIZONTAL : &str = "░";
48pub const LG_LINE: Set = Set {
49  vertical         : THICK_VERTICAL,
50  //horizontal       : THICK_HORIZONTAL,
51  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, // disk usage in per cent
70  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    // check if the alerts are active
88    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;// CPUMoniData::new();
128    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    // in this case we can check in with the alert system
166    if self.alerts_active {
167      match self.alerts.lock() {
168        Ok(mut al) => {
169          // we can work with mtb relevant alerts here
170          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; // this should be an 
247                                  // alert color
248    } 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      //.use_unicode(true)
268      .label(label_str)
269      //.line_set(symbols::line::THICK)  // THICK
270      .line_set(LG_LINE)
271      //.percent(self.disk_usage as u16);
272      .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}