puzzles/edge-telemetry/src/main.rs

157 lines
4.1 KiB
Rust

pub trait EdgeTelemetry<E> {
/// 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<Item = &'a (E, usize)>
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<Entry> for RingBufferStorage {
fn add_reading(&mut self, entry: Entry) {
self.add_entry(entry);
}
fn get_compressed_data<'a>(&'a self) -> impl Iterator<Item = &'a (Entry, usize)>
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