metrics/
lib.rs

1//! A lightweight metrics facade.
2//!
3//! The `metrics` crate provides a single metrics API that abstracts over the actual metrics
4//! implementation.  Libraries can use the metrics API provided by this crate, and the consumer of
5//! those libraries can choose the metrics implementation that is most suitable for its use case.
6//!
7//! # Overview
8//! `metrics` exposes two main concepts: emitting a metric, and recording it.
9//!
10//! ## Metric types, or kinds
11//! This crate supports three fundamental metric types, or kinds: counters, gauges, and histograms.
12//!
13//! ### Counters
14//! A counter is a cumulative metric that represents a monotonically increasing value which can only
15//! be increased or be reset to zero on restart. For example, you might use a counter to
16//! represent the number of operations performed, or the number of errors that have occurred.
17//!
18//! Counters are unsigned 64-bit integers.
19//!
20//! If you have a value that goes up and down over time, consider using a gauge.
21//!
22//! ### Gauges
23//! A gauge is a metric that can go up and down, arbitrarily, over time.
24//!
25//! Gauges are typically used for measured, external values, such as temperature, throughput, or
26//! things like current memory usage.  Even if the value is monotonically increasing, but there is
27//! no way to store the delta in order to properly figure out how much to increment by, then a gauge
28//! might be a suitable choice.
29//!
30//! Gauges support two modes: incremental updates, or absolute updates.  This allows callers to use
31//! them for external measurements -- where no delta can be computed -- as well as internal measurements.
32//!
33//! Gauges are floating-point 64-bit numbers.
34//!
35//! ### Histograms
36//! A histogram stores an arbitrary number of observations of a specific measurement and provides
37//! statistical analysis over the observed values.  Typically, measurements such as request latency
38//! are recorded with histograms: a specific action that is repeated over and over which can have a
39//! varying result each time.
40//!
41//! Histograms are used to explore the distribution of values, allowing a caller to understand the
42//! modalities of the distribution, such as whether or not all values are grouped close together, or
43//! spread evenly, or even whether or not there are multiple groupings or clusters.
44//!
45//! Colloquially, histograms are usually associated with percentiles, although by definition, they
46//! specifically deal with bucketed or binned values: how many values fell within 0-10, how many
47//! fell within 11-20, and so on and so forth.  Percentiles, commonly associated with "summaries",
48//! deal with understanding how much of a distribution falls below or at a particular percentage of
49//! that distribution: 50% of requests are faster than 500ms, 99% of requests are faster than
50//! 2450ms, and so on and so forth.
51//!
52//! While we use the term "histogram" in `metrics`, we enforce no particular usage of true
53//! histograms or summaries.  The choice of output is based entirely on the exporter being used to
54//! ship your metric data out of your application.  For example, if you're using
55//! [metrics-exporter-prometheus], Prometheus supports both histograms and summaries, and the
56//! exporter can be configured to output our "histogram" data as either.  Other exporters may choose
57//! to stick to using summaries, as is traditional, in order to generate percentile data.
58//!
59//! Histograms take floating-point 64-bit numbers.
60//!
61//! ## Emission
62//!
63//! Metrics are emitted by utilizing the emission methods.  There is a macro for
64//! registering and returning a handle for each fundamental metric type:
65//!
66//! - [`counter!`] returns the [`Counter`] handle then
67//!     - [`Counter::increment`] increments the counter.
68//!     - [`Counter::absolute`] sets the counter.
69//! - [`gauge!`] returns the [`Gauge`] handle then
70//!     - [`Gauge::increment`] increments the gauge.
71//!     - [`Gauge::decrement`] decrements the gauge.
72//!     - [`Gauge::set`] sets the gauge.
73//! - [`histogram!`] for histograms then
74//!     - [`Histogram::record`] records a data point.
75//!
76//! Additionally, metrics can be described -- setting either the unit of measure or long-form
77//! description -- by using the `describe_*` macros:
78//!
79//! - [`describe_counter!`] for counters
80//! - [`describe_gauge!`] for gauges
81//! - [`describe_histogram!`] for histograms
82//!
83//! In order to register or emit a metric, you need a way to record these events, which is where
84//! [`Recorder`] comes into play.
85//!
86//! ## Recording
87//! The [`Recorder`] trait defines the interface between the registration/emission macros, and
88//! exporters, which is how we refer to concrete implementations of [`Recorder`].  The trait defines
89//! what the exporters are doing -- recording -- but ultimately exporters are sending data from your
90//! application to somewhere else: whether it be a third-party service or logging via standard out.
91//! It's "exporting" the metric data out of your application.
92//!
93//! Each metric type is usually reserved for a specific type of use case, whether it be tracking a
94//! single value or allowing the summation of multiple values, and the respective macros elaborate
95//! more on the usage and invariants provided by each.
96//!
97//! # Getting Started
98//!
99//! ## In libraries
100//! Libraries need only include the `metrics` crate to emit metrics.  When an executable installs a
101//! recorder, all included crates which emitting metrics will now emit their metrics to that record,
102//! which allows library authors to seamless emit their own metrics without knowing or caring which
103//! exporter implementation is chosen, or even if one is installed.
104//!
105//! In cases where no global recorder is installed, a "noop" recorder lives in its place, which has
106//! an incredibly very low overhead: an atomic load and comparison.  Libraries can safely instrument
107//! their code without fear of ruining baseline performance.
108//!
109//! By default, a "noop" recorder is present so that the macros can work even if no exporter has
110//! been installed.  This recorder has extremely low overhead -- a relaxed load and conditional --
111//! and so, practically speaking, the overhead when no exporter is installed is extremely low.  You
112//! can safely instrument applications knowing that you won't pay a heavy performance cost even if
113//! you're not shipping metrics.
114//!
115//! ### Examples
116//!
117//! ```rust
118//! use metrics::{counter, histogram};
119//!
120//! # use std::time::Instant;
121//! # pub fn run_query(_: &str) -> u64 { 42 }
122//! pub fn process(query: &str) -> u64 {
123//!     let start = Instant::now();
124//!     let row_count = run_query(query);
125//!     let delta = start.elapsed();
126//!
127//!     histogram!("process.query_time").record(delta);
128//!     counter!("process.query_row_count").increment(row_count);
129//!
130//!     row_count
131//! }
132//! # fn main() {}
133//! ```
134//!
135//! ## In executables
136//!
137//! Executables, which themselves can emit their own metrics, are intended to install a global
138//! recorder so that metrics can actually be recorded and exported somewhere.
139//!
140//! Initialization of the global recorder isn't required for macros to function, but any metrics
141//! emitted before a global recorder is installed will not be recorded, so initialization and
142//! installation of an exporter should happen as early as possible in the application lifecycle.
143//!
144//! ### Warning
145//!
146//! The metrics system may only be initialized once.
147//!
148//! For most use cases, you'll be using an off-the-shelf exporter implementation that hooks up to an
149//! existing metrics collection system, or interacts with the existing systems/processes that you use.
150//!
151//! Out of the box, some exporter implementations are available for you to use:
152//!
153//! * [metrics-exporter-tcp] - outputs metrics to clients over TCP
154//! * [metrics-exporter-prometheus] - serves a Prometheus scrape endpoint
155//!
156//! You can also implement your own recorder if a suitable one doesn't already exist.
157//!
158//! # Development
159//!
160//! The primary interface with `metrics` is through the [`Recorder`] trait, which is the connection
161//! between the user-facing emission macros -- `counter!`, and so on -- and the actual logic for
162//! handling those metrics and doing something with them, like logging them to the console or
163//! sending them to a remote metrics system.
164//!
165//! ## Keys
166//!
167//! All metrics are, in essence, the combination of a metric type and metric identifier, such as a
168//! histogram called "response_latency".  You could conceivably have multiple metrics with the same
169//! name, so long as they are of different types.
170//!
171//! As the types are enforced/limited by the [`Recorder`] trait itself, the remaining piece is the
172//! identifier, which we handle by using [`Key`]. Keys hold both the metric name, and potentially,
173//! labels related to the metric. The metric name and labels are always string values.
174//!
175//! Internally, `metrics` uses a clone-on-write "smart pointer" for these values to optimize cases
176//! where the values are static strings, which can provide significant performance benefits.  These
177//! smart pointers can also hold owned `String` values, though, so users can mix and match static
178//! strings and owned strings without issue.
179//!
180//! Two [`Key`] objects can be checked for equality and considered to point to the same metric if
181//! they are equal.  Equality checks both the name of the key and the labels of a key.  Labels are
182//! _not_ sorted prior to checking for equality, but insertion order is maintained, so any [`Key`]
183//! constructed from the same set of labels in the same order should be equal.
184//!
185//! It is an implementation detail if a recorder wishes to do an deeper equality check that ignores
186//! the order of labels, but practically speaking, metric emission, and thus labels, should be
187//! fixed in ordering in nearly all cases, and so it typically is not a problem.
188//!
189//! ## Registration
190//!
191//! Recorders must handle the "registration" of a metric.
192//!
193//! In practice, registration solves two potential problems: providing metadata for a metric, and
194//! creating an entry for a metric even though it has not been emitted yet.
195//!
196//! Callers may wish to provide a human-readable description of what the metric is, or provide the
197//! units the metrics uses.  Additionally, users may wish to register their metrics so that they
198//! show up in the output of the installed exporter even if the metrics have yet to be emitted.
199//! This allows callers to ensure the metrics output is stable, or allows them to expose all of the
200//! potential metrics a system has to offer, again, even if they have not all yet been emitted.
201//!
202//! As you can see from the trait, the registration methods treats the metadata as optional, and
203//! the macros allow users to mix and match whichever fields they want to provide.
204//!
205//! When a metric is registered, the expectation is that it will show up in output with a default
206//! value, so, for example, a counter should be initialized to zero, a histogram would have no
207//! values, and so on.
208//!
209//! ## Emission
210//!
211//! Likewise, recorders must handle the emission of metrics as well.
212//!
213//! Comparatively speaking, emission is not too different from registration: you have access to the
214//! same [`Key`] as well as the value being emitted.
215//!
216//! For recorders which temporarily buffer or hold on to values before exporting, a typical approach
217//! would be to utilize atomic variables for the storage.  For counters and gauges, this can be done
218//! simply by using types like [`AtomicU64`](std::sync::atomic::AtomicU64).  For histograms, this can be
219//! slightly tricky as you must hold on to all of the distinct values.  In our helper crate,
220//! [`metrics-util`][metrics-util], we've provided a type called [`AtomicBucket`][AtomicBucket].  For
221//! exporters that will want to get all of the current values in a batch, while clearing the bucket so
222//! that values aren't processed again, [AtomicBucket] provides a simple interface to do so, as well as
223//! optimized performance on both the insertion and read side.
224//!
225//! Combined together, exporter authors can use [`Handle`][Handle], also from the `metrics-util`
226//! crate, which provides a consolidated type for holding metric data.  These types, and many more
227//! from the `metrics-util` crate, form the basis of typical exporter behavior and have been exposed
228//! to help you quickly build a new exporter.
229//!
230//! ## Installing recorders
231//!
232//! Recorders, also referred to as exporters, must be "installed" such that the emission macros can
233//! access them. As users of `metrics`, you'll typically see exporters provide methods to install
234//! themselves that hide the nitty gritty details.  These methods will usually be aptly named, such
235//! as `install`.
236//!
237//! However, at a low level, this can happen in one of two ways: installing a recorder globally, or
238//! temporarily using it locally.
239//!
240//! ### Global recorder
241//!
242//! The global recorder is the recorder that the macros use by default. It is stored in a static
243//! variable accessible by all portions of the compiled application, including dependencies. This is
244//! what allows us to provide the same "initialize once, benefit everywhere" behavior that users are
245//! familiar with from other telemetry crates like `tracing` and `log`.
246//!
247//! Only one global recorder can be installed in the lifetime of the process. If a global recorder
248//! has already been installed, it cannot be replaced: this is due to the fact that once installed,
249//! the recorder is "leaked" so that a static reference can be obtained to it and used by subsequent
250//! calls to the emission macros, and any downstream crates.
251//!
252//! ### Local recorder
253//!
254//! In many scenarios, such as in unit tests, you may wish to temporarily set a recorder to
255//! influence all calls to the emission macros within a specific section of code, without
256//! influencing other areas of the code, or being limited by the constraints of only one global
257//! recorder being allowed.
258//!
259//! [`with_local_recorder`] allows you to do this by changing the recorder used by the emission macros for
260//! the duration of a given closure. While in that closure, the given recorder will act as if it was
261//! the global recorder for the current thread. Once the closure returns, the true global recorder
262//! takes priority again for the current thread.
263//!
264//! [metrics-exporter-tcp]: https://docs.rs/metrics-exporter-tcp
265//! [metrics-exporter-prometheus]: https://docs.rs/metrics-exporter-prometheus
266//! [metrics-util]: https://docs.rs/metrics-util
267//! [AtomicBucket]: https://docs.rs/metrics-util/0.5.0/metrics_util/struct.AtomicBucket.html
268//! [Handle]: https://docs.rs/metrics-util/0.5.0/metrics_util/enum.Handle.html
269#![deny(missing_docs)]
270#![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))]
271
272pub mod atomics;
273
274mod common;
275mod macros;
276pub use self::common::*;
277
278mod cow;
279
280mod handles;
281pub use self::handles::*;
282
283mod key;
284pub use self::key::*;
285
286mod label;
287pub use self::label::*;
288
289mod metadata;
290pub use self::metadata::*;
291
292mod recorder;
293pub use self::recorder::*;