liftof_tui/tabs/
tab_heartbeats.rs

1//! Heartbeats are a special kind of monitoring
2//! which monitor the individual main threads of 
3//! liftof-cc and are sent in regular intervals
4//! 
5//! The main threads are for the Master Trigger,
6//! Event Builder and the Data sender.
7
8//use chrono::Utc;
9use std::collections::VecDeque; 
10
11use crossbeam_channel::Receiver;
12
13use ratatui::prelude::*;
14use ratatui::widgets::{
15  Block,
16  BorderType,
17  Borders,
18  Paragraph,
19  //BarChart,
20};
21
22use tof_dataclasses::packets::{
23  TofPacket,
24  PacketType
25};
26
27use tof_dataclasses::serialization::{
28  SerializationError,
29};
30
31use tof_dataclasses::heartbeats::{
32  EVTBLDRHeartbeat,
33  HeartBeatDataSink,
34  MTBHeartbeat,
35};
36
37use crate::colors::ColorTheme;
38use crate::widgets::timeseries;
39
40pub enum HeartBeatView {
41  EventBuilder,
42  MTB,
43  DataSink
44}
45
46pub struct HeartBeatTab {
47  pub theme      : ColorTheme,
48  // FIXME - we don't seemt to need this queues, 
49  // apparently 
50  //pub evb_queue  : VecDeque<EVTBLDRHeartbeat>,
51  //pub mtb_queue  : VecDeque<MTBHeartbeat>,
52  //pub gds_queue  : VecDeque<HeartBeatDataSink>,
53  pub pkt_recv   : Receiver<TofPacket>,
54  last_evb       : Option<EVTBLDRHeartbeat>,
55  last_mtb       : Option<MTBHeartbeat>,
56  last_gds       : Option<HeartBeatDataSink>,
57  pub view       : HeartBeatView,
58  pub queue_size : usize,
59
60  // containers for a bunch of nice looking plots
61  pub ev_c_q     : VecDeque<(f64,f64)>,
62  pub to_q       : VecDeque<(f64,f64)>,
63  pub mangl_q    : VecDeque<(f64,f64)>,
64  pub rb_disc_q  : VecDeque<(f64,f64)>,
65  pub lhit_fr_q  : VecDeque<(f64,f64)>,
66  pub ch_len_mte : VecDeque<(f64,f64)>,
67  pub ch_len_rbe : VecDeque<(f64,f64)>,
68}
69
70impl HeartBeatTab{
71
72  pub fn new(pkt_recv     : Receiver<TofPacket>,
73             theme        : ColorTheme) -> HeartBeatTab{  
74
75    HeartBeatTab {
76      theme   ,
77      //evb_queue  : VecDeque::<EVTBLDRHeartbeat>::new(),
78      //mtb_queue  : VecDeque::<MTBHeartbeat>::new(),
79      //gds_queue  : VecDeque::<HeartBeatDataSink>::new(),
80      last_evb   : None,
81      last_mtb   : None,
82      last_gds   : None,
83      pkt_recv   : pkt_recv,
84      view       : HeartBeatView::EventBuilder,
85      queue_size : 1000,
86  
87      ev_c_q     : VecDeque::<(f64,f64)>::with_capacity(1000),
88      to_q       : VecDeque::<(f64,f64)>::with_capacity(1000),
89      mangl_q    : VecDeque::<(f64,f64)>::with_capacity(1000),
90      rb_disc_q  : VecDeque::<(f64,f64)>::with_capacity(1000),
91      lhit_fr_q  : VecDeque::<(f64,f64)>::with_capacity(1000),
92      ch_len_mte : VecDeque::<(f64,f64)>::with_capacity(1000),
93      ch_len_rbe : VecDeque::<(f64,f64)>::with_capacity(1000),
94    }
95  }
96  
97  pub fn receive_packet(&mut self) -> Result<(), SerializationError> {
98    match self.pkt_recv.try_recv() {
99      Err(_err)   => {
100        debug!("Unable to receive heartbeat TofPacket!");  
101      }
102      Ok(pack)    => {
103        //println!("Received {}", pack);
104        match pack.packet_type {
105          PacketType::MTBHeartbeat=> {
106            let hb : MTBHeartbeat = pack.unpack()?;
107            //self.mtb_queue.push_back(hb);
108            //if self.mtb_queue.len() > self.queue_size {
109            //  self.mtb_queue.pop_front(); 
110            //}
111            self.last_mtb = Some(hb);
112          },
113          PacketType::EVTBLDRHeartbeat   => {
114            let hb : EVTBLDRHeartbeat = pack.unpack()?;
115            //self.evb_queue.push_back(hb);
116            //if self.evb_queue.len() > self.queue_size {
117            //  self.evb_queue.pop_front(); 
118            //}
119            self.ev_c_q    .push_back((hb.met_seconds as f64,hb.event_cache_size as f64));
120            if self.ev_c_q.len() > self.queue_size {
121              self.ev_c_q.pop_front(); 
122            }
123            self.to_q      .push_back((hb.met_seconds as f64,hb.get_timed_out_frac()*100.0));
124            if self.to_q.len() > self.queue_size {
125              self.to_q.pop_front(); 
126            }
127            self.mangl_q   .push_back((hb.met_seconds as f64,hb.get_mangled_frac()*100.0));
128            if self.mangl_q.len() > self.queue_size {
129              self.mangl_q.pop_front(); 
130            }
131            self.rb_disc_q .push_back((hb.met_seconds as f64,hb.get_nrbe_discarded_frac()*100.0));
132            if self.rb_disc_q.len() > self.queue_size {
133              self.rb_disc_q.pop_front(); 
134            }
135            self.lhit_fr_q .push_back((hb.met_seconds as f64,hb.get_drs_lost_frac()*100.0));
136            if self.lhit_fr_q.len() > self.queue_size {
137              self.lhit_fr_q.pop_front(); 
138            }
139            self.ch_len_mte.push_back((hb.met_seconds as f64,hb.mte_receiver_cbc_len as f64));
140            if self.ch_len_mte.len() > self.queue_size {
141              self.ch_len_mte.pop_front(); 
142            }
143            self.ch_len_rbe.push_back((hb.met_seconds as f64,hb.rbe_receiver_cbc_len as f64));
144            if self.ch_len_rbe.len() > self.queue_size {
145              self.ch_len_rbe.pop_front(); 
146            }
147            self.last_evb = Some(hb);
148          }
149          PacketType::HeartBeatDataSink => {
150            let hb : HeartBeatDataSink = pack.unpack()?;
151            //self.gds_queue.push_back(hb);
152            //if self.gds_queue.len() > self.queue_size {
153            //  self.gds_queue.pop_front(); 
154            //}
155            self.last_gds = Some(hb);
156          }
157          _ => () // we don't care
158        }
159      }
160    }
161    Ok(())
162  }
163
164  pub fn render(&mut self, main_window : &Rect, frame : &mut Frame) {
165
166    let main_lo = Layout::default()
167      .direction(Direction::Horizontal)
168      .constraints(
169          [Constraint::Percentage(50),
170           Constraint::Percentage(50)].as_ref(),
171      )
172      .split(*main_window);
173
174    let mut view_string = String::from("HB QUEUE EMPTY!");
175    //self.last_evb = self.evb_queue.back().copied();
176    //self.last_mtb = self.mtb_queue.back().copied();
177    //self.last_gds = self.gds_queue.back().copied();
178
179    match self.view {
180      HeartBeatView::EventBuilder => {
181        if self.last_evb.is_some() {
182          view_string = self.last_evb.unwrap().to_string();
183        }
184        // A bunch of charts :
185        //
186        // event_cache   Lost hit frac 
187        // time_out ev   Ch len MTE rec
188        // data mangl    Ch len RBE rec
189        // RBEv discar   XX
190        //
191        let hb_ev_cols = Layout::default()
192          .direction(Direction::Horizontal)
193          .constraints(
194              [Constraint::Percentage(50),
195               Constraint::Percentage(50)].as_ref(),
196          )
197          .split(main_lo[1]);
198        
199        let hb_ev_rows_left = Layout::default()
200          .direction(Direction::Vertical)
201          .constraints(
202              [Constraint::Percentage(25),
203               Constraint::Percentage(25),
204               Constraint::Percentage(25),
205               Constraint::Percentage(25)].as_ref(),
206          )
207          .split(hb_ev_cols[0]);
208        let hb_ev_rows_right = Layout::default()
209          .direction(Direction::Vertical)
210          .constraints(
211              [Constraint::Percentage(25),
212               Constraint::Percentage(25),
213               Constraint::Percentage(25),
214               Constraint::Percentage(25)].as_ref(),
215          )
216          .split(hb_ev_cols[1]);
217       
218        // event cache graph 
219        let mut ts_label   = String::from("Size of event cache [#evts]");
220        let ts_ev_theme    = self.theme.clone();
221        let mut ts_ev_data = self.ev_c_q.clone(); 
222        let ts_ev          = timeseries(&mut ts_ev_data,
223                                        ts_label.clone(),
224                                        ts_label.clone(),
225                                        &ts_ev_theme);
226        frame.render_widget(ts_ev, hb_ev_rows_left[0]);
227        
228        // time out event graph 
229        ts_label           = String::from("Fraction of timed out events [%]");
230        let ts_to_theme    = self.theme.clone();
231        let mut ts_to_data = self.to_q.clone(); 
232        let ts_to          = timeseries(&mut ts_to_data,
233                                        ts_label.clone(),
234                                        ts_label.clone(),
235                                        &ts_to_theme);
236        frame.render_widget(ts_to, hb_ev_rows_left[1]);
237        
238        // data mangl graph 
239        ts_label           = String::from("Fraction of mangled events [%]");
240        let ts_dm_theme    = self.theme.clone();
241        let mut ts_dm_data = self.mangl_q.clone(); 
242        let ts_dm          = timeseries(&mut ts_dm_data,
243                                        ts_label.clone(),
244                                        ts_label.clone(),
245                                        &ts_dm_theme);
246        frame.render_widget(ts_dm, hb_ev_rows_left[2]);
247        
248        // rb events discarded graph
249        ts_label               = String::from("Fraction of discarded RBEvents [%]");
250        let ts_rbdisc_theme    = self.theme.clone();
251        let mut ts_rbdisc_data = self.rb_disc_q.clone(); 
252        let ts_rbdisc          = timeseries(&mut ts_rbdisc_data,
253                                            ts_label.clone(),
254                                            ts_label.clone(),
255                                            &ts_rbdisc_theme);
256        frame.render_widget(ts_rbdisc, hb_ev_rows_left[3]);
257        
258        // lost hit fraction
259        ts_label           = String::from("Fraction of DRS4 dead hits [%]");
260        let ts_lh_theme    = self.theme.clone();
261        let mut ts_lh_data = self.lhit_fr_q.clone(); 
262        let ts_lh          = timeseries(&mut ts_lh_data,
263                                        ts_label.clone(),
264                                        ts_label.clone(),
265                                        &ts_lh_theme);
266        frame.render_widget(ts_lh, hb_ev_rows_right[0]);
267        
268        // mte incoming ch len
269        ts_label              = String::from("Incoming MTE buffer len [#ets]");
270        let ts_mtech_theme    = self.theme.clone();
271        let mut ts_mtech_data = self.ch_len_mte.clone(); 
272        let ts_mtech          = timeseries(&mut ts_mtech_data,
273                                           ts_label.clone(),
274                                           ts_label.clone(),
275                                           &ts_mtech_theme);
276        frame.render_widget(ts_mtech, hb_ev_rows_right[1]);
277        
278        // rbe incoming ch len
279        ts_label              = String::from("Incoming RBE buffer len [#ets]");
280        let ts_rbech_theme    = self.theme.clone();
281        let mut ts_rbech_data = self.ch_len_rbe.clone(); 
282        let ts_rbech          = timeseries(&mut ts_rbech_data,
283                                           ts_label.clone(),
284                                           ts_label.clone(),
285                                           &ts_rbech_theme);
286        frame.render_widget(ts_rbech, hb_ev_rows_right[2]);
287      
288      }
289      HeartBeatView::MTB => {
290        if self.last_mtb.is_some() {
291          view_string = self.last_mtb.unwrap().to_string();
292        }
293      }
294      HeartBeatView::DataSink => {
295        if self.last_gds.is_some() {
296          view_string = self.last_gds.unwrap().to_string();
297        }
298      }
299    }
300
301    let hb_view = Paragraph::new(view_string)
302      // FIXME color
303      .style(Style::default().fg(Color::LightCyan))
304      .alignment(Alignment::Left)
305      //.scroll((5, 10))
306      .block(
307        Block::default()
308          .borders(Borders::ALL)
309          .style(self.theme.style())
310          .title("Last Heartbeat \u{1f493}")
311          .border_type(BorderType::Rounded),
312      );
313    frame.render_widget(hb_view, main_lo[0]);
314  }
315} // end impl
316