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