Add label support + rename crates. (#27)

This commit is contained in:
Toby Lawrence 2019-07-05 21:14:08 -04:00 committed by GitHub
parent d33d54f0ac
commit d459db8084
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 2201 additions and 1309 deletions

View File

@ -45,8 +45,8 @@ their own copyright notices and license terms:
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
* metrics-facade is a fork of rust-lang-nursery/log which carries the
following license:
* metrics is a fork of rust-lang-nursery/log which carries the following
license:
Copyright (c) 2014 The Rust Project Developers

View File

@ -1,8 +1,8 @@
[workspace]
members = [
"metrics-core",
"metrics-facade",
"metrics",
"metrics-runtime",
"metrics-util",
"metrics-exporter-log",
"metrics-exporter-http",

View File

@ -32,24 +32,214 @@
//!
//! Histograms are a convenient way to measure behavior not only at the median, but at the edges of
//! normal operating behavior.
#![deny(missing_docs)]
use futures::future::Future;
use std::borrow::Cow;
use std::fmt;
use std::slice::Iter;
use std::time::Duration;
/// An optimized metric key.
/// An allocation-optimized string.
///
/// As some metrics might be sent at high frequency, it makes no sense to constantly allocate and
/// reallocate owned [`String`]s when a static [`str`] would suffice. As we don't want to limit
/// callers, though, we opt to use a copy-on-write pointer -- [`Cow`] -- to allow callers
/// flexiblity in how and what they pass.
pub type Key = Cow<'static, str>;
/// We specify `ScopedString` to attempt to get the best of both worlds: flexibility to provide a
/// static or dynamic (owned) string, while retaining the performance benefits of being able to
/// take ownership of owned strings and borrows of completely static strings.
pub type ScopedString = Cow<'static, str>;
/// A value which can be converted into a nanosecond representation.
/// A key/value pair used to further describe a metric.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct Label(ScopedString, ScopedString);
impl Label {
/// Creates a `Label` from a key and value.
pub fn new<K, V>(key: K, value: V) -> Self
where
K: Into<ScopedString>,
V: Into<ScopedString>,
{
Label(key.into(), value.into())
}
/// The key of this label.
pub fn key(&self) -> &str {
self.0.as_ref()
}
/// The value of this label.
pub fn value(&self) -> &str {
self.1.as_ref()
}
/// Consumes this `Label`, returning the key and value.
pub fn into_parts(self) -> (ScopedString, ScopedString) {
(self.0, self.1)
}
}
/// A metric key.
///
/// A key always includes a name, but can optional include multiple labels used to further describe
/// the metric.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct Key {
name: ScopedString,
labels: Vec<Label>,
}
impl Key {
/// Creates a `Key` from a name.
pub fn from_name<N>(name: N) -> Self
where
N: Into<ScopedString>,
{
Key {
name: name.into(),
labels: Vec::new(),
}
}
/// Creates a `Key` from a name and vector of `Label`s.
pub fn from_name_and_labels<N, L>(name: N, labels: L) -> Self
where
N: Into<ScopedString>,
L: IntoLabels,
{
Key {
name: name.into(),
labels: labels.into_labels(),
}
}
/// Adds a new set of labels to this key.
///
/// New labels will be appended to any existing labels.
pub fn add_labels<L>(&mut self, new_labels: L)
where
L: IntoLabels,
{
self.labels.extend(new_labels.into_labels());
}
/// Name of this key.
pub fn name(&self) -> ScopedString {
self.name.clone()
}
/// Labels of this key, if they exist.
pub fn labels(&self) -> Iter<Label> {
self.labels.iter()
}
/// Maps the name of this `Key` to a new name.
pub fn map_name<F>(self, f: F) -> Self
where
F: FnOnce(ScopedString) -> ScopedString,
{
Key {
name: f(self.name),
labels: self.labels,
}
}
/// Consumes this `Key`, returning the name and any labels.
pub fn into_parts(self) -> (ScopedString, Vec<Label>) {
(self.name, self.labels)
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.labels.is_empty() {
true => write!(f, "Key({}", self.name),
false => {
let kv_pairs = self
.labels
.iter()
.map(|label| format!("{} = {}", label.0, label.1))
.collect::<Vec<_>>();
write!(f, "Key({}, [{}])", self.name, kv_pairs.join(", "))
}
}
}
}
impl From<String> for Key {
fn from(name: String) -> Key {
Key::from_name(name)
}
}
impl From<&'static str> for Key {
fn from(name: &'static str) -> Key {
Key::from_name(name)
}
}
impl From<ScopedString> for Key {
fn from(name: ScopedString) -> Key {
Key::from_name(name)
}
}
impl<K, L> From<(K, L)> for Key
where
K: Into<ScopedString>,
L: IntoLabels,
{
fn from(parts: (K, L)) -> Key {
Key::from_name_and_labels(parts.0, parts.1)
}
}
impl<K, V> From<(K, V)> for Label
where
K: Into<ScopedString>,
V: Into<ScopedString>,
{
fn from(pair: (K, V)) -> Label {
Label::new(pair.0, pair.1)
}
}
impl<K, V> From<&(K, V)> for Label
where
K: Into<ScopedString> + Clone,
V: Into<ScopedString> + Clone,
{
fn from(pair: &(K, V)) -> Label {
Label::new(pair.0.clone(), pair.1.clone())
}
}
/// A value that can be converted to `Label`s.
pub trait IntoLabels {
/// Consumes this value, turning it into a vector of `Label`s.
fn into_labels(self) -> Vec<Label>;
}
impl IntoLabels for Vec<Label> {
fn into_labels(self) -> Vec<Label> {
self
}
}
impl<T, L> IntoLabels for &T
where
Self: IntoIterator<Item = L>,
L: Into<Label>,
{
fn into_labels(self) -> Vec<Label> {
self.into_iter().map(|l| l.into()).collect()
}
}
/// Used to do a nanosecond conversion.
///
/// This trait allows us to interchangably accept raw integer time values, ones already in
/// nanoseconds, as well as the more conventional [`Duration`] which is a result of getting the
/// difference between two [`Instant`](std::time::Instant)s.
pub trait AsNanoseconds {
/// Performs the conversion.
fn as_nanos(&self) -> u64;
}
@ -74,7 +264,7 @@ pub trait Recorder {
/// counters and gauges usually have slightly different modes of operation.
///
/// For the sake of flexibility on the exporter side, both are provided.
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64);
fn record_counter(&mut self, key: Key, value: u64);
/// Records a gauge.
///
@ -83,7 +273,7 @@ pub trait Recorder {
/// counters and gauges usually have slightly different modes of operation.
///
/// For the sake of flexibility on the exporter side, both are provided.
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64);
fn record_gauge(&mut self, key: Key, value: i64);
/// Records a histogram.
///
@ -91,7 +281,7 @@ pub trait Recorder {
/// of the underlying observed values, and callers will need to process them accordingly.
///
/// There is no guarantee that this method will not be called multiple times for the same key.
fn record_histogram<K: Into<Key>>(&mut self, key: K, values: &[u64]);
fn record_histogram(&mut self, key: Key, values: &[u64]);
}
/// A value that holds a point-in-time view of collected metrics.
@ -102,7 +292,9 @@ pub trait Snapshot {
/// A value that can provide on-demand snapshots.
pub trait SnapshotProvider {
/// Snapshot given by the provider.
type Snapshot: Snapshot;
/// Errors produced during generation.
type SnapshotError;
/// Gets a snapshot.
@ -111,10 +303,48 @@ pub trait SnapshotProvider {
/// A value that can provide on-demand snapshots asynchronously.
pub trait AsyncSnapshotProvider {
/// Snapshot given by the provider.
type Snapshot: Snapshot;
/// Errors produced during generation.
type SnapshotError;
/// The future response value.
type SnapshotFuture: Future<Item = Self::Snapshot, Error = Self::SnapshotError>;
/// Gets a snapshot asynchronously.
fn get_snapshot_async(&self) -> Self::SnapshotFuture;
}
/// Helper macro for generating a set of labels.
///
/// While a `Label` can be generated manually, most users will tend towards the key => value format
/// commonly used for defining hashes/maps in many programming languages. This macro allows users
/// to do the exact same thing in calls that depend on [`metrics_core::IntoLabels`].
///
/// # Examples
/// ```rust
/// # #[macro_use] extern crate metrics_core;
/// # use metrics_core::IntoLabels;
/// fn takes_labels<L: IntoLabels>(name: &str, labels: L) {
/// println!("name: {} labels: {:?}", name, labels.into_labels());
/// }
///
/// takes_labels("requests_processed", labels!("request_type" => "admin"));
/// ```
#[macro_export]
macro_rules! labels {
(@ { $($out:expr),* $(,)* } $(,)*) => {
std::vec![ $($out),* ]
};
(@ { } $k:expr => $v:expr, $($rest:tt)*) => {
labels!(@ { $crate::Label::new($k, $v) } $($rest)*)
};
(@ { $($out:expr),+ } $k:expr => $v:expr, $($rest:tt)*) => {
labels!(@ { $($out),+, $crate::Label::new($k, $v) } $($rest)*)
};
($($args:tt)*) => {
labels!(@ { } $($args)*, )
};
}

View File

@ -9,7 +9,7 @@ license = "MIT"
description = "metric exporter for serving metrics over HTTP"
homepage = "https://github.com/metrics-rs/metrics"
repository = "https://github.com/metrics-rs/metrics-exporter-http"
repository = "https://github.com/metrics-rs/metrics"
documentation = "https://docs.rs/metrics-exporter-http"
[dependencies]

View File

@ -8,6 +8,7 @@
//! address
//! - `into_future` will return a [`Future`] that when driven will run the HTTP server on the
//! configured address
#![deny(missing_docs)]
#[macro_use]
extern crate log;

View File

@ -9,7 +9,7 @@ license = "MIT"
description = "metric exporter for outputting to logs"
homepage = "https://github.com/metrics-rs/metrics"
repository = "https://github.com/metrics-rs/metrics-exporter-log"
repository = "https://github.com/metrics-rs/metrics"
documentation = "https://docs.rs/metrics-exporter-log"
[dependencies]

View File

@ -9,6 +9,7 @@
//! interval
//! - `into_future` will return a [`Future`] that when driven will take a snapshot on the
//! configured interval and log it
#![deny(missing_docs)]
#[macro_use]
extern crate log;

View File

@ -1,27 +0,0 @@
[package]
name = "metrics-facade"
version = "0.1.1"
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
license = "MIT"
edition = "2018"
repository = "https://github.com/metrics-rs/metrics"
documentation = "https://docs.rs/metrics-facade"
description = """
A lightweight metrics facade for Rust
"""
categories = ["development-tools::debugging"]
readme = "README.md"
keywords = ["metrics", "facade"]
build = "build.rs"
[dependencies]
metrics-core = { path = "../metrics-core", version = "^0.4" }
[dev-dependencies]
log = "^0.4"
[package.metadata.docs.rs]
features = ["std"]
[features]
std = []

View File

@ -1,22 +0,0 @@
# metrics-facade
[![conduct-badge][]][conduct] [![downloads-badge][] ![release-badge][]][crate] [![docs-badge][]][docs] [![license-badge][]](#license)
[conduct-badge]: https://img.shields.io/badge/%E2%9D%A4-code%20of%20conduct-blue.svg
[downloads-badge]: https://img.shields.io/crates/d/metrics-facade.svg
[release-badge]: https://img.shields.io/crates/v/metrics-facade.svg
[license-badge]: https://img.shields.io/crates/l/metrics-facade.svg
[docs-badge]: https://docs.rs/metrics-facade/badge.svg
[conduct]: https://github.com/metrics-rs/metrics/blob/master/CODE_OF_CONDUCT.md
[crate]: https://crates.io/crates/metrics-facade
[docs]: https://docs.rs/metrics-facade
__metrics-facade__ is a lightweight metrics facade.
## code of conduct
**NOTE**: All conversations and contributions to this project shall adhere to the [Code of Conduct][conduct].
# what's it all about?
__metrics-facade__ provides macros, similar to the [`log`](https://docs.rs/log) crate, that let library and executable authors instrument their code by collecting metrics -- incrementing counters, gauges, and histograms -- about their code, deferring the collecting and export of these metrics to whatever the installed metrics library is.

View File

@ -1,45 +0,0 @@
#[macro_use]
extern crate metrics_facade;
use metrics_core::Key;
use metrics_facade::Recorder;
#[allow(dead_code)]
static RECORDER: PrintRecorder = PrintRecorder;
#[derive(Default)]
struct PrintRecorder;
impl Recorder for PrintRecorder {
fn record_counter(&self, key: Key, value: u64) {
println!("metrics -> counter(name={}, value={})", key, value);
}
fn record_gauge(&self, key: Key, value: i64) {
println!("metrics -> gauge(name={}, value={})", key, value);
}
fn record_histogram(&self, key: Key, value: u64) {
println!("metrics -> histogram(name={}, value={})", key, value);
}
}
#[cfg(feature = "std")]
fn init_print_logger() {
let recorder = PrintRecorder::default();
metrics_facade::set_boxed_recorder(Box::new(recorder)).unwrap()
}
#[cfg(not(feature = "std"))]
fn init_print_logger() {
metrics_facade::set_recorder(&RECORDER).unwrap()
}
fn main() {
init_print_logger();
counter!("mycounter", 42);
gauge!("mygauge", 123);
timing!("mytiming", 120, 190);
timing!("mytiming", 70);
value!("myvalue", 666);
}

View File

@ -1,334 +0,0 @@
//! A lightweight metrics facade.
//!
//! The `metrics-facade` 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.
//!
//! If no metrics implementation is selected, the facade falls back to a "noop" implementation that
//! ignores all metrics. The overhead in this case is very small - an atomic load and comparison.
//!
//! # Use
//! The basic use of the facade crate is through the four metrics macros: [`counter!`], [`gauge!`],
//! [`timing!`], and [`value!`]. These macros correspond to updating a counter, updating a gauge,
//! updating a histogram based on a start/end, and updating a histogram with a single value.
//!
//! Both [`timing!`] and [`value!`] are effectively identical in so far as that they both translate
//! to recording a single value to an underlying histogram, but [`timing!`] is provided for
//! contextual consistency: if you're recording a measurement of the time passed during an
//! operation, the end result is a single value, but it's more of a "timing" value than just a
//! "value". The [`timing!`] macro also has a branch to accept the start and end values which
//! allows for a potentially clearer invocation.
//!
//! ## In libraries
//! Libraries should link only to the `metrics-facade` crate, and use the provided macros to record
//! whatever metrics will be useful to downstream consumers.
//!
//! ### Examples
//!
//! ```rust
//! #[macro_use]
//! extern crate metrics_facade;
//!
//! # 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 end = Instant::now();
//!
//! timing!("process.query_time", start, end);
//! counter!("process.query_row_count", row_count);
//!
//! row_count
//! }
//! # fn main() {}
//! ```
//!
//! ## In executables
//!
//! Executables should choose a metrics implementation and initialize it early in the runtime of
//! the program. Metrics implementations will typically include a function to do this. Any
//! metrics recordered before the implementation is initialized will be ignored.
//!
//! The executable itself may use the `metrics-facade` crate to record metrics well.
//!
//! ### Warning
//!
//! The metrics system may only be initialized once.
//!
//! # Available metrics implementations
//!
//! Currently, the only available metrics implementation is [metrics].
//!
//! # Implementing a Recorder
//!
//! Recorders implement the [`Recorder`] trait. Here's a basic example which writes the
//! metrics in text form via the `log` crate.
//!
//! ```rust
//! #[macro_use]
//! extern crate log;
//! extern crate metrics_facade;
//! extern crate metrics_core;
//!
//! use metrics_facade::Recorder;
//! use metrics_core::Key;
//!
//! struct LogRecorder;
//!
//! impl Recorder for LogRecorder {
//! fn record_counter(&self, key: Key, value: u64) {
//! info!("counter '{}' -> {}", key, value);
//! }
//!
//! fn record_gauge(&self, key: Key, value: i64) {
//! info!("gauge '{}' -> {}", key, value);
//! }
//!
//! fn record_histogram(&self, key: Key, value: u64) {
//! info!("histogram '{}' -> {}", key, value);
//! }
//! }
//! # fn main() {}
//! ```
//!
//! Recorders are installed by calling the [`set_recorder`] function. Recorders should provide a
//! function that wraps the creation and installation of the recorder:
//!
//! ```rust
//! # extern crate metrics_facade;
//! # extern crate metrics_core;
//! # use metrics_facade::Recorder;
//! # use metrics_core::Key;
//! # struct SimpleRecorder;
//! # impl Recorder for SimpleRecorder {
//! # fn record_counter(&self, _key: Key, _value: u64) {}
//! # fn record_gauge(&self, _key: Key, _value: i64) {}
//! # fn record_histogram(&self, _key: Key, _value: u64) {}
//! # }
//! use metrics_facade::SetRecorderError;
//!
//! static RECORDER: SimpleRecorder = SimpleRecorder;
//!
//! pub fn init() -> Result<(), SetRecorderError> {
//! metrics_facade::set_recorder(&RECORDER)
//! }
//! # fn main() {}
//! ```
//!
//! # Use with `std`
//!
//! `set_recorder` requires you to provide a `&'static Recorder`, which can be hard to
//! obtain if your recorder depends on some runtime configuration. The `set_boxed_recorder`
//! function is available with the `std` Cargo feature. It is identical to `set_recorder` except
//! that it takes a `Box<Recorder>` rather than a `&'static Recorder`:
//!
//! ```rust
//! # extern crate metrics_facade;
//! # extern crate metrics_core;
//! # use metrics_facade::Recorder;
//! # use metrics_core::Key;
//! # struct SimpleRecorder;
//! # impl Recorder for SimpleRecorder {
//! # fn record_counter(&self, _key: Key, _value: u64) {}
//! # fn record_gauge(&self, _key: Key, _value: i64) {}
//! # fn record_histogram(&self, _key: Key, _value: u64) {}
//! # }
//! use metrics_facade::SetRecorderError;
//!
//! # #[cfg(feature = "std")]
//! pub fn init() -> Result<(), SetRecorderError> {
//! metrics_facade::set_boxed_recorder(Box::new(SimpleRecorder))
//! }
//! # fn main() {}
//! ```
//!
//! [metrics]: https://docs.rs/metrics
#![deny(missing_docs)]
use metrics_core::{AsNanoseconds, Key};
#[cfg(feature = "std")]
use std::error;
use std::fmt;
use std::sync::atomic::{AtomicUsize, Ordering};
#[macro_use]
mod macros;
static mut RECORDER: &'static Recorder = &NoopRecorder;
static STATE: AtomicUsize = AtomicUsize::new(0);
const UNINITIALIZED: usize = 0;
const INITIALIZING: usize = 1;
const INITIALIZED: usize = 2;
static SET_RECORDER_ERROR: &'static str =
"attempted to set a recorder after the metrics system was already initialized";
/// A value that records metrics behind the facade.
pub trait Recorder {
/// Records a counter.
///
/// From the perspective of an recorder, a counter and gauge are essentially identical, insofar
/// as they are both a single value tied to a key. From the perspective of a collector,
/// counters and gauges usually have slightly different modes of operation.
///
/// For the sake of flexibility on the exporter side, both are provided.
fn record_counter(&self, key: Key, value: u64);
/// Records a gauge.
///
/// From the perspective of a recorder, a counter and gauge are essentially identical, insofar
/// as they are both a single value tied to a key. From the perspective of a collector,
/// counters and gauges usually have slightly different modes of operation.
///
/// For the sake of flexibility on the exporter side, both are provided.
fn record_gauge(&self, key: Key, value: i64);
/// Records a histogram.
///
/// Recorders are expected to tally their own histogram views, so this will be called with all
/// of the underlying observed values, and callers will need to process them accordingly.
///
/// There is no guarantee that this method will not be called multiple times for the same key.
fn record_histogram(&self, key: Key, value: u64);
}
struct NoopRecorder;
impl Recorder for NoopRecorder {
fn record_counter(&self, _key: Key, _value: u64) {}
fn record_gauge(&self, _key: Key, _value: i64) {}
fn record_histogram(&self, _key: Key, _value: u64) {}
}
/// Sets the global recorder to a `&'static Recorder`.
///
/// This function may only be called once in the lifetime of a program. Any metrics recorded
/// before the call to `set_recorder` occurs will be completely ignored.
///
/// This function does not typically need to be called manually. Metrics implementations should
/// provide an initialization method that installs the recorder internally.
///
/// # Errors
///
/// An error is returned if a recorder has already been set.
#[cfg(atomic_cas)]
pub fn set_recorder(recorder: &'static Recorder) -> Result<(), SetRecorderError> {
set_recorder_inner(|| recorder)
}
/// Sets the global recorder to a `Box<Recorder>`.
///
/// This is a simple convenience wrapper over `set_recorder`, which takes a `Box<Recorder>`
/// rather than a `&'static Recorder`. See the document for [`set_recorder`] for more
/// details.
///
/// Requires the `std` feature.
///
/// # Errors
///
/// An error is returned if a recorder has already been set.
#[cfg(all(feature = "std", atomic_cas))]
pub fn set_boxed_recorder(recorder: Box<Recorder>) -> Result<(), SetRecorderError> {
set_recorder_inner(|| unsafe { &*Box::into_raw(recorder) })
}
#[cfg(atomic_cas)]
fn set_recorder_inner<F>(make_recorder: F) -> Result<(), SetRecorderError>
where
F: FnOnce() -> &'static Recorder,
{
unsafe {
match STATE.compare_and_swap(UNINITIALIZED, INITIALIZING, Ordering::SeqCst) {
UNINITIALIZED => {
RECORDER = make_recorder();
STATE.store(INITIALIZED, Ordering::SeqCst);
Ok(())
}
INITIALIZING => {
while STATE.load(Ordering::SeqCst) == INITIALIZING {}
Err(SetRecorderError(()))
}
_ => Err(SetRecorderError(())),
}
}
}
/// A thread-unsafe version of [`set_recorder`].
///
/// This function is available on all platforms, even those that do not have support for atomics
/// that is need by [`set_recorder`].
///
/// In almost all cases, [`set_recorder`] should be preferred.
///
/// # Safety
///
/// This function is only safe to call when no other metrics initialization function is called
/// while this function still executes.
///
/// This can be upheld by (for example) making sure that **there are no other threads**, and (on
/// embedded) that **interrupts are disabled**.
///
/// It is safe to use other metrics functions while this function runs (including all metrics
/// macros).
pub unsafe fn set_recorder_racy(recorder: &'static Recorder) -> Result<(), SetRecorderError> {
match STATE.load(Ordering::SeqCst) {
UNINITIALIZED => {
RECORDER = recorder;
STATE.store(INITIALIZED, Ordering::SeqCst);
Ok(())
}
INITIALIZING => {
// This is just plain UB, since we were racing another initialization function
unreachable!("set_recorder_racy must not be used with other initialization functions")
}
_ => Err(SetRecorderError(())),
}
}
/// The type returned by [`set_recorder`] if [`set_recorder`] has already been called.
#[derive(Debug)]
pub struct SetRecorderError(());
impl fmt::Display for SetRecorderError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(SET_RECORDER_ERROR)
}
}
// The Error trait is not available in libcore
#[cfg(feature = "std")]
impl error::Error for SetRecorderError {
fn description(&self) -> &str {
SET_RECORDER_ERROR
}
}
/// Returns a reference to the recorder.
///
/// If a recorder has not been set, a no-op implementation is returned.
pub fn recorder() -> &'static Recorder {
unsafe {
if STATE.load(Ordering::SeqCst) != INITIALIZED {
static NOOP: NoopRecorder = NoopRecorder;
&NOOP
} else {
RECORDER
}
}
}
#[doc(hidden)]
pub fn __private_api_record_count<K: Into<Key>>(key: K, value: u64) {
recorder().record_counter(key.into(), value);
}
#[doc(hidden)]
pub fn __private_api_record_gauge<K: Into<Key>>(key: K, value: i64) {
recorder().record_gauge(key.into(), value);
}
#[doc(hidden)]
pub fn __private_api_record_histogram<K: Into<Key>, V: AsNanoseconds>(key: K, value: V) {
recorder().record_histogram(key.into(), value.as_nanos());
}

View File

@ -1,112 +0,0 @@
/// Records a counter.
///
/// Functionally equivalent to calling [`Recorder::record_counter`].
///
/// ### Examples
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics_facade;
/// fn do_thing() {
/// let count: u64 = 42;
/// counter!("do_thing", count);
/// }
/// # fn main() {}
/// ```
#[macro_export]
macro_rules! counter {
($name:tt, $value:expr) => {{
$crate::__private_api_record_count($name, $value);
}};
}
/// Records a gauge.
///
/// Functionally equivalent to calling [`Recorder::record_gauge`].
///
/// ### Examples
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics_facade;
/// fn update_current_value() {
/// let value: i64 = -131;
/// gauge!("current_value", value);
/// }
/// # fn main() {}
/// ```
#[macro_export]
macro_rules! gauge {
($name:tt, $value:expr) => {{
$crate::__private_api_record_gauge($name, $value);
}};
}
/// Records a timing.
///
/// Functionally equivalent to calling [`Recorder::record_histogram`].
///
/// ### Examples
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics_facade;
/// # use std::time::Instant;
/// # fn process() {}
/// fn handle_request() {
/// let start = Instant::now();
/// process();
/// let end = Instant::now();
///
/// // We can pass instances of `Instant` directly:
/// timing!("performance.request_processed", start, end);
///
/// // Or we can pass just the delta:
/// let delta = end - start;
/// timing!("performance.request_processed", delta);
///
/// // And we can even pass unsigned values, both for the start/end notation:
/// let start: u64 = 100;
/// let end: u64 = 200;
/// timing!("performance.request_processed", start, end);
///
/// // And the delta notation:
/// let delta: u64 = end - start;
/// timing!("performance.request_processed", delta);
/// }
/// # fn main() {}
/// ```
#[macro_export]
macro_rules! timing {
($name:tt, $value:expr) => {{
$crate::__private_api_record_histogram($name, $value);
}};
($name:tt, $start:expr, $end:expr) => {{
let delta = $end - $start;
$crate::__private_api_record_histogram($name, delta);
}};
}
/// Records a value.
///
/// Functionally equivalent to calling [`Recorder::record_histogram`].
///
/// ### Examples
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics_facade;
/// # use std::time::Instant;
/// # fn process() -> u64 { 42 }
/// fn handle_request() {
/// let rows_read = process();
/// value!("client.process_num_rows", rows_read);
/// }
/// # fn main() {}
/// ```
#[macro_export]
macro_rules! value {
($name:tt, $value:expr) => {{
$crate::__private_api_record_histogram($name, $value);
}};
}

View File

@ -9,7 +9,7 @@ license = "MIT"
description = "metric recorder for Prometheus exposition output"
homepage = "https://github.com/metrics-rs/metrics"
repository = "https://github.com/metrics-rs/metrics-recorder-prometheus"
repository = "https://github.com/metrics-rs/metrics"
documentation = "https://docs.rs/metrics-recorder-prometheus"
[dependencies]

View File

@ -1,12 +1,15 @@
//! Records metrics in the Prometheus exposition format.
#![deny(missing_docs)]
use hdrhistogram::Histogram;
use metrics_core::{Key, Recorder};
use metrics_util::{parse_quantiles, Quantile};
use std::collections::HashMap;
use std::time::SystemTime;
/// Records metrics in the Prometheus exposition format.
pub struct PrometheusRecorder {
quantiles: Vec<Quantile>,
histos: HashMap<Key, (u64, Histogram<u64>)>,
output: String,
}
@ -29,64 +32,48 @@ impl PrometheusRecorder {
let actual_quantiles = parse_quantiles(quantiles);
Self {
quantiles: actual_quantiles,
histos: HashMap::new(),
output: get_prom_expo_header(),
}
}
}
impl Recorder for PrometheusRecorder {
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64) {
let label = key.into().as_ref().replace('.', "_");
fn record_counter(&mut self, key: Key, value: u64) {
let (name, labels) = key_to_parts(key);
let full_name = render_labeled_name(&name, &labels);
self.output.push_str("\n# TYPE ");
self.output.push_str(label.as_str());
self.output.push_str(name.as_str());
self.output.push_str(" counter\n");
self.output.push_str(label.as_str());
self.output.push_str(full_name.as_str());
self.output.push_str(" ");
self.output.push_str(value.to_string().as_str());
self.output.push_str("\n");
}
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64) {
let label = key.into().as_ref().replace('.', "_");
fn record_gauge(&mut self, key: Key, value: i64) {
let (name, labels) = key_to_parts(key);
let full_name = render_labeled_name(&name, &labels);
self.output.push_str("\n# TYPE ");
self.output.push_str(label.as_str());
self.output.push_str(name.as_str());
self.output.push_str(" gauge\n");
self.output.push_str(label.as_str());
self.output.push_str(full_name.as_str());
self.output.push_str(" ");
self.output.push_str(value.to_string().as_str());
self.output.push_str("\n");
}
fn record_histogram<K: Into<Key>>(&mut self, key: K, values: &[u64]) {
let mut sum = 0;
let mut h = Histogram::<u64>::new(3).expect("failed to create histogram");
fn record_histogram(&mut self, key: Key, values: &[u64]) {
let entry = self.histos.entry(key).or_insert_with(|| {
let h = Histogram::<u64>::new(3).expect("failed to create histogram");
(0, h)
});
let (sum, h) = entry;
for value in values {
h.record(*value).expect("failed to record histogram value");
sum += *value;
*sum += *value;
}
let label = key.into().as_ref().replace('.', "_");
self.output.push_str("\n# TYPE ");
self.output.push_str(label.as_str());
self.output.push_str(" summary\n");
for quantile in &self.quantiles {
let value = h.value_at_quantile(quantile.value());
self.output.push_str(label.as_str());
self.output.push_str("{quantile=\"");
self.output.push_str(quantile.value().to_string().as_str());
self.output.push_str("\"} ");
self.output.push_str(value.to_string().as_str());
self.output.push_str("\n");
}
self.output.push_str(label.as_str());
self.output.push_str("_sum ");
self.output.push_str(sum.to_string().as_str());
self.output.push_str("\n");
self.output.push_str(label.as_str());
self.output.push_str("_count ");
self.output.push_str(values.len().to_string().as_str());
self.output.push_str("\n");
}
}
@ -94,17 +81,74 @@ impl Clone for PrometheusRecorder {
fn clone(&self) -> Self {
Self {
output: get_prom_expo_header(),
histos: HashMap::new(),
quantiles: self.quantiles.clone(),
}
}
}
impl Into<String> for PrometheusRecorder {
fn into(self) -> String {
self.output
impl From<PrometheusRecorder> for String {
fn from(r: PrometheusRecorder) -> String {
let mut output = r.output;
for (key, sh) in r.histos {
let (sum, hist) = sh;
let (name, labels) = key_to_parts(key);
output.push_str("\n# TYPE ");
output.push_str(name.as_str());
output.push_str(" summary\n");
for quantile in &r.quantiles {
let value = hist.value_at_quantile(quantile.value());
let mut labels = labels.clone();
labels.push(format!("quantile=\"{}\"", quantile.value()));
let full_name = render_labeled_name(&name, &labels);
output.push_str(full_name.as_str());
output.push_str(" ");
output.push_str(value.to_string().as_str());
output.push_str("\n");
}
let sum_name = format!("{}_sum", name);
let full_sum_name = render_labeled_name(&sum_name, &labels);
output.push_str(full_sum_name.as_str());
output.push_str(" ");
output.push_str(sum.to_string().as_str());
output.push_str("\n");
let count_name = format!("{}_count", name);
let full_count_name = render_labeled_name(&count_name, &labels);
output.push_str(full_count_name.as_str());
output.push_str(" ");
output.push_str(hist.len().to_string().as_str());
output.push_str("\n");
}
output
}
}
fn key_to_parts(key: Key) -> (String, Vec<String>) {
let (name, labels) = key.into_parts();
let name = name.replace('.', "_");
let labels = labels
.into_iter()
.map(|label| label.into_parts())
.map(|(k, v)| format!("{}=\"{}\"", k, v))
.collect();
(name, labels)
}
fn render_labeled_name(name: &str, labels: &[String]) -> String {
let mut output = name.to_string();
if !labels.is_empty() {
let joined = labels.join(",");
output.push_str("{");
output.push_str(&joined);
output.push_str("}");
}
output
}
fn get_prom_expo_header() -> String {
let ts = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)

View File

@ -9,7 +9,7 @@ license = "MIT"
description = "metric recorder for hierarchical, text-based output"
homepage = "https://github.com/metrics-rs/metrics"
repository = "https://github.com/metrics-rs/metrics-recorder-text"
repository = "https://github.com/metrics-rs/metrics"
documentation = "https://docs.rs/metrics-recorder-text"
[dependencies]

View File

@ -42,6 +42,7 @@
//! connect_time max: 139389
//! ```
//!
#![deny(missing_docs)]
use hdrhistogram::Histogram;
use metrics_core::{Key, Recorder};
use metrics_util::{parse_quantiles, Quantile};
@ -51,6 +52,7 @@ use std::fmt::Display;
/// Records metrics in a hierarchical, text-based format.
pub struct TextRecorder {
structure: MetricsTree,
histos: HashMap<Key, Histogram<u64>>,
quantiles: Vec<Quantile>,
}
@ -74,33 +76,36 @@ impl TextRecorder {
Self {
structure: MetricsTree::with_level(0),
histos: HashMap::new(),
quantiles: actual_quantiles,
}
}
}
impl Recorder for TextRecorder {
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64) {
let (name_parts, name) = name_to_parts(key.into().as_ref());
fn record_counter(&mut self, key: Key, value: u64) {
let (name_parts, name) = key_to_parts(key);
let mut values = single_value_to_values(name, value);
self.structure.insert(name_parts, &mut values);
}
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64) {
let (name_parts, name) = name_to_parts(key.into().as_ref());
fn record_gauge(&mut self, key: Key, value: i64) {
let (name_parts, name) = key_to_parts(key);
let mut values = single_value_to_values(name, value);
self.structure.insert(name_parts, &mut values);
}
fn record_histogram<K: Into<Key>>(&mut self, key: K, values: &[u64]) {
let mut h = Histogram::new(3).expect("failed to create histogram");
fn record_histogram(&mut self, key: Key, values: &[u64]) {
let entry = self
.histos
.entry(key)
.or_insert_with(|| Histogram::<u64>::new(3).expect("failed to create histogram"));
for value in values {
h.record(*value).expect("failed to record histogram value");
entry
.record(*value)
.expect("failed to record histogram value");
}
let (name_parts, name) = name_to_parts(key.into().as_ref());
let mut values = hist_to_values(name, h, &self.quantiles);
self.structure.insert(name_parts, &mut values);
}
}
@ -108,6 +113,7 @@ impl Clone for TextRecorder {
fn clone(&self) -> Self {
Self {
structure: MetricsTree::with_level(0),
histos: HashMap::new(),
quantiles: self.quantiles.clone(),
}
}
@ -132,7 +138,7 @@ impl MetricsTree {
pub fn insert(&mut self, mut name_parts: VecDeque<String>, values: &mut Vec<String>) {
match name_parts.len() {
0 => {
let indent = " ".repeat(self.level + 1);
let indent = " ".repeat(self.level);
let mut indented = values
.iter()
.map(move |x| format!("{}{}", indent, x))
@ -154,11 +160,8 @@ impl MetricsTree {
}
pub fn into_output(self) -> String {
let indent = " ".repeat(self.level + 1);
let indent = " ".repeat(self.level);
let mut output = String::new();
if self.level == 0 {
output.push_str("\nroot:\n");
}
let mut sorted = self
.current
@ -189,9 +192,15 @@ impl MetricsTree {
}
}
impl Into<String> for TextRecorder {
fn into(self) -> String {
self.structure.into_output()
impl From<TextRecorder> for String {
fn from(r: TextRecorder) -> String {
let mut structure = r.structure;
for (key, h) in r.histos {
let (name_parts, name) = key_to_parts(key);
let mut values = hist_to_values(name, h, &r.quantiles);
structure.insert(name_parts, &mut values);
}
structure.into_output()
}
}
@ -229,14 +238,29 @@ impl std::cmp::Ord for SortEntry {
}
}
fn name_to_parts(name: &str) -> (VecDeque<String>, String) {
fn key_to_parts(key: Key) -> (VecDeque<String>, String) {
let (name, labels) = key.into_parts();
let mut parts = name
.split('.')
.map(ToOwned::to_owned)
.collect::<VecDeque<_>>();
let name = parts.pop_back().expect("name didn't have a single part");
(parts, name)
let labels = labels
.into_iter()
.map(|label| label.into_parts())
.map(|(k, v)| format!("{}=\"{}\"", k, v))
.collect::<Vec<_>>()
.join(",");
let label = if labels.is_empty() {
String::new()
} else {
format!("{{{}}}", labels)
};
let fname = format!("{}{}", name, label);
(parts, fname)
}
fn single_value_to_values<T>(name: String, value: T) -> Vec<String>

View File

@ -0,0 +1,55 @@
[package]
name = "metrics-runtime"
version = "0.1.0"
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
edition = "2018"
license = "MIT"
description = "high-speed metrics collection library"
homepage = "https://github.com/metrics-rs/metrics"
repository = "https://github.com/metrics-rs/metrics"
documentation = "https://docs.rs/metrics"
readme = "README.md"
keywords = ["metrics", "telemetry", "histogram", "counter", "gauge"]
[profile.release]
debug = true
opt-level = 3
lto = true
[features]
default = ["exporters", "recorders"]
exporters = ["metrics-exporter-log", "metrics-exporter-http"]
recorders = ["metrics-recorder-text", "metrics-recorder-prometheus"]
[[bench]]
name = "histogram"
harness = false
[dependencies]
metrics-core = { path = "../metrics-core", version = "^0.4" }
metrics-util = { path = "../metrics-util", version = "^0.2" }
metrics = { path = "../metrics", version = "^0.11", features = ["std"] }
im = "^12"
arc-swap = "^0.3"
parking_lot = "^0.8"
hashbrown = "^0.5"
quanta = "^0.3"
futures = "^0.1"
crossbeam-utils = "^0.6"
metrics-exporter-log = { path = "../metrics-exporter-log", version = "^0.2", optional = true }
metrics-exporter-http = { path = "../metrics-exporter-http", version = "^0.1", optional = true }
metrics-recorder-text = { path = "../metrics-recorder-text", version = "^0.2", optional = true }
metrics-recorder-prometheus = { path = "../metrics-recorder-prometheus", version = "^0.2", optional = true }
[dev-dependencies]
log = "^0.4"
env_logger = "^0.6"
getopts = "^0.2"
hdrhistogram = "^6.1"
criterion = "^0.2.9"
lazy_static = "^1.3"

44
metrics-runtime/README.md Normal file
View File

@ -0,0 +1,44 @@
# metrics
[![conduct-badge][]][conduct] [![downloads-badge][] ![release-badge][]][crate] [![docs-badge][]][docs] [![license-badge][]](#license)
[conduct-badge]: https://img.shields.io/badge/%E2%9D%A4-code%20of%20conduct-blue.svg
[downloads-badge]: https://img.shields.io/crates/d/metrics.svg
[release-badge]: https://img.shields.io/crates/v/metrics.svg
[license-badge]: https://img.shields.io/crates/l/metrics.svg
[docs-badge]: https://docs.rs/metrics/badge.svg
[conduct]: https://github.com/metrics-rs/metrics/blob/master/CODE_OF_CONDUCT.md
[crate]: https://crates.io/crates/metrics
[docs]: https://docs.rs/metrics
__metrics__ is a high-quality, batteries-included metrics library for Rust.
## code of conduct
**NOTE**: All conversations and contributions to this project shall adhere to the [Code of Conduct][conduct].
# what's it all about?
Running applications in production can be hard when you don't have insight into what the application is doing. We're lucky to have so many good system monitoring programs and services to show us how are servers are performing, but we still have to do the work of instrumenting our applications to gain deep insight into their behavior and performance.
`metrics` makes it easy to instrument your application to provide real-time insight into what's happening. It provides a straight-forward interface for you to collect metrics at different points, and a flexible approach to exporting those metrics in a way that meets your needs.
Some of the most common scenarios for collecting metrics from an application:
- see how many times a codepath was hit
- track the time it takes for a piece of code to execute
- expose internal counters and values in a standardized way
The number of reasons why you'd want to collect metrics is too large to list out here, and some applications emit metrics that have nothing to do with the application performance itself! Ultimately, `metrics` strives to simply provide support for the most basic types of metrics so that you can spend more time focusing on the data you'd like to collect and less time on how you're going to accomplish that.
## high-level technical features
- Supports the three most common metric types: counters, gauges, and histograms.
- Based on `metrics-core` for composability at the exporter level.
- Access to ultra-high-speed timing facilities out-of-the-box with [quanta](https://github.com/nuclearfurnace/quanta).
- Scoped metrics for effortless nesting.
- Bundled with Prometheus pull endpoint capabilities by default.
## performance
High. `metrics` is fast enough that you'll barely notice the overhead.
There is a `benchmark` example in the crate that can be run to see the type of performance acheivable on your system. A 2015 MacBook Pro (4c/8t, 2.1GHz) can push over 5 million samples per second from a single thread.

View File

@ -5,7 +5,7 @@ extern crate criterion;
extern crate lazy_static;
use criterion::{Benchmark, Criterion, Throughput};
use metrics::data::AtomicWindowedHistogram;
use metrics_runtime::data::AtomicWindowedHistogram;
use quanta::{Builder as UpkeepBuilder, Clock, Handle as UpkeepHandle};
use std::time::Duration;

View File

@ -3,13 +3,13 @@ extern crate log;
extern crate env_logger;
extern crate getopts;
extern crate hdrhistogram;
extern crate metrics;
extern crate metrics_core;
extern crate metrics_runtime;
use getopts::Options;
use hdrhistogram::Histogram;
use metrics::{Receiver, Sink};
use metrics_core::{Key, Recorder, Snapshot, SnapshotProvider};
use metrics_core::SnapshotProvider;
use metrics_runtime::{Receiver, Sink};
use quanta::Clock;
use std::{
env,
@ -65,13 +65,13 @@ impl Generator {
let t1 = self.stats.now();
if let Some(t0) = self.t0 {
let start = if counter % 33 == 0 {
let start = if counter % 1000 == 0 {
self.stats.now()
} else {
0
};
let _ = self.stats.record_count("ok", 1);
let _ = self.stats.record_counter("ok", 1);
let _ = self.stats.record_timing("ok", t0, t1);
let _ = self.stats.record_gauge("total", self.gauge);
@ -162,6 +162,7 @@ pub fn opts() -> Options {
"INTEGER",
);
opts.optopt("p", "producers", "number of producers", "INTEGER");
opts.optflag("c", "cached", "whether or not to use cached handles");
opts.optflag("h", "help", "print this help menu");
opts
@ -187,6 +188,11 @@ fn main() {
return;
}
let use_cached = matches.opt_present("cached");
if use_cached {
info!("using cached handles");
}
info!("metrics benchmark");
// Build our sink and configure the facets.
@ -226,7 +232,12 @@ fn main() {
let r = rate_counter.clone();
let c = clock.clone();
let handle = thread::spawn(move || {
Generator::new(s, d, r, c).run_cached();
let mut gen = Generator::new(s, d, r, c);
if use_cached {
gen.run_cached();
} else {
gen.run();
}
});
handles.push(handle);
@ -244,7 +255,7 @@ fn main() {
let t1 = Instant::now();
let start = Instant::now();
let snapshot = controller.get_snapshot().unwrap();
let _snapshot = controller.get_snapshot().unwrap();
let end = Instant::now();
snapshot_hist.saturating_record(duration_as_nanos(end - start) as u64);
@ -277,34 +288,6 @@ fn main() {
}
}
struct TotalRecorder {
total: u64,
}
impl TotalRecorder {
pub fn new() -> Self {
Self { total: 0 }
}
pub fn total(&self) -> u64 {
self.total
}
}
impl Recorder for TotalRecorder {
fn record_counter<K: Into<Key>>(&mut self, _key: K, value: u64) {
self.total += value;
}
fn record_gauge<K: Into<Key>>(&mut self, _key: K, value: i64) {
self.total += value as u64;
}
fn record_histogram<K: Into<Key>>(&mut self, _key: K, values: &[u64]) {
self.total += values.len() as u64;
}
}
fn duration_as_nanos(d: Duration) -> f64 {
(d.as_secs() as f64 * 1e9) + d.subsec_nanos() as f64
}

View File

@ -3,16 +3,16 @@ extern crate log;
extern crate env_logger;
extern crate getopts;
extern crate hdrhistogram;
extern crate metrics;
extern crate metrics_core;
extern crate metrics_runtime;
#[macro_use]
extern crate metrics_facade;
extern crate metrics;
use getopts::Options;
use hdrhistogram::Histogram;
use metrics::{Receiver, Sink};
use metrics_core::{Key, Recorder, Snapshot, SnapshotProvider};
use metrics_core::SnapshotProvider;
use metrics_runtime::Receiver;
use quanta::Clock;
use std::{
env,
@ -68,7 +68,7 @@ impl Generator {
};
counter!("ok", 1);
timing!("ok", t1 - t0);
timing!("ok", t0, t1);
gauge!("total", self.gauge);
if start != 0 {
@ -192,7 +192,7 @@ fn main() {
let t1 = Instant::now();
let start = Instant::now();
let snapshot = controller.get_snapshot().unwrap();
let _snapshot = controller.get_snapshot().unwrap();
let end = Instant::now();
snapshot_hist.saturating_record(duration_as_nanos(end - start) as u64);
@ -209,7 +209,7 @@ fn main() {
info!("--------------------------------------------------------------------------------");
info!(" ingested samples total: {}", total);
info!(
"snapshot retrieval: min: {:9} p50: {:9} p95: {:9} p99: {:9} p999: {:9} max: {:9}",
"snapshot end-to-end: min: {:9} p50: {:9} p95: {:9} p99: {:9} p999: {:9} max: {:9}",
nanos_to_readable(snapshot_hist.min()),
nanos_to_readable(snapshot_hist.value_at_percentile(50.0)),
nanos_to_readable(snapshot_hist.value_at_percentile(95.0)),
@ -225,34 +225,6 @@ fn main() {
}
}
struct TotalRecorder {
total: u64,
}
impl TotalRecorder {
pub fn new() -> Self {
Self { total: 0 }
}
pub fn total(&self) -> u64 {
self.total
}
}
impl Recorder for TotalRecorder {
fn record_counter<K: Into<Key>>(&mut self, _key: K, value: u64) {
self.total += value;
}
fn record_gauge<K: Into<Key>>(&mut self, _key: K, value: i64) {
self.total += value as u64;
}
fn record_histogram<K: Into<Key>>(&mut self, _key: K, values: &[u64]) {
self.total += values.len() as u64;
}
}
fn duration_as_nanos(d: Duration) -> f64 {
(d.as_secs() as f64 * 1e9) + d.subsec_nanos() as f64
}

View File

@ -1,25 +1,16 @@
use crate::data::AtomicWindowedHistogram;
use metrics_core::{Key, ScopedString};
use metrics_util::StreamingIntegers;
use quanta::Clock;
use std::borrow::Cow;
use std::ops::Deref;
use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
/// Optimized metric name.
///
/// This can either be a [`&'static str`](str) or [`String`].
pub type MetricName = Cow<'static, str>;
/// A scope, or context, for a metric.
///
/// Not interacted with directly by end users, and only exposed due to a lack of trait method
/// visbility controls.
///
/// See also: [Sink::scoped](crate::Sink::scoped).
#[derive(PartialEq, Eq, Hash, Clone)]
pub enum MetricScope {
#[doc(hidden)]
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum Scope {
/// Root scope.
Root,
@ -27,32 +18,47 @@ pub enum MetricScope {
Nested(Vec<String>),
}
impl MetricScope {
pub(crate) fn into_scoped(self, name: MetricName) -> String {
impl Scope {
pub(crate) fn into_scoped(self, name: ScopedString) -> ScopedString {
match self {
MetricScope::Root => name.to_string(),
MetricScope::Nested(mut parts) => {
Scope::Root => name,
Scope::Nested(mut parts) => {
if !name.is_empty() {
parts.push(name.to_string());
}
parts.join(".")
parts.join(".").into()
}
}
}
}
pub(crate) type MetricScopeHandle = u64;
pub(crate) type ScopeHandle = u64;
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub(crate) enum MetricKind {
pub(crate) enum Kind {
Counter,
Gauge,
Histogram,
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub(crate) enum MetricIdentifier {
Unlabeled(MetricName, MetricScopeHandle, MetricKind),
pub(crate) struct Identifier(Key, ScopeHandle, Kind);
impl Identifier {
pub fn new<K>(key: K, handle: ScopeHandle, kind: Kind) -> Self
where
K: Into<Key>,
{
Identifier(key.into(), handle, kind)
}
pub fn kind(&self) -> Kind {
self.2.clone()
}
pub fn into_parts(self) -> (Key, ScopeHandle, Kind) {
(self.0, self.1, self.2)
}
}
#[derive(Debug)]
@ -71,13 +77,13 @@ pub(crate) enum ValueSnapshot {
#[derive(Clone, Debug)]
/// Handle to the underlying measurement for a metric.
pub(crate) struct MetricValue {
pub(crate) struct ValueHandle {
state: Arc<ValueState>,
}
impl MetricValue {
impl ValueHandle {
fn new(state: ValueState) -> Self {
MetricValue {
ValueHandle {
state: Arc::new(state),
}
}
@ -163,31 +169,31 @@ impl Delta for Instant {
#[cfg(test)]
mod tests {
use super::{MetricScope, MetricValue, ValueSnapshot};
use super::{Scope, ValueHandle, ValueSnapshot};
use quanta::Clock;
use std::time::Duration;
#[test]
fn test_metric_scope() {
let root_scope = MetricScope::Root;
let root_scope = Scope::Root;
assert_eq!(root_scope.into_scoped("".into()), "".to_string());
let root_scope = MetricScope::Root;
let root_scope = Scope::Root;
assert_eq!(
root_scope.into_scoped("jambalaya".into()),
"jambalaya".to_string()
);
let nested_scope = MetricScope::Nested(vec![]);
let nested_scope = Scope::Nested(vec![]);
assert_eq!(nested_scope.into_scoped("".into()), "".to_string());
let nested_scope = MetricScope::Nested(vec![]);
let nested_scope = Scope::Nested(vec![]);
assert_eq!(
nested_scope.into_scoped("toilet".into()),
"toilet".to_string()
);
let nested_scope = MetricScope::Nested(vec![
let nested_scope = Scope::Nested(vec![
"chamber".to_string(),
"of".to_string(),
"secrets".to_string(),
@ -197,7 +203,7 @@ mod tests {
"chamber.of.secrets".to_string()
);
let nested_scope = MetricScope::Nested(vec![
let nested_scope = Scope::Nested(vec![
"chamber".to_string(),
"of".to_string(),
"secrets".to_string(),
@ -210,14 +216,14 @@ mod tests {
#[test]
fn test_metric_values() {
let counter = MetricValue::counter();
let counter = ValueHandle::counter();
counter.update_counter(42);
match counter.snapshot() {
ValueSnapshot::Counter(value) => assert_eq!(value, 42),
_ => panic!("incorrect value snapshot type for counter"),
}
let gauge = MetricValue::gauge();
let gauge = ValueHandle::gauge();
gauge.update_gauge(23);
match gauge.snapshot() {
ValueSnapshot::Gauge(value) => assert_eq!(value, 23),
@ -226,7 +232,7 @@ mod tests {
let (mock, _) = Clock::mock();
let histogram =
MetricValue::histogram(Duration::from_secs(10), Duration::from_secs(1), mock);
ValueHandle::histogram(Duration::from_secs(10), Duration::from_secs(1), mock);
histogram.update_histogram(8675309);
histogram.update_histogram(5551212);
match histogram.snapshot() {

View File

@ -17,4 +17,13 @@ impl Configuration {
upkeep_interval: builder.upkeep_interval,
}
}
#[allow(dead_code)]
pub(crate) fn mock() -> Self {
Self {
histogram_window: Duration::from_secs(5),
histogram_granularity: Duration::from_secs(1),
upkeep_interval: Duration::from_millis(10),
}
}
}

View File

@ -0,0 +1,24 @@
use crate::common::ValueHandle;
/// Proxy object to update a counter.
pub struct Counter {
handle: ValueHandle,
}
impl Counter {
/// Records a value for the counter.
pub fn record(&self, value: u64) {
self.handle.update_counter(value);
}
/// Increments the counter by one.
pub fn increment(&self) {
self.handle.update_counter(1);
}
}
impl From<ValueHandle> for Counter {
fn from(handle: ValueHandle) -> Self {
Self { handle }
}
}

View File

@ -1,8 +1,8 @@
use crate::common::MetricValue;
use crate::common::ValueHandle;
/// Proxy object to update a gauge.
pub struct Gauge {
handle: MetricValue,
handle: ValueHandle,
}
impl Gauge {
@ -12,8 +12,8 @@ impl Gauge {
}
}
impl From<MetricValue> for Gauge {
fn from(handle: MetricValue) -> Self {
impl From<ValueHandle> for Gauge {
fn from(handle: ValueHandle) -> Self {
Self { handle }
}
}

View File

@ -1,4 +1,4 @@
use crate::common::{Delta, MetricValue};
use crate::common::{Delta, ValueHandle};
use crate::helper::duration_as_nanos;
use crossbeam_utils::Backoff;
use metrics_util::{AtomicBucket, StreamingIntegers};
@ -9,7 +9,7 @@ use std::time::Duration;
/// Proxy object to update a histogram.
pub struct Histogram {
handle: MetricValue,
handle: ValueHandle,
}
impl Histogram {
@ -25,8 +25,8 @@ impl Histogram {
}
}
impl From<MetricValue> for Histogram {
fn from(handle: MetricValue) -> Self {
impl From<ValueHandle> for Histogram {
fn from(handle: ValueHandle) -> Self {
Self { handle }
}
}

View File

@ -1,16 +1,25 @@
use crate::common::ValueSnapshot;
use metrics_core::{Key, Recorder, Snapshot as MetricsSnapshot};
use std::borrow::Cow;
/// A point-in-time view of metric data.
#[derive(Default, Debug)]
pub struct Snapshot {
measurements: Vec<(String, ValueSnapshot)>,
measurements: Vec<(Key, ValueSnapshot)>,
}
impl Snapshot {
pub(crate) fn from(from: Vec<(String, ValueSnapshot)>) -> Self {
Snapshot { measurements: from }
pub(crate) fn new(measurements: Vec<(Key, ValueSnapshot)>) -> Self {
Snapshot { measurements }
}
/// Number of measurements in this snapshot.
pub fn len(&self) -> usize {
self.measurements.len()
}
/// Whether or not the snapshot is empty.
pub fn is_empty(&self) -> bool {
self.measurements.len() != 0
}
}
@ -18,14 +27,12 @@ impl MetricsSnapshot for Snapshot {
/// Records the snapshot to the given recorder.
fn record<R: Recorder>(&self, recorder: &mut R) {
for (key, snapshot) in &self.measurements {
// TODO: switch this to Key::Owned once type_alias_enum_variants lands
// in 1.37.0 (#61682)
let owned_key: Key = Cow::Owned(key.clone());
let key = key.clone();
match snapshot {
ValueSnapshot::Counter(value) => recorder.record_counter(owned_key.clone(), *value),
ValueSnapshot::Gauge(value) => recorder.record_gauge(owned_key.clone(), *value),
ValueSnapshot::Counter(value) => recorder.record_counter(key, *value),
ValueSnapshot::Gauge(value) => recorder.record_gauge(key, *value),
ValueSnapshot::Histogram(stream) => stream.decompress_with(|values| {
recorder.record_histogram(owned_key.clone(), values);
recorder.record_histogram(key.clone(), values);
}),
}
}
@ -41,44 +48,42 @@ mod tests {
#[derive(Default)]
struct MockRecorder {
counter: HashMap<String, u64>,
gauge: HashMap<String, i64>,
histogram: HashMap<String, Vec<u64>>,
counter: HashMap<Key, u64>,
gauge: HashMap<Key, i64>,
histogram: HashMap<Key, Vec<u64>>,
}
impl MockRecorder {
pub fn get_counter_value(&self, key: &String) -> Option<&u64> {
pub fn get_counter_value(&self, key: &Key) -> Option<&u64> {
self.counter.get(key)
}
pub fn get_gauge_value(&self, key: &String) -> Option<&i64> {
pub fn get_gauge_value(&self, key: &Key) -> Option<&i64> {
self.gauge.get(key)
}
pub fn get_histogram_values(&self, key: &String) -> Option<&Vec<u64>> {
pub fn get_histogram_values(&self, key: &Key) -> Option<&Vec<u64>> {
self.histogram.get(key)
}
}
impl Recorder for MockRecorder {
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64) {
let _ = self.counter.insert(key.into().to_string(), value);
fn record_counter(&mut self, key: Key, value: u64) {
let _ = self.counter.insert(key, value);
}
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64) {
let _ = self.gauge.insert(key.into().to_string(), value);
fn record_gauge(&mut self, key: Key, value: i64) {
let _ = self.gauge.insert(key, value);
}
fn record_histogram<K: Into<Key>>(&mut self, key: K, values: &[u64]) {
let _ = self
.histogram
.insert(key.into().to_string(), values.to_vec());
fn record_histogram(&mut self, key: Key, values: &[u64]) {
let _ = self.histogram.insert(key, values.to_vec());
}
}
#[test]
fn test_snapshot_recorder() {
let key = "ok".to_owned();
let key = Key::from_name("ok");
let mut measurements = Vec::new();
measurements.push((key.clone(), ValueSnapshot::Counter(7)));
measurements.push((key.clone(), ValueSnapshot::Gauge(42)));
@ -88,7 +93,7 @@ mod tests {
stream.compress(&hvalues);
measurements.push((key.clone(), ValueSnapshot::Histogram(stream)));
let snapshot: Snapshot = Snapshot::from(measurements);
let snapshot = Snapshot::new(measurements);
let mut recorder = MockRecorder::default();
snapshot.record(&mut recorder);

262
metrics-runtime/src/lib.rs Normal file
View File

@ -0,0 +1,262 @@
//! High-speed metrics collection library.
//!
//! `metrics-runtime` provides a generalized metrics collection library targeted at users who want
//! to log metrics at high volume and high speed.
//!
//! # Design
//!
//! The library follows a pattern of "senders" and a "receiver."
//!
//! Callers create a [`Receiver`], which acts as a registry for all metrics that flow through it.
//! It allows creating new sinks as well as controllers, both necessary to push in and pull out
//! metrics from the system. It also manages background resources necessary for the registry to
//! operate.
//!
//! Once a [`Receiver`] is created, callers can either create a [`Sink`] for sending metrics, or a
//! [`Controller`] for getting metrics out.
//!
//! A [`Sink`] can be cheaply cloned, and offers convenience methods for getting the current time
//! as well as getting direct handles to a given metric. This allows users to either work with the
//! fuller API exposed by [`Sink`] or to take a compositional approach and embed fields that
//! represent each particular metric to be sent.
//!
//! A [`Controller`] provides both a synchronous and asynchronous snapshotting interface, which is
//! [`metrics-core`][metrics_core] compatible for exporting. This allows flexibility in
//! integration amongst traditional single-threaded or hand-rolled multi-threaded applications and
//! the emerging asynchronous Rust ecosystem.
//!
//! # Performance
//!
//! Users can expect to be able to send tens of millions of samples per second, with ingest
//! latencies at roughly 65-70ns at p50, and 250ns at p99. Depending on the workload -- counters
//! vs histograms -- latencies may be even lower, as counters and gauges are markedly faster to
//! update than histograms. Concurrent updates of the same metric will also cause natural
//! contention and lower the throughput/increase the latency of ingestion.
//!
//! # Metrics
//!
//! Counters, gauges, and histograms are supported, and follow the definitions outlined in
//! [`metrics-core`][metrics_core].
//!
//! Here's a simple example of creating a receiver and working with a sink:
//!
//! ```rust
//! # extern crate metrics_runtime;
//! use metrics_runtime::Receiver;
//! use std::{thread, time::Duration};
//! let receiver = Receiver::builder().build().expect("failed to create receiver");
//! let mut sink = receiver.get_sink();
//!
//! // We can update a counter. Counters are monotonic, unsigned integers that start at 0 and
//! // increase over time.
//! sink.record_counter("widgets", 5);
//!
//! // We can update a gauge. Gauges are signed, and hold on to the last value they were updated
//! // to, so you need to track the overall value on your own.
//! sink.record_gauge("red_balloons", 99);
//!
//! // We can update a timing histogram. For timing, we're using the built-in `Sink::now` method
//! // which utilizes a high-speed internal clock. This method returns the time in nanoseconds, so
//! // we get great resolution, but giving the time in nanoseconds isn't required! If you want to
//! // send it in another unit, that's fine, but just pay attention to that fact when viewing and
//! // using those metrics once exported. We also support passing `Instant` values -- both `start`
//! // and `end` need to be the same type, though! -- and we'll take the nanosecond output of that.
//! let start = sink.now();
//! thread::sleep(Duration::from_millis(10));
//! let end = sink.now();
//! sink.record_timing("db.queries.select_products_ns", start, end);
//!
//! // Finally, we can update a value histogram. Technically speaking, value histograms aren't
//! // fundamentally different from timing histograms. If you use a timing histogram, we do the
//! // math for you of getting the time difference, but other than that, identical under the hood.
//! let row_count = 46;
//! sink.record_value("db.queries.select_products_num_rows", row_count);
//! ```
//!
//! # Scopes
//!
//! Metrics can be scoped, not unlike loggers, at the [`Sink`] level. This allows sinks to easily
//! nest themselves without callers ever needing to care about where they're located.
//!
//! This feature is a simpler approach to tagging: while not as semantically rich, it provides the
//! level of detail necessary to distinguish a single metric between multiple callsites.
//!
//! For example, after getting a [`Sink`] from the [`Receiver`], we can easily nest ourselves under
//! the root scope and then send some metrics:
//!
//! ```rust
//! # extern crate metrics_runtime;
//! use metrics_runtime::Receiver;
//! let receiver = Receiver::builder().build().expect("failed to create receiver");
//!
//! // This sink has no scope aka the root scope. The metric will just end up as "widgets".
//! let mut root_sink = receiver.get_sink();
//! root_sink.record_counter("widgets", 42);
//!
//! // This sink is under the "secret" scope. Since we derived ourselves from the root scope,
//! // we're not nested under anything, but our metric name will end up being "secret.widgets".
//! let mut scoped_sink = root_sink.scoped("secret");
//! scoped_sink.record_counter("widgets", 42);
//!
//! // This sink is under the "supersecret" scope, but we're also nested! The metric name for this
//! // sample will end up being "secret.supersecret.widget".
//! let mut scoped_sink_two = scoped_sink.scoped("supersecret");
//! scoped_sink_two.record_counter("widgets", 42);
//!
//! // Sinks retain their scope even when cloned, so the metric name will be the same as above.
//! let mut cloned_sink = scoped_sink_two.clone();
//! cloned_sink.record_counter("widgets", 42);
//!
//! // This sink will be nested two levels deeper than its parent by using a slightly different
//! // input scope: scope can be a single string, or multiple strings, which is interpreted as
//! // nesting N levels deep.
//! //
//! // This metric name will end up being "super.secret.ultra.special.widgets".
//! let mut scoped_sink_three = scoped_sink.scoped(&["super", "secret", "ultra", "special"]);
//! scoped_sink_two.record_counter("widgets", 42);
//! ```
//!
//! # Labels
//!
//! On top of scope support, metrics can also have labels. If scopes are for organizing metrics in
//! a hierarchy, then labels are for differentiating the same metric being emitted from multiple
//! sources.
//!
//! This is most easily demonstrated with an example:
//!
//! ```rust
//! # extern crate metrics_runtime;
//! # fn run_query(_: &str) -> u64 { 42 }
//! use metrics_runtime::Receiver;
//! let receiver = Receiver::builder().build().expect("failed to create receiver");
//!
//! let mut sink = receiver.get_sink();
//!
//! // We might have a function that interacts with a database and returns the number of rows it
//! // touched in doing so.
//! fn process_query(query: &str) -> u64 {
//! run_query(query)
//! }
//!
//! // We might call this function multiple times, but hitting different tables.
//! let rows_a = process_query("UPDATE posts SET public = 1 WHERE public = 0");
//! let rows_b = process_query("UPDATE comments SET public = 1 WHERE public = 0");
//!
//! // Now, we want to track a metric that shows how many rows are updated overall, so the metric
//! // name should be the same no matter which table we update, but we'd also like to be able to
//! // differentiate by table, too!
//! sink.record_value_with_labels("db.rows_updated", rows_a, &[("table", "posts")]);
//! sink.record_value_with_labels("db.rows_updated", rows_b, &[("table", "comments")]);
//!
//! // If you want to send a specific set of labels with every metric from this sink, you can also
//! // add default labels. This action is additive, so you can call it multiple times to build up
//! // the set of labels sent with metrics, and labels are inherited when creating a scoped sink or
//! // cloning an existing sink, which allows label usage to either supplement scopes or to
//! // potentially replace them entirely.
//! sink.add_default_labels(&[("database", "primary")]);
//! # fn main() {}
//! ```
//!
//! As shown in the example, labels allow a user to submit values to the underlying metric name,
//! while also differentiating between unique situations, whatever the facet that the user decides
//! to utilize.
//!
//! Naturally, these methods can be slightly cumbersome and visually detracting, in which case
//! you can utilize the proxy metric types -- [`Counter`], [`Gauge`], and [`Histogram`] -- and
//! create them with labels ahead of time. These types, by their nature, are bound to a specific
//! metric, which encompasses name, scope, and labels, and so extra labels cannot be passed when
//! actually updating them.
//!
//! # Snapshots
//!
//! Naturally, we need a way to get the metrics out of the system, which is where snapshots come
//! into play. By utilizing a [`Controller`], we can take a snapshot of the current metrics in the
//! registry, and then output them to any desired system/interface by utilizing
//! [`Recorder`](metrics_core::Recorder). A number of pre-baked recorders (which only concern
//! themselves with formatting the data) and exporters (which take the formatted data and either
//! serve it up, such as exposing an HTTP endpoint, or write it somewhere, like stdout) are
//! available, some of which are exposed by this crate.
//!
//! Let's take an example of writing out our metrics in a yaml-like format, writing them via
//! `log!`:
//! ```rust
//! # extern crate metrics_runtime;
//! use metrics_runtime::{Receiver, recorders::TextRecorder, exporters::LogExporter};
//! use log::Level;
//! use std::{thread, time::Duration};
//! let receiver = Receiver::builder().build().expect("failed to create receiver");
//! let mut sink = receiver.get_sink();
//!
//! // We can update a counter. Counters are monotonic, unsigned integers that start at 0 and
//! // increase over time.
//! // Take some measurements, similar to what we had in other examples:
//! sink.record_counter("widgets", 5);
//! sink.record_gauge("red_balloons", 99);
//!
//! let start = sink.now();
//! thread::sleep(Duration::from_millis(10));
//! let end = sink.now();
//! sink.record_timing("db.queries.select_products_ns", start, end);
//! sink.record_timing("db.gizmo_query", start, end);
//!
//! let num_rows = 46;
//! sink.record_value("db.queries.select_products_num_rows", num_rows);
//!
//! // Now create our exporter/recorder configuration, and wire it up.
//! let exporter = LogExporter::new(receiver.get_controller(), TextRecorder::new(), Level::Info);
//!
//! // This exporter will now run every 5 seconds, taking a snapshot, rendering it, and writing it
//! // via `log!` at the informational level. This particular exporter is running directly on the
//! // current thread, and not on a background thread.
//! //
//! // exporter.run(Duration::from_secs(5));
//! ```
//! Most exporters have the ability to run on the current thread or to be converted into a future
//! which can be spawned on any Tokio-compatible runtime.
//!
//! # Facade
//!
//! `metrics-runtime` is `metrics` compatible, and can be installed as the global metrics facade:
//! ```
//! # #[macro_use] extern crate metrics;
//! extern crate metrics_runtime;
//! use metrics_runtime::Receiver;
//!
//! Receiver::builder()
//! .build()
//! .expect("failed to create receiver")
//! .install();
//!
//! counter!("items_processed", 42);
//! ```
//!
//! [metrics_core]: https://docs.rs/metrics-core
//! [`Recorder`]: https://docs.rs/metrics-core/0.3.1/metrics_core/trait.Recorder.html
#![deny(missing_docs)]
#![warn(unused_extern_crates)]
mod builder;
mod common;
mod config;
mod control;
pub mod data;
mod helper;
mod receiver;
mod registry;
mod sink;
#[cfg(any(feature = "metrics-exporter-log", feature = "metrics-exporter-http"))]
pub mod exporters;
#[cfg(any(
feature = "metrics-recorder-text",
feature = "metrics-recorder-prometheus"
))]
pub mod recorders;
pub use self::{
builder::{Builder, BuilderError},
common::{Delta, Scope},
control::{Controller, SnapshotError},
receiver::Receiver,
sink::{AsScoped, Sink, SinkError},
};

View File

@ -1,13 +1,13 @@
use crate::{
builder::{Builder, BuilderError},
common::MetricScope,
common::Scope,
config::Configuration,
control::Controller,
registry::{MetricRegistry, ScopeRegistry},
sink::Sink,
};
use metrics::Recorder;
use metrics_core::Key;
use metrics_facade::Recorder;
use quanta::{Builder as UpkeepBuilder, Clock, Handle as UpkeepHandle};
use std::cell::RefCell;
use std::sync::Arc;
@ -61,7 +61,7 @@ impl Receiver {
/// Installs this receiver as the global metrics facade.
pub fn install(self) {
metrics_facade::set_boxed_recorder(Box::new(self)).unwrap();
metrics::set_boxed_recorder(Box::new(self)).unwrap();
}
/// Creates a [`Sink`] bound to this receiver.
@ -69,7 +69,7 @@ impl Receiver {
Sink::new(
self.metric_registry.clone(),
self.scope_registry.clone(),
MetricScope::Root,
Scope::Root,
self.clock.clone(),
)
}
@ -89,7 +89,7 @@ impl Recorder for Receiver {
*sink = Some(new_sink);
}
sink.as_mut().unwrap().record_count(key, value);
sink.as_mut().unwrap().record_counter(key, value);
});
}

View File

@ -1,4 +1,4 @@
use crate::common::{MetricIdentifier, MetricKind, MetricValue};
use crate::common::{Identifier, Kind, ValueHandle};
use crate::config::Configuration;
use crate::data::Snapshot;
use crate::registry::ScopeRegistry;
@ -8,9 +8,10 @@ use quanta::Clock;
use std::ops::Deref;
use std::sync::Arc;
#[derive(Debug)]
pub(crate) struct MetricRegistry {
scope_registry: Arc<ScopeRegistry>,
metrics: ArcSwap<HashMap<MetricIdentifier, MetricValue>>,
metrics: ArcSwap<HashMap<Identifier, ValueHandle>>,
config: Configuration,
clock: Clock,
}
@ -25,19 +26,15 @@ impl MetricRegistry {
}
}
pub fn get_value_handle(&self, identifier: MetricIdentifier) -> MetricValue {
pub fn get_or_register(&self, id: Identifier) -> ValueHandle {
loop {
match self.metrics.lease().deref().get(&identifier) {
match self.metrics.lease().deref().get(&id) {
Some(handle) => return handle.clone(),
None => {
let kind = match &identifier {
MetricIdentifier::Unlabeled(_, _, kind) => kind,
};
let value_handle = match kind {
MetricKind::Counter => MetricValue::counter(),
MetricKind::Gauge => MetricValue::gauge(),
MetricKind::Histogram => MetricValue::histogram(
let value_handle = match id.kind() {
Kind::Counter => ValueHandle::counter(),
Kind::Gauge => ValueHandle::gauge(),
Kind::Histogram => ValueHandle::histogram(
self.config.histogram_window,
self.config.histogram_granularity,
self.clock.clone(),
@ -46,7 +43,7 @@ impl MetricRegistry {
let metrics_ptr = self.metrics.lease();
let mut metrics = metrics_ptr.deref().clone();
match metrics.insert(identifier.clone(), value_handle.clone()) {
match metrics.insert(id.clone(), value_handle.clone()) {
// Somebody else beat us to it, loop.
Some(_) => continue,
None => {
@ -70,17 +67,15 @@ impl MetricRegistry {
let mut named_values = Vec::new();
let metrics = self.metrics.load().deref().clone();
for (identifier, value) in metrics.into_iter() {
let (name, scope_handle) = match identifier {
MetricIdentifier::Unlabeled(name, scope, _) => (name, scope),
};
for (id, value) in metrics.into_iter() {
let (key, scope_handle, _) = id.into_parts();
let scope = self.scope_registry.get(scope_handle);
let scoped_name = scope.into_scoped(name);
let key = key.map_name(|name| scope.into_scoped(name));
let snapshot = value.snapshot();
named_values.push((scoped_name, snapshot));
named_values.push((key, snapshot));
}
Snapshot::from(named_values)
Snapshot::new(named_values)
}
}

View File

@ -1,11 +1,12 @@
use crate::common::{MetricScope, MetricScopeHandle};
use crate::common::{Scope, ScopeHandle};
use hashbrown::HashMap;
use parking_lot::RwLock;
use std::collections::HashMap;
#[derive(Debug)]
struct Inner {
id: u64,
forward: HashMap<MetricScope, MetricScopeHandle>,
backward: HashMap<MetricScopeHandle, MetricScope>,
forward: HashMap<Scope, ScopeHandle>,
backward: HashMap<ScopeHandle, Scope>,
}
impl Inner {
@ -18,6 +19,7 @@ impl Inner {
}
}
#[derive(Debug)]
pub(crate) struct ScopeRegistry {
inner: RwLock<Inner>,
}
@ -29,7 +31,7 @@ impl ScopeRegistry {
}
}
pub fn register(&self, scope: MetricScope) -> u64 {
pub fn register(&self, scope: Scope) -> u64 {
let mut wg = self.inner.write();
// If the key is already registered, send back the existing scope ID.
@ -46,12 +48,9 @@ impl ScopeRegistry {
scope_id
}
pub fn get(&self, scope_id: MetricScopeHandle) -> MetricScope {
pub fn get(&self, scope_id: ScopeHandle) -> Scope {
// See if we have an entry for the scope ID, and clone the scope if so.
let rg = self.inner.read();
rg.backward
.get(&scope_id)
.cloned()
.unwrap_or(MetricScope::Root)
rg.backward.get(&scope_id).cloned().unwrap_or(Scope::Root)
}
}

View File

@ -1,5 +1,5 @@
use parking_lot::RwLock;
use std::collections::HashMap;
use hashbrown::HashMap;
pub struct Inner {
id: u64,

656
metrics-runtime/src/sink.rs Normal file
View File

@ -0,0 +1,656 @@
use crate::{
common::{Delta, Identifier, Kind, Scope, ScopeHandle, ValueHandle},
data::{Counter, Gauge, Histogram},
registry::{MetricRegistry, ScopeRegistry},
};
use hashbrown::HashMap;
use metrics_core::{IntoLabels, Key, Label, ScopedString};
use quanta::Clock;
use std::error::Error;
use std::fmt;
use std::sync::Arc;
/// Errors during sink creation.
#[derive(Debug, Clone)]
pub enum SinkError {
/// The scope value given was invalid i.e. empty or illegal characters.
InvalidScope,
}
impl Error for SinkError {}
impl fmt::Display for SinkError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SinkError::InvalidScope => write!(f, "given scope is invalid"),
}
}
}
/// A value that can be used as a metric scope.
///
/// This helper trait allows us to accept either a single string or a slice of strings to use as a
/// scope, to avoid needing to allocate in the case where we want to be able to specify multiple
/// scope levels in a single go.
pub trait AsScoped<'a> {
/// Creates a new [`Scope`] by adding `self` to the `base` scope.
fn as_scoped(&'a self, base: Scope) -> Scope;
}
/// Handle for sending metric samples.
#[derive(Debug)]
pub struct Sink {
metric_registry: Arc<MetricRegistry>,
metric_cache: HashMap<Identifier, ValueHandle>,
scope_registry: Arc<ScopeRegistry>,
scope: Scope,
scope_handle: ScopeHandle,
clock: Clock,
default_labels: Vec<Label>,
}
impl Sink {
pub(crate) fn new(
metric_registry: Arc<MetricRegistry>,
scope_registry: Arc<ScopeRegistry>,
scope: Scope,
clock: Clock,
) -> Sink {
let scope_handle = scope_registry.register(scope.clone());
Sink {
metric_registry,
metric_cache: HashMap::default(),
scope_registry,
scope,
scope_handle,
clock,
default_labels: Vec::new(),
}
}
/// Adds default labels for this sink and any derived sinks.
///
/// Default labels are added to all metrics. If a metric is updated and requested and it has
/// its own labels specified, the default labels will be appended to the existing labels.
///
/// Labels are passed on, with scope, to any scoped children or cloned sinks.
pub fn add_default_labels<L>(&mut self, labels: L)
where
L: IntoLabels,
{
let labels = labels.into_labels();
self.default_labels.extend(labels);
}
/// Creates a scoped clone of this [`Sink`].
///
/// Scoping controls the resulting metric name for any metrics sent by this [`Sink`]. For
/// example, you might have a metric called `messages_sent`.
///
/// With scoping, you could have independent versions of the same metric. This is useful for
/// having the same "base" metric name but with broken down values.
///
/// Going further with the above example, if you had a server, and listened on multiple
/// addresses, maybe you would have a scoped [`Sink`] per listener, and could end up with
/// metrics that look like this:
/// - `listener.a.messages_sent`
/// - `listener.b.messages_sent`
/// - `listener.c.messages_sent`
/// - etc
///
/// Scopes are also inherited. If you create a scoped [`Sink`] from another [`Sink`] which is
/// already scoped, the scopes will be merged together using a `.` as the string separator.
/// This makes it easy to nest scopes. Cloning a scoped [`Sink`], though, will inherit the
/// same scope as the original.
pub fn scoped<'a, S: AsScoped<'a> + ?Sized>(&self, scope: &'a S) -> Sink {
let new_scope = scope.as_scoped(self.scope.clone());
let mut sink = Sink::new(
self.metric_registry.clone(),
self.scope_registry.clone(),
new_scope,
self.clock.clone(),
);
if !self.default_labels.is_empty() {
sink.add_default_labels(self.default_labels.clone());
}
sink
}
/// Gets the current time, in nanoseconds, from the internal high-speed clock.
pub fn now(&self) -> u64 {
self.clock.now()
}
/// Records a value for a counter identified by the given name.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// sink.record_counter("messages_processed", 1);
/// # }
/// ```
pub fn record_counter<N>(&mut self, name: N, value: u64)
where
N: Into<Key>,
{
let key = self.construct_key(name);
let id = Identifier::new(key, self.scope_handle, Kind::Counter);
let value_handle = self.get_cached_value_handle(id);
value_handle.update_counter(value);
}
/// Records a value for a counter identified by the given name and labels.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// sink.record_counter_with_labels("messages_processed", 1, &[("message_type", "mgmt")]);
/// # }
/// ```
pub fn record_counter_with_labels<N, L>(&mut self, name: N, value: u64, labels: L)
where
N: Into<ScopedString>,
L: IntoLabels,
{
let key = self.construct_key((name, labels));
let id = Identifier::new(key, self.scope_handle, Kind::Counter);
let value_handle = self.get_cached_value_handle(id);
value_handle.update_counter(value);
}
/// Records a value for a gauge identified by the given name.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// sink.record_gauge("current_offset", -131);
/// # }
/// ```
pub fn record_gauge<N>(&mut self, name: N, value: i64)
where
N: Into<Key>,
{
let key = self.construct_key(name);
let id = Identifier::new(key, self.scope_handle, Kind::Gauge);
let value_handle = self.get_cached_value_handle(id);
value_handle.update_gauge(value);
}
/// Records a value for a gauge identified by the given name and labels.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// sink.record_gauge_with_labels("current_offset", -131, &[("source", "stratum-1")]);
/// # }
/// ```
pub fn record_gauge_with_labels<N, L>(&mut self, name: N, value: i64, labels: L)
where
N: Into<ScopedString>,
L: IntoLabels,
{
let key = self.construct_key((name, labels));
let id = Identifier::new(key, self.scope_handle, Kind::Gauge);
let value_handle = self.get_cached_value_handle(id);
value_handle.update_gauge(value);
}
/// Records the value for a timing histogram identified by the given name.
///
/// Both the start and end times must be supplied, but any values that implement [`Delta`] can
/// be used which allows for raw values from [`quanta::Clock`] to be used, or measurements from
/// [`Instant::now`].
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # use std::thread;
/// # use std::time::Duration;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// let start = sink.now();
/// thread::sleep(Duration::from_millis(10));
/// let end = sink.now();
/// sink.record_timing("sleep_time", start, end);
/// # }
/// ```
pub fn record_timing<N, V>(&mut self, name: N, start: V, end: V)
where
N: Into<Key>,
V: Delta,
{
let delta = end.delta(start);
self.record_value(name, delta);
}
/// Records the value for a timing histogram identified by the given name and labels.
///
/// Both the start and end times must be supplied, but any values that implement [`Delta`] can
/// be used which allows for raw values from [`quanta::Clock`] to be used, or measurements from
/// [`Instant::now`].
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # use std::thread;
/// # use std::time::Duration;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// let start = sink.now();
/// thread::sleep(Duration::from_millis(10));
/// let end = sink.now();
/// sink.record_timing_with_labels("sleep_time", start, end, &[("mode", "low_priority")]);
/// # }
/// ```
pub fn record_timing_with_labels<N, L, V>(&mut self, name: N, start: V, end: V, labels: L)
where
N: Into<ScopedString>,
L: IntoLabels,
V: Delta,
{
let delta = end.delta(start);
self.record_value_with_labels(name, delta, labels);
}
/// Records the value for a value histogram identified by the given name.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # use std::thread;
/// # use std::time::Duration;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// sink.record_value("rows_returned", 42);
/// # }
/// ```
pub fn record_value<N>(&mut self, name: N, value: u64)
where
N: Into<Key>,
{
let key = self.construct_key(name);
let id = Identifier::new(key, self.scope_handle, Kind::Histogram);
let value_handle = self.get_cached_value_handle(id);
value_handle.update_histogram(value);
}
/// Records the value for a value histogram identified by the given name and labels.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # use std::thread;
/// # use std::time::Duration;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// sink.record_value_with_labels("rows_returned", 42, &[("table", "posts")]);
/// # }
/// ```
pub fn record_value_with_labels<N, L>(&mut self, name: N, value: u64, labels: L)
where
N: Into<ScopedString>,
L: IntoLabels,
{
let key = self.construct_key((name, labels));
let id = Identifier::new(key, self.scope_handle, Kind::Histogram);
let value_handle = self.get_cached_value_handle(id);
value_handle.update_histogram(value);
}
/// Creates a handle to the given counter.
///
/// This handle can be embedded into an existing type and used to directly update the
/// underlying counter. It is merely a proxy, so multiple handles to the same counter can be
/// held and used.
///`
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// let counter = sink.counter("messages_processed");
/// counter.record(1);
///
/// // Alternate, simpler usage:
/// counter.increment();
/// # }
/// ```
pub fn counter<N>(&mut self, name: N) -> Counter
where
N: Into<Key>,
{
let key = self.construct_key(name);
self.get_owned_value_handle(key, Kind::Counter).into()
}
/// Creates a handle to the given counter, with labels attached.
///
/// This handle can be embedded into an existing type and used to directly update the
/// underlying counter. It is merely a proxy, so multiple handles to the same counter can be
/// held and used.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// let counter = sink.counter_with_labels("messages_processed", &[("service", "secure")]);
/// counter.record(1);
///
/// // Alternate, simpler usage:
/// counter.increment();
/// # }
/// ```
pub fn counter_with_labels<N, L>(&mut self, name: N, labels: L) -> Counter
where
N: Into<ScopedString>,
L: IntoLabels,
{
let key = self.construct_key((name, labels));
self.get_owned_value_handle(key, Kind::Counter).into()
}
/// Creates a handle to the given gauge.
///
/// This handle can be embedded into an existing type and used to directly update the
/// underlying gauge. It is merely a proxy, so multiple handles to the same gauge can be
/// held and used.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// let gauge = sink.gauge("current_offset");
/// gauge.record(-131);
/// # }
/// ```
pub fn gauge<N>(&mut self, name: N) -> Gauge
where
N: Into<Key>,
{
let key = self.construct_key(name);
self.get_owned_value_handle(key, Kind::Gauge).into()
}
/// Creates a handle to the given gauge.
///
/// This handle can be embedded into an existing type and used to directly update the
/// underlying gauge. It is merely a proxy, so multiple handles to the same gauge can be
/// held and used.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// let gauge = sink.gauge_with_labels("current_offset", &[("source", "stratum-1")]);
/// gauge.record(-131);
/// # }
/// ```
pub fn gauge_with_labels<N, L>(&mut self, name: N, labels: L) -> Gauge
where
N: Into<ScopedString>,
L: IntoLabels,
{
let key = self.construct_key((name, labels));
self.get_owned_value_handle(key, Kind::Gauge).into()
}
/// Creates a handle to the given histogram.
///
/// This handle can be embedded into an existing type and used to directly update the
/// underlying histogram. It is merely a proxy, so multiple handles to the same histogram
/// can be held and used.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # use std::thread;
/// # use std::time::Duration;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// let histogram = sink.histogram("request_duration");
///
/// let start = sink.now();
/// thread::sleep(Duration::from_millis(10));
/// let end = sink.now();
/// histogram.record_timing(start, end);
///
/// // Alternatively, you can just push the raw value into a histogram:
/// let delta = end - start;
/// histogram.record_value(delta);
/// # }
/// ```
pub fn histogram<N>(&mut self, name: N) -> Histogram
where
N: Into<Key>,
{
let key = self.construct_key(name);
self.get_owned_value_handle(key, Kind::Histogram).into()
}
/// Creates a handle to the given histogram.
///
/// This handle can be embedded into an existing type and used to directly update the
/// underlying histogram. It is merely a proxy, so multiple handles to the same histogram
/// can be held and used.
///
/// # Examples
///
/// ```rust
/// # extern crate metrics_runtime;
/// # use metrics_runtime::Receiver;
/// # use std::thread;
/// # use std::time::Duration;
/// # fn main() {
/// let receiver = Receiver::builder().build().expect("failed to create receiver");
/// let mut sink = receiver.get_sink();
/// let histogram = sink.histogram_with_labels("request_duration", &[("service", "secure")]);
///
/// let start = sink.now();
/// thread::sleep(Duration::from_millis(10));
/// let end = sink.now();
/// histogram.record_timing(start, end);
///
/// // Alternatively, you can just push the raw value into a histogram:
/// let delta = end - start;
/// histogram.record_value(delta);
/// # }
/// ```
pub fn histogram_with_labels<N, L>(&mut self, name: N, labels: L) -> Histogram
where
N: Into<ScopedString>,
L: IntoLabels,
{
let key = self.construct_key((name, labels));
self.get_owned_value_handle(key, Kind::Histogram).into()
}
pub(crate) fn construct_key<K>(&self, key: K) -> Key
where
K: Into<Key>,
{
let mut key = key.into();
if !self.default_labels.is_empty() {
key.add_labels(self.default_labels.clone());
}
key
}
fn get_owned_value_handle<K>(&mut self, key: K, kind: Kind) -> ValueHandle
where
K: Into<Key>,
{
let id = Identifier::new(key.into(), self.scope_handle, kind);
self.get_cached_value_handle(id).clone()
}
fn get_cached_value_handle(&mut self, identifier: Identifier) -> &ValueHandle {
// This gross hack gets around lifetime rules until full NLL is stable. Without it, the
// borrow checker doesn't understand the flow control and thinks the reference lives all
// the way until the of the function, which breaks when we try to take a mutable reference
// for inserting into the handle cache.
if let Some(handle) = self.metric_cache.get(&identifier) {
return unsafe { &*(handle as *const ValueHandle) };
}
let handle = self.metric_registry.get_or_register(identifier.clone());
self.metric_cache.insert(identifier.clone(), handle);
self.metric_cache.get(&identifier).unwrap()
}
}
impl Clone for Sink {
fn clone(&self) -> Sink {
Sink {
metric_registry: self.metric_registry.clone(),
metric_cache: self.metric_cache.clone(),
scope_registry: self.scope_registry.clone(),
scope: self.scope.clone(),
scope_handle: self.scope_handle,
clock: self.clock.clone(),
default_labels: self.default_labels.clone(),
}
}
}
impl<'a> AsScoped<'a> for str {
fn as_scoped(&'a self, base: Scope) -> Scope {
match base {
Scope::Root => {
let parts = vec![self.to_owned()];
Scope::Nested(parts)
}
Scope::Nested(mut parts) => {
parts.push(self.to_owned());
Scope::Nested(parts)
}
}
}
}
impl<'a, 'b, T> AsScoped<'a> for T
where
&'a T: AsRef<[&'b str]>,
T: 'a,
{
fn as_scoped(&'a self, base: Scope) -> Scope {
match base {
Scope::Root => {
let parts = self.as_ref().iter().map(|s| s.to_string()).collect();
Scope::Nested(parts)
}
Scope::Nested(mut parts) => {
let mut new_parts = self.as_ref().iter().map(|s| s.to_string()).collect();
parts.append(&mut new_parts);
Scope::Nested(parts)
}
}
}
}
#[cfg(test)]
mod tests {
use super::{Clock, MetricRegistry, Scope, ScopeRegistry, Sink};
use crate::config::Configuration;
use std::sync::Arc;
#[test]
fn test_construct_key() {
// TODO(tobz): this is a lot of boilerplate to get a `Sink` for testing, wonder if there's
// anything better we could be doing?
let sregistry = Arc::new(ScopeRegistry::new());
let config = Configuration::mock();
let (clock, _) = Clock::mock();
let mregistry = Arc::new(MetricRegistry::new(
sregistry.clone(),
config,
clock.clone(),
));
let mut sink = Sink::new(mregistry, sregistry, Scope::Root, clock);
let no_labels = sink.construct_key("foo");
assert_eq!(no_labels.name(), "foo");
assert_eq!(no_labels.labels().count(), 0);
let labels_given = sink.construct_key(("baz", &[("type", "test")]));
assert_eq!(labels_given.name(), "baz");
let label_str = labels_given
.labels()
.map(|l| format!("{}={}", l.key(), l.value()))
.collect::<Vec<_>>()
.join(",");
assert_eq!(label_str, "type=test");
sink.add_default_labels(&[(("service", "foo"))]);
let no_labels = sink.construct_key("bar");
assert_eq!(no_labels.name(), "bar");
let label_str = no_labels
.labels()
.map(|l| format!("{}={}", l.key(), l.value()))
.collect::<Vec<_>>()
.join(",");
assert_eq!(label_str, "service=foo");
let labels_given = sink.construct_key(("quux", &[("type", "test")]));
assert_eq!(labels_given.name(), "quux");
let label_str = labels_given
.labels()
.map(|l| format!("{}={}", l.key(), l.value()))
.collect::<Vec<_>>()
.join(",");
assert_eq!(label_str, "type=test,service=foo");
}
}

View File

@ -9,7 +9,7 @@ license = "MIT"
description = "helper types/functions used by the metrics ecosystem"
homepage = "https://github.com/metrics-rs/metrics"
repository = "https://github.com/metrics-rs/metrics-util"
repository = "https://github.com/metrics-rs/metrics"
documentation = "https://docs.rs/metrics-util"
readme = "README.md"

View File

@ -1,4 +1,5 @@
//! Helper types and functions used within the metrics ecosystem.
#![deny(missing_docs)]
mod bucket;
pub use bucket::AtomicBucket;

View File

@ -1,56 +1,32 @@
[package]
name = "metrics"
version = "0.10.3"
version = "0.11.0"
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
edition = "2018"
license = "MIT"
description = "high-speed metrics collection library"
homepage = "https://github.com/metrics-rs/metrics"
edition = "2018"
repository = "https://github.com/metrics-rs/metrics"
documentation = "https://docs.rs/metrics"
description = """
A lightweight metrics facade for Rust
"""
categories = ["development-tools::debugging"]
readme = "README.md"
keywords = ["metrics", "telemetry", "histogram", "counter", "gauge"]
[profile.release]
debug = true
opt-level = 3
lto = true
[features]
default = ["exporters", "recorders"]
exporters = ["metrics-exporter-log", "metrics-exporter-http"]
recorders = ["metrics-recorder-text", "metrics-recorder-prometheus"]
keywords = ["metrics", "facade"]
build = "build.rs"
[[bench]]
name = "histogram"
name = "macros"
harness = false
[dependencies]
metrics-core = { path = "../metrics-core", version = "^0.4" }
metrics-util = { path = "../metrics-util", version = "^0.2" }
metrics-facade = { path = "../metrics-facade", version = "^0.1", features = ["std"] }
im = "^12"
fxhash = "^0.2"
arc-swap = "^0.3"
parking_lot = "^0.8"
hashbrown = "^0.4"
quanta = "^0.3"
futures = "^0.1"
crossbeam-utils = "^0.6"
metrics-exporter-log = { path = "../metrics-exporter-log", version = "^0.2", optional = true }
metrics-exporter-http = { path = "../metrics-exporter-http", version = "^0.1", optional = true }
metrics-recorder-text = { path = "../metrics-recorder-text", version = "^0.2", optional = true }
metrics-recorder-prometheus = { path = "../metrics-recorder-prometheus", version = "^0.2", optional = true }
[dev-dependencies]
log = "^0.4"
env_logger = "^0.6"
getopts = "^0.2"
hdrhistogram = "^6.1"
criterion = "^0.2.9"
lazy_static = "^1.3"
criterion = "^0.2"
[package.metadata.docs.rs]
features = ["std"]
[features]
std = []

View File

@ -11,7 +11,7 @@
[crate]: https://crates.io/crates/metrics
[docs]: https://docs.rs/metrics
__metrics__ is a high-quality, batteries-included metrics library for Rust.
__metrics__ is a lightweight metrics facade.
## code of conduct
@ -19,26 +19,4 @@ __metrics__ is a high-quality, batteries-included metrics library for Rust.
# what's it all about?
Running applications in production can be hard when you don't have insight into what the application is doing. We're lucky to have so many good system monitoring programs and services to show us how are servers are performing, but we still have to do the work of instrumenting our applications to gain deep insight into their behavior and performance.
`metrics` makes it easy to instrument your application to provide real-time insight into what's happening. It provides a straight-forward interface for you to collect metrics at different points, and a flexible approach to exporting those metrics in a way that meets your needs.
Some of the most common scenarios for collecting metrics from an application:
- see how many times a codepath was hit
- track the time it takes for a piece of code to execute
- expose internal counters and values in a standardized way
The number of reasons why you'd want to collect metrics is too large to list out here, and some applications emit metrics that have nothing to do with the application performance itself! Ultimately, `metrics` strives to simply provide support for the most basic types of metrics so that you can spend more time focusing on the data you'd like to collect and less time on how you're going to accomplish that.
## high-level technical features
- Supports the three most common metric types: counters, gauges, and histograms.
- Based on `metrics-core` for composability at the exporter level.
- Access to ultra-high-speed timing facilities out-of-the-box with [quanta](https://github.com/nuclearfurnace/quanta).
- Scoped metrics for effortless nesting.
- Bundled with Prometheus pull endpoint capabilities by default.
## performance
High. `metrics` is fast enough that you'll barely notice the overhead.
There is a `benchmark` example in the crate that can be run to see the type of performance acheivable on your system. A 2015 MacBook Pro (4c/8t, 2.1GHz) can push over 5 million samples per second from a single thread.
__metrics__ provides macros, similar to the [`log`](https://docs.rs/log) crate, that let library and executable authors instrument their code by collecting metrics -- incrementing counters, gauges, and histograms -- about their code, deferring the collecting and export of these metrics to whatever the installed metrics library is.

26
metrics/benches/macros.rs Normal file
View File

@ -0,0 +1,26 @@
#[macro_use]
extern crate criterion;
#[macro_use]
extern crate metrics;
use criterion::{Benchmark, Criterion};
fn macro_benchmark(c: &mut Criterion) {
c.bench(
"counter",
Benchmark::new("no labels", |b| {
b.iter(|| {
counter!("counter_bench", 42);
})
})
.with_function("with labels", |b| {
b.iter(|| {
counter!("counter_bench", 42, "request" => "http", "svc" => "admin");
})
}),
);
}
criterion_group!(benches, macro_benchmark);
criterion_main!(benches);

62
metrics/examples/basic.rs Normal file
View File

@ -0,0 +1,62 @@
#[macro_use]
extern crate metrics;
use metrics::Recorder;
use metrics_core::Key;
#[allow(dead_code)]
static RECORDER: PrintRecorder = PrintRecorder;
#[derive(Default)]
struct PrintRecorder;
impl Recorder for PrintRecorder {
fn record_counter(&self, key: Key, value: u64) {
println!("metrics -> counter(name={}, value={})", key, value);
}
fn record_gauge(&self, key: Key, value: i64) {
println!("metrics -> gauge(name={}, value={})", key, value);
}
fn record_histogram(&self, key: Key, value: u64) {
println!("metrics -> histogram(name={}, value={})", key, value);
}
}
#[cfg(feature = "std")]
fn init_print_logger() {
let recorder = PrintRecorder::default();
metrics::set_boxed_recorder(Box::new(recorder)).unwrap()
}
#[cfg(not(feature = "std"))]
fn init_print_logger() {
metrics::set_recorder(&RECORDER).unwrap()
}
fn main() {
let server_name = "web03".to_string();
init_print_logger();
counter!("requests_processed", 1);
counter!("requests_processed", 1, "request_type" => "admin");
counter!("requests_processed", 1, "request_type" => "admin", "server" => server_name.clone());
counter!("requests_processed", 1, "request_type" => "admin", "server" => server_name.clone(), "version" => "e7d6f12");
gauge!("connection_count", 300);
gauge!("connection_count", 300, "listener" => "frontend");
gauge!("connection_count", 300, "listener" => "frontend", "server" => server_name.clone());
gauge!("connection_count", 300, "listener" => "frontend", "server" => server_name.clone(), "version" => "e7d6f12");
timing!("service.execution_time", 120, 190);
timing!("service.execution_time", 120, 190, "type" => "users");
timing!("service.execution_time", 120, 190, "type" => "users", "server" => server_name.clone());
timing!("service.execution_time", 120, 190, "type" => "users", "server" => server_name.clone(), "version" => "e7d6f12");
timing!("service.execution_time", 70);
timing!("service.execution_time", 70, "type" => "users");
timing!("service.execution_time", 70, "type" => "users", "server" => server_name.clone());
timing!("service.execution_time", 70, "type" => "users", "server" => server_name.clone(), "version" => "e7d6f12");
value!("service.results_returned", 666);
value!("service.results_returned", 666, "type" => "users");
value!("service.results_returned", 666, "type" => "users", "server" => server_name.clone());
value!("service.results_returned", 666, "type" => "users", "server" => server_name.clone(), "version" => "e7d6f12");
}

View File

@ -1,19 +0,0 @@
use crate::common::MetricValue;
/// Proxy object to update a counter.
pub struct Counter {
handle: MetricValue,
}
impl Counter {
/// Records a value for the counter.
pub fn record(&self, value: u64) {
self.handle.update_counter(value);
}
}
impl From<MetricValue> for Counter {
fn from(handle: MetricValue) -> Self {
Self { handle }
}
}

View File

@ -1,211 +1,335 @@
//! High-speed metrics collection library.
//! A lightweight metrics facade.
//!
//! `metrics` provides a generalized metrics collection library targeted at users who want to log
//! metrics at high volume and high speed.
//! 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.
//!
//! # Design
//! If no metrics implementation is selected, the facade falls back to a "noop" implementation that
//! ignores all metrics. The overhead in this case is very small - an atomic load and comparison.
//!
//! The library follows a pattern of "senders" and a "receiver."
//! # Use
//! The basic use of the facade crate is through the four metrics macros: [`counter!`], [`gauge!`],
//! [`timing!`], and [`value!`]. These macros correspond to updating a counter, updating a gauge,
//! updating a histogram based on a start/end, and updating a histogram with a single value.
//!
//! Callers create a [`Receiver`], which acts as a registry for all metrics that flow through it.
//! It allows creating new sinks as well as controllers, both necessary to push in and pull out
//! metrics from the system. It also manages background resources necessary for the registry to
//! operate.
//! Both [`timing!`] and [`value!`] are effectively identical in so far as that they both translate
//! to recording a single value to an underlying histogram, but [`timing!`] is provided for
//! contextual consistency: if you're recording a measurement of the time passed during an
//! operation, the end result is a single value, but it's more of a "timing" value than just a
//! "value". The [`timing!`] macro also has a branch to accept the start and end values which
//! allows for a potentially clearer invocation.
//!
//! Once a [`Receiver`] is created, callers can either create a [`Sink`] for sending metrics, or a
//! [`Controller`] for getting metrics out.
//! ## In libraries
//! Libraries should link only to the `metrics` crate, and use the provided macros to record
//! whatever metrics will be useful to downstream consumers.
//!
//! A [`Sink`] can be cheaply cloned, and offers convenience methods for getting the current time
//! as well as getting direct handles to a given metric. This allows users to either work with the
//! fuller API exposed by [`Sink`] or to take a compositional approach and embed fields that
//! represent each particular metric to be sent.
//! ### Examples
//!
//! A [`Controller`] provides both a synchronous and asynchronous snapshotting interface, which is
//! [`metrics-core`][metrics_core] compatible for exporting. This allows flexibility in
//! integration amongst traditional single-threaded or hand-rolled multi-threaded applications and
//! the emerging asynchronous Rust ecosystem.
//!
//! # Performance
//!
//! Users can expect to be able to send tens of millions of samples per second, with ingest
//! latencies at roughly 65-70ns at p50, and 250ns at p99. Depending on the workload -- counters
//! vs histograms -- latencies may be even lower, as counters and gauges are markedly faster to
//! update than histograms. Concurrent updates of the same metric will also cause natural
//! contention and lower the throughput/increase the latency of ingestion.
//!
//! # Metrics
//!
//! Counters, gauges, and histograms are supported, and follow the definitions outlined in
//! [`metrics-core`][metrics_core].
//!
//! Here's a simple example of creating a receiver and working with a sink:
//!
//! ```
//! # extern crate metrics;
//! use metrics::Receiver;
//! use std::{thread, time::Duration};
//! let receiver = Receiver::builder().build().expect("failed to create receiver");
//! let mut sink = receiver.get_sink();
//!
//! // We can update a counter. Counters are monotonic, unsigned integers that start at 0 and
//! // increase over time.
//! sink.record_count("widgets", 5);
//!
//! // We can update a gauge. Gauges are signed, and hold on to the last value they were updated
//! // to, so you need to track the overall value on your own.
//! sink.record_gauge("red_balloons", 99);
//!
//! // We can update a timing histogram. For timing, we're using the built-in `Sink::now` method
//! // which utilizes a high-speed internal clock. This method returns the time in nanoseconds, so
//! // we get great resolution, but giving the time in nanoseconds isn't required! If you want to
//! // send it in another unit, that's fine, but just pay attention to that fact when viewing and
//! // using those metrics once exported. We also support passing `Instant` values -- both `start`
//! // and `end` need to be the same type, though! -- and we'll take the nanosecond output of that.
//! let start = sink.now();
//! thread::sleep(Duration::from_millis(10));
//! let end = sink.now();
//! sink.record_timing("db.queries.select_products_ns", start, end);
//!
//! // Finally, we can update a value histogram. Technically speaking, value histograms aren't
//! // fundamentally different from timing histograms. If you use a timing histogram, we do the
//! // math for you of getting the time difference, but other than that, identical under the hood.
//! let row_count = 46;
//! sink.record_value("db.queries.select_products_num_rows", row_count);
//! ```
//!
//! # Scopes
//!
//! Metrics can be scoped, not unlike loggers, at the [`Sink`] level. This allows sinks to easily
//! nest themselves without callers ever needing to care about where they're located.
//!
//! This feature is a simpler approach to tagging: while not as semantically rich, it provides the
//! level of detail necessary to distinguish a single metric between multiple callsites.
//!
//! For example, after getting a [`Sink`] from the [`Receiver`], we can easily nest ourselves under
//! the root scope and then send some metrics:
//!
//! ```
//! # extern crate metrics;
//! use metrics::Receiver;
//! let receiver = Receiver::builder().build().expect("failed to create receiver");
//!
//! // This sink has no scope aka the root scope. The metric will just end up as "widgets".
//! let mut root_sink = receiver.get_sink();
//! root_sink.record_count("widgets", 42);
//!
//! // This sink is under the "secret" scope. Since we derived ourselves from the root scope,
//! // we're not nested under anything, but our metric name will end up being "secret.widgets".
//! let mut scoped_sink = root_sink.scoped("secret");
//! scoped_sink.record_count("widgets", 42);
//!
//! // This sink is under the "supersecret" scope, but we're also nested! The metric name for this
//! // sample will end up being "secret.supersecret.widget".
//! let mut scoped_sink_two = scoped_sink.scoped("supersecret");
//! scoped_sink_two.record_count("widgets", 42);
//!
//! // Sinks retain their scope even when cloned, so the metric name will be the same as above.
//! let mut cloned_sink = scoped_sink_two.clone();
//! cloned_sink.record_count("widgets", 42);
//!
//! // This sink will be nested two levels deeper than its parent by using a slightly different
//! // input scope: scope can be a single string, or multiple strings, which is interpreted as
//! // nesting N levels deep.
//! //
//! // This metric name will end up being "super.secret.ultra.special.widgets".
//! let mut scoped_sink_three = scoped_sink.scoped(&["super", "secret", "ultra", "special"]);
//! scoped_sink_two.record_count("widgets", 42);
//! ```
//!
//! # Snapshots
//!
//! Naturally, we need a way to get the metrics out of the system, which is where snapshots come
//! into play. By utilizing a [`Controller`], we can take a snapshot of the current metrics in the
//! registry, and then output them to any desired system/interface by utilizing
//! [`Recorder`](metrics_core::Recorder). A number of pre-baked recorders (which only concern
//! themselves with formatting the data) and exporters (which take the formatted data and either
//! serve it up, such as exposing an HTTP endpoint, or write it somewhere, like stdout) are
//! available, some of which are exposed by this crate.
//!
//! Let's take an example of writing out our metrics in a yaml-like format, writing them via
//! `log!`:
//! ```
//! # extern crate metrics;
//! use metrics::{Receiver, recorders::TextRecorder, exporters::LogExporter};
//! use log::Level;
//! use std::{thread, time::Duration};
//! let receiver = Receiver::builder().build().expect("failed to create receiver");
//! let mut sink = receiver.get_sink();
//!
//! // We can update a counter. Counters are monotonic, unsigned integers that start at 0 and
//! // increase over time.
//! // Take some measurements, similar to what we had in other examples:
//! sink.record_count("widgets", 5);
//! sink.record_gauge("red_balloons", 99);
//!
//! let start = sink.now();
//! thread::sleep(Duration::from_millis(10));
//! let end = sink.now();
//! sink.record_timing("db.queries.select_products_ns", start, end);
//! sink.record_timing("db.gizmo_query", start, end);
//!
//! let num_rows = 46;
//! sink.record_value("db.queries.select_products_num_rows", num_rows);
//!
//! // Now create our exporter/recorder configuration, and wire it up.
//! let exporter = LogExporter::new(receiver.get_controller(), TextRecorder::new(), Level::Info);
//!
//! // This exporter will now run every 5 seconds, taking a snapshot, rendering it, and writing it
//! // via `log!` at the informational level. This particular exporter is running directly on the
//! // current thread, and not on a background thread.
//! //
//! // exporter.run(Duration::from_secs(5));
//! ```
//! Most exporters have the ability to run on the current thread or to be converted into a future
//! which can be spawned on any Tokio-compatible runtime.
//!
//! # Facade
//!
//! `metrics` is `metrics-facade` compatible, and can be installed as the global metrics facade:
//! ```
//! # #[macro_use] extern crate metrics_facade;
//! ```rust
//! #[macro_use]
//! extern crate metrics;
//! use metrics::Receiver;
//!
//! Receiver::builder()
//! .build()
//! .expect("failed to create receiver")
//! .install();
//! # 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 end = Instant::now();
//!
//! counter!("items_processed", 42);
//! timing!("process.query_time", start, end);
//! counter!("process.query_row_count", row_count);
//!
//! row_count
//! }
//! # fn main() {}
//! ```
//!
//! [metrics_core]: https://docs.rs/metrics-core
//! [`Recorder`]: https://docs.rs/metrics-core/0.3.1/metrics_core/trait.Recorder.html
//! ## In executables
//!
//! Executables should choose a metrics implementation and initialize it early in the runtime of
//! the program. Metrics implementations will typically include a function to do this. Any
//! metrics recordered before the implementation is initialized will be ignored.
//!
//! The executable itself may use the `metrics` crate to record metrics well.
//!
//! ### Warning
//!
//! The metrics system may only be initialized once.
//!
//! # Available metrics implementations
//!
//! Currently, the only available metrics implementation is [metricsi-runtime].
//!
//! # Implementing a Recorder
//!
//! Recorders implement the [`Recorder`] trait. Here's a basic example which writes the
//! metrics in text form via the `log` crate.
//!
//! ```rust
//! #[macro_use]
//! extern crate log;
//! extern crate metrics;
//! extern crate metrics_core;
//!
//! use metrics::Recorder;
//! use metrics_core::Key;
//!
//! struct LogRecorder;
//!
//! impl Recorder for LogRecorder {
//! fn record_counter(&self, key: Key, value: u64) {
//! info!("counter '{}' -> {}", key, value);
//! }
//!
//! fn record_gauge(&self, key: Key, value: i64) {
//! info!("gauge '{}' -> {}", key, value);
//! }
//!
//! fn record_histogram(&self, key: Key, value: u64) {
//! info!("histogram '{}' -> {}", key, value);
//! }
//! }
//! # fn main() {}
//! ```
//!
//! Recorders are installed by calling the [`set_recorder`] function. Recorders should provide a
//! function that wraps the creation and installation of the recorder:
//!
//! ```rust
//! # extern crate metrics;
//! # extern crate metrics_core;
//! # use metrics::Recorder;
//! # use metrics_core::Key;
//! # struct SimpleRecorder;
//! # impl Recorder for SimpleRecorder {
//! # fn record_counter(&self, _key: Key, _value: u64) {}
//! # fn record_gauge(&self, _key: Key, _value: i64) {}
//! # fn record_histogram(&self, _key: Key, _value: u64) {}
//! # }
//! use metrics::SetRecorderError;
//!
//! static RECORDER: SimpleRecorder = SimpleRecorder;
//!
//! pub fn init() -> Result<(), SetRecorderError> {
//! metrics::set_recorder(&RECORDER)
//! }
//! # fn main() {}
//! ```
//!
//! # Use with `std`
//!
//! `set_recorder` requires you to provide a `&'static Recorder`, which can be hard to
//! obtain if your recorder depends on some runtime configuration. The `set_boxed_recorder`
//! function is available with the `std` Cargo feature. It is identical to `set_recorder` except
//! that it takes a `Box<Recorder>` rather than a `&'static Recorder`:
//!
//! ```rust
//! # extern crate metrics;
//! # extern crate metrics_core;
//! # use metrics::Recorder;
//! # use metrics_core::Key;
//! # struct SimpleRecorder;
//! # impl Recorder for SimpleRecorder {
//! # fn record_counter(&self, _key: Key, _value: u64) {}
//! # fn record_gauge(&self, _key: Key, _value: i64) {}
//! # fn record_histogram(&self, _key: Key, _value: u64) {}
//! # }
//! use metrics::SetRecorderError;
//!
//! # #[cfg(feature = "std")]
//! pub fn init() -> Result<(), SetRecorderError> {
//! metrics::set_boxed_recorder(Box::new(SimpleRecorder))
//! }
//! # fn main() {}
//! ```
//!
//! [metrics-runtime]: https://docs.rs/metrics-runtime
#![deny(missing_docs)]
#![warn(unused_extern_crates)]
mod builder;
mod common;
mod config;
mod control;
pub mod data;
mod helper;
mod receiver;
mod registry;
mod sink;
use metrics_core::AsNanoseconds;
pub use metrics_core::{labels, Key, Label};
#[cfg(feature = "std")]
use std::error;
use std::fmt;
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(any(feature = "metrics-exporter-log", feature = "metrics-exporter-http"))]
pub mod exporters;
#[macro_use]
mod macros;
#[cfg(any(
feature = "metrics-recorder-text",
feature = "metrics-recorder-prometheus"
))]
pub mod recorders;
static mut RECORDER: &'static Recorder = &NoopRecorder;
static STATE: AtomicUsize = AtomicUsize::new(0);
pub use self::{
builder::{Builder, BuilderError},
common::{Delta, MetricName, MetricScope},
control::{Controller, SnapshotError},
receiver::Receiver,
sink::{AsScoped, Sink, SinkError},
};
const UNINITIALIZED: usize = 0;
const INITIALIZING: usize = 1;
const INITIALIZED: usize = 2;
static SET_RECORDER_ERROR: &'static str =
"attempted to set a recorder after the metrics system was already initialized";
/// A value that records metrics behind the facade.
pub trait Recorder {
/// Records a counter.
///
/// From the perspective of an recorder, a counter and gauge are essentially identical, insofar
/// as they are both a single value tied to a key. From the perspective of a collector,
/// counters and gauges usually have slightly different modes of operation.
///
/// For the sake of flexibility on the exporter side, both are provided.
fn record_counter(&self, key: Key, value: u64);
/// Records a gauge.
///
/// From the perspective of a recorder, a counter and gauge are essentially identical, insofar
/// as they are both a single value tied to a key. From the perspective of a collector,
/// counters and gauges usually have slightly different modes of operation.
///
/// For the sake of flexibility on the exporter side, both are provided.
fn record_gauge(&self, key: Key, value: i64);
/// Records a histogram.
///
/// Recorders are expected to tally their own histogram views, so this will be called with all
/// of the underlying observed values, and callers will need to process them accordingly.
///
/// There is no guarantee that this method will not be called multiple times for the same key.
fn record_histogram(&self, key: Key, value: u64);
}
struct NoopRecorder;
impl Recorder for NoopRecorder {
fn record_counter(&self, _key: Key, _value: u64) {}
fn record_gauge(&self, _key: Key, _value: i64) {}
fn record_histogram(&self, _key: Key, _value: u64) {}
}
/// Sets the global recorder to a `&'static Recorder`.
///
/// This function may only be called once in the lifetime of a program. Any metrics recorded
/// before the call to `set_recorder` occurs will be completely ignored.
///
/// This function does not typically need to be called manually. Metrics implementations should
/// provide an initialization method that installs the recorder internally.
///
/// # Errors
///
/// An error is returned if a recorder has already been set.
#[cfg(atomic_cas)]
pub fn set_recorder(recorder: &'static Recorder) -> Result<(), SetRecorderError> {
set_recorder_inner(|| recorder)
}
/// Sets the global recorder to a `Box<Recorder>`.
///
/// This is a simple convenience wrapper over `set_recorder`, which takes a `Box<Recorder>`
/// rather than a `&'static Recorder`. See the document for [`set_recorder`] for more
/// details.
///
/// Requires the `std` feature.
///
/// # Errors
///
/// An error is returned if a recorder has already been set.
#[cfg(all(feature = "std", atomic_cas))]
pub fn set_boxed_recorder(recorder: Box<Recorder>) -> Result<(), SetRecorderError> {
set_recorder_inner(|| unsafe { &*Box::into_raw(recorder) })
}
#[cfg(atomic_cas)]
fn set_recorder_inner<F>(make_recorder: F) -> Result<(), SetRecorderError>
where
F: FnOnce() -> &'static Recorder,
{
unsafe {
match STATE.compare_and_swap(UNINITIALIZED, INITIALIZING, Ordering::SeqCst) {
UNINITIALIZED => {
RECORDER = make_recorder();
STATE.store(INITIALIZED, Ordering::SeqCst);
Ok(())
}
INITIALIZING => {
while STATE.load(Ordering::SeqCst) == INITIALIZING {}
Err(SetRecorderError(()))
}
_ => Err(SetRecorderError(())),
}
}
}
/// A thread-unsafe version of [`set_recorder`].
///
/// This function is available on all platforms, even those that do not have support for atomics
/// that is need by [`set_recorder`].
///
/// In almost all cases, [`set_recorder`] should be preferred.
///
/// # Safety
///
/// This function is only safe to call when no other metrics initialization function is called
/// while this function still executes.
///
/// This can be upheld by (for example) making sure that **there are no other threads**, and (on
/// embedded) that **interrupts are disabled**.
///
/// It is safe to use other metrics functions while this function runs (including all metrics
/// macros).
pub unsafe fn set_recorder_racy(recorder: &'static Recorder) -> Result<(), SetRecorderError> {
match STATE.load(Ordering::SeqCst) {
UNINITIALIZED => {
RECORDER = recorder;
STATE.store(INITIALIZED, Ordering::SeqCst);
Ok(())
}
INITIALIZING => {
// This is just plain UB, since we were racing another initialization function
unreachable!("set_recorder_racy must not be used with other initialization functions")
}
_ => Err(SetRecorderError(())),
}
}
/// The type returned by [`set_recorder`] if [`set_recorder`] has already been called.
#[derive(Debug)]
pub struct SetRecorderError(());
impl fmt::Display for SetRecorderError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(SET_RECORDER_ERROR)
}
}
// The Error trait is not available in libcore
#[cfg(feature = "std")]
impl error::Error for SetRecorderError {
fn description(&self) -> &str {
SET_RECORDER_ERROR
}
}
/// Returns a reference to the recorder.
///
/// If a recorder has not been set, a no-op implementation is returned.
pub fn recorder() -> &'static Recorder {
unsafe {
if STATE.load(Ordering::SeqCst) != INITIALIZED {
static NOOP: NoopRecorder = NoopRecorder;
&NOOP
} else {
RECORDER
}
}
}
#[doc(hidden)]
pub fn __private_api_record_count(key: Key, value: u64) {
recorder().record_counter(key, value);
}
#[doc(hidden)]
pub fn __private_api_record_gauge<K: Into<Key>>(key: K, value: i64) {
recorder().record_gauge(key.into(), value);
}
#[doc(hidden)]
pub fn __private_api_record_histogram<K: Into<Key>, V: AsNanoseconds>(key: K, value: V) {
recorder().record_histogram(key.into(), value.as_nanos());
}

211
metrics/src/macros.rs Normal file
View File

@ -0,0 +1,211 @@
/// Records a counter.
///
/// Functionally equivalent to calling [`Recorder::record_counter`].
///
/// ### Examples
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics;
/// fn do_thing() {
/// let count: u64 = 42;
/// counter!("do_thing", count);
/// }
/// # fn main() {}
/// ```
///
/// Labels can also be passed along:
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics;
/// fn do_thing() {
/// let count: u64 = 42;
/// let user: String = String::from("jane");
/// counter!("do_thing", count, "service" => "admin", "user" => user);
/// }
/// # fn main() {}
/// ```
#[macro_export]
macro_rules! counter {
($name:expr, $value:expr) => {
$crate::__private_api_record_count($crate::Key::from_name($name), $value);
};
($name:expr, $value:expr, $($labels:tt)*) => {
let labels = $crate::labels!( $($labels)* );
let key = $crate::Key::from_name_and_labels($name, labels);
$crate::__private_api_record_count(key, $value);
};
}
/// Records a gauge.
///
/// Functionally equivalent to calling [`Recorder::record_gauge`].
///
/// ### Examples
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics;
/// fn update_current_value() {
/// let value: i64 = -131;
/// gauge!("current_value", value);
/// }
/// # fn main() {}
/// ```
///
/// Labels can also be passed along:
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics;
/// fn update_current_value() {
/// let value: i64 = -131;
/// let creator: String = String::from("jane");
/// gauge!("current_value", value, "creator" => creator);
/// }
/// # fn main() {}
/// ```
#[macro_export]
macro_rules! gauge {
($name:expr, $value:expr) => {
$crate::__private_api_record_gauge($crate::Key::from_name($name), $value);
};
($name:expr, $value:expr, $($labels:tt)*) => {
let labels = $crate::labels!( $($labels)* );
let key = $crate::Key::from_name_and_labels($name, labels);
$crate::__private_api_record_gauge(key, $value);
};
}
/// Records a timing.
///
/// Functionally equivalent to calling [`Recorder::record_histogram`].
///
/// ### Examples
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics;
/// # use std::time::Instant;
/// # fn process() {}
/// fn handle_request() {
/// let start = Instant::now();
/// process();
/// let end = Instant::now();
///
/// // We can pass instances of `Instant` directly:
/// timing!("perf.request_processed", start, end);
///
/// // Or we can pass just the delta:
/// let delta = end - start;
/// timing!("perf.request_processed", delta);
///
/// // And we can even pass unsigned values, both for the start/end notation:
/// let start: u64 = 100;
/// let end: u64 = 200;
/// timing!("perf.request_processed", start, end);
///
/// // And the delta notation:
/// let delta: u64 = end - start;
/// timing!("perf.request_processed", delta);
/// }
/// # fn main() {}
/// ```
///
/// Labels can also be passed along:
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics;
/// # use std::time::Instant;
/// # fn process() {}
/// fn handle_request() {
/// let start = Instant::now();
/// process();
/// let end = Instant::now();
///
/// // We can pass instances of `Instant` directly:
/// timing!("perf.request_processed", start, end, "service" => "http", "type" => "checkout");
///
/// // Or we can pass just the delta:
/// let delta = end - start;
/// timing!("perf.request_processed", delta, "service" => "http", "type" => "checkout");
///
/// // And we can even pass unsigned values, both for the start/end notation:
/// let start: u64 = 100;
/// let end: u64 = 200;
/// timing!("perf.request_processed", start, end, "service" => "http", "type" => "checkout");
///
/// // And the delta notation:
/// let delta: u64 = end - start;
/// timing!("perf.request_processed", delta, "service" => "http", "type" => "checkout");
/// }
/// # fn main() {}
/// ```
#[macro_export]
macro_rules! timing {
($name:expr, $value:expr) => {
$crate::__private_api_record_histogram($crate::Key::from_name($name), $value);
};
($name:expr, $start:expr, $end:expr) => {
timing!($name, $end - $start)
};
($name:expr, $start:expr, $end:expr, $($labels:tt)*) => {
timing!($name, $end - $start, $($labels)*)
};
($name:expr, $value:expr, $($labels:tt)*) => {
let labels = $crate::labels!( $($labels)* );
let key = $crate::Key::from_name_and_labels($name, labels);
$crate::__private_api_record_histogram(key, $value);
};
}
/// Records a value.
///
/// Functionally equivalent to calling [`Recorder::record_histogram`].
///
/// ### Examples
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics;
/// # use std::time::Instant;
/// # fn process() -> u64 { 42 }
/// fn handle_request() {
/// let rows_read = process();
/// value!("client.process_num_rows", rows_read);
/// }
/// # fn main() {}
/// ```
///
/// Labels can also be passed along:
///
/// ```rust
/// # #[macro_use]
/// # extern crate metrics;
/// # use std::time::Instant;
/// # fn process() -> u64 { 42 }
/// fn handle_request() {
/// let rows_read = process();
/// value!("client.process_num_rows", rows_read, "resource" => "shard1", "table" => "posts");
/// }
/// # fn main() {}
/// ```
#[macro_export]
macro_rules! value {
($name:expr, $value:expr) => {
$crate::__private_api_record_histogram($crate::Key::from_name($name), $value);
};
($name:expr, $value:expr, $($labels:tt)*) => {
let labels = $crate::labels!( $($labels)* );
let key = $crate::Key::from_name_and_labels($name, labels);
$crate::__private_api_record_histogram(key, $value);
};
}

View File

@ -1,237 +0,0 @@
use crate::{
common::{
Delta, MetricIdentifier, MetricKind, MetricName, MetricScope, MetricScopeHandle,
MetricValue,
},
data::{Counter, Gauge, Histogram},
registry::{MetricRegistry, ScopeRegistry},
};
use fxhash::FxBuildHasher;
use quanta::Clock;
use std::error::Error;
use std::fmt;
use std::sync::Arc;
type FastHashMap<K, V> = hashbrown::HashMap<K, V, FxBuildHasher>;
/// Errors during sink creation.
#[derive(Debug, Clone)]
pub enum SinkError {
/// The scope value given was invalid i.e. empty or illegal characters.
InvalidScope,
}
impl Error for SinkError {}
impl fmt::Display for SinkError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SinkError::InvalidScope => write!(f, "given scope is invalid"),
}
}
}
/// A value that can be used as a metric scope.
///
/// This helper trait allows us to accept either a single string or a slice of strings to use as a
/// scope, to avoid needing to allocate in the case where we want to be able to specify multiple
/// scope levels in a single go.
pub trait AsScoped<'a> {
/// Creates a new [`MetricScope`] by adding `self` to the `base` scope.
fn as_scoped(&'a self, base: MetricScope) -> MetricScope;
}
/// Handle for sending metric samples.
pub struct Sink {
metric_registry: Arc<MetricRegistry>,
metric_cache: FastHashMap<MetricIdentifier, MetricValue>,
scope_registry: Arc<ScopeRegistry>,
scope: MetricScope,
scope_handle: MetricScopeHandle,
clock: Clock,
}
impl Sink {
pub(crate) fn new(
metric_registry: Arc<MetricRegistry>,
scope_registry: Arc<ScopeRegistry>,
scope: MetricScope,
clock: Clock,
) -> Sink {
let scope_handle = scope_registry.register(scope.clone());
Sink {
metric_registry,
metric_cache: FastHashMap::default(),
scope_registry,
scope,
scope_handle,
clock,
}
}
/// Creates a scoped clone of this [`Sink`].
///
/// Scoping controls the resulting metric name for any metrics sent by this [`Sink`]. For
/// example, you might have a metric called `messages_sent`.
///
/// With scoping, you could have independent versions of the same metric. This is useful for
/// having the same "base" metric name but with broken down values.
///
/// Going further with the above example, if you had a server, and listened on multiple
/// addresses, maybe you would have a scoped [`Sink`] per listener, and could end up with
/// metrics that look like this:
/// - `listener.a.messages_sent`
/// - `listener.b.messages_sent`
/// - `listener.c.messages_sent`
/// - etc
///
/// Scopes are also inherited. If you create a scoped [`Sink`] from another [`Sink`] which is
/// already scoped, the scopes will be merged together using a `.` as the string separator.
/// This makes it easy to nest scopes. Cloning a scoped [`Sink`], though, will inherit the
/// same scope as the original.
pub fn scoped<'a, S: AsScoped<'a> + ?Sized>(&self, scope: &'a S) -> Sink {
let new_scope = scope.as_scoped(self.scope.clone());
Sink::new(
self.metric_registry.clone(),
self.scope_registry.clone(),
new_scope,
self.clock.clone(),
)
}
/// Gets the current time, in nanoseconds, from the internal high-speed clock.
pub fn now(&self) -> u64 {
self.clock.now()
}
/// Records a value for a counter identified by the given name.
pub fn record_count<N: Into<MetricName>>(&mut self, name: N, value: u64) {
let identifier =
MetricIdentifier::Unlabeled(name.into(), self.scope_handle, MetricKind::Counter);
let value_handle = self.get_cached_value_handle(identifier);
value_handle.update_counter(value);
}
/// Records the value for a gauge identified by the given name.
pub fn record_gauge<N: Into<MetricName>>(&mut self, name: N, value: i64) {
let identifier =
MetricIdentifier::Unlabeled(name.into(), self.scope_handle, MetricKind::Gauge);
let value_handle = self.get_cached_value_handle(identifier);
value_handle.update_gauge(value);
}
/// Records the value for a timing histogram identified by the given name.
///
/// Both the start and end times must be supplied, but any values that implement [`Delta`] can
/// be used which allows for raw values from [`quanta::Clock`] to be used, or measurements from
/// [`Instant::now`].
pub fn record_timing<N: Into<MetricName>, V: Delta>(&mut self, name: N, start: V, end: V) {
let value = end.delta(start);
self.record_value(name, value);
}
/// Records the value for a value histogram identified by the given name.
pub fn record_value<N: Into<MetricName>>(&mut self, name: N, value: u64) {
let identifier =
MetricIdentifier::Unlabeled(name.into(), self.scope_handle, MetricKind::Histogram);
let value_handle = self.get_cached_value_handle(identifier);
value_handle.update_histogram(value);
}
/// Creates a handle to the given counter.
///
/// This handle can be embedded into an existing type and used to directly update the
/// underlying counter. It is merely a proxy, so multiple handles to the same counter can be
/// held and used.
pub fn counter<N: Into<MetricName>>(&mut self, name: N) -> Counter {
let identifier =
MetricIdentifier::Unlabeled(name.into(), self.scope_handle, MetricKind::Counter);
self.get_cached_value_handle(identifier).clone().into()
}
/// Creates a handle to the given gauge.
///
/// This handle can be embedded into an existing type and used to directly update the
/// underlying gauge. It is merely a proxy, so multiple handles to the same gauge can be
/// held and used.
pub fn gauge<N: Into<MetricName>>(&mut self, name: N) -> Gauge {
let identifier =
MetricIdentifier::Unlabeled(name.into(), self.scope_handle, MetricKind::Gauge);
self.get_cached_value_handle(identifier).clone().into()
}
/// Creates a handle to the given histogram.
///
/// This handle can be embedded into an existing type and used to directly update the
/// underlying histogram. It is merely a proxy, so multiple handles to the same histogram
/// can be held and used.
pub fn histogram<N: Into<MetricName>>(&mut self, name: N) -> Histogram {
let identifier =
MetricIdentifier::Unlabeled(name.into(), self.scope_handle, MetricKind::Histogram);
self.get_cached_value_handle(identifier).clone().into()
}
fn get_cached_value_handle(&mut self, identifier: MetricIdentifier) -> &MetricValue {
// This gross hack gets around lifetime rules until full NLL is stable. Without it, the
// borrow checker doesn't understand the flow control and thinks the reference lives all
// the way until the of the function, which breaks when we try to take a mutable reference
// for inserting into the handle cache.
if let Some(handle) = self.metric_cache.get(&identifier) {
return unsafe { &*(handle as *const MetricValue) };
}
let handle = self.metric_registry.get_value_handle(identifier.clone());
self.metric_cache.insert(identifier.clone(), handle);
self.metric_cache.get(&identifier).unwrap()
}
}
impl Clone for Sink {
fn clone(&self) -> Sink {
Sink {
metric_registry: self.metric_registry.clone(),
metric_cache: self.metric_cache.clone(),
scope_registry: self.scope_registry.clone(),
scope: self.scope.clone(),
scope_handle: self.scope_handle,
clock: self.clock.clone(),
}
}
}
impl<'a> AsScoped<'a> for str {
fn as_scoped(&'a self, base: MetricScope) -> MetricScope {
match base {
MetricScope::Root => {
let parts = vec![self.to_owned()];
MetricScope::Nested(parts)
}
MetricScope::Nested(mut parts) => {
parts.push(self.to_owned());
MetricScope::Nested(parts)
}
}
}
}
impl<'a, 'b, T> AsScoped<'a> for T
where
&'a T: AsRef<[&'b str]>,
T: 'a,
{
fn as_scoped(&'a self, base: MetricScope) -> MetricScope {
match base {
MetricScope::Root => {
let parts = self.as_ref().iter().map(|s| s.to_string()).collect();
MetricScope::Nested(parts)
}
MetricScope::Nested(mut parts) => {
let mut new_parts = self.as_ref().iter().map(|s| s.to_string()).collect();
parts.append(&mut new_parts);
MetricScope::Nested(parts)
}
}
}
}