Skip to main content

gondola_core/
monitoring.rs

1// This file is part of gaps-online-software and published 
2// under the GPLv3 license
3
4pub mod pa_moni_data;
5pub use pa_moni_data::{
6  PAMoniData,
7  PAMoniDataSeries
8};
9pub mod pb_moni_data;
10pub use pb_moni_data::{
11  PBMoniData,
12  PBMoniDataSeries
13};
14pub mod mtb_moni_data;
15pub use mtb_moni_data::{
16  MtbMoniData,
17  MtbMoniDataSeries
18};
19pub mod ltb_moni_data;
20pub use ltb_moni_data::{
21  LTBMoniData,
22  LTBMoniDataSeries
23};
24pub mod rb_moni_data;
25pub use rb_moni_data::{
26  RBMoniData,
27  RBMoniDataSeries
28};
29pub mod cpu_moni_data;
30pub use cpu_moni_data::{
31  CPUMoniData,
32  CPUMoniDataSeries
33};
34
35pub mod heartbeats;
36pub use heartbeats::{
37  DataSinkHB,
38  DataSinkHBSeries,
39  MasterTriggerHB,
40  MasterTriggerHBSeries,
41  EventBuilderHB,
42  EventBuilderHBSeries,
43};
44
45pub mod run_statistics;
46pub use run_statistics::RunStatistics;
47
48pub mod sip_position;
49pub use sip_position::{
50  SipPosMoniData,
51  SipPosMoniDataSeries
52};
53
54pub mod sip_pressure;
55pub use sip_pressure::{
56  SipPresMoniData,
57  SipPresMoniDataSeries,
58};
59
60pub mod sip_time;
61pub use sip_time::{
62  SipTimeMoniData,
63  SipTimeMoniDataSeries
64};
65
66pub mod gcu_ev_stats;
67pub use gcu_ev_stats::{
68  GcuEvBldStatsMoniData,
69  GcuEvBldStatsMoniDataSeries
70};
71
72pub mod tracker_gps;
73pub use tracker_gps::{
74  TrackerGpsMoniData,
75  TrackerGpsMoniDataSeries
76}; 
77
78pub mod cooling; 
79pub use cooling::{
80  CoolingMoniData,
81  CoolingMoniDataSeries
82};
83
84pub mod wastie;
85pub use wastie::*;
86
87use std::collections::VecDeque;
88use std::collections::HashMap;
89
90#[cfg(feature="pybindings")]
91use crate::prelude::*;
92
93#[cfg(feature="pybindings")]
94use polars::frame::column::Column;
95#[cfg(feature="pybindings")]
96use polars::prelude::NamedFrom; 
97
98/// Monitoring data shall share the same kind 
99/// of interface. 
100pub trait MoniData {
101  /// Monitoring data is always tied to a specific
102  /// board. This might not be its own board, but 
103  /// maybe the RB the data was gathered from
104  /// This is an unique identifier for the 
105  /// monitoring data across the same type. 
106  ///
107  /// MoniData which only belongs to a single board will 
108  /// have board id 0
109  fn get_board_id(&self) -> u8;
110  
111  /// Access the (data) members by name 
112  fn get(&self, varname : &str) -> Option<f32>;
113
114  /// A list of the variables in this MoniData
115  fn keys() -> Vec<&'static str>;
116
117  fn get_timestamp(&self) -> u64 {
118    0
119  }
120
121  fn set_timestamp(&mut self, _ts : u64) {
122  }
123}
124
125// FIXME - this whole system gets too much. We need to reorganize this 
126// -> push it to 0.13. For now, just make the timestamp settable, which 
127// will basically achieve the same thing
128///// A specialized MoniData series, originating in housekeeping 
129///// from the TOF system. During flight, the housekeeping 
130///// data has been wrapped in telemetry packets as an "outer layer" 
131//pub trait TofMoniData 
132//  where Self : MoniData {
133//  
134//  // Read TofMoniData from TelemetryPackets, since technically, 
135//  // they are not TelemetryPackable
136//  fn from_telemetrypacket(packet : TelemetryPacket) -> PyResult<Self> {
137//  } 
138//}
139
140
141/// A MoniSeries is a collection of (primarily) monitoring
142/// data, which comes from multiple senders.
143/// E.g. a MoniSeries could hold RBMoniData from all 
144/// 40 ReadoutBoards.
145pub trait MoniSeries<T>
146  where T : Copy + MoniData {
147
148  fn get_first_ts(&self) -> u64;
149
150  fn get_data(&self) -> &HashMap<u8,VecDeque<T>>;
151
152  fn get_data_mut(&mut self) -> &mut HashMap<u8,VecDeque<T>>;
153 
154  fn get_max_size(&self) -> usize;
155
156  fn set_max_size(&mut self, size : usize);
157
158  fn get_timestamps(&self) -> &Vec<u64>;
159
160  fn add_timestamp(&mut self, ts : u64);
161
162  /// A HashMap of -> rbid, Vec\<var\> 
163  fn get_var(&self, varname : &str) -> HashMap<u8, Vec<f32>> {
164    let mut values = HashMap::<u8, Vec<f32>>::new();
165    for k in self.get_data().keys() {
166      match self.get_var_for_board(varname, k) {
167        None => (),
168        Some(vals) => {
169          values.insert(*k, vals);
170        }
171      }
172      //values.insert(*k, Vec::<f32>::new());
173      //match self.get_data().get(k) {
174      //  None => (),
175      //  Some(vec_moni) => {
176      //    for moni in vec_moni {
177      //      match moni.get(varname) {
178      //        None => (),
179      //        Some(val) => {
180      //          values.get_mut(k).unwrap().push(val);
181      //        }
182      //      }
183      //    }
184      //  }
185      //}
186    }
187    values
188  }
189
190  /// Get a certain variable, but only for a single board
191  fn get_var_for_board(&self, varname : &str, rb_id : &u8) -> Option<Vec<f32>> {
192    let mut values = Vec::<f32>::new();
193    match self.get_data().get(&rb_id) {
194      None => (),
195      Some(vec_moni) => {
196        for moni in vec_moni {
197          match moni.get(varname) {
198            None => {
199              return None;
200            },
201            Some(val) => {
202              values.push(val);
203            }
204          }
205        }
206      }
207    }
208    // FIXME This needs to be returning a reference,
209    // not cloning
210    Some(values)
211  }
212
213  #[cfg(feature = "pybindings")]
214  fn get_dataframe(&self) -> PolarsResult<DataFrame> {
215    let mut series = Vec::<Column>::new();
216    for k in Self::keys() {
217      match self.get_series(k) {
218        None => {
219          error!("Unable to get series for {}", k);
220        }
221        Some(ser) => {
222          //println!("{}", ser);
223          series.push(ser.into());
224        }
225      }
226    }
227    //if self.get_timestamps().len() > 0 {
228    //  let timestamps  = Series::new("timestamps".into(), self.get_timestamps());
229    //  series.push(timestamps.into());
230    //}
231    // each column is now the specific variable but in terms for 
232    // all rbs
233    let df = DataFrame::new(series)?;
234    Ok(df)
235  }
236
237  #[cfg(feature = "pybindings")]
238  /// Get the variable for all boards. This keeps the order of the 
239  /// underlying VecDeque. Values of all boards intermixed.
240  /// To get a more useful version, use the Dataframe instead.
241  ///
242  /// # Arguments
243  ///
244  /// * varname : The name of the attribute of the underlying
245  ///             moni structure
246  fn get_series(&self, varname : &str) -> Option<Series> {
247    let mut data = Vec::<f32>::with_capacity(self.get_data().len());
248    let sorted_keys: Vec<u8> = self.get_data().keys().cloned().collect();
249    for board_id in sorted_keys.iter() {
250      let dqe = self.get_data().get(board_id).unwrap(); //uwrap is fine, bc we checked earlier
251      for moni in dqe {
252        match moni.get(varname) {
253          None => {
254            error!("This type of MoniData does not have a key called {}", varname);
255            return None;
256          }
257          Some(var) => {
258            data.push(var);
259          }
260        }
261      }
262    }
263    let series = Series::new(varname.into(), data);
264    Some(series)
265  }
266
267  /// A list of the variables in this MoniSeries
268  fn keys() -> Vec<&'static str> {
269    T::keys()
270  }
271
272  /// A list of boards in this series
273  fn get_board_ids(&self) -> Vec<u8> {
274    self.get_data().keys().cloned().collect()
275  }
276
277  /// Add another instance of the data container to the series
278  fn add(&mut self, data : T) {
279    let board_id = data.get_board_id();
280    if !self.get_data().contains_key(&board_id) {
281      self.get_data_mut().insert(board_id, VecDeque::<T>::new());
282    } 
283    self.get_data_mut().get_mut(&board_id).unwrap().push_back(data);
284    if self.get_data_mut().get_mut(&board_id).unwrap().len() > self.get_max_size() {
285      error!("The queue is too large, returning the first element! If you need a larger series size, set the max_size field");
286      self.get_data_mut().get_mut(&board_id).unwrap().pop_front();
287    }
288  }
289 
290  fn get_last_moni(&self, board_id : u8) -> Option<T> {
291    let size = self.get_data().get(&board_id)?.len();
292    Some(self.get_data().get(&board_id).unwrap()[size - 1])
293  }
294}
295
296//--------------------------------------------------
297
298#[macro_export]
299macro_rules! moniseries_general {
300  ($name : ident, $class:ty) => {
301     use std::collections::VecDeque;
302     use std::collections::HashMap;
303
304     use crate::monitoring::MoniSeries;
305
306     #[cfg_attr(feature="pybindings",pyclass)]
307     #[derive(Debug, Clone, PartialEq)]
308     pub struct $name {
309       data        : HashMap<u8, VecDeque<$class>>,
310       max_size    : usize,
311       timestamps  : Vec<u64>,
312     }
313     
314     impl $name {
315       pub fn new() -> Self {
316        Self {
317           data       : HashMap::<u8, VecDeque<$class>>::new(),
318           max_size   : 10000,
319           timestamps : Vec::<u64>::new()
320         }
321       }
322     } 
323     
324     impl Default for $name {
325       fn default() -> Self {
326         Self::new()
327       }
328     }
329     
330     impl fmt::Display for $name {
331       fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
332         write!(f, "<{} : {} boards>", stringify!($name), self.data.len())
333       }
334     }
335     
336     impl MoniSeries<$class> for $name {
337   
338       fn get_first_ts(&self) -> u64 {
339         if self.timestamps.len() == 0 {
340           return 0;
341         } else {
342           self.timestamps[0]
343         }
344       }
345
346       fn get_data(&self) -> &HashMap<u8,VecDeque<$class>> {
347         return &self.data;
348       }
349     
350       fn get_data_mut(&mut self) -> &mut HashMap<u8,VecDeque<$class>> {
351         return &mut self.data;
352       }
353      
354       fn get_max_size(&self) -> usize {
355         return self.max_size;
356       }
357       
358       fn set_max_size(&mut self, size : usize) {
359         self.max_size = size;
360       }
361  
362       fn add_timestamp(&mut self, ts : u64) {
363         self.timestamps.push(ts);
364       }
365
366       fn get_timestamps(&self) -> &Vec<u64> {
367         //if self.timestamps.len() == 0 {
368         //  let mut timestamps = Vec::<u64>::new();
369         //  for k in self.
370         //} 
371         return &self.timestamps;
372       }
373     }
374  
375     #[cfg(feature="pybindings")]
376     #[pymethods]
377     impl $name {
378       #[new]
379       fn new_py() -> Self {
380         Self::new() 
381       }
382  
383       #[pyo3(name="keys")]
384       #[staticmethod]
385       fn keys_py() -> Vec<&'static str> {
386         Self::keys()
387       }
388
389       /// The maximum size of the series. If more data 
390       /// are added, data from the front will be removed 
391       #[getter]
392       #[pyo3(name="max_size")]
393       fn get_max_size_py(&self) -> usize {
394         self.get_max_size()
395       }
396
397       #[setter]
398       #[pyo3(name="max_size")] 
399       fn set_max_size_py(&mut self, size : usize) {
400         self.set_max_size(size);
401       }
402       
403       #[getter]
404       #[pyo3(name="first_ts")]
405       pub fn get_first_ts_py(&self) -> u64 {
406         self.get_first_ts()
407       }
408
409       /// If monitoring is retrieved from telemetry, we 
410       /// save the gcu timestamp of the packet, wich 
411       /// herein can be accessed.
412       #[getter] 
413       #[pyo3(name="timestamps")] 
414       fn get_timestamps_py(&self) -> Vec<u64> {
415         warn!("This returns a full copy and is a performance bottleneck!");
416         return self.timestamps.clone();
417       }
418
419       /// Manually add a timestamp. There is a caveat to the 
420       /// timestamps. In general, f32 is not large enough to 
421       /// hold a 48bit timestamp, as the gcutime. 
422       ///
423       /// This needs some overhaul, this will be done in v0.13. 
424       /// For now, we can keep track manually
425       #[pyo3(name="add_timestamp")]
426       fn add_timestamp_py(&mut self, ts : u64) {
427         self.timestamps.push(ts);
428       }
429
430       /// Add a new daughter item to the series
431       #[pyo3(name="add")]
432       fn add_py(&mut self, moni : $class) {
433         self.add(moni);
434       }
435
436       #[pyo3(name="get_var_for_board")]
437       fn get_var_for_board_py(&self, varname : &str, rb_id : u8) -> Option<Vec<f32>> {
438         self.get_var_for_board(varname, &rb_id)
439       }
440
441       /// Reduces the MoniSeries to a single polars data frame
442       /// The structure itself will not be changed
443       #[pyo3(name="get_dataframe")]
444       fn get_dataframe_py(&self) -> PyResult<PyDataFrame> {
445         match self.get_dataframe() {
446           Ok(df) => {
447             let pydf = PyDataFrame(df);
448             return Ok(pydf);
449           },
450           Err(err) => {
451             return Err(PyValueError::new_err(err.to_string()));
452           }
453         }
454       } 
455     }
456     #[cfg(feature="pybindings")]
457     pythonize_display!($name);
458  }
459}
460
461/// Implements the moniseries trait for a MoniData 
462/// type of class
463// this should basically be moniseries_tof
464#[macro_export]
465macro_rules! moniseries {
466  ($name : ident, $class:ty) => {
467    
468    moniseries_general!($name, $class);
469
470    #[cfg(feature="pybindings")]
471    #[pymethods]
472    impl $name {
473      
474      //fn __len__(&self) -> usize {
475      //  self.items.len()
476      //}
477      
478      /// Add an additional (Caraspace) file to the series 
479      ///
480      /// # Arguments:
481      ///   * filename    : The name of the (caraspace) file to add
482      ///   * from_object : Since this adds caraspace files, it is possible 
483      ///                   to choose from where to get the information.
484      ///                   Either the telemetry packet, or the tofpacket, if 
485      ///                   either is present in the frame. When 
486      ///                   CRFrameObjectType = Unknown, it will figure it out 
487      ///                   automatically, preferring the telemetry since it has
488      ///                   the gcu timestamp
489      #[pyo3(signature = (filename, from_object = CRFrameObjectType::TelemetryPacket))]
490      fn add_crfile(&mut self, filename : String, from_object : CRFrameObjectType) {
491        let reader = CRReader::new(filename).expect("Unable to open file!");
492        // now we have a problem - from which frame should we get it?
493        // if we get it from the dedicated TOF stream it will be much 
494        // faster (if that is available) since it will be it's own 
495        // presence in the frame
496        //let address = &source.clone();
497        //let mut try_from_telly = false;
498        let tp_source     = String::from("TofPacketType.") + stringify!($class);
499        let tp_source_alt = String::from("PacketType.") + stringify!($class);
500        let tel_source    = "TelemetryPacketType.AnyTofHK";
501        for frame in reader {
502          match from_object { 
503            CRFrameObjectType::TofPacket =>  {
504              if frame.has(&tp_source) || frame.has(&tp_source_alt) {
505                if frame.has(&tp_source) {
506                  let moni_res = frame.get::<TofPacket>(&tp_source).unwrap().unpack::<$class>();
507                  match moni_res {
508                    Err(err) => {
509                      println!("Error unpacking! {err}");
510                    }
511                    Ok(moni) => {
512                      self.add(moni);
513                    }
514                  }
515                }
516                if frame.has(&tp_source_alt) {
517                  let moni_res = frame.get::<TofPacket>(&tp_source_alt).unwrap().unpack::<$class>();
518                  match moni_res { 
519                    Err(err) => {
520                      println!("Error unpacking! {err}");
521                    }
522                    Ok(moni) => {
523                      self.add(moni);
524                    }
525                  }
526                }
527              } 
528            }
529            CRFrameObjectType::TelemetryPacket | CRFrameObjectType::Unknown => {
530              if frame.has(tel_source) {
531                let hk_res = frame.get::<TelemetryPacket>(tel_source);
532                match hk_res {
533                  Err(err) => {
534                    println!("Error unpacking! {err}");
535                  }
536                  Ok(hk) => {
537                    let mut pos = 0;
538                    // subtract the 2020/1/1 midnight from the gcutime to make 
539                    // it f32
540                    let gcutime = hk.header.get_gcutime() as u64;
541                    match TofPacket::from_bytestream(&hk.payload, &mut pos) {
542                      Err(err) => {
543                        println!("Error unpackin! {err}");
544                      }
545                      Ok(tp) => {
546                        if tp.packet_type == <$class>::TOF_PACKET_TYPE  {
547                          match tp.unpack::<$class>() {
548                            Err(err) => {
549                              println!("Error unpacking! {err}");
550                            }
551                            Ok(mut moni) => {
552                              self.add_timestamp(gcutime);
553                              moni.set_timestamp(gcutime - self.get_first_ts()); 
554                              self.add(moni);
555                              //self.timestamps.push(gcutime);
556                            }
557                          }
558                        }
559                      }
560                    } 
561                  }
562                }
563              }
564            }
565            _ => () // do nothing for other types
566          }
567        }
568      }
569      
570      /// Add an additional (Telemetry) file to the series 
571      ///
572      /// # Arguments:
573      ///   * filename    : The name of the (telemetry) file to add
574      fn add_telemetryfile(&mut self, filename : String) {
575        let reader = TelemetryPacketReader::new(filename, true, None, None, 0, 0);
576        for pack in reader {
577          if pack.header.packet_type == TelemetryPacketType::AnyTofHK {
578            let mut pos = 0;
579            // subtract the 2020/1/1 midnight from the gcutime to make 
580            // it f32
581            let gcutime = pack.header.get_gcutime() as u64;
582            match TofPacket::from_bytestream(&pack.payload, &mut pos) {
583              Err(err) => {
584                println!("Error unpackin! {err}");
585              }
586              Ok(tp) => {
587                if tp.packet_type == <$class>::TOF_PACKET_TYPE  {
588                  match tp.unpack::<$class>() {
589                    Err(err) => {
590                      println!("Error unpacking! {err}");
591                    }
592                    Ok(mut moni) => {
593                      self.add_timestamp(gcutime);
594                      moni.set_timestamp(gcutime - self.get_first_ts()); 
595                      self.add(moni);
596                      //self.timestamps.push(gcutime);
597                    }
598                  }
599                }
600              }
601            } 
602          }
603        }
604      }
605      
606      /// Add an additional (Tof) file to the series 
607      ///
608      /// # Arguments:
609      ///   * filename    : The name of the (caraspace) file to add
610      ///   * from_object : Since this adds caraspace files, it is possible 
611      ///                   to choose from where to get the information.
612      ///                   Either the telemetry packet, or the tofpacket, if 
613      ///                   either is present in the frame. When 
614      ///                   CRFrameObjectType = Unknown, it will figure it out 
615      ///                   automatically, preferring the telemetry since it has
616      ///                   the gcu timestamp
617      #[pyo3(signature = (filename))]
618      fn add_toffile(&mut self, filename : String) {
619        let reader = TofPacketReader::new(&filename);
620        for tp in reader { 
621          if tp.packet_type == <$class>::TOF_PACKET_TYPE  {
622            match tp.unpack::<$class>() {
623              Err(err) => {
624                println!("Error unpacking! {err}");
625              }
626              Ok(mut moni) => {
627                self.add_timestamp(moni.get_timestamp());
628                moni.set_timestamp(moni.get_timestamp()); 
629                self.add(moni);
630                //self.timestamps.push(gcutime);
631              }
632            }
633          }
634        }
635      }
636
637      /// Generate a polars dataframe with monitoring data from the 
638      /// given TOF file.
639      /// This will load ONLY data of the specific type of the 
640      /// MoniSeries itself
641      ///
642      /// # Arguments:
643      ///   * filename : A single .tof.gaps file with monitoring 
644      ///                information 
645      #[staticmethod]
646      fn from_tof_file(filename : String) -> PyResult<PyDataFrame> {
647        let mut reader = TofPacketReader::new(&filename);
648        let mut series = Self::new();
649        // it would be nice to set the filter here, but I 
650        // don't know how that can be done in the macro
651        reader.filter  = <$class>::TOF_PACKET_TYPE;
652        for tp in reader {
653          if let Ok(moni) =  tp.unpack::<$class>() {
654            series.add(moni);
655          }
656          // other packets will get thrown away 
657        }
658        match series.get_dataframe() {
659          Ok(df) => {
660            let pydf = PyDataFrame(df);
661            return Ok(pydf);
662          },
663          Err(err) => {
664            return Err(PyValueError::new_err(err.to_string()));
665          }
666        }
667      }  
668    }
669  }
670}
671
672// ------------------------------------------------
673
674// baiscally the same macro, but for data coming 
675// from telemetry 
676
677/// Implements the moniseries trait for a MoniData 
678/// type of class
679#[macro_export]
680macro_rules! moniseries_telemetry {
681  ($name : ident, $class:ty) => {
682    
683    moniseries_general!($name, $class);
684
685    #[cfg(feature="pybindings")]
686    #[pymethods]
687    impl $name {
688
689      /// Add an additional (Caraspace) file to the series 
690      ///
691      /// # Arguments:
692      ///   * filename    : The name of the (caraspace) file to add
693      ///   * from_object : Since this adds caraspace files, it is possible 
694      ///                   to choose from where to get the information.
695      ///                   Either the telemetry packet, or the tofpacket, if 
696      ///                   either is present in the frame. When 
697      ///                   CRFrameObjectType = Unknown, it will figure it out 
698      ///                   automatically, preferring the telemetry since it has
699      ///                   the gcu timestamp
700      #[pyo3(signature = (filename, from_object = CRFrameObjectType::TelemetryPacket))]
701      fn add_crfile(&mut self, filename : String, from_object : CRFrameObjectType) {
702        let reader = CRReader::new(filename).expect("Unable to open file!");
703        // now we have a problem - from which frame should we get it?
704        // if we get it from the dedicated TOF stream it will be much 
705        // faster (if that is available) since it will be it's own 
706        // presence in the frame
707        //let address = &source.clone();
708        //let mut try_from_telly = false;
709        let tel_source    = "TelemetryPacketType.AnyTofHK";
710        for frame in reader {
711          match from_object { 
712            CRFrameObjectType::TelemetryPacket | CRFrameObjectType::Unknown => {
713              if frame.has(tel_source) {
714                let hk_res = frame.get::<TelemetryPacket>(tel_source);
715                match hk_res {
716                  Err(err) => {
717                    println!("Error unpacking! {err}");
718                  }
719                  Ok(hk) => {
720                    if hk.header.packet_type != <$class>::TEL_PACKET_TYPE  {
721                      // this is not the packet you are looking for 
722                      continue;
723                    }   
724                    let mut pos = 0;
725                    // subtract the 2020/1/1 midnight from the gcutime to make 
726                    // it f32
727                    let gcutime = hk.header.get_gcutime() as u64;
728                    match <$class>::from_bytestream(&hk.payload, &mut pos) {
729                      Err(err) => {
730                        println!("Error unpackin! {err}");
731                      }
732                      Ok(mut moni) => {
733                        self.add_timestamp(gcutime);
734                        moni.set_timestamp(gcutime - self.get_first_ts()); 
735                        self.add(moni);
736                        //self.timestamps.push(gcutime);
737                      }
738                    } 
739                  }
740                }
741              }
742            }
743            _ => ()
744          }
745        }
746      }
747      
748      /// Add an additional (Telemetry) file to the series 
749      ///
750      /// # Arguments:
751      ///   * filename    : The name of the (telemetry) file to add
752      fn add_telemetryfile(&mut self, filename : String) {
753        let reader = TelemetryPacketReader::new(filename, true, None, None, 0, 0);
754        for pack in reader {
755          if pack.header.packet_type == <$class>::TEL_PACKET_TYPE {
756            let mut pos = 0;
757            // subtract the 2020/1/1 midnight from the gcutime to make 
758            // it f32
759            let gcutime = pack.header.get_gcutime() as u64;
760            match <$class>::from_bytestream(&pack.payload, &mut pos) {
761              Err(err) => {
762                println!("Error unpackin! {err}");
763              }
764              Ok(mut moni) => {
765                self.add_timestamp(gcutime);
766                moni.set_timestamp(gcutime - self.get_first_ts()); 
767                self.add(moni);
768              }
769            }
770          }
771        }
772      }
773    }
774  }
775}
776
777