use std::{fmt::Debug, num::NonZeroUsize};
use crate::daemon::{
clock_sync_algorithm::RingBuffer,
event::TscRtt,
time::{Duration, TscCount, tsc::Period},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Local<T> {
inner: RingBuffer<T>,
rtt_threshold_multiplier: usize,
window: Duration,
}
impl<T> Local<T> {
pub const SKM_WINDOW: Duration = Duration::from_secs(1024);
pub fn new(capacity: NonZeroUsize) -> Self {
Self {
inner: RingBuffer::new(capacity),
rtt_threshold_multiplier: 5,
window: Self::SKM_WINDOW,
}
}
#[must_use]
pub fn with_window(mut self, window: Duration) -> Self {
self.window = window;
self
}
#[must_use]
pub fn with_rtt_threshold_multiplier(mut self, rtt_threshold_multiplier: usize) -> Self {
self.rtt_threshold_multiplier = rtt_threshold_multiplier;
self
}
pub fn window(&self) -> Duration {
self.window
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &T> {
self.inner.iter()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn handle_disruption(&mut self) {
let Self {
inner,
rtt_threshold_multiplier: _rtt,
window: _,
} = self;
inner.clear();
}
pub fn rtt_threshold_multiplier(&self) -> usize {
self.rtt_threshold_multiplier
}
}
impl<T: TscRtt + Debug> Local<T> {
pub fn feed(&mut self, event: T) -> Result<(), FeedError<T>> {
self.push_if_newer(event)
}
fn push_if_newer(&mut self, event: T) -> Result<(), FeedError<T>> {
if let Some(last) = self.inner.head()
&& event.counter_post() <= last.counter_post()
{
return Err(FeedError::Old {
latest_tsc: last.counter_post(),
event,
});
}
self.inner.push(event);
Ok(())
}
pub fn expunge_old_events(&mut self, period: Period) {
if self.inner.is_empty() {
return;
}
match self.cutoff(self.window, period) {
Some(cutoff_tsc) => self.inner.expunge_old(cutoff_tsc),
None => {
tracing::debug!(%period, ?self.window, "We booted less than 1 window ago. Not deleting events.");
}
}
}
pub fn cutoff(&self, window: Duration, period: Period) -> Option<TscCount> {
let now_post_tsc = self.inner.head()?.counter_post();
let cutoff_tsc = now_post_tsc - (window / period);
(cutoff_tsc > TscCount::EPOCH).then_some(cutoff_tsc)
}
pub fn window_start(&self, window: Duration, period: Period) -> usize {
if window >= self.window {
return 0;
}
match self.cutoff(window, period) {
Some(cutoff_tsc) => self.inner.index_at_or_after(cutoff_tsc),
None => 0,
}
}
}
impl<T> AsRef<RingBuffer<T>> for Local<T> {
fn as_ref(&self) -> &RingBuffer<T> {
&self.inner
}
}
#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
pub enum FeedError<T: std::fmt::Debug> {
#[error(
"Event is older than latest event. Buffer's latest event TSC {latest_tsc:?}, event: {event:?}."
)]
Old { latest_tsc: TscCount, event: T },
}
#[cfg(test)]
mod tests {
use super::super::test_assets::TestEvent;
use super::*;
use std::num::NonZeroUsize;
#[test]
fn new_buffer() {
let buffer: Local<TestEvent> = Local::new(NonZeroUsize::new(5).unwrap());
assert!(buffer.as_ref().is_empty())
}
#[test]
fn feed_empty_buffer() {
let mut buffer: Local<TestEvent> = Local::new(NonZeroUsize::new(5).unwrap());
let event = TestEvent::new(5, 10);
assert!(buffer.feed(event).is_ok());
assert_eq!(buffer.iter().count(), 1);
}
#[test]
fn push_stale() {
let mut buffer: Local<TestEvent> = Local::new(NonZeroUsize::new(5).unwrap());
let event1 = TestEvent::new(100, 110);
buffer.feed(event1).unwrap();
let event2 = TestEvent::new(50, 60);
let err = buffer.feed(event2).unwrap_err();
assert!(matches!(err, FeedError::Old { .. }));
}
#[test]
fn push_newer() {
let mut buffer: Local<TestEvent> = Local::new(NonZeroUsize::new(5).unwrap());
let event1 = TestEvent::new(100, 110);
buffer.feed(event1).unwrap();
let event3 = TestEvent::new(150, 180);
buffer.feed(event3).unwrap();
}
#[test]
fn retain_high_quality_events() {
let mut buffer: Local<TestEvent> = Local::new(NonZeroUsize::new(5).unwrap());
let period = Period::from_seconds(1e-9);
let events = vec![
TestEvent::new(0, 10),
TestEvent::new(500_000_000_000, 500_000_000_000 + 10), TestEvent::new(1_500_000_000_000, 1_500_000_000_000 + 10), ];
for event in events {
buffer.feed(event).unwrap();
}
buffer.expunge_old_events(period);
for event in buffer.iter() {
assert!(event.counter_post().get() >= 500_000_000_000);
}
}
#[test]
fn buffer_capacity() {
let mut buffer: Local<TestEvent> = Local::new(NonZeroUsize::new(3).unwrap());
for i in 0..5 {
let event = TestEvent::new(i * 100, i * 100 + 10);
assert!(buffer.feed(event).is_ok());
}
assert_eq!(buffer.iter().count(), 3);
for (i, event) in buffer.iter().enumerate() {
assert_eq!(event.counter_pre().get(), ((i + 2) * 100) as i64);
}
}
#[test]
fn handle_disruption() {
let mut local = Local::new(NonZeroUsize::new(3).unwrap());
let events = vec![
TestEvent::pre_and_rtt(1000, 100),
TestEvent::pre_and_rtt(2000, 90),
TestEvent::pre_and_rtt(3000, 80),
];
for event in &events {
local.feed(event.clone()).unwrap();
}
let thresh = local.rtt_threshold_multiplier;
assert_eq!(local.inner.len(), 3);
local.handle_disruption();
assert_eq!(local.inner.len(), 0);
assert_eq!(local.rtt_threshold_multiplier, thresh); }
#[test]
fn cutoff_is_window_before_head_or_none_near_epoch() {
let period = Period::from_seconds(1e-9); let mut local: Local<TestEvent> = Local::new(NonZeroUsize::new(8).unwrap());
assert!(local.cutoff(Duration::from_secs(1), period).is_none()); local
.feed(TestEvent::new(5_000_000_000, 5_000_000_100))
.unwrap(); assert_eq!(
local.cutoff(Duration::from_secs(2), period),
Some(TscCount::new(3_000_000_100))
);
assert_eq!(
local.cutoff(Duration::from_secs(5), period),
Some(TscCount::new(100))
);
assert!(
local
.cutoff(Duration::from_secs(5) + Duration::from_nanos(100), period)
.is_none()
);
assert!(local.cutoff(Duration::from_secs(6), period).is_none());
}
#[test]
fn window_start_whole_buffer_when_window_is_not_shorter() {
let period = Period::from_seconds(1e-9);
let mut local: Local<TestEvent> =
Local::new(NonZeroUsize::new(8).unwrap()).with_window(Duration::from_secs(100));
for s in 1..=5_u64 {
local
.feed(TestEvent::new(s * 1_000_000_000, s * 1_000_000_000 + 100))
.unwrap();
}
assert_eq!(local.window_start(Duration::from_secs(100), period), 0);
assert_eq!(local.window_start(Duration::from_secs(1024), period), 0);
}
#[test]
fn window_start_narrows_to_the_last_window() {
let period = Period::from_seconds(1e-9);
let mut local: Local<TestEvent> =
Local::new(NonZeroUsize::new(8).unwrap()).with_window(Duration::from_secs(100));
for s in 10..=15_u64 {
local
.feed(TestEvent::new(s * 1_000_000_000, s * 1_000_000_000 + 100))
.unwrap();
}
assert_eq!(local.window_start(Duration::from_secs(2), period), 3);
assert_eq!(local.window_start(Duration::from_millis(500), period), 5);
assert_eq!(local.window_start(Duration::from_secs(20), period), 0);
}
}