metricus/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod counter;
4mod histogram;
5
6use crate::access::get_metrics;
7// re-exports
8pub use counter::{Counter, CounterOps};
9pub use histogram::{Histogram, HistogramOps};
10use serde::{Deserialize, Serialize};
11use serde_with::serde_as;
12use std::collections::HashMap;
13use std::sync::atomic::{AtomicPtr, Ordering};
14
15/// Metric id.
16pub type Id = u64;
17/// Metric tag expresses as key-value pair.
18pub type Tag<'a> = (&'a str, &'a str);
19/// Metrics tags expresses as array of key-value pairs.
20pub type Tags<'a> = &'a [Tag<'a>];
21
22/// Returns empty tags.
23pub const fn empty_tags() -> Tags<'static> {
24    &[]
25}
26
27/// Common interface for metrics backend. Each new backend must implement this trait.
28pub trait Metrics {
29    fn name(&self) -> &'static str;
30
31    fn new_counter(&mut self, name: &str, tags: Tags) -> Id;
32
33    fn delete_counter(&mut self, id: Id);
34
35    fn increment_counter_by(&mut self, id: Id, delta: u64);
36
37    fn increment_counter(&mut self, id: Id) {
38        self.increment_counter_by(id, 1)
39    }
40
41    fn new_histogram(&mut self, name: &str, tags: Tags) -> Id;
42
43    fn delete_histogram(&mut self, id: Id);
44
45    fn record(&mut self, id: Id, value: u64);
46}
47
48trait IntoHandle {
49    fn into_handle(self) -> MetricsHandle;
50}
51
52impl<T: Metrics + Sized> IntoHandle for T {
53    fn into_handle(self) -> MetricsHandle {
54        let name = self.name();
55        let ptr = Box::into_raw(Box::new(self)) as *mut _;
56
57        let vtable = MetricsVTable {
58            new_counter: new_counter_raw::<Self>,
59            delete_counter: delete_counter_raw::<Self>,
60            increment_counter: increment_counter_raw::<Self>,
61            increment_counter_by: increment_counter_by_raw::<Self>,
62            new_histogram: new_histogram_raw::<Self>,
63            delete_histogram: delete_histogram_raw::<Self>,
64            record: record_raw::<Self>,
65        };
66        MetricsHandle { ptr, vtable, name }
67    }
68}
69
70#[inline]
71fn new_counter_raw<T: Metrics>(ptr: *mut u8, name: &str, tags: Tags) -> Id {
72    let metrics = unsafe { &mut *(ptr as *mut T) };
73    metrics.new_counter(name, tags)
74}
75
76#[inline]
77fn delete_counter_raw<T: Metrics>(ptr: *mut u8, id: Id) {
78    let metrics = unsafe { &mut *(ptr as *mut T) };
79    metrics.delete_counter(id)
80}
81
82#[inline]
83fn increment_counter_by_raw<T: Metrics>(ptr: *mut u8, id: Id, delta: u64) {
84    let metrics = unsafe { &mut *(ptr as *mut T) };
85    metrics.increment_counter_by(id, delta)
86}
87
88#[inline]
89fn increment_counter_raw<T: Metrics>(ptr: *mut u8, id: Id) {
90    increment_counter_by_raw::<T>(ptr, id, 1)
91}
92
93#[inline]
94fn new_histogram_raw<T: Metrics>(ptr: *mut u8, name: &str, tags: Tags) -> Id {
95    let metrics = unsafe { &mut *(ptr as *mut T) };
96    metrics.new_histogram(name, tags)
97}
98
99#[inline]
100fn delete_histogram_raw<T: Metrics>(ptr: *mut u8, id: Id) {
101    let metrics = unsafe { &mut *(ptr as *mut T) };
102    metrics.delete_histogram(id)
103}
104
105#[inline]
106fn record_raw<T: Metrics>(ptr: *mut u8, id: Id, value: u64) {
107    let metrics = unsafe { &mut *(ptr as *mut T) };
108    metrics.record(id, value)
109}
110
111/// Pre-allocated metric consists of name, id and tags.
112#[serde_as]
113#[derive(Serialize, Deserialize, Debug, Clone)]
114#[serde(rename_all = "snake_case")]
115#[serde(tag = "type")]
116pub enum PreAllocatedMetric {
117    Counter {
118        name: String,
119        id: Id,
120        #[serde_as(as = "HashMap<_, _>")]
121        #[serde(default)]
122        tags: Vec<(String, String)>,
123    },
124    Histogram {
125        name: String,
126        id: Id,
127        #[serde_as(as = "HashMap<_, _>")]
128        #[serde(default)]
129        tags: Vec<(String, String)>,
130    },
131}
132
133impl PreAllocatedMetric {
134    pub fn counter(name: &str, id: Id, tags: &[Tag]) -> Self {
135        PreAllocatedMetric::Counter {
136            name: name.to_owned(),
137            id,
138            tags: tags.iter().map(|tag| (tag.0.to_owned(), tag.1.to_owned())).collect(),
139        }
140    }
141
142    pub fn histogram(name: &str, id: Id, tags: &[Tag]) -> Self {
143        PreAllocatedMetric::Histogram {
144            name: name.to_owned(),
145            id,
146            tags: tags.iter().map(|tag| (tag.0.to_owned(), tag.1.to_owned())).collect(),
147        }
148    }
149}
150
151/// A trivial no-op backend for the "uninitialized" state.
152struct NoOpMetrics;
153
154impl Metrics for NoOpMetrics {
155    fn name(&self) -> &'static str {
156        "no-op"
157    }
158
159    fn new_counter(&mut self, _name: &str, _tags: Tags) -> Id {
160        Id::default()
161    }
162
163    fn delete_counter(&mut self, _id: Id) {
164        // no-op
165    }
166
167    fn increment_counter_by(&mut self, _id: Id, _delta: u64) {
168        // no-op
169    }
170
171    fn new_histogram(&mut self, _name: &str, _tags: Tags) -> Id {
172        Id::default()
173    }
174
175    fn delete_histogram(&mut self, _id: Id) {
176        // no-op
177    }
178
179    fn record(&mut self, _id: Id, _value: u64) {
180        // no-op
181    }
182}
183
184const NO_OP_METRICS: NoOpMetrics = NoOpMetrics;
185
186const NO_OP_METRICS_VTABLE: MetricsVTable = MetricsVTable {
187    new_counter: new_counter_raw::<NoOpMetrics>,
188    delete_counter: delete_counter_raw::<NoOpMetrics>,
189    increment_counter: increment_counter_raw::<NoOpMetrics>,
190    increment_counter_by: increment_counter_by_raw::<NoOpMetrics>,
191    new_histogram: new_histogram_raw::<NoOpMetrics>,
192    delete_histogram: delete_histogram_raw::<NoOpMetrics>,
193    record: record_raw::<NoOpMetrics>,
194};
195
196const NO_OP_METRICS_HANDLE: MetricsHandle = MetricsHandle {
197    ptr: &NO_OP_METRICS as *const NoOpMetrics as *mut u8,
198    vtable: NO_OP_METRICS_VTABLE,
199    name: "no-op",
200};
201
202struct MetricsHolder {
203    handle: AtomicRef<MetricsHandle>,
204}
205
206/// Initially set to no-op backend.
207static mut METRICS: MetricsHolder = MetricsHolder {
208    handle: AtomicRef::new(&NO_OP_METRICS_HANDLE),
209};
210
211/// Set a new metrics backend. This should be called as early as possible. Otherwise,
212/// all metrics calls will delegate to the `NoOpMetrics`.
213pub fn set_metrics(metrics: impl Metrics) {
214    #[allow(static_mut_refs)]
215    unsafe { &mut METRICS }
216        .handle
217        .set(Box::leak(Box::new(metrics.into_handle())), Ordering::SeqCst);
218}
219
220/// Get name of the active metrics backend.
221pub fn get_metrics_backend_name() -> &'static str {
222    get_metrics().name
223}
224
225struct MetricsVTable {
226    new_counter: fn(*mut u8, &str, Tags) -> Id,
227    delete_counter: fn(*mut u8, Id),
228    increment_counter: fn(*mut u8, Id),
229    increment_counter_by: fn(*mut u8, Id, u64),
230    new_histogram: fn(*mut u8, &str, Tags) -> Id,
231    delete_histogram: fn(*mut u8, Id),
232    record: fn(*mut u8, Id, u64),
233}
234
235/// Metrics backend handle.
236pub struct MetricsHandle {
237    ptr: *mut u8,
238    vtable: MetricsVTable,
239    name: &'static str,
240}
241
242impl MetricsHandle {
243    #[inline]
244    fn new_counter(&mut self, name: &str, tags: Tags) -> Id {
245        (self.vtable.new_counter)(self.ptr, name, tags)
246    }
247
248    #[inline]
249    fn delete_counter(&mut self, id: Id) {
250        (self.vtable.delete_counter)(self.ptr, id)
251    }
252
253    #[inline]
254    fn increment_counter_by(&mut self, id: Id, delta: u64) {
255        (self.vtable.increment_counter_by)(self.ptr, id, delta)
256    }
257
258    #[inline]
259    fn increment_counter(&mut self, id: Id) {
260        (self.vtable.increment_counter)(self.ptr, id)
261    }
262
263    #[inline]
264    fn new_histogram(&mut self, name: &str, tags: Tags) -> Id {
265        (self.vtable.new_histogram)(self.ptr, name, tags)
266    }
267
268    #[inline]
269    fn delete_histogram(&mut self, id: Id) {
270        (self.vtable.delete_histogram)(self.ptr, id)
271    }
272
273    #[inline]
274    fn record(&mut self, id: Id, value: u64) {
275        (self.vtable.record)(self.ptr, id, value)
276    }
277}
278
279struct AtomicRef<T> {
280    ptr: AtomicPtr<T>,
281}
282
283impl<T> AtomicRef<T> {
284    pub const fn new(data: &T) -> Self {
285        Self {
286            ptr: AtomicPtr::new(data as *const T as *mut T),
287        }
288    }
289
290    #[inline]
291    pub fn get(&self, order: Ordering) -> &T {
292        unsafe { &*self.ptr.load(order) }
293    }
294
295    #[inline]
296    pub fn get_mut(&mut self, order: Ordering) -> &mut T {
297        unsafe { &mut *self.ptr.load(order) }
298    }
299
300    #[inline]
301    pub fn set(&self, new_ref: &T, order: Ordering) {
302        self.ptr.store(new_ref as *const T as *mut T, order);
303    }
304}
305
306mod access {
307    use crate::{MetricsHandle, METRICS};
308    use std::sync::atomic::Ordering;
309
310    #[allow(static_mut_refs)]
311    pub fn get_metrics_mut() -> &'static mut MetricsHandle {
312        unsafe { &mut METRICS }.handle.get_mut(Ordering::Acquire)
313    }
314
315    #[allow(static_mut_refs)]
316    pub fn get_metrics() -> &'static MetricsHandle {
317        unsafe { &METRICS }.handle.get(Ordering::Acquire)
318    }
319}