1use std::collections::VecDeque;
9
10use crossbeam_channel::Receiver;
11
12use ratatui::prelude::*;
13use ratatui::widgets::{
14 Block,
15 BorderType,
16 Borders,
17 Paragraph,
18 };
20
21use gondola_core::prelude::*;
22
23use crate::colors::ColorTheme;
24use crate::widgets::timeseries;
25
26pub enum HeartBeatView {
27 EventBuilder,
28 MTB,
29 DataSink
30}
31
32pub struct HeartBeatTab {
33 pub theme : ColorTheme,
34 pub pkt_recv : Receiver<TofPacket>,
40 last_evb : Option<EventBuilderHB>,
41 last_mtb : Option<MasterTriggerHB>,
42 last_gds : Option<DataSinkHB>,
43 pub view : HeartBeatView,
44 pub queue_size : usize,
45
46 pub ev_c_q : VecDeque<(f64,f64)>,
48 pub to_q : VecDeque<(f64,f64)>,
49 pub to_combo_q : VecDeque<(f64,f64)>,
51 pub mangl_q : VecDeque<(f64,f64)>,
52 pub rb_disc_q : VecDeque<(f64,f64)>,
53 pub lhit_fr_q : VecDeque<(f64,f64)>,
54 pub ch_len_mte : VecDeque<(f64,f64)>,
55 pub ch_len_rbe : VecDeque<(f64,f64)>,
56}
57
58impl HeartBeatTab{
59
60 pub fn new(pkt_recv : Receiver<TofPacket>,
61 theme : ColorTheme) -> HeartBeatTab{
62
63 HeartBeatTab {
64 theme ,
65 last_evb : None,
69 last_mtb : None,
70 last_gds : None,
71 pkt_recv : pkt_recv,
72 view : HeartBeatView::EventBuilder,
73 queue_size : 1000,
74
75 ev_c_q : VecDeque::<(f64,f64)>::with_capacity(1000),
76 to_q : VecDeque::<(f64,f64)>::with_capacity(1000),
77 to_combo_q : VecDeque::<(f64,f64)>::with_capacity(1000),
78 mangl_q : VecDeque::<(f64,f64)>::with_capacity(1000),
79 rb_disc_q : VecDeque::<(f64,f64)>::with_capacity(1000),
80 lhit_fr_q : VecDeque::<(f64,f64)>::with_capacity(1000),
81 ch_len_mte : VecDeque::<(f64,f64)>::with_capacity(1000),
82 ch_len_rbe : VecDeque::<(f64,f64)>::with_capacity(1000),
83 }
84 }
85
86 pub fn receive_packet(&mut self) -> Result<(), SerializationError> {
87 match self.pkt_recv.try_recv() {
88 Err(_err) => {
89 debug!("Unable to receive heartbeat TofPacket!");
90 }
91 Ok(pack) => {
92 match pack.packet_type {
94 TofPacketType::MasterTriggerHB=> {
95 let hb : MasterTriggerHB = pack.unpack()?;
96 self.last_mtb = Some(hb);
101 },
102 TofPacketType::EventBuilderHB => {
103 let hb : EventBuilderHB = pack.unpack()?;
104 self.ev_c_q .push_back((hb.met_seconds as f64,hb.event_cache_size as f64));
109 if self.ev_c_q.len() > self.queue_size {
110 self.ev_c_q.pop_front();
111 }
112 self.to_q .push_back((hb.met_seconds as f64,hb.get_timed_out_trigger_frac()*100.0));
113 if self.to_q.len() > self.queue_size {
114 self.to_q.pop_front();
115 }
116 self.to_combo_q.push_back((hb.met_seconds as f64,hb.get_timed_out_combo_frac()*100.0));
117 if self.to_combo_q.len() > self.queue_size {
118 self.to_combo_q.pop_front();
119 }
120 self.mangl_q .push_back((hb.met_seconds as f64,hb.get_mangled_frac()*100.0));
121 if self.mangl_q.len() > self.queue_size {
122 self.mangl_q.pop_front();
123 }
124 self.rb_disc_q .push_back((hb.met_seconds as f64,hb.get_nrbe_discarded_frac()*100.0));
125 if self.rb_disc_q.len() > self.queue_size {
126 self.rb_disc_q.pop_front();
127 }
128 self.lhit_fr_q .push_back((hb.met_seconds as f64,hb.get_drs_lost_frac()*100.0));
129 if self.lhit_fr_q.len() > self.queue_size {
130 self.lhit_fr_q.pop_front();
131 }
132 self.ch_len_mte.push_back((hb.met_seconds as f64,hb.mte_receiver_cbc_len as f64));
133 if self.ch_len_mte.len() > self.queue_size {
134 self.ch_len_mte.pop_front();
135 }
136 self.ch_len_rbe.push_back((hb.met_seconds as f64,hb.rbe_receiver_cbc_len as f64));
137 if self.ch_len_rbe.len() > self.queue_size {
138 self.ch_len_rbe.pop_front();
139 }
140 self.last_evb = Some(hb);
141 }
142 TofPacketType::DataSinkHB => {
143 let hb : DataSinkHB = pack.unpack()?;
144 self.last_gds = Some(hb);
149 }
150 _ => () }
152 }
153 }
154 Ok(())
155 }
156
157 pub fn render(&mut self, main_window : &Rect, frame : &mut Frame) {
158
159 let main_lo = Layout::default()
160 .direction(Direction::Horizontal)
161 .constraints(
162 [Constraint::Percentage(50),
163 Constraint::Percentage(50)].as_ref(),
164 )
165 .split(*main_window);
166
167 let mut view_string = String::from("HB QUEUE EMPTY!");
168 match self.view {
173 HeartBeatView::EventBuilder => {
174 if self.last_evb.is_some() {
175 view_string = self.last_evb.unwrap().to_string();
176 }
177 let hb_ev_cols = Layout::default()
185 .direction(Direction::Horizontal)
186 .constraints(
187 [Constraint::Percentage(50),
188 Constraint::Percentage(50)].as_ref(),
189 )
190 .split(main_lo[1]);
191
192 let hb_ev_rows_left = Layout::default()
193 .direction(Direction::Vertical)
194 .constraints(
195 [Constraint::Percentage(25),
196 Constraint::Percentage(25),
197 Constraint::Percentage(25),
198 Constraint::Percentage(25)].as_ref(),
199 )
200 .split(hb_ev_cols[0]);
201 let hb_ev_rows_right = Layout::default()
202 .direction(Direction::Vertical)
203 .constraints(
204 [Constraint::Percentage(25),
205 Constraint::Percentage(25),
206 Constraint::Percentage(25),
207 Constraint::Percentage(25)].as_ref(),
208 )
209 .split(hb_ev_cols[1]);
210
211 let mut ts_label = String::from("Size of event cache [#evts]");
213 let ts_ev_theme = self.theme.clone();
214 let mut ts_ev_data = self.ev_c_q.clone();
215 let ts_ev = timeseries(&mut ts_ev_data,
216 ts_label.clone(),
217 ts_label.clone(),
218 &ts_ev_theme);
219 frame.render_widget(ts_ev, hb_ev_rows_left[0]);
220
221 ts_label = String::from("Fraction of timed out events [%]");
223 let ts_to_theme = self.theme.clone();
224 let mut ts_to_data = self.to_q.clone();
225 let ts_to = timeseries(&mut ts_to_data,
226 ts_label.clone(),
227 ts_label.clone(),
228 &ts_to_theme);
229 frame.render_widget(ts_to, hb_ev_rows_left[1]);
230
231 ts_label = String::from("Fraction of timed out events (combo) [%]");
233 let ts_to_combo_theme = self.theme.clone();
234 let mut ts_to_combo_data = self.to_combo_q.clone();
235 let ts_to_combo = timeseries(&mut ts_to_combo_data,
236 ts_label.clone(),
237 ts_label.clone(),
238 &ts_to_combo_theme);
239 frame.render_widget(ts_to_combo, hb_ev_rows_right[1]);
240
241 ts_label = String::from("Fraction of mangled events [%]");
243 let ts_dm_theme = self.theme.clone();
244 let mut ts_dm_data = self.mangl_q.clone();
245 let ts_dm = timeseries(&mut ts_dm_data,
246 ts_label.clone(),
247 ts_label.clone(),
248 &ts_dm_theme);
249 frame.render_widget(ts_dm, hb_ev_rows_left[2]);
250
251 ts_label = String::from("Fraction of discarded RBEvents [%]");
253 let ts_rbdisc_theme = self.theme.clone();
254 let mut ts_rbdisc_data = self.rb_disc_q.clone();
255 let ts_rbdisc = timeseries(&mut ts_rbdisc_data,
256 ts_label.clone(),
257 ts_label.clone(),
258 &ts_rbdisc_theme);
259 frame.render_widget(ts_rbdisc, hb_ev_rows_left[3]);
260
261 ts_label = String::from("Fraction of DRS4 dead hits [%]");
263 let ts_lh_theme = self.theme.clone();
264 let mut ts_lh_data = self.lhit_fr_q.clone();
265 let ts_lh = timeseries(&mut ts_lh_data,
266 ts_label.clone(),
267 ts_label.clone(),
268 &ts_lh_theme);
269 frame.render_widget(ts_lh, hb_ev_rows_right[0]);
270
271 ts_label = String::from("Incoming MTE buffer len [#ets]");
273 let ts_mtech_theme = self.theme.clone();
274 let mut ts_mtech_data = self.ch_len_mte.clone();
275 let ts_mtech = timeseries(&mut ts_mtech_data,
276 ts_label.clone(),
277 ts_label.clone(),
278 &ts_mtech_theme);
279 frame.render_widget(ts_mtech, hb_ev_rows_right[2]);
280
281 ts_label = String::from("Incoming RBE buffer len [#ets]");
283 let ts_rbech_theme = self.theme.clone();
284 let mut ts_rbech_data = self.ch_len_rbe.clone();
285 let ts_rbech = timeseries(&mut ts_rbech_data,
286 ts_label.clone(),
287 ts_label.clone(),
288 &ts_rbech_theme);
289 frame.render_widget(ts_rbech, hb_ev_rows_right[3]);
290
291 }
292 HeartBeatView::MTB => {
293 if self.last_mtb.is_some() {
294 view_string = self.last_mtb.unwrap().to_string();
295 }
296 }
297 HeartBeatView::DataSink => {
298 if self.last_gds.is_some() {
299 view_string = self.last_gds.unwrap().to_string();
300 }
301 }
302 }
303
304 let hb_view = Paragraph::new(view_string)
305 .style(Style::default().fg(Color::LightCyan))
307 .alignment(Alignment::Left)
308 .block(
310 Block::default()
311 .borders(Borders::ALL)
312 .style(self.theme.style())
313 .title("Last Heartbeat \u{1f493}")
314 .border_type(BorderType::Rounded),
315 );
316 frame.render_widget(hb_view, main_lo[0]);
317 }
318}