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 gondola_core::prelude::*;
35
36//use tof_dataclasses::packets::{
37//  TofPacket,
38//  PacketType,
39//};
40//use tof_dataclasses::monitoring::CPUMoniData;
41//use tof_dataclasses::errors::SerializationError;
42//use tof_dataclasses::alerts::TofAlert;
43
44use crate::colors::ColorTheme;
45use crate::widgets::timeseries;
46
47//pub const LG_LINE_HORIZONTAL : &str = "◉";
48//pub const LG_LINE_HORIZONTAL : &str = "▥";
49pub const LG_LINE_HORIZONTAL : &str = "░";
50pub const LG_LINE: Set = Set {
51  vertical         : THICK_VERTICAL,
52  //horizontal       : THICK_HORIZONTAL,
53  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, // disk usage in per cent
72  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    // check if the alerts are active
90    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;// CPUMoniData::new();
130    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    // in this case we can check in with the alert system
168    if self.alerts_active {
169      match self.alerts.lock() {
170        Ok(mut al) => {
171          // we can work with mtb relevant alerts here
172          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; // this should be an 
249                                  // alert color
250    } 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      //.use_unicode(true)
270      .label(label_str)
271      //.line_set(symbols::line::THICK)  // THICK
272      .line_set(LG_LINE)
273      //.percent(self.disk_usage as u16);
274      .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}