Skip to main content

datafusion_physical_expr_common/metrics/
mod.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Metrics for recording information about execution
19
20mod baseline;
21mod builder;
22mod custom;
23mod elapsed_compute;
24mod expression;
25mod value;
26
27use datafusion_common::HashMap;
28pub use datafusion_common::format::{MetricCategory, MetricType};
29use parking_lot::Mutex;
30use std::{
31    borrow::Cow,
32    fmt::{self, Debug, Display},
33    hash::{Hash, Hasher},
34    sync::Arc,
35    vec::IntoIter,
36};
37
38// public exports
39
40pub use baseline::{BaselineMetrics, RecordOutput, SpillMetrics, SplitMetrics};
41pub use builder::MetricBuilder;
42pub use custom::CustomMetricValue;
43pub use elapsed_compute::{ElapsedComputeFuture, ElapsedComputeFutureExt};
44pub use expression::ExpressionEvaluatorMetrics;
45pub use value::{
46    Count, Gauge, MetricValue, PruningMetrics, RatioMergeStrategy, RatioMetrics,
47    ScopedTimerGuard, Time, Timestamp,
48};
49
50/// Something that tracks a value of interest (metric) during execution.
51///
52/// Typically [`Metric`]s are not created directly, but instead
53/// are created using [`MetricBuilder`] or methods on
54/// [`ExecutionPlanMetricsSet`].
55///
56/// ```
57/// use datafusion_physical_expr_common::metrics::*;
58///
59/// let metrics = ExecutionPlanMetricsSet::new();
60/// assert!(metrics.clone_inner().output_rows().is_none());
61///
62/// // Create a counter to increment using the MetricBuilder
63/// let partition = 1;
64/// let output_rows = MetricBuilder::new(&metrics).output_rows(partition);
65///
66/// // Counter can be incremented
67/// output_rows.add(13);
68///
69/// // The value can be retrieved directly:
70/// assert_eq!(output_rows.value(), 13);
71///
72/// // As well as from the metrics set
73/// assert_eq!(metrics.clone_inner().output_rows(), Some(13));
74/// ```
75
76#[derive(Debug)]
77pub struct Metric {
78    /// The value of the metric
79    value: MetricValue,
80
81    /// arbitrary name=value pairs identifying this metric
82    labels: Vec<Label>,
83
84    /// To which partition of an operators output did this metric
85    /// apply? If `None` then means all partitions.
86    partition: Option<usize>,
87
88    metric_type: MetricType,
89
90    /// Optional semantic category (rows / bytes / timing).
91    ///
92    /// When `None` (the default for custom metrics), the metric is
93    /// **always included** unless the user sets
94    /// `analyze_categories = 'none'`.
95    metric_category: Option<MetricCategory>,
96}
97
98impl Display for Metric {
99    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100        write!(f, "{}", self.value.name())?;
101
102        let mut iter = self
103            .partition
104            .iter()
105            .map(|partition| Label::new("partition", partition.to_string()))
106            .chain(self.labels().iter().cloned())
107            .peekable();
108
109        // print out the labels specially
110        if iter.peek().is_some() {
111            write!(f, "{{")?;
112
113            let mut is_first = true;
114            for i in iter {
115                if !is_first {
116                    write!(f, ", ")?;
117                } else {
118                    is_first = false;
119                }
120
121                write!(f, "{i}")?;
122            }
123
124            write!(f, "}}")?;
125        }
126
127        // and now the value
128        write!(f, "={}", self.value)
129    }
130}
131
132impl Metric {
133    /// Create a new [`Metric`]. Consider using [`MetricBuilder`]
134    /// rather than this function directly.
135    pub fn new(value: MetricValue, partition: Option<usize>) -> Self {
136        Self {
137            value,
138            labels: vec![],
139            partition,
140            metric_type: MetricType::Dev,
141            metric_category: None,
142        }
143    }
144
145    /// Create a new [`Metric`]. Consider using [`MetricBuilder`]
146    /// rather than this function directly.
147    pub fn new_with_labels(
148        value: MetricValue,
149        partition: Option<usize>,
150        labels: Vec<Label>,
151    ) -> Self {
152        Self {
153            value,
154            labels,
155            partition,
156            metric_type: MetricType::Dev,
157            metric_category: None,
158        }
159    }
160
161    /// Set the type for this metric. Defaults to [`MetricType::Dev`]
162    pub fn with_type(mut self, metric_type: MetricType) -> Self {
163        self.metric_type = metric_type;
164        self
165    }
166
167    /// Set the semantic category for this metric.
168    ///
169    /// See [`MetricCategory`] for details on the determinism properties
170    /// of each category.
171    pub fn with_category(mut self, category: MetricCategory) -> Self {
172        self.metric_category = Some(category);
173        self
174    }
175
176    /// Add a new label to this metric
177    pub fn with_label(mut self, label: Label) -> Self {
178        self.labels.push(label);
179        self
180    }
181
182    /// What labels are present for this metric?
183    pub fn labels(&self) -> &[Label] {
184        &self.labels
185    }
186
187    /// Return a reference to the value of this metric
188    pub fn value(&self) -> &MetricValue {
189        &self.value
190    }
191
192    /// Return a mutable reference to the value of this metric
193    pub fn value_mut(&mut self) -> &mut MetricValue {
194        &mut self.value
195    }
196
197    /// Return a reference to the partition
198    pub fn partition(&self) -> Option<usize> {
199        self.partition
200    }
201
202    /// Return the metric type (verbosity level) associated with this metric
203    pub fn metric_type(&self) -> MetricType {
204        self.metric_type
205    }
206
207    /// Return the metric category, if one was declared.
208    ///
209    /// `None` means the metric is always included (except in `none` mode).
210    pub fn metric_category(&self) -> Option<MetricCategory> {
211        self.metric_category
212    }
213}
214
215/// A snapshot of the metrics for a particular execution plan.
216#[derive(Default, Debug, Clone)]
217pub struct MetricsSet {
218    metrics: Vec<Arc<Metric>>,
219}
220
221impl MetricsSet {
222    /// Create a new container of metrics
223    pub fn new() -> Self {
224        Default::default()
225    }
226
227    /// Add the specified metric
228    pub fn push(&mut self, metric: Arc<Metric>) {
229        self.metrics.push(metric)
230    }
231
232    /// Returns an iterator across all metrics
233    pub fn iter(&self) -> impl Iterator<Item = &Arc<Metric>> {
234        self.metrics.iter()
235    }
236
237    /// Convenience: return the number of rows produced, aggregated
238    /// across partitions or `None` if no metric is present
239    pub fn output_rows(&self) -> Option<usize> {
240        self.sum(|metric| matches!(metric.value(), MetricValue::OutputRows(_)))
241            .map(|v| v.as_usize())
242    }
243
244    /// Convenience: return the count of spills, aggregated
245    /// across partitions or `None` if no metric is present
246    pub fn spill_count(&self) -> Option<usize> {
247        self.sum(|metric| matches!(metric.value(), MetricValue::SpillCount(_)))
248            .map(|v| v.as_usize())
249    }
250
251    /// Convenience: return the total byte size of spills, aggregated
252    /// across partitions or `None` if no metric is present
253    pub fn spilled_bytes(&self) -> Option<usize> {
254        self.sum(|metric| matches!(metric.value(), MetricValue::SpilledBytes(_)))
255            .map(|v| v.as_usize())
256    }
257
258    /// Convenience: return the total rows of spills, aggregated
259    /// across partitions or `None` if no metric is present
260    pub fn spilled_rows(&self) -> Option<usize> {
261        self.sum(|metric| matches!(metric.value(), MetricValue::SpilledRows(_)))
262            .map(|v| v.as_usize())
263    }
264
265    /// Convenience: return the amount of elapsed CPU time spent,
266    /// aggregated across partitions or `None` if no metric is present
267    pub fn elapsed_compute(&self) -> Option<usize> {
268        self.sum(|metric| matches!(metric.value(), MetricValue::ElapsedCompute(_)))
269            .map(|v| v.as_usize())
270    }
271
272    /// Sums the values for metrics for which `f(metric)` returns
273    /// `true`, and returns the value. Returns `None` if no metrics match
274    /// the predicate.
275    pub fn sum<F>(&self, mut f: F) -> Option<MetricValue>
276    where
277        F: FnMut(&Metric) -> bool,
278    {
279        let mut iter = self
280            .metrics
281            .iter()
282            .filter(|metric| f(metric.as_ref()))
283            .peekable();
284
285        let mut accum = match iter.peek() {
286            None => {
287                return None;
288            }
289            Some(metric) => metric.value().new_empty(),
290        };
291
292        iter.for_each(|metric| accum.aggregate(metric.value()));
293
294        Some(accum)
295    }
296
297    /// Returns the sum of all the metrics with the specified name
298    /// in the returned set.
299    pub fn sum_by_name(&self, metric_name: &str) -> Option<MetricValue> {
300        self.sum(|m| match m.value() {
301            MetricValue::Count { name, .. } => name == metric_name,
302            MetricValue::Time { name, .. } => name == metric_name,
303            MetricValue::OutputRows(_) => false,
304            MetricValue::ElapsedCompute(_) => false,
305            MetricValue::SpillCount(_) => false,
306            MetricValue::SpilledBytes(_) => false,
307            MetricValue::OutputBytes(_) => false,
308            MetricValue::OutputBatches(_) => false,
309            MetricValue::SpilledRows(_) => false,
310            MetricValue::CurrentMemoryUsage(_) => false,
311            MetricValue::Gauge { name, .. } => name == metric_name,
312            MetricValue::PeakMemoryUsage { name, .. } => name == metric_name,
313            MetricValue::StartTimestamp(_) => false,
314            MetricValue::EndTimestamp(_) => false,
315            MetricValue::PruningMetrics { name, .. } => name == metric_name,
316            MetricValue::Ratio { name, .. } => name == metric_name,
317            MetricValue::Custom { .. } => false,
318        })
319    }
320
321    /// Returns a new derived `MetricsSet` where all metrics
322    /// that had the same name have been
323    /// aggregated together. The resulting `MetricsSet` has all
324    /// metrics with `Partition=None`
325    pub fn aggregate_by_name(&self) -> Self {
326        let mut map = HashMap::new();
327
328        // There are all sorts of ways to make this more efficient
329        for metric in &self.metrics {
330            let key = metric.value.name();
331            map.entry(key)
332                .and_modify(|accum: &mut Metric| {
333                    accum.value_mut().aggregate(metric.value());
334                })
335                .or_insert_with(|| {
336                    // accumulate with no partition
337                    let partition = None;
338                    let mut accum = Metric::new(metric.value().new_empty(), partition)
339                        .with_type(metric.metric_type());
340                    if let Some(cat) = metric.metric_category() {
341                        accum = accum.with_category(cat);
342                    }
343                    accum.value_mut().aggregate(metric.value());
344                    accum
345                });
346        }
347
348        let new_metrics = map
349            .into_iter()
350            .map(|(_k, v)| Arc::new(v))
351            .collect::<Vec<_>>();
352
353        Self {
354            metrics: new_metrics,
355        }
356    }
357
358    /// Sort the order of metrics so the "most useful" show up first
359    pub fn sorted_for_display(mut self) -> Self {
360        self.metrics.sort_unstable_by_key(|metric| {
361            (
362                metric.value().display_sort_key(),
363                metric.value().name().to_owned(),
364            )
365        });
366        self
367    }
368
369    /// Remove all timestamp metrics (for more compact display)
370    pub fn timestamps_removed(self) -> Self {
371        let Self { metrics } = self;
372
373        let metrics = metrics
374            .into_iter()
375            .filter(|m| !m.value.is_timestamp())
376            .collect::<Vec<_>>();
377
378        Self { metrics }
379    }
380
381    /// Returns a new derived `MetricsSet` containing only metrics whose
382    /// [`MetricType`] appears in `allowed`.
383    pub fn filter_by_metric_types(self, allowed: &[MetricType]) -> Self {
384        if allowed.is_empty() {
385            return Self { metrics: vec![] };
386        }
387
388        let metrics = self
389            .metrics
390            .into_iter()
391            .filter(|metric| allowed.contains(&metric.metric_type()))
392            .collect::<Vec<_>>();
393        Self { metrics }
394    }
395
396    /// Returns a new `MetricsSet` filtered by [`MetricCategory`].
397    ///
398    /// - Metrics that declared a category are kept only when that
399    ///   category appears in `allowed`.
400    /// - Metrics with **no** declared category are treated as
401    ///   [`Uncategorized`](MetricCategory::Uncategorized) for filtering.
402    /// - An **empty** `allowed` slice means "plan only": all metrics are
403    ///   removed.
404    pub fn filter_by_categories(self, allowed: &[MetricCategory]) -> Self {
405        if allowed.is_empty() {
406            return Self { metrics: vec![] };
407        }
408
409        let metrics = self
410            .metrics
411            .into_iter()
412            .filter(|metric| {
413                let cat = metric
414                    .metric_category()
415                    .unwrap_or(MetricCategory::Uncategorized);
416                allowed.contains(&cat)
417            })
418            .collect::<Vec<_>>();
419        Self { metrics }
420    }
421
422    /// Returns a new `MetricsSet` filtered by metric name.
423    /// Only metrics with the names appearing the list will be kept.
424    pub fn filter_by_names(self, names: &[String]) -> Self {
425        if names.is_empty() {
426            return Self { metrics: vec![] };
427        }
428
429        let metrics = self
430            .metrics
431            .into_iter()
432            .filter(|metric| names.iter().any(|name| name == metric.value().name()))
433            .collect::<Vec<_>>();
434        Self { metrics }
435    }
436}
437
438impl Display for MetricsSet {
439    /// Format the [`MetricsSet`] as a single string
440    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
441        let mut is_first = true;
442        for i in self.metrics.iter() {
443            if !is_first {
444                write!(f, ", ")?;
445            } else {
446                is_first = false;
447            }
448
449            write!(f, "{i}")?;
450        }
451        Ok(())
452    }
453}
454
455impl IntoIterator for MetricsSet {
456    type Item = Arc<Metric>;
457    type IntoIter = IntoIter<Self::Item>;
458
459    fn into_iter(self) -> Self::IntoIter {
460        self.metrics.into_iter()
461    }
462}
463
464impl<'a> IntoIterator for &'a MetricsSet {
465    type Item = &'a Arc<Metric>;
466    type IntoIter = std::slice::Iter<'a, Arc<Metric>>;
467
468    fn into_iter(self) -> Self::IntoIter {
469        self.metrics.iter()
470    }
471}
472
473impl Extend<Arc<Metric>> for MetricsSet {
474    fn extend<I: IntoIterator<Item = Arc<Metric>>>(&mut self, iter: I) {
475        self.metrics.extend(iter);
476    }
477}
478
479impl FromIterator<Arc<Metric>> for MetricsSet {
480    fn from_iter<T: IntoIterator<Item = Arc<Metric>>>(iter: T) -> Self {
481        Self {
482            metrics: iter.into_iter().collect(),
483        }
484    }
485}
486
487/// A set of [`Metric`]s for an individual operator.
488///
489/// This structure is intended as a convenience for execution plan
490/// implementations so they can generate different streams for multiple
491/// partitions but easily report them together.
492///
493/// Each `clone()` of this structure will add metrics to the same
494/// underlying metrics set
495#[derive(Default, Debug, Clone)]
496pub struct ExecutionPlanMetricsSet {
497    inner: Arc<Mutex<MetricsSet>>,
498}
499
500impl ExecutionPlanMetricsSet {
501    /// Create a new empty shared metrics set
502    pub fn new() -> Self {
503        Self {
504            inner: Arc::new(Mutex::new(MetricsSet::new())),
505        }
506    }
507
508    /// Add the specified metric to the underlying metric set
509    pub fn register(&self, metric: Arc<Metric>) {
510        self.inner.lock().push(metric)
511    }
512
513    /// Return a clone of the inner [`MetricsSet`]
514    pub fn clone_inner(&self) -> MetricsSet {
515        let guard = self.inner.lock();
516        (*guard).clone()
517    }
518}
519
520impl From<MetricsSet> for ExecutionPlanMetricsSet {
521    fn from(metrics: MetricsSet) -> Self {
522        Self {
523            inner: Arc::new(Mutex::new(metrics)),
524        }
525    }
526}
527
528/// `name=value` pairs identifying a metric. This concept is called various things
529/// in various different systems:
530///
531/// "labels" in
532/// [prometheus](https://prometheus.io/docs/concepts/data_model/) and
533/// "tags" in
534/// [InfluxDB](https://docs.influxdata.com/influxdb/v1.8/write_protocols/line_protocol_tutorial/)
535/// , "attributes" in [open
536/// telemetry]<https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md>,
537/// etc.
538///
539/// As the name and value are expected to often be constant strings, borrowed
540/// static strings avoid allocations in that common case. Dynamic strings are
541/// stored behind [`Arc<str>`] so cloning labels does not copy the underlying
542/// string data.
543#[derive(Debug, Clone, PartialEq, Eq, Hash)]
544pub struct Label {
545    name: LabelValue,
546    value: LabelValue,
547}
548
549impl Label {
550    /// Create a new [`Label`]
551    pub fn new(name: impl Into<LabelValue>, value: impl Into<LabelValue>) -> Self {
552        let name = name.into();
553        let value = value.into();
554        Self { name, value }
555    }
556
557    /// Returns the name of this label
558    pub fn name(&self) -> &str {
559        self.name.as_str()
560    }
561
562    /// Returns the value of this label
563    pub fn value(&self) -> &str {
564        self.value.as_str()
565    }
566}
567
568impl Display for Label {
569    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
570        write!(f, "{}={}", self.name, self.value)
571    }
572}
573
574/// A label name or value.
575///
576/// String literals preserve the existing allocation-free path. Dynamic strings
577/// can be stored behind [`Arc<str>`], so cloning a [`Label`] only increments an
578/// atomic reference count and does not allocate or copy the underlying string
579/// data.
580#[derive(Clone)]
581pub struct LabelValue(LabelValueInner);
582
583/// Internal representation for label names and values.
584///
585/// `LabelValue` is public because `Label::new` accepts it, but these storage
586/// variants are implementation details. Keeping them private prevents external
587/// code from constructing or matching on `Static` and `Shared` directly.
588#[derive(Clone)]
589enum LabelValueInner {
590    Static(&'static str),
591    Shared(Arc<str>),
592}
593
594impl LabelValue {
595    /// Return this label value as a string slice.
596    pub fn as_str(&self) -> &str {
597        match &self.0 {
598            LabelValueInner::Static(value) => value,
599            LabelValueInner::Shared(value) => value.as_ref(),
600        }
601    }
602}
603
604impl From<&'static str> for LabelValue {
605    fn from(value: &'static str) -> Self {
606        Self(LabelValueInner::Static(value))
607    }
608}
609
610impl From<String> for LabelValue {
611    fn from(value: String) -> Self {
612        Self(LabelValueInner::Shared(Arc::from(value)))
613    }
614}
615
616impl From<Arc<str>> for LabelValue {
617    fn from(value: Arc<str>) -> Self {
618        Self(LabelValueInner::Shared(value))
619    }
620}
621
622impl From<Cow<'static, str>> for LabelValue {
623    fn from(value: Cow<'static, str>) -> Self {
624        match value {
625            Cow::Borrowed(value) => value.into(),
626            Cow::Owned(value) => value.into(),
627        }
628    }
629}
630
631impl PartialEq for LabelValue {
632    fn eq(&self, other: &Self) -> bool {
633        self.as_str() == other.as_str()
634    }
635}
636
637impl Eq for LabelValue {}
638
639impl Hash for LabelValue {
640    fn hash<H: Hasher>(&self, state: &mut H) {
641        self.as_str().hash(state);
642    }
643}
644
645impl Debug for LabelValue {
646    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
647        Debug::fmt(self.as_str(), f)
648    }
649}
650
651impl Display for LabelValue {
652    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653        Display::fmt(self.as_str(), f)
654    }
655}
656
657#[cfg(test)]
658mod tests {
659    use std::time::Duration;
660
661    use chrono::{TimeZone, Utc};
662
663    use super::*;
664
665    #[test]
666    fn test_display_no_labels_no_partition() {
667        let count = Count::new();
668        count.add(33);
669        let value = MetricValue::OutputRows(count);
670        let partition = None;
671        let metric = Metric::new(value, partition);
672
673        assert_eq!("output_rows=33", metric.to_string())
674    }
675
676    #[test]
677    fn test_display_no_labels_with_partition() {
678        let count = Count::new();
679        count.add(44);
680        let value = MetricValue::OutputRows(count);
681        let partition = Some(1);
682        let metric = Metric::new(value, partition);
683
684        assert_eq!("output_rows{partition=1}=44", metric.to_string())
685    }
686
687    #[test]
688    fn test_display_labels_no_partition() {
689        let count = Count::new();
690        count.add(55);
691        let value = MetricValue::OutputRows(count);
692        let partition = None;
693        let label = Label::new("foo", "bar");
694        let metric = Metric::new_with_labels(value, partition, vec![label]);
695
696        assert_eq!("output_rows{foo=bar}=55", metric.to_string())
697    }
698
699    #[test]
700    fn test_display_labels_and_partition() {
701        let count = Count::new();
702        count.add(66);
703        let value = MetricValue::OutputRows(count);
704        let partition = Some(2);
705        let label = Label::new("foo", "bar");
706        let metric = Metric::new_with_labels(value, partition, vec![label]);
707
708        assert_eq!("output_rows{partition=2, foo=bar}=66", metric.to_string())
709    }
710
711    #[test]
712    fn test_label_owned_and_borrowed_values_are_equal() {
713        let borrowed = Label::new("foo", "bar");
714        let owned = Label::new("foo".to_string(), "bar".to_string());
715        let shared = Label::new("foo", Arc::<str>::from("bar"));
716
717        assert_eq!(borrowed, owned);
718        assert_eq!(borrowed, shared);
719        assert_eq!(borrowed.to_string(), owned.to_string());
720        assert_eq!(borrowed.to_string(), shared.to_string());
721    }
722
723    #[test]
724    fn test_output_rows() {
725        let metrics = ExecutionPlanMetricsSet::new();
726        assert!(metrics.clone_inner().output_rows().is_none());
727
728        let partition = 1;
729        let output_rows = MetricBuilder::new(&metrics).output_rows(partition);
730        output_rows.add(13);
731
732        let output_rows = MetricBuilder::new(&metrics).output_rows(partition + 1);
733        output_rows.add(7);
734        assert_eq!(metrics.clone_inner().output_rows().unwrap(), 20);
735    }
736
737    #[test]
738    fn test_elapsed_compute() {
739        let metrics = ExecutionPlanMetricsSet::new();
740        assert!(metrics.clone_inner().elapsed_compute().is_none());
741
742        let partition = 1;
743        let elapsed_compute = MetricBuilder::new(&metrics).elapsed_compute(partition);
744        elapsed_compute.add_duration(Duration::from_nanos(1234));
745
746        let elapsed_compute = MetricBuilder::new(&metrics).elapsed_compute(partition + 1);
747        elapsed_compute.add_duration(Duration::from_nanos(6));
748        assert_eq!(metrics.clone_inner().elapsed_compute().unwrap(), 1240);
749    }
750
751    #[test]
752    fn test_sum() {
753        let metrics = ExecutionPlanMetricsSet::new();
754
755        let count1 = MetricBuilder::new(&metrics)
756            .with_new_label("foo", "bar")
757            .counter("my_counter", 1);
758        count1.add(1);
759
760        let count2 = MetricBuilder::new(&metrics).counter("my_counter", 2);
761        count2.add(2);
762
763        let metrics = metrics.clone_inner();
764        assert!(metrics.sum(|_| false).is_none());
765
766        let expected_count = Count::new();
767        expected_count.add(3);
768        let expected_sum = MetricValue::Count {
769            name: "my_counter".into(),
770            count: expected_count,
771        };
772
773        assert_eq!(metrics.sum(|_| true), Some(expected_sum));
774    }
775
776    #[test]
777    #[should_panic(expected = "Mismatched metric types. Can not aggregate Count")]
778    fn test_bad_sum() {
779        // can not add different kinds of metrics
780        let metrics = ExecutionPlanMetricsSet::new();
781
782        let count = MetricBuilder::new(&metrics).counter("my_metric", 1);
783        count.add(1);
784
785        let time = MetricBuilder::new(&metrics).subset_time("my_metric", 1);
786        time.add_duration(Duration::from_nanos(10));
787
788        // expect that this will error out
789        metrics.clone_inner().sum(|_| true);
790    }
791
792    #[test]
793    fn test_aggregate_by_name() {
794        let metrics = ExecutionPlanMetricsSet::new();
795
796        // Note cpu_time1 has labels but it is still aggregated with metrics 2 and 3
797        let elapsed_compute1 = MetricBuilder::new(&metrics)
798            .with_new_label("foo", "bar")
799            .elapsed_compute(1);
800        elapsed_compute1.add_duration(Duration::from_nanos(12));
801
802        let elapsed_compute2 = MetricBuilder::new(&metrics).elapsed_compute(2);
803        elapsed_compute2.add_duration(Duration::from_nanos(34));
804
805        let elapsed_compute3 = MetricBuilder::new(&metrics).elapsed_compute(4);
806        elapsed_compute3.add_duration(Duration::from_nanos(56));
807
808        let output_rows = MetricBuilder::new(&metrics).output_rows(1); // output rows
809        output_rows.add(56);
810
811        let aggregated = metrics.clone_inner().aggregate_by_name();
812
813        // cpu time should be aggregated:
814        let elapsed_computes = aggregated
815            .iter()
816            .filter(|metric| matches!(metric.value(), MetricValue::ElapsedCompute(_)))
817            .collect::<Vec<_>>();
818        assert_eq!(elapsed_computes.len(), 1);
819        assert_eq!(elapsed_computes[0].value().as_usize(), 12 + 34 + 56);
820        assert!(elapsed_computes[0].partition().is_none());
821
822        // output rows should
823        let output_rows = aggregated
824            .iter()
825            .filter(|metric| matches!(metric.value(), MetricValue::OutputRows(_)))
826            .collect::<Vec<_>>();
827        assert_eq!(output_rows.len(), 1);
828        assert_eq!(output_rows[0].value().as_usize(), 56);
829        assert!(output_rows[0].partition.is_none())
830    }
831
832    #[test]
833    #[should_panic(expected = "Mismatched metric types. Can not aggregate Count")]
834    fn test_aggregate_partition_bad_sum() {
835        let metrics = ExecutionPlanMetricsSet::new();
836
837        let count = MetricBuilder::new(&metrics).counter("my_metric", 1);
838        count.add(1);
839
840        let time = MetricBuilder::new(&metrics).subset_time("my_metric", 1);
841        time.add_duration(Duration::from_nanos(10));
842
843        // can't aggregate time and count -- expect a panic
844        metrics.clone_inner().aggregate_by_name();
845    }
846
847    #[test]
848    fn test_aggregate_partition_timestamps() {
849        let metrics = ExecutionPlanMetricsSet::new();
850
851        // 1431648000000000 == 1970-01-17 13:40:48 UTC
852        let t1 = Utc.timestamp_nanos(1431648000000000);
853        // 1531648000000000 == 1970-01-18 17:27:28 UTC
854        let t2 = Utc.timestamp_nanos(1531648000000000);
855        // 1631648000000000 == 1970-01-19 21:14:08 UTC
856        let t3 = Utc.timestamp_nanos(1631648000000000);
857        // 1731648000000000 == 1970-01-21 01:00:48 UTC
858        let t4 = Utc.timestamp_nanos(1731648000000000);
859
860        let start_timestamp0 = MetricBuilder::new(&metrics).start_timestamp(0);
861        start_timestamp0.set(t1);
862        let end_timestamp0 = MetricBuilder::new(&metrics).end_timestamp(0);
863        end_timestamp0.set(t2);
864        let start_timestamp1 = MetricBuilder::new(&metrics).start_timestamp(0);
865        start_timestamp1.set(t3);
866        let end_timestamp1 = MetricBuilder::new(&metrics).end_timestamp(0);
867        end_timestamp1.set(t4);
868
869        // aggregate
870        let aggregated = metrics.clone_inner().aggregate_by_name();
871
872        let mut ts = aggregated
873            .iter()
874            .filter(|metric| {
875                matches!(metric.value(), MetricValue::StartTimestamp(_))
876                    && metric.labels().is_empty()
877            })
878            .collect::<Vec<_>>();
879        assert_eq!(ts.len(), 1);
880        match ts.remove(0).value() {
881            MetricValue::StartTimestamp(ts) => {
882                // expect earliest of t1, t2
883                assert_eq!(ts.value(), Some(t1));
884            }
885            _ => {
886                panic!("Not a timestamp");
887            }
888        };
889
890        let mut ts = aggregated
891            .iter()
892            .filter(|metric| {
893                matches!(metric.value(), MetricValue::EndTimestamp(_))
894                    && metric.labels().is_empty()
895            })
896            .collect::<Vec<_>>();
897        assert_eq!(ts.len(), 1);
898        match ts.remove(0).value() {
899            MetricValue::EndTimestamp(ts) => {
900                // expect latest of t3, t4
901                assert_eq!(ts.value(), Some(t4));
902            }
903            _ => {
904                panic!("Not a timestamp");
905            }
906        };
907    }
908
909    #[test]
910    fn test_extend() {
911        let mut metrics = MetricsSet::new();
912        let m1 = Arc::new(Metric::new(MetricValue::OutputRows(Count::new()), None));
913        let m2 = Arc::new(Metric::new(MetricValue::SpillCount(Count::new()), None));
914
915        metrics.extend([Arc::clone(&m1), Arc::clone(&m2)]);
916        assert_eq!(metrics.iter().count(), 2);
917
918        let m3 = Arc::new(Metric::new(MetricValue::SpilledBytes(Count::new()), None));
919        metrics.extend(std::iter::once(Arc::clone(&m3)));
920        assert_eq!(metrics.iter().count(), 3);
921    }
922
923    #[test]
924    fn test_collect() {
925        let m1 = Arc::new(Metric::new(MetricValue::OutputRows(Count::new()), None));
926        let m2 = Arc::new(Metric::new(MetricValue::SpillCount(Count::new()), None));
927
928        let metrics: MetricsSet =
929            vec![Arc::clone(&m1), Arc::clone(&m2)].into_iter().collect();
930        assert_eq!(metrics.iter().count(), 2);
931
932        let empty: MetricsSet = std::iter::empty().collect();
933        assert_eq!(empty.iter().count(), 0);
934    }
935
936    #[test]
937    fn test_into_iterator_by_ref() {
938        let mut metrics = MetricsSet::new();
939        metrics.push(Arc::new(Metric::new(
940            MetricValue::OutputRows(Count::new()),
941            None,
942        )));
943        metrics.push(Arc::new(Metric::new(
944            MetricValue::SpillCount(Count::new()),
945            None,
946        )));
947
948        let mut count = 0;
949        for _m in &metrics {
950            count += 1;
951        }
952        assert_eq!(count, 2);
953    }
954
955    #[test]
956    fn test_sorted_for_display() {
957        let metrics = ExecutionPlanMetricsSet::new();
958        MetricBuilder::new(&metrics).end_timestamp(0);
959        MetricBuilder::new(&metrics).start_timestamp(0);
960        MetricBuilder::new(&metrics).elapsed_compute(0);
961        MetricBuilder::new(&metrics).counter("the_second_counter", 0);
962        MetricBuilder::new(&metrics).counter("the_counter", 0);
963        MetricBuilder::new(&metrics).counter("the_third_counter", 0);
964        MetricBuilder::new(&metrics).subset_time("the_time", 0);
965        MetricBuilder::new(&metrics).output_rows(0);
966        let metrics = metrics.clone_inner();
967
968        fn metric_names(metrics: &MetricsSet) -> String {
969            let n = metrics.iter().map(|m| m.value().name()).collect::<Vec<_>>();
970            n.join(", ")
971        }
972
973        assert_eq!(
974            "end_timestamp, start_timestamp, elapsed_compute, the_second_counter, the_counter, the_third_counter, the_time, output_rows",
975            metric_names(&metrics)
976        );
977
978        let metrics = metrics.sorted_for_display();
979        assert_eq!(
980            "output_rows, elapsed_compute, the_counter, the_second_counter, the_third_counter, the_time, start_timestamp, end_timestamp",
981            metric_names(&metrics)
982        );
983    }
984
985    #[test]
986    fn test_filter_by_names() {
987        let metrics = ExecutionPlanMetricsSet::new();
988        MetricBuilder::new(&metrics).output_rows(0);
989        MetricBuilder::new(&metrics).counter("custom_counter", 0);
990
991        assert!(
992            metrics
993                .clone_inner()
994                .filter_by_names(&[])
995                .iter()
996                .next()
997                .is_none()
998        );
999
1000        let names = vec!["output_rows".to_string()];
1001        let filtered = metrics.clone_inner().filter_by_names(&names);
1002
1003        assert_eq!(filtered.iter().count(), 1);
1004        assert_eq!(
1005            filtered.iter().next().unwrap().value().name(),
1006            "output_rows"
1007        );
1008    }
1009}