metrics/metrics/src/lib.rs

477 lines
19 KiB
Rust

//! A lightweight metrics facade.
//!
//! The `metrics` crate provides a single metrics API that abstracts over the actual metrics
//! implementation. Libraries can use the metrics API provided by this crate, and the consumer of
//! those libraries can choose the metrics implementation that is most suitable for its use case.
//!
//! # Overview
//! `metrics` exposes two main concepts: emitting a metric, and recording it.
//!
//! ## Emission
//! Metrics are emitted by utilizing the registration or emission macros. There is a macro for
//! registering and emitting each fundamental metric type:
//! - [`register_counter!`], [`increment!`], and [`counter!`] for counters
//! - [`register_gauge!`] and [`gauge!`] for gauges
//! - [`register_histogram!`] and [`histogram!`] for histograms
//!
//! In order to register or emit a metric, you need a way to record these events, which is where
//! [`Recorder`] comes into play.
//!
//! ## Recording
//! The [`Recorder`] trait defines the interface between the registration/emission macros, and
//! exporters, which is how we refer to concrete implementations of [`Recorder`]. The trait defines
//! what the exporters are doing -- recording -- but ultimately exporters are sending data from your
//! application to somewhere else: whether it be a third-party service or logging via standard out.
//! It's "exporting" the metric data somewhere else besides your application.
//!
//! Each metric type is usually reserved for a specific type of use case, whether it be tracking a
//! single value or allowing the summation of multiple values, and the respective macros elaborate
//! more on the usage and invariants provided by each.
//!
//! # Getting Started
//!
//! ## In libraries
//! Libraries need only include the `metrics` crate to emit metrics. When an executable installs a
//! recorder, all included crates which emitting metrics will now emit their metrics to that record,
//! which allows library authors to seamless emit their own metrics without knowing or caring which
//! exporter implementation is chosen, or even if one is installed.
//!
//! In cases where no global recorder is installed, a "noop" recorder lives in its place, which has
//! an incredibly very low overhead: an atomic load and comparison. Libraries can safely instrument
//! their code without fear of ruining baseline performance.
//!
//! ### Examples
//!
//! ```rust
//! use metrics::{histogram, counter};
//!
//! # use std::time::Instant;
//! # pub fn run_query(_: &str) -> u64 { 42 }
//! pub fn process(query: &str) -> u64 {
//! let start = Instant::now();
//! let row_count = run_query(query);
//! let delta = Instant::now() - start;
//!
//! histogram!("process.query_time", delta);
//! counter!("process.query_row_count", row_count);
//!
//! row_count
//! }
//! # fn main() {}
//! ```
//!
//! ## In executables
//!
//! Executables, which themselves can emit their own metrics, are intended to install a global
//! recorder so that metrics can actually be recorded and exported somewhere.
//!
//! Initialization of the global recorder isn't required for macros to function, but any metrics
//! emitted before a global recorder is installed will not be recorded, so early initialization is
//! recommended when possible.
//!
//! ### Warning
//!
//! The metrics system may only be initialized once.
//!
//! For most use cases, you'll be using an off-the-shelf exporter implementation that hooks up to an
//! existing metrics collection system, or interacts with the existing systems/processes that you use.
//!
//! Out of the box, some exporter implementations are available for you to use:
//!
//! * [metrics-exporter-tcp] - outputs metrics to clients over TCP
//! * [metrics-exporter-prometheus] - serves a Prometheus scrape endpoint
//!
//! You can also implement your own recorder if a suitable one doesn't already exist.
//!
//! # Development
//!
//! The primary interface with `metrics` is through the [`Recorder`] trait, so we'll show examples
//! below of the trait and implementation notes.
//!
//! ## Implementing and installing a basic recorder
//!
//! Here's a basic example which writes metrics in text form via the `log` crate.
//!
//! ```rust
//! use log::info;
//! use metrics::{Key, Recorder, Unit};
//! use metrics::SetRecorderError;
//!
//! struct LogRecorder;
//!
//! impl Recorder for LogRecorder {
//! fn register_counter(&self, key: Key, _unit: Option<Unit>, _description: Option<&'static str>) {}
//!
//! fn register_gauge(&self, key: Key, _unit: Option<Unit>, _description: Option<&'static str>) {}
//!
//! fn register_histogram(&self, key: Key, _unit: Option<Unit>, _description: Option<&'static str>) {}
//!
//! fn increment_counter(&self, key: Key, value: u64) {
//! info!("counter '{}' -> {}", key, value);
//! }
//!
//! fn update_gauge(&self, key: Key, value: f64) {
//! info!("gauge '{}' -> {}", key, value);
//! }
//!
//! fn record_histogram(&self, key: Key, value: u64) {
//! info!("histogram '{}' -> {}", key, value);
//! }
//! }
//!
//! // Recorders are installed by calling the [`set_recorder`] function. Recorders should provide a
//! // function that wraps the creation and installation of the recorder:
//!
//! static RECORDER: LogRecorder = LogRecorder;
//!
//! pub fn init() -> Result<(), SetRecorderError> {
//! metrics::set_recorder(&RECORDER)
//! }
//! # fn main() {}
//! ```
//! ## Keys
//!
//! All metrics are, in essence, the combination of a metric type and metric identifier, such as a
//! histogram called "response_latency". You could conceivably have multiple metrics with the same
//! name, so long as they are of different types.
//!
//! As the types are enforced/limited by the [`Recorder`] trait itself, the remaining piece is the
//! identifier, which we handle by using [`Key`].
//!
//! [`Key`] itself is a wrapper for [`KeyData`], which holds not only the name of a metric, but
//! potentially holds labels for it as well. The name of a metric must always be a literal string.
//! The labels are a key/value pair, where both components are strings as well.
//!
//! Internally, `metrics` uses a clone-on-write "smart pointer" for these values to optimize cases
//! where the values are static strings, which can provide significant performance benefits. These
//! smart pointers can also hold owned `String` values, though, so users can mix and match static
//! strings and owned strings for labels without issue. Metric names, as mentioned above, are always
//! static strings.
//!
//! Two [`Key`] objects can be checked for equality and considered to point to the same metric if
//! they are equal. Equality checks both the name of the key and the labels of a key. Labels are
//! _not_ sorted prior to checking for equality, but insertion order is maintained, so any [`Key`]
//! constructed from the same set of labels in the same order should be equal.
//!
//! It is an implementation detail if a recorder wishes to do an deeper equality check that ignores
//! the order of labels, but practically speaking, metric emission, and thus labels, should be
//! fixed in ordering in nearly all cases, and so it isn't typically a problem.
//!
//! ## Registration
//!
//! Recorders must handle the "registration" of a metric.
//!
//! In practice, registration solves two potential problems: providing metadata for a metric, and
//! creating an entry for a metric even though it has not been emitted yet.
//!
//! Callers may wish to provide a human-readable description of what the metric is, or provide the
//! units the metrics uses. Additionally, users may wish to register their metrics so that they
//! show up in the output of the installed exporter even if the metrics have yet to be emitted.
//! This allows callers to ensure the metrics output is stable, or allows them to expose all of the
//! potential metrics a system has to offer, again, even if they have not all yet been emitted.
//!
//! As you can see from the trait, the registration methods treats the metadata as optional, and
//! the macros allow users to mix and match whichever fields they want to provide.
//!
//! When a metric is registered, the expectation is that it will show up in output with a default
//! value, so, for example, a counter should be initialized to zero, a histogram would have no
//! values, and so on.
//!
//! ## Emission
//!
//! Likewise, records must handle the emission of metrics as well.
//!
//! Comparatively speaking, emission is not too different from registration: you have access to the
//! same [`Key`] as well as the value being emitted.
//!
//! For recorders which temporarily buffer or hold on to values before exporting, a typical approach
//! would be to utilize atomic variables for the storage. For counters and gauges, this can be done
//! simply by using types like [`AtomicU64`](std::sync::atomic::AtomicU64). For histograms, this can be
//! slightly tricky as you must hold on to all of the distinct values. In our helper crate,
//! [`metrics-util`][metrics-util], we've provided a type called [`AtomicBucket`][AtomicBucket]. For
//! exporters that will want to get all of the current values in a batch, while clearing the bucket so
//! that values aren't processed again, [AtomicBucket] provides a simple interface to do so, as well as
//! optimized performance on both the insertion and read side.
//!
//! ## Installing recorders
//!
//! In order to actually use an exporter, it must be installed as the "global" recorder. This is a
//! static recorder that the registration and emission macros refer to behind-the-scenes. `metrics`
//! provides a few methods to do so: [`set_recorder`], [`set_boxed_recorder`], and [`set_recorder_racy`].
//!
//! Primarily, you'll use [`set_boxed_recorder`] to pass a boxed version of the exporter to be
//! installed. This is due to the fact that most exporters won't be able to be constructed
//! statically. If you could construct your exporter statically, though, then you could instead
//! choose [`set_recorder`].
//!
//! Similarly, [`set_recorder_racy`] takes a static reference, but is also not thread safe, and
//! should only be used on platforms which do not support atomic operations, such as embedded
//! environments.
//!
//! [metrics-exporter-tcp]: https://docs.rs/metrics-exporter-tcp
//! [metrics-exporter-prometheus]: https://docs.rs/metrics-exporter-prometheus
//! [metrics-util]: https://docs.rs/metrics-util
//! [AtomicBucket]: https://docs.rs/metrics-util/0.4.0-alpha.6/metrics_util/struct.AtomicBucket.html
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg), deny(broken_intra_doc_links))]
extern crate alloc;
use proc_macro_hack::proc_macro_hack;
mod common;
pub use self::common::*;
mod cow;
mod key;
pub use self::key::*;
mod label;
pub use self::label::*;
mod recorder;
pub use self::recorder::*;
/// Registers a counter.
///
/// Counters represent a single monotonic value, which means the value can only be incremented, not
/// decremented, and always starts out with an initial value of zero.
///
/// Metrics can be registered with an optional unit and description. Whether or not the installed
/// recorder does anything with the description is implementation defined. Labels can also be
/// specified when registering a metric.
///
/// # Example
/// ```
/// # use metrics::register_counter;
/// # use metrics::Unit;
/// # fn main() {
/// // A basic counter:
/// register_counter!("some_metric_name");
///
/// // Providing a unit for a counter:
/// register_counter!("some_metric_name", Unit::Bytes);
///
/// // Providing a description for a counter:
/// register_counter!("some_metric_name", "total number of bytes");
///
/// // Specifying labels:
/// register_counter!("some_metric_name", "service" => "http");
///
/// // We can combine the units, description, and labels arbitrarily:
/// register_counter!("some_metric_name", Unit::Bytes, "total number of bytes");
/// register_counter!("some_metric_name", Unit::Bytes, "service" => "http");
/// register_counter!("some_metric_name", "total number of bytes", "service" => "http");
///
/// // And all combined:
/// register_counter!("some_metric_name", Unit::Bytes, "number of woopsy daisies", "service" => "http");
///
/// /// We can also pass labels by giving a vector or slice of key/value pairs. In this scenario,
/// // a unit or description can still be passed in their respective positions:
/// let dynamic_val = "woo";
/// let labels = [("dynamic_key", format!("{}!", dynamic_val))];
/// register_counter!("some_metric_name", &labels);
/// # }
/// ```
#[proc_macro_hack]
pub use metrics_macros::register_counter;
/// Registers a gauge.
///
/// Gauges represent a single value that can go up or down over time, and always starts out with an
/// initial value of zero.
///
/// Metrics can be registered with an optional unit and description. Whether or not the installed
/// recorder does anything with the description is implementation defined. Labels can also be
/// specified when registering a metric.
///
/// # Example
/// ```
/// # use metrics::register_gauge;
/// # use metrics::Unit;
/// # fn main() {
/// // A basic gauge:
/// register_gauge!("some_metric_name");
///
/// // Providing a unit for a gauge:
/// register_gauge!("some_metric_name", Unit::Bytes);
///
/// // Providing a description for a gauge:
/// register_gauge!("some_metric_name", "total number of bytes");
///
/// // Specifying labels:
/// register_gauge!("some_metric_name", "service" => "http");
///
/// // We can combine the units, description, and labels arbitrarily:
/// register_gauge!("some_metric_name", Unit::Bytes, "total number of bytes");
/// register_gauge!("some_metric_name", Unit::Bytes, "service" => "http");
/// register_gauge!("some_metric_name", "total number of bytes", "service" => "http");
///
/// // And all combined:
/// register_gauge!("some_metric_name", Unit::Bytes, "total number of bytes", "service" => "http");
///
/// // We can also pass labels by giving a vector or slice of key/value pairs. In this scenario,
/// // a unit or description can still be passed in their respective positions:
/// let dynamic_val = "woo";
/// let labels = [("dynamic_key", format!("{}!", dynamic_val))];
/// register_gauge!("some_metric_name", &labels);
/// # }
/// ```
#[proc_macro_hack]
pub use metrics_macros::register_gauge;
/// Records a histogram.
///
/// Histograms measure the distribution of values for a given set of measurements, and start with no
/// initial values.
///
/// Metrics can be registered with an optional unit and description. Whether or not the installed
/// recorder does anything with the description is implementation defined. Labels can also be
/// specified when registering a metric.
///
/// # Example
/// ```
/// # use metrics::register_histogram;
/// # use metrics::Unit;
/// # fn main() {
/// // A basic histogram:
/// register_histogram!("some_metric_name");
///
/// // Providing a unit for a histogram:
/// register_histogram!("some_metric_name", Unit::Nanoseconds);
///
/// // Providing a description for a histogram:
/// register_histogram!("some_metric_name", "request handler duration");
///
/// // Specifying labels:
/// register_histogram!("some_metric_name", "service" => "http");
///
/// // We can combine the units, description, and labels arbitrarily:
/// register_histogram!("some_metric_name", Unit::Nanoseconds, "request handler duration");
/// register_histogram!("some_metric_name", Unit::Nanoseconds, "service" => "http");
/// register_histogram!("some_metric_name", "request handler duration", "service" => "http");
///
/// // And all combined:
/// register_histogram!("some_metric_name", Unit::Nanoseconds, "request handler duration", "service" => "http");
///
/// // We can also pass labels by giving a vector or slice of key/value pairs. In this scenario,
/// // a unit or description can still be passed in their respective positions:
/// let dynamic_val = "woo";
/// let labels = [("dynamic_key", format!("{}!", dynamic_val))];
/// register_histogram!("some_metric_name", &labels);
/// # }
/// ```
#[proc_macro_hack]
pub use metrics_macros::register_histogram;
/// Increments a counter by one.
///
/// Counters represent a single monotonic value, which means the value can only be incremented, not
/// decremented, and always starts out with an initial value of zero.
///
/// # Example
/// ```
/// # use metrics::increment;
/// # fn main() {
/// // A basic increment:
/// increment!("some_metric_name");
///
/// // Specifying labels:
/// increment!("some_metric_name", "service" => "http");
///
/// // We can also pass labels by giving a vector or slice of key/value pairs:
/// let dynamic_val = "woo";
/// let labels = [("dynamic_key", format!("{}!", dynamic_val))];
/// increment!("some_metric_name", &labels);
/// # }
/// ```
#[proc_macro_hack]
pub use metrics_macros::increment;
/// Increments a counter.
///
/// Counters represent a single monotonic value, which means the value can only be incremented, not
/// decremented, and always starts out with an initial value of zero.
///
/// # Example
/// ```
/// # use metrics::counter;
/// # fn main() {
/// // A basic counter:
/// counter!("some_metric_name", 12);
///
/// // Specifying labels:
/// counter!("some_metric_name", 12, "service" => "http");
///
/// // We can also pass labels by giving a vector or slice of key/value pairs:
/// let dynamic_val = "woo";
/// let labels = [("dynamic_key", format!("{}!", dynamic_val))];
/// counter!("some_metric_name", 12, &labels);
/// # }
/// ```
#[proc_macro_hack]
pub use metrics_macros::counter;
/// Updates a gauge.
///
/// Gauges represent a single value that can go up or down over time, and always starts out with an
/// initial value of zero.
///
/// # Example
/// ```
/// # use metrics::gauge;
/// # fn main() {
/// // A basic gauge:
/// gauge!("some_metric_name", 42.2222);
///
/// // Specifying labels:
/// gauge!("some_metric_name", 66.6666, "service" => "http");
///
/// // We can also pass labels by giving a vector or slice of key/value pairs:
/// let dynamic_val = "woo";
/// let labels = [("dynamic_key", format!("{}!", dynamic_val))];
/// gauge!("some_metric_name", 42.42, &labels);
/// # }
/// ```
#[proc_macro_hack]
pub use metrics_macros::gauge;
/// Records a histogram.
///
/// Histograms measure the distribution of values for a given set of measurements, and start with no
/// initial values.
///
/// # Implicit conversions
/// Histograms are represented as `u64` values, but often come from another source, such as a time
/// measurement. By default, `histogram!` will accept a `u64` directly or a
/// [`Duration`](std::time::Duration), which uses the nanoseconds total as the converted value.
///
/// External libraries and applications can create their own conversions by implementing the
/// [`IntoU64`] trait for their types, which is required for the value being passed to `histogram!`.
///
/// # Example
/// ```
/// # use metrics::histogram;
/// # use std::time::Duration;
/// # fn main() {
/// // A basic histogram:
/// histogram!("some_metric_name", 34);
///
/// // An implicit conversion from `Duration`:
/// let d = Duration::from_millis(17);
/// histogram!("some_metric_name", d);
///
/// // Specifying labels:
/// histogram!("some_metric_name", 38, "service" => "http");
///
/// // We can also pass labels by giving a vector or slice of key/value pairs:
/// let dynamic_val = "woo";
/// let labels = [("dynamic_key", format!("{}!", dynamic_val))];
/// histogram!("some_metric_name", 1337, &labels);
/// # }
/// ```
#[proc_macro_hack]
pub use metrics_macros::histogram;