Skip to content

Commit d118d12

Browse files
committed
ringbuf: add #[count(skip)] & #[count(children)] (#1630)
Currently, the `#[derive(ringbuf::Count)]` attribute is pretty simple: it generates a single counter for every variant of the annotated `enum` type. For some of the ringbuf events we would like to generate counters for, this is sufficient. However, there are a few things that would be nice to have that we can't currently do with the derive attribute: - Most ringbuf entry enum types have an "Empty"/"None" variant that's used for initializing the ringbuffer, but are never actually recorded at runtime. We could save a few bytes by not generating a counter for these variants. - Many ringbuf entry enum variants contain a nested enum variant. such as a variant representing an error which contains an error kind enum, or, the motivating example, `Log::MgsMessage(MgsMessage)` in control-plane-agent. In this case, we would like to be able to generate a counter of each MGS message variant, which is not currently possible with the current `#[derive(ringbuf::Count)]` attribute. This commit adds new helper attributes to the derive macro: `#[count(skip)]` and `#[count(children)]`. The `#[count(skip)]` attribute can be placed on an enum variant to indicate that a counter *shouldn't* be generated for it. The `#[count(children)]` attribute can be placed on an enum variant that has a single field, to indicate that a _nested_ set of counters should be generated for that variant's inner field. When the `#[count(children)]` attribute is used, the inner field must also implement the `Count` trait. If it does, the field on the counter type for that variant will be the child type's `Count::Counters` type, rather than an `AtomicU32`, and we will increment the nested counter. My Humility PR #449 adds nice support for interpreting and displaying these nested counters.
1 parent 5b13a97 commit d118d12

File tree

4 files changed

+185
-31
lines changed

4 files changed

+185
-31
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
#[derive(ringbuf::Count, Debug, Copy, Clone, PartialEq, Eq)]
6+
pub enum MainEvent {
7+
#[count(skip)]
8+
None,
9+
10+
/// This generates a counter for each variant of `Person`.
11+
#[count(children)]
12+
Person(Person),
13+
14+
/// This generates a counter for each variant of `Place`.
15+
#[count(children)]
16+
Place(Place),
17+
18+
/// This generates a single counter for `Number`.
19+
Number(u32),
20+
}
21+
22+
#[derive(ringbuf::Count, Debug, Copy, Clone, PartialEq, Eq)]
23+
pub enum Person {
24+
Cliff,
25+
Matt,
26+
Laura,
27+
Bryan,
28+
John,
29+
Steve,
30+
}
31+
32+
#[derive(ringbuf::Count, Debug, Copy, Clone, PartialEq, Eq)]
33+
pub enum Place {
34+
Emeryville,
35+
Cambridge,
36+
Austin,
37+
SanFrancisco,
38+
ZipCode(u16),
39+
}
40+
41+
ringbuf::counted_ringbuf!(MainEvent, 16, MainEvent::None);
42+
43+
fn main() {}

lib/ringbuf/examples/counts.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ use ringbuf::*;
1010

1111
#[derive(ringbuf::Count, Debug, Copy, Clone, PartialEq, Eq)]
1212
pub enum Event {
13+
#[count(skip)]
1314
NothingHappened,
1415
SomethingHappened,
1516
SomethingElse(u32),
16-
SecretThirdThing { secret: () },
17+
SecretThirdThing {
18+
secret: (),
19+
},
1720
}
1821

1922
counted_ringbuf!(Event, 16, Event::NothingHappened);

lib/ringbuf/macros/src/lib.rs

Lines changed: 134 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,32 @@ extern crate proc_macro;
66
use proc_macro::TokenStream;
77
use proc_macro2::{Ident, Span};
88
use quote::{quote, ToTokens};
9-
use syn::{parse_macro_input, DeriveInput};
10-
9+
use syn::{parse_macro_input, DeriveInput, parse::{Parse, ParseStream}};
10+
1111
/// Derives an implementation of the `ringbuf::Count` trait for the annotated
1212
/// `enum` type.
1313
///
1414
/// Note that this macro can currently only be used on `enum` types.
15-
#[proc_macro_derive(Count)]
15+
///
16+
/// # Variant Attributes
17+
///
18+
/// The following attributes may be added on one or more of the variants of the
19+
/// `enum` type deriving `Count`:
20+
///
21+
/// - `#[count(skip)]`: Skip counting this variant. Enums used as ringbuf
22+
/// entries often have a `None` or `Empty` variant which is used to initialize
23+
/// the ring buffer but not recorded as an entry at runtime. The
24+
/// `#[count(skip)]` attribute avoids generating a counter for such variants,
25+
/// reducing the memory used by the counter struct a little bit.
26+
///
27+
/// - `#[count(children)]`: Count variants of a nested enum. Typically, when a
28+
/// variant of a type deriving `Count` has fields, all instances of that
29+
/// variant increment the same counter, regardless of the value of the fields.
30+
/// When a variant has a single field of a type which also implements the
31+
/// `Count` trait, however, the `#[count(children)]` attribute can be used to
32+
/// generate an instance of the field type's counter struct, and implement
33+
/// those counters instead.
34+
#[proc_macro_derive(Count, attributes(count))]
1635
pub fn derive_count(input: TokenStream) -> TokenStream {
1736
let input = parse_macro_input!(input as DeriveInput);
1837
match gen_count_impl(input) {
@@ -23,6 +42,7 @@ pub fn derive_count(input: TokenStream) -> TokenStream {
2342

2443
fn gen_count_impl(input: DeriveInput) -> Result<impl ToTokens, syn::Error> {
2544
let name = &input.ident;
45+
let vis = &input.vis;
2646
let data_enum = match input.data {
2747
syn::Data::Enum(ref data_enum) => data_enum,
2848
_ => {
@@ -34,35 +54,98 @@ fn gen_count_impl(input: DeriveInput) -> Result<impl ToTokens, syn::Error> {
3454
};
3555
let variants = &data_enum.variants;
3656
let len = variants.len();
37-
let mut variant_names = Vec::with_capacity(len);
57+
let mut field_defs = Vec::with_capacity(len);
58+
let mut field_inits = Vec::with_capacity(len);
3859
let mut variant_patterns = Vec::with_capacity(len);
39-
for variant in variants {
60+
let mut any_skipped = false;
61+
'variants: for variant in variants {
4062
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 }
63+
let mut count_children = None;
64+
for attr in &variant.attrs {
65+
if !attr.path().is_ident("count") {
66+
continue;
4567
}
46-
syn::Fields::Unnamed(_) => {
47-
quote! { #name::#ident(..) => &counters.#ident }
68+
match attr.parse_args_with(VariantAttr::parse)? {
69+
VariantAttr::Skip => {
70+
any_skipped = true;
71+
continue 'variants;
72+
},
73+
VariantAttr::Children => {
74+
count_children = Some(attr);
75+
},
4876
}
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-
#(
77+
}
78+
79+
if let Some(count_children) = count_children {
80+
match variant.fields {
81+
syn::Fields::Unit => return Err(syn::Error::new_spanned(
82+
count_children,
83+
"the `count_children` attribute may not be used on unit variants",
84+
)),
85+
syn::Fields::Named(_) => return Err(syn::Error::new_spanned(
86+
count_children,
87+
"the `count_children` attribute does not currently support variants with named fields",
88+
)),
89+
90+
syn::Fields::Unnamed(_) if variant.fields.len() > 1 => return Err(syn::Error::new_spanned(
91+
count_children,
92+
"the `count_children` attribute does not currently support variants with multiple fields",
93+
)),
94+
syn::Fields::Unnamed(ref u) => {
95+
let field = u.unnamed.first().unwrap();
96+
let ty = &field.ty;
97+
variant_patterns.push(quote! { #name::#ident(c) => c.count(&counters.#ident) });
98+
field_defs.push(quote! {
99+
#[doc = concat!(
100+
" The total number of times a [`",
101+
stringify!(#name), "::", stringify!(#ident),
102+
"`] entry"
103+
)]
104+
#[doc = " has been recorded by this ringbuf."]
105+
pub #ident: <#ty as ringbuf::Count>::Counters
106+
});
107+
field_inits.push(quote! {
108+
#ident: <#ty as ringbuf::Count>::NEW_COUNTERS
109+
});
110+
}
111+
}
112+
} else {
113+
let incr = quote! {
114+
counters.#ident.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
115+
};
116+
variant_patterns.push(match variant.fields {
117+
syn::Fields::Unit => quote! { #name::#ident => { #incr } },
118+
syn::Fields::Named(_) => {
119+
quote! { #name::#ident { .. } => { #incr } }
120+
}
121+
syn::Fields::Unnamed(_) => {
122+
quote! { #name::#ident(..) => { #incr } }
123+
}
124+
});
125+
field_defs.push(quote! {
58126
#[doc = concat!(
59127
" The total number of times a [`",
60-
stringify!(#name), "::", stringify!(#variant_names),
128+
stringify!(#name), "::", stringify!(#ident),
61129
"`] entry"
62130
)]
63131
#[doc = " has been recorded by this ringbuf."]
64-
pub #variant_names: core::sync::atomic::AtomicU32
65-
),*
132+
pub #ident: core::sync::atomic::AtomicU32
133+
});
134+
field_inits.push(quote! { #ident: core::sync::atomic::AtomicU32::new(0) });
135+
}
136+
}
137+
138+
// If we skipped any variants, generate a catchall case.
139+
if any_skipped {
140+
variant_patterns.push(quote! { _ => {} });
141+
}
142+
143+
let counts_ty = counts_ty(name);
144+
let code = quote! {
145+
#[doc = concat!(" Ringbuf entry total counts for [`", stringify!(#name), "`].")]
146+
#[allow(nonstandard_style)]
147+
#vis struct #counts_ty {
148+
#(#field_defs),*
66149
}
67150

68151
#[automatically_derived]
@@ -77,23 +160,48 @@ fn gen_count_impl(input: DeriveInput) -> Result<impl ToTokens, syn::Error> {
77160
// Lint...
78161
#[allow(clippy::declare_interior_mutable_const)]
79162
const NEW_COUNTERS: #counts_ty = #counts_ty {
80-
#(#variant_names: core::sync::atomic::AtomicU32::new(0)),*
163+
#(#field_inits),*
81164
};
82165

83166
fn count(&self, counters: &Self::Counters) {
84167
#[cfg(all(target_arch = "arm", armv6m))]
85168
use ringbuf::rmv6m_atomic_hack::AtomicU32Ext;
86169

87-
let counter = match self {
170+
match self {
88171
#(#variant_patterns),*
89172
};
90-
counter.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
91173
}
92174
}
93175
};
94176
Ok(code)
95177
}
96178

179+
#[derive(Copy, Clone, PartialEq, Eq)]
180+
enum VariantAttr {
181+
Skip,
182+
Children,
183+
}
184+
185+
impl Parse for VariantAttr {
186+
fn parse(input: ParseStream) -> syn::Result<Self> {
187+
let ident = input.fork().parse::<syn::Ident>()?;
188+
if ident == "skip" {
189+
// consume the token
190+
let _: syn::Ident = input.parse()?;
191+
Ok(VariantAttr::Skip)
192+
} else if ident == "children" {
193+
let _: syn::Ident = input.parse()?;
194+
Ok(VariantAttr::Children)
195+
} else {
196+
Err(syn::Error::new(
197+
ident.span(),
198+
"unrecognized count option, expected `skip` or `children`",
199+
))
200+
}
201+
}
202+
}
203+
204+
97205
fn counts_ty(ident: &Ident) -> Ident {
98206
Ident::new(&format!("{ident}Counts"), Span::call_site())
99207
}

task/control-plane-agent/src/main.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ task_slot!(NET, net);
4646
task_slot!(SYS, sys);
4747

4848
#[allow(dead_code)] // Not all cases are used by all variants
49-
#[derive(Debug, Clone, Copy, PartialEq)]
49+
#[derive(Debug, Clone, Copy, PartialEq, ringbuf::Count)]
5050
enum Log {
5151
Empty,
5252
BarcodeParseError(BarcodeParseError),
@@ -153,9 +153,9 @@ enum MgsMessage {
153153
ReadRotPage,
154154
}
155155

156-
ringbuf!(Log, 16, Log::Empty);
156+
counted_ringbuf!(Log, 16, Log::Empty);
157157

158-
#[derive(Copy, Clone, Debug, PartialEq)]
158+
#[derive(Copy, Clone, Debug, PartialEq, ringbuf::Count)]
159159
enum CriticalEvent {
160160
Empty,
161161
/// We have received a network request to change power states. This record
@@ -171,7 +171,7 @@ enum CriticalEvent {
171171

172172
// This ringbuf exists to record critical events _only_ and thus not get
173173
// overwritten by chatter in the debug/trace-style messages.
174-
ringbuf!(CRITICAL, CriticalEvent, 16, CriticalEvent::Empty);
174+
counted_ringbuf!(CRITICAL, CriticalEvent, 16, CriticalEvent::Empty);
175175

176176
const SOCKET: SocketName = SocketName::control_plane_agent;
177177

0 commit comments

Comments
 (0)