Skip to main content

mz_ore/
metrics.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Metrics for materialize systems.
17//!
18//! The idea here is that each subsystem keeps its metrics in a scoped-to-it struct, which gets
19//! registered (once) to the server's (or a test's) prometheus registry.
20//!
21//! Instead of using prometheus's (very verbose) metrics definitions, we rely on type inference to
22//! reduce the verbosity a little bit. A typical subsystem will look like the following:
23//!
24//! ```rust
25//! # use mz_ore::metrics::{MetricsRegistry, IntCounter};
26//! # use mz_ore::metric;
27//! #[derive(Debug, Clone)] // Note that prometheus metrics can safely be cloned
28//! struct Metrics {
29//!     pub bytes_sent: IntCounter,
30//! }
31//!
32//! impl Metrics {
33//!     pub fn register_into(registry: &MetricsRegistry) -> Metrics {
34//!         Metrics {
35//!             bytes_sent: registry.register(metric!(
36//!                 name: "mz_pg_sent_bytes",
37//!                 help: "total number of bytes sent here",
38//!             )),
39//!         }
40//!     }
41//! }
42//! ```
43
44use 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/// Define a metric for use in materialize.
70#[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        // Set buckets if passed
101        $(mk_opts.buckets = Some($bk_name);)*
102        // `visibility` is documentation metadata for the metrics catalog
103        // (`bin/gen-metrics-catalog`).
104        // It has no runtime effect; we only type-check it here so a bad value is
105        // a compile error rather than silently ignored.
106        $(let _: $crate::metrics::MetricVisibility = $visibility;)?
107        // `tags` is documentation metadata for the metrics catalog.
108        // It has no runtime effect.
109        $($(let _: $crate::metrics::MetricTag = $tag;)*)?
110        mk_opts
111    }}
112}
113
114/// Options for MakeCollector. This struct should be instantiated using the metric macro.
115#[derive(Debug, Clone)]
116pub struct MakeCollectorOpts {
117    /// Common Prometheus options
118    pub opts: PrometheusOpts,
119    /// Buckets to be used with Histogram and HistogramVec. Must be set to create Histogram types
120    /// and must not be set for other types.
121    pub buckets: Option<Vec<f64>>,
122}
123
124/// This is documentation metadata: it is set via the optional `visibility:`
125/// field of [`metric!`] and consumed by the metrics catalog
126/// (`bin/gen-metrics-catalog`), which reads it from the source tree to produce
127/// the user-facing metrics reference. It has no effect on the metric at
128/// runtime.
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize)]
130#[serde(rename_all = "snake_case")]
131pub enum MetricVisibility {
132    /// A metric we use for internal development.
133    #[default]
134    Internal,
135    /// A metric we want customers to build dashboards and
136    /// alerts on. We do not guarantee stability for this group
137    /// of metrics.
138    Public,
139}
140
141/// A tag categorizing a metric in the user-facing metrics catalog.
142///
143/// It is set via the optional `tags:` field of [`metric!`] and
144/// consumed by the metrics catalog (`bin/gen-metrics-catalog`).
145/// It has no effect on the metric at runtime.
146///
147/// A metric may carry many tags, or none (the default). A tag names the
148/// grouping the metric is presented under in user-facing documentation.
149#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
150#[serde(rename_all = "kebab-case")]
151pub enum MetricTag {
152    /// SQL Control plane metrics (client connections, availability, catalog).
153    Environment,
154    /// Metrics for compute objects (indexes, materialized views).
155    Compute,
156    /// Metrics for sources.
157    Source,
158    /// Metrics for sinks.
159    Sink,
160}
161
162/// The materialize metrics registry.
163#[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/// A wrapper for metrics to require delete on drop semantics
172///
173/// The wrapper behaves like regular metrics but only provides functions to create delete-on-drop
174/// variants. This way, no metrics of this type can be leaked.
175///
176/// In situations where the delete-on-drop behavior is not desired or in legacy code, use the raw
177/// variants of the metrics, as defined in [self::raw].
178#[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    /// Returns a metric that deletes its labels from this metrics vector when dropped.
209    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
217/// The unsigned integer version of [`Gauge`]. Provides better performance if
218/// metric values are all unsigned integers.
219pub type UIntGauge = GenericGauge<AtomicU64>;
220
221/// Delete-on-drop shadow of Prometheus [prometheus::CounterVec].
222pub type CounterVec = DeleteOnDropWrapper<prometheus::CounterVec>;
223/// Delete-on-drop shadow of Prometheus [prometheus::Gauge].
224pub type Gauge = DeleteOnDropWrapper<prometheus::Gauge>;
225/// Delete-on-drop shadow of Prometheus [prometheus::GaugeVec].
226pub type GaugeVec = DeleteOnDropWrapper<prometheus::GaugeVec>;
227/// Delete-on-drop shadow of Prometheus [prometheus::HistogramVec].
228pub type HistogramVec = DeleteOnDropWrapper<prometheus::HistogramVec>;
229/// Delete-on-drop shadow of Prometheus [prometheus::IntCounterVec].
230pub type IntCounterVec = DeleteOnDropWrapper<prometheus::IntCounterVec>;
231/// Delete-on-drop shadow of Prometheus [prometheus::IntGaugeVec].
232pub type IntGaugeVec = DeleteOnDropWrapper<prometheus::IntGaugeVec>;
233/// Delete-on-drop shadow of Prometheus [raw::UIntGaugeVec].
234pub type UIntGaugeVec = DeleteOnDropWrapper<raw::UIntGaugeVec>;
235
236use crate::assert_none;
237
238pub use prometheus::{Counter, Histogram, IntCounter, IntGauge};
239
240/// Access to non-delete-on-drop vector types
241pub mod raw {
242    use prometheus::core::{AtomicU64, GenericGaugeVec};
243
244    /// The unsigned integer version of [`GaugeVec`].
245    /// Provides better performance if metric values are all unsigned integers.
246    pub type UIntGaugeVec = GenericGaugeVec<AtomicU64>;
247
248    pub use prometheus::{CounterVec, Gauge, GaugeVec, HistogramVec, IntCounterVec, IntGaugeVec};
249}
250
251impl MetricsRegistry {
252    /// Creates a new metrics registry.
253    pub fn new() -> Self {
254        MetricsRegistry {
255            inner: Registry::new(),
256            postprocessors: Arc::new(Mutex::new(vec![])),
257        }
258    }
259
260    /// Register a metric defined with the [`metric`] macro.
261    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    /// Registers a gauge whose value is computed when observed.
271    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    /// Register a pre-defined prometheus collector.
288    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    /// Register a pre-defined collector and return a handle that unregisters it on drop.
295    ///
296    /// Use this for collectors with a bounded lifetime (e.g. a metric sink that is torn down with
297    /// its dataflow), so the collector's series stop being scraped once the owner drops the handle.
298    /// The returned value is opaque: the caller only needs to hold it for as long as the collector
299    /// should stay registered, then drop it.
300    ///
301    /// `prometheus::Registry::unregister` matches a collector by the id of its `Desc`s, not by
302    /// object identity, so the guard keeps a clone of the collector and hands it back to
303    /// `unregister` on drop.
304    ///
305    /// If a collector with the same descriptor id is already registered this soft-panics and
306    /// returns a handle that owns no registration. A duplicate registration is a logic error, but
307    /// panicking here would run on a worker thread and could crash the process, so it degrades to
308    /// missing series rather than taking down the whole scrape. The contract is that a caller
309    /// replacing a collector must drop the old handle before registering the new one: doing so
310    /// unregisters the old descriptor id first, so the re-registration succeeds cleanly.
311    ///
312    /// A caller that expects such a collision should use
313    /// [`MetricsRegistry::try_register_collector_with_dropper`] instead.
314    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                // Nothing was registered, so the handle must not unregister anything on drop.
322                Box::new(())
323            })
324    }
325
326    /// Like [`MetricsRegistry::register_collector_with_dropper`], but returns `Err` on a duplicate
327    /// descriptor id instead of soft-panicking.
328    ///
329    /// For callers that expect a transient collision, where a collector is re-registered before
330    /// its predecessor's teardown has dropped the old handle, and want to retry until the id frees
331    /// up. Nothing is registered on `Err`, so the caller may retry with the same collector.
332    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        // `prometheus::Registry` is `Arc`-backed, so this clone is cheap and shares the same
341        // underlying registry the collector was registered into.
342        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    /// Registers a metric postprocessor.
349    ///
350    /// Postprocessors are invoked on every call to [`MetricsRegistry::gather`]
351    /// in the order that they are registered.
352    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    /// Gather all the metrics from the metrics registry for reporting.
361    ///
362    /// This function invokes the postprocessors on all gathered metrics (see
363    /// [`MetricsRegistry::register_postprocessor`]) in the order the
364    /// postprocessors were registered.
365    ///
366    /// See also [`prometheus::Registry::gather`].
367    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
377/// A wrapper for creating prometheus metrics more conveniently.
378///
379/// Together with the [`metric`] macro, this trait is mainly used by [`MetricsRegistry`] and should
380/// not normally be used outside the metric registration flow.
381pub trait MakeCollector: Collector + Clone + 'static {
382    /// Creates a new collector.
383    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
457/// A [`Gauge`] whose value is computed whenever it is observed.
458pub 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    /// Computes the current value of the gauge.
508    pub fn get(&self) -> P::T {
509        (self.f)()
510    }
511}
512
513/// A [`ComputedGenericGauge`] for 64-bit floating point numbers.
514pub type ComputedGauge = ComputedGenericGauge<AtomicF64>;
515
516/// A [`ComputedGenericGauge`] for 64-bit signed integers.
517pub type ComputedIntGauge = ComputedGenericGauge<AtomicI64>;
518
519/// A [`ComputedGenericGauge`] for 64-bit unsigned integers.
520pub type ComputedUIntGauge = ComputedGenericGauge<AtomicU64>;
521
522/// Exposes combinators that report metrics related to the execution of a [`Future`] to prometheus.
523pub trait MetricsFutureExt<F> {
524    /// Records the number of seconds it takes a [`Future`] to complete according to "the clock on
525    /// the wall".
526    ///
527    /// More specifically, it records the instant at which the `Future` was first polled, and the
528    /// instant at which the `Future` completes. Then reports the duration between those two
529    /// instances to the provided metric.
530    ///
531    /// # Wall Time vs Execution Time
532    ///
533    /// There is also [`MetricsFutureExt::exec_time`], which measures how long a [`Future`] spent
534    /// executing, instead of how long it took to complete. For example, a network request may have
535    /// a wall time of 1 second, meanwhile it's execution time may have only been 50ms. The 950ms
536    /// delta would be how long the [`Future`] waited for a response from the network.
537    ///
538    /// # Uses
539    ///
540    /// Recording the wall time can be useful for monitoring latency, for example the latency of a
541    /// SQL request.
542    ///
543    /// Note: You must call either [`observe`] to record the execution time to a [`Histogram`] or
544    /// [`inc_by`] to record to a [`Counter`]. The following will not compile:
545    ///
546    /// ```compile_fail
547    /// use mz_ore::metrics::MetricsFutureExt;
548    ///
549    /// # let _ = async {
550    /// async { Ok(()) }
551    ///     .wall_time()
552    ///     .await;
553    /// # };
554    /// ```
555    ///
556    /// [`observe`]: WallTimeFuture::observe
557    /// [`inc_by`]: WallTimeFuture::inc_by
558    fn wall_time(self) -> WallTimeFuture<F, UnspecifiedMetric>;
559
560    /// Records the total number of seconds for which a [`Future`] was executing.
561    ///
562    /// More specifically, every time the `Future` is polled it records how long that individual
563    /// call took, and maintains a running sum until the `Future` completes. Then we report that
564    /// duration to the provided metric.
565    ///
566    /// # Wall Time vs Execution Time
567    ///
568    /// There is also [`MetricsFutureExt::wall_time`], which measures how long a [`Future`] took to
569    /// complete, instead of how long it spent executing. For example, a network request may have
570    /// a wall time of 1 second, meanwhile it's execution time may have only been 50ms. The 950ms
571    /// delta would be how long the [`Future`] waited for a response from the network.
572    ///
573    /// # Uses
574    ///
575    /// Recording execution time can be useful if you want to monitor [`Future`]s that could be
576    /// sensitive to CPU usage. For example, if you have a single logical control thread you'll
577    /// want to make sure that thread never spends too long running a single `Future`. Reporting
578    /// the execution time of `Future`s running on this thread can help ensure there is no
579    /// unexpected blocking.
580    ///
581    /// Note: You must call either [`observe`] to record the execution time to a [`Histogram`] or
582    /// [`inc_by`] to record to a [`Counter`]. The following will not compile:
583    ///
584    /// ```compile_fail
585    /// use mz_ore::metrics::MetricsFutureExt;
586    ///
587    /// # let _ = async {
588    /// async { Ok(()) }
589    ///     .exec_time()
590    ///     .await;
591    /// # };
592    /// ```
593    ///
594    /// [`observe`]: ExecTimeFuture::observe
595    /// [`inc_by`]: ExecTimeFuture::inc_by
596    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/// Future returned by [`MetricsFutureExt::wall_time`].
620#[must_use = "futures do nothing unless you `.await` or poll them"]
621#[pin_project]
622pub struct WallTimeFuture<F, Metric> {
623    /// The inner [`Future`] that we're recording the wall time for.
624    #[pin]
625    fut: F,
626    /// Prometheus metric that we'll report to.
627    metric: Metric,
628    /// [`Instant`] at which the [`Future`] was first polled.
629    start: Option<Instant>,
630    /// Optional filter that determines if we observe the wall time of this [`Future`].
631    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    /// Sets the recored metric to be a [`prometheus::Histogram`].
647    ///
648    /// ```text
649    /// my_future
650    ///     .wall_time()
651    ///     .observe(metrics.slow_queries_hist.with_label_values(&["select"]))
652    /// ```
653    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    /// Sets the recored metric to be a [`prometheus::Counter`].
666    ///
667    /// ```text
668    /// my_future
669    ///     .wall_time()
670    ///     .inc_by(metrics.slow_queries.with_label_values(&["select"]))
671    /// ```
672    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    /// Sets the recorded duration in a specific f64.
682    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    /// Specifies a filter which much return `true` for the wall time to be recorded.
694    ///
695    /// This can be particularly useful if you have a high volume `Future` and you only want to
696    /// record ones that take a long time to complete.
697    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/// Future returned by [`MetricsFutureExt::exec_time`].
736#[must_use = "futures do nothing unless you `.await` or poll them"]
737#[pin_project]
738pub struct ExecTimeFuture<F, Metric> {
739    /// The inner [`Future`] that we're recording the wall time for.
740    #[pin]
741    fut: F,
742    /// Prometheus metric that we'll report to.
743    metric: Metric,
744    /// Total [`Duration`] for which this [`Future`] has been executing.
745    running_duration: Duration,
746    /// Optional filter that determines if we observe the execution time of this [`Future`].
747    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    /// Sets the recored metric to be a [`prometheus::Histogram`].
763    ///
764    /// ```text
765    /// my_future
766    ///     .exec_time()
767    ///     .observe(metrics.slow_queries_hist.with_label_values(&["select"]))
768    /// ```
769    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    /// Sets the recored metric to be a [`prometheus::Counter`].
782    ///
783    /// ```text
784    /// my_future
785    ///     .exec_time()
786    ///     .inc_by(metrics.slow_queries.with_label_values(&["select"]))
787    /// ```
788    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    /// Specifies a filter which much return `true` for the execution time to be recorded.
800    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/// A type level flag used to ensure callers specify the kind of metric to record for
841/// [`MetricsFutureExt`].
842///
843/// For example, `WallTimeFuture<F, M>` only implements [`Future`] for `M` that implements
844/// `DurationMetric` which [`UnspecifiedMetric`] does not. This forces users at build time to
845/// call [`WallTimeFuture::observe`] or [`WallTimeFuture::inc_by`].
846#[derive(Debug)]
847pub struct UnspecifiedMetric(());
848
849/// A trait makes recording a duration generic over different prometheus metrics. This allows us to
850/// de-dupe the implemenation of [`Future`] for our wrapper Futures like [`WallTimeFuture`] and
851/// [`ExecTimeFuture`] over different kinds of prometheus metrics.
852trait 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
868// An implementation of `DurationMetric` that lets the user take the recorded
869// value and use it elsewhere.
870impl DurationMetric for &'_ mut f64 {
871    fn record(&mut self, seconds: f64) {
872        **self = seconds;
873    }
874}
875
876/// Register the Tokio runtime's metrics in our metrics registry.
877#[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/// Returns the `(name, help, labels, source)` of every Tokio runtime metric
1020/// registered by [`register_runtime_metrics`].
1021#[cfg(feature = "async")]
1022pub fn describe_runtime_metrics() -> Vec<(String, String, Vec<String>, &'static str)> {
1023    // A current-thread runtime is enough to enumerate the metrics; we only read
1024    // their names, help text, and labels, never their values.
1025    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(), &registry);
1030    registry
1031        .gather()
1032        .into_iter()
1033        .map(|mf| {
1034            // Every series in a family shares the same label keys, so the first
1035            // metric's labels are representative.
1036            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
1048/// Removes every child of `vec` whose label `name` has the value `value`.
1049///
1050/// Prometheus removes children only by their full label tuple, so this learns
1051/// the tuples by collecting the vec, which clones every child once. Meant for
1052/// occasional cleanup such as dropping an object's series, not for hot paths.
1053pub fn remove_children_with_label<V: MetricVec_ + Collector>(vec: &V, name: &str, value: &str) {
1054    let descs = vec.desc();
1055    // A metric vec has exactly one `Desc`.
1056    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            // `remove_label_values` fails when the series is already gone, which happens
1075            // if another caller removed it between the `collect` snapshot above and now.
1076            // That is the state we wanted, so the error is ignored.
1077            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)] // unsupported operation: integer-to-pointer casts and `ptr::from_exposed_addr` are not supported with `-Zmiri-strict-provenance`
1131    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(&registry);
1138
1139        // Record the walltime and execution time of an async sleep.
1140        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        // This future will normally complete very quickly, but it's hard to guarantee any
1164        // particular timing in an arbitrary test environment, so we don't assert on it
1165        // here.
1166
1167        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        // The 13th bucket is 512ms, which the wall time should be longer than, but is also much
1178        // faster than the actual execution time of the async sleep.
1179        assert_eq!(wall_histogram.get_bucket()[12].cumulative_count(), 0);
1180
1181        // Reset the registery to make collecting metrics easier.
1182        let registry = MetricsRegistry::new();
1183        let metrics = Metrics::register_into(&registry);
1184
1185        // Record the walltime and execution time of a thread sleep.
1186        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        // Since we're synchronously sleeping the execution time will be long.
1210        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        // We filtered wall time to < 10ms, so our wall time metric should be filtered out.
1221        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        // Dropping the handle unregisters the collector, so its series stops being scraped.
1237        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        // Two collectors sharing a descriptor id: re-registering the second while the first is
1246        // still registered would collide. This locks in the contract that dropping the old handle
1247        // first clears the id so the re-registration succeeds cleanly, the ordering a caller
1248        // replacing a collector (e.g. a metric sink re-rendered on reconciliation) must uphold.
1249        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 old before registering new, matching the required ordering.
1258        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        // The fallible variant reports the collision instead of soft-panicking, and the collision
1271        // clears as soon as the incumbent's handle drops. This is the retry contract the metric
1272        // sink operator leans on when a new incarnation races the old one's teardown.
1273        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        // The failed attempt registered nothing, so the incumbent is still the only series.
1289        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}