metrics_runtime/lib.rs
1//! High-speed metrics collection library.
2//!
3//! `metrics-runtime` provides a generalized metrics collection library targeted at users who want
4//! to log metrics at high volume and high speed.
5//!
6//! # Design
7//!
8//! The library follows a pattern of "senders" and a "receiver."
9//!
10//! Callers create a [`Receiver`], which acts as a registry for all metrics that flow through it.
11//! It allows creating new sinks as well as controllers, both necessary to push in and pull out
12//! metrics from the system. It also manages background resources necessary for the registry to
13//! operate.
14//!
15//! Once a [`Receiver`] is created, callers can either create a [`Sink`] for sending metrics, or a
16//! [`Controller`] for getting metrics out.
17//!
18//! A [`Sink`] can be cheaply cloned, and offers convenience methods for getting the current time
19//! as well as getting direct handles to a given metric. This allows users to either work with the
20//! fuller API exposed by [`Sink`] or to take a compositional approach and embed fields that
21//! represent each particular metric to be sent.
22//!
23//! A [`Controller`] provides both a synchronous and asynchronous snapshotting interface, which is
24//! [`metrics-core`][metrics_core] compatible for exporting. This allows flexibility in
25//! integration amongst traditional single-threaded or hand-rolled multi-threaded applications and
26//! the emerging asynchronous Rust ecosystem.
27//!
28//! # Performance
29//!
30//! Users can expect to be able to send tens of millions of samples per second, with ingest
31//! latencies at roughly 65-70ns at p50, and 250ns at p99. Depending on the workload -- counters
32//! vs histograms -- latencies may be even lower, as counters and gauges are markedly faster to
33//! update than histograms. Concurrent updates of the same metric will also cause natural
34//! contention and lower the throughput/increase the latency of ingestion.
35//!
36//! # Metrics
37//!
38//! Counters, gauges, and histograms are supported, and follow the definitions outlined in
39//! [`metrics-core`][metrics_core].
40//!
41//! Here's a simple example of creating a receiver and working with a sink:
42//!
43//! ```rust
44//! # extern crate metrics_runtime;
45//! use metrics_runtime::Receiver;
46//! use std::{thread, time::Duration};
47//! let receiver = Receiver::builder().build().expect("failed to create receiver");
48//! let mut sink = receiver.sink();
49//!
50//! // We can update a counter. Counters are monotonic, unsigned integers that start at 0 and
51//! // increase over time.
52//! sink.increment_counter("widgets", 5);
53//!
54//! // We can update a gauge. Gauges are signed, and hold on to the last value they were updated
55//! // to, so you need to track the overall value on your own.
56//! sink.update_gauge("red_balloons", 99);
57//!
58//! // We can update a timing histogram. For timing, we're using the built-in `Sink::now` method
59//! // which utilizes a high-speed internal clock. This method returns the time in nanoseconds, so
60//! // we get great resolution, but giving the time in nanoseconds isn't required! If you want to
61//! // send it in another unit, that's fine, but just pay attention to that fact when viewing and
62//! // using those metrics once exported. We also support passing `Instant` values -- both `start`
63//! // and `end` need to be the same type, though! -- and we'll take the nanosecond output of that.
64//! let start = sink.now();
65//! thread::sleep(Duration::from_millis(10));
66//! let end = sink.now();
67//! sink.record_timing("db.queries.select_products_ns", start, end);
68//!
69//! // Finally, we can update a value histogram. Technically speaking, value histograms aren't
70//! // fundamentally different from timing histograms. If you use a timing histogram, we do the
71//! // math for you of getting the time difference, but other than that, identical under the hood.
72//! let row_count = 46;
73//! sink.record_value("db.queries.select_products_num_rows", row_count);
74//! ```
75//!
76//! # Scopes
77//!
78//! Metrics can be scoped, not unlike loggers, at the [`Sink`] level. This allows sinks to easily
79//! nest themselves without callers ever needing to care about where they're located.
80//!
81//! This feature is a simpler approach to tagging: while not as semantically rich, it provides the
82//! level of detail necessary to distinguish a single metric between multiple callsites.
83//!
84//! For example, after getting a [`Sink`] from the [`Receiver`], we can easily nest ourselves under
85//! the root scope and then send some metrics:
86//!
87//! ```rust
88//! # extern crate metrics_runtime;
89//! # use metrics_runtime::Receiver;
90//! # let receiver = Receiver::builder().build().expect("failed to create receiver");
91//! // This sink has no scope aka the root scope. The metric will just end up as "widgets".
92//! let mut root_sink = receiver.sink();
93//! root_sink.increment_counter("widgets", 42);
94//!
95//! // This sink is under the "secret" scope. Since we derived ourselves from the root scope,
96//! // we're not nested under anything, but our metric name will end up being "secret.widgets".
97//! let mut scoped_sink = root_sink.scoped("secret");
98//! scoped_sink.increment_counter("widgets", 42);
99//!
100//! // This sink is under the "supersecret" scope, but we're also nested! The metric name for this
101//! // sample will end up being "secret.supersecret.widget".
102//! let mut scoped_sink_two = scoped_sink.scoped("supersecret");
103//! scoped_sink_two.increment_counter("widgets", 42);
104//!
105//! // Sinks retain their scope even when cloned, so the metric name will be the same as above.
106//! let mut cloned_sink = scoped_sink_two.clone();
107//! cloned_sink.increment_counter("widgets", 42);
108//!
109//! // This sink will be nested two levels deeper than its parent by using a slightly different
110//! // input scope: scope can be a single string, or multiple strings, which is interpreted as
111//! // nesting N levels deep.
112//! //
113//! // This metric name will end up being "super.secret.ultra.special.widgets".
114//! let mut scoped_sink_three = scoped_sink.scoped(&["super", "secret", "ultra", "special"]);
115//! scoped_sink_two.increment_counter("widgets", 42);
116//! ```
117//!
118//! # Labels
119//!
120//! On top of scope support, metrics can also have labels. If scopes are for organizing metrics in
121//! a hierarchy, then labels are for differentiating the same metric being emitted from multiple
122//! sources.
123//!
124//! This is most easily demonstrated with an example:
125//!
126//! ```rust
127//! # extern crate metrics_runtime;
128//! # fn run_query(_: &str) -> u64 { 42 }
129//! # use metrics_runtime::Receiver;
130//! # let receiver = Receiver::builder().build().expect("failed to create receiver");
131//! # let mut sink = receiver.sink();
132//! // We might have a function that interacts with a database and returns the number of rows it
133//! // touched in doing so.
134//! fn process_query(query: &str) -> u64 {
135//! run_query(query)
136//! }
137//!
138//! // We might call this function multiple times, but hitting different tables.
139//! let rows_a = process_query("UPDATE posts SET public = 1 WHERE public = 0");
140//! let rows_b = process_query("UPDATE comments SET public = 1 WHERE public = 0");
141//!
142//! // Now, we want to track a metric that shows how many rows are updated overall, so the metric
143//! // name should be the same no matter which table we update, but we'd also like to be able to
144//! // differentiate by table, too!
145//! sink.record_value_with_labels("db.rows_updated", rows_a, &[("table", "posts")]);
146//! sink.record_value_with_labels("db.rows_updated", rows_b, &[("table", "comments")]);
147//!
148//! // If you want to send a specific set of labels with every metric from this sink, you can also
149//! // add default labels. This action is additive, so you can call it multiple times to build up
150//! // the set of labels sent with metrics, and labels are inherited when creating a scoped sink or
151//! // cloning an existing sink, which allows label usage to either supplement scopes or to
152//! // potentially replace them entirely.
153//! sink.add_default_labels(&[("database", "primary")]);
154//! # fn main() {}
155//! ```
156//!
157//! As shown in the example, labels allow a user to submit values to the underlying metric name,
158//! while also differentiating between unique situations, whatever the facet that the user decides
159//! to utilize.
160//!
161//! Naturally, these methods can be slightly cumbersome and visually detracting, in which case
162//! you can utilize the metric handles -- [`Counter`](crate::data::Counter),
163//! [`Gauge`](crate::data::Gauge), and [`Histogram`](crate::data::Histogram) -- and create them
164//! with labels ahead of time.
165//!
166//! These handles are bound to the given metric type, as well as the name, labels, and scope of the
167//! sink. Thus, there is no overhead of looking up the metric as with the `record_*` methods, and
168//! the values can be updated directly, and with less overhead, resulting in faster method calls.
169//!
170//! ```rust
171//! # extern crate metrics_runtime;
172//! # use metrics_runtime::Receiver;
173//! # use std::time::Instant;
174//! # let receiver = Receiver::builder().build().expect("failed to create receiver");
175//! # let mut sink = receiver.sink();
176//! // Let's create a counter.
177//! let egg_count = sink.counter("eggs");
178//!
179//! // I want a baker's dozen of eggs!
180//! egg_count.increment();
181//! egg_count.record(12);
182//!
183//! // This updates the same metric as above! We have so many eggs now!
184//! sink.increment_counter("eggs", 12);
185//!
186//! // Gauges and histograms don't have any extra helper methods, just `record`:
187//! let gauge = sink.gauge("population");
188//! gauge.record(8_000_000_000);
189//!
190//! let histogram = sink.histogram("distribution");
191//!
192//! // You can record a histogram value directly:
193//! histogram.record_value(42);
194//!
195//! // Or handily pass it two [`Delta`]-compatible values, and have it calculate the delta for you:
196//! let start = Instant::now();
197//! let end = Instant::now();
198//! histogram.record_timing(start, end);
199//!
200//! // Each of these methods also has a labels-aware companion:
201//! let labeled_counter = sink.counter_with_labels("egg_count", &[("type", "large_brown")]);
202//! let labeled_gauge = sink.gauge_with_labels("population", &[("country", "austria")]);
203//! let labeled_histogram = sink.histogram_with_labels("distribution", &[("type", "performance")]);
204//! # fn main() {}
205//! ```
206//!
207//! # Proxies
208//!
209//! Sometimes, you may have a need to pull in "external" metrics: values related to your
210//! application that your application itself doesn't generate, such as system-level metrics.
211//!
212//! [`Sink`] allows you to register a "proxy metric", which gives the ability to return metrics
213//! on-demand when a snapshot is being taken. Users provide a closure that is run every time a
214//! snapshot is being taken, which can return multiple metrics, which are then added to overall
215//! list of metrics being held by `metrics-runtime` itself.
216//!
217//! If metrics are relatively expensive to calculate -- say, accessing the /proc filesytem on Linux
218//! -- then this can be a great alternative to polling them yourself and having to update them
219//! normally on some sort of schedule.
220//!
221//! ```rust
222//! # extern crate metrics_runtime;
223//! # extern crate metrics_core;
224//! # use metrics_core::Key;
225//! # use metrics_runtime::{Receiver, Measurement};
226//! # use std::time::Instant;
227//! # let receiver = Receiver::builder().build().expect("failed to create receiver");
228//! # let mut sink = receiver.sink();
229//! // A proxy is now registered under the name "load_stats", which is prepended to all the metrics
230//! // generated by the closure i.e. "load_stats.avg_1min". These metrics are also still scoped
231//! // normally based on the [`Sink`].
232//! sink.proxy("load_stat", || {
233//! let mut values = Vec::new();
234//! values.push((Key::from_name("avg_1min"), Measurement::Gauge(19)));
235//! values.push((Key::from_name("avg_5min"), Measurement::Gauge(12)));
236//! values.push((Key::from_name("avg_10min"), Measurement::Gauge(10)));
237//! values
238//! });
239//! # fn main() { }
240//! ```
241//!
242//! # Snapshots
243//!
244//! Naturally, we need a way to get the metrics out of the system, which is where snapshots come
245//! into play. By utilizing a [`Controller`], we can take a snapshot of the current metrics in the
246//! registry, and then output them to any desired system/interface by utilizing
247//! [`Observer`](metrics_core::Observer). A number of pre-baked observers (which only concern
248//! themselves with formatting the data) and exporters (which take the formatted data and either
249//! serve it up, such as exposing an HTTP endpoint, or write it somewhere, like stdout) are
250//! available, some of which are exposed by this crate.
251//!
252//! Let's take an example of writing out our metrics in a yaml-like format, writing them via
253//! `log!`:
254//! ```rust
255//! # extern crate metrics_runtime;
256//! use metrics_runtime::{
257//! Receiver, observers::YamlBuilder, exporters::LogExporter,
258//! };
259//! use log::Level;
260//! use std::{thread, time::Duration};
261//! let receiver = Receiver::builder().build().expect("failed to create receiver");
262//! let mut sink = receiver.sink();
263//!
264//! // We can update a counter. Counters are monotonic, unsigned integers that start at 0 and
265//! // increase over time.
266//! // Take some measurements, similar to what we had in other examples:
267//! sink.increment_counter("widgets", 5);
268//! sink.update_gauge("red_balloons", 99);
269//!
270//! let start = sink.now();
271//! thread::sleep(Duration::from_millis(10));
272//! let end = sink.now();
273//! sink.record_timing("db.queries.select_products_ns", start, end);
274//! sink.record_timing("db.gizmo_query", start, end);
275//!
276//! let num_rows = 46;
277//! sink.record_value("db.queries.select_products_num_rows", num_rows);
278//!
279//! // Now create our exporter/observer configuration, and wire it up.
280//! let exporter = LogExporter::new(
281//! receiver.controller(),
282//! YamlBuilder::new(),
283//! Level::Info,
284//! Duration::from_secs(5),
285//! );
286//!
287//! // This exporter will now run every 5 seconds, taking a snapshot, rendering it, and writing it
288//! // via `log!` at the informational level. This particular exporter is running directly on the
289//! // current thread, and not on a background thread.
290//! //
291//! // exporter.run();
292//! ```
293//! Most exporters have the ability to run on the current thread or to be converted into a future
294//! which can be spawned on any Tokio-compatible runtime.
295//!
296//! # Facade
297//!
298//! `metrics-runtime` is `metrics` compatible, and can be installed as the global metrics facade:
299//! ```
300//! # #[macro_use] extern crate metrics;
301//! extern crate metrics_runtime;
302//! use metrics_runtime::Receiver;
303//!
304//! Receiver::builder()
305//! .build()
306//! .expect("failed to create receiver")
307//! .install();
308//!
309//! counter!("items_processed", 42);
310//! ```
311//!
312//! [metrics_core]: https://docs.rs/metrics-core
313//! [`Observer`]: https://docs.rs/metrics-core/0.3.1/metrics_core/trait.Observer.html
314#![deny(missing_docs)]
315#![warn(unused_extern_crates)]
316mod builder;
317mod common;
318mod config;
319mod control;
320pub mod data;
321mod helper;
322mod receiver;
323mod registry;
324mod sink;
325
326#[cfg(any(feature = "metrics-exporter-log", feature = "metrics-exporter-http"))]
327pub mod exporters;
328
329#[cfg(any(
330 feature = "metrics-observer-yaml",
331 feature = "metrics-observer-json",
332 feature = "metrics-observer-prometheus"
333))]
334pub mod observers;
335
336pub use self::{
337 builder::{Builder, BuilderError},
338 common::{Delta, Measurement, Scope},
339 control::Controller,
340 receiver::Receiver,
341 sink::{AsScoped, Sink, SinkError},
342};