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