1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//! Cumulative `u64` totals over a portable core's drained `u32` counts.
//!
//! The cores of `bacnet-router-core` count as the link cores do: `u32`
//! counts that saturate between takes and start again from zero when
//! taken. The hosted [`BACnetRouter`](crate::router::BACnetRouter) and
//! [`NetworkLayer`](crate::layer::NetworkLayer) take them after each call
//! that may have counted, add them to these totals, and read the totals
//! without a lock, so their users see counts since start.
/// Declares a totals type: one `AtomicU64` per named field of a core's
/// counts, `add` for the counts a take returned, and `load` for a copy as
/// the hosted counters type. Both types must name every field: the sizes
/// are checked at compile time, so a count a core adds fails the build
/// until it is listed here and totalled.
///
/// Each total saturates at `u64::MAX`. Additions release and loads
/// acquire, so a reader who sees a total also sees what the call that
/// counted it did before its counts were added.
macro_rules! totals {
(
$(#[$meta:meta])*
$name:ident: $counts:ty => $totals:ty { $($field:ident),+ $(,)? }
) => {
$(#[$meta])*
#[derive(Debug, Default)]
pub(crate) struct $name {
$($field: std::sync::atomic::AtomicU64,)+
}
impl $name {
/// How many counts it keeps.
const FIELDS: usize = [$(stringify!($field)),+].len();
/// Add the counts a take returned to the totals, saturating.
pub(crate) fn add(&self, counts: &$counts) {
use std::sync::atomic::Ordering;
$(
if counts.$field != 0 {
let added = u64::from(counts.$field);
#[allow(deprecated, reason = "try_update needs Rust 1.95; the MSRV is 1.93")]
let _ = self.$field.fetch_update(
Ordering::Release,
Ordering::Relaxed,
|total| Some(total.saturating_add(added)),
);
}
)+
}
/// The totals, as the hosted counters.
pub(crate) fn load(&self) -> $totals {
use std::sync::atomic::Ordering;
let mut totals = <$totals>::default();
$(totals.$field = self.$field.load(Ordering::Acquire);)+
totals
}
}
// The core's counts are all `u32`s and the totals all `u64`s, so
// their sizes say how many there are.
const _: () = assert!(
std::mem::size_of::<$counts>() == $name::FIELDS * std::mem::size_of::<u32>(),
"total every count the core keeps"
);
const _: () = assert!(
std::mem::size_of::<$totals>() == $name::FIELDS * std::mem::size_of::<u64>(),
"publish every total"
);
};
}
pub(crate) use totals;