risingwave_common_metrics/
guarded_metrics.rs1use std::any::type_name;
16use std::collections::{HashMap, HashSet};
17use std::fmt::{Debug, Formatter};
18use std::ops::Deref;
19use std::sync::{Arc, LazyLock};
20
21use parking_lot::Mutex;
22use prometheus::core::{
23 Atomic, AtomicF64, AtomicI64, AtomicU64, Collector, Desc, GenericCounter, GenericLocalCounter,
24 MetricVec, MetricVecBuilder,
25};
26use prometheus::local::{LocalHistogram, LocalIntCounter};
27use prometheus::proto::MetricFamily;
28use prometheus::{Gauge, Histogram, IntCounter, IntGauge};
29use thiserror_ext::AsReport;
30use tracing::warn;
31
32#[macro_export]
33macro_rules! register_guarded_histogram_vec_with_registry {
34 ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{
35 $crate::register_guarded_histogram_vec_with_registry! {
36 {prometheus::histogram_opts!($NAME, $HELP)},
37 $LABELS_NAMES,
38 $REGISTRY
39 }
40 }};
41 ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $BUCKETS:expr, $REGISTRY:expr $(,)?) => {{
42 $crate::register_guarded_histogram_vec_with_registry! {
43 {prometheus::histogram_opts!($NAME, $HELP, $BUCKETS)},
44 $LABELS_NAMES,
45 $REGISTRY
46 }
47 }};
48 ($HOPTS:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{
49 let inner = prometheus::HistogramVec::new($HOPTS, $LABELS_NAMES);
50 inner.and_then(|inner| {
51 let inner = $crate::__extract_histogram_builder(inner);
52 let label_guarded = $crate::LabelGuardedHistogramVec::new(inner, { $LABELS_NAMES });
53 let result = ($REGISTRY).register(Box::new(label_guarded.clone()));
54 result.map(move |()| label_guarded)
55 })
56 }};
57}
58
59#[macro_export]
60macro_rules! register_guarded_gauge_vec_with_registry {
61 ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{
62 let inner = prometheus::GaugeVec::new(prometheus::opts!($NAME, $HELP), $LABELS_NAMES);
63 inner.and_then(|inner| {
64 let inner = $crate::__extract_gauge_builder(inner);
65 let label_guarded = $crate::LabelGuardedGaugeVec::new(inner, { $LABELS_NAMES });
66 let result = ($REGISTRY).register(Box::new(label_guarded.clone()));
67 result.map(move |()| label_guarded)
68 })
69 }};
70}
71
72#[macro_export]
73macro_rules! register_guarded_int_gauge_vec_with_registry {
74 ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{
75 let inner = prometheus::IntGaugeVec::new(prometheus::opts!($NAME, $HELP), $LABELS_NAMES);
76 inner.and_then(|inner| {
77 let inner = $crate::__extract_gauge_builder(inner);
78 let label_guarded = $crate::LabelGuardedIntGaugeVec::new(inner, { $LABELS_NAMES });
79 let result = ($REGISTRY).register(Box::new(label_guarded.clone()));
80 result.map(move |()| label_guarded)
81 })
82 }};
83}
84
85#[macro_export]
86macro_rules! register_guarded_uint_gauge_vec_with_registry {
87 ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{
88 let inner = prometheus::core::GenericGaugeVec::<prometheus::core::AtomicU64>::new(
89 prometheus::opts!($NAME, $HELP),
90 $LABELS_NAMES,
91 );
92 inner.and_then(|inner| {
93 let inner = $crate::__extract_gauge_builder(inner);
94 let label_guarded = $crate::LabelGuardedUintGaugeVec::new(inner, { $LABELS_NAMES });
95 let result = ($REGISTRY).register(Box::new(label_guarded.clone()));
96 result.map(move |()| label_guarded)
97 })
98 }};
99}
100
101#[macro_export]
102macro_rules! register_guarded_int_counter_vec_with_registry {
103 ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{
104 let inner = prometheus::IntCounterVec::new(prometheus::opts!($NAME, $HELP), $LABELS_NAMES);
105 inner.and_then(|inner| {
106 let inner = $crate::__extract_counter_builder(inner);
107 let label_guarded = $crate::LabelGuardedIntCounterVec::new(inner, { $LABELS_NAMES });
108 let result = ($REGISTRY).register(Box::new(label_guarded.clone()));
109 result.map(move |()| label_guarded)
110 })
111 }};
112}
113
114mod tait {
116 use prometheus::core::{
117 Atomic, GenericCounter, GenericCounterVec, GenericGauge, GenericGaugeVec, MetricVec,
118 MetricVecBuilder,
119 };
120 use prometheus::{Histogram, HistogramVec};
121
122 pub type VecBuilderOfCounter<P: Atomic> = impl MetricVecBuilder<M = GenericCounter<P>>;
123 pub type VecBuilderOfGauge<P: Atomic> = impl MetricVecBuilder<M = GenericGauge<P>>;
124 pub type VecBuilderOfHistogram = impl MetricVecBuilder<M = Histogram>;
125
126 #[define_opaque(VecBuilderOfCounter)]
127 pub fn __extract_counter_builder<P: Atomic>(
128 vec: GenericCounterVec<P>,
129 ) -> MetricVec<VecBuilderOfCounter<P>> {
130 vec
131 }
132
133 #[define_opaque(VecBuilderOfGauge)]
134 pub fn __extract_gauge_builder<P: Atomic>(
135 vec: GenericGaugeVec<P>,
136 ) -> MetricVec<VecBuilderOfGauge<P>> {
137 vec
138 }
139
140 #[define_opaque(VecBuilderOfHistogram)]
141 pub fn __extract_histogram_builder(vec: HistogramVec) -> MetricVec<VecBuilderOfHistogram> {
142 vec
143 }
144}
145pub use tait::*;
146
147use crate::UintGauge;
148
149pub type LabelGuardedHistogramVec = LabelGuardedMetricVec<VecBuilderOfHistogram>;
150pub type LabelGuardedIntCounterVec = LabelGuardedMetricVec<VecBuilderOfCounter<AtomicU64>>;
151pub type LabelGuardedIntGaugeVec = LabelGuardedMetricVec<VecBuilderOfGauge<AtomicI64>>;
152pub type LabelGuardedUintGaugeVec = LabelGuardedMetricVec<VecBuilderOfGauge<AtomicU64>>;
153pub type LabelGuardedGaugeVec = LabelGuardedMetricVec<VecBuilderOfGauge<AtomicF64>>;
154
155pub type LabelGuardedHistogram = LabelGuardedMetric<Histogram>;
156pub type LabelGuardedIntCounter = LabelGuardedMetric<IntCounter>;
157pub type LabelGuardedIntGauge = LabelGuardedMetric<IntGauge>;
158pub type LabelGuardedUintGauge = LabelGuardedMetric<UintGauge>;
159pub type LabelGuardedGauge = LabelGuardedMetric<Gauge>;
160
161pub type LabelGuardedLocalHistogram = LabelGuardedMetric<LocalHistogram>;
162pub type LabelGuardedLocalIntCounter = LabelGuardedMetric<LocalIntCounter>;
163
164fn gen_test_label<const N: usize>() -> [&'static str; N] {
165 const TEST_LABELS: [&str; 5] = ["test1", "test2", "test3", "test4", "test5"];
166 (0..N)
167 .map(|i| TEST_LABELS[i])
168 .collect::<Vec<_>>()
169 .try_into()
170 .unwrap()
171}
172
173#[derive(Default)]
174struct LabelGuardedMetricsInfo {
175 labeled_metrics_count: HashMap<Box<[String]>, usize>,
176 uncollected_removed_labels: HashSet<Box<[String]>>,
177}
178
179impl LabelGuardedMetricsInfo {
180 fn register_new_label<V: AsRef<str>>(mutex: &Arc<Mutex<Self>>, labels: &[V]) -> LabelGuard {
181 let mut guard = mutex.lock();
182 let label_string = labels
183 .iter()
184 .map(|label| label.as_ref().to_owned())
185 .collect::<Vec<_>>()
186 .into_boxed_slice();
187 guard.uncollected_removed_labels.remove(&label_string);
188 *guard
189 .labeled_metrics_count
190 .entry(label_string.clone())
191 .or_insert(0) += 1;
192 LabelGuard {
193 labels: label_string,
194 info: mutex.clone(),
195 }
196 }
197}
198
199#[derive(Clone)]
216pub struct LabelGuardedMetricVec<T: MetricVecBuilder> {
217 inner: MetricVec<T>,
218 info: Arc<Mutex<LabelGuardedMetricsInfo>>,
219 labels: Box<[&'static str]>,
220}
221
222impl<T: MetricVecBuilder> Debug for LabelGuardedMetricVec<T> {
223 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
224 f.debug_struct(format!("LabelGuardedMetricVec<{}>", type_name::<T>()).as_str())
225 .field("label", &self.labels)
226 .finish()
227 }
228}
229
230impl<T: MetricVecBuilder> Collector for LabelGuardedMetricVec<T> {
231 fn desc(&self) -> Vec<&Desc> {
232 self.inner.desc()
233 }
234
235 fn collect(&self) -> Vec<MetricFamily> {
236 let mut guard = self.info.lock();
237 let ret = self.inner.collect();
238 for labels in guard.uncollected_removed_labels.drain() {
239 if let Err(e) = self.inner.remove_label_values(&labels) {
240 warn!(
241 error = %e.as_report(),
242 "err when delete metrics of {:?} of labels {:?}",
243 self.inner.desc().first().expect("should have desc").fq_name,
244 self.labels,
245 );
246 }
247 }
248 ret
249 }
250}
251
252impl<T: MetricVecBuilder> LabelGuardedMetricVec<T> {
253 pub fn new(inner: MetricVec<T>, labels: &[&'static str]) -> Self {
254 Self {
255 inner,
256 info: Default::default(),
257 labels: labels.to_vec().into_boxed_slice(),
258 }
259 }
260
261 pub fn with_guarded_label_values<V: AsRef<str> + std::fmt::Debug>(
277 &self,
278 labels: &[V],
279 ) -> LabelGuardedMetric<T::M> {
280 let guard = LabelGuardedMetricsInfo::register_new_label(&self.info, labels);
281 let inner = self.inner.with_label_values(labels);
282 LabelGuardedMetric {
283 inner,
284 _guard: Arc::new(guard),
285 }
286 }
287
288 pub fn with_test_label<const N: usize>(&self) -> LabelGuardedMetric<T::M> {
289 let labels = gen_test_label::<N>();
290 self.with_guarded_label_values(&labels)
291 }
292}
293
294impl LabelGuardedIntCounterVec {
295 pub fn test_int_counter_vec<const N: usize>() -> Self {
296 let registry = prometheus::Registry::new();
297 let labels = gen_test_label::<N>();
298 register_guarded_int_counter_vec_with_registry!("test", "test", &labels, ®istry).unwrap()
299 }
300}
301
302impl LabelGuardedIntGaugeVec {
303 pub fn test_int_gauge_vec<const N: usize>() -> Self {
304 let registry = prometheus::Registry::new();
305 let labels = gen_test_label::<N>();
306 register_guarded_int_gauge_vec_with_registry!("test", "test", &labels, ®istry).unwrap()
307 }
308}
309
310impl LabelGuardedGaugeVec {
311 pub fn test_gauge_vec<const N: usize>() -> Self {
312 let registry = prometheus::Registry::new();
313 let labels = gen_test_label::<N>();
314 register_guarded_gauge_vec_with_registry!("test", "test", &labels, ®istry).unwrap()
315 }
316}
317
318impl LabelGuardedHistogramVec {
319 pub fn test_histogram_vec<const N: usize>() -> Self {
320 let registry = prometheus::Registry::new();
321 let labels = gen_test_label::<N>();
322 register_guarded_histogram_vec_with_registry!("test", "test", &labels, ®istry).unwrap()
323 }
324}
325
326#[derive(Clone)]
327struct LabelGuard {
328 labels: Box<[String]>,
329 info: Arc<Mutex<LabelGuardedMetricsInfo>>,
330}
331
332impl Drop for LabelGuard {
333 fn drop(&mut self) {
334 let mut guard = self.info.lock();
335 let count = guard.labeled_metrics_count.get_mut(&self.labels).expect(
336 "should exist because the current existing dropping one means the count is not zero",
337 );
338 *count -= 1;
339 if *count == 0 {
340 guard
341 .labeled_metrics_count
342 .remove(&self.labels)
343 .expect("should exist");
344 guard.uncollected_removed_labels.insert(self.labels.clone());
345 }
346 }
347}
348
349#[derive(Clone)]
350pub struct LabelGuardedMetric<T> {
351 inner: T,
352 _guard: Arc<LabelGuard>,
353}
354
355impl<T> Debug for LabelGuardedMetric<T> {
356 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
357 f.debug_struct("LabelGuardedMetric").finish()
358 }
359}
360
361impl<T> Deref for LabelGuardedMetric<T> {
362 type Target = T;
363
364 fn deref(&self) -> &Self::Target {
365 &self.inner
366 }
367}
368
369impl LabelGuardedHistogram {
370 pub fn test_histogram<const N: usize>() -> Self {
371 LabelGuardedHistogramVec::test_histogram_vec::<N>().with_test_label::<N>()
372 }
373}
374
375impl LabelGuardedIntCounter {
376 pub fn test_int_counter<const N: usize>() -> Self {
377 LabelGuardedIntCounterVec::test_int_counter_vec::<N>().with_test_label::<N>()
378 }
379}
380
381impl LabelGuardedIntGauge {
382 pub fn test_int_gauge<const N: usize>() -> Self {
383 LabelGuardedIntGaugeVec::test_int_gauge_vec::<N>().with_test_label::<N>()
384 }
385}
386
387impl LabelGuardedGauge {
388 pub fn test_gauge<const N: usize>() -> Self {
389 LabelGuardedGaugeVec::test_gauge_vec::<N>().with_test_label::<N>()
390 }
391}
392
393pub type LazyLabelGuardedMetrics<T: MetricVecBuilder> =
394 LazyLock<LabelGuardedMetric<T::M>, impl FnOnce() -> LabelGuardedMetric<T::M>>;
395
396impl<T: MetricVecBuilder> LabelGuardedMetricVec<T> {
397 #[define_opaque(LazyLabelGuardedMetrics)]
398 pub fn lazy_guarded_metrics(self, labels: Vec<String>) -> LazyLabelGuardedMetrics<T> {
399 LazyLock::new(move || self.with_guarded_label_values(&labels))
400 }
401}
402
403pub type LazyLabelGuardedHistogram = LazyLabelGuardedMetrics<VecBuilderOfHistogram>;
404pub type LazyLabelGuardedIntCounter = LazyLabelGuardedMetrics<VecBuilderOfCounter<AtomicU64>>;
405pub type LazyLabelGuardedIntGauge = LazyLabelGuardedMetrics<VecBuilderOfGauge<AtomicI64>>;
406pub type LazyLabelGuardedUintGauge = LazyLabelGuardedMetrics<VecBuilderOfGauge<AtomicU64>>;
407pub type LazyLabelGuardedGauge = LazyLabelGuardedMetrics<VecBuilderOfGauge<AtomicF64>>;
408
409pub trait MetricWithLocal {
410 type Local;
411 fn local(&self) -> Self::Local;
412}
413
414impl MetricWithLocal for Histogram {
415 type Local = LocalHistogram;
416
417 fn local(&self) -> Self::Local {
418 self.local()
419 }
420}
421
422impl<P: Atomic> MetricWithLocal for GenericCounter<P> {
423 type Local = GenericLocalCounter<P>;
424
425 fn local(&self) -> Self::Local {
426 self.local()
427 }
428}
429
430impl<T: MetricWithLocal> LabelGuardedMetric<T> {
431 pub fn local(&self) -> LabelGuardedMetric<T::Local> {
432 LabelGuardedMetric {
433 inner: self.inner.local(),
434 _guard: self._guard.clone(),
435 }
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use prometheus::core::Collector;
442
443 use crate::{LabelGuardedIntCounterVec, LabelGuardedIntGaugeVec};
444
445 #[test]
446 fn test_label_guarded_metrics_drop() {
447 let vec = LabelGuardedIntCounterVec::test_int_counter_vec::<3>();
448 let m1_1 = vec.with_guarded_label_values(&["1", "2", "3"]);
449 assert_eq!(1, vec.collect().pop().unwrap().get_metric().len());
450 let m1_2 = vec.with_guarded_label_values(&["1", "2", "3"]);
451 let m1_3 = m1_2.clone();
452 assert_eq!(1, vec.collect().pop().unwrap().get_metric().len());
453 let m2 = vec.with_guarded_label_values(&["2", "2", "3"]);
454 assert_eq!(2, vec.collect().pop().unwrap().get_metric().len());
455 drop(m1_3);
456 assert_eq!(2, vec.collect().pop().unwrap().get_metric().len());
457 assert_eq!(2, vec.collect().pop().unwrap().get_metric().len());
458 drop(m2);
459 assert_eq!(2, vec.collect().pop().unwrap().get_metric().len());
460 assert_eq!(1, vec.collect().pop().unwrap().get_metric().len());
461 drop(m1_1);
462 assert_eq!(1, vec.collect().pop().unwrap().get_metric().len());
463 assert_eq!(1, vec.collect().pop().unwrap().get_metric().len());
464 drop(m1_2);
465 assert_eq!(1, vec.collect().pop().unwrap().get_metric().len());
466 assert_eq!(0, vec.collect().pop().unwrap().get_metric().len());
467 }
468
469 #[test]
470 fn test_short_lived_guarded_gauge() {
471 let vec = LabelGuardedIntGaugeVec::test_int_gauge_vec::<1>();
472
473 vec.with_guarded_label_values(&["1"]).set(7);
474 vec.with_guarded_label_values(&["1"]).set(11);
475 let collected = vec.collect().pop().unwrap();
476 assert_eq!(
477 11.0,
478 collected.get_metric()[0]
479 .get_gauge()
480 .as_ref()
481 .unwrap()
482 .value()
483 );
484 assert!(vec.collect().pop().unwrap().get_metric().is_empty());
485
486 vec.with_guarded_label_values(&["1"]).set(13);
487 let collected = vec.collect().pop().unwrap();
488 assert_eq!(
489 13.0,
490 collected.get_metric()[0]
491 .get_gauge()
492 .as_ref()
493 .unwrap()
494 .value()
495 );
496 }
497}