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