metrics_prometheus/
metric.rs

1//! Machinery around [`prometheus`] metrics for making them usable via
2//! [`metrics`] crate.
3
4use std::{iter, sync::Arc};
5
6use arc_swap::ArcSwap;
7use sealed::sealed;
8use smallvec::SmallVec;
9
10#[doc(inline)]
11pub use self::bundle::Bundle;
12use self::bundle::Either;
13
14/// Wrapper allowing implementing [`metrics::CounterFn`], [`metrics::GaugeFn`]
15/// and [`metrics::HistogramFn`] for [`prometheus`] metrics.
16#[derive(Clone, Copy, Debug)]
17pub struct Metric<M>(M);
18
19impl<M> Metric<M> {
20    /// Wraps the provided [`prometheus`] `metric`.
21    #[must_use]
22    pub const fn wrap(metric: M) -> Self {
23        Self(metric)
24    }
25
26    /// Unwraps this [`Metric`] returning its inner [`prometheus`] metric
27    #[must_use]
28    pub fn into_inner(self) -> M {
29        self.0
30    }
31}
32
33impl<M> AsRef<M> for Metric<M> {
34    fn as_ref(&self) -> &M {
35        &self.0
36    }
37}
38
39impl<M> AsMut<M> for Metric<M> {
40    fn as_mut(&mut self) -> &mut M {
41        &mut self.0
42    }
43}
44
45#[warn(clippy::missing_trait_methods)]
46impl metrics::CounterFn for Metric<prometheus::IntCounter> {
47    fn increment(&self, value: u64) {
48        self.0.inc_by(value);
49    }
50
51    fn absolute(&self, value: u64) {
52        // `prometheus::IntCounter` doesn't provide any atomic way to set its
53        // absolute value, so the implementation below may introduce races when
54        // two `.absolute()` operations content, leading to the incorrect value
55        // of a sum of two absolute values.
56        // However, considering that `.absolute()` operations should be quite
57        // rare, and so, rarely content, we do imply this trade-off as
58        // acceptable, for a while.
59        // TODO: Make a PR to `prometheus` crate allowing setting absolute value
60        //       atomically.
61        self.0.reset();
62        self.0.inc_by(value);
63    }
64}
65
66#[warn(clippy::missing_trait_methods)]
67impl metrics::GaugeFn for Metric<prometheus::Gauge> {
68    fn increment(&self, value: f64) {
69        self.0.add(value);
70    }
71
72    fn decrement(&self, value: f64) {
73        self.0.sub(value);
74    }
75
76    fn set(&self, value: f64) {
77        self.0.set(value);
78    }
79}
80
81#[warn(clippy::missing_trait_methods)]
82impl metrics::HistogramFn for Metric<prometheus::Histogram> {
83    fn record(&self, value: f64) {
84        self.0.observe(value);
85    }
86
87    fn record_many(&self, value: f64, count: usize) {
88        for _ in 0..count {
89            self.record(value);
90        }
91    }
92}
93
94/// Fallible [`Metric`] stored in [`metrics::Registry`].
95///
96/// We're obligated to store [`Fallible`] metrics inside [`metrics::Registry`],
97/// because panicking earlier, rather than inside directly called
98/// [`metrics::Recorder`] methods, will poison locks the [`metrics::Registry`]
99/// is built upon on.
100///
101/// [`metrics::Registry`]: metrics_util::registry::Registry
102#[derive(Debug)]
103pub struct Fallible<M>(pub Arc<prometheus::Result<Arc<Metric<M>>>>);
104
105// Manual implementation is required to omit the redundant `M: Clone` trait
106// bound imposed by `#[derive(Clone)]`.
107impl<M> Clone for Fallible<M> {
108    fn clone(&self) -> Self {
109        Self(Arc::clone(&self.0))
110    }
111}
112
113impl<M> From<prometheus::Result<Arc<Metric<M>>>> for Fallible<M> {
114    fn from(res: prometheus::Result<Arc<Metric<M>>>) -> Self {
115        Self(Arc::new(res))
116    }
117}
118
119impl<M> Fallible<M> {
120    /// Mimics [`Result::as_ref()`] method for this [`Fallible`].
121    ///
122    /// # Errors
123    ///
124    /// If this [`Fallible`] contains a [`prometheus::Error`].
125    pub fn as_ref(&self) -> Result<&Arc<Metric<M>>, &prometheus::Error> {
126        (*self.0).as_ref()
127    }
128}
129
130// Not really used, only implemented to satisfy
131// `metrics_util::registry::Storage` requirements for stored items.
132#[warn(clippy::missing_trait_methods)]
133impl<M> metrics::CounterFn for Fallible<M>
134where
135    Metric<M>: metrics::CounterFn,
136{
137    fn increment(&self, value: u64) {
138        if let Ok(m) = &*self.0 {
139            m.increment(value);
140        }
141    }
142
143    fn absolute(&self, value: u64) {
144        if let Ok(m) = &*self.0 {
145            m.absolute(value);
146        }
147    }
148}
149
150// Not really used, only implemented to satisfy
151// `metrics_util::registry::Storage` requirements for stored items.
152#[warn(clippy::missing_trait_methods)]
153impl<M> metrics::GaugeFn for Fallible<M>
154where
155    Metric<M>: metrics::GaugeFn,
156{
157    fn increment(&self, value: f64) {
158        if let Ok(m) = &*self.0 {
159            m.increment(value);
160        }
161    }
162
163    fn decrement(&self, value: f64) {
164        if let Ok(m) = &*self.0 {
165            m.decrement(value);
166        }
167    }
168
169    fn set(&self, value: f64) {
170        if let Ok(m) = &*self.0 {
171            m.set(value);
172        }
173    }
174}
175
176// Not really used, only implemented to satisfy
177// `metrics_util::registry::Storage` requirements for stored items.
178#[warn(clippy::missing_trait_methods)]
179impl<M> metrics::HistogramFn for Fallible<M>
180where
181    Metric<M>: metrics::HistogramFn,
182{
183    fn record(&self, value: f64) {
184        if let Ok(m) = &*self.0 {
185            m.record(value);
186        }
187    }
188
189    fn record_many(&self, value: f64, count: usize) {
190        if let Ok(m) = &*self.0 {
191            for _ in 0..count {
192                m.record(value);
193            }
194        }
195    }
196}
197
198/// [`prometheus`] metric with an ability to substitute its [`help` description]
199/// after registration in a [`prometheus::Registry`].
200///
201/// [`help` description]: prometheus::proto::MetricFamily::get_help
202#[derive(Clone, Debug, Default)]
203pub struct Describable<Metric> {
204    /// Swappable [`help` description] of the [`prometheus`] metric.
205    ///
206    /// [`help` description]: prometheus::proto::MetricFamily::get_help
207    pub(crate) description: Arc<ArcSwap<String>>,
208
209    /// [`prometheus`] metric itself.
210    pub(crate) metric: Metric,
211}
212
213impl<M> Describable<M> {
214    /// Wraps the provided [`prometheus`] `metric` into a [`Describable`] one.
215    #[must_use]
216    pub fn wrap(metric: M) -> Self {
217        Self { description: Arc::default(), metric }
218    }
219
220    /// Generates a [`Default`] [`prometheus`] metric with the provided
221    /// [`help` description].
222    ///
223    /// [`help` description]: prometheus::proto::MetricFamily::get_help
224    #[must_use]
225    pub fn only_description(help: impl Into<String>) -> Self
226    where
227        M: Default,
228    {
229        Self {
230            description: Arc::new(ArcSwap::new(Arc::new(help.into()))),
231            metric: M::default(),
232        }
233    }
234
235    /// Maps the wrapped [`prometheus`] metric `into` another one, preserving
236    /// the current overwritten [`help` description] (if any).
237    ///
238    /// [`help` description]: prometheus::proto::MetricFamily::get_help
239    #[must_use]
240    pub fn map<Into>(self, into: impl FnOnce(M) -> Into) -> Describable<Into> {
241        Describable { description: self.description, metric: into(self.metric) }
242    }
243}
244
245impl<M> Describable<Option<M>> {
246    /// Transposes this [`Describable`] [`Option`]al metric into an [`Option`]
247    /// of a [`Describable`] metric.
248    #[must_use]
249    pub fn transpose(self) -> Option<Describable<M>> {
250        self.metric
251            .map(|metric| Describable { description: self.description, metric })
252    }
253}
254
255#[warn(clippy::missing_trait_methods)]
256impl<M> prometheus::core::Collector for Describable<M>
257where
258    M: prometheus::core::Collector,
259{
260    fn desc(&self) -> Vec<&prometheus::core::Desc> {
261        // We could omit changing `help` field here, because `Collector::desc()`
262        // method is used by `prometheus::Registry` only for metrics
263        // registration and validation in its `.register()` and `.unregister()`
264        // methods. When `prometheus::Registry` `.gather()`s metrics, it invokes
265        // `Collector::collect()` method, where we do the actual `help` field
266        // substitution.
267        self.metric.desc()
268    }
269
270    fn collect(&self) -> Vec<prometheus::proto::MetricFamily> {
271        let mut out = self.metric.collect();
272        let new_help = self.description.load_full();
273        if !new_help.is_empty() {
274            for mf in &mut out {
275                mf.set_help((*new_help).clone());
276            }
277        }
278        out
279    }
280}
281
282/// Custom conversion trait to convert between foreign types.
283trait To<T> {
284    /// Converts this reference into a `T` value.
285    fn to(&self) -> T;
286}
287
288impl To<prometheus::Opts> for metrics::Key {
289    fn to(&self) -> prometheus::Opts {
290        // We use `key.name()` as `help` description here, because `prometheus`
291        // crate doesn't allow to make it empty.
292        prometheus::Opts::new(self.name(), self.name())
293    }
294}
295
296impl To<prometheus::HistogramOpts> for metrics::Key {
297    fn to(&self) -> prometheus::HistogramOpts {
298        // We use `key.name()` as `help` description here, because `prometheus`
299        // crate doesn't allow to make it empty.
300        prometheus::HistogramOpts::new(self.name(), self.name())
301    }
302}
303
304/// [`prometheus`] metric being [`Bundle`]d.
305#[sealed]
306pub trait Bundled {
307    /// Type of a [`Bundle`] bundling this [`prometheus`] metric.
308    type Bundle: Bundle;
309
310    /// Wraps this [`prometheus`] metric into its [`Bundle`].
311    fn into_bundle(self) -> Self::Bundle;
312}
313
314#[sealed]
315impl Bundled for prometheus::IntCounter {
316    type Bundle = PrometheusIntCounter;
317
318    fn into_bundle(self) -> Self::Bundle {
319        PrometheusIntCounter::Single(self)
320    }
321}
322
323#[sealed]
324impl Bundled for prometheus::IntCounterVec {
325    type Bundle = PrometheusIntCounter;
326
327    fn into_bundle(self) -> Self::Bundle {
328        PrometheusIntCounter::Vec(self)
329    }
330}
331
332#[sealed]
333impl Bundled for prometheus::Gauge {
334    type Bundle = PrometheusGauge;
335
336    fn into_bundle(self) -> Self::Bundle {
337        PrometheusGauge::Single(self)
338    }
339}
340
341#[sealed]
342impl Bundled for prometheus::GaugeVec {
343    type Bundle = PrometheusGauge;
344
345    fn into_bundle(self) -> Self::Bundle {
346        PrometheusGauge::Vec(self)
347    }
348}
349
350#[sealed]
351impl Bundled for prometheus::Histogram {
352    type Bundle = PrometheusHistogram;
353
354    fn into_bundle(self) -> Self::Bundle {
355        PrometheusHistogram::Single(self)
356    }
357}
358
359#[sealed]
360impl Bundled for prometheus::HistogramVec {
361    type Bundle = PrometheusHistogram;
362
363    fn into_bundle(self) -> Self::Bundle {
364        PrometheusHistogram::Vec(self)
365    }
366}
367
368/// [`Bundle`] of [`prometheus::IntCounter`] metrics.
369pub type PrometheusIntCounter =
370    Either<prometheus::IntCounter, prometheus::IntCounterVec>;
371
372impl TryFrom<&metrics::Key> for PrometheusIntCounter {
373    type Error = prometheus::Error;
374
375    fn try_from(key: &metrics::Key) -> Result<Self, Self::Error> {
376        let mut labels_iter = key.labels();
377        Ok(if let Some(first_label) = labels_iter.next() {
378            let label_names = iter::once(first_label)
379                .chain(labels_iter)
380                .map(metrics::Label::key)
381                .collect::<SmallVec<[_; 10]>>();
382            Self::Vec(prometheus::IntCounterVec::new(key.to(), &label_names)?)
383        } else {
384            Self::Single(prometheus::IntCounter::with_opts(key.to())?)
385        })
386    }
387}
388
389/// [`Bundle`] of [`prometheus::Gauge`] metrics.
390pub type PrometheusGauge = Either<prometheus::Gauge, prometheus::GaugeVec>;
391
392impl TryFrom<&metrics::Key> for PrometheusGauge {
393    type Error = prometheus::Error;
394
395    fn try_from(key: &metrics::Key) -> Result<Self, Self::Error> {
396        let mut labels_iter = key.labels();
397        Ok(if let Some(first_label) = labels_iter.next() {
398            let label_names = iter::once(first_label)
399                .chain(labels_iter)
400                .map(metrics::Label::key)
401                .collect::<SmallVec<[_; 10]>>();
402            Self::Vec(prometheus::GaugeVec::new(key.to(), &label_names)?)
403        } else {
404            Self::Single(prometheus::Gauge::with_opts(key.to())?)
405        })
406    }
407}
408
409/// [`Bundle`] of [`prometheus::Histogram`] metrics.
410pub type PrometheusHistogram =
411    Either<prometheus::Histogram, prometheus::HistogramVec>;
412
413impl TryFrom<&metrics::Key> for PrometheusHistogram {
414    type Error = prometheus::Error;
415
416    fn try_from(key: &metrics::Key) -> Result<Self, Self::Error> {
417        let mut labels_iter = key.labels();
418        Ok(if let Some(first_label) = labels_iter.next() {
419            let label_names = iter::once(first_label)
420                .chain(labels_iter)
421                .map(metrics::Label::key)
422                .collect::<SmallVec<[_; 10]>>();
423            Self::Vec(prometheus::HistogramVec::new(key.to(), &label_names)?)
424        } else {
425            Self::Single(prometheus::Histogram::with_opts(key.to())?)
426        })
427    }
428}
429
430/// Definitions of [`Bundle`] machinery.
431pub mod bundle {
432    use std::{collections::HashMap, hash::BuildHasher};
433
434    use sealed::sealed;
435
436    /// Either a single [`prometheus::Metric`] or a [`prometheus::MetricVec`] of
437    /// them, forming a [`Bundle`].
438    ///
439    /// [`prometheus::Metric`]: prometheus::core::Metric
440    /// [`prometheus::MetricVec`]: prometheus::core::MetricVec
441    #[derive(Clone, Copy, Debug)]
442    pub enum Either<Single, Vec> {
443        /// Single [`prometheus::Metric`].
444        ///
445        /// [`prometheus::Metric`]: prometheus::core::Metric
446        Single(Single),
447
448        /// [`prometheus::MetricVec`] of [`prometheus::Metric`]s.
449        ///
450        /// [`prometheus::Metric`]: prometheus::core::Metric
451        /// [`prometheus::MetricVec`]: prometheus::core::MetricVec
452        Vec(Vec),
453    }
454
455    #[warn(clippy::missing_trait_methods)]
456    impl<S, V> prometheus::core::Collector for Either<S, V>
457    where
458        S: prometheus::core::Collector,
459        V: prometheus::core::Collector,
460    {
461        fn desc(&self) -> Vec<&prometheus::core::Desc> {
462            match self {
463                Self::Single(m) => m.desc(),
464                Self::Vec(v) => v.desc(),
465            }
466        }
467
468        fn collect(&self) -> Vec<prometheus::proto::MetricFamily> {
469            match self {
470                Self::Single(m) => m.collect(),
471                Self::Vec(v) => v.collect(),
472            }
473        }
474    }
475
476    /// [`prometheus::MetricVec`] of [`prometheus::Metric`]s.
477    ///
478    /// [`prometheus::Metric`]: prometheus::core::Metric
479    /// [`prometheus::MetricVec`]: prometheus::core::MetricVec
480    #[sealed]
481    pub trait MetricVec {
482        /// Type of [`prometheus::Metric`]s forming this [`MetricVec`].
483        ///
484        /// [`prometheus::Metric`]: prometheus::core::Metric
485        type Metric: prometheus::core::Metric;
486
487        /// Calls [`prometheus::MetricVec::get_metric_with()`][0] method of this
488        /// [`MetricVec`].
489        ///
490        /// # Errors
491        ///
492        /// If a [`prometheus::Metric`] cannot be identified or created for the
493        /// provided label `values`.
494        ///
495        /// [`prometheus::Metric`]: prometheus::core::Metric
496        /// [0]: prometheus::core::MetricVec::get_metric_with()
497        fn get_metric_with<S: BuildHasher>(
498            &self,
499            labels: &HashMap<&str, &str, S>,
500        ) -> prometheus::Result<Self::Metric>;
501    }
502
503    #[sealed]
504    impl<M, B> MetricVec for prometheus::core::MetricVec<B>
505    where
506        M: prometheus::core::Metric,
507        B: prometheus::core::MetricVecBuilder<M = M>,
508    {
509        type Metric = M;
510
511        fn get_metric_with<S: BuildHasher>(
512            &self,
513            labels: &HashMap<&str, &str, S>,
514        ) -> prometheus::Result<M> {
515            self.get_metric_with(labels)
516        }
517    }
518
519    /// Bundle of a [`prometheus::Metric`]s family.
520    ///
521    /// [`Either`] a single [`prometheus::Metric`] or a
522    /// [`prometheus::MetricVec`] of them.
523    ///
524    /// [`prometheus::Metric`]: prometheus::core::Metric
525    /// [`prometheus::MetricVec`]: prometheus::core::MetricVec
526    #[sealed]
527    pub trait Bundle {
528        /// Type of single [`prometheus::Metric`] that may be stored in this
529        /// [`Bundle`].
530        ///
531        /// [`prometheus::Metric`]: prometheus::core::Metric
532        type Single: prometheus::core::Metric;
533
534        /// Type of [`prometheus::MetricVec`] that may be stored in this
535        /// [`Bundle`].
536        ///
537        /// [`prometheus::MetricVec`]: prometheus::core::MetricVec
538        type Vec: MetricVec<Metric = Self::Single>;
539
540        /// Returns a single [`prometheus::Metric`] of this [`Bundle`],
541        /// identified by the provided [`metrics::Key`].
542        ///
543        /// # Errors
544        ///
545        /// If the provided [`metrics::Key`] cannot identify any
546        /// [`prometheus::Metric`] in this [`Bundle`].
547        ///
548        /// [`prometheus::Metric`]: prometheus::core::Metric
549        fn get_single_metric(
550            &self,
551            key: &metrics::Key,
552        ) -> prometheus::Result<Self::Single>;
553    }
554
555    #[sealed]
556    impl<M, B> Bundle for Either<M, prometheus::core::MetricVec<B>>
557    where
558        M: prometheus::core::Metric + Clone,
559        B: prometheus::core::MetricVecBuilder<M = M>,
560    {
561        type Single = M;
562        type Vec = prometheus::core::MetricVec<B>;
563
564        fn get_single_metric(
565            &self,
566            key: &metrics::Key,
567        ) -> prometheus::Result<M> {
568            match self {
569                Self::Single(c) => {
570                    if key.labels().next().is_some() {
571                        return Err(
572                            prometheus::Error::InconsistentCardinality {
573                                expect: 0,
574                                got: key.labels().count(),
575                            },
576                        );
577                    }
578                    Ok(c.clone())
579                }
580                Self::Vec(v) => {
581                    // TODO: Experiment with more rapid hash? See:
582                    //       https://github.com/tikv/rust-prometheus/pull/532
583                    let labels = key
584                        .labels()
585                        .map(|l| (l.key(), l.value()))
586                        .collect::<HashMap<_, _>>();
587                    v.get_metric_with(&labels)
588                }
589            }
590        }
591    }
592}