inspect_counters/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Inspectable types for implementing performance counters.
5
6#![forbid(unsafe_code)]
7
8use inspect::Inspect;
9use std::sync::atomic::AtomicU64;
10use std::sync::atomic::Ordering;
11
12/// A simple 64-bit counter.
13#[derive(Debug, Default, Clone)]
14pub struct Counter(u64);
15
16impl Counter {
17    /// Returns an empty counter.
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    /// Increments the counter by one.
23    pub fn increment(&mut self) {
24        self.add(1);
25    }
26
27    /// Adds `n` to the counter, wrapping on overflow.
28    pub fn add(&mut self, n: u64) {
29        self.0 = self.0.wrapping_add(n);
30    }
31
32    /// Gets the current counter value.
33    pub fn get(&self) -> u64 {
34        self.0
35    }
36}
37
38impl Inspect for Counter {
39    fn inspect(&self, req: inspect::Request<'_>) {
40        req.with_counter_format().value(self.0)
41    }
42}
43
44/// A 64-bit counter that can be concurrently accessed by multiple threads.
45///
46/// Prefer [`Counter`] for counters that are not accessed concurrently.
47#[derive(Debug, Default)]
48pub struct SharedCounter(AtomicU64);
49
50impl SharedCounter {
51    /// Returns an empty counter.
52    pub fn new() -> Self {
53        Self::default()
54    }
55
56    /// Increments the counter by one.
57    pub fn increment(&self) {
58        self.add(1);
59    }
60
61    /// Adds `n` to the counter, wrapping on overflow.
62    pub fn add(&self, n: u64) {
63        self.0.fetch_add(n, Ordering::Relaxed);
64    }
65
66    /// Gets the current counter value.
67    pub fn get(&self) -> u64 {
68        self.0.load(Ordering::Relaxed)
69    }
70}
71
72impl Inspect for SharedCounter {
73    fn inspect(&self, req: inspect::Request<'_>) {
74        req.with_counter_format()
75            .value(self.0.load(Ordering::Relaxed))
76    }
77}
78
79/// A power-of-two histogram with `N` buckets.
80#[derive(Clone, Debug)]
81pub struct Histogram<const N: usize>([u64; N]);
82
83impl<const N: usize> Default for Histogram<N> {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89impl<const N: usize> Histogram<N> {
90    /// Returns an empty histogram.
91    pub fn new() -> Self {
92        assert!(N > 2);
93        assert!(N < BUCKETS.len());
94        Self([0; N])
95    }
96
97    /// Adds a sample to the histogram.
98    pub fn add_sample(&mut self, n: impl Into<u64>) {
99        self.0[(64 - n.into().leading_zeros() as usize).min(N - 1)] += 1;
100    }
101}
102
103static BUCKETS: &[&str] = &[
104    "0",
105    "1",
106    "2-3",
107    "4-7",
108    "8-15",
109    "16-31",
110    "32-63",
111    "64-127",
112    "128-255",
113    "256-511",
114    "512-1023",
115    "1024-2047",
116    "2048-4195",
117    "4196-8191",
118    "8192-16383",
119    "16384-32767",
120    "32768-65535",
121];
122
123static WIDTH: &[usize] = &[1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5];
124
125impl<const N: usize> Inspect for Histogram<N> {
126    fn inspect(&self, req: inspect::Request<'_>) {
127        let mut resp = req.respond();
128        for (i, &n) in self.0[..N - 1].iter().enumerate() {
129            resp.counter(BUCKETS[i], n);
130        }
131        resp.counter(&BUCKETS[N - 1][..WIDTH[N - 1] + 1], self.0[N - 1]);
132    }
133}