mz_ore/metrics/
deregister_on_drop.rs1use std::collections::BTreeMap;
28use std::fmt::{self, Debug, Formatter};
29use std::ops::Deref;
30use std::sync::{Arc, Mutex, Weak};
31
32use prometheus::HistogramVec;
33use prometheus::core::{Collector, Desc, GenericCounterVec, GenericGaugeVec};
34use prometheus::proto::MetricFamily;
35
36use crate::metrics::{MakeCollector, MakeCollectorOpts, MetricVec_};
37
38type Registrations<V> = Arc<Mutex<BTreeMap<Vec<String>, Weak<Registration<V>>>>>;
40
41#[derive(Clone)]
45pub struct DeregisterOnDropVec<V: MetricVec_> {
46 inner: V,
47 registrations: Registrations<V>,
48}
49
50impl<V: MetricVec_ + Debug> Debug for DeregisterOnDropVec<V> {
51 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
52 self.inner.fmt(f)
53 }
54}
55
56impl<V: MetricVec_ + Collector> Collector for DeregisterOnDropVec<V> {
57 fn desc(&self) -> Vec<&Desc> {
58 self.inner.desc()
59 }
60
61 fn collect(&self) -> Vec<MetricFamily> {
62 self.inner.collect()
63 }
64}
65
66impl<V: MetricVec_ + MakeCollector> MakeCollector for DeregisterOnDropVec<V> {
67 fn make_collector(opts: MakeCollectorOpts) -> Self {
68 DeregisterOnDropVec {
69 inner: V::make_collector(opts),
70 registrations: Default::default(),
71 }
72 }
73}
74
75impl<V: MetricVec_ + Clone> DeregisterOnDropVec<V> {
76 pub fn get_deregister_on_drop_metric(&self, labels: Vec<String>) -> DeregisterOnDropMetric<V> {
81 let mut registrations = self.registrations.lock().expect("lock poisoned");
82 let inner = {
85 let values: Vec<&str> = labels.iter().map(String::as_str).collect();
86 self.inner
87 .get_metric_with_label_values(&values)
88 .expect("retrieving a metric by label values")
89 };
90 let registration = match registrations.get(&labels).and_then(Weak::upgrade) {
91 Some(registration) => registration,
92 None => {
93 let registration = Arc::new(Registration {
94 labels: labels.clone(),
95 vec: self.clone(),
96 });
97 registrations.insert(labels, Arc::downgrade(®istration));
98 registration
99 }
100 };
101 DeregisterOnDropMetric {
102 inner,
103 _registration: registration,
104 }
105 }
106}
107
108struct Registration<V: MetricVec_> {
110 labels: Vec<String>,
111 vec: DeregisterOnDropVec<V>,
112}
113
114impl<V: MetricVec_> Debug for Registration<V> {
115 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
116 f.debug_struct("Registration")
117 .field("labels", &self.labels)
118 .finish_non_exhaustive()
119 }
120}
121
122impl<V: MetricVec_> Drop for Registration<V> {
123 fn drop(&mut self) {
124 let mut registrations = self.vec.registrations.lock().expect("lock poisoned");
125 if registrations
128 .get(&self.labels)
129 .is_some_and(|live| live.strong_count() > 0)
130 {
131 return;
132 }
133 registrations.remove(&self.labels);
134 let values: Vec<&str> = self.labels.iter().map(String::as_str).collect();
135 let _ = self.vec.inner.remove_label_values(&values);
137 }
138}
139
140#[derive(Debug, Clone)]
142pub struct DeregisterOnDropMetric<V: MetricVec_> {
143 inner: V::M,
144 _registration: Arc<Registration<V>>,
145}
146
147impl<V: MetricVec_> Deref for DeregisterOnDropMetric<V> {
148 type Target = V::M;
149
150 fn deref(&self) -> &Self::Target {
151 &self.inner
152 }
153}
154
155pub type DeregisterOnDropGauge<P> = DeregisterOnDropMetric<GenericGaugeVec<P>>;
158
159pub type DeregisterOnDropCounter<P> = DeregisterOnDropMetric<GenericCounterVec<P>>;
162
163pub type DeregisterOnDropHistogram = DeregisterOnDropMetric<HistogramVec>;
166
167#[cfg(test)]
168mod test {
169 use prometheus::IntCounterVec;
170
171 use crate::metric;
172 use crate::metrics::MetricsRegistry;
173
174 use super::*;
175
176 fn labels(value: &str) -> Vec<String> {
177 vec![value.to_string()]
178 }
179
180 #[crate::test]
181 fn independent_handles_share_registration() {
182 let reg = MetricsRegistry::new();
183 let vec: DeregisterOnDropVec<IntCounterVec> = reg.register(metric!(
184 name: "test_metric",
185 help: "a test metric",
186 var_labels: ["dimension"]));
187
188 let first = vec.get_deregister_on_drop_metric(labels("one"));
189 let second = vec.get_deregister_on_drop_metric(labels("one"));
190 first.inc();
191 second.inc();
192
193 drop(first);
194 let gathered = reg.gather();
195 assert_eq!(
196 gathered.len(),
197 1,
198 "series must survive while a handle is alive"
199 );
200 assert_eq!(gathered[0].get_metric()[0].get_counter().value(), 2.0);
201
202 second.inc();
203 let gathered = reg.gather();
204 assert_eq!(gathered[0].get_metric()[0].get_counter().value(), 3.0);
205
206 drop(second);
207 assert_eq!(
208 reg.gather().len(),
209 0,
210 "last handle must deregister the series"
211 );
212 assert!(vec.registrations.lock().expect("lock poisoned").is_empty());
213 }
214
215 #[crate::test]
216 fn label_sets_are_independent() {
217 let reg = MetricsRegistry::new();
218 let vec: DeregisterOnDropVec<IntCounterVec> = reg.register(metric!(
219 name: "test_metric",
220 help: "a test metric",
221 var_labels: ["dimension"]));
222
223 let one = vec.get_deregister_on_drop_metric(labels("one"));
224 let two = vec.get_deregister_on_drop_metric(labels("two"));
225 one.inc();
226 two.inc();
227
228 drop(one);
229 let gathered = reg.gather();
230 let series = gathered[0].get_metric();
231 assert_eq!(series.len(), 1);
232 assert_eq!(series[0].get_label()[0].value(), "two");
233
234 drop(two);
235 assert_eq!(reg.gather().len(), 0);
236 }
237
238 #[crate::test]
239 fn reregistration_after_last_drop_starts_fresh() {
240 let reg = MetricsRegistry::new();
241 let vec: DeregisterOnDropVec<IntCounterVec> = reg.register(metric!(
242 name: "test_metric",
243 help: "a test metric",
244 var_labels: ["dimension"]));
245
246 let first = vec.get_deregister_on_drop_metric(labels("one"));
247 first.inc_by(5);
248 drop(first);
249
250 let second = vec.get_deregister_on_drop_metric(labels("one"));
251 second.inc();
252 let gathered = reg.gather();
253 assert_eq!(gathered[0].get_metric()[0].get_counter().value(), 1.0);
254 }
255}