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
54
55use gondola_core::prelude::*;
56
57use 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 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, 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 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 if self.alerts_active {
195 match self.alerts.lock() {
196 Ok(mut al) => {
197 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 } } 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 },
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 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 }
268 self.nch_histo.fill(&(hits.len() as f32));
269 for k in rb_links {
270 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 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 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(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 .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 .labels(rate_labels.clone())
395 )
396 .style(self.theme.style());
397
398 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 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(); 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())
454 )
455 .y_axis(Axis::default()
456 .style(self.theme.style())
458 .bounds([tmp_min as f64, tmp_max as f64])
459 .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 .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 let ml_labels = create_labels(&self.mtb_link_histo);
510 let mlh_data = prep_data(&self.mtb_link_histo, &ml_labels, 10, true);
511 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 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 }
529}
530