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