metrics_core/
lib.rs

1//! Foundational traits for interoperable metrics libraries in Rust.
2//!
3//! # Common Ground
4//! Most libraries, under the hood, are all based around a core set of data types: counters,
5//! gauges, and histograms.  While the API surface may differ, the underlying data is the same.
6//!
7//! # Metric Types
8//!
9//! ## Counters
10//! Counters represent a single value that can only ever be incremented over time, or reset to
11//! zero.
12//!
13//! Counters are useful for tracking things like operations completed, or errors raised, where
14//! the value naturally begins at zero when a process or service is started or restarted.
15//!
16//! ## Gauges
17//! Gauges represent a single value that can go up _or_ down over time.
18//!
19//! Gauges are useful for tracking things like the current number of connected users, or a stock
20//! price, or the temperature outside.
21//!
22//! ## Histograms
23//! Histograms measure the distribution of values for a given set of measurements.
24//!
25//! Histograms are generally used to derive statistics about a particular measurement from an
26//! operation or event that happens over and over, such as the duration of a request, or number of
27//! rows returned by a particular database query.
28//!
29//! Histograms allow you to answer questions of these measurements, such as:
30//! - "What were the fastest and slowest requests in this window?"
31//! - "What is the slowest request we've seen out of 90% of the requests measured? 99%?"
32//!
33//! Histograms are a convenient way to measure behavior not only at the median, but at the edges of
34//! normal operating behavior.
35#![deny(missing_docs)]
36use std::{borrow::Cow, fmt, slice::Iter, time::Duration};
37
38/// An allocation-optimized string.
39///
40/// We specify `ScopedString` to attempt to get the best of both worlds: flexibility to provide a
41/// static or dynamic (owned) string, while retaining the performance benefits of being able to
42/// take ownership of owned strings and borrows of completely static strings.
43pub type ScopedString = Cow<'static, str>;
44
45/// A key/value pair used to further describe a metric.
46#[derive(PartialEq, Eq, Hash, Clone, Debug)]
47pub struct Label(ScopedString, ScopedString);
48
49impl Label {
50    /// Creates a `Label` from a key and value.
51    pub fn new<K, V>(key: K, value: V) -> Self
52    where
53        K: Into<ScopedString>,
54        V: Into<ScopedString>,
55    {
56        Label(key.into(), value.into())
57    }
58
59    /// The key of this label.
60    pub fn key(&self) -> &str {
61        self.0.as_ref()
62    }
63
64    /// The value of this label.
65    pub fn value(&self) -> &str {
66        self.1.as_ref()
67    }
68
69    /// Consumes this `Label`, returning the key and value.
70    pub fn into_parts(self) -> (ScopedString, ScopedString) {
71        (self.0, self.1)
72    }
73}
74
75/// A metric key.
76///
77/// A key always includes a name, but can optional include multiple labels used to further describe
78/// the metric.
79#[derive(PartialEq, Eq, Hash, Clone, Debug)]
80pub struct Key {
81    name: ScopedString,
82    labels: Vec<Label>,
83}
84
85impl Key {
86    /// Creates a `Key` from a name.
87    pub fn from_name<N>(name: N) -> Self
88    where
89        N: Into<ScopedString>,
90    {
91        Key {
92            name: name.into(),
93            labels: Vec::new(),
94        }
95    }
96
97    /// Creates a `Key` from a name and vector of `Label`s.
98    pub fn from_name_and_labels<N, L>(name: N, labels: L) -> Self
99    where
100        N: Into<ScopedString>,
101        L: IntoLabels,
102    {
103        Key {
104            name: name.into(),
105            labels: labels.into_labels(),
106        }
107    }
108
109    /// Adds a new set of labels to this key.
110    ///
111    /// New labels will be appended to any existing labels.
112    pub fn add_labels<L>(&mut self, new_labels: L)
113    where
114        L: IntoLabels,
115    {
116        self.labels.extend(new_labels.into_labels());
117    }
118
119    /// Name of this key.
120    pub fn name(&self) -> ScopedString {
121        self.name.clone()
122    }
123
124    /// Labels of this key, if they exist.
125    pub fn labels(&self) -> Iter<Label> {
126        self.labels.iter()
127    }
128
129    /// Maps the name of this `Key` to a new name.
130    pub fn map_name<F, S>(self, f: F) -> Self
131    where
132        F: FnOnce(ScopedString) -> S,
133        S: Into<ScopedString>,
134    {
135        Key {
136            name: f(self.name).into(),
137            labels: self.labels,
138        }
139    }
140
141    /// Consumes this `Key`, returning the name and any labels.
142    pub fn into_parts(self) -> (ScopedString, Vec<Label>) {
143        (self.name, self.labels)
144    }
145}
146
147impl fmt::Display for Key {
148    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149        if self.labels.is_empty() {
150            write!(f, "Key({})", self.name)
151        } else {
152            let kv_pairs = self
153                .labels
154                .iter()
155                .map(|label| format!("{} = {}", label.0, label.1))
156                .collect::<Vec<_>>();
157            write!(f, "Key({}, [{}])", self.name, kv_pairs.join(", "))
158        }
159    }
160}
161
162impl From<String> for Key {
163    fn from(name: String) -> Key {
164        Key::from_name(name)
165    }
166}
167
168impl From<&'static str> for Key {
169    fn from(name: &'static str) -> Key {
170        Key::from_name(name)
171    }
172}
173
174impl From<ScopedString> for Key {
175    fn from(name: ScopedString) -> Key {
176        Key::from_name(name)
177    }
178}
179
180impl<K, L> From<(K, L)> for Key
181where
182    K: Into<ScopedString>,
183    L: IntoLabels,
184{
185    fn from(parts: (K, L)) -> Key {
186        Key::from_name_and_labels(parts.0, parts.1)
187    }
188}
189
190impl<K, V> From<(K, V)> for Label
191where
192    K: Into<ScopedString>,
193    V: Into<ScopedString>,
194{
195    fn from(pair: (K, V)) -> Label {
196        Label::new(pair.0, pair.1)
197    }
198}
199
200impl<K, V> From<&(K, V)> for Label
201where
202    K: Into<ScopedString> + Clone,
203    V: Into<ScopedString> + Clone,
204{
205    fn from(pair: &(K, V)) -> Label {
206        Label::new(pair.0.clone(), pair.1.clone())
207    }
208}
209
210/// A value that can be converted to `Label`s.
211pub trait IntoLabels {
212    /// Consumes this value, turning it into a vector of `Label`s.
213    fn into_labels(self) -> Vec<Label>;
214}
215
216impl IntoLabels for Vec<Label> {
217    fn into_labels(self) -> Vec<Label> {
218        self
219    }
220}
221
222impl<T, L> IntoLabels for &T
223where
224    Self: IntoIterator<Item = L>,
225    L: Into<Label>,
226{
227    fn into_labels(self) -> Vec<Label> {
228        self.into_iter().map(|l| l.into()).collect()
229    }
230}
231
232/// Used to do a nanosecond conversion.
233///
234/// This trait allows us to interchangably accept raw integer time values, ones already in
235/// nanoseconds, as well as the more conventional [`Duration`] which is a result of getting the
236/// difference between two [`Instant`](std::time::Instant)s.
237pub trait AsNanoseconds {
238    /// Performs the conversion.
239    fn as_nanos(&self) -> u64;
240}
241
242impl AsNanoseconds for u64 {
243    fn as_nanos(&self) -> u64 {
244        *self
245    }
246}
247
248impl AsNanoseconds for Duration {
249    fn as_nanos(&self) -> u64 {
250        self.as_nanos() as u64
251    }
252}
253
254/// A value that observes metrics.
255pub trait Observer {
256    /// The method called when a counter is observed.
257    ///
258    /// From the perspective of an observer, a counter and gauge are essentially identical, insofar
259    /// as they are both a single value tied to a key.  From the perspective of a collector,
260    /// counters and gauges usually have slightly different modes of operation.
261    ///
262    /// For the sake of flexibility on the exporter side, both are provided.
263    fn observe_counter(&mut self, key: Key, value: u64);
264
265    /// The method called when a gauge is observed.
266    ///
267    /// From the perspective of a observer, a counter and gauge are essentially identical, insofar
268    /// as they are both a single value tied to a key.  From the perspective of a collector,
269    /// counters and gauges usually have slightly different modes of operation.
270    ///
271    /// For the sake of flexibility on the exporter side, both are provided.
272    fn observe_gauge(&mut self, key: Key, value: i64);
273
274    /// The method called when an histogram is observed.
275    ///
276    /// Observers are expected to tally their own histogram views, so this will be called with all
277    /// of the underlying observed values, and callers will need to process them accordingly.
278    ///
279    /// There is no guarantee that this method will not be called multiple times for the same key.
280    fn observe_histogram(&mut self, key: Key, values: &[u64]);
281}
282
283/// A value that can build an observer.
284///
285/// Observers are containers used for rendering a snapshot in a particular format.
286/// As many systems are multi-threaded, we can't easily share a single recorder amongst
287/// multiple threads, and so we create a recorder per observation, tying them together.
288///
289/// A builder allows us to generate an observer on demand, giving each specific recorder an
290/// interface by which they can do any necessary configuration, initialization, etc of the
291/// observer before handing it over to the exporter.
292pub trait Builder {
293    /// The observer created by this builder.
294    type Output: Observer;
295
296    /// Creates a new recorder.
297    fn build(&self) -> Self::Output;
298}
299
300/// A value that can produce a `T` by draining its content.
301///
302/// After being drained, the value should be ready to be reused.
303pub trait Drain<T> {
304    /// Drain the `Observer`, producing a `T`.
305    fn drain(&mut self) -> T;
306}
307
308/// A value whose metrics can be observed by an `Observer`.
309pub trait Observe {
310    /// Observe point-in-time view of the collected metrics.
311    fn observe<O: Observer>(&self, observer: &mut O);
312}
313
314/// Helper macro for generating a set of labels.
315///
316/// While a `Label` can be generated manually, most users will tend towards the key => value format
317/// commonly used for defining hashes/maps in many programming languages.  This macro allows users
318/// to do the exact same thing in calls that depend on [`metrics_core::IntoLabels`].
319///
320/// # Examples
321/// ```rust
322/// # #[macro_use] extern crate metrics_core;
323/// # use metrics_core::IntoLabels;
324/// fn takes_labels<L: IntoLabels>(name: &str, labels: L) {
325///     println!("name: {} labels: {:?}", name, labels.into_labels());
326/// }
327///
328/// takes_labels("requests_processed", labels!("request_type" => "admin"));
329/// ```
330#[macro_export]
331macro_rules! labels {
332    (@ { $($out:expr),* $(,)* } $(,)*) => {
333        std::vec![ $($out),* ]
334    };
335
336    (@ { } $k:expr => $v:expr, $($rest:tt)*) => {
337        $crate::labels!(@ { $crate::Label::new($k, $v) } $($rest)*)
338    };
339
340    (@ { $($out:expr),+ } $k:expr => $v:expr, $($rest:tt)*) => {
341        $crate::labels!(@ { $($out),+, $crate::Label::new($k, $v) } $($rest)*)
342    };
343
344    ($($args:tt)*) => {
345        $crate::labels!(@ { } $($args)*, )
346    };
347}