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::{
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 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, 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 match pack.packet_type {
165 TofPacketType::MtbMoniData => {
166 info!("Got new MtbMoniData!");
167 let moni : MtbMoniData = pack.unpack()?;
168 self.n_moni += 1;
169 if self.alerts_active {
171 match self.alerts.lock() {
172 Ok(mut al) => {
173 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 } } 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 },
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 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 }
244 self.nch_histo.fill(&(hits.len() as f32));
245 for k in rb_links {
246 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 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 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(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 .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 .labels(rate_labels.clone())
371 )
372 .style(self.theme.style());
373
374 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 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(); 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())
430 )
431 .y_axis(Axis::default()
432 .style(self.theme.style())
434 .bounds([tmp_min as f64, tmp_max as f64])
435 .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 .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 let ml_labels = create_labels(&self.mtb_link_histo);
486 let mlh_data = prep_data(&self.mtb_link_histo, &ml_labels, 10, true);
487 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 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 }
505}
506