Skip to main content

reovim_kernel/debug/
metrics.rs

1//! Metrics collection infrastructure.
2//!
3//! Linux equivalent: `kernel/trace/ftrace` (function tracing metrics)
4//!
5//! Provides counters and histograms for performance monitoring.
6//! All operations are lock-free using atomics for minimal overhead.
7
8use std::{
9    collections::HashMap,
10    sync::{
11        OnceLock,
12        atomic::{AtomicU64, Ordering},
13    },
14};
15
16use reovim_arch::sync::RwLock;
17
18/// Counter for tracking occurrences.
19///
20/// Uses atomic operations for lock-free updates.
21///
22/// # Example
23///
24/// ```ignore
25/// use reovim_kernel::debug::Counter;
26///
27/// static BUFFER_CREATES: Counter = Counter::new();
28///
29/// fn create_buffer() {
30///     BUFFER_CREATES.increment();
31///     // ...
32/// }
33/// ```
34pub struct Counter {
35    value: AtomicU64,
36}
37
38impl Counter {
39    /// Create a new counter initialized to zero.
40    #[must_use]
41    pub const fn new() -> Self {
42        Self {
43            value: AtomicU64::new(0),
44        }
45    }
46
47    /// Increment the counter by 1.
48    pub fn increment(&self) {
49        self.value.fetch_add(1, Ordering::Relaxed);
50    }
51
52    /// Add a value to the counter.
53    pub fn add(&self, n: u64) {
54        self.value.fetch_add(n, Ordering::Relaxed);
55    }
56
57    /// Get the current counter value.
58    #[must_use]
59    pub fn get(&self) -> u64 {
60        self.value.load(Ordering::Relaxed)
61    }
62
63    /// Reset the counter to zero.
64    pub fn reset(&self) {
65        self.value.store(0, Ordering::Relaxed);
66    }
67}
68
69impl Default for Counter {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75/// Histogram for tracking value distributions.
76///
77/// Uses power-of-two bucket boundaries for efficient indexing.
78/// Bucket boundaries: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, inf
79///
80/// # Example
81///
82/// ```ignore
83/// use reovim_kernel::debug::Histogram;
84///
85/// static RESPONSE_TIMES: Histogram = Histogram::new();
86///
87/// fn handle_request() {
88///     let start = std::time::Instant::now();
89///     // ... handle request ...
90///     RESPONSE_TIMES.record(start.elapsed().as_micros() as u64);
91/// }
92/// ```
93pub struct Histogram {
94    /// 16 buckets with power-of-two boundaries.
95    buckets: [AtomicU64; 16],
96    /// Sum of all recorded values.
97    sum: AtomicU64,
98    /// Count of all recorded values.
99    count: AtomicU64,
100}
101
102impl Histogram {
103    /// Create a new histogram.
104    #[must_use]
105    pub const fn new() -> Self {
106        Self {
107            buckets: [
108                AtomicU64::new(0),
109                AtomicU64::new(0),
110                AtomicU64::new(0),
111                AtomicU64::new(0),
112                AtomicU64::new(0),
113                AtomicU64::new(0),
114                AtomicU64::new(0),
115                AtomicU64::new(0),
116                AtomicU64::new(0),
117                AtomicU64::new(0),
118                AtomicU64::new(0),
119                AtomicU64::new(0),
120                AtomicU64::new(0),
121                AtomicU64::new(0),
122                AtomicU64::new(0),
123                AtomicU64::new(0),
124            ],
125            sum: AtomicU64::new(0),
126            count: AtomicU64::new(0),
127        }
128    }
129
130    /// Record a value in the histogram.
131    ///
132    /// Value 0 goes to bucket 0.
133    /// Values 1-1 go to bucket 1 (2^0).
134    /// Values 2-3 go to bucket 2 (2^1).
135    /// And so on...
136    pub fn record(&self, value: u64) {
137        // Calculate bucket index using leading zeros
138        // For value 0, this gives bucket 0
139        // For values 1-1, bucket 1
140        // For values 2-3, bucket 2
141        // etc.
142        let bucket = if value == 0 {
143            0
144        } else {
145            (64 - value.leading_zeros()).min(15) as usize
146        };
147
148        self.buckets[bucket].fetch_add(1, Ordering::Relaxed);
149        self.sum.fetch_add(value, Ordering::Relaxed);
150        self.count.fetch_add(1, Ordering::Relaxed);
151    }
152
153    /// Get the mean value of all recorded samples.
154    #[must_use]
155    #[allow(clippy::cast_precision_loss)] // Acceptable for metrics
156    pub fn mean(&self) -> f64 {
157        let count = self.count.load(Ordering::Relaxed);
158        if count == 0 {
159            0.0
160        } else {
161            self.sum.load(Ordering::Relaxed) as f64 / count as f64
162        }
163    }
164
165    /// Get the total count of recorded samples.
166    #[must_use]
167    pub fn count(&self) -> u64 {
168        self.count.load(Ordering::Relaxed)
169    }
170
171    /// Get the sum of all recorded values.
172    #[must_use]
173    pub fn sum(&self) -> u64 {
174        self.sum.load(Ordering::Relaxed)
175    }
176
177    /// Get bucket counts.
178    #[must_use]
179    pub fn buckets(&self) -> [u64; 16] {
180        let mut result = [0u64; 16];
181        for (i, bucket) in self.buckets.iter().enumerate() {
182            result[i] = bucket.load(Ordering::Relaxed);
183        }
184        result
185    }
186
187    /// Reset all histogram data.
188    pub fn reset(&self) {
189        for bucket in &self.buckets {
190            bucket.store(0, Ordering::Relaxed);
191        }
192        self.sum.store(0, Ordering::Relaxed);
193        self.count.store(0, Ordering::Relaxed);
194    }
195}
196
197impl Default for Histogram {
198    fn default() -> Self {
199        Self::new()
200    }
201}
202
203/// Metrics registry for named metrics.
204///
205/// Provides a centralized registry for counters and histograms.
206/// Uses `Arc` for safe shared access without raw pointers.
207pub struct MetricsRegistry {
208    counters: RwLock<HashMap<&'static str, std::sync::Arc<Counter>>>,
209    histograms: RwLock<HashMap<&'static str, std::sync::Arc<Histogram>>>,
210}
211
212impl MetricsRegistry {
213    /// Create a new empty registry.
214    #[must_use]
215    pub fn new() -> Self {
216        Self {
217            counters: RwLock::new(HashMap::new()),
218            histograms: RwLock::new(HashMap::new()),
219        }
220    }
221
222    /// Get or create a counter by name.
223    ///
224    /// Returns an `Arc<Counter>` that can be used to update the counter.
225    #[must_use]
226    pub fn counter(&self, name: &'static str) -> std::sync::Arc<Counter> {
227        // First try read-only access
228        {
229            let counters = self.counters.read();
230            if let Some(counter) = counters.get(name) {
231                return counter.clone();
232            }
233        }
234
235        // Need to insert - acquire write lock
236        let mut counters = self.counters.write();
237        counters
238            .entry(name)
239            .or_insert_with(|| std::sync::Arc::new(Counter::new()))
240            .clone()
241    }
242
243    /// Get or create a histogram by name.
244    #[must_use]
245    pub fn histogram(&self, name: &'static str) -> std::sync::Arc<Histogram> {
246        {
247            let histograms = self.histograms.read();
248            if let Some(histogram) = histograms.get(name) {
249                return histogram.clone();
250            }
251        }
252
253        let mut histograms = self.histograms.write();
254        histograms
255            .entry(name)
256            .or_insert_with(|| std::sync::Arc::new(Histogram::new()))
257            .clone()
258    }
259
260    /// Take a snapshot of all metrics.
261    #[must_use]
262    pub fn snapshot(&self) -> MetricsSnapshot {
263        let counters = self.counters.read();
264        let histograms = self.histograms.read();
265
266        MetricsSnapshot {
267            counters: counters.iter().map(|(k, v)| (*k, v.get())).collect(),
268            histograms: histograms
269                .iter()
270                .map(|(k, v)| (*k, (v.count(), v.mean())))
271                .collect(),
272        }
273    }
274}
275
276impl Default for MetricsRegistry {
277    fn default() -> Self {
278        Self::new()
279    }
280}
281
282/// Snapshot of all metrics at a point in time.
283#[derive(Debug)]
284pub struct MetricsSnapshot {
285    /// Counter name -> current value.
286    pub counters: HashMap<&'static str, u64>,
287    /// Histogram name -> (count, mean).
288    pub histograms: HashMap<&'static str, (u64, f64)>,
289}
290
291/// Global metrics registry.
292static METRICS: OnceLock<MetricsRegistry> = OnceLock::new();
293
294/// Get the global metrics registry.
295#[must_use]
296pub fn metrics() -> &'static MetricsRegistry {
297    METRICS.get_or_init(MetricsRegistry::new)
298}