Skip to content

Commit 5b13a97

Browse files
authored
ringbuf: Add event counters (#1621)
Ringbufs provide a fixed-size buffer for storing diagnostic events, which may include data. This is very useful for debugging. However, because the ring buffer has a fixed size, only the last `N` events are visible at any given time. If a ringbuf has a capacity of, say, 16 events, and a given event has occurred 10,000 times over the lifespan of the task, there's currently no way to know that the event has happened more than 16 times. Furthermore, there's no way to use the ringbuf to determine whether a given event has _ever_ occurred during the task's lifetime: if a given variant is not _currently_ present in the ringbuf, it may still have occurred previously and been "drowned out" by subsequent events. Finally, some targets disable the ringbuf entirely, for space reasons, and on those targets, it may still be desirable to be able to get some less granular diagnostic data out of the ringbuf events a task records. Therefore, this commit extends the `ringbuf` crate to support generating counts of the number of times a particular event variant has been recorded, in addition to a fixed-size buffer of events. These counters provide less detailed data than inspecting the ringbuf contents, because they don't record the values of any _fields_ on that event. However, they allow recording very large numbers of events in a fixed amount of space, and provide historical data for events that may have occurred in the past and then "fallen off" the end of the ringbuf as new events were recorded. By inspecting these counters, we can determine if a given variant has *ever* been recorded, even if it isn't currently in the buffer, and we can see the total number of times it has been recorded over the task's entire lifetime. Event counters are implemented using a new `CountedRingbuf` type, which marries a ring buffer with a type which can count occurances of entry variants. `CountedRingbuf`s may be declared using the `counted_ringbuf!` macro, and entries can be recorded using the existing `ringbuf_entry!` macro. `CountedRingbuf` requires that the entry type implement a new `Count` trait, which defines the counter type, an initializer for creating new instances of the counter type, and a method to increment the counter type with a given instance of the entry type. An implementation of `ringbuf::Count` can be generated for an entry type using the `#[derive(ringbuf::Count)]` attribute, which generates a `struct` with an `AtomicU32` field for each of the deriving `enum`'s variants. This `struct` can then be loaded by Humility to provide a view of the event counters. Because recording event counters requires only `4 * <N_VARIANTS>` bytes, counters are currently recorded even when the `ringbuf` crate has the `disabled` feature set. This way, we can still record some diagnostic data on targets that don't have the space to store the ringbuf itself. If it becomes necessary to also disable counters, we could add a separate `disable-counters` feature as well, so that a target can pick and choose between recording ringbuf entries, counts, or both. As a proof of concept, I've also updated `gimlet-seq-server` to use the new event counters for its `Trace` ringbuf. Subsequent commits will roll it out elsewhere. Future work will include: - [ ] Actually adopting event counters in existing ringbufs - [ ] Updating `humility ringbuf` to also dump ringbuf event counts (see oxidecomputer/humility#449). Depends on #1624, both to reduce merge conflicts and to mitigate an increase in size due to the changes in this branch.
1 parent c53180c commit 5b13a97

File tree

19 files changed

+427
-64
lines changed

19 files changed

+427
-64
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/gimlet/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
fn main() {
66
build_util::expose_target_board();
7-
build_util::expose_m_profile();
7+
build_util::expose_m_profile().unwrap();
88
}

build/util/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub fn has_feature(s: &str) -> bool {
5656
///
5757
/// This will set one of `cfg(armv6m)`, `cfg(armv7m)`, or `cfg(armv8m)`
5858
/// depending on the value of the `TARGET` environment variable.
59-
pub fn expose_m_profile() {
59+
pub fn expose_m_profile() -> Result<()> {
6060
let target = crate::target();
6161

6262
if target.starts_with("thumbv6m") {
@@ -67,9 +67,9 @@ pub fn expose_m_profile() {
6767
} else if target.starts_with("thumbv8m") {
6868
println!("cargo:rustc-cfg=armv8m");
6969
} else {
70-
println!("Don't know the target {}", target);
71-
std::process::exit(1);
70+
bail!("Don't know the target {target}");
7271
}
72+
Ok(())
7373
}
7474

7575
/// Returns the `HUBRIS_BOARD` envvar, if set.

drv/gimlet-seq-server/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ include!(concat!(env!("OUT_DIR"), "/i2c_config.rs"));
4545
)]
4646
mod payload;
4747

48-
#[derive(Copy, Clone, PartialEq)]
48+
#[derive(Copy, Clone, PartialEq, ringbuf::Count)]
4949
enum Trace {
5050
Ice40Rails(bool, bool),
5151
IdentValid(bool),
@@ -112,7 +112,7 @@ enum Trace {
112112
None,
113113
}
114114

115-
ringbuf!(Trace, 128, Trace::None);
115+
counted_ringbuf!(Trace, 128, Trace::None);
116116

117117
#[export_name = "main"]
118118
fn main() -> ! {

lib/armv6m-atomic-hack/build.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

55
fn main() {
6-
build_util::expose_m_profile();
6+
match build_util::expose_m_profile() {
7+
Ok(_) => (),
8+
Err(e) => println!("cargo:warn={e}"),
9+
}
710
}

lib/ringbuf/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@ edition = "2021"
77
# To disable a ring buffer (but leave it otherwise present), enable the
88
# "disabled" feature
99
disabled = []
10+
# Enable deriving the `Count` trait
11+
derive = ["ringbuf-macros"]
12+
default = ["derive"]
1013

1114
[dependencies]
1215
static-cell = { path = "../static-cell" }
16+
ringbuf-macros = { path = "macros", optional = true }
17+
18+
[target.'cfg(target_arch = "arm")'.dependencies]
19+
armv6m-atomic-hack = { path = "../armv6m-atomic-hack" }
1320

1421
[lib]
1522
test = false

lib/ringbuf/examples/counts.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Demonstrates the use of `counted_ringbuf!` and friends.
6+
//!
7+
//! This example is primarily intended to be used with `cargo expand` to show
8+
//! the macro-generated code for `#[derive(ringbuf::Count)]` and friends.
9+
use ringbuf::*;
10+
11+
#[derive(ringbuf::Count, Debug, Copy, Clone, PartialEq, Eq)]
12+
pub enum Event {
13+
NothingHappened,
14+
SomethingHappened,
15+
SomethingElse(u32),
16+
SecretThirdThing { secret: () },
17+
}
18+
19+
counted_ringbuf!(Event, 16, Event::NothingHappened);
20+
counted_ringbuf!(MY_NAMED_RINGBUF, Event, 16, Event::NothingHappened);
21+
22+
ringbuf!(NON_COUNTED_RINGBUF, Event, 16, Event::NothingHappened);
23+
24+
pub fn example() {
25+
ringbuf_entry!(Event::SomethingHappened);
26+
ringbuf_entry!(NON_COUNTED_RINGBUF, Event::SomethingHappened);
27+
}
28+
29+
pub fn example_named() {
30+
ringbuf_entry!(MY_NAMED_RINGBUF, Event::SomethingElse(420));
31+
}
32+
33+
pub mod nested {
34+
use super::Event;
35+
36+
ringbuf::counted_ringbuf!(Event, 16, Event::NothingHappened);
37+
38+
pub fn example() {
39+
ringbuf::ringbuf_entry!(Event::SomethingHappened);
40+
ringbuf::ringbuf_entry_root!(Event::SomethingElse(666));
41+
ringbuf::ringbuf_entry_root!(
42+
MY_NAMED_RINGBUF,
43+
Event::SecretThirdThing { secret: () }
44+
);
45+
}
46+
}
47+
48+
fn main() {}

lib/ringbuf/macros/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "ringbuf-macros"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
proc-macro2 = "1.0.78"
11+
quote = "1.0.35"
12+
syn = "2.0.48"

lib/ringbuf/macros/src/lib.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
extern crate proc_macro;
6+
use proc_macro::TokenStream;
7+
use proc_macro2::{Ident, Span};
8+
use quote::{quote, ToTokens};
9+
use syn::{parse_macro_input, DeriveInput};
10+
11+
/// Derives an implementation of the `ringbuf::Count` trait for the annotated
12+
/// `enum` type.
13+
///
14+
/// Note that this macro can currently only be used on `enum` types.
15+
#[proc_macro_derive(Count)]
16+
pub fn derive_count(input: TokenStream) -> TokenStream {
17+
let input = parse_macro_input!(input as DeriveInput);
18+
match gen_count_impl(input) {
19+
Ok(tokens) => tokens.to_token_stream().into(),
20+
Err(err) => err.to_compile_error().into(),
21+
}
22+
}
23+
24+
fn gen_count_impl(input: DeriveInput) -> Result<impl ToTokens, syn::Error> {
25+
let name = &input.ident;
26+
let data_enum = match input.data {
27+
syn::Data::Enum(ref data_enum) => data_enum,
28+
_ => {
29+
return Err(syn::Error::new_spanned(
30+
input,
31+
"`ringbuf::Count` can only be derived for enums",
32+
));
33+
}
34+
};
35+
let variants = &data_enum.variants;
36+
let len = variants.len();
37+
let mut variant_names = Vec::with_capacity(len);
38+
let mut variant_patterns = Vec::with_capacity(len);
39+
for variant in variants {
40+
let ident = &variant.ident;
41+
variant_patterns.push(match variant.fields {
42+
syn::Fields::Unit => quote! { #name::#ident => &counters.#ident },
43+
syn::Fields::Named(_) => {
44+
quote! { #name::#ident { .. } => &counters.#ident }
45+
}
46+
syn::Fields::Unnamed(_) => {
47+
quote! { #name::#ident(..) => &counters.#ident }
48+
}
49+
});
50+
variant_names.push(ident.clone());
51+
}
52+
let counts_ty = counts_ty(name);
53+
let code = quote! {
54+
#[doc = concat!(" Ringbuf entry total counts for [`", stringify!(#name), "`].")]
55+
#[allow(nonstandard_style)]
56+
pub struct #counts_ty {
57+
#(
58+
#[doc = concat!(
59+
" The total number of times a [`",
60+
stringify!(#name), "::", stringify!(#variant_names),
61+
"`] entry"
62+
)]
63+
#[doc = " has been recorded by this ringbuf."]
64+
pub #variant_names: core::sync::atomic::AtomicU32
65+
),*
66+
}
67+
68+
#[automatically_derived]
69+
impl ringbuf::Count for #name {
70+
type Counters = #counts_ty;
71+
72+
// This is intended for use in a static initializer, so the fact that every
73+
// time the constant is used it will be a different instance is not a
74+
// problem --- in fact, it's the desired behavior.
75+
//
76+
// `declare_interior_mutable_const` is really Not My Favorite Clippy
77+
// Lint...
78+
#[allow(clippy::declare_interior_mutable_const)]
79+
const NEW_COUNTERS: #counts_ty = #counts_ty {
80+
#(#variant_names: core::sync::atomic::AtomicU32::new(0)),*
81+
};
82+
83+
fn count(&self, counters: &Self::Counters) {
84+
#[cfg(all(target_arch = "arm", armv6m))]
85+
use ringbuf::rmv6m_atomic_hack::AtomicU32Ext;
86+
87+
let counter = match self {
88+
#(#variant_patterns),*
89+
};
90+
counter.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
91+
}
92+
}
93+
};
94+
Ok(code)
95+
}
96+
97+
fn counts_ty(ident: &Ident) -> Ident {
98+
Ident::new(&format!("{ident}Counts"), Span::call_site())
99+
}

0 commit comments

Comments
 (0)