Next Generation WASM Microkernel Operating System
1// Copyright 2025 Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7//! Kernel counters
8//!
9//! Kernel counters are per-cpu, unsigned integer counters that facilitate diagnostics across the
10//! whole kernel. Questions like "how many times has X happened over N seconds?", "has X ever happened?"
11//! can be answered using this API.
12//!
13//! Counters are declared in their respective modules like so:
14//! ```rust
15//! use crate::metrics::{Counter, counter};
16//!
17//! static TEST_CNT: Counter = counter!("test-event");
18//!
19//! fn some_function() {
20//! TEST_CNT.increment(1);
21//! }
22//! ```
23//!
24//! Kernel counters are always per-cpu, which means each cpu keeps an individual counter. Methods
25//! on `Counter` can be used to sum events across cpus or even get the maximum or minimum value across
26//! cpus.
27
28use core::sync::atomic::{AtomicU64, Ordering};
29use cpu_local::collection::CpuLocal;
30
31/// Declares a new counter.
32#[macro_export]
33macro_rules! counter {
34 ($name:expr) => {{
35 #[unsafe(link_section = concat!(".bss.kcounter.", $name))]
36 static ARENA: $crate::cpu_local::CpuLocal<::core::sync::atomic::AtomicU64> =
37 $crate::cpu_local::CpuLocal::new();
38
39 Counter::new(&ARENA, $name)
40 }};
41}
42
43/// A kernel counter.
44pub struct Counter {
45 arena: &'static CpuLocal<AtomicU64>,
46 name: &'static str,
47}
48
49impl Counter {
50 #[doc(hidden)]
51 pub const fn new(arena: &'static CpuLocal<AtomicU64>, name: &'static str) -> Self {
52 Self { arena, name }
53 }
54
55 /// Increment the counter.
56 pub fn increment(&self, value: u64) {
57 self.arena
58 .get_or_default()
59 .fetch_add(value, Ordering::Relaxed);
60 }
61
62 /// Decrement the counter.
63 pub fn decrement(&self, value: u64) {
64 self.arena
65 .get_or_default()
66 .fetch_sub(value, Ordering::Relaxed);
67 }
68
69 /// Set the absolute value of the counter.
70 pub fn set(&self, value: u64) {
71 self.arena.get_or_default().store(value, Ordering::Relaxed);
72 }
73
74 /// Set the absolute value of the counter if the provided value is larger than the current value.
75 pub fn max(&self, value: u64) {
76 let _ =
77 self.arena
78 .get_or_default()
79 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old| {
80 (old < value).then_some(value)
81 });
82 }
83
84 /// Set the absolute value of the counter if the provided value is smaller than the current value.
85 pub fn min(&self, value: u64) {
86 let _ =
87 self.arena
88 .get_or_default()
89 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old| {
90 (old > value).then_some(value)
91 });
92 }
93
94 /// Get the counter value of the calling cpu, or `None` if the counter was never written to.
95 pub fn get(&self) -> Option<u64> {
96 Some(self.arena.get()?.load(Ordering::Relaxed))
97 }
98
99 /// Return the sum of all counters across all cpus.
100 pub fn sum_across_all_cpus(&self) -> u64 {
101 self.arena.iter().map(|v| v.load(Ordering::Relaxed)).sum()
102 }
103
104 /// Return the largest value from across all CPUs.
105 pub fn max_across_all_cpus(&self) -> u64 {
106 self.arena
107 .iter()
108 .map(|v| v.load(Ordering::Relaxed))
109 .max()
110 .unwrap()
111 }
112
113 /// Return the smallest value from across all CPUs.
114 pub fn min_across_all_cpus(&self) -> u64 {
115 self.arena
116 .iter()
117 .map(|v| v.load(Ordering::Relaxed))
118 .min()
119 .unwrap()
120 }
121}