metrics_prometheus/storage/
immutable.rs

1//! Immutable storage of [`metric::Describable`].
2
3use std::{collections::HashMap, sync::Arc};
4
5use sealed::sealed;
6
7use super::KeyName;
8use crate::{Metric, metric};
9
10/// Collection of [`Describable`] [`metric::Bundle`]s, stored in an immutable
11/// [`Storage`].
12///
13/// [`Describable`]: metric::Describable
14pub type Collection<M> = HashMap<KeyName, metric::Describable<M>>;
15
16/// Snapshot of a [`mutable::Storage`], that is not capable of registering
17/// metrics in a [`prometheus::Registry`] on the fly.
18///
19/// However, still allows to change metrics [`help` description] on the fly,
20/// once they are registered in a [`mutable::Storage`] before snapshot.
21///
22/// In comparison with a [`metrics::Registry`], this immutable [`Storage`]
23/// provides much less overhead of accessing an already registered metric (just
24/// a simple [`HashMap`] lookup), however, is not capable of registering new
25/// metrics on the fly.
26///
27/// [`metrics::Registry`]: metrics_util::registry::Registry
28/// [`mutable::Storage`]: super::Mutable
29/// [`help` description]: prometheus::proto::MetricFamily::get_help
30#[derive(Debug)]
31pub struct Storage {
32    /// [`Collection`] of [`prometheus::IntCounter`] metrics registered in this
33    /// immutable [`Storage`].
34    counters: Collection<metric::PrometheusIntCounter>,
35
36    /// [`Collection`] of [`prometheus::Gauge`] metrics registered in this
37    /// immutable [`Storage`].
38    gauges: Collection<metric::PrometheusGauge>,
39
40    /// [`Collection`] of [`prometheus::Histogram`] metrics registered in this
41    /// immutable [`Storage`].
42    histograms: Collection<metric::PrometheusHistogram>,
43}
44
45#[sealed]
46impl super::Get<Collection<metric::PrometheusIntCounter>> for Storage {
47    fn collection(&self) -> &Collection<metric::PrometheusIntCounter> {
48        &self.counters
49    }
50}
51
52#[sealed]
53impl super::Get<Collection<metric::PrometheusGauge>> for Storage {
54    fn collection(&self) -> &Collection<metric::PrometheusGauge> {
55        &self.gauges
56    }
57}
58
59#[sealed]
60impl super::Get<Collection<metric::PrometheusHistogram>> for Storage {
61    fn collection(&self) -> &Collection<metric::PrometheusHistogram> {
62        &self.histograms
63    }
64}
65
66impl Storage {
67    /// Changes the [`help` description] of the [`prometheus`] `M`etric
68    /// identified by its `name`. No-op if this immutable [`Storage`] doesn't
69    /// contain it.
70    ///
71    /// Accepts only the following [`prometheus`] `M`etrics:
72    /// - [`prometheus::IntCounter`], [`prometheus::IntCounterVec`]
73    /// - [`prometheus::Gauge`], [`prometheus::GaugeVec`]
74    /// - [`prometheus::Histogram`], [`prometheus::HistogramVec`]
75    ///
76    /// Intended to be used in [`metrics::Recorder::describe_counter()`],
77    /// [`metrics::Recorder::describe_gauge()`] and
78    /// [`metrics::Recorder::describe_histogram()`] implementations.
79    ///
80    /// [`help` description]: prometheus::proto::MetricFamily::get_help
81    pub fn describe<M>(&self, name: &str, description: String)
82    where
83        M: metric::Bundled,
84        <M as metric::Bundled>::Bundle: metric::Bundle,
85        Self: super::Get<Collection<<M as metric::Bundled>::Bundle>>,
86    {
87        use super::Get as _;
88
89        if let Some(bundle) = self.collection().get(name) {
90            bundle.description.store(Arc::new(description));
91        }
92    }
93
94    /// Returns a [`prometheus`] `M`etric stored in this immutable [`Storage`]
95    /// and identified by the provided [`metrics::Key`].
96    ///
97    /// Accepts only the following [`prometheus`] metrics:
98    /// - [`prometheus::IntCounter`]
99    /// - [`prometheus::Gauge`]
100    /// - [`prometheus::Histogram`]
101    ///
102    /// Intended to be used in [`metrics::Recorder::register_counter()`],
103    /// [`metrics::Recorder::register_gauge()`] and
104    /// [`metrics::Recorder::register_histogram()`] implementations.
105    ///
106    /// # Errors
107    ///
108    /// If the identified [`prometheus`] `M`etric doesn't comply with the
109    /// labeling of the provided [`metrics::Key`].
110    pub fn get_metric<M>(
111        &self,
112        key: &metrics::Key,
113    ) -> Option<Result<Metric<M>, prometheus::Error>>
114    where
115        M: metric::Bundled,
116        <M as metric::Bundled>::Bundle: metric::Bundle<Single = M>,
117        Self: super::Get<Collection<<M as metric::Bundled>::Bundle>>,
118    {
119        use metric::Bundle as _;
120
121        use super::Get as _;
122
123        self.collection().get(key.name()).map(|bundle| {
124            bundle.metric.get_single_metric(key).map(Metric::wrap)
125        })
126    }
127}
128
129#[expect( // intentional
130    clippy::fallible_impl_from,
131    reason = "`RwLock` usage is fully panic-safe inside, so the `impl` is \
132              infallible, in fact"
133)]
134impl From<&super::mutable::Storage> for Storage {
135    /// Creates a new immutable [`Storage`] by [draining] the referred
136    /// [`mutable::Storage`] and leaving it empty.
137    ///
138    /// [`mutable::Storage`]: super::mutable::Storage
139    /// [draining]: HashMap::drain
140    #[expect( // intentional
141        clippy::unwrap_used,
142        reason = "`RwLock` usage is fully panic-safe here"
143    )]
144    fn from(mutable: &super::mutable::Storage) -> Self {
145        Self {
146            counters: mutable
147                .counters
148                .write()
149                .unwrap()
150                .drain()
151                .filter_map(|(name, bundle)| Some((name, bundle.transpose()?)))
152                .collect(),
153            gauges: mutable
154                .gauges
155                .write()
156                .unwrap()
157                .drain()
158                .filter_map(|(name, bundle)| Some((name, bundle.transpose()?)))
159                .collect(),
160            histograms: mutable
161                .histograms
162                .write()
163                .unwrap()
164                .drain()
165                .filter_map(|(name, bundle)| Some((name, bundle.transpose()?)))
166                .collect(),
167        }
168    }
169}