diff --git a/lib/ringbuf/src/lib.rs b/lib/ringbuf/src/lib.rs index 5a939282c..943010a34 100644 --- a/lib/ringbuf/src/lib.rs +++ b/lib/ringbuf/src/lib.rs @@ -14,7 +14,11 @@ //! ## Constraints //! //! The main constraint for a ring buffer is that the type in the ring buffer -//! must implement both `Copy` and `PartialEq`. +//! must implement [`Copy`]. If [de-duplication](#entry-de-duplication) is +//! enabled, the entry type must also implement [`PartialEq`]. When using the +//! [`counted_ringbuf!`] macro to [count ring buffer +//! entries](#counted-ring-buffers), the entry type must implement the +//! [`counters::Count`] trait. //! //! If you use the variants of the `ringbuf!` macro that leave the name of the //! data structure implicit, you can only have one per module. (You can lift @@ -124,6 +128,54 @@ //! ringbuf_entry!(MyEvent::SomethingElseHappened(666)); //! ``` //! +//! ### Entry de-duplication +//! +//! By default, code that records the same value multiple times in a row +//! will result in a separate ring buffer entry being recorded for each value. +//! If this occurs often, it can quickly fill up the whole ring buffer with +//! repetitive data. Therefore, ring buffers of entries that implement the +//! [`PartialEq`] trait can be configured to de-duplicate repeated values by +//! incrementing a counter on the previous entry, rather than recording a new +//! entry. +//! +//! To enable entry de-duplication, add the `dedup` argument at the end of the +//! [`ringbuf!`] or [`counted_ringbuf!`] macro. For example: +//! +//! ``` +//! // When recording the same `u32` value multiple times in a row, +//! // this ring buffer will increment the counter on the previous +//! // entry, rather than recording two separate ones. +//! ringbuf!(u32, 16, 0, dedup); +//! ``` +//! +//! Or, with [`counted_ringbuf!`]: +//! +//! ```` +//! //! // Declare an enum type and derive the `Count` trait for it: +//! #[derive(Copy, Clone, Debug, PartialEq, Eq, counters::Count)] +//! pub enum MyEvent { +//! NothingHappened, +//! SomethingHappened, +//! SomethingElseHappened(u32), +//! // ... +//! } +//! +//! // When this ring buffer records a `MyEvent` entry which is equal to +//! // the most recent entry, the counter on the previous entry will be +//! // incremented, rather than recording a new entry. +//! counted_ringbuf!(MyEvent, 16, MyEvent::NothingHappened, dedup); +//! ``` +//! +//! Note that the per-entry counts generated by de-duplication are +//! distinct from the entry variant counters produced by [`counted_ringbuf!`]. +//! A [`counted_ringbuf!`] will count the _total number of times_ a particular +//! entry variant has been recorded, coalescing those values based not on their +//! [`PartialEq`] implementation, but based on their implementation of the +//! [`counters::Count`] trait. If we consider the `MyEvent` enum in the above +//! example, two `MyEvent::SomethingElseHappened` with different `u32` values +//! would increment the _same_ total counter, because they are the same variant +//! of the enum. However, they would *not* be de-duplicated, because they are +//! not equal to each other. //! //! ## Inspecting a ring buffer via Humility //! @@ -205,6 +257,25 @@ pub use counters::Count; /// macros is guaranteed to be able to find them. pub use static_cell::StaticCell; +#[cfg(feature = "disabled")] +#[macro_export] +macro_rules! ringbuf { + ($name:ident, $t:ty, $n:expr, $init:expr, dedup) => { + $crate::ringbuf!($name, $t, $n, $init) + }; + ($name:ident, $t:ty, $n:expr, $init:expr) => { + #[allow(dead_code)] + const _: $t = $init; + static $name: () = (); + }; + ($t:ty, $n:expr, $init:expr, dedup) => { + $crate::ringbuf!(__RINGBUF, $t, $n, $init); + }; + ($t:ty, $n:expr, $init:expr) => { + $crate::ringbuf!(__RINGBUF, $t, $n, $init); + }; +} + /// Declares a ringbuffer in the current module or context. /// /// `ringbuf!(NAME, Type, N, expr)` makes a ringbuffer named `NAME`, @@ -224,29 +295,32 @@ pub use static_cell::StaticCell; macro_rules! ringbuf { ($name:ident, $t:ty, $n:expr, $init:expr) => { #[used] - static $name: $crate::StaticCell<$crate::Ringbuf<$t, $n>> = + static $name: $crate::StaticCell<$crate::Ringbuf<$t, (), $n>> = $crate::StaticCell::new($crate::Ringbuf { last: None, buffer: [$crate::RingbufEntry { line: 0, generation: 0, - count: 0, + count: (), payload: $init, }; $n], }); }; - ($t:ty, $n:expr, $init:expr) => { - $crate::ringbuf!(__RINGBUF, $t, $n, $init); + ($name:ident, $t:ty, $n:expr, $init:expr, dedup) => { + #[used] + static $name: $crate::StaticCell<$crate::Ringbuf<$t, u16 $n>> = + $crate::StaticCell::new($crate::Ringbuf { + last: None, + buffer: [$crate::RingbufEntry { + line: 0, + generation: 0, + count: 0, + payload: $init, + }; $n], + }); }; -} - -#[cfg(feature = "disabled")] -#[macro_export] -macro_rules! ringbuf { - ($name:ident, $t:ty, $n:expr, $init:expr) => { - #[allow(dead_code)] - const _: $t = $init; - static $name: () = (); + ($t:ty, $n:expr, $init:expr, dedup) => { + $crate::ringbuf!(__RINGBUF, $t, $n, $init, dedup); }; ($t:ty, $n:expr, $init:expr) => { $crate::ringbuf!(__RINGBUF, $t, $n, $init); @@ -279,18 +353,38 @@ macro_rules! ringbuf { macro_rules! counted_ringbuf { ($name:ident, $t:ident, $n:expr, $init:expr) => { #[used] - static $name: $crate::CountedRingbuf<$t, $n> = $crate::CountedRingbuf { - ringbuf: $crate::StaticCell::new($crate::Ringbuf { - last: None, - buffer: [$crate::RingbufEntry { - line: 0, - generation: 0, - count: 0, - payload: $init, - }; $n], - }), - counters: <$t as $crate::Count>::NEW_COUNTERS, - }; + static $name: $crate::CountedRingbuf<$t, (), $n> = + $crate::CountedRingbuf { + ringbuf: $crate::StaticCell::new($crate::Ringbuf { + last: None, + buffer: [$crate::RingbufEntry { + line: 0, + generation: 0, + count: (), + payload: $init, + }; $n], + }), + counters: <$t as $crate::Count>::NEW_COUNTERS, + }; + }; + ($name:ident, $t:ident, $n:expr, $init:expr, dedup) => { + #[used] + static $name: $crate::CountedRingbuf<$t, u16, $n> = + $crate::CountedRingbuf { + ringbuf: $crate::StaticCell::new($crate::Ringbuf { + last: None, + buffer: [$crate::RingbufEntry { + line: 0, + generation: 0, + count: 0, + payload: $init, + }; $n], + }), + counters: <$t as $crate::Count>::NEW_COUNTERS, + }; + }; + ($t:ident, $n:expr, $init:expr, dedup) => { + $crate::counted_ringbuf!(__RINGBUF, $t, $n, $init, dedup); }; ($t:ident, $n:expr, $init:expr) => { $crate::counted_ringbuf!(__RINGBUF, $t, $n, $init); @@ -304,11 +398,22 @@ macro_rules! counted_ringbuf { ))] #[macro_export] macro_rules! counted_ringbuf { + ($name:ident, $t:ident, $n:expr, $init:expr, dedup) => { + #[used] + static $name: $crate::CountedRingbuf<$t, (), $n> = + $crate::CountedRingbuf { + counters: <$t as $crate::Count>::NEW_COUNTERS, + }; + }; ($name:ident, $t:ident, $n:expr, $init:expr) => { #[used] - static $name: $crate::CountedRingbuf<$t, $n> = $crate::CountedRingbuf { - counters: <$t as $crate::Count>::NEW_COUNTERS, - }; + static $name: $crate::CountedRingbuf<$t, (), $n> = + $crate::CountedRingbuf { + counters: <$t as $crate::Count>::NEW_COUNTERS, + }; + }; + ($t:ident, $n:expr, $init:expr, dedup) => { + $crate::counted_ringbuf!(__RINGBUF, $t, $n, $init, dedup); }; ($t:ident, $n:expr, $init:expr) => { $crate::counted_ringbuf!(__RINGBUF, $t, $n, $init); @@ -322,10 +427,14 @@ macro_rules! counted_ringbuf { ))] #[macro_export] macro_rules! counted_ringbuf { + ($name:ident, $t:ident, $n:expr, $init:expr, dedup) => { + $crate::ringbuf!($name, $t, $n, $init, dedup) + }; ($name:ident, $t:ident, $n:expr, $init:expr) => { - #[allow(dead_code)] - const _: $t = $init; - static $name: () = (); + $crate::ringbuf!($name, $t, $n, $init) + }; + ($t:ident, $n:expr, $init:expr, dedup) => { + $crate::ringbuf!(__RINGBUF, $t, $n, $init, dedup); }; ($t:ident, $n:expr, $init:expr) => { $crate::ringbuf!(__RINGBUF, $t, $n, $init); @@ -339,11 +448,17 @@ macro_rules! counted_ringbuf { ))] #[macro_export] macro_rules! counted_ringbuf { + ($name:ident, $t:ident, $n:expr, $init:expr, dedup) => { + $crate::counted_ringbuf!(%name, $t, $n, $init) + }; ($name:ident, $t:ident, $n:expr, $init:expr) => { #[allow(dead_code)] const _: $t = $init; static $name: () = (); }; + ($t:ident, $n:expr, $init:expr, dedup) => { + $crate::counted_ringbuf!(__RINGBUF, $t, $n, $init); + }; ($t:ident, $n:expr, $init:expr) => { $crate::counted_ringbuf!(__RINGBUF, $t, $n, $init); }; @@ -394,11 +509,11 @@ macro_rules! ringbuf_entry_root { /// be incremented rather than generating a new entry. /// #[derive(Debug, Copy, Clone)] -pub struct RingbufEntry { +pub struct RingbufEntry { pub line: u16, pub generation: u16, - pub count: u32, pub payload: T, + pub count: C, } /// @@ -406,9 +521,9 @@ pub struct RingbufEntry { /// this directly is strange -- see the [`ringbuf!`] macro. /// #[derive(Debug)] -pub struct Ringbuf { +pub struct Ringbuf { pub last: Option, - pub buffer: [RingbufEntry; N], + pub buffer: [RingbufEntry; N], } /// @@ -425,11 +540,11 @@ pub struct Ringbuf { /// [`counted_ringbuf!`] macro. /// #[cfg(feature = "counters")] -pub struct CountedRingbuf { +pub struct CountedRingbuf { /// A ring buffer of the `N` most recent entries recorded by this /// `CountedRingbuf`. #[cfg(not(feature = "disabled"))] - pub ringbuf: StaticCell>, + pub ringbuf: StaticCell>, /// Counts of the total number of times each variant of `T` has been /// recorded, as defined by `T`'s [`Count`] impl. @@ -455,7 +570,7 @@ pub struct CountedRingbuf { /// It's typically unnecessary to implement this trait for other types, as its /// only purpose is to allow the [`ringbuf_entry!`] and [`ringbuf_entry_root!`] /// macros to dispatch based on which ringbuf type is being used. -pub trait RecordEntry { +pub trait RecordEntry { /// Record a `T`-typed entry in this ringbuf. The `line` parameter should be /// the source code line on which the entry was recorded. /// @@ -466,7 +581,7 @@ pub trait RecordEntry { } impl RecordEntry - for StaticCell> + for StaticCell> { fn record_entry(&self, line: u16, payload: T) { let mut ring = self.borrow_mut(); @@ -493,6 +608,48 @@ impl RecordEntry } } + ring.do_record(last, line, 1, payload); + } +} + +impl RecordEntry + for StaticCell> +{ + fn record_entry(&self, line: u16, payload: T) { + let mut ring = self.borrow_mut(); + // If this is the first time this ringbuf has been poked, last will be + // None. In this specific case we want to make sure we don't add to the + // count of an existing entry, and also that we deposit the first entry + // in slot 0. From a code generation perspective, the cheapest thing to + // do is to treat None as an out-of-range value: + let last = ring.last.unwrap_or(usize::MAX); + ring.do_record(last, line, (), payload); + } +} + +#[cfg(feature = "counters")] +impl RecordEntry for CountedRingbuf +where + T: Count + Copy, + StaticCell>: RecordEntry, +{ + fn record_entry(&self, _line: u16, payload: T) { + payload.count(&self.counters); + + #[cfg(not(feature = "disabled"))] + self.ringbuf.record_entry(_line, payload) + } +} + +impl RecordEntry for () +where + T: Copy + PartialEq, +{ + fn record_entry(&self, _: u16, _: T) {} +} + +impl Ringbuf { + fn do_record(&mut self, last: usize, line: u16, count: C, payload: T) { // Either we were unable to reuse the entry, or the last index was out // of range (perhaps because this is the first insertion). We're going // to advance last and wrap if required. This uses a wrapping_add @@ -504,41 +661,21 @@ impl RecordEntry // This is because none of our target platforms currently have // hardware modulus, and many of them don't even have hardware // divide, making remainder quite expensive. - if last_plus_1 >= ring.buffer.len() { + if last_plus_1 >= self.buffer.len() { 0 } else { last_plus_1 } }; - let ent = &mut ring.buffer[ndx]; + let ent = &mut self.buffer[ndx]; *ent = RingbufEntry { line, payload, - count: 1, + count, generation: ent.generation.wrapping_add(1), }; - ring.last = Some(ndx); - } -} - -#[cfg(feature = "counters")] -impl RecordEntry for CountedRingbuf -where - T: Count + Copy + PartialEq, -{ - fn record_entry(&self, _line: u16, payload: T) { - payload.count(&self.counters); - - #[cfg(not(feature = "disabled"))] - self.ringbuf.record_entry(_line, payload) + self.last = Some(ndx); } } - -impl RecordEntry for () -where - T: Copy + PartialEq, -{ - fn record_entry(&self, _: u16, _: T) {} -}