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]
|
||||
members = [
|
||||
"metrics-core",
|
||||
"metrics-facade",
|
||||
"metrics",
|
||||
"metrics-util",
|
||||
"metrics-exporter-log",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "metrics-core"
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
|
|
@ -33,6 +33,37 @@
|
|||
//! Histograms are a convenient way to measure behavior not only at the median, but at the edges of
|
||||
//! normal operating behavior.
|
||||
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.
|
||||
pub trait Recorder {
|
||||
|
@ -43,7 +74,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: AsRef<str>>(&mut self, key: K, value: u64);
|
||||
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64);
|
||||
|
||||
/// Records a gauge.
|
||||
///
|
||||
|
@ -52,13 +83,15 @@ 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: AsRef<str>>(&mut self, key: K, value: i64);
|
||||
fn record_gauge<K: Into<Key>>(&mut self, key: K, 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.
|
||||
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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "metrics-exporter-http"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -13,6 +13,6 @@ repository = "https://github.com/metrics-rs/metrics-exporter-http"
|
|||
documentation = "https://docs.rs/metrics-exporter-http"
|
||||
|
||||
[dependencies]
|
||||
metrics-core = { path = "../metrics-core", version = "^0.3" }
|
||||
metrics-core = { path = "../metrics-core", version = "^0.4" }
|
||||
hyper = "^0.12"
|
||||
log = "^0.4"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "metrics-exporter-log"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -13,7 +13,7 @@ repository = "https://github.com/metrics-rs/metrics-exporter-log"
|
|||
documentation = "https://docs.rs/metrics-exporter-log"
|
||||
|
||||
[dependencies]
|
||||
metrics-core = { path = "../metrics-core", version = "^0.3" }
|
||||
metrics-core = { path = "../metrics-core", version = "^0.4" }
|
||||
log = "^0.4"
|
||||
futures = "^0.1"
|
||||
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]
|
||||
name = "metrics-recorder-prometheus"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -13,6 +13,6 @@ repository = "https://github.com/metrics-rs/metrics-recorder-prometheus"
|
|||
documentation = "https://docs.rs/metrics-recorder-prometheus"
|
||||
|
||||
[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" }
|
||||
hdrhistogram = "^6.1"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Records metrics in the Prometheus exposition format.
|
||||
use hdrhistogram::Histogram;
|
||||
use metrics_core::Recorder;
|
||||
use metrics_core::{Key, Recorder};
|
||||
use metrics_util::{parse_quantiles, Quantile};
|
||||
use std::time::SystemTime;
|
||||
|
||||
|
@ -35,8 +35,8 @@ impl PrometheusRecorder {
|
|||
}
|
||||
|
||||
impl Recorder for PrometheusRecorder {
|
||||
fn record_counter<K: AsRef<str>>(&mut self, key: K, value: u64) {
|
||||
let label = key.as_ref().replace('.', "_");
|
||||
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64) {
|
||||
let label = key.into().as_ref().replace('.', "_");
|
||||
self.output.push_str("\n# TYPE ");
|
||||
self.output.push_str(label.as_str());
|
||||
self.output.push_str(" counter\n");
|
||||
|
@ -46,8 +46,8 @@ impl Recorder for PrometheusRecorder {
|
|||
self.output.push_str("\n");
|
||||
}
|
||||
|
||||
fn record_gauge<K: AsRef<str>>(&mut self, key: K, value: i64) {
|
||||
let label = key.as_ref().replace('.', "_");
|
||||
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64) {
|
||||
let label = key.into().as_ref().replace('.', "_");
|
||||
self.output.push_str("\n# TYPE ");
|
||||
self.output.push_str(label.as_str());
|
||||
self.output.push_str(" gauge\n");
|
||||
|
@ -57,7 +57,7 @@ impl Recorder for PrometheusRecorder {
|
|||
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 h = Histogram::<u64>::new(3).expect("failed to create histogram");
|
||||
for value in values {
|
||||
|
@ -65,7 +65,7 @@ impl Recorder for PrometheusRecorder {
|
|||
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(label.as_str());
|
||||
self.output.push_str(" summary\n");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "metrics-recorder-text"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
authors = ["Toby Lawrence <toby@nuclearfurnace.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -13,6 +13,6 @@ repository = "https://github.com/metrics-rs/metrics-recorder-text"
|
|||
documentation = "https://docs.rs/metrics-recorder-text"
|
||||
|
||||
[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" }
|
||||
hdrhistogram = "^6.1"
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
//! ```
|
||||
//!
|
||||
use hdrhistogram::Histogram;
|
||||
use metrics_core::Recorder;
|
||||
use metrics_core::{Key, Recorder};
|
||||
use metrics_util::{parse_quantiles, Quantile};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt::Display;
|
||||
|
@ -80,25 +80,25 @@ impl TextRecorder {
|
|||
}
|
||||
|
||||
impl Recorder for TextRecorder {
|
||||
fn record_counter<K: AsRef<str>>(&mut self, key: K, value: u64) {
|
||||
let (name_parts, name) = name_to_parts(key.as_ref());
|
||||
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64) {
|
||||
let (name_parts, name) = name_to_parts(key.into().as_ref());
|
||||
let mut values = single_value_to_values(name, value);
|
||||
self.structure.insert(name_parts, &mut values);
|
||||
}
|
||||
|
||||
fn record_gauge<K: AsRef<str>>(&mut self, key: K, value: i64) {
|
||||
let (name_parts, name) = name_to_parts(key.as_ref());
|
||||
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64) {
|
||||
let (name_parts, name) = name_to_parts(key.into().as_ref());
|
||||
let mut values = single_value_to_values(name, value);
|
||||
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");
|
||||
for value in values {
|
||||
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);
|
||||
self.structure.insert(name_parts, &mut values);
|
||||
}
|
||||
|
|
|
@ -31,13 +31,14 @@ name = "histogram"
|
|||
harness = false
|
||||
|
||||
[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-facade = { path = "../metrics-facade", version = "^0.1", features = ["std"] }
|
||||
im = "^12"
|
||||
fxhash = "^0.2"
|
||||
arc-swap = "^0.3"
|
||||
parking_lot = "^0.7"
|
||||
hashbrown = "^0.3"
|
||||
parking_lot = "^0.8"
|
||||
hashbrown = "^0.4"
|
||||
quanta = "^0.3"
|
||||
futures = "^0.1"
|
||||
crossbeam-utils = "^0.6"
|
||||
|
|
|
@ -9,7 +9,7 @@ extern crate metrics_core;
|
|||
use getopts::Options;
|
||||
use hdrhistogram::Histogram;
|
||||
use metrics::{Receiver, Sink};
|
||||
use metrics_core::{Recorder, Snapshot, SnapshotProvider};
|
||||
use metrics_core::{Key, Recorder, Snapshot, SnapshotProvider};
|
||||
use quanta::Clock;
|
||||
use std::{
|
||||
env,
|
||||
|
@ -292,15 +292,15 @@ impl 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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.
|
||||
#[derive(Default, Debug)]
|
||||
|
@ -17,11 +18,14 @@ 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());
|
||||
match snapshot {
|
||||
ValueSnapshot::Counter(value) => recorder.record_counter(key, *value),
|
||||
ValueSnapshot::Gauge(value) => recorder.record_gauge(key, *value),
|
||||
ValueSnapshot::Counter(value) => recorder.record_counter(owned_key.clone(), *value),
|
||||
ValueSnapshot::Gauge(value) => recorder.record_gauge(owned_key.clone(), *value),
|
||||
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)]
|
||||
mod tests {
|
||||
use super::{MetricsSnapshot, Recorder, Snapshot, ValueSnapshot};
|
||||
use metrics_core::Key;
|
||||
use metrics_util::StreamingIntegers;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -56,18 +61,18 @@ mod tests {
|
|||
}
|
||||
|
||||
impl Recorder for MockRecorder {
|
||||
fn record_counter<K: AsRef<str>>(&mut self, key: K, value: u64) {
|
||||
let _ = self.counter.insert(key.as_ref().to_owned(), value);
|
||||
fn record_counter<K: Into<Key>>(&mut self, key: K, value: u64) {
|
||||
let _ = self.counter.insert(key.into().to_string(), value);
|
||||
}
|
||||
|
||||
fn record_gauge<K: AsRef<str>>(&mut self, key: K, value: i64) {
|
||||
let _ = self.gauge.insert(key.as_ref().to_owned(), value);
|
||||
fn record_gauge<K: Into<Key>>(&mut self, key: K, value: i64) {
|
||||
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
|
||||
.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},
|
||||
sink::Sink,
|
||||
};
|
||||
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;
|
||||
|
||||
thread_local! {
|
||||
static SINK: RefCell<Option<Sink>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// Central store for metrics.
|
||||
///
|
||||
/// `Receiver` is the nucleus for all metrics operations. While no operations are performed by it
|
||||
|
@ -52,6 +59,11 @@ impl Receiver {
|
|||
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.
|
||||
pub fn get_sink(&self) -> Sink {
|
||||
Sink::new(
|
||||
|
@ -67,3 +79,41 @@ impl Receiver {
|
|||
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