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