1use 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 Style},
26 text::Span,
27 Frame,
28 widgets::{
30 Block,
31 Dataset,
32 Axis,
33 GraphType,
34 BorderType,
35 Chart,
36 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 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, 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 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 if self.alerts_active {
192 match self.alerts.lock() {
193 Ok(mut al) => {
194 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 } } 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 },
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 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 }
265 self.nch_histo.fill(&(hits.len() as f32));
266 for k in rb_links {
267 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 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 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(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 .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 .labels(rate_labels.clone())
392 )
393 .style(self.theme.style());
394
395 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 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(); 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())
451 )
452 .y_axis(Axis::default()
453 .style(self.theme.style())
455 .bounds([tmp_min as f64, tmp_max as f64])
456 .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 .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 let ml_labels = create_labels(&self.mtb_link_histo);
507 let mlh_data = prep_data(&self.mtb_link_histo, &ml_labels, 10, true);
508 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 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 }
526}
527