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}