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}