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