1use std::any::Any;
45use std::collections::BTreeMap;
46use std::fmt;
47use std::fmt::{Debug, Formatter};
48use std::future::Future;
49use std::pin::Pin;
50use std::sync::{Arc, Mutex};
51use std::task::{Context, Poll};
52use std::time::{Duration, Instant};
53
54use derivative::Derivative;
55use pin_project::pin_project;
56use prometheus::core::{
57 Atomic, AtomicF64, AtomicI64, AtomicU64, Collector, Desc, GenericCounter, GenericCounterVec,
58 GenericGauge, GenericGaugeVec,
59};
60use prometheus::proto::MetricFamily;
61use prometheus::{HistogramOpts, Registry};
62
63pub mod aggregation;
64mod delete_on_drop;
65
66pub use delete_on_drop::*;
67pub use prometheus::Opts as PrometheusOpts;
68
69#[macro_export]
71macro_rules! metric {
72 (
73 name: $name:expr,
74 help: $help:expr
75 $(, subsystem: $subsystem_name:expr)?
76 $(, const_labels: { $($cl_key:expr => $cl_value:expr ),* })?
77 $(, var_labels: [ $($vl_name:expr),* ])?
78 $(, buckets: $bk_name:expr)?
79 $(, visibility: $visibility:expr)?
80 $(, tags: [ $($tag:expr),* $(,)? ])?
81 $(,)?
82 ) => {{
83 let const_labels = (&[
84 $($(
85 ($cl_key.to_string(), $cl_value.to_string()),
86 )*)?
87 ]).into_iter().cloned().collect();
88 let var_labels = vec![
89 $(
90 $($vl_name.into(),)*
91 )?];
92 #[allow(unused_mut)]
93 let mut mk_opts = $crate::metrics::MakeCollectorOpts {
94 opts: $crate::metrics::PrometheusOpts::new($name, $help)
95 $(.subsystem( $subsystem_name ))?
96 .const_labels(const_labels)
97 .variable_labels(var_labels),
98 buckets: None,
99 };
100 $(mk_opts.buckets = Some($bk_name);)*
102 $(let _: $crate::metrics::MetricVisibility = $visibility;)?
107 $($(let _: $crate::metrics::MetricTag = $tag;)*)?
110 mk_opts
111 }}
112}
113
114#[derive(Debug, Clone)]
116pub struct MakeCollectorOpts {
117 pub opts: PrometheusOpts,
119 pub buckets: Option<Vec<f64>>,
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize)]
130#[serde(rename_all = "snake_case")]
131pub enum MetricVisibility {
132 #[default]
134 Internal,
135 Public,
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
150#[serde(rename_all = "kebab-case")]
151pub enum MetricTag {
152 Environment,
154 Compute,
156 Source,
158 Sink,
160}
161
162#[derive(Clone, Derivative)]
164#[derivative(Debug)]
165pub struct MetricsRegistry {
166 inner: Registry,
167 #[derivative(Debug = "ignore")]
168 postprocessors: Arc<Mutex<Vec<Box<dyn FnMut(&mut Vec<MetricFamily>) + Send + Sync>>>>,
169}
170
171#[derive(Clone)]
179pub struct DeleteOnDropWrapper<M> {
180 inner: M,
181}
182
183impl<M: MakeCollector + Debug> Debug for DeleteOnDropWrapper<M> {
184 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
185 self.inner.fmt(f)
186 }
187}
188
189impl<M: Collector> Collector for DeleteOnDropWrapper<M> {
190 fn desc(&self) -> Vec<&Desc> {
191 self.inner.desc()
192 }
193
194 fn collect(&self) -> Vec<MetricFamily> {
195 self.inner.collect()
196 }
197}
198
199impl<M: MakeCollector> MakeCollector for DeleteOnDropWrapper<M> {
200 fn make_collector(opts: MakeCollectorOpts) -> Self {
201 DeleteOnDropWrapper {
202 inner: M::make_collector(opts),
203 }
204 }
205}
206
207impl<M: MetricVecExt> DeleteOnDropWrapper<M> {
208 pub fn get_delete_on_drop_metric<L: PromLabelsExt>(
210 &self,
211 labels: L,
212 ) -> DeleteOnDropMetric<M, L> {
213 self.inner.get_delete_on_drop_metric(labels)
214 }
215}
216
217pub type UIntGauge = GenericGauge<AtomicU64>;
220
221pub type CounterVec = DeleteOnDropWrapper<prometheus::CounterVec>;
223pub type Gauge = DeleteOnDropWrapper<prometheus::Gauge>;
225pub type GaugeVec = DeleteOnDropWrapper<prometheus::GaugeVec>;
227pub type HistogramVec = DeleteOnDropWrapper<prometheus::HistogramVec>;
229pub type IntCounterVec = DeleteOnDropWrapper<prometheus::IntCounterVec>;
231pub type IntGaugeVec = DeleteOnDropWrapper<prometheus::IntGaugeVec>;
233pub type UIntGaugeVec = DeleteOnDropWrapper<raw::UIntGaugeVec>;
235
236use crate::assert_none;
237
238pub use prometheus::{Counter, Histogram, IntCounter, IntGauge};
239
240pub mod raw {
242 use prometheus::core::{AtomicU64, GenericGaugeVec};
243
244 pub type UIntGaugeVec = GenericGaugeVec<AtomicU64>;
247
248 pub use prometheus::{CounterVec, Gauge, GaugeVec, HistogramVec, IntCounterVec, IntGaugeVec};
249}
250
251impl MetricsRegistry {
252 pub fn new() -> Self {
254 MetricsRegistry {
255 inner: Registry::new(),
256 postprocessors: Arc::new(Mutex::new(vec![])),
257 }
258 }
259
260 pub fn register<M>(&self, opts: MakeCollectorOpts) -> M
262 where
263 M: MakeCollector,
264 {
265 let collector = M::make_collector(opts);
266 self.inner.register(Box::new(collector.clone())).unwrap();
267 collector
268 }
269
270 pub fn register_computed_gauge<P>(
272 &self,
273 opts: MakeCollectorOpts,
274 f: impl Fn() -> P::T + Send + Sync + 'static,
275 ) -> ComputedGenericGauge<P>
276 where
277 P: Atomic + 'static,
278 {
279 let gauge = ComputedGenericGauge {
280 gauge: GenericGauge::make_collector(opts),
281 f: Arc::new(f),
282 };
283 self.inner.register(Box::new(gauge.clone())).unwrap();
284 gauge
285 }
286
287 pub fn register_collector<C: 'static + prometheus::core::Collector>(&self, collector: C) {
289 self.inner
290 .register(Box::new(collector))
291 .expect("registering pre-defined metrics collector");
292 }
293
294 pub fn register_collector_with_dropper<C>(&self, collector: C) -> Box<dyn Any + Send + Sync>
315 where
316 C: 'static + prometheus::core::Collector + Clone + Send + Sync,
317 {
318 self.try_register_collector_with_dropper(collector)
319 .unwrap_or_else(|e| {
320 crate::soft_panic_or_log!("collector already registered: {e}");
321 Box::new(())
323 })
324 }
325
326 pub fn try_register_collector_with_dropper<C>(
333 &self,
334 collector: C,
335 ) -> Result<Box<dyn Any + Send + Sync>, prometheus::Error>
336 where
337 C: 'static + prometheus::core::Collector + Clone + Send + Sync,
338 {
339 self.inner.register(Box::new(collector.clone()))?;
340 let registry = self.inner.clone();
343 Ok(Box::new(scopeguard::guard(collector, move |c| {
344 let _ = registry.unregister(Box::new(c));
345 })))
346 }
347
348 pub fn register_postprocessor<F>(&self, f: F)
353 where
354 F: FnMut(&mut Vec<MetricFamily>) + Send + Sync + 'static,
355 {
356 let mut postprocessors = self.postprocessors.lock().expect("lock poisoned");
357 postprocessors.push(Box::new(f));
358 }
359
360 pub fn gather(&self) -> Vec<MetricFamily> {
368 let mut metrics = self.inner.gather();
369 let mut postprocessors = self.postprocessors.lock().expect("lock poisoned");
370 for postprocessor in &mut *postprocessors {
371 postprocessor(&mut metrics);
372 }
373 metrics
374 }
375}
376
377pub trait MakeCollector: Collector + Clone + 'static {
382 fn make_collector(opts: MakeCollectorOpts) -> Self;
384}
385
386impl<T> MakeCollector for GenericCounter<T>
387where
388 T: Atomic + 'static,
389{
390 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
391 assert_none!(mk_opts.buckets);
392 Self::with_opts(mk_opts.opts).expect("defining a counter")
393 }
394}
395
396impl<T> MakeCollector for GenericCounterVec<T>
397where
398 T: Atomic + 'static,
399{
400 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
401 assert_none!(mk_opts.buckets);
402 let labels: Vec<String> = mk_opts.opts.variable_labels.clone();
403 let label_refs: Vec<&str> = labels.iter().map(String::as_str).collect();
404 Self::new(mk_opts.opts, label_refs.as_slice()).expect("defining a counter vec")
405 }
406}
407
408impl<T> MakeCollector for GenericGauge<T>
409where
410 T: Atomic + 'static,
411{
412 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
413 assert_none!(mk_opts.buckets);
414 Self::with_opts(mk_opts.opts).expect("defining a gauge")
415 }
416}
417
418impl<T> MakeCollector for GenericGaugeVec<T>
419where
420 T: Atomic + 'static,
421{
422 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
423 assert_none!(mk_opts.buckets);
424 let labels = mk_opts.opts.variable_labels.clone();
425 let labels = &labels.iter().map(|x| x.as_str()).collect::<Vec<_>>();
426 Self::new(mk_opts.opts, labels).expect("defining a gauge vec")
427 }
428}
429
430impl MakeCollector for Histogram {
431 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
432 assert!(mk_opts.buckets.is_some());
433 Self::with_opts(HistogramOpts {
434 common_opts: mk_opts.opts,
435 buckets: mk_opts.buckets.unwrap(),
436 })
437 .expect("defining a histogram")
438 }
439}
440
441impl MakeCollector for raw::HistogramVec {
442 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
443 assert!(mk_opts.buckets.is_some());
444 let labels = mk_opts.opts.variable_labels.clone();
445 let labels = &labels.iter().map(|x| x.as_str()).collect::<Vec<_>>();
446 Self::new(
447 HistogramOpts {
448 common_opts: mk_opts.opts,
449 buckets: mk_opts.buckets.unwrap(),
450 },
451 labels,
452 )
453 .expect("defining a histogram vec")
454 }
455}
456
457pub struct ComputedGenericGauge<P>
459where
460 P: Atomic,
461{
462 gauge: GenericGauge<P>,
463 f: Arc<dyn Fn() -> P::T + Send + Sync>,
464}
465
466impl<P> fmt::Debug for ComputedGenericGauge<P>
467where
468 P: Atomic + fmt::Debug,
469{
470 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
471 f.debug_struct("ComputedGenericGauge")
472 .field("gauge", &self.gauge)
473 .finish_non_exhaustive()
474 }
475}
476
477impl<P> Clone for ComputedGenericGauge<P>
478where
479 P: Atomic,
480{
481 fn clone(&self) -> ComputedGenericGauge<P> {
482 ComputedGenericGauge {
483 gauge: self.gauge.clone(),
484 f: Arc::clone(&self.f),
485 }
486 }
487}
488
489impl<T> Collector for ComputedGenericGauge<T>
490where
491 T: Atomic,
492{
493 fn desc(&self) -> Vec<&prometheus::core::Desc> {
494 self.gauge.desc()
495 }
496
497 fn collect(&self) -> Vec<MetricFamily> {
498 self.gauge.set((self.f)());
499 self.gauge.collect()
500 }
501}
502
503impl<P> ComputedGenericGauge<P>
504where
505 P: Atomic,
506{
507 pub fn get(&self) -> P::T {
509 (self.f)()
510 }
511}
512
513pub type ComputedGauge = ComputedGenericGauge<AtomicF64>;
515
516pub type ComputedIntGauge = ComputedGenericGauge<AtomicI64>;
518
519pub type ComputedUIntGauge = ComputedGenericGauge<AtomicU64>;
521
522pub trait MetricsFutureExt<F> {
524 fn wall_time(self) -> WallTimeFuture<F, UnspecifiedMetric>;
559
560 fn exec_time(self) -> ExecTimeFuture<F, UnspecifiedMetric>;
597}
598
599impl<F: Future> MetricsFutureExt<F> for F {
600 fn wall_time(self) -> WallTimeFuture<F, UnspecifiedMetric> {
601 WallTimeFuture {
602 fut: self,
603 metric: UnspecifiedMetric(()),
604 start: None,
605 filter: None,
606 }
607 }
608
609 fn exec_time(self) -> ExecTimeFuture<F, UnspecifiedMetric> {
610 ExecTimeFuture {
611 fut: self,
612 metric: UnspecifiedMetric(()),
613 running_duration: Duration::from_millis(0),
614 filter: None,
615 }
616 }
617}
618
619#[must_use = "futures do nothing unless you `.await` or poll them"]
621#[pin_project]
622pub struct WallTimeFuture<F, Metric> {
623 #[pin]
625 fut: F,
626 metric: Metric,
628 start: Option<Instant>,
630 filter: Option<Box<dyn FnMut(Duration) -> bool + Send + Sync>>,
632}
633
634impl<F: Debug, M: Debug> fmt::Debug for WallTimeFuture<F, M> {
635 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
636 f.debug_struct("WallTimeFuture")
637 .field("fut", &self.fut)
638 .field("metric", &self.metric)
639 .field("start", &self.start)
640 .field("filter", &self.filter.is_some())
641 .finish()
642 }
643}
644
645impl<F> WallTimeFuture<F, UnspecifiedMetric> {
646 pub fn observe(
654 self,
655 histogram: prometheus::Histogram,
656 ) -> WallTimeFuture<F, prometheus::Histogram> {
657 WallTimeFuture {
658 fut: self.fut,
659 metric: histogram,
660 start: self.start,
661 filter: self.filter,
662 }
663 }
664
665 pub fn inc_by(self, counter: prometheus::Counter) -> WallTimeFuture<F, prometheus::Counter> {
673 WallTimeFuture {
674 fut: self.fut,
675 metric: counter,
676 start: self.start,
677 filter: self.filter,
678 }
679 }
680
681 pub fn set_at(self, place: &mut f64) -> WallTimeFuture<F, &mut f64> {
683 WallTimeFuture {
684 fut: self.fut,
685 metric: place,
686 start: self.start,
687 filter: self.filter,
688 }
689 }
690}
691
692impl<F, M> WallTimeFuture<F, M> {
693 pub fn with_filter(
698 mut self,
699 filter: impl FnMut(Duration) -> bool + Send + Sync + 'static,
700 ) -> Self {
701 self.filter = Some(Box::new(filter));
702 self
703 }
704}
705
706impl<F: Future, M: DurationMetric> Future for WallTimeFuture<F, M> {
707 type Output = F::Output;
708
709 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
710 let this = self.project();
711
712 if this.start.is_none() {
713 *this.start = Some(Instant::now());
714 }
715
716 let result = match this.fut.poll(cx) {
717 Poll::Ready(r) => r,
718 Poll::Pending => return Poll::Pending,
719 };
720 let duration = Instant::now().duration_since(this.start.expect("timer to be started"));
721
722 let pass = this
723 .filter
724 .as_mut()
725 .map(|filter| filter(duration))
726 .unwrap_or(true);
727 if pass {
728 this.metric.record(duration.as_secs_f64())
729 }
730
731 Poll::Ready(result)
732 }
733}
734
735#[must_use = "futures do nothing unless you `.await` or poll them"]
737#[pin_project]
738pub struct ExecTimeFuture<F, Metric> {
739 #[pin]
741 fut: F,
742 metric: Metric,
744 running_duration: Duration,
746 filter: Option<Box<dyn FnMut(Duration) -> bool + Send + Sync>>,
748}
749
750impl<F: Debug, M: Debug> fmt::Debug for ExecTimeFuture<F, M> {
751 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
752 f.debug_struct("ExecTimeFuture")
753 .field("fut", &self.fut)
754 .field("metric", &self.metric)
755 .field("running_duration", &self.running_duration)
756 .field("filter", &self.filter.is_some())
757 .finish()
758 }
759}
760
761impl<F> ExecTimeFuture<F, UnspecifiedMetric> {
762 pub fn observe(
770 self,
771 histogram: prometheus::Histogram,
772 ) -> ExecTimeFuture<F, prometheus::Histogram> {
773 ExecTimeFuture {
774 fut: self.fut,
775 metric: histogram,
776 running_duration: self.running_duration,
777 filter: self.filter,
778 }
779 }
780
781 pub fn inc_by(self, counter: prometheus::Counter) -> ExecTimeFuture<F, prometheus::Counter> {
789 ExecTimeFuture {
790 fut: self.fut,
791 metric: counter,
792 running_duration: self.running_duration,
793 filter: self.filter,
794 }
795 }
796}
797
798impl<F, M> ExecTimeFuture<F, M> {
799 pub fn with_filter(
801 mut self,
802 filter: impl FnMut(Duration) -> bool + Send + Sync + 'static,
803 ) -> Self {
804 self.filter = Some(Box::new(filter));
805 self
806 }
807}
808
809impl<F: Future, M: DurationMetric> Future for ExecTimeFuture<F, M> {
810 type Output = F::Output;
811
812 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
813 let this = self.project();
814
815 let start = Instant::now();
816 let result = this.fut.poll(cx);
817 let duration = Instant::now().duration_since(start);
818
819 *this.running_duration = this.running_duration.saturating_add(duration);
820
821 let result = match result {
822 Poll::Ready(result) => result,
823 Poll::Pending => return Poll::Pending,
824 };
825
826 let duration = *this.running_duration;
827 let pass = this
828 .filter
829 .as_mut()
830 .map(|filter| filter(duration))
831 .unwrap_or(true);
832 if pass {
833 this.metric.record(duration.as_secs_f64());
834 }
835
836 Poll::Ready(result)
837 }
838}
839
840#[derive(Debug)]
847pub struct UnspecifiedMetric(());
848
849trait DurationMetric {
853 fn record(&mut self, seconds: f64);
854}
855
856impl DurationMetric for prometheus::Histogram {
857 fn record(&mut self, seconds: f64) {
858 self.observe(seconds)
859 }
860}
861
862impl DurationMetric for prometheus::Counter {
863 fn record(&mut self, seconds: f64) {
864 self.inc_by(seconds)
865 }
866}
867
868impl DurationMetric for &'_ mut f64 {
871 fn record(&mut self, seconds: f64) {
872 **self = seconds;
873 }
874}
875
876#[cfg(feature = "async")]
878pub fn register_runtime_metrics(
879 name: &'static str,
880 runtime_metrics: tokio::runtime::RuntimeMetrics,
881 registry: &MetricsRegistry,
882) {
883 macro_rules! register {
884 ($method:ident, $doc:literal) => {
885 let metrics = runtime_metrics.clone();
886 registry.register_computed_gauge::<prometheus::core::AtomicU64>(
887 crate::metric!(
888 name: concat!("mz_tokio_", stringify!($method)),
889 help: $doc,
890 const_labels: {"runtime" => name},
891 ),
892 move || <u64 as crate::cast::CastFrom<_>>::cast_from(metrics.$method()),
893 );
894 };
895 }
896
897 macro_rules! register_per_worker {
898 ($method:ident, $doc:literal) => {
899 let metrics = runtime_metrics.clone();
900 registry.register_computed_gauge::<prometheus::core::AtomicU64>(
901 crate::metric!(
902 name: concat!("mz_tokio_", stringify!($method)),
903 help: $doc,
904 const_labels: {"runtime" => name},
905 ),
906 move || {
907 (0..metrics.num_workers())
908 .map(|i| <u64 as crate::cast::CastFrom<_>>::cast_from(metrics.$method(i)))
909 .sum::<u64>()
910 },
911 );
912 };
913 }
914
915 macro_rules! register_per_worker_duration_secs {
916 ($method:ident, $doc:literal) => {
917 let metrics = runtime_metrics.clone();
918 registry.register_computed_gauge::<prometheus::core::AtomicF64>(
919 crate::metric!(
920 name: concat!("mz_tokio_", stringify!($method)),
921 help: $doc,
922 const_labels: {"runtime" => name},
923 ),
924 move || {
925 (0..metrics.num_workers())
926 .map(|i| metrics.$method(i).as_secs_f64())
927 .sum::<f64>()
928 },
929 );
930 };
931 }
932
933 register!(
934 num_workers,
935 "The number of worker threads used by the runtime."
936 );
937 register!(
938 num_alive_tasks,
939 "The current number of alive tasks in the runtime."
940 );
941 register!(
942 global_queue_depth,
943 "The number of tasks currently scheduled in the runtime's global queue."
944 );
945 register_per_worker_duration_secs!(
946 worker_total_busy_duration,
947 "The amount of time the worker threads have been busy, in seconds."
948 );
949 register_per_worker!(
950 worker_park_count,
951 "The total number of times the worker threads have parked."
952 );
953 register_per_worker!(
954 worker_park_unpark_count,
955 "The total number of times the worker threads have parked and unparked."
956 );
957
958 #[cfg(tokio_unstable)]
959 {
960 register!(
961 num_blocking_threads,
962 "The number of additional threads spawned by the runtime."
963 );
964 register!(
965 num_idle_blocking_threads,
966 "The number of idle threads which have spawned by the runtime for spawn_blocking calls."
967 );
968 register_per_worker!(
969 worker_local_queue_depth,
970 "The number of tasks currently scheduled in the workers' local queues."
971 );
972 register!(
973 blocking_queue_depth,
974 "The number of tasks currently scheduled in the blocking thread pool, spawned using spawn_blocking."
975 );
976 register!(
977 spawned_tasks_count,
978 "The number of tasks spawned in this runtime since it was created."
979 );
980 register!(
981 remote_schedule_count,
982 "The number of tasks scheduled from outside of the runtime."
983 );
984 register!(
985 budget_forced_yield_count,
986 "The number of times that tasks have been forced to yield back to the scheduler after exhausting their task budgets."
987 );
988 register_per_worker!(
989 worker_noop_count,
990 "The number of times the given worker thread unparked but performed no work before parking again."
991 );
992 register_per_worker!(
993 worker_steal_count,
994 "The number of tasks the given worker thread stole from another worker thread."
995 );
996 register_per_worker!(
997 worker_steal_operations,
998 "The number of times the given worker thread stole tasks from another worker thread."
999 );
1000 register_per_worker!(
1001 worker_poll_count,
1002 "The number of tasks the given worker thread has polled."
1003 );
1004 register_per_worker!(
1005 worker_local_schedule_count,
1006 "The number of tasks scheduled from within the runtime on the given worker's local queue."
1007 );
1008 register_per_worker!(
1009 worker_overflow_count,
1010 "The number of times the given worker thread saturated its local queue."
1011 );
1012 register_per_worker_duration_secs!(
1013 worker_mean_poll_time,
1014 "The mean duration of task polls in seconds."
1015 );
1016 }
1017}
1018
1019#[cfg(feature = "async")]
1022pub fn describe_runtime_metrics() -> Vec<(String, String, Vec<String>, &'static str)> {
1023 let runtime = tokio::runtime::Builder::new_current_thread()
1026 .build()
1027 .expect("building a current-thread runtime");
1028 let registry = MetricsRegistry::new();
1029 register_runtime_metrics("describe", runtime.handle().metrics(), ®istry);
1030 registry
1031 .gather()
1032 .into_iter()
1033 .map(|mf| {
1034 let mut labels: Vec<String> = mf
1037 .get_metric()
1038 .first()
1039 .map(|m| m.get_label().iter().map(|l| l.name().to_owned()).collect())
1040 .unwrap_or_default();
1041 labels.sort();
1042 labels.dedup();
1043 (mf.name().to_owned(), mf.help().to_owned(), labels, file!())
1044 })
1045 .collect()
1046}
1047
1048pub fn remove_children_with_label<V: MetricVec_ + Collector>(vec: &V, name: &str, value: &str) {
1054 let descs = vec.desc();
1055 let Some(desc) = descs.first() else {
1057 return;
1058 };
1059 for family in vec.collect() {
1060 for child in family.get_metric() {
1061 let labels: BTreeMap<&str, &str> = child
1062 .get_label()
1063 .iter()
1064 .map(|pair| (pair.name(), pair.value()))
1065 .collect();
1066 if labels.get(name) != Some(&value) {
1067 continue;
1068 }
1069 let values: Vec<&str> = desc
1070 .variable_labels
1071 .iter()
1072 .map(|label| labels.get(label.as_str()).copied().unwrap_or_default())
1073 .collect();
1074 let _ = vec.remove_label_values(&values);
1078 }
1079 }
1080}
1081
1082#[cfg(test)]
1083mod tests {
1084 use std::time::Duration;
1085
1086 use prometheus::core::Collector;
1087 use prometheus::{CounterVec, HistogramVec};
1088
1089 use crate::stats::histogram_seconds_buckets;
1090
1091 use super::{MetricsFutureExt, MetricsRegistry};
1092
1093 struct Metrics {
1094 pub wall_time_hist: HistogramVec,
1095 pub wall_time_cnt: CounterVec,
1096 pub exec_time_hist: HistogramVec,
1097 pub exec_time_cnt: CounterVec,
1098 }
1099
1100 impl Metrics {
1101 pub fn register_into(registry: &MetricsRegistry) -> Self {
1102 Self {
1103 wall_time_hist: registry.register(metric!(
1104 name: "wall_time_hist",
1105 help: "help",
1106 var_labels: ["action"],
1107 buckets: histogram_seconds_buckets(0.000_128, 8.0),
1108 )),
1109 wall_time_cnt: registry.register(metric!(
1110 name: "wall_time_cnt",
1111 help: "help",
1112 var_labels: ["action"],
1113 )),
1114 exec_time_hist: registry.register(metric!(
1115 name: "exec_time_hist",
1116 help: "help",
1117 var_labels: ["action"],
1118 buckets: histogram_seconds_buckets(0.000_128, 8.0),
1119 )),
1120 exec_time_cnt: registry.register(metric!(
1121 name: "exec_time_cnt",
1122 help: "help",
1123 var_labels: ["action"],
1124 )),
1125 }
1126 }
1127 }
1128
1129 #[crate::test]
1130 #[cfg_attr(miri, ignore)] fn smoke_test_metrics_future_ext() {
1132 let runtime = tokio::runtime::Builder::new_current_thread()
1133 .enable_time()
1134 .build()
1135 .expect("failed to start runtime");
1136 let registry = MetricsRegistry::new();
1137 let metrics = Metrics::register_into(®istry);
1138
1139 let async_sleep_future = async {
1141 tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
1142 };
1143 runtime.block_on(
1144 async_sleep_future
1145 .wall_time()
1146 .observe(metrics.wall_time_hist.with_label_values(&["async_sleep_w"]))
1147 .exec_time()
1148 .observe(metrics.exec_time_hist.with_label_values(&["async_sleep_e"])),
1149 );
1150
1151 let reports = registry.gather();
1152
1153 let exec_family = reports
1154 .iter()
1155 .find(|m| m.name() == "exec_time_hist")
1156 .expect("metric not found");
1157 let exec_metric = exec_family.get_metric();
1158 assert_eq!(exec_metric.len(), 1);
1159 assert_eq!(exec_metric[0].get_label()[0].value(), "async_sleep_e");
1160
1161 let exec_histogram = exec_metric[0].get_histogram();
1162 assert_eq!(exec_histogram.get_sample_count(), 1);
1163 let wall_family = reports
1168 .iter()
1169 .find(|m| m.name() == "wall_time_hist")
1170 .expect("metric not found");
1171 let wall_metric = wall_family.get_metric();
1172 assert_eq!(wall_metric.len(), 1);
1173 assert_eq!(wall_metric[0].get_label()[0].value(), "async_sleep_w");
1174
1175 let wall_histogram = wall_metric[0].get_histogram();
1176 assert_eq!(wall_histogram.get_sample_count(), 1);
1177 assert_eq!(wall_histogram.get_bucket()[12].cumulative_count(), 0);
1180
1181 let registry = MetricsRegistry::new();
1183 let metrics = Metrics::register_into(®istry);
1184
1185 let thread_sleep_future = async {
1187 std::thread::sleep(std::time::Duration::from_secs(1));
1188 };
1189 runtime.block_on(
1190 thread_sleep_future
1191 .wall_time()
1192 .with_filter(|duration| duration < Duration::from_millis(10))
1193 .inc_by(metrics.wall_time_cnt.with_label_values(&["thread_sleep_w"]))
1194 .exec_time()
1195 .inc_by(metrics.exec_time_cnt.with_label_values(&["thread_sleep_e"])),
1196 );
1197
1198 let reports = registry.gather();
1199
1200 let exec_family = reports
1201 .iter()
1202 .find(|m| m.name() == "exec_time_cnt")
1203 .expect("metric not found");
1204 let exec_metric = exec_family.get_metric();
1205 assert_eq!(exec_metric.len(), 1);
1206 assert_eq!(exec_metric[0].get_label()[0].value(), "thread_sleep_e");
1207
1208 let exec_counter = exec_metric[0].get_counter();
1209 assert!(exec_counter.value() >= 1.0);
1211
1212 let wall_family = reports
1213 .iter()
1214 .find(|m| m.name() == "wall_time_cnt")
1215 .expect("metric not found");
1216 let wall_metric = wall_family.get_metric();
1217 assert_eq!(wall_metric.len(), 1);
1218
1219 let wall_counter = wall_metric[0].get_counter();
1220 assert_eq!(wall_counter.value(), 0.0);
1222 }
1223
1224 #[crate::test]
1225 fn collector_drop_handle_unregisters() {
1226 use prometheus::IntGauge;
1227
1228 let registry = MetricsRegistry::new();
1229 let gauge = IntGauge::new("mz_test_guarded", "help").unwrap();
1230 gauge.set(7);
1231 let before = registry.gather().len();
1232
1233 let handle = registry.register_collector_with_dropper(gauge.clone());
1234 assert_eq!(registry.gather().len(), before + 1);
1235
1236 drop(handle);
1238 assert_eq!(registry.gather().len(), before);
1239 }
1240
1241 #[crate::test]
1242 fn register_drop_then_reregister() {
1243 use prometheus::IntGauge;
1244
1245 let registry = MetricsRegistry::new();
1250 let old = IntGauge::new("mz_test_reregister", "help").unwrap();
1251 let new = IntGauge::new("mz_test_reregister", "help").unwrap();
1252 let before = registry.gather().len();
1253
1254 let handle = registry.register_collector_with_dropper(old);
1255 assert_eq!(registry.gather().len(), before + 1);
1256
1257 drop(handle);
1259 let handle = registry.register_collector_with_dropper(new);
1260 assert_eq!(registry.gather().len(), before + 1);
1261
1262 drop(handle);
1263 assert_eq!(registry.gather().len(), before);
1264 }
1265
1266 #[crate::test]
1267 fn try_register_errors_on_duplicate_then_succeeds_after_drop() {
1268 use prometheus::IntGauge;
1269
1270 let registry = MetricsRegistry::new();
1274 let old = IntGauge::new("mz_test_try_register", "help").unwrap();
1275 let new = IntGauge::new("mz_test_try_register", "help").unwrap();
1276 let before = registry.gather().len();
1277
1278 let handle = registry
1279 .try_register_collector_with_dropper(old)
1280 .expect("first registration succeeds");
1281 assert_eq!(registry.gather().len(), before + 1);
1282
1283 let err = registry
1284 .try_register_collector_with_dropper(new.clone())
1285 .err()
1286 .expect("duplicate descriptor id is rejected");
1287 assert!(matches!(err, prometheus::Error::AlreadyReg));
1288 assert_eq!(registry.gather().len(), before + 1);
1290
1291 drop(handle);
1292 let handle = registry
1293 .try_register_collector_with_dropper(new)
1294 .expect("registration succeeds once the id is free");
1295 assert_eq!(registry.gather().len(), before + 1);
1296
1297 drop(handle);
1298 assert_eq!(registry.gather().len(), before);
1299 }
1300
1301 #[crate::test]
1302 fn remove_children_with_label_removes_only_matching_children() {
1303 let registry = MetricsRegistry::new();
1304 let vec: HistogramVec = registry.register(metric!(
1305 name: "labeled_hist",
1306 help: "help",
1307 var_labels: ["cluster", "kind"],
1308 buckets: histogram_seconds_buckets(0.000_128, 8.0),
1309 ));
1310 vec.with_label_values(&["u1", "a"]).observe(1.0);
1311 vec.with_label_values(&["u1", "b"]).observe(1.0);
1312 vec.with_label_values(&["u2", "a"]).observe(1.0);
1313
1314 super::remove_children_with_label(&vec, "cluster", "u1");
1315
1316 let remaining: Vec<Vec<String>> = vec
1317 .collect()
1318 .into_iter()
1319 .flat_map(|family| family.get_metric().to_vec())
1320 .map(|child| {
1321 child
1322 .get_label()
1323 .iter()
1324 .map(|pair| pair.value().to_string())
1325 .collect()
1326 })
1327 .collect();
1328 assert_eq!(remaining, vec![vec!["u2".to_string(), "a".to_string()]]);
1329 }
1330}