1use std::borrow::Borrow;
31use std::collections::BTreeMap;
32use std::ops::Deref;
33
34use prometheus::core::{
35 Atomic, GenericCounter, GenericCounterVec, GenericGauge, GenericGaugeVec, Metric, MetricVec,
36 MetricVecBuilder,
37};
38use prometheus::{Histogram, HistogramVec};
39
40#[allow(clippy::disallowed_types)]
43type PromLabelMap<'a> = std::collections::HashMap<&'a str, &'a str>;
44
45pub trait MetricVec_: Sized {
47 type M: Metric;
49
50 fn get_metric_with_label_values(&self, vals: &[&str]) -> Result<Self::M, prometheus::Error>;
52
53 fn get_metric_with(&self, labels: &PromLabelMap) -> Result<Self::M, prometheus::Error>;
55
56 fn remove_label_values(&self, vals: &[&str]) -> Result<(), prometheus::Error>;
58
59 fn remove(&self, labels: &PromLabelMap) -> Result<(), prometheus::Error>;
61}
62
63impl<P: MetricVecBuilder> MetricVec_ for MetricVec<P> {
64 type M = P::M;
65
66 fn get_metric_with_label_values(&self, vals: &[&str]) -> prometheus::Result<Self::M> {
67 self.get_metric_with_label_values(vals)
68 }
69
70 fn get_metric_with(&self, labels: &PromLabelMap) -> Result<Self::M, prometheus::Error> {
71 self.get_metric_with(labels)
72 }
73
74 fn remove_label_values(&self, vals: &[&str]) -> Result<(), prometheus::Error> {
75 self.remove_label_values(vals)
76 }
77
78 fn remove(&self, labels: &PromLabelMap) -> Result<(), prometheus::Error> {
79 self.remove(labels)
80 }
81}
82
83pub trait MetricVecExt: MetricVec_ {
88 fn get_delete_on_drop_metric<L: PromLabelsExt>(&self, labels: L)
90 -> DeleteOnDropMetric<Self, L>;
91}
92
93impl<V: MetricVec_ + Clone> MetricVecExt for V {
94 fn get_delete_on_drop_metric<L>(&self, labels: L) -> DeleteOnDropMetric<Self, L>
95 where
96 L: PromLabelsExt,
97 {
98 DeleteOnDropMetric::from_metric_vector(self.clone(), labels)
99 }
100}
101
102pub trait PromLabelsExt {
105 fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M;
108
109 fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error>;
111}
112
113impl PromLabelsExt for &[&str] {
114 fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M {
115 vec.get_metric_with_label_values(self)
116 .expect("retrieving a metric by label values")
117 }
118
119 fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error> {
120 vec.remove_label_values(self)
121 }
122}
123
124impl PromLabelsExt for Vec<String> {
125 fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M {
126 let labels: Vec<&str> = self.iter().map(String::as_str).collect();
127 vec.get_metric_with_label_values(labels.as_slice())
128 .expect("retrieving a metric by label values")
129 }
130
131 fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error> {
132 let labels: Vec<&str> = self.iter().map(String::as_str).collect();
133 vec.remove_label_values(labels.as_slice())
134 }
135}
136
137impl PromLabelsExt for Vec<&str> {
138 fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M {
139 vec.get_metric_with_label_values(self.as_slice())
140 .expect("retrieving a metric by label values")
141 }
142
143 fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error> {
144 vec.remove_label_values(self.as_slice())
145 }
146}
147
148impl PromLabelsExt for BTreeMap<String, String> {
149 fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M {
150 let labels = self.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
151 vec.get_metric_with(&labels)
152 .expect("retrieving a metric by label values")
153 }
154
155 fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error> {
156 let labels = self.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
157 vec.remove(&labels)
158 }
159}
160
161impl PromLabelsExt for BTreeMap<&str, &str> {
162 fn get_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> V::M {
163 let labels = self.iter().map(|(k, v)| (*k, *v)).collect();
164 vec.get_metric_with(&labels)
165 .expect("retrieving a metric by label values")
166 }
167
168 fn remove_from_metric_vec<V: MetricVec_>(&self, vec: &V) -> Result<(), prometheus::Error> {
169 let labels = self.iter().map(|(k, v)| (*k, *v)).collect();
170 vec.remove(&labels)
171 }
172}
173
174#[derive(Debug, Clone)]
183pub struct DeleteOnDropMetric<V, L>
184where
185 V: MetricVec_,
186 L: PromLabelsExt,
187{
188 inner: V::M,
189 labels: L,
190 vec: V,
191}
192
193impl<V, L> DeleteOnDropMetric<V, L>
194where
195 V: MetricVec_,
196 L: PromLabelsExt,
197{
198 fn from_metric_vector(vec: V, labels: L) -> Self {
199 let inner = labels.get_from_metric_vec(&vec);
200 Self { inner, labels, vec }
201 }
202}
203
204impl<V, L> Deref for DeleteOnDropMetric<V, L>
205where
206 V: MetricVec_,
207 L: PromLabelsExt,
208{
209 type Target = V::M;
210
211 fn deref(&self) -> &Self::Target {
212 &self.inner
213 }
214}
215
216impl<V, L> Drop for DeleteOnDropMetric<V, L>
217where
218 V: MetricVec_,
219 L: PromLabelsExt,
220{
221 fn drop(&mut self) {
222 if self.labels.remove_from_metric_vec(&self.vec).is_err() {
223 }
225 }
226}
227
228pub type DeleteOnDropCounter<P, L> = DeleteOnDropMetric<GenericCounterVec<P>, L>;
230
231impl<P, L> Borrow<GenericCounter<P>> for DeleteOnDropCounter<P, L>
232where
233 P: Atomic,
234 L: PromLabelsExt,
235{
236 fn borrow(&self) -> &GenericCounter<P> {
237 &self.inner
238 }
239}
240
241pub type DeleteOnDropGauge<P, L> = DeleteOnDropMetric<GenericGaugeVec<P>, L>;
243
244impl<P, L> Borrow<GenericGauge<P>> for DeleteOnDropGauge<P, L>
245where
246 P: Atomic,
247 L: PromLabelsExt,
248{
249 fn borrow(&self) -> &GenericGauge<P> {
250 &self.inner
251 }
252}
253
254pub type DeleteOnDropHistogram<L> = DeleteOnDropMetric<HistogramVec, L>;
256
257impl<L> Borrow<Histogram> for DeleteOnDropHistogram<L>
258where
259 L: PromLabelsExt,
260{
261 fn borrow(&self) -> &Histogram {
262 &self.inner
263 }
264}
265
266#[cfg(test)]
267mod test {
268 use prometheus::IntGaugeVec;
269 use prometheus::core::{AtomicI64, AtomicU64};
270
271 use crate::metric;
272 use crate::metrics::{IntCounterVec, MetricsRegistry};
273
274 use super::*;
275
276 #[crate::test]
277 fn dropping_counters() {
278 let reg = MetricsRegistry::new();
279 let vec: IntCounterVec = reg.register(metric!(
280 name: "test_metric",
281 help: "a test metric",
282 var_labels: ["dimension"]));
283
284 let dims: &[&str] = &["one"];
285 let metric_1 = vec.get_delete_on_drop_metric(dims);
286 metric_1.inc();
287
288 let metrics = reg.gather();
289 assert_eq!(metrics.len(), 1);
290 let reported_vec = &metrics[0];
291 assert_eq!(reported_vec.get_name(), "test_metric");
292 let dims = reported_vec.get_metric();
293 assert_eq!(dims.len(), 1);
294 assert_eq!(dims[0].get_label()[0].get_value(), "one");
295
296 drop(metric_1);
297 let metrics = reg.gather();
298 assert_eq!(metrics.len(), 0);
299
300 let string_labels: Vec<String> = ["owned"].iter().map(ToString::to_string).collect();
301 struct Ownership {
302 counter: DeleteOnDropCounter<AtomicU64, Vec<String>>,
303 }
304 let metric_owned = Ownership {
305 counter: vec.get_delete_on_drop_metric(string_labels),
306 };
307 metric_owned.counter.inc();
308
309 let metrics = reg.gather();
310 assert_eq!(metrics.len(), 1);
311 let reported_vec = &metrics[0];
312 assert_eq!(reported_vec.get_name(), "test_metric");
313 let dims = reported_vec.get_metric();
314 assert_eq!(dims.len(), 1);
315 assert_eq!(dims[0].get_label()[0].get_value(), "owned");
316
317 drop(metric_owned);
318 let metrics = reg.gather();
319 assert_eq!(metrics.len(), 0);
320 }
321
322 #[crate::test]
323 fn dropping_gauges() {
324 let reg = MetricsRegistry::new();
325 let vec: IntGaugeVec = reg.register(metric!(
326 name: "test_metric",
327 help: "a test metric",
328 var_labels: ["dimension"]));
329
330 let dims: &[&str] = &["one"];
331 let metric_1 = vec.get_delete_on_drop_metric(dims);
332 metric_1.set(666);
333
334 let metrics = reg.gather();
335 assert_eq!(metrics.len(), 1);
336 let reported_vec = &metrics[0];
337 assert_eq!(reported_vec.get_name(), "test_metric");
338 let dims = reported_vec.get_metric();
339 assert_eq!(dims.len(), 1);
340 assert_eq!(dims[0].get_label()[0].get_value(), "one");
341
342 drop(metric_1);
343 let metrics = reg.gather();
344 assert_eq!(metrics.len(), 0);
345
346 let string_labels: Vec<String> = ["owned"].iter().map(ToString::to_string).collect();
347 struct Ownership {
348 gauge: DeleteOnDropGauge<AtomicI64, Vec<String>>,
349 }
350 let metric_owned = Ownership {
351 gauge: vec.get_delete_on_drop_metric(string_labels),
352 };
353 metric_owned.gauge.set(666);
354
355 let metrics = reg.gather();
356 assert_eq!(metrics.len(), 1);
357 let reported_vec = &metrics[0];
358 assert_eq!(reported_vec.get_name(), "test_metric");
359 let dims = reported_vec.get_metric();
360 assert_eq!(dims.len(), 1);
361 assert_eq!(dims[0].get_label()[0].get_value(), "owned");
362
363 drop(metric_owned);
364 let metrics = reg.gather();
365 assert_eq!(metrics.len(), 0);
366 }
367}