157 lines
4.1 KiB
Rust
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
|