metrics
] + [prometheus
] = ❤️[prometheus
] backend for [metrics
] crate.
[Rust] has at least two ecosystems regarding metrics collection:
- One is based on the [prometheus
] crate, focusing on delivering metrics to [Prometheus] (or its drop-in replacements like [VictoriaMetrics]). It provides a lot of [Prometheus]-specific capabilities and validates metrics strictly to meet the format used by [Prometheus].
- Another one is based on the [metrics
] crate, being more generic and targeting a wider scope, rather than [Prometheus] only. It provides a convenient and ergonomic facade, allowing to work with metrics in the very similar way we do work with logs and traces via [log
]/[tracing
] ecosystems (and even [supports tracing::Span
s for metrics labels][metrics-tracing-context
]).
As the result, some crates use [prometheus
] crate for providing their metrics, and another crates do use [metrics
] crate for that. Furthermore, [prometheus
] and [metrics
] crates are designed quite differently, making their composition a non-trivial task. This crate aims to mitigate this gap, allowing to combine both [prometheus
] and [metrics
] ecosystems in a single project.
If you're not obligated to deal with [prometheus
] crate directly or via third-party crates which do use it, consider the [metrics-exporter-prometheus
] crate, which provides a simple [Prometheus] backend for [metrics
] facade, without bringing in the whole [prometheus
] crate's machinery.
This crate provides a [metrics::Recorder
] implementation, allowing to work with a [prometheus::Registry
] via [metrics
] facade.
It comes in 3 flavours, allowing to choose the smallest performance overhead depending on a use case:
- Regular [Recorder
], allowing to create new metrics via [metrics
] facade anytime, without limits. Provides the same overhead of accessing an already registered metric as a [metrics::Registry
] does: [read
-lock] on a sharded [HashMap
] plus [Arc
] cloning.
- [FrozenRecorder
], unable to create new metrics via [metrics
] facade at all (just no-op in such case). Provides the smallest overhead of accessing an already registered metric: just a regular [HashMap
] lookup plus [Arc
] cloning.
- [FreezableRecorder
], acting the same way as the [Recorder
] at first, but being able to [.freeze()
] and so, becoming a [FrozenRecorder
] at the end. The overhead of accessing an already registered metric is the same as [Recorder
] and [FrozenRecorder
] provide, plus [AtomicBool
] loading to check whether it has been [.freeze()
]d.
Not any [prometheus
] metric is supported, because [metrics
] crate implies only few of them. This is how the [metrics
] crate's metrics are mapped onto [prometheus
] ones:
- [metrics::Counter
]: [prometheus::IntCounter
] + [prometheus::IntCounterVec
]
- [metrics::Gauge
]: [prometheus::Gauge
] + [prometheus::GaugeVec
]
- [metrics::Histogram
]: [prometheus::Histogram
] + [prometheus::HistogramVec
]
[prometheus::MetricVec
] types are used whenever any labels are specified via [metrics
] facade.
To satisfy the [metrics::Recorder
]'s requirement of allowing changing metrics description anytime after its registration ([prometheus
] crate doesn't imply and allow that), the [Describable
] wrapper is used, allowing to [arc-swap
] the description.
``rust
// By default
prometheus::defaultregistry()` is used.
let recorder = metricsprometheus::install();
// Either use metrics
crate interfaces.
metrics::incrementcounter!("count", "whose" => "mine", "kind" => "owned");
metrics::incrementcounter!("count", "whose" => "mine", "kind" => "ref");
metrics::increment_counter!("count", "kind" => "owned", "whose" => "dummy");
// Or construct and provide prometheus
metrics directly.
recorder.register_metric(prometheus::Gauge::new("value", "help")?);
let report = prometheus::TextEncoder::new() .encodetostring(&prometheus::defaultregistry().gather())?; asserteq!( report.trim(), r#"
count{kind="owned",whose="dummy"} 1 count{kind="owned",whose="mine"} 1 count{kind="ref",whose="mine"} 1
value 0 "# .trim(), );
// Metrics can be described anytime after being registered in
// prometheus::Registry
.
metrics::describecounter!("count", "Example of counter.");
metrics::describegauge!("value", "Example of gauge.");
let report = prometheus::TextEncoder::new() .encodetostring(&recorder.registry().gather())?; assert_eq!( report.trim(), r#"
count{kind="owned",whose="dummy"} 1 count{kind="owned",whose="mine"} 1 count{kind="ref",whose="mine"} 1
value 0 "# .trim(), );
// Description can be changed multiple times and anytime. metrics::describe_counter!("count", "Another description.");
// Even before a metric is registered in prometheus::Registry
.
metrics::describecounter!("another", "Yet another counter.");
metrics::incrementcounter!("another");
let report = prometheus::TextEncoder::new() .encodetostring(&recorder.registry().gather())?; assert_eq!( report.trim(), r#"
another 1
count{kind="owned",whose="dummy"} 1 count{kind="owned",whose="mine"} 1 count{kind="ref",whose="mine"} 1
value 0 "# .trim(), );
```
Since [prometheus
] crate validates the metrics format very strictly, not everything, expressed via [metrics
] facade, may be put into a [prometheus::Registry
], ending up with a [prometheus::Error
] being emitted.
Metric names cannot be namespaced with dots (and should follow [Prometheus] format). ```rust,shouldpanic metricsprometheus::install();
// panics: 'queries.count' is not a valid metric name metrics::increment_counter!("queries.count"); ```
The same metric should use always the same set of labels: ```rust,shouldpanic metricsprometheus::install();
metrics::incrementcounter!("count");
// panics: Inconsistent label cardinality, expect 0 label values, but got 1
metrics::incrementcounter!("count", "whose" => "mine");
rust,shouldpanic
metricsprometheus::install();
metrics::incrementcounter!("count", "kind" => "owned");
// panics: label name kind missing in label map
metrics::incrementcounter!("count", "whose" => "mine");
rust,shouldpanic
metricsprometheus::install();
metrics::incrementcounter!("count", "kind" => "owned"); // panics: Inconsistent label cardinality, expect 1 label values, but got 2 metrics::incrementcounter!("count", "kind" => "ref", "whose" => "mine"); ```
The same name cannot be used for different types of metrics: ```rust,shouldpanic metricsprometheus::install();
metrics::incrementcounter!("count"); // panics: Duplicate metrics collector registration attempted metrics::incrementgauge!("count", 1.0); ```
Any metric registered in a [prometheus::Registry
] directly, without using [metrics
] or this crate interfaces, is not usable via [metrics
] facade and will cause a [prometheus::Error
].
```rust,shouldpanic
metricsprometheus::install();
prometheus::default_registry() .register(Box::new(prometheus::Gauge::new("value", "help")?))?;
// panics: Duplicate metrics collector registration attempted metrics::increment_gauge!("value", 4.5);
```
[metrics::Unit
]s are not supported, as [Prometheus] has no notion of ones. Specifying them via [metrics
] macros will be no-op.
prometheus::Error
] handlingSince [metrics::Recorder
] doesn't expose any errors in its API, the emitted [prometheus::Error
]s can be either turned into a panic, or just silently ignored, returning a no-op metric instead (see [metrics::Counter::noop()
] for example).
This can be tuned by providing a [failure::Strategy
] when building a [Recorder
].
```rust use metrics_prometheus::failure::strategy;
metricsprometheus::Recorder::builder() .withfailurestrategy(strategy::NoOp) .buildand_install();
// prometheus::Error
is ignored inside.
metrics::increment_counter!("invalid.name");
let stats = prometheus::defaultregistry().gather(); asserteq!(stats.len(), 0); ```
The default [failure::Strategy
] is [PanicInDebugNoOpInRelease
]. See [failure::strategy
] module for other available [failure::Strategy
]s,
or provide your own one by implementing the [failure::Strategy
] trait.
Copyright © 2022 Instrumentisto Team, https://github.com/instrumentisto
This software is subject to the terms of the Blue Oak Model License 1.0.0. If a copy of the BlueOak-1.0.0 license was not distributed with this file, You can obtain one at https://blueoakcouncil.org/license/1.0.0.