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};
29
30use k23_cpu_local::collection::CpuLocal;
31
32/// Declares a new counter.
33#[macro_export]
34macro_rules! counter {
35 ($name:expr) => {{
36 #[unsafe(link_section = concat!(".bss.kcounter.", $name))]
37 static ARENA: $crate::k23_cpu_local::CpuLocal<::core::sync::atomic::AtomicU64> =
38 $crate::cpu_local::CpuLocal::new();
39
40 Counter::new(&ARENA, $name)
41 }};
42}
43
44/// A kernel counter.
45pub struct Counter {
46 arena: &'static CpuLocal<AtomicU64>,
47 name: &'static str,
48}
49
50impl Counter {
51 #[doc(hidden)]
52 pub const fn new(arena: &'static CpuLocal<AtomicU64>, name: &'static str) -> Self {
53 Self { arena, name }
54 }
55
56 /// Increment the counter.
57 pub fn increment(&self, value: u64) {
58 self.arena
59 .get_or_default()
60 .fetch_add(value, Ordering::Relaxed);
61 }
62
63 /// Decrement the counter.
64 pub fn decrement(&self, value: u64) {
65 self.arena
66 .get_or_default()
67 .fetch_sub(value, Ordering::Relaxed);
68 }
69
70 /// Set the absolute value of the counter.
71 pub fn set(&self, value: u64) {
72 self.arena.get_or_default().store(value, Ordering::Relaxed);
73 }
74
75 /// Set the absolute value of the counter if the provided value is larger than the current value.
76 pub fn max(&self, value: u64) {
77 let _ =
78 self.arena
79 .get_or_default()
80 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old| {
81 (old < value).then_some(value)
82 });
83 }
84
85 /// Set the absolute value of the counter if the provided value is smaller than the current value.
86 pub fn min(&self, value: u64) {
87 let _ =
88 self.arena
89 .get_or_default()
90 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old| {
91 (old > value).then_some(value)
92 });
93 }
94
95 /// Get the counter value of the calling cpu, or `None` if the counter was never written to.
96 pub fn get(&self) -> Option<u64> {
97 Some(self.arena.get()?.load(Ordering::Relaxed))
98 }
99
100 /// Return the sum of all counters across all cpus.
101 pub fn sum_across_all_cpus(&self) -> u64 {
102 self.arena.iter().map(|v| v.load(Ordering::Relaxed)).sum()
103 }
104
105 /// Return the largest value from across all CPUs.
106 pub fn max_across_all_cpus(&self) -> u64 {
107 self.arena
108 .iter()
109 .map(|v| v.load(Ordering::Relaxed))
110 .max()
111 .unwrap()
112 }
113
114 /// Return the smallest value from across all CPUs.
115 pub fn min_across_all_cpus(&self) -> u64 {
116 self.arena
117 .iter()
118 .map(|v| v.load(Ordering::Relaxed))
119 .min()
120 .unwrap()
121 }
122}