liftof_tui/tabs/
tab_heartbeats.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
//! Heartbeats are a special kind of monitoring
//! which monitor the individual main threads of 
//! liftof-cc and are sent in regular intervals
//! 
//! The main threads are for the Master Trigger,
//! Event Builder and the Data sender.

//use chrono::Utc;
use std::collections::VecDeque; 

use crossbeam_channel::Receiver;

use ratatui::prelude::*;
use ratatui::widgets::{
  Block,
  BorderType,
  Borders,
  Paragraph,
  //BarChart,
};

use tof_dataclasses::packets::{
  TofPacket,
  PacketType
};

use tof_dataclasses::serialization::{
  SerializationError,
};

use tof_dataclasses::heartbeats::{
  EVTBLDRHeartbeat,
  HeartBeatDataSink,
  MTBHeartbeat,
};

use crate::colors::ColorTheme;
use crate::widgets::timeseries;

pub enum HeartBeatView {
  EventBuilder,
  MTB,
  DataSink
}

pub struct HeartBeatTab {
  pub theme      : ColorTheme,
  // FIXME - we don't seemt to need this queues, 
  // apparently 
  //pub evb_queue  : VecDeque<EVTBLDRHeartbeat>,
  //pub mtb_queue  : VecDeque<MTBHeartbeat>,
  //pub gds_queue  : VecDeque<HeartBeatDataSink>,
  pub pkt_recv   : Receiver<TofPacket>,
  last_evb       : Option<EVTBLDRHeartbeat>,
  last_mtb       : Option<MTBHeartbeat>,
  last_gds       : Option<HeartBeatDataSink>,
  pub view       : HeartBeatView,
  pub queue_size : usize,

  // containers for a bunch of nice looking plots
  pub ev_c_q     : VecDeque<(f64,f64)>,
  pub to_q       : VecDeque<(f64,f64)>,
  pub mangl_q    : VecDeque<(f64,f64)>,
  pub rb_disc_q  : VecDeque<(f64,f64)>,
  pub lhit_fr_q  : VecDeque<(f64,f64)>,
  pub ch_len_mte : VecDeque<(f64,f64)>,
  pub ch_len_rbe : VecDeque<(f64,f64)>,
}

impl HeartBeatTab{

  pub fn new(pkt_recv     : Receiver<TofPacket>,
             theme        : ColorTheme) -> HeartBeatTab{  

    HeartBeatTab {
      theme   ,
      //evb_queue  : VecDeque::<EVTBLDRHeartbeat>::new(),
      //mtb_queue  : VecDeque::<MTBHeartbeat>::new(),
      //gds_queue  : VecDeque::<HeartBeatDataSink>::new(),
      last_evb   : None,
      last_mtb   : None,
      last_gds   : None,
      pkt_recv   : pkt_recv,
      view       : HeartBeatView::EventBuilder,
      queue_size : 1000,
  
      ev_c_q     : VecDeque::<(f64,f64)>::with_capacity(1000),
      to_q       : VecDeque::<(f64,f64)>::with_capacity(1000),
      mangl_q    : VecDeque::<(f64,f64)>::with_capacity(1000),
      rb_disc_q  : VecDeque::<(f64,f64)>::with_capacity(1000),
      lhit_fr_q  : VecDeque::<(f64,f64)>::with_capacity(1000),
      ch_len_mte : VecDeque::<(f64,f64)>::with_capacity(1000),
      ch_len_rbe : VecDeque::<(f64,f64)>::with_capacity(1000),
    }
  }
  
  pub fn receive_packet(&mut self) -> Result<(), SerializationError> {
    match self.pkt_recv.try_recv() {
      Err(_err)   => {
        debug!("Unable to receive heartbeat TofPacket!");  
      }
      Ok(pack)    => {
        //println!("Received {}", pack);
        match pack.packet_type {
          PacketType::MTBHeartbeat=> {
            let hb : MTBHeartbeat = pack.unpack()?;
            //self.mtb_queue.push_back(hb);
            //if self.mtb_queue.len() > self.queue_size {
            //  self.mtb_queue.pop_front(); 
            //}
            self.last_mtb = Some(hb);
          },
          PacketType::EVTBLDRHeartbeat   => {
            let hb : EVTBLDRHeartbeat = pack.unpack()?;
            //self.evb_queue.push_back(hb);
            //if self.evb_queue.len() > self.queue_size {
            //  self.evb_queue.pop_front(); 
            //}
            self.ev_c_q    .push_back((hb.met_seconds as f64,hb.event_cache_size as f64));
            if self.ev_c_q.len() > self.queue_size {
              self.ev_c_q.pop_front(); 
            }
            self.to_q      .push_back((hb.met_seconds as f64,hb.get_timed_out_frac()*100.0));
            if self.to_q.len() > self.queue_size {
              self.to_q.pop_front(); 
            }
            self.mangl_q   .push_back((hb.met_seconds as f64,hb.get_mangled_frac()*100.0));
            if self.mangl_q.len() > self.queue_size {
              self.mangl_q.pop_front(); 
            }
            self.rb_disc_q .push_back((hb.met_seconds as f64,hb.get_nrbe_discarded_frac()*100.0));
            if self.rb_disc_q.len() > self.queue_size {
              self.rb_disc_q.pop_front(); 
            }
            self.lhit_fr_q .push_back((hb.met_seconds as f64,hb.get_drs_lost_frac()*100.0));
            if self.lhit_fr_q.len() > self.queue_size {
              self.lhit_fr_q.pop_front(); 
            }
            self.ch_len_mte.push_back((hb.met_seconds as f64,hb.mte_receiver_cbc_len as f64));
            if self.ch_len_mte.len() > self.queue_size {
              self.ch_len_mte.pop_front(); 
            }
            self.ch_len_rbe.push_back((hb.met_seconds as f64,hb.rbe_receiver_cbc_len as f64));
            if self.ch_len_rbe.len() > self.queue_size {
              self.ch_len_rbe.pop_front(); 
            }
            self.last_evb = Some(hb);
          }
          PacketType::HeartBeatDataSink => {
            let hb : HeartBeatDataSink = pack.unpack()?;
            //self.gds_queue.push_back(hb);
            //if self.gds_queue.len() > self.queue_size {
            //  self.gds_queue.pop_front(); 
            //}
            self.last_gds = Some(hb);
          }
          _ => () // we don't care
        }
      }
    }
    Ok(())
  }

  pub fn render(&mut self, main_window : &Rect, frame : &mut Frame) {

    let main_lo = Layout::default()
      .direction(Direction::Horizontal)
      .constraints(
          [Constraint::Percentage(50),
           Constraint::Percentage(50)].as_ref(),
      )
      .split(*main_window);

    let mut view_string = String::from("HB QUEUE EMPTY!");
    //self.last_evb = self.evb_queue.back().copied();
    //self.last_mtb = self.mtb_queue.back().copied();
    //self.last_gds = self.gds_queue.back().copied();

    match self.view {
      HeartBeatView::EventBuilder => {
        if self.last_evb.is_some() {
          view_string = self.last_evb.unwrap().to_string();
        }
        // A bunch of charts :
        //
        // event_cache   Lost hit frac 
        // time_out ev   Ch len MTE rec
        // data mangl    Ch len RBE rec
        // RBEv discar   XX
        //
        let hb_ev_cols = Layout::default()
          .direction(Direction::Horizontal)
          .constraints(
              [Constraint::Percentage(50),
               Constraint::Percentage(50)].as_ref(),
          )
          .split(main_lo[1]);
        
        let hb_ev_rows_left = Layout::default()
          .direction(Direction::Vertical)
          .constraints(
              [Constraint::Percentage(25),
               Constraint::Percentage(25),
               Constraint::Percentage(25),
               Constraint::Percentage(25)].as_ref(),
          )
          .split(hb_ev_cols[0]);
        let hb_ev_rows_right = Layout::default()
          .direction(Direction::Vertical)
          .constraints(
              [Constraint::Percentage(25),
               Constraint::Percentage(25),
               Constraint::Percentage(25),
               Constraint::Percentage(25)].as_ref(),
          )
          .split(hb_ev_cols[1]);
       
        // event cache graph 
        let mut ts_label   = String::from("Size of event cache [#evts]");
        let ts_ev_theme    = self.theme.clone();
        let mut ts_ev_data = self.ev_c_q.clone(); 
        let ts_ev          = timeseries(&mut ts_ev_data,
                                        ts_label.clone(),
                                        ts_label.clone(),
                                        &ts_ev_theme);
        frame.render_widget(ts_ev, hb_ev_rows_left[0]);
        
        // time out event graph 
        ts_label           = String::from("Fraction of timed out events [%]");
        let ts_to_theme    = self.theme.clone();
        let mut ts_to_data = self.to_q.clone(); 
        let ts_to          = timeseries(&mut ts_to_data,
                                        ts_label.clone(),
                                        ts_label.clone(),
                                        &ts_to_theme);
        frame.render_widget(ts_to, hb_ev_rows_left[1]);
        
        // data mangl graph 
        ts_label           = String::from("Fraction of mangled events [%]");
        let ts_dm_theme    = self.theme.clone();
        let mut ts_dm_data = self.mangl_q.clone(); 
        let ts_dm          = timeseries(&mut ts_dm_data,
                                        ts_label.clone(),
                                        ts_label.clone(),
                                        &ts_dm_theme);
        frame.render_widget(ts_dm, hb_ev_rows_left[2]);
        
        // rb events discarded graph
        ts_label               = String::from("Fraction of discarded RBEvents [%]");
        let ts_rbdisc_theme    = self.theme.clone();
        let mut ts_rbdisc_data = self.rb_disc_q.clone(); 
        let ts_rbdisc          = timeseries(&mut ts_rbdisc_data,
                                            ts_label.clone(),
                                            ts_label.clone(),
                                            &ts_rbdisc_theme);
        frame.render_widget(ts_rbdisc, hb_ev_rows_left[3]);
        
        // lost hit fraction
        ts_label           = String::from("Fraction of DRS4 dead hits [%]");
        let ts_lh_theme    = self.theme.clone();
        let mut ts_lh_data = self.lhit_fr_q.clone(); 
        let ts_lh          = timeseries(&mut ts_lh_data,
                                        ts_label.clone(),
                                        ts_label.clone(),
                                        &ts_lh_theme);
        frame.render_widget(ts_lh, hb_ev_rows_right[0]);
        
        // mte incoming ch len
        ts_label              = String::from("Incoming MTE buffer len [#ets]");
        let ts_mtech_theme    = self.theme.clone();
        let mut ts_mtech_data = self.ch_len_mte.clone(); 
        let ts_mtech          = timeseries(&mut ts_mtech_data,
                                           ts_label.clone(),
                                           ts_label.clone(),
                                           &ts_mtech_theme);
        frame.render_widget(ts_mtech, hb_ev_rows_right[1]);
        
        // rbe incoming ch len
        ts_label              = String::from("Incoming RBE buffer len [#ets]");
        let ts_rbech_theme    = self.theme.clone();
        let mut ts_rbech_data = self.ch_len_rbe.clone(); 
        let ts_rbech          = timeseries(&mut ts_rbech_data,
                                           ts_label.clone(),
                                           ts_label.clone(),
                                           &ts_rbech_theme);
        frame.render_widget(ts_rbech, hb_ev_rows_right[2]);
      
      }
      HeartBeatView::MTB => {
        if self.last_mtb.is_some() {
          view_string = self.last_mtb.unwrap().to_string();
        }
      }
      HeartBeatView::DataSink => {
        if self.last_gds.is_some() {
          view_string = self.last_gds.unwrap().to_string();
        }
      }
    }

    let hb_view = Paragraph::new(view_string)
      // FIXME color
      .style(Style::default().fg(Color::LightCyan))
      .alignment(Alignment::Left)
      //.scroll((5, 10))
      .block(
        Block::default()
          .borders(Borders::ALL)
          .style(self.theme.style())
          .title("Last Heartbeat \u{1f493}")
          .border_type(BorderType::Rounded),
      );
    frame.render_widget(hb_view, main_lo[0]);
  }
} // end impl