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