pub trait EdgeTelemetry { /// Add a new telemetry reading to the underlying storage type // TODO: update the timestamp to something that makes more sense maybe? fn add_reading(&mut self, entry: E); /// Returns the compressed data as a collection of tuples, where each tuple has the format: /// `(usage, consecutive occurances)` fn get_compressed_data<'a>(&'a self) -> impl Iterator where E: 'a; } mod entry { #[derive(Clone)] pub struct Entry { timestamp: usize, reading: usize, } impl Entry { pub fn new(timestamp: usize, reading: usize) -> Self { Self { timestamp, reading } } pub fn timestamp(&self) -> usize { self.timestamp } pub fn reading(&self) -> usize { self.reading } } #[cfg(test)] mod test { use super::*; #[test] fn create_entry() { let t = 3; let r = 1; let e = Entry::new(t, r); assert_eq!(t, e.timestamp()); assert_eq!(r, e.reading()); } } } mod storage { use std::collections::VecDeque; use crate::EdgeTelemetry; use crate::entry::Entry; pub struct RingBufferStorage { size: usize, buf: VecDeque<(Entry, usize)>, } impl RingBufferStorage { pub fn new(size: usize) -> Self { Self { size, buf: VecDeque::with_capacity(size), } } pub fn add_entry(&mut self, entry: Entry) { // buffer is not empty, and the previous value matches if let Some((prev, quantity)) = self.buf.iter_mut().last() && prev.reading() == entry.reading() { // TODO: add some logic here to overflow into a new entry if needed *quantity = quantity.saturating_add(1); } // buffer is either empty, or the previous value does not match else { // check capacity to make sure we aren't full (not really necessary on the empty case but this is a little easier to read) if self.buf.len() == self.size { self.buf.pop_front(); } self.buf.push_back((entry, 1)); } } } impl EdgeTelemetry for RingBufferStorage { fn add_reading(&mut self, entry: Entry) { self.add_entry(entry); } fn get_compressed_data<'a>(&'a self) -> impl Iterator where Entry: 'a, { self.buf.iter() } } } #[cfg(test)] mod test { use crate::EdgeTelemetry; use crate::entry::Entry; use crate::storage::RingBufferStorage; #[test] fn add_and_get_reading() { let size = 10; let mut s = RingBufferStorage::new(size); let t = 1; let r = 3; let e = Entry::new(t, r); s.add_reading(e); let data = s.get_compressed_data(); data.into_iter().for_each(|(entry, quantity)| { assert_eq!(entry.timestamp(), t); assert_eq!(entry.reading(), r); assert_eq!(*quantity, 1); }); } #[test] fn duplicate_readings() { let size = 10; let mut s = RingBufferStorage::new(size); let t = 1; let r = 3; let q = 3; for _ in 0..q { let e = Entry::new(t, r); s.add_reading(e); } let data = s.get_compressed_data(); data.into_iter().for_each(|(entry, quantity)| { assert_eq!(entry.timestamp(), t); assert_eq!(entry.reading(), r); assert_eq!(*quantity, q); }); } } fn main() { println!("Hello, world!"); } // NOTES // * assuming the sample rate is constant, we can extract sample time from the run length by knowing the base stamp, and incrementing to the offset // * this also means we can actually just stamp the start time and then compute the sample time via the offset