liftof_tui/tabs/
tab_mt.rs

1//! Master Trigger tab
2//! 
3//! Show current data from the master trigger
4
5use std::collections::{
6  VecDeque,
7  HashMap
8};
9
10use std::sync::{
11  Arc,
12  Mutex,
13};
14
15use std::time::{
16  Instant
17};
18
19use ratatui::{
20  symbols,
21  layout::{Alignment, Constraint, Direction, Layout, Rect},
22  style::{
23      Color,
24      //Modifier,
25      Style},
26  text::Span,
27  Frame,
28  //terminal::Frame,
29  widgets::{
30      Block,
31      Dataset,
32      Axis,
33      GraphType,
34      BorderType,
35      Chart,
36      //BarChart,
37      Borders,
38      Paragraph
39  },
40};
41
42use crossbeam_channel::Receiver;
43
44use ndhistogram::{
45  ndhistogram,
46  Histogram,
47  Hist1D,
48};
49
50use ndhistogram::axis::{
51  Uniform,
52};
53
54use tof_dataclasses::packets::{
55  TofPacket,
56  PacketType
57};
58
59use tof_dataclasses::events::MasterTriggerEvent;
60use tof_dataclasses::monitoring::MtbMoniData;
61use tof_dataclasses::errors::SerializationError;
62use tof_dataclasses::database::DsiJChPidMapping;
63use tof_dataclasses::events::master_trigger::LTBThreshold;
64
65use tof_dataclasses::alerts::*;
66
67use crate::colors::{
68  ColorTheme,
69};
70
71use crate::widgets::{
72  prep_data,
73  create_labels,
74  histogram,
75  timeseries
76};
77
78#[derive(Debug, Clone)]
79pub struct MTTab<'a> {
80  pub event_queue    : VecDeque<MasterTriggerEvent>,
81  pub moni_queue     : VecDeque<MtbMoniData>,
82  pub met_queue      : VecDeque<f64>,
83  pub rate_queue     : VecDeque<(f64,f64)>,
84  pub lost_r_queue   : VecDeque<(f64,f64)>,
85  pub fpgatmp_queue  : VecDeque<(f64,f64)>,
86  pub tp_receiver    : Receiver<TofPacket>,
87  pub mte_receiver   : Receiver<MasterTriggerEvent>,
88  pub queue_size     : usize,
89 
90  pub n_events       : usize,
91  pub n_moni         : usize,
92  pub miss_evid      : usize,
93  pub last_evid      : u32,
94  pub nch_histo      : Hist1D<Uniform<f32>>,
95  pub mtb_link_histo : Hist1D<Uniform<f32>>,
96  pub panel_histo    : Hist1D<Uniform<f32>>,
97  pub theme          : ColorTheme,
98
99  pub mapping        : DsiJChPidMapping,
100  pub mtlink_rb_map  : HashMap<u8,u8>,
101  pub problem_hits   : Vec<(u8, u8, (u8, u8), LTBThreshold)>,
102  timer              : Instant,
103  moni_old_check     : Instant,
104  alerts             : Arc<Mutex<HashMap<&'a str,TofAlert<'a>>>>,
105  alerts_active      : bool
106}
107
108impl<'a> MTTab<'a> {
109
110  pub fn new(tp_receiver   : Receiver<TofPacket>,
111             mte_receiver  : Receiver<MasterTriggerEvent>,
112             mapping       : DsiJChPidMapping,
113             mtlink_rb_map : HashMap<u8,u8>,
114             alerts        : Arc<Mutex<HashMap<&'a str, TofAlert<'a>>>>,
115             theme         : ColorTheme) -> MTTab {
116    // check if the alerts are active
117    let mut alerts_active = false;
118    match alerts.lock() {
119      Ok(al) => {
120        if al.len() > 0 {
121          alerts_active = true;
122          info!("Found {} active alerts!", al.len());
123        }
124      }
125      Err(err) => {
126        error!("Unable to lock alert mutex! {err}");
127      }
128    }
129    let bins          = Uniform::new(50, 0.0, 50.0).unwrap();
130    let mtb_link_bins = Uniform::new(50, 0.0, 50.0).unwrap();
131    let panel_bins    = Uniform::new(22, 1.0, 22.0).unwrap();
132    Self {
133      event_queue    : VecDeque::<MasterTriggerEvent>::with_capacity(1000),
134      moni_queue     : VecDeque::<MtbMoniData>::with_capacity(1000),
135      met_queue      : VecDeque::<f64>::with_capacity(1000),
136      rate_queue     : VecDeque::<(f64,f64)>::with_capacity(1000),
137      lost_r_queue   : VecDeque::<(f64,f64)>::with_capacity(1000),
138      fpgatmp_queue  : VecDeque::<(f64,f64)>::with_capacity(1000),
139      tp_receiver    : tp_receiver,
140      mte_receiver   : mte_receiver,
141      queue_size     : 1000, // random
142      n_events       : 0,
143      n_moni         : 0,
144      miss_evid      : 0,
145      last_evid      : 0,
146      nch_histo      : ndhistogram!(bins),
147      mtb_link_histo : ndhistogram!(mtb_link_bins),
148      panel_histo    : ndhistogram!(panel_bins),
149      theme          : theme,
150      mapping        : mapping.clone(),
151      mtlink_rb_map  : mtlink_rb_map,
152      problem_hits   : Vec::<(u8, u8, (u8, u8), LTBThreshold)>::new(),
153      timer          : Instant::now(),
154      moni_old_check : Instant::now(),
155      alerts         : alerts,
156      alerts_active  : alerts_active,
157    }
158  }
159
160  pub fn receive_packet(&mut self) -> Result<(), SerializationError> {
161    let mut mte = MasterTriggerEvent::new();
162    let met     = self.timer.elapsed().as_secs_f64();
163    match self.tp_receiver.try_recv() {
164      Err(_err)   => {
165        match self.mte_receiver.try_recv() {
166          Err(_err) => (),
167          Ok(_ev)   => {
168            mte = _ev;
169          }
170        }
171      },
172      Ok(pack)    => {
173        //error!("Got next packet {}!", pack);
174        match pack.packet_type {
175          PacketType::MasterTrigger => {
176            match pack.unpack() {
177              Ok(_ev) => {
178                mte = _ev;
179              },
180              Err(err) => {
181                error!("Unable to unpack MasterTriggerEvent! {err}");
182                return Err(err);
183              }
184            }
185          },
186          PacketType::MonitorMtb   => {
187            info!("Got new MtbMoniData!");
188            let moni : MtbMoniData = pack.unpack()?;
189            self.n_moni += 1;
190            // check alert conditions
191            if self.alerts_active {
192              match self.alerts.lock() {
193                Ok(mut al) => {
194                  // we can work with mtb relevant alerts here
195                  al.get_mut("mtb_lost_rate").unwrap()     .trigger(moni.lost_rate as f32);
196                  al.get_mut("mtb_fpga_temp").unwrap() .trigger(moni.get_fpga_temp());
197                  al.get_mut("mtb_rate_zero").unwrap() .trigger(moni.rate as f32);
198                  al.get_mut("mtb_hk_too_old").unwrap().trigger(self.moni_old_check.elapsed().as_secs() as f32);
199                },
200                Err(err)   =>  error!("Unable to lock global alerts! {err}"),
201              }
202            }
203            self.moni_old_check = Instant::now();
204
205            self.moni_queue.push_back(moni);
206            if self.moni_queue.len() > self.queue_size {
207              self.moni_queue.pop_front();
208            }
209            self.rate_queue.push_back((met, moni.rate as f64));
210            if self.rate_queue.len() > self.queue_size {
211              self.rate_queue.pop_front();
212            }
213            self.lost_r_queue.push_back((met, moni.lost_rate as f64));
214            if self.lost_r_queue.len() > self.queue_size {
215              self.lost_r_queue.pop_front();
216            }
217            self.fpgatmp_queue.push_back((met, moni.get_fpga_temp() as f64));
218            if self.fpgatmp_queue.len() > self.queue_size {
219              self.fpgatmp_queue.pop_front();
220            }
221            self.met_queue.push_back(met);
222            if self.met_queue.len() > self.queue_size {
223              self.met_queue.pop_front();
224            }
225            return Ok(());
226          },
227          _ => (),
228        }
229      } // end Ok
230    } // end match
231    if mte.event_id != 0 {
232      let hits     = mte.get_trigger_hits();
233      let rb_links = mte.get_rb_link_ids();
234      for h in &hits {
235        match self.mapping.get(&h.0) {
236          None => {
237            error!("Can't get mapping for hit {:?}", h);
238            //self.problem_hits.push(*h);
239          },
240          Some(jmap) => {
241            match jmap.get(&h.1) {
242              None => {
243                error!("Can't get mapping for hit {:?}", h);
244                self.problem_hits.push(*h);
245              },
246              Some(chmap) => {
247                // let's just consider one side of the paddle 
248                // here. If the two sides are not connected to 
249                // the same LTB we have bigger problems
250                match chmap.get(&h.2.0) {
251                  None => {
252                    error!("Can't get mapping for hit {:?}", h);
253                    self.problem_hits.push(*h);
254                  },
255                  Some((_,panel_id)) => {
256                    self.panel_histo.fill(&(*panel_id as f32));
257                  }
258                }
259              }
260            }
261          }
262        }
263        //self.panel_histo.fill(&(self.mapping[&h.0][&h.1][&h.2].1 as f32));
264      }
265      self.nch_histo.fill(&(hits.len() as f32));
266      for k in rb_links {
267        // FIXME unwrap
268        let linked_rbid = self.mtlink_rb_map.get(&k).unwrap_or(&0);
269        self.mtb_link_histo.fill(&(*linked_rbid as f32));
270      }
271      self.n_events += 1;
272      self.event_queue.push_back(mte.clone());
273      if self.event_queue.len() > self.queue_size {
274        self.event_queue.pop_front();
275      }
276      if self.last_evid != 0 {
277        if mte.event_id - self.last_evid != 1 {
278          self.miss_evid += (mte.event_id - self.last_evid) as usize;
279        }
280      }
281      self.last_evid = mte.event_id;
282    }
283    Ok(())
284  }
285
286  pub fn render(&mut self, main_window : &Rect, frame : &mut Frame) {
287    let main_chunks = Layout::default()
288      .direction(Direction::Horizontal)
289      .constraints(
290          [Constraint::Percentage(70),
291           Constraint::Percentage(30)].as_ref(),
292      )
293      .split(*main_window);
294   
295    // these are the 3 plots on the right side
296    let info_chunks = Layout::default()
297      .direction(Direction::Vertical)
298      .constraints(
299          [Constraint::Percentage(30),
300           Constraint::Percentage(30),
301           Constraint::Percentage(40),
302          ].as_ref(),
303      )
304      .split(main_chunks[1]);
305       
306    let detail_chunks = Layout::default()
307      .direction(Direction::Vertical)
308      .constraints(
309          [Constraint::Percentage(60),
310           Constraint::Percentage(40),
311          ].as_ref(),
312      )
313      .split(main_chunks[0]);
314
315    let view_chunks = Layout::default()
316      .direction(Direction::Horizontal)
317      .constraints(
318          [Constraint::Percentage(33),
319           Constraint::Percentage(33),
320           Constraint::Percentage(34),
321          ].as_ref(),
322      )
323      .split(detail_chunks[0]);
324    let trig_pan_and_hits = Layout::default()
325      .direction(Direction::Vertical)
326      .constraints(
327          [Constraint::Percentage(50),
328           Constraint::Percentage(50)].as_ref(),
329      )
330      .split(view_chunks[2]);
331      
332    let bottom_row = detail_chunks[1];
333
334    //let main_layout   = main_chunks.to_vec();
335    //let detail_layout = detail_chunks.to_vec();
336    let info_layout   = info_chunks.to_vec();
337    let view_layout   = view_chunks.to_vec();
338
339    let t_min = *self.met_queue.front().unwrap_or(&0.0) as u64;
340    let t_max = *self.met_queue.back().unwrap_or(&0.0)  as u64;
341    let t_spacing = (t_max - t_min)/5;
342
343    let t_labels = vec![Span::from(t_min.to_string()),
344                        Span::from((t_min + t_spacing).to_string()),
345                        Span::from((t_min + 2*t_spacing).to_string()),
346                        Span::from((t_min + 3*t_spacing).to_string()),
347                        Span::from((t_min + 4*t_spacing).to_string()),
348                        Span::from((t_min + 5*t_spacing).to_string())];
349    
350     let rate_only : Vec::<i64> = self.rate_queue.iter().map(|z| z.1.round() as i64).collect();
351     let r_max = *rate_only.iter().max().unwrap_or(&0) + 5;
352     let r_min = *rate_only.iter().min().unwrap_or(&0) - 5;
353     let rate_spacing = (r_max - r_min)/5;
354     let rate_labels = vec![Span::from(r_min.to_string()),
355                            Span::from((r_min + rate_spacing).to_string()),
356                            Span::from((r_min + 2*rate_spacing).to_string()),
357                            Span::from((r_min + 3*rate_spacing).to_string()),
358                            Span::from((r_min + 4*rate_spacing).to_string()),
359                            Span::from((r_min + 5*rate_spacing).to_string())];
360     
361     let rate_dataset = vec![Dataset::default()
362         .name("MTB Rate")
363         .marker(symbols::Marker::Braille)
364         .graph_type(GraphType::Line)
365         //.style(Style::default().fg(pl.get_fg_light()).bg(pl.get_bg_dark()))
366         .style(self.theme.style())
367         .data(self.rate_queue.make_contiguous())];
368
369    let rate_chart = Chart::new(rate_dataset)
370      .block(
371        Block::default()
372          .borders(Borders::ALL)
373          .style(Style::default().patch(self.theme.style()))
374          .title("MT rate ".to_owned() )
375          .border_type(BorderType::Rounded),
376      )
377      .x_axis(Axis::default()
378        .title(Span::styled("MET [s]", Style::default().patch(self.theme.style())))
379        .style(Style::default().patch(self.theme.style()))
380        .bounds([t_min as f64, t_max as f64])
381        //.bounds([0.0, 1000.0])
382        //.labels(t_labels.clone().iter().cloned().map(Span::from).collect()))
383        .labels(t_labels.clone())
384      )
385      .y_axis(Axis::default()
386        .title(Span::styled("Hz", Style::default().patch(self.theme.style())))
387        .style(Style::default().patch(self.theme.style()))
388        .bounds([r_min as f64, r_max as f64])
389        //.bounds([0.0,1000.0])
390        //.labels(rate_labels.clone().iter().cloned().map(Span::from).collect()))
391        .labels(rate_labels.clone())
392      )
393      .style(self.theme.style()); 
394     
395    // NChannel distribution
396    let nch_labels  = create_labels(&self.nch_histo);
397    let nch_data    = prep_data(&self.nch_histo, &nch_labels, 2, true); 
398    let nch_chart   = histogram(nch_data, String::from("N Hits (N CH)"), 2, 0, &self.theme);
399
400    // FPGA temperature (future)
401    //let fpga_t_label    = String::from("FPGA T [\u{00B0}C] ");
402    //let fpga_t_theme    = self.theme.clone();
403    //let mut fpga_t_data = self.fpgatmp_queue.clone(); //.make_contiguous();
404    //let mut fpga_t_ts   = timeseries(&mut fpga_t_data,
405    //                                 fpga_t_label.clone(),
406    //                                 fpga_t_label.clone(),
407    //                                 &fpga_t_theme);
408    //frame.render_widget(fpga_t_ts, info_layout[2]);
409
410    // Lost Trigger rate
411    let lost_t_label    = String::from("Lost Trigger Rate [Hz]");
412    let lost_t_theme    = self.theme.clone();
413    let mut lost_t_data = self.lost_r_queue.clone(); //.make_contiguous();
414    let lost_t_ts       = timeseries(&mut lost_t_data,
415                                     lost_t_label.clone(),
416                                     lost_t_label.clone(),
417                                     &lost_t_theme);
418
419    
420    let tmp_only : Vec::<i64> = self.fpgatmp_queue.iter().map(|z| z.1.round() as i64).collect();
421    let tmp_max = *tmp_only.iter().max().unwrap_or(&0) + 5;
422    let tmp_min = *tmp_only.iter().min().unwrap_or(&0) - 5;
423    let tmp_spacing = (tmp_max - tmp_min)/5;
424    let tmp_labels = vec![Span::from(tmp_min.to_string()),
425                          Span::from((tmp_min + tmp_spacing).to_string()),
426                          Span::from((tmp_min + 2*tmp_spacing).to_string()),
427                          Span::from((tmp_min + 3*tmp_spacing).to_string()),
428                          Span::from((tmp_min + 4*tmp_spacing).to_string()),
429                          Span::from((tmp_min + 5*tmp_spacing).to_string())];
430    let fpga_temp_dataset = vec![Dataset::default()
431        .name("FPGA T")
432        .marker(symbols::Marker::Braille)
433        .graph_type(GraphType::Line)
434        .style(Style::default().patch(self.theme.style()))
435        .data(self.fpgatmp_queue.make_contiguous())];
436
437    let fpga_temp_chart = Chart::new(fpga_temp_dataset)
438      .block(
439        Block::default()
440          .borders(Borders::ALL)
441          .style(Style::default().patch(self.theme.style()))
442          .title("FPGA T [\u{00B0}C] ".to_owned() )
443          .border_type(BorderType::Rounded),
444      )
445      .x_axis(Axis::default()
446        .title(Span::styled("MET [s]", Style::default().fg(Color::White)))
447        .style(Style::default().patch(self.theme.style()))
448        .bounds([t_min as f64, t_max as f64])
449        //.labels(t_labels.clone().iter().cloned().map(Span::from).collect()))
450        .labels(t_labels.clone())
451      )
452      .y_axis(Axis::default()
453        //.title(Span::styled("T [\u{00B0}C]", Style::default().fg(Color::White)))
454        .style(self.theme.style())
455        .bounds([tmp_min as f64, tmp_max as f64])
456        //.labels(tmp_labels.clone().iter().cloned().map(Span::from).collect()))
457        .labels(tmp_labels.clone())
458      )
459      .style(self.theme.style());
460    
461    let last_event = self.event_queue.back();
462    let view_string : String;
463    match last_event {
464      Some(event) => { 
465        view_string = event.to_string();
466      }, 
467      None => {
468        view_string = String::from("EVT QUEUE EMPTY!");
469      }
470    }
471    let event_view = Paragraph::new(view_string)
472      .style(Style::default().fg(Color::LightCyan))
473      .alignment(Alignment::Left)
474      //.scroll((5, 10))
475      .block(
476        Block::default()
477          .borders(Borders::ALL)
478          .style(self.theme.style())
479          .title("Last MasterTriggerEvent")
480          .border_type(BorderType::Rounded),
481      );
482
483    let last_moni = self.moni_queue.back();
484    let view_moni : String;
485    match last_moni {
486      Some(moni) => { 
487        view_moni = moni.to_string();
488      }, 
489      None => {
490        view_moni = String::from("MTBMONI QUEUE EMPTY!");
491      }
492    }
493    
494    let moni_view = Paragraph::new(view_moni)
495    .style(self.theme.style())
496    .alignment(Alignment::Left)
497    .block(
498      Block::default()
499        .borders(Borders::ALL)
500        .style(self.theme.style())
501        .title("Last MtbMoniData")
502        .border_type(BorderType::Rounded),
503    );
504    
505    // histograms
506    let ml_labels  = create_labels(&self.mtb_link_histo);
507    let mlh_data   = prep_data(&self.mtb_link_histo, &ml_labels, 10, true); 
508    // this actually now is the RB ID!
509    let mlh_chart  = histogram(mlh_data, String::from("RB ID"), 3, 0, &self.theme);
510    frame.render_widget(mlh_chart, bottom_row);   
511    
512    let tp_labels  = create_labels(&self.panel_histo);
513    let tph_data   = prep_data(&self.panel_histo, &tp_labels, 2, true); 
514    let tpc_chart  = histogram(tph_data, String::from("Triggered Panel ID"), 3, 0, &self.theme);
515    frame.render_widget(tpc_chart, trig_pan_and_hits[0]);
516    frame.render_widget(nch_chart, trig_pan_and_hits[1]);
517
518    // render everything
519    frame.render_widget(rate_chart,      info_layout[0]); 
520    frame.render_widget(lost_t_ts,       info_layout[1]);
521    frame.render_widget(fpga_temp_chart, info_layout[2]);
522    frame.render_widget(event_view,      view_layout[0]);
523    frame.render_widget(moni_view,       view_layout[1]);
524    //frame.render_widget(summary_view,    bottom_row[0]);
525  }
526}
527