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
63mod delete_on_drop;
64
65pub use delete_on_drop::*;
66pub use prometheus::Opts as PrometheusOpts;
67
68#[macro_export]
70macro_rules! metric {
71 (
72 name: $name:expr,
73 help: $help:expr
74 $(, subsystem: $subsystem_name:expr)?
75 $(, const_labels: { $($cl_key:expr => $cl_value:expr ),* })?
76 $(, var_labels: [ $($vl_name:expr),* ])?
77 $(, buckets: $bk_name:expr)?
78 $(, visibility: $visibility:expr)?
79 $(, tags: [ $($tag:expr),* $(,)? ])?
80 $(,)?
81 ) => {{
82 let const_labels = (&[
83 $($(
84 ($cl_key.to_string(), $cl_value.to_string()),
85 )*)?
86 ]).into_iter().cloned().collect();
87 let var_labels = vec![
88 $(
89 $($vl_name.into(),)*
90 )?];
91 #[allow(unused_mut)]
92 let mut mk_opts = $crate::metrics::MakeCollectorOpts {
93 opts: $crate::metrics::PrometheusOpts::new($name, $help)
94 $(.subsystem( $subsystem_name ))?
95 .const_labels(const_labels)
96 .variable_labels(var_labels),
97 buckets: None,
98 };
99 $(mk_opts.buckets = Some($bk_name);)*
101 $(let _: $crate::metrics::MetricVisibility = $visibility;)?
106 $($(let _: $crate::metrics::MetricTag = $tag;)*)?
109 mk_opts
110 }}
111}
112
113#[derive(Debug, Clone)]
115pub struct MakeCollectorOpts {
116 pub opts: PrometheusOpts,
118 pub buckets: Option<Vec<f64>>,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize)]
129#[serde(rename_all = "snake_case")]
130pub enum MetricVisibility {
131 #[default]
133 Internal,
134 Public,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
149#[serde(rename_all = "kebab-case")]
150pub enum MetricTag {
151 Environment,
153 Compute,
155 Source,
157 Sink,
159}
160
161#[derive(Clone, Derivative)]
163#[derivative(Debug)]
164pub struct MetricsRegistry {
165 inner: Registry,
166 #[derivative(Debug = "ignore")]
167 postprocessors: Arc<Mutex<Vec<Box<dyn FnMut(&mut Vec<MetricFamily>) + Send + Sync>>>>,
168}
169
170#[derive(Clone)]
178pub struct DeleteOnDropWrapper<M> {
179 inner: M,
180}
181
182impl<M: MakeCollector + Debug> Debug for DeleteOnDropWrapper<M> {
183 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
184 self.inner.fmt(f)
185 }
186}
187
188impl<M: Collector> Collector for DeleteOnDropWrapper<M> {
189 fn desc(&self) -> Vec<&Desc> {
190 self.inner.desc()
191 }
192
193 fn collect(&self) -> Vec<MetricFamily> {
194 self.inner.collect()
195 }
196}
197
198impl<M: MakeCollector> MakeCollector for DeleteOnDropWrapper<M> {
199 fn make_collector(opts: MakeCollectorOpts) -> Self {
200 DeleteOnDropWrapper {
201 inner: M::make_collector(opts),
202 }
203 }
204}
205
206impl<M: MetricVecExt> DeleteOnDropWrapper<M> {
207 pub fn get_delete_on_drop_metric<L: PromLabelsExt>(
209 &self,
210 labels: L,
211 ) -> DeleteOnDropMetric<M, L> {
212 self.inner.get_delete_on_drop_metric(labels)
213 }
214}
215
216pub type UIntGauge = GenericGauge<AtomicU64>;
219
220pub type CounterVec = DeleteOnDropWrapper<prometheus::CounterVec>;
222pub type Gauge = DeleteOnDropWrapper<prometheus::Gauge>;
224pub type GaugeVec = DeleteOnDropWrapper<prometheus::GaugeVec>;
226pub type HistogramVec = DeleteOnDropWrapper<prometheus::HistogramVec>;
228pub type IntCounterVec = DeleteOnDropWrapper<prometheus::IntCounterVec>;
230pub type IntGaugeVec = DeleteOnDropWrapper<prometheus::IntGaugeVec>;
232pub type UIntGaugeVec = DeleteOnDropWrapper<raw::UIntGaugeVec>;
234
235use crate::assert_none;
236
237pub use prometheus::{Counter, Histogram, IntCounter, IntGauge};
238
239pub mod raw {
241 use prometheus::core::{AtomicU64, GenericGaugeVec};
242
243 pub type UIntGaugeVec = GenericGaugeVec<AtomicU64>;
246
247 pub use prometheus::{CounterVec, Gauge, GaugeVec, HistogramVec, IntCounterVec, IntGaugeVec};
248}
249
250impl MetricsRegistry {
251 pub fn new() -> Self {
253 MetricsRegistry {
254 inner: Registry::new(),
255 postprocessors: Arc::new(Mutex::new(vec![])),
256 }
257 }
258
259 pub fn register<M>(&self, opts: MakeCollectorOpts) -> M
261 where
262 M: MakeCollector,
263 {
264 let collector = M::make_collector(opts);
265 self.inner.register(Box::new(collector.clone())).unwrap();
266 collector
267 }
268
269 pub fn register_computed_gauge<P>(
271 &self,
272 opts: MakeCollectorOpts,
273 f: impl Fn() -> P::T + Send + Sync + 'static,
274 ) -> ComputedGenericGauge<P>
275 where
276 P: Atomic + 'static,
277 {
278 let gauge = ComputedGenericGauge {
279 gauge: GenericGauge::make_collector(opts),
280 f: Arc::new(f),
281 };
282 self.inner.register(Box::new(gauge.clone())).unwrap();
283 gauge
284 }
285
286 pub fn register_collector<C: 'static + prometheus::core::Collector>(&self, collector: C) {
288 self.inner
289 .register(Box::new(collector))
290 .expect("registering pre-defined metrics collector");
291 }
292
293 pub fn register_collector_with_dropper<C>(&self, collector: C) -> Box<dyn Any + Send + Sync>
311 where
312 C: 'static + prometheus::core::Collector + Clone + Send + Sync,
313 {
314 match self.inner.register(Box::new(collector.clone())) {
315 Ok(()) => {
316 let registry = self.inner.clone();
319 Box::new(scopeguard::guard(collector, move |c| {
320 let _ = registry.unregister(Box::new(c));
321 }))
322 }
323 Err(e) => {
324 crate::soft_panic_or_log!("collector already registered: {e}");
325 Box::new(())
327 }
328 }
329 }
330
331 pub fn register_postprocessor<F>(&self, f: F)
336 where
337 F: FnMut(&mut Vec<MetricFamily>) + Send + Sync + 'static,
338 {
339 let mut postprocessors = self.postprocessors.lock().expect("lock poisoned");
340 postprocessors.push(Box::new(f));
341 }
342
343 pub fn gather(&self) -> Vec<MetricFamily> {
351 let mut metrics = self.inner.gather();
352 let mut postprocessors = self.postprocessors.lock().expect("lock poisoned");
353 for postprocessor in &mut *postprocessors {
354 postprocessor(&mut metrics);
355 }
356 metrics
357 }
358}
359
360pub trait MakeCollector: Collector + Clone + 'static {
365 fn make_collector(opts: MakeCollectorOpts) -> Self;
367}
368
369impl<T> MakeCollector for GenericCounter<T>
370where
371 T: Atomic + 'static,
372{
373 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
374 assert_none!(mk_opts.buckets);
375 Self::with_opts(mk_opts.opts).expect("defining a counter")
376 }
377}
378
379impl<T> MakeCollector for GenericCounterVec<T>
380where
381 T: Atomic + 'static,
382{
383 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
384 assert_none!(mk_opts.buckets);
385 let labels: Vec<String> = mk_opts.opts.variable_labels.clone();
386 let label_refs: Vec<&str> = labels.iter().map(String::as_str).collect();
387 Self::new(mk_opts.opts, label_refs.as_slice()).expect("defining a counter vec")
388 }
389}
390
391impl<T> MakeCollector for GenericGauge<T>
392where
393 T: Atomic + 'static,
394{
395 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
396 assert_none!(mk_opts.buckets);
397 Self::with_opts(mk_opts.opts).expect("defining a gauge")
398 }
399}
400
401impl<T> MakeCollector for GenericGaugeVec<T>
402where
403 T: Atomic + 'static,
404{
405 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
406 assert_none!(mk_opts.buckets);
407 let labels = mk_opts.opts.variable_labels.clone();
408 let labels = &labels.iter().map(|x| x.as_str()).collect::<Vec<_>>();
409 Self::new(mk_opts.opts, labels).expect("defining a gauge vec")
410 }
411}
412
413impl MakeCollector for Histogram {
414 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
415 assert!(mk_opts.buckets.is_some());
416 Self::with_opts(HistogramOpts {
417 common_opts: mk_opts.opts,
418 buckets: mk_opts.buckets.unwrap(),
419 })
420 .expect("defining a histogram")
421 }
422}
423
424impl MakeCollector for raw::HistogramVec {
425 fn make_collector(mk_opts: MakeCollectorOpts) -> Self {
426 assert!(mk_opts.buckets.is_some());
427 let labels = mk_opts.opts.variable_labels.clone();
428 let labels = &labels.iter().map(|x| x.as_str()).collect::<Vec<_>>();
429 Self::new(
430 HistogramOpts {
431 common_opts: mk_opts.opts,
432 buckets: mk_opts.buckets.unwrap(),
433 },
434 labels,
435 )
436 .expect("defining a histogram vec")
437 }
438}
439
440pub struct ComputedGenericGauge<P>
442where
443 P: Atomic,
444{
445 gauge: GenericGauge<P>,
446 f: Arc<dyn Fn() -> P::T + Send + Sync>,
447}
448
449impl<P> fmt::Debug for ComputedGenericGauge<P>
450where
451 P: Atomic + fmt::Debug,
452{
453 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
454 f.debug_struct("ComputedGenericGauge")
455 .field("gauge", &self.gauge)
456 .finish_non_exhaustive()
457 }
458}
459
460impl<P> Clone for ComputedGenericGauge<P>
461where
462 P: Atomic,
463{
464 fn clone(&self) -> ComputedGenericGauge<P> {
465 ComputedGenericGauge {
466 gauge: self.gauge.clone(),
467 f: Arc::clone(&self.f),
468 }
469 }
470}
471
472impl<T> Collector for ComputedGenericGauge<T>
473where
474 T: Atomic,
475{
476 fn desc(&self) -> Vec<&prometheus::core::Desc> {
477 self.gauge.desc()
478 }
479
480 fn collect(&self) -> Vec<MetricFamily> {
481 self.gauge.set((self.f)());
482 self.gauge.collect()
483 }
484}
485
486impl<P> ComputedGenericGauge<P>
487where
488 P: Atomic,
489{
490 pub fn get(&self) -> P::T {
492 (self.f)()
493 }
494}
495
496pub type ComputedGauge = ComputedGenericGauge<AtomicF64>;
498
499pub type ComputedIntGauge = ComputedGenericGauge<AtomicI64>;
501
502pub type ComputedUIntGauge = ComputedGenericGauge<AtomicU64>;
504
505pub trait MetricsFutureExt<F> {
507 fn wall_time(self) -> WallTimeFuture<F, UnspecifiedMetric>;
542
543 fn exec_time(self) -> ExecTimeFuture<F, UnspecifiedMetric>;
580}
581
582impl<F: Future> MetricsFutureExt<F> for F {
583 fn wall_time(self) -> WallTimeFuture<F, UnspecifiedMetric> {
584 WallTimeFuture {
585 fut: self,
586 metric: UnspecifiedMetric(()),
587 start: None,
588 filter: None,
589 }
590 }
591
592 fn exec_time(self) -> ExecTimeFuture<F, UnspecifiedMetric> {
593 ExecTimeFuture {
594 fut: self,
595 metric: UnspecifiedMetric(()),
596 running_duration: Duration::from_millis(0),
597 filter: None,
598 }
599 }
600}
601
602#[must_use = "futures do nothing unless you `.await` or poll them"]
604#[pin_project]
605pub struct WallTimeFuture<F, Metric> {
606 #[pin]
608 fut: F,
609 metric: Metric,
611 start: Option<Instant>,
613 filter: Option<Box<dyn FnMut(Duration) -> bool + Send + Sync>>,
615}
616
617impl<F: Debug, M: Debug> fmt::Debug for WallTimeFuture<F, M> {
618 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
619 f.debug_struct("WallTimeFuture")
620 .field("fut", &self.fut)
621 .field("metric", &self.metric)
622 .field("start", &self.start)
623 .field("filter", &self.filter.is_some())
624 .finish()
625 }
626}
627
628impl<F> WallTimeFuture<F, UnspecifiedMetric> {
629 pub fn observe(
637 self,
638 histogram: prometheus::Histogram,
639 ) -> WallTimeFuture<F, prometheus::Histogram> {
640 WallTimeFuture {
641 fut: self.fut,
642 metric: histogram,
643 start: self.start,
644 filter: self.filter,
645 }
646 }
647
648 pub fn inc_by(self, counter: prometheus::Counter) -> WallTimeFuture<F, prometheus::Counter> {
656 WallTimeFuture {
657 fut: self.fut,
658 metric: counter,
659 start: self.start,
660 filter: self.filter,
661 }
662 }
663
664 pub fn set_at(self, place: &mut f64) -> WallTimeFuture<F, &mut f64> {
666 WallTimeFuture {
667 fut: self.fut,
668 metric: place,
669 start: self.start,
670 filter: self.filter,
671 }
672 }
673}
674
675impl<F, M> WallTimeFuture<F, M> {
676 pub fn with_filter(
681 mut self,
682 filter: impl FnMut(Duration) -> bool + Send + Sync + 'static,
683 ) -> Self {
684 self.filter = Some(Box::new(filter));
685 self
686 }
687}
688
689impl<F: Future, M: DurationMetric> Future for WallTimeFuture<F, M> {
690 type Output = F::Output;
691
692 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
693 let this = self.project();
694
695 if this.start.is_none() {
696 *this.start = Some(Instant::now());
697 }
698
699 let result = match this.fut.poll(cx) {
700 Poll::Ready(r) => r,
701 Poll::Pending => return Poll::Pending,
702 };
703 let duration = Instant::now().duration_since(this.start.expect("timer to be started"));
704
705 let pass = this
706 .filter
707 .as_mut()
708 .map(|filter| filter(duration))
709 .unwrap_or(true);
710 if pass {
711 this.metric.record(duration.as_secs_f64())
712 }
713
714 Poll::Ready(result)
715 }
716}
717
718#[must_use = "futures do nothing unless you `.await` or poll them"]
720#[pin_project]
721pub struct ExecTimeFuture<F, Metric> {
722 #[pin]
724 fut: F,
725 metric: Metric,
727 running_duration: Duration,
729 filter: Option<Box<dyn FnMut(Duration) -> bool + Send + Sync>>,
731}
732
733impl<F: Debug, M: Debug> fmt::Debug for ExecTimeFuture<F, M> {
734 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
735 f.debug_struct("ExecTimeFuture")
736 .field("fut", &self.fut)
737 .field("metric", &self.metric)
738 .field("running_duration", &self.running_duration)
739 .field("filter", &self.filter.is_some())
740 .finish()
741 }
742}
743
744impl<F> ExecTimeFuture<F, UnspecifiedMetric> {
745 pub fn observe(
753 self,
754 histogram: prometheus::Histogram,
755 ) -> ExecTimeFuture<F, prometheus::Histogram> {
756 ExecTimeFuture {
757 fut: self.fut,
758 metric: histogram,
759 running_duration: self.running_duration,
760 filter: self.filter,
761 }
762 }
763
764 pub fn inc_by(self, counter: prometheus::Counter) -> ExecTimeFuture<F, prometheus::Counter> {
772 ExecTimeFuture {
773 fut: self.fut,
774 metric: counter,
775 running_duration: self.running_duration,
776 filter: self.filter,
777 }
778 }
779}
780
781impl<F, M> ExecTimeFuture<F, M> {
782 pub fn with_filter(
784 mut self,
785 filter: impl FnMut(Duration) -> bool + Send + Sync + 'static,
786 ) -> Self {
787 self.filter = Some(Box::new(filter));
788 self
789 }
790}
791
792impl<F: Future, M: DurationMetric> Future for ExecTimeFuture<F, M> {
793 type Output = F::Output;
794
795 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
796 let this = self.project();
797
798 let start = Instant::now();
799 let result = this.fut.poll(cx);
800 let duration = Instant::now().duration_since(start);
801
802 *this.running_duration = this.running_duration.saturating_add(duration);
803
804 let result = match result {
805 Poll::Ready(result) => result,
806 Poll::Pending => return Poll::Pending,
807 };
808
809 let duration = *this.running_duration;
810 let pass = this
811 .filter
812 .as_mut()
813 .map(|filter| filter(duration))
814 .unwrap_or(true);
815 if pass {
816 this.metric.record(duration.as_secs_f64());
817 }
818
819 Poll::Ready(result)
820 }
821}
822
823#[derive(Debug)]
830pub struct UnspecifiedMetric(());
831
832trait DurationMetric {
836 fn record(&mut self, seconds: f64);
837}
838
839impl DurationMetric for prometheus::Histogram {
840 fn record(&mut self, seconds: f64) {
841 self.observe(seconds)
842 }
843}
844
845impl DurationMetric for prometheus::Counter {
846 fn record(&mut self, seconds: f64) {
847 self.inc_by(seconds)
848 }
849}
850
851impl DurationMetric for &'_ mut f64 {
854 fn record(&mut self, seconds: f64) {
855 **self = seconds;
856 }
857}
858
859#[cfg(feature = "async")]
861pub fn register_runtime_metrics(
862 name: &'static str,
863 runtime_metrics: tokio::runtime::RuntimeMetrics,
864 registry: &MetricsRegistry,
865) {
866 macro_rules! register {
867 ($method:ident, $doc:literal) => {
868 let metrics = runtime_metrics.clone();
869 registry.register_computed_gauge::<prometheus::core::AtomicU64>(
870 crate::metric!(
871 name: concat!("mz_tokio_", stringify!($method)),
872 help: $doc,
873 const_labels: {"runtime" => name},
874 ),
875 move || <u64 as crate::cast::CastFrom<_>>::cast_from(metrics.$method()),
876 );
877 };
878 }
879
880 macro_rules! register_per_worker {
881 ($method:ident, $doc:literal) => {
882 let metrics = runtime_metrics.clone();
883 registry.register_computed_gauge::<prometheus::core::AtomicU64>(
884 crate::metric!(
885 name: concat!("mz_tokio_", stringify!($method)),
886 help: $doc,
887 const_labels: {"runtime" => name},
888 ),
889 move || {
890 (0..metrics.num_workers())
891 .map(|i| <u64 as crate::cast::CastFrom<_>>::cast_from(metrics.$method(i)))
892 .sum::<u64>()
893 },
894 );
895 };
896 }
897
898 macro_rules! register_per_worker_duration_secs {
899 ($method:ident, $doc:literal) => {
900 let metrics = runtime_metrics.clone();
901 registry.register_computed_gauge::<prometheus::core::AtomicF64>(
902 crate::metric!(
903 name: concat!("mz_tokio_", stringify!($method)),
904 help: $doc,
905 const_labels: {"runtime" => name},
906 ),
907 move || {
908 (0..metrics.num_workers())
909 .map(|i| metrics.$method(i).as_secs_f64())
910 .sum::<f64>()
911 },
912 );
913 };
914 }
915
916 register!(
917 num_workers,
918 "The number of worker threads used by the runtime."
919 );
920 register!(
921 num_alive_tasks,
922 "The current number of alive tasks in the runtime."
923 );
924 register!(
925 global_queue_depth,
926 "The number of tasks currently scheduled in the runtime's global queue."
927 );
928 register_per_worker_duration_secs!(
929 worker_total_busy_duration,
930 "The amount of time the worker threads have been busy, in seconds."
931 );
932 register_per_worker!(
933 worker_park_count,
934 "The total number of times the worker threads have parked."
935 );
936 register_per_worker!(
937 worker_park_unpark_count,
938 "The total number of times the worker threads have parked and unparked."
939 );
940
941 #[cfg(tokio_unstable)]
942 {
943 register!(
944 num_blocking_threads,
945 "The number of additional threads spawned by the runtime."
946 );
947 register!(
948 num_idle_blocking_threads,
949 "The number of idle threads which have spawned by the runtime for spawn_blocking calls."
950 );
951 register_per_worker!(
952 worker_local_queue_depth,
953 "The number of tasks currently scheduled in the workers' local queues."
954 );
955 register!(
956 blocking_queue_depth,
957 "The number of tasks currently scheduled in the blocking thread pool, spawned using spawn_blocking."
958 );
959 register!(
960 spawned_tasks_count,
961 "The number of tasks spawned in this runtime since it was created."
962 );
963 register!(
964 remote_schedule_count,
965 "The number of tasks scheduled from outside of the runtime."
966 );
967 register!(
968 budget_forced_yield_count,
969 "The number of times that tasks have been forced to yield back to the scheduler after exhausting their task budgets."
970 );
971 register_per_worker!(
972 worker_noop_count,
973 "The number of times the given worker thread unparked but performed no work before parking again."
974 );
975 register_per_worker!(
976 worker_steal_count,
977 "The number of tasks the given worker thread stole from another worker thread."
978 );
979 register_per_worker!(
980 worker_steal_operations,
981 "The number of times the given worker thread stole tasks from another worker thread."
982 );
983 register_per_worker!(
984 worker_poll_count,
985 "The number of tasks the given worker thread has polled."
986 );
987 register_per_worker!(
988 worker_local_schedule_count,
989 "The number of tasks scheduled from within the runtime on the given worker's local queue."
990 );
991 register_per_worker!(
992 worker_overflow_count,
993 "The number of times the given worker thread saturated its local queue."
994 );
995 register_per_worker_duration_secs!(
996 worker_mean_poll_time,
997 "The mean duration of task polls in seconds."
998 );
999 }
1000}
1001
1002#[cfg(feature = "async")]
1005pub fn describe_runtime_metrics() -> Vec<(String, String, Vec<String>, &'static str)> {
1006 let runtime = tokio::runtime::Builder::new_current_thread()
1009 .build()
1010 .expect("building a current-thread runtime");
1011 let registry = MetricsRegistry::new();
1012 register_runtime_metrics("describe", runtime.handle().metrics(), ®istry);
1013 registry
1014 .gather()
1015 .into_iter()
1016 .map(|mf| {
1017 let mut labels: Vec<String> = mf
1020 .get_metric()
1021 .first()
1022 .map(|m| m.get_label().iter().map(|l| l.name().to_owned()).collect())
1023 .unwrap_or_default();
1024 labels.sort();
1025 labels.dedup();
1026 (mf.name().to_owned(), mf.help().to_owned(), labels, file!())
1027 })
1028 .collect()
1029}
1030
1031pub fn remove_children_with_label<V: MetricVec_ + Collector>(vec: &V, name: &str, value: &str) {
1037 let descs = vec.desc();
1038 let Some(desc) = descs.first() else {
1040 return;
1041 };
1042 for family in vec.collect() {
1043 for child in family.get_metric() {
1044 let labels: BTreeMap<&str, &str> = child
1045 .get_label()
1046 .iter()
1047 .map(|pair| (pair.name(), pair.value()))
1048 .collect();
1049 if labels.get(name) != Some(&value) {
1050 continue;
1051 }
1052 let values: Vec<&str> = desc
1053 .variable_labels
1054 .iter()
1055 .map(|label| labels.get(label.as_str()).copied().unwrap_or_default())
1056 .collect();
1057 let _ = vec.remove_label_values(&values);
1061 }
1062 }
1063}
1064
1065#[cfg(test)]
1066mod tests {
1067 use std::time::Duration;
1068
1069 use prometheus::core::Collector;
1070 use prometheus::{CounterVec, HistogramVec};
1071
1072 use crate::stats::histogram_seconds_buckets;
1073
1074 use super::{MetricsFutureExt, MetricsRegistry};
1075
1076 struct Metrics {
1077 pub wall_time_hist: HistogramVec,
1078 pub wall_time_cnt: CounterVec,
1079 pub exec_time_hist: HistogramVec,
1080 pub exec_time_cnt: CounterVec,
1081 }
1082
1083 impl Metrics {
1084 pub fn register_into(registry: &MetricsRegistry) -> Self {
1085 Self {
1086 wall_time_hist: registry.register(metric!(
1087 name: "wall_time_hist",
1088 help: "help",
1089 var_labels: ["action"],
1090 buckets: histogram_seconds_buckets(0.000_128, 8.0),
1091 )),
1092 wall_time_cnt: registry.register(metric!(
1093 name: "wall_time_cnt",
1094 help: "help",
1095 var_labels: ["action"],
1096 )),
1097 exec_time_hist: registry.register(metric!(
1098 name: "exec_time_hist",
1099 help: "help",
1100 var_labels: ["action"],
1101 buckets: histogram_seconds_buckets(0.000_128, 8.0),
1102 )),
1103 exec_time_cnt: registry.register(metric!(
1104 name: "exec_time_cnt",
1105 help: "help",
1106 var_labels: ["action"],
1107 )),
1108 }
1109 }
1110 }
1111
1112 #[crate::test]
1113 #[cfg_attr(miri, ignore)] fn smoke_test_metrics_future_ext() {
1115 let runtime = tokio::runtime::Builder::new_current_thread()
1116 .enable_time()
1117 .build()
1118 .expect("failed to start runtime");
1119 let registry = MetricsRegistry::new();
1120 let metrics = Metrics::register_into(®istry);
1121
1122 let async_sleep_future = async {
1124 tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
1125 };
1126 runtime.block_on(
1127 async_sleep_future
1128 .wall_time()
1129 .observe(metrics.wall_time_hist.with_label_values(&["async_sleep_w"]))
1130 .exec_time()
1131 .observe(metrics.exec_time_hist.with_label_values(&["async_sleep_e"])),
1132 );
1133
1134 let reports = registry.gather();
1135
1136 let exec_family = reports
1137 .iter()
1138 .find(|m| m.name() == "exec_time_hist")
1139 .expect("metric not found");
1140 let exec_metric = exec_family.get_metric();
1141 assert_eq!(exec_metric.len(), 1);
1142 assert_eq!(exec_metric[0].get_label()[0].value(), "async_sleep_e");
1143
1144 let exec_histogram = exec_metric[0].get_histogram();
1145 assert_eq!(exec_histogram.get_sample_count(), 1);
1146 let wall_family = reports
1151 .iter()
1152 .find(|m| m.name() == "wall_time_hist")
1153 .expect("metric not found");
1154 let wall_metric = wall_family.get_metric();
1155 assert_eq!(wall_metric.len(), 1);
1156 assert_eq!(wall_metric[0].get_label()[0].value(), "async_sleep_w");
1157
1158 let wall_histogram = wall_metric[0].get_histogram();
1159 assert_eq!(wall_histogram.get_sample_count(), 1);
1160 assert_eq!(wall_histogram.get_bucket()[12].cumulative_count(), 0);
1163
1164 let registry = MetricsRegistry::new();
1166 let metrics = Metrics::register_into(®istry);
1167
1168 let thread_sleep_future = async {
1170 std::thread::sleep(std::time::Duration::from_secs(1));
1171 };
1172 runtime.block_on(
1173 thread_sleep_future
1174 .wall_time()
1175 .with_filter(|duration| duration < Duration::from_millis(10))
1176 .inc_by(metrics.wall_time_cnt.with_label_values(&["thread_sleep_w"]))
1177 .exec_time()
1178 .inc_by(metrics.exec_time_cnt.with_label_values(&["thread_sleep_e"])),
1179 );
1180
1181 let reports = registry.gather();
1182
1183 let exec_family = reports
1184 .iter()
1185 .find(|m| m.name() == "exec_time_cnt")
1186 .expect("metric not found");
1187 let exec_metric = exec_family.get_metric();
1188 assert_eq!(exec_metric.len(), 1);
1189 assert_eq!(exec_metric[0].get_label()[0].value(), "thread_sleep_e");
1190
1191 let exec_counter = exec_metric[0].get_counter();
1192 assert!(exec_counter.value() >= 1.0);
1194
1195 let wall_family = reports
1196 .iter()
1197 .find(|m| m.name() == "wall_time_cnt")
1198 .expect("metric not found");
1199 let wall_metric = wall_family.get_metric();
1200 assert_eq!(wall_metric.len(), 1);
1201
1202 let wall_counter = wall_metric[0].get_counter();
1203 assert_eq!(wall_counter.value(), 0.0);
1205 }
1206
1207 #[crate::test]
1208 fn collector_drop_handle_unregisters() {
1209 use prometheus::IntGauge;
1210
1211 let registry = MetricsRegistry::new();
1212 let gauge = IntGauge::new("mz_test_guarded", "help").unwrap();
1213 gauge.set(7);
1214 let before = registry.gather().len();
1215
1216 let handle = registry.register_collector_with_dropper(gauge.clone());
1217 assert_eq!(registry.gather().len(), before + 1);
1218
1219 drop(handle);
1221 assert_eq!(registry.gather().len(), before);
1222 }
1223
1224 #[crate::test]
1225 fn register_drop_then_reregister() {
1226 use prometheus::IntGauge;
1227
1228 let registry = MetricsRegistry::new();
1233 let old = IntGauge::new("mz_test_reregister", "help").unwrap();
1234 let new = IntGauge::new("mz_test_reregister", "help").unwrap();
1235 let before = registry.gather().len();
1236
1237 let handle = registry.register_collector_with_dropper(old);
1238 assert_eq!(registry.gather().len(), before + 1);
1239
1240 drop(handle);
1242 let handle = registry.register_collector_with_dropper(new);
1243 assert_eq!(registry.gather().len(), before + 1);
1244
1245 drop(handle);
1246 assert_eq!(registry.gather().len(), before);
1247 }
1248
1249 #[crate::test]
1250 fn remove_children_with_label_removes_only_matching_children() {
1251 let registry = MetricsRegistry::new();
1252 let vec: HistogramVec = registry.register(metric!(
1253 name: "labeled_hist",
1254 help: "help",
1255 var_labels: ["cluster", "kind"],
1256 buckets: histogram_seconds_buckets(0.000_128, 8.0),
1257 ));
1258 vec.with_label_values(&["u1", "a"]).observe(1.0);
1259 vec.with_label_values(&["u1", "b"]).observe(1.0);
1260 vec.with_label_values(&["u2", "a"]).observe(1.0);
1261
1262 super::remove_children_with_label(&vec, "cluster", "u1");
1263
1264 let remaining: Vec<Vec<String>> = vec
1265 .collect()
1266 .into_iter()
1267 .flat_map(|family| family.get_metric().to_vec())
1268 .map(|child| {
1269 child
1270 .get_label()
1271 .iter()
1272 .map(|pair| pair.value().to_string())
1273 .collect()
1274 })
1275 .collect();
1276 assert_eq!(remaining, vec![vec!["u2".to_string(), "a".to_string()]]);
1277 }
1278}