#[cfg(feature = "runtime-metrics")]
use core::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Metrics {
pub timer_threads_spawned: u64,
pub sched_yield_calls: u64,
pub worker_spins: u64,
pub worker_parks: u64,
pub worker_unparks: u64,
pub timers_registered: u64,
pub timers_fired: u64,
pub timers_cancelled: u64,
pub active_timers: u64,
}
impl core::fmt::Display for Metrics {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"timer_threads_spawned={} sched_yield_calls={} worker_spins={} \
worker_parks={} worker_unparks={} timers_registered={} \
timers_fired={} timers_cancelled={} active_timers={}",
self.timer_threads_spawned,
self.sched_yield_calls,
self.worker_spins,
self.worker_parks,
self.worker_unparks,
self.timers_registered,
self.timers_fired,
self.timers_cancelled,
self.active_timers,
)
}
}
#[cfg(feature = "runtime-metrics")]
struct Counters {
timer_threads_spawned: AtomicU64,
sched_yield_calls: AtomicU64,
worker_spins: AtomicU64,
worker_parks: AtomicU64,
worker_unparks: AtomicU64,
timers_registered: AtomicU64,
timers_fired: AtomicU64,
timers_cancelled: AtomicU64,
}
#[cfg(feature = "runtime-metrics")]
static COUNTERS: Counters = Counters {
timer_threads_spawned: AtomicU64::new(0),
sched_yield_calls: AtomicU64::new(0),
worker_spins: AtomicU64::new(0),
worker_parks: AtomicU64::new(0),
worker_unparks: AtomicU64::new(0),
timers_registered: AtomicU64::new(0),
timers_fired: AtomicU64::new(0),
timers_cancelled: AtomicU64::new(0),
};
#[inline]
pub fn record_timer_thread_spawned() {
#[cfg(feature = "runtime-metrics")]
COUNTERS
.timer_threads_spawned
.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_sched_yield() {
#[cfg(feature = "runtime-metrics")]
COUNTERS.sched_yield_calls.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_worker_spin() {
#[cfg(feature = "runtime-metrics")]
COUNTERS.worker_spins.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_worker_park() {
#[cfg(feature = "runtime-metrics")]
COUNTERS.worker_parks.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_worker_unpark() {
#[cfg(feature = "runtime-metrics")]
COUNTERS.worker_unparks.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_timer_registered() {
#[cfg(feature = "runtime-metrics")]
COUNTERS.timers_registered.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_timers_fired(count: u64) {
#[cfg(feature = "runtime-metrics")]
COUNTERS.timers_fired.fetch_add(count, Ordering::Relaxed);
#[cfg(not(feature = "runtime-metrics"))]
let _ = count;
}
#[inline]
pub fn record_timer_cancelled() {
#[cfg(feature = "runtime-metrics")]
COUNTERS.timers_cancelled.fetch_add(1, Ordering::Relaxed);
}
#[must_use]
pub fn snapshot() -> Metrics {
#[cfg(feature = "runtime-metrics")]
{
let registered = COUNTERS.timers_registered.load(Ordering::Relaxed);
let fired = COUNTERS.timers_fired.load(Ordering::Relaxed);
let cancelled = COUNTERS.timers_cancelled.load(Ordering::Relaxed);
Metrics {
timer_threads_spawned: COUNTERS.timer_threads_spawned.load(Ordering::Relaxed),
sched_yield_calls: COUNTERS.sched_yield_calls.load(Ordering::Relaxed),
worker_spins: COUNTERS.worker_spins.load(Ordering::Relaxed),
worker_parks: COUNTERS.worker_parks.load(Ordering::Relaxed),
worker_unparks: COUNTERS.worker_unparks.load(Ordering::Relaxed),
timers_registered: registered,
timers_fired: fired,
timers_cancelled: cancelled,
active_timers: registered.saturating_sub(fired.saturating_add(cancelled)),
}
}
#[cfg(not(feature = "runtime-metrics"))]
{
Metrics::default()
}
}
#[cfg(feature = "runtime-metrics")]
pub fn reset() {
COUNTERS.timer_threads_spawned.store(0, Ordering::Relaxed);
COUNTERS.sched_yield_calls.store(0, Ordering::Relaxed);
COUNTERS.worker_spins.store(0, Ordering::Relaxed);
COUNTERS.worker_parks.store(0, Ordering::Relaxed);
COUNTERS.worker_unparks.store(0, Ordering::Relaxed);
COUNTERS.timers_registered.store(0, Ordering::Relaxed);
COUNTERS.timers_fired.store(0, Ordering::Relaxed);
COUNTERS.timers_cancelled.store(0, Ordering::Relaxed);
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "runtime-metrics"))]
#[test]
fn snapshot_is_zero_when_feature_disabled() {
record_timer_thread_spawned();
record_sched_yield();
record_worker_spin();
record_worker_park();
record_worker_unpark();
record_timer_registered();
record_timers_fired(5);
record_timer_cancelled();
assert_eq!(snapshot(), Metrics::default());
}
#[cfg(feature = "runtime-metrics")]
#[test]
fn record_helpers_increment_their_counters() {
let before = snapshot();
record_timer_thread_spawned();
record_sched_yield();
record_sched_yield();
record_worker_spin();
record_worker_park();
record_worker_unpark();
record_timer_registered();
record_timer_registered();
record_timer_registered();
record_timers_fired(2);
record_timer_cancelled();
let after = snapshot();
assert!(after.timer_threads_spawned >= before.timer_threads_spawned + 1);
assert!(after.sched_yield_calls >= before.sched_yield_calls + 2);
assert!(after.worker_spins >= before.worker_spins + 1);
assert!(after.worker_parks >= before.worker_parks + 1);
assert!(after.worker_unparks >= before.worker_unparks + 1);
assert!(after.timers_registered >= before.timers_registered + 3);
assert!(after.timers_fired >= before.timers_fired + 2);
assert!(after.timers_cancelled >= before.timers_cancelled + 1);
}
#[cfg(feature = "runtime-metrics")]
#[test]
fn active_timers_is_consistent_and_saturating() {
record_timer_registered();
record_timers_fired(1);
let m = snapshot();
assert_eq!(
m.active_timers,
m.timers_registered
.saturating_sub(m.timers_fired.saturating_add(m.timers_cancelled))
);
}
#[test]
fn display_lists_all_counters() {
let s = format!("{}", Metrics::default());
for key in [
"timer_threads_spawned",
"sched_yield_calls",
"worker_spins",
"worker_parks",
"worker_unparks",
"timers_registered",
"timers_fired",
"timers_cancelled",
"active_timers",
] {
assert!(s.contains(key), "Display missing {key}: {s}");
}
}
}