Add metrics-facade crate to provide global macros. (#20)
Add support for a `log`-esque crate that can provide globally-installed metrics collection and ingest via basic macros.
This commit is contained in:
parent
2046be737d
commit
1de4343fc5
|
@ -1,6 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"metrics-core",
|
"metrics-core",
|
||||||
|
"metrics-facade",
|
||||||
"metrics",
|
"metrics",
|
||||||
"metrics-util",
|
"metrics-util",
|
||||||
"metrics-exporter-log",
|
"metrics-exporter-log",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "metrics-core"
|
name = "metrics-core"
|
||||||
version = "0.3.1"
|
version = "0.4.0"
|
||||||
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,37 @@
|
||||||
//! Histograms are a convenient way to measure behavior not only at the median, but at the edges of
|
//! Histograms are a convenient way to measure behavior not only at the median, but at the edges of
|
||||||
//! normal operating behavior.
|
//! normal operating behavior.
|
||||||
use futures::future::Future;
|
use futures::future::Future;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// An optimized metric key.
|
||||||
|
///
|
||||||
|
/// 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>;
|
||||||
|
|
||||||
|
/// A value which can be converted into a nanosecond representation.
|
||||||
|
///
|
||||||
|
/// 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 {
|
||||||
|
fn as_nanos(&self) -> u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsNanoseconds for u64 {
|
||||||
|
fn as_nanos(&self) -> u64 {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsNanoseconds for Duration {
|
||||||
|
fn as_nanos(&self) -> u64 {
|
||||||
|
self.as_nanos() as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A value that records metrics.
|
/// A value that records metrics.
|
||||||
pub trait Recorder {
|
pub trait Recorder {
|
||||||
|
@ -43,7 +74,7 @@ pub trait Recorder {
|
||||||
/// counters and gauges usually have slightly different modes of operation.
|
/// counters and gauges usually have slightly different modes of operation.
|
||||||
///
|
///
|
||||||
/// For the sake of flexibility on the exporter side, both are provided.
|
/// For the sake of flexibility on the exporter side, both are provided.
|
||||||
fn record_counter<K: AsRef<str>>(&mut self, key: K, value: u64);
|
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64);
|
||||||
|
|
||||||
/// Records a gauge.
|
/// Records a gauge.
|
||||||
///
|
///
|
||||||
|
@ -52,13 +83,15 @@ pub trait Recorder {
|
||||||
/// counters and gauges usually have slightly different modes of operation.
|
/// counters and gauges usually have slightly different modes of operation.
|
||||||
///
|
///
|
||||||
/// For the sake of flexibility on the exporter side, both are provided.
|
/// For the sake of flexibility on the exporter side, both are provided.
|
||||||
fn record_gauge<K: AsRef<str>>(&mut self, key: K, value: i64);
|
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64);
|
||||||
|
|
||||||
/// Records a histogram.
|
/// Records a histogram.
|
||||||
///
|
///
|
||||||
/// Recorders are expected to tally their own histogram views, so this will be called with all
|
/// 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.
|
/// of the underlying observed values, and callers will need to process them accordingly.
|
||||||
fn record_histogram<K: AsRef<str>>(&mut self, key: K, values: &[u64]);
|
///
|
||||||
|
/// 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]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A value that holds a point-in-time view of collected metrics.
|
/// A value that holds a point-in-time view of collected metrics.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "metrics-exporter-http"
|
name = "metrics-exporter-http"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -13,6 +13,6 @@ repository = "https://github.com/metrics-rs/metrics-exporter-http"
|
||||||
documentation = "https://docs.rs/metrics-exporter-http"
|
documentation = "https://docs.rs/metrics-exporter-http"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
metrics-core = { path = "../metrics-core", version = "^0.3" }
|
metrics-core = { path = "../metrics-core", version = "^0.4" }
|
||||||
hyper = "^0.12"
|
hyper = "^0.12"
|
||||||
log = "^0.4"
|
log = "^0.4"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "metrics-exporter-log"
|
name = "metrics-exporter-log"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ repository = "https://github.com/metrics-rs/metrics-exporter-log"
|
||||||
documentation = "https://docs.rs/metrics-exporter-log"
|
documentation = "https://docs.rs/metrics-exporter-log"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
metrics-core = { path = "../metrics-core", version = "^0.3" }
|
metrics-core = { path = "../metrics-core", version = "^0.4" }
|
||||||
log = "^0.4"
|
log = "^0.4"
|
||||||
futures = "^0.1"
|
futures = "^0.1"
|
||||||
tokio-timer = "^0.2"
|
tokio-timer = "^0.2"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,27 @@
|
||||||
|
[package]
|
||||||
|
name = "metrics-facade"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
edition = "2018"
|
||||||
|
repository = "https://github.com/metrics-rs/metrics-facade"
|
||||||
|
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 = []
|
|
@ -0,0 +1,13 @@
|
||||||
|
//! This build script detects target platforms that lack proper support for
|
||||||
|
//! atomics and sets `cfg` flags accordingly.
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// CAS is not available on thumbv6.
|
||||||
|
let target = env::var("TARGET").unwrap();
|
||||||
|
if !target.starts_with("thumbv6") {
|
||||||
|
println!("cargo:rustc-cfg=atomic_cas");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
#[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);
|
||||||
|
}
|
|
@ -0,0 +1,334 @@
|
||||||
|
//! 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());
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
/// 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);
|
||||||
|
}};
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "metrics-recorder-prometheus"
|
name = "metrics-recorder-prometheus"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -13,6 +13,6 @@ repository = "https://github.com/metrics-rs/metrics-recorder-prometheus"
|
||||||
documentation = "https://docs.rs/metrics-recorder-prometheus"
|
documentation = "https://docs.rs/metrics-recorder-prometheus"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
metrics-core = { path = "../metrics-core", version = "^0.3" }
|
metrics-core = { path = "../metrics-core", version = "^0.4" }
|
||||||
metrics-util = { path = "../metrics-util", version = "^0.2" }
|
metrics-util = { path = "../metrics-util", version = "^0.2" }
|
||||||
hdrhistogram = "^6.1"
|
hdrhistogram = "^6.1"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Records metrics in the Prometheus exposition format.
|
//! Records metrics in the Prometheus exposition format.
|
||||||
use hdrhistogram::Histogram;
|
use hdrhistogram::Histogram;
|
||||||
use metrics_core::Recorder;
|
use metrics_core::{Key, Recorder};
|
||||||
use metrics_util::{parse_quantiles, Quantile};
|
use metrics_util::{parse_quantiles, Quantile};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@ impl PrometheusRecorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Recorder for PrometheusRecorder {
|
impl Recorder for PrometheusRecorder {
|
||||||
fn record_counter<K: AsRef<str>>(&mut self, key: K, value: u64) {
|
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64) {
|
||||||
let label = key.as_ref().replace('.', "_");
|
let label = key.into().as_ref().replace('.', "_");
|
||||||
self.output.push_str("\n# TYPE ");
|
self.output.push_str("\n# TYPE ");
|
||||||
self.output.push_str(label.as_str());
|
self.output.push_str(label.as_str());
|
||||||
self.output.push_str(" counter\n");
|
self.output.push_str(" counter\n");
|
||||||
|
@ -46,8 +46,8 @@ impl Recorder for PrometheusRecorder {
|
||||||
self.output.push_str("\n");
|
self.output.push_str("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_gauge<K: AsRef<str>>(&mut self, key: K, value: i64) {
|
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64) {
|
||||||
let label = key.as_ref().replace('.', "_");
|
let label = key.into().as_ref().replace('.', "_");
|
||||||
self.output.push_str("\n# TYPE ");
|
self.output.push_str("\n# TYPE ");
|
||||||
self.output.push_str(label.as_str());
|
self.output.push_str(label.as_str());
|
||||||
self.output.push_str(" gauge\n");
|
self.output.push_str(" gauge\n");
|
||||||
|
@ -57,7 +57,7 @@ impl Recorder for PrometheusRecorder {
|
||||||
self.output.push_str("\n");
|
self.output.push_str("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_histogram<K: AsRef<str>>(&mut self, key: K, values: &[u64]) {
|
fn record_histogram<K: Into<Key>>(&mut self, key: K, values: &[u64]) {
|
||||||
let mut sum = 0;
|
let mut sum = 0;
|
||||||
let mut h = Histogram::<u64>::new(3).expect("failed to create histogram");
|
let mut h = Histogram::<u64>::new(3).expect("failed to create histogram");
|
||||||
for value in values {
|
for value in values {
|
||||||
|
@ -65,7 +65,7 @@ impl Recorder for PrometheusRecorder {
|
||||||
sum += *value;
|
sum += *value;
|
||||||
}
|
}
|
||||||
|
|
||||||
let label = key.as_ref().replace('.', "_");
|
let label = key.into().as_ref().replace('.', "_");
|
||||||
self.output.push_str("\n# TYPE ");
|
self.output.push_str("\n# TYPE ");
|
||||||
self.output.push_str(label.as_str());
|
self.output.push_str(label.as_str());
|
||||||
self.output.push_str(" summary\n");
|
self.output.push_str(" summary\n");
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "metrics-recorder-text"
|
name = "metrics-recorder-text"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -13,6 +13,6 @@ repository = "https://github.com/metrics-rs/metrics-recorder-text"
|
||||||
documentation = "https://docs.rs/metrics-recorder-text"
|
documentation = "https://docs.rs/metrics-recorder-text"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
metrics-core = { path = "../metrics-core", version = "^0.3" }
|
metrics-core = { path = "../metrics-core", version = "^0.4" }
|
||||||
metrics-util = { path = "../metrics-util", version = "^0.2" }
|
metrics-util = { path = "../metrics-util", version = "^0.2" }
|
||||||
hdrhistogram = "^6.1"
|
hdrhistogram = "^6.1"
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
use hdrhistogram::Histogram;
|
use hdrhistogram::Histogram;
|
||||||
use metrics_core::Recorder;
|
use metrics_core::{Key, Recorder};
|
||||||
use metrics_util::{parse_quantiles, Quantile};
|
use metrics_util::{parse_quantiles, Quantile};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
@ -80,25 +80,25 @@ impl TextRecorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Recorder for TextRecorder {
|
impl Recorder for TextRecorder {
|
||||||
fn record_counter<K: AsRef<str>>(&mut self, key: K, value: u64) {
|
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64) {
|
||||||
let (name_parts, name) = name_to_parts(key.as_ref());
|
let (name_parts, name) = name_to_parts(key.into().as_ref());
|
||||||
let mut values = single_value_to_values(name, value);
|
let mut values = single_value_to_values(name, value);
|
||||||
self.structure.insert(name_parts, &mut values);
|
self.structure.insert(name_parts, &mut values);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_gauge<K: AsRef<str>>(&mut self, key: K, value: i64) {
|
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64) {
|
||||||
let (name_parts, name) = name_to_parts(key.as_ref());
|
let (name_parts, name) = name_to_parts(key.into().as_ref());
|
||||||
let mut values = single_value_to_values(name, value);
|
let mut values = single_value_to_values(name, value);
|
||||||
self.structure.insert(name_parts, &mut values);
|
self.structure.insert(name_parts, &mut values);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_histogram<K: AsRef<str>>(&mut self, key: K, values: &[u64]) {
|
fn record_histogram<K: Into<Key>>(&mut self, key: K, values: &[u64]) {
|
||||||
let mut h = Histogram::new(3).expect("failed to create histogram");
|
let mut h = Histogram::new(3).expect("failed to create histogram");
|
||||||
for value in values {
|
for value in values {
|
||||||
h.record(*value).expect("failed to record histogram value");
|
h.record(*value).expect("failed to record histogram value");
|
||||||
}
|
}
|
||||||
|
|
||||||
let (name_parts, name) = name_to_parts(key.as_ref());
|
let (name_parts, name) = name_to_parts(key.into().as_ref());
|
||||||
let mut values = hist_to_values(name, h, &self.quantiles);
|
let mut values = hist_to_values(name, h, &self.quantiles);
|
||||||
self.structure.insert(name_parts, &mut values);
|
self.structure.insert(name_parts, &mut values);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,14 @@ name = "histogram"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
metrics-core = { path = "../metrics-core", version = "^0.3" }
|
metrics-core = { path = "../metrics-core", version = "^0.4" }
|
||||||
metrics-util = { path = "../metrics-util", version = "^0.2" }
|
metrics-util = { path = "../metrics-util", version = "^0.2" }
|
||||||
|
metrics-facade = { path = "../metrics-facade", version = "^0.1", features = ["std"] }
|
||||||
im = "^12"
|
im = "^12"
|
||||||
fxhash = "^0.2"
|
fxhash = "^0.2"
|
||||||
arc-swap = "^0.3"
|
arc-swap = "^0.3"
|
||||||
parking_lot = "^0.7"
|
parking_lot = "^0.8"
|
||||||
hashbrown = "^0.3"
|
hashbrown = "^0.4"
|
||||||
quanta = "^0.3"
|
quanta = "^0.3"
|
||||||
futures = "^0.1"
|
futures = "^0.1"
|
||||||
crossbeam-utils = "^0.6"
|
crossbeam-utils = "^0.6"
|
||||||
|
|
|
@ -9,7 +9,7 @@ extern crate metrics_core;
|
||||||
use getopts::Options;
|
use getopts::Options;
|
||||||
use hdrhistogram::Histogram;
|
use hdrhistogram::Histogram;
|
||||||
use metrics::{Receiver, Sink};
|
use metrics::{Receiver, Sink};
|
||||||
use metrics_core::{Recorder, Snapshot, SnapshotProvider};
|
use metrics_core::{Key, Recorder, Snapshot, SnapshotProvider};
|
||||||
use quanta::Clock;
|
use quanta::Clock;
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
|
@ -292,15 +292,15 @@ impl TotalRecorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Recorder for TotalRecorder {
|
impl Recorder for TotalRecorder {
|
||||||
fn record_counter<K: AsRef<str>>(&mut self, _key: K, value: u64) {
|
fn record_counter<K: Into<Key>>(&mut self, _key: K, value: u64) {
|
||||||
self.total += value;
|
self.total += value;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_gauge<K: AsRef<str>>(&mut self, _key: K, value: i64) {
|
fn record_gauge<K: Into<Key>>(&mut self, _key: K, value: i64) {
|
||||||
self.total += value as u64;
|
self.total += value as u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_histogram<K: AsRef<str>>(&mut self, _key: K, values: &[u64]) {
|
fn record_histogram<K: Into<Key>>(&mut self, _key: K, values: &[u64]) {
|
||||||
self.total += values.len() as u64;
|
self.total += values.len() as u64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,271 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate getopts;
|
||||||
|
extern crate hdrhistogram;
|
||||||
|
extern crate metrics;
|
||||||
|
extern crate metrics_core;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate metrics_facade;
|
||||||
|
|
||||||
|
use getopts::Options;
|
||||||
|
use hdrhistogram::Histogram;
|
||||||
|
use metrics::{Receiver, Sink};
|
||||||
|
use metrics_core::{Key, Recorder, Snapshot, SnapshotProvider};
|
||||||
|
use quanta::Clock;
|
||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
const LOOP_SAMPLE: u64 = 1000;
|
||||||
|
|
||||||
|
struct Generator {
|
||||||
|
t0: Option<u64>,
|
||||||
|
gauge: i64,
|
||||||
|
hist: Histogram<u64>,
|
||||||
|
done: Arc<AtomicBool>,
|
||||||
|
rate_counter: Arc<AtomicU64>,
|
||||||
|
clock: Clock,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Generator {
|
||||||
|
fn new(done: Arc<AtomicBool>, rate_counter: Arc<AtomicU64>, clock: Clock) -> Generator {
|
||||||
|
Generator {
|
||||||
|
t0: None,
|
||||||
|
gauge: 0,
|
||||||
|
hist: Histogram::<u64>::new_with_bounds(1, u64::max_value(), 3).unwrap(),
|
||||||
|
done,
|
||||||
|
rate_counter,
|
||||||
|
clock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self) {
|
||||||
|
let mut counter = 0;
|
||||||
|
loop {
|
||||||
|
counter += 1;
|
||||||
|
|
||||||
|
if self.done.load(Ordering::Relaxed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gauge += 1;
|
||||||
|
|
||||||
|
let t1 = self.clock.now();
|
||||||
|
|
||||||
|
if let Some(t0) = self.t0 {
|
||||||
|
let start = if counter % LOOP_SAMPLE == 0 {
|
||||||
|
self.clock.now()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
counter!("ok", 1);
|
||||||
|
timing!("ok", t1 - t0);
|
||||||
|
gauge!("total", self.gauge);
|
||||||
|
|
||||||
|
if start != 0 {
|
||||||
|
let delta = self.clock.now() - start;
|
||||||
|
self.hist.saturating_record(delta);
|
||||||
|
|
||||||
|
// We also increment our global counter for the sample rate here.
|
||||||
|
self.rate_counter
|
||||||
|
.fetch_add(LOOP_SAMPLE * 3, Ordering::AcqRel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.t0 = Some(t1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Generator {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
info!(
|
||||||
|
" sender latency: min: {:9} p50: {:9} p95: {:9} p99: {:9} p999: {:9} max: {:9}",
|
||||||
|
nanos_to_readable(self.hist.min()),
|
||||||
|
nanos_to_readable(self.hist.value_at_percentile(50.0)),
|
||||||
|
nanos_to_readable(self.hist.value_at_percentile(95.0)),
|
||||||
|
nanos_to_readable(self.hist.value_at_percentile(99.0)),
|
||||||
|
nanos_to_readable(self.hist.value_at_percentile(99.9)),
|
||||||
|
nanos_to_readable(self.hist.max())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_usage(program: &str, opts: &Options) {
|
||||||
|
let brief = format!("Usage: {} [options]", program);
|
||||||
|
print!("{}", opts.usage(&brief));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opts() -> Options {
|
||||||
|
let mut opts = Options::new();
|
||||||
|
|
||||||
|
opts.optopt(
|
||||||
|
"d",
|
||||||
|
"duration",
|
||||||
|
"number of seconds to run the benchmark",
|
||||||
|
"INTEGER",
|
||||||
|
);
|
||||||
|
opts.optopt("p", "producers", "number of producers", "INTEGER");
|
||||||
|
opts.optflag("h", "help", "print this help menu");
|
||||||
|
|
||||||
|
opts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let program = &args[0];
|
||||||
|
let opts = opts();
|
||||||
|
|
||||||
|
let matches = match opts.parse(&args[1..]) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(f) => {
|
||||||
|
error!("Failed to parse command line args: {}", f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches.opt_present("help") {
|
||||||
|
print_usage(program, &opts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("metrics benchmark");
|
||||||
|
|
||||||
|
// Build our sink and configure the facets.
|
||||||
|
let seconds = matches
|
||||||
|
.opt_str("duration")
|
||||||
|
.unwrap_or_else(|| "60".to_owned())
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
let producers = matches
|
||||||
|
.opt_str("producers")
|
||||||
|
.unwrap_or_else(|| "1".to_owned())
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
info!("duration: {}s", seconds);
|
||||||
|
info!("producers: {}", producers);
|
||||||
|
|
||||||
|
let receiver = Receiver::builder()
|
||||||
|
.histogram(Duration::from_secs(5), Duration::from_millis(100))
|
||||||
|
.build()
|
||||||
|
.expect("failed to build receiver");
|
||||||
|
|
||||||
|
let controller = receiver.get_controller();
|
||||||
|
receiver.install();
|
||||||
|
info!("receiver configured");
|
||||||
|
|
||||||
|
// Spin up our sample producers.
|
||||||
|
let done = Arc::new(AtomicBool::new(false));
|
||||||
|
let rate_counter = Arc::new(AtomicU64::new(0));
|
||||||
|
let mut handles = Vec::new();
|
||||||
|
let clock = Clock::new();
|
||||||
|
|
||||||
|
for _ in 0..producers {
|
||||||
|
let d = done.clone();
|
||||||
|
let r = rate_counter.clone();
|
||||||
|
let c = clock.clone();
|
||||||
|
let handle = thread::spawn(move || {
|
||||||
|
Generator::new(d, r, c).run();
|
||||||
|
});
|
||||||
|
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll the controller to figure out the sample rate.
|
||||||
|
let mut total = 0;
|
||||||
|
let mut t0 = Instant::now();
|
||||||
|
|
||||||
|
let mut snapshot_hist = Histogram::<u64>::new_with_bounds(1, u64::max_value(), 3).unwrap();
|
||||||
|
for _ in 0..seconds {
|
||||||
|
let t1 = Instant::now();
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let snapshot = controller.get_snapshot().unwrap();
|
||||||
|
let end = Instant::now();
|
||||||
|
snapshot_hist.saturating_record(duration_as_nanos(end - start) as u64);
|
||||||
|
|
||||||
|
let turn_total = rate_counter.load(Ordering::Acquire);
|
||||||
|
let turn_delta = turn_total - total;
|
||||||
|
total = turn_total;
|
||||||
|
let rate = turn_delta as f64 / (duration_as_nanos(t1 - t0) / 1_000_000_000.0);
|
||||||
|
|
||||||
|
info!("sample ingest rate: {:.0} samples/sec", rate);
|
||||||
|
t0 = t1;
|
||||||
|
thread::sleep(Duration::new(1, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("--------------------------------------------------------------------------------");
|
||||||
|
info!(" ingested samples total: {}", total);
|
||||||
|
info!(
|
||||||
|
"snapshot retrieval: 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)),
|
||||||
|
nanos_to_readable(snapshot_hist.value_at_percentile(99.0)),
|
||||||
|
nanos_to_readable(snapshot_hist.value_at_percentile(99.9)),
|
||||||
|
nanos_to_readable(snapshot_hist.max())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for the producers to finish so we can get their stats too.
|
||||||
|
done.store(true, Ordering::SeqCst);
|
||||||
|
for handle in handles {
|
||||||
|
let _ = handle.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nanos_to_readable(t: u64) -> String {
|
||||||
|
let f = t as f64;
|
||||||
|
if f < 1_000.0 {
|
||||||
|
format!("{}ns", f)
|
||||||
|
} else if f < 1_000_000.0 {
|
||||||
|
format!("{:.0}μs", f / 1_000.0)
|
||||||
|
} else if f < 2_000_000_000.0 {
|
||||||
|
format!("{:.2}ms", f / 1_000_000.0)
|
||||||
|
} else {
|
||||||
|
format!("{:.3}s", f / 1_000_000_000.0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::common::ValueSnapshot;
|
use crate::common::ValueSnapshot;
|
||||||
use metrics_core::{Recorder, Snapshot as MetricsSnapshot};
|
use metrics_core::{Key, Recorder, Snapshot as MetricsSnapshot};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A point-in-time view of metric data.
|
/// A point-in-time view of metric data.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
@ -17,11 +18,14 @@ impl MetricsSnapshot for Snapshot {
|
||||||
/// Records the snapshot to the given recorder.
|
/// Records the snapshot to the given recorder.
|
||||||
fn record<R: Recorder>(&self, recorder: &mut R) {
|
fn record<R: Recorder>(&self, recorder: &mut R) {
|
||||||
for (key, snapshot) in &self.measurements {
|
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());
|
||||||
match snapshot {
|
match snapshot {
|
||||||
ValueSnapshot::Counter(value) => recorder.record_counter(key, *value),
|
ValueSnapshot::Counter(value) => recorder.record_counter(owned_key.clone(), *value),
|
||||||
ValueSnapshot::Gauge(value) => recorder.record_gauge(key, *value),
|
ValueSnapshot::Gauge(value) => recorder.record_gauge(owned_key.clone(), *value),
|
||||||
ValueSnapshot::Histogram(stream) => stream.decompress_with(|values| {
|
ValueSnapshot::Histogram(stream) => stream.decompress_with(|values| {
|
||||||
recorder.record_histogram(key, values);
|
recorder.record_histogram(owned_key.clone(), values);
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +35,7 @@ impl MetricsSnapshot for Snapshot {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{MetricsSnapshot, Recorder, Snapshot, ValueSnapshot};
|
use super::{MetricsSnapshot, Recorder, Snapshot, ValueSnapshot};
|
||||||
|
use metrics_core::Key;
|
||||||
use metrics_util::StreamingIntegers;
|
use metrics_util::StreamingIntegers;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -56,18 +61,18 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Recorder for MockRecorder {
|
impl Recorder for MockRecorder {
|
||||||
fn record_counter<K: AsRef<str>>(&mut self, key: K, value: u64) {
|
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64) {
|
||||||
let _ = self.counter.insert(key.as_ref().to_owned(), value);
|
let _ = self.counter.insert(key.into().to_string(), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_gauge<K: AsRef<str>>(&mut self, key: K, value: i64) {
|
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64) {
|
||||||
let _ = self.gauge.insert(key.as_ref().to_owned(), value);
|
let _ = self.gauge.insert(key.into().to_string(), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_histogram<K: AsRef<str>>(&mut self, key: K, values: &[u64]) {
|
fn record_histogram<K: Into<Key>>(&mut self, key: K, values: &[u64]) {
|
||||||
let _ = self
|
let _ = self
|
||||||
.histogram
|
.histogram
|
||||||
.insert(key.as_ref().to_owned(), values.to_vec());
|
.insert(key.into().to_string(), values.to_vec());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,16 @@ use crate::{
|
||||||
registry::{MetricRegistry, ScopeRegistry},
|
registry::{MetricRegistry, ScopeRegistry},
|
||||||
sink::Sink,
|
sink::Sink,
|
||||||
};
|
};
|
||||||
|
use metrics_core::Key;
|
||||||
|
use metrics_facade::Recorder;
|
||||||
use quanta::{Builder as UpkeepBuilder, Clock, Handle as UpkeepHandle};
|
use quanta::{Builder as UpkeepBuilder, Clock, Handle as UpkeepHandle};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static SINK: RefCell<Option<Sink>> = RefCell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
/// Central store for metrics.
|
/// Central store for metrics.
|
||||||
///
|
///
|
||||||
/// `Receiver` is the nucleus for all metrics operations. While no operations are performed by it
|
/// `Receiver` is the nucleus for all metrics operations. While no operations are performed by it
|
||||||
|
@ -52,6 +59,11 @@ impl Receiver {
|
||||||
Builder::default()
|
Builder::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Installs this receiver as the global metrics facade.
|
||||||
|
pub fn install(self) {
|
||||||
|
metrics_facade::set_boxed_recorder(Box::new(self)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a [`Sink`] bound to this receiver.
|
/// Creates a [`Sink`] bound to this receiver.
|
||||||
pub fn get_sink(&self) -> Sink {
|
pub fn get_sink(&self) -> Sink {
|
||||||
Sink::new(
|
Sink::new(
|
||||||
|
@ -67,3 +79,41 @@ impl Receiver {
|
||||||
Controller::new(self.metric_registry.clone(), self.scope_registry.clone())
|
Controller::new(self.metric_registry.clone(), self.scope_registry.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Recorder for Receiver {
|
||||||
|
fn record_counter(&self, key: Key, value: u64) {
|
||||||
|
SINK.with(move |sink| {
|
||||||
|
let mut sink = sink.borrow_mut();
|
||||||
|
if sink.is_none() {
|
||||||
|
let new_sink = self.get_sink();
|
||||||
|
*sink = Some(new_sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
sink.as_mut().unwrap().record_count(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_gauge(&self, key: Key, value: i64) {
|
||||||
|
SINK.with(move |sink| {
|
||||||
|
let mut sink = sink.borrow_mut();
|
||||||
|
if sink.is_none() {
|
||||||
|
let new_sink = self.get_sink();
|
||||||
|
*sink = Some(new_sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
sink.as_mut().unwrap().record_gauge(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_histogram(&self, key: Key, value: u64) {
|
||||||
|
SINK.with(move |sink| {
|
||||||
|
let mut sink = sink.borrow_mut();
|
||||||
|
if sink.is_none() {
|
||||||
|
let new_sink = self.get_sink();
|
||||||
|
*sink = Some(new_sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
sink.as_mut().unwrap().record_value(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue