hotmic/lib.rs
1//! High-speed metrics collection library.
2//!
3//! hotmic provides a generalized metrics collection library targeted at users who want to log
4//! 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 contained unit: metric registration,
11//! aggregation, and summarization. The [`Receiver`] is intended to be spawned onto a dedicated
12//! background thread.
13//!
14//! From a [`Receiver`], callers can create a [`Sink`], which allows registering facets -- or
15//! interests -- in a given metric, along with sending the metrics themselves. All metrics need to
16//! be pre-registered, in essence, with the receiver, which allows us to know which aspects of a
17//! metric to track: count, value, or percentile.
18//!
19//! A [`Sink`] can be cheaply cloned and does not require a mutable reference to send metrics, and
20//! so callers have great flexibility in being able to control their resource consumption when it
21//! comes to sinks. [`Receiver`] also allows configuring the capacity of the underlying channels to
22//! finely tune resource consumption.
23//!
24//! Being based on [`crossbeam-channel`] allows us to process close to fifteen million metrics per
25//! second on a single core, with very low ingest latencies: 100ns on average at full throughput.
26//!
27//! # Metrics
28//!
29//! hotmic supports counters, gauges, and histograms.
30//!
31//! A counter is a single value that can be updated with deltas to increase or decrease the value.
32//! This would be your typical "messages sent" or "database queries executed" style of metric,
33//! where the value changes over time.
34//!
35//! A gauge is also a single value but does not support delta updates. When a gauge is set, the
36//! value sent becomes _the_ value of the gauge. Gauges can be useful for metrics that measure a
37//! point-in-time value, such as "connected clients" or "running queries". While those metrics
38//! could also be represented by a count, gauges can be simpler in cases where you're already
39//! computing and storing the value, and simply want to expose it in your metrics.
40//!
41//! A histogram tracks the distribution of values: how many values were between 0-5, between 6-10,
42//! etc. This is the canonical way to measure latency: the time spent running a piece of code or
43//! servicing an operation. By keeping track of the individual measurements, we can better see how
44//! many are slow, fast, average, and in what proportions.
45//!
46//! ```
47//! # extern crate hotmic;
48//! use hotmic::Receiver;
49//! use std::{thread, time::Duration};
50//! let receiver = Receiver::builder().build();
51//! let sink = receiver.get_sink();
52//!
53//! // We can update a counter. Counters are signed, and can be updated either with a delta, or
54//! // can be incremented and decremented with the [`Sink::increment`] and [`Sink::decrement`].
55//! sink.update_count("widgets", 5);
56//! sink.update_count("widgets", -3);
57//! sink.increment("widgets");
58//! sink.decrement("widgets");
59//!
60//! // We can update a gauge. Gauges are unsigned, and hold on to the last value they were updated
61//! // to, so you need to track the overall value on your own.
62//! sink.update_gauge("red_balloons", 99);
63//!
64//! // We can update a timing histogram. For timing, you also must measure the start and end
65//! // time using the built-in `Clock` exposed by the sink. The receiver internally converts the
66//! // raw values to calculate the actual wall clock time (in nanoseconds) on your behalf, so you
67//! // can't just pass in any old number.. otherwise you'll get erroneous measurements!
68//! let start = sink.clock().start();
69//! thread::sleep(Duration::from_millis(10));
70//! let end = sink.clock().end();
71//! let rows = 42;
72//!
73//! // This would just set the timing:
74//! sink.update_timing("db.gizmo_query", start, end);
75//!
76//! // This would set the timing and also let you provide a customized count value. Being able to
77//! // specify a count is handy when tracking things like the time it took to execute a database
78//! // query, along with how many rows that query returned:
79//! sink.update_timing_with_count("db.gizmo_query", start, end, rows);
80//!
81//! // Finally, we can update a value histogram. Technically speaking, value histograms aren't
82//! // fundamentally different from timing histograms. If you use a timing histogram, we do the
83//! // math for you of getting the time difference, and we make sure the metric name has the right
84//! // unit suffix so you can tell it's measuring time, but other than that, nearly identical!
85//! let buf_size = 4096;
86//! sink.update_value("buf_size", buf_size);
87//! ```
88//!
89//! # Scopes
90//!
91//! Metrics can be scoped, not unlike loggers, at the [`Sink`] level. This allows sinks to easily
92//! nest themselves without callers ever needing to care about where they're located.
93//!
94//! This feature is a simpler approach to tagging: while not as semantically rich, it provides the
95//! level of detail necessary to distinguish a single metric between multiple callsites.
96//!
97//! An important thing to note is: registered metrics are only good for the scope they were
98//! registered at. If you create a scoped [`Sink`], you must register, or reregister, the metrics
99//! you will be sending to it.
100//!
101//! For example, after getting a [`Sink`] from the [`Receiver`], we can easily nest ourselves under
102//! the root scope and then send some metrics:
103//!
104//! ```
105//! # extern crate hotmic;
106//! use hotmic::Receiver;
107//! let receiver = Receiver::builder().build();
108//!
109//! // This sink has no scope aka the root scope. The metric will just end up as "widgets".
110//! let root_sink = receiver.get_sink();
111//! root_sink.update_count("widgets", 42);
112//!
113//! // This sink is under the "secret" scope. Since we derived ourselves from the root scope,
114//! // we're not nested under anything, but our metric name will end up being "secret.widgets".
115//! let scoped_sink = root_sink.scoped("secret");
116//! scoped_sink.update_count("widgets", 42);
117//!
118//! // This sink is under the "supersecret" scope, but we're also nested! The metric name for this
119//! // sample will end up being "secret.supersecret.widget".
120//! let scoped_sink_two = scoped_sink.scoped("supersecret");
121//! scoped_sink_two.update_count("widgets", 42);
122//!
123//! // Sinks retain their scope even when cloned, so the metric name will be the same as above.
124//! let cloned_sink = scoped_sink_two.clone();
125//! cloned_sink.update_count("widgets", 42);
126//!
127//! // This sink will be nested two levels deeper than its parent by using a slightly different
128//! // input scope: scope can be a single string, or multiple strings, which is interpreted as
129//! // nesting N levels deep.
130//! //
131//! // This metric name will end up being "super.secret.ultra.special.widgets".
132//! let scoped_sink_three = scoped_sink.scoped(&["super", "secret", "ultra", "special"]);
133//! scoped_sink_two.update_count("widgets", 42);
134//! ```
135#[macro_use]
136extern crate derivative;
137
138mod configuration;
139mod control;
140mod data;
141mod helper;
142mod receiver;
143mod scopes;
144mod sink;
145
146pub use self::{
147 configuration::Configuration,
148 control::{Controller, SnapshotError},
149 data::Percentile,
150 receiver::Receiver,
151 sink::{Sink, SinkError},
152};
153
154pub mod snapshot {
155 pub use super::data::snapshot::{SimpleSnapshot, Snapshot, SummarizedHistogram, TypedMeasurement};
156}