iai_callgrind_runner/runner/
metrics.rs

1//! The module containing all elements and logic around the [`Metrics`], [`MetricsDiff`], ...
2
3#![allow(clippy::cast_precision_loss)]
4
5use std::borrow::Cow;
6use std::cmp::Ordering;
7use std::fmt::Display;
8use std::hash::Hash;
9use std::ops::{Add, AddAssign, Div, Mul, Sub};
10use std::str::FromStr;
11
12use anyhow::{anyhow, Context, Result};
13use indexmap::map::Iter;
14use indexmap::{indexmap, IndexMap, IndexSet};
15#[cfg(feature = "schema")]
16use schemars::JsonSchema;
17use serde::{Deserialize, Serialize};
18
19use super::summary::Diffs;
20use crate::api::{self, CachegrindMetric, DhatMetric, ErrorMetric, EventKind};
21use crate::util::{to_string_unsigned_short, EitherOrBoth};
22
23/// The metric measured by valgrind or derived from one or more other metrics
24///
25/// The valgrind metrics measured by any of its tools are `u64`. However, to be able to represent
26/// derived metrics like cache miss/hit rates it is inevitable to have a type which can store a
27/// `u64` or a `f64`. When doing math with metrics, the original type should be preserved as far as
28/// possible by using `u64` operations. A float metric should be a last resort.
29///
30/// Float operations with a `Metric` that stores a `u64` introduce a precision loss and are to be
31/// avoided. Especially comparison between a `u64` metric and `f64` metric are not exact because the
32/// `u64` has to be converted to a `f64`. Also, if adding/multiplying two `u64` metrics would result
33/// in an overflow the metric saturates at `u64::MAX`. This choice was made to preserve precision
34/// and the original type (instead of for example adding the two `u64` by converting both of them to
35/// `f64`).
36#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
37#[cfg_attr(feature = "schema", derive(JsonSchema))]
38pub enum Metric {
39    /// An integer `Metric`
40    Int(u64),
41    /// A float `Metric`
42    Float(f64),
43}
44
45/// The different metrics distinguished by tool and if it is an error checking tool as `ErrorMetric`
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47#[cfg_attr(feature = "schema", derive(JsonSchema))]
48pub enum MetricKind {
49    /// The `None` kind if there are no metrics for a tool
50    None,
51    /// The Callgrind metric kind
52    Callgrind(EventKind),
53    /// The Cachegrind metric kind
54    Cachegrind(CachegrindMetric),
55    /// The DHAT metric kind
56    Dhat(DhatMetric),
57    /// The Memcheck metric kind
58    Memcheck(ErrorMetric),
59    /// The Helgrind metric kind
60    Helgrind(ErrorMetric),
61    /// The DRD metric kind
62    DRD(ErrorMetric),
63}
64
65/// The `Metrics` backed by an [`indexmap::IndexMap`]
66///
67/// The insertion order is preserved.
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[cfg_attr(feature = "schema", derive(JsonSchema))]
70pub struct Metrics<K: Hash + Eq>(pub IndexMap<K, Metric>);
71
72/// The `MetricsDiff` describes the difference between a `new` and `old` metric as percentage and
73/// factor.
74///
75/// Only if both metrics are present there is also a `Diffs` present. Otherwise, it just stores the
76/// `new` or `old` metric.
77#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
78#[cfg_attr(feature = "schema", derive(JsonSchema))]
79pub struct MetricsDiff {
80    /// If both metrics are present there is also a `Diffs` present
81    pub diffs: Option<Diffs>,
82    /// Either the `new`, `old` or both metrics
83    pub metrics: EitherOrBoth<Metric>,
84}
85
86/// The `MetricsSummary` contains all differences between two tool run segments
87#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
88#[cfg_attr(feature = "schema", derive(JsonSchema))]
89pub struct MetricsSummary<K: Hash + Eq = EventKind>(IndexMap<K, MetricsDiff>);
90
91/// Trait for tools which summarize and calculate derived metrics
92pub trait Summarize: Hash + Eq + Clone {
93    /// Calculate the derived metrics if any
94    fn summarize(_: &mut Cow<Metrics<Self>>) {}
95}
96
97/// Trait for checking the [`Metric`] type of a metric kind (like [`api::EventKind`])
98pub trait TypeChecker {
99    /// Return true if the metric kind is a [`Metric::Float`]
100    fn is_float(&self) -> bool;
101    /// Return true if the metric kind is a [`Metric::Int`]
102    fn is_int(&self) -> bool;
103    /// Return true if the `Metric` has the expected metric type
104    fn verify_metric(&self, metric: Metric) -> bool {
105        (self.is_int() && metric.is_int()) || (self.is_float() && metric.is_float())
106    }
107}
108
109impl Metric {
110    /// Divide by `rhs` normally but if rhs is `0` the result is by convention `0.0`
111    ///
112    /// No difference is made between negative 0.0 and positive 0.0 os rhs value. The result is
113    /// always positive 0.0.
114    #[must_use]
115    pub fn div0(self, rhs: Self) -> Self {
116        match (self, rhs) {
117            (_, Self::Int(0) | Self::Float(0.0f64)) => Self::Float(0.0f64),
118            (a, b) => a / b,
119        }
120    }
121
122    /// Return true if this `Metric` is [`Metric::Int`]
123    pub fn is_int(&self) -> bool {
124        match self {
125            Self::Int(_) => true,
126            Self::Float(_) => false,
127        }
128    }
129
130    /// Return true if this `Metric` is [`Metric::Float`]
131    pub fn is_float(&self) -> bool {
132        match self {
133            Self::Int(_) => false,
134            Self::Float(_) => true,
135        }
136    }
137
138    /// If needed and possible convert this metric to the other [`Metric`] returning the result
139    ///
140    /// A metric is converted if the expected type of the `metric_kind` is [`Metric::Float`] but the
141    /// given metric was [`Metric::Int`]. The metrics of float type are usually percentages with a
142    /// value range of `0.0` to `100.0`. Converting `u64` to `f64` within this range happens without
143    /// precision loss.
144    pub fn try_convert<T: Display + TypeChecker>(&self, metric_kind: T) -> Option<(T, Self)> {
145        if metric_kind.verify_metric(*self) {
146            Some((metric_kind, *self))
147        } else if let Self::Int(a) = self {
148            Some((metric_kind, Self::Float(*a as f64)))
149        } else {
150            None
151        }
152    }
153}
154
155impl Add for Metric {
156    type Output = Self;
157
158    fn add(self, rhs: Self) -> Self::Output {
159        match (self, rhs) {
160            (Self::Int(a), Self::Int(b)) => Self::Int(a.saturating_add(b)),
161            (Self::Int(a), Self::Float(b)) => Self::Float((a as f64) + b),
162            (Self::Float(a), Self::Int(b)) => Self::Float((b as f64) + a),
163            (Self::Float(a), Self::Float(b)) => Self::Float(a + b),
164        }
165    }
166}
167
168impl AddAssign for Metric {
169    fn add_assign(&mut self, rhs: Self) {
170        *self = *self + rhs;
171    }
172}
173
174impl Display for Metric {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        match self {
177            Self::Int(a) => f.pad(&format!("{a}")),
178            Self::Float(a) => f.pad(&to_string_unsigned_short(*a)),
179        }
180    }
181}
182
183impl Div for Metric {
184    type Output = Self;
185
186    fn div(self, rhs: Self) -> Self::Output {
187        match (self, rhs) {
188            (Self::Int(a), Self::Int(b)) => Self::Float((a as f64) / (b as f64)),
189            (Self::Int(a), Self::Float(b)) => Self::Float((a as f64) / b),
190            (Self::Float(a), Self::Int(b)) => Self::Float(a / (b as f64)),
191            (Self::Float(a), Self::Float(b)) => Self::Float(a / b),
192        }
193    }
194}
195
196impl Eq for Metric {}
197
198impl From<u64> for Metric {
199    fn from(value: u64) -> Self {
200        Self::Int(value)
201    }
202}
203
204impl From<f64> for Metric {
205    fn from(value: f64) -> Self {
206        Self::Float(value)
207    }
208}
209
210impl From<api::Limit> for Metric {
211    fn from(value: api::Limit) -> Self {
212        match value {
213            api::Limit::Int(a) => Self::Int(a),
214            api::Limit::Float(f) => Self::Float(f),
215        }
216    }
217}
218
219impl FromStr for Metric {
220    type Err = anyhow::Error;
221
222    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
223        match s.parse::<u64>() {
224            Ok(a) => Ok(Self::Int(a)),
225            Err(_) => match s.parse::<f64>() {
226                Ok(a) => Ok(Self::Float(a)),
227                Err(error) => Err(anyhow!("Invalid metric: {error}")),
228            },
229        }
230    }
231}
232
233impl Mul<u64> for Metric {
234    type Output = Self;
235
236    fn mul(self, rhs: u64) -> Self::Output {
237        match self {
238            Self::Int(a) => Self::Int(a.saturating_mul(rhs)),
239            Self::Float(a) => Self::Float(a * (rhs as f64)),
240        }
241    }
242}
243
244impl Ord for Metric {
245    fn cmp(&self, other: &Self) -> Ordering {
246        match (self, other) {
247            (Self::Int(a), Self::Int(b)) => a.cmp(b),
248            (Self::Int(a), Self::Float(b)) => (*a as f64).total_cmp(b),
249            (Self::Float(a), Self::Int(b)) => a.total_cmp(&(*b as f64)),
250            (Self::Float(a), Self::Float(b)) => a.total_cmp(b),
251        }
252    }
253}
254
255impl PartialEq for Metric {
256    fn eq(&self, other: &Self) -> bool {
257        match (self, other) {
258            (Self::Int(a), Self::Int(b)) => a == b,
259            (Self::Int(a), Self::Float(b)) => (*a as f64).total_cmp(b) == Ordering::Equal,
260            (Self::Float(a), Self::Int(b)) => a.total_cmp(&(*b as f64)) == Ordering::Equal,
261            (Self::Float(a), Self::Float(b)) => a.total_cmp(b) == Ordering::Equal,
262        }
263    }
264}
265
266impl PartialOrd for Metric {
267    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
268        Some(self.cmp(other))
269    }
270}
271
272impl Sub for Metric {
273    type Output = Self;
274
275    fn sub(self, rhs: Self) -> Self::Output {
276        match (self, rhs) {
277            (Self::Int(a), Self::Int(b)) => Self::Int(a.saturating_sub(b)),
278            (Self::Int(a), Self::Float(b)) => Self::Float((a as f64) - b),
279            (Self::Float(a), Self::Int(b)) => Self::Float(a - (b as f64)),
280            (Self::Float(a), Self::Float(b)) => Self::Float(a - b),
281        }
282    }
283}
284
285impl Display for MetricKind {
286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287        match self {
288            Self::None => Ok(()),
289            Self::Callgrind(metric) => f.write_fmt(format_args!("Callgrind: {metric}")),
290            Self::Cachegrind(metric) => f.write_fmt(format_args!("Cachegrind: {metric}")),
291            Self::Dhat(metric) => f.write_fmt(format_args!("DHAT: {metric}")),
292            Self::Memcheck(metric) => f.write_fmt(format_args!("Memcheck: {metric}")),
293            Self::Helgrind(metric) => f.write_fmt(format_args!("Helgrind: {metric}")),
294            Self::DRD(metric) => f.write_fmt(format_args!("DRD: {metric}")),
295        }
296    }
297}
298
299impl<K: Hash + Eq + Display + Clone> Metrics<K> {
300    /// Return empty `Metrics`
301    pub fn empty() -> Self {
302        Self(IndexMap::new())
303    }
304
305    /// The order matters. The index is derived from the insertion order
306    pub fn with_metric_kinds<I, T>(kinds: T) -> Self
307    where
308        I: Into<Metric>,
309        T: IntoIterator<Item = (K, I)>,
310    {
311        Self(kinds.into_iter().map(|(k, n)| (k, n.into())).collect())
312    }
313
314    /// Add metrics from an iterator over strings
315    ///
316    /// Adding metrics stops as soon as there are no more keys in this `Metrics` or no more values
317    /// in the iterator. This property is especially important for the metrics from the callgrind
318    /// output files. From the documentation of the callgrind format:
319    ///
320    /// > If a cost line specifies less event counts than given in the "events" line, the
321    /// > rest is assumed to be zero.
322    ///
323    /// # Errors
324    ///
325    /// If one of the strings in the iterator is not parsable as u64 or f64
326    pub fn add_iter_str<I, T>(&mut self, iter: T) -> Result<()>
327    where
328        I: AsRef<str>,
329        T: IntoIterator<Item = I>,
330    {
331        for (this, other) in self.0.values_mut().zip(iter.into_iter()) {
332            *this += other
333                .as_ref()
334                .parse::<Metric>()
335                .context("A metric must be a valid number")?;
336        }
337
338        Ok(())
339    }
340
341    /// Sum this `Metric` with another `Metric`
342    ///
343    /// Do not use this method if both `Metrics` can differ in their keys order.
344    pub fn add(&mut self, other: &Self) {
345        for (this, other) in self.0.values_mut().zip(other.0.values()) {
346            *this += *other;
347        }
348    }
349
350    /// Return the metric of the kind at index (of insertion order) if present
351    ///
352    /// This operation is O(1)
353    pub fn metric_by_index(&self, index: usize) -> Option<Metric> {
354        self.0.get_index(index).map(|(_, c)| *c)
355    }
356
357    /// Return the metric of the `kind` if present
358    ///
359    /// This operation is O(1)
360    pub fn metric_by_kind(&self, kind: &K) -> Option<Metric> {
361        self.0.get_key_value(kind).map(|(_, c)| *c)
362    }
363
364    /// Return the metric kind or an error
365    ///
366    /// # Errors
367    ///
368    /// If the metric kind is not present
369    pub fn try_metric_by_kind(&self, kind: &K) -> Result<Metric> {
370        self.metric_by_kind(kind)
371            .with_context(|| format!("Missing event type '{kind}"))
372    }
373
374    /// Return the contained metric kinds
375    pub fn metric_kinds(&self) -> Vec<K> {
376        self.0.iter().map(|(k, _)| k.clone()).collect()
377    }
378
379    /// Create the union set of the keys of this and another `Metrics`
380    ///
381    /// The order of the keys is preserved. New keys from the `other` Metrics are appended in their
382    /// original order.
383    pub fn metric_kinds_union<'a>(&'a self, other: &'a Self) -> IndexSet<&'a K> {
384        let set = self.0.keys().collect::<IndexSet<_>>();
385        let other_set = other.0.keys().collect::<IndexSet<_>>();
386        set.union(&other_set).copied().collect()
387    }
388
389    /// Return an iterator over the metrics in insertion order
390    pub fn iter(&self) -> Iter<'_, K, Metric> {
391        self.0.iter()
392    }
393
394    /// Return true if there are no metrics present
395    pub fn is_empty(&self) -> bool {
396        self.0.is_empty()
397    }
398
399    /// Insert a single metric
400    ///
401    /// If an equivalent key already exists in the map: the key remains and retains in its place in
402    /// the order, its corresponding value is updated with `value`, and the older value is returned
403    /// inside `Some(_)`.
404    ///
405    /// If no equivalent key existed in the map: the new key-value pair is inserted, last in order,
406    /// and `None` is returned.
407    pub fn insert(&mut self, key: K, value: Metric) -> Option<Metric> {
408        self.0.insert(key, value)
409    }
410
411    /// Insert all metrics
412    ///
413    /// See also [`Metrics::insert`]
414    pub fn insert_all(&mut self, entries: &[(K, Metric)]) {
415        for (key, value) in entries {
416            self.insert(key.clone(), *value);
417        }
418    }
419}
420
421impl<'a, K: Hash + Eq + Display + Clone> IntoIterator for &'a Metrics<K> {
422    type Item = (&'a K, &'a Metric);
423
424    type IntoIter = Iter<'a, K, Metric>;
425
426    fn into_iter(self) -> Self::IntoIter {
427        self.iter()
428    }
429}
430
431impl<I, K: Hash + Eq + From<I>> FromIterator<I> for Metrics<K> {
432    fn from_iter<T>(iter: T) -> Self
433    where
434        T: IntoIterator<Item = I>,
435    {
436        Self(
437            iter.into_iter()
438                .map(|s| (K::from(s), Metric::Int(0)))
439                .collect::<IndexMap<_, _>>(),
440        )
441    }
442}
443
444impl MetricsDiff {
445    /// Create a new `MetricsDiff` from a [`Metric`]
446    pub fn new(metrics: EitherOrBoth<Metric>) -> Self {
447        if let EitherOrBoth::Both(new, old) = metrics {
448            Self {
449                metrics,
450                diffs: Some(Diffs::new(new, old)),
451            }
452        } else {
453            Self {
454                metrics,
455                diffs: None,
456            }
457        }
458    }
459
460    /// Sum this metrics diff with another [`MetricsDiff`]
461    #[must_use]
462    pub fn add(&self, other: &Self) -> Self {
463        match (&self.metrics, &other.metrics) {
464            (EitherOrBoth::Left(new), EitherOrBoth::Left(other_new)) => {
465                Self::new(EitherOrBoth::Left(*new + *other_new))
466            }
467            (EitherOrBoth::Right(old), EitherOrBoth::Left(new))
468            | (EitherOrBoth::Left(new), EitherOrBoth::Right(old)) => {
469                Self::new(EitherOrBoth::Both(*new, *old))
470            }
471            (EitherOrBoth::Right(old), EitherOrBoth::Right(other_old)) => {
472                Self::new(EitherOrBoth::Right(*old + *other_old))
473            }
474            (EitherOrBoth::Both(new, old), EitherOrBoth::Left(other_new))
475            | (EitherOrBoth::Left(new), EitherOrBoth::Both(other_new, old)) => {
476                Self::new(EitherOrBoth::Both(*new + *other_new, *old))
477            }
478            (EitherOrBoth::Both(new, old), EitherOrBoth::Right(other_old))
479            | (EitherOrBoth::Right(old), EitherOrBoth::Both(new, other_old)) => {
480                Self::new(EitherOrBoth::Both(*new, *old + *other_old))
481            }
482            (EitherOrBoth::Both(new, old), EitherOrBoth::Both(other_new, other_old)) => {
483                Self::new(EitherOrBoth::Both(*new + *other_new, *old + *other_old))
484            }
485        }
486    }
487}
488
489impl<K> MetricsSummary<K>
490where
491    K: Hash + Eq + Summarize + Display + Clone,
492{
493    /// Create a new `MetricsSummary` calculating the differences between new and old (if any)
494    /// [`Metrics`]
495    ///
496    /// # Panics
497    ///
498    /// If one of the [`Metrics`] is empty
499    pub fn new(metrics: EitherOrBoth<Metrics<K>>) -> Self {
500        match metrics {
501            EitherOrBoth::Left(new) => {
502                assert!(!new.is_empty());
503
504                let mut new = Cow::Owned(new);
505                K::summarize(&mut new);
506
507                Self(
508                    new.iter()
509                        .map(|(metric_kind, metric)| {
510                            (
511                                metric_kind.clone(),
512                                MetricsDiff::new(EitherOrBoth::Left(*metric)),
513                            )
514                        })
515                        .collect::<IndexMap<_, _>>(),
516                )
517            }
518            EitherOrBoth::Right(old) => {
519                assert!(!old.is_empty());
520
521                let mut old = Cow::Owned(old);
522                K::summarize(&mut old);
523
524                Self(
525                    old.iter()
526                        .map(|(metric_kind, metric)| {
527                            (
528                                metric_kind.clone(),
529                                MetricsDiff::new(EitherOrBoth::Right(*metric)),
530                            )
531                        })
532                        .collect::<IndexMap<_, _>>(),
533                )
534            }
535            EitherOrBoth::Both(new, old) => {
536                assert!(!new.is_empty());
537                assert!(!old.is_empty());
538
539                let mut new = Cow::Owned(new);
540                K::summarize(&mut new);
541                let mut old = Cow::Owned(old);
542                K::summarize(&mut old);
543
544                let mut map = indexmap! {};
545                for metric_kind in new.metric_kinds_union(&old) {
546                    let diff = match (
547                        new.metric_by_kind(metric_kind),
548                        old.metric_by_kind(metric_kind),
549                    ) {
550                        (Some(metric), None) => MetricsDiff::new(EitherOrBoth::Left(metric)),
551                        (None, Some(metric)) => MetricsDiff::new(EitherOrBoth::Right(metric)),
552                        (Some(new), Some(old)) => MetricsDiff::new(EitherOrBoth::Both(new, old)),
553                        (None, None) => {
554                            unreachable!(
555                                "The union contains the event kinds either from new or old or \
556                                 from both"
557                            )
558                        }
559                    };
560                    map.insert(metric_kind.clone(), diff);
561                }
562                Self(map)
563            }
564        }
565    }
566
567    /// Try to return a [`MetricsDiff`] for the specified `MetricKind`
568    pub fn diff_by_kind(&self, metric_kind: &K) -> Option<&MetricsDiff> {
569        self.0.get(metric_kind)
570    }
571
572    /// Return an iterator over all [`MetricsDiff`]s
573    pub fn all_diffs(&self) -> impl Iterator<Item = (&K, &MetricsDiff)> {
574        self.0.iter()
575    }
576
577    /// Extract the [`Metrics`] from this summary
578    ///
579    /// This is the exact reverse operation to [`MetricsSummary::new`]
580    pub fn extract_costs(&self) -> EitherOrBoth<Metrics<K>> {
581        let mut new_metrics: Metrics<K> = Metrics::empty();
582        let mut old_metrics: Metrics<K> = Metrics::empty();
583
584        // The diffs should not be empty
585        for (metric_kind, diff) in self.all_diffs() {
586            match diff.metrics {
587                EitherOrBoth::Left(new) => {
588                    new_metrics.insert(metric_kind.clone(), new);
589                }
590                EitherOrBoth::Right(old) => {
591                    old_metrics.insert(metric_kind.clone(), old);
592                }
593                EitherOrBoth::Both(new, old) => {
594                    new_metrics.insert(metric_kind.clone(), new);
595                    old_metrics.insert(metric_kind.clone(), old);
596                }
597            }
598        }
599
600        match (new_metrics.is_empty(), old_metrics.is_empty()) {
601            (false, false) => EitherOrBoth::Both(new_metrics, old_metrics),
602            (false, true) => EitherOrBoth::Left(new_metrics),
603            (true, false) => EitherOrBoth::Right(old_metrics),
604            (true, true) => unreachable!("A costs diff contains new or old values or both."),
605        }
606    }
607
608    /// Sum up another `MetricsSummary` with this one
609    ///
610    /// If a [`MetricsDiff`] is not present in this summary but in the other, it is added to this
611    /// summary.
612    pub fn add(&mut self, other: &Self) {
613        let other_keys = other.0.keys().cloned().collect::<IndexSet<_>>();
614        let keys = self.0.keys().cloned().collect::<IndexSet<_>>();
615        let union = keys.union(&other_keys);
616
617        for key in union {
618            match (self.diff_by_kind(key), other.diff_by_kind(key)) {
619                (None, None) => unreachable!("One key of the union set must be present"),
620                (None, Some(other_diff)) => {
621                    self.0.insert(key.clone(), other_diff.clone());
622                }
623                (Some(_), None) => {
624                    // Nothing to be done
625                }
626                (Some(this_diff), Some(other_diff)) => {
627                    let new_diff = this_diff.add(other_diff);
628                    self.0.insert(key.clone(), new_diff);
629                }
630            }
631        }
632    }
633}
634
635impl<K> Default for MetricsSummary<K>
636where
637    K: Hash + Eq,
638{
639    fn default() -> Self {
640        Self(IndexMap::default())
641    }
642}
643
644impl From<Metric> for f64 {
645    fn from(value: Metric) -> Self {
646        match value {
647            Metric::Int(a) => a as Self,
648            Metric::Float(a) => a,
649        }
650    }
651}
652
653impl Mul<Metric> for u64 {
654    type Output = Metric;
655
656    fn mul(self, rhs: Metric) -> Self::Output {
657        match rhs {
658            Metric::Int(b) => Metric::Int(self.saturating_mul(b)),
659            Metric::Float(b) => Metric::Float((self as f64) * b),
660        }
661    }
662}
663
664#[cfg(test)]
665mod tests {
666    use std::cmp::Ordering;
667    use std::{f64, iter};
668
669    use pretty_assertions::assert_eq;
670    use rstest::rstest;
671
672    use super::*;
673    use crate::api::EventKind::{self, *};
674    use crate::runner::summary::Diffs;
675    use crate::util::EitherOrBoth;
676
677    fn expected_metrics<I, T>(events: T) -> Metrics<EventKind>
678    where
679        I: Into<Metric>,
680        T: IntoIterator<Item = (EventKind, I)>,
681    {
682        Metrics(
683            events
684                .into_iter()
685                .map(|(k, n)| (k, n.into()))
686                .collect::<IndexMap<_, _>>(),
687        )
688    }
689
690    fn expected_metrics_diff<D>(metrics: EitherOrBoth<Metric>, diffs: D) -> MetricsDiff
691    where
692        D: Into<Option<(f64, f64)>>,
693    {
694        MetricsDiff {
695            metrics,
696            diffs: diffs
697                .into()
698                .map(|(diff_pct, factor)| Diffs { diff_pct, factor }),
699        }
700    }
701
702    fn metrics_fixture(metrics: &[u64]) -> Metrics<EventKind> {
703        // events: Ir Dr Dw I1mr D1mr D1mw ILmr DLmr DLmw
704        let event_kinds = [
705            Ir,
706            Dr,
707            Dw,
708            I1mr,
709            D1mr,
710            D1mw,
711            ILmr,
712            DLmr,
713            DLmw,
714            L1hits,
715            LLhits,
716            RamHits,
717            TotalRW,
718            EstimatedCycles,
719            I1MissRate,
720            D1MissRate,
721            LLiMissRate,
722            LLdMissRate,
723            LLMissRate,
724            L1HitRate,
725            LLHitRate,
726            RamHitRate,
727        ];
728
729        Metrics::with_metric_kinds(
730            event_kinds
731                .iter()
732                .zip(metrics.iter())
733                .map(|(e, v)| (*e, *v)),
734        )
735    }
736
737    fn metrics_summary_fixture<T, U>(kinds: U) -> MetricsSummary<EventKind>
738    where
739        T: Into<Option<(f64, f64)>> + Clone,
740        U: IntoIterator<Item = (EitherOrBoth<Metric>, T)>,
741    {
742        // events: Ir Dr Dw I1mr D1mr D1mw ILmr DLmr DLmw
743        let event_kinds = [
744            Ir,
745            Dr,
746            Dw,
747            I1mr,
748            D1mr,
749            D1mw,
750            ILmr,
751            DLmr,
752            DLmw,
753            L1hits,
754            LLhits,
755            RamHits,
756            TotalRW,
757            EstimatedCycles,
758            I1MissRate,
759            D1MissRate,
760            LLiMissRate,
761            LLdMissRate,
762            LLMissRate,
763            L1HitRate,
764            LLHitRate,
765            RamHitRate,
766        ];
767
768        let map: IndexMap<EventKind, MetricsDiff> = event_kinds
769            .iter()
770            .zip(kinds)
771            .map(|(e, (m, d))| (*e, expected_metrics_diff(m, d)))
772            .collect();
773
774        MetricsSummary(map)
775    }
776
777    #[rstest]
778    #[case::single_zero(&[Ir], &["0"], expected_metrics([(Ir, 0)]))]
779    #[case::single_one(&[Ir], &["1"], expected_metrics([(Ir, 1)]))]
780    #[case::single_float(&[Ir], &["1.0"], expected_metrics([(Ir, 1.0f64)]))]
781    #[case::single_u64_max(&[Ir], &[u64::MAX.to_string()], expected_metrics([(Ir, u64::MAX)]))]
782    #[case::one_more_than_max_u64(&[Ir], &["18446744073709551616"],
783        // This float has the correct value to represent the value above
784        expected_metrics([(Ir, 18_446_744_073_709_552_000_f64)])
785    )]
786    #[case::more_values_than_kinds(&[Ir], &["1", "2"], expected_metrics([(Ir, 1)]))]
787    #[case::more_kinds_than_values(&[Ir, I1mr], &["1"], expected_metrics([(Ir, 1), (I1mr, 0)]))]
788    fn test_metrics_add_iter_str<I>(
789        #[case] event_kinds: &[EventKind],
790        #[case] to_add: &[I],
791        #[case] expected_metrics: Metrics<EventKind>,
792    ) where
793        I: AsRef<str>,
794    {
795        let mut metrics =
796            Metrics::with_metric_kinds(event_kinds.iter().copied().zip(iter::repeat(0)));
797        metrics.add_iter_str(to_add).unwrap();
798
799        assert_eq!(metrics, expected_metrics);
800    }
801
802    #[rstest]
803    #[case::word(&[Ir], &["abc"])]
804    #[case::empty(&[Ir], &[""])]
805    fn test_metrics_add_iter_str_when_error<I>(
806        #[case] event_kinds: &[EventKind],
807        #[case] to_add: &[I],
808    ) where
809        I: AsRef<str>,
810    {
811        let mut metrics =
812            Metrics::with_metric_kinds(event_kinds.iter().copied().zip(iter::repeat(0)));
813        assert!(metrics.add_iter_str(to_add).is_err());
814    }
815
816    #[rstest]
817    #[case::all_zero_int(0, 0, 0.0f64)]
818    #[case::lhs_zero_int_one(0, 1, 0.0f64)]
819    #[case::lhs_zero_int_two(0, 2, 0.0f64)]
820    #[case::one_rhs_zero_int(1, 0, 0.0f64)]
821    #[case::two_rhs_zero_int(2, 0, 0.0f64)]
822    #[case::all_zero_float(0.0f64, 0.0f64, 0.0f64)]
823    #[case::lhs_zero_float_one(0.0f64, 1.0f64, 0.0f64)]
824    #[case::lhs_zero_float_two(0.0f64, 2.0f64, 0.0f64)]
825    #[case::lhs_zero_float_neg_two(0.0f64, -2.0f64, -0.0f64)]
826    #[case::one_rhs_zero_float(1.0f64, 0.0f64, 0.0f64)]
827    #[case::two_rhs_zero_float(2.0f64, 0.0f64, 0.0f64)]
828    #[case::one_neg_rhs_zero_float(1.0f64, -0.0f64, 0.0f64)]
829    #[case::one_one_int(1, 1, 1.0f64)]
830    #[case::two_one_int(2, 1, 2.0f64)]
831    #[case::one_two_int(1, 2, 0.5f64)]
832    #[case::one_float_one(1, 1.0f64, 1.0f64)]
833    #[case::float_one_int_one(1.0f64, 1, 1.0f64)]
834    #[case::float_one(1.0f64, 1.0f64, 1.0f64)]
835    #[case::one_float_two(1, 2.0f64, 0.5f64)]
836    #[case::float_one_int_two(1.0f64, 2, 0.5f64)]
837    #[case::float_one_two(1.0f64, 2.0f64, 0.5f64)]
838    fn test_metric_safe_div<L, R, E>(#[case] lhs: L, #[case] rhs: R, #[case] expected: E)
839    where
840        L: Into<Metric>,
841        R: Into<Metric>,
842        E: Into<Metric>,
843    {
844        let expected = expected.into();
845
846        let lhs = lhs.into();
847        let rhs = rhs.into();
848
849        assert_eq!(lhs.div0(rhs), expected);
850    }
851
852    #[rstest]
853    #[case::zero(0, 0, 0)]
854    #[case::one_zero(1, 0, 1)]
855    #[case::zero_one(0, 1, 1)]
856    #[case::u64_max(0, u64::MAX, u64::MAX)]
857    #[case::one_u64_max_saturates(1, u64::MAX, u64::MAX)]
858    #[case::one(1, 1, 2)]
859    #[case::two_one(2, 1, 3)]
860    #[case::one_two(1, 2, 3)]
861    #[case::float_one_int_zero(1.0f64, 0, 1.0f64)]
862    #[case::int_zero_float_one(0, 1.0f64, 1.0f64)]
863    #[case::float_zero(0.0f64, 0.0f64, 0.0f64)]
864    #[case::float_one(1.0f64, 1.0f64, 2.0f64)]
865    #[case::float_one_two(1.0f64, 2.0f64, 3.0f64)]
866    #[case::float_two_one(2.0f64, 1.0f64, 3.0f64)]
867    fn test_metric_add_and_add_assign<L, R, E>(#[case] lhs: L, #[case] rhs: R, #[case] expected: E)
868    where
869        L: Into<Metric>,
870        R: Into<Metric>,
871        E: Into<Metric>,
872    {
873        let expected = expected.into();
874
875        let mut lhs = lhs.into();
876        let rhs = rhs.into();
877
878        assert_eq!(lhs + rhs, expected);
879
880        lhs += rhs;
881        assert_eq!(lhs, expected);
882    }
883
884    #[rstest]
885    #[case::zero("0", 0)]
886    #[case::one("1", 1)]
887    #[case::u64_max(&format!("{}", u64::MAX), u64::MAX)]
888    #[case::one_below_u64_max(&format!("{}", u64::MAX - 1), u64::MAX - 1)]
889    #[case::zero_float("0.0", 0.0f64)]
890    #[case::one_float("1.0", 1.0f64)]
891    #[case::one_point("1.", 1.0f64)]
892    #[case::point_one(".1", 0.1f64)]
893    #[case::two_float("2.0", 2.0f64)]
894    #[case::neg_one_float("-1.0", -1.0f64)]
895    #[case::neg_two_float("-2.0", -2.0f64)]
896    #[case::inf("inf", f64::INFINITY)]
897    fn test_metric_from_str<E>(#[case] input: &str, #[case] expected: E)
898    where
899        E: Into<Metric>,
900    {
901        let expected = expected.into();
902        assert_eq!(input.parse::<Metric>().unwrap(), expected);
903    }
904
905    #[test]
906    fn test_metric_from_str_when_invalid_then_error() {
907        let err = "abc".parse::<Metric>().unwrap_err();
908        assert_eq!(
909            "Invalid metric: invalid float literal".to_owned(),
910            err.to_string()
911        );
912    }
913
914    #[rstest]
915    #[case::zero(0, 0, 0)]
916    #[case::zero_one(0, 1, 0)]
917    #[case::one(1, 1, 1)]
918    #[case::one_two(1, 2, 2)]
919    #[case::u64_max_one(u64::MAX, 1, u64::MAX)]
920    #[case::u64_max_two_saturates(u64::MAX, 2, u64::MAX)]
921    #[case::zero_float(0, 0.0f64, 0.0f64)]
922    #[case::zero_one_float(0, 1.0f64, 0.0f64)]
923    #[case::one_float(1, 1.0f64, 1.0f64)]
924    #[case::one_two_float(1, 2.0f64, 2.0f64)]
925    #[case::u64_max_two_float(u64::MAX, 2.0f64, 2.0f64 * (u64::MAX as f64))]
926    fn test_metric_mul_u64<B, E>(#[case] a: u64, #[case] b: B, #[case] expected: E)
927    where
928        B: Into<Metric>,
929        E: Into<Metric>,
930    {
931        let expected = expected.into();
932        let b = b.into();
933
934        assert_eq!(a * b, expected);
935        assert_eq!(b * a, expected);
936    }
937
938    #[rstest]
939    #[case::zero(0, 0, 0)]
940    #[case::one_zero(1, 0, 1)]
941    #[case::zero_one_saturates(0, 1, 0)]
942    #[case::u64_max_saturates(0, u64::MAX, 0)]
943    #[case::one_u64_max_saturates(1, u64::MAX, 0)]
944    #[case::u64_max_one(u64::MAX, 1, u64::MAX - 1)]
945    #[case::one(1, 1, 0)]
946    #[case::two_one(2, 1, 1)]
947    #[case::one_two(1, 2, 0)]
948    #[case::float_one_int_zero(1.0f64, 0, 1.0f64)]
949    #[case::int_zero_float_one(0, 1.0f64, -1.0f64)]
950    #[case::float_zero(0.0f64, 0.0f64, 0.0f64)]
951    #[case::float_one(1.0f64, 1.0f64, 0.0f64)]
952    #[case::float_one_two(1.0f64, 2.0f64, -1.0f64)]
953    #[case::float_two_one(2.0f64, 1.0f64, 1.0f64)]
954    fn test_metric_sub<L, R, E>(#[case] lhs: L, #[case] rhs: R, #[case] expected: E)
955    where
956        L: Into<Metric>,
957        R: Into<Metric>,
958        E: Into<Metric>,
959    {
960        let expected = expected.into();
961
962        let lhs = lhs.into();
963        let rhs = rhs.into();
964
965        assert_eq!(lhs - rhs, expected);
966    }
967
968    #[rstest]
969    #[case::zero(0, 0, Ordering::Equal)]
970    #[case::one_zero(1, 0, Ordering::Greater)]
971    #[case::zero_float(0.0f64, 0.0f64, Ordering::Equal)]
972    #[case::one_zero_float(1.0f64, 0.0f64, Ordering::Greater)]
973    #[case::one_int_zero_float(1, 0.0f64, Ordering::Greater)]
974    #[case::one_float_zero_int(1.0f64, 0, Ordering::Greater)]
975    #[case::some_number(220, 220.0f64, Ordering::Equal)]
976    fn test_metric_ordering<L, R>(#[case] lhs: L, #[case] rhs: R, #[case] expected: Ordering)
977    where
978        L: Into<Metric>,
979        R: Into<Metric>,
980    {
981        let lhs: Metric = lhs.into();
982        let rhs = rhs.into();
983
984        assert_eq!(lhs.cmp(&rhs), expected);
985        assert_eq!(rhs.cmp(&lhs), expected.reverse());
986    }
987
988    #[rstest]
989    #[case::new_zero(EitherOrBoth::Left(0), None)]
990    #[case::new_one(EitherOrBoth::Left(1), None)]
991    #[case::new_u64_max(EitherOrBoth::Left(u64::MAX), None)]
992    #[case::old_zero(EitherOrBoth::Right(0), None)]
993    #[case::old_one(EitherOrBoth::Right(1), None)]
994    #[case::old_u64_max(EitherOrBoth::Right(u64::MAX), None)]
995    #[case::both_zero(
996        EitherOrBoth::Both(0, 0),
997        (0f64, 1f64)
998    )]
999    #[case::both_one(
1000        EitherOrBoth::Both(1, 1),
1001        (0f64, 1f64)
1002    )]
1003    #[case::both_u64_max(
1004        EitherOrBoth::Both(u64::MAX, u64::MAX),
1005        (0f64, 1f64)
1006    )]
1007    #[case::new_one_old_zero(
1008        EitherOrBoth::Both(1, 0),
1009        (f64::INFINITY, f64::INFINITY)
1010    )]
1011    #[case::new_one_old_two(
1012        EitherOrBoth::Both(1, 2),
1013        (-50f64, -2f64)
1014    )]
1015    #[case::new_zero_old_one(
1016        EitherOrBoth::Both(0, 1),
1017        (-100f64, f64::NEG_INFINITY)
1018    )]
1019    #[case::new_two_old_one(
1020        EitherOrBoth::Both(2, 1),
1021        (100f64, 2f64)
1022    )]
1023    fn test_metrics_diff_new<T>(#[case] metrics: EitherOrBoth<u64>, #[case] expected_diffs: T)
1024    where
1025        T: Into<Option<(f64, f64)>>,
1026    {
1027        let expected = expected_metrics_diff(metrics.clone().map(Metric::Int), expected_diffs);
1028        let actual = MetricsDiff::new(metrics.map(Metric::Int));
1029
1030        assert_eq!(actual, expected);
1031    }
1032
1033    #[rstest]
1034    #[case::new_new(EitherOrBoth::Left(1), EitherOrBoth::Left(2), EitherOrBoth::Left(3))]
1035    #[case::new_old(
1036        EitherOrBoth::Left(1),
1037        EitherOrBoth::Right(2),
1038        EitherOrBoth::Both(1, 2)
1039    )]
1040    #[case::new_both(
1041        EitherOrBoth::Left(1),
1042        EitherOrBoth::Both(2, 5),
1043        EitherOrBoth::Both(3, 5)
1044    )]
1045    #[case::old_old(EitherOrBoth::Right(1), EitherOrBoth::Right(2), EitherOrBoth::Right(3))]
1046    #[case::old_new(
1047        EitherOrBoth::Right(1),
1048        EitherOrBoth::Left(2),
1049        EitherOrBoth::Both(2, 1)
1050    )]
1051    #[case::old_both(
1052        EitherOrBoth::Right(1),
1053        EitherOrBoth::Both(2, 5),
1054        EitherOrBoth::Both(2, 6)
1055    )]
1056    #[case::both_new(
1057        EitherOrBoth::Both(2, 5),
1058        EitherOrBoth::Left(1),
1059        EitherOrBoth::Both(3, 5)
1060    )]
1061    #[case::both_old(
1062        EitherOrBoth::Both(2, 5),
1063        EitherOrBoth::Right(1),
1064        EitherOrBoth::Both(2, 6)
1065    )]
1066    #[case::both_both(
1067        EitherOrBoth::Both(2, 5),
1068        EitherOrBoth::Both(1, 3),
1069        EitherOrBoth::Both(3, 8)
1070    )]
1071    #[case::saturating_new(
1072        EitherOrBoth::Left(u64::MAX),
1073        EitherOrBoth::Left(1),
1074        EitherOrBoth::Left(u64::MAX)
1075    )]
1076    #[case::saturating_new_other(
1077        EitherOrBoth::Left(1),
1078        EitherOrBoth::Left(u64::MAX),
1079        EitherOrBoth::Left(u64::MAX)
1080    )]
1081    #[case::saturating_old(
1082        EitherOrBoth::Right(u64::MAX),
1083        EitherOrBoth::Right(1),
1084        EitherOrBoth::Right(u64::MAX)
1085    )]
1086    #[case::saturating_old_other(
1087        EitherOrBoth::Right(1),
1088        EitherOrBoth::Right(u64::MAX),
1089        EitherOrBoth::Right(u64::MAX)
1090    )]
1091    #[case::saturating_both(
1092        EitherOrBoth::Both(u64::MAX, u64::MAX),
1093        EitherOrBoth::Both(1, 1),
1094        EitherOrBoth::Both(u64::MAX, u64::MAX)
1095    )]
1096    #[case::saturating_both_other(
1097        EitherOrBoth::Both(1, 1),
1098        EitherOrBoth::Both(u64::MAX, u64::MAX),
1099        EitherOrBoth::Both(u64::MAX, u64::MAX)
1100    )]
1101    fn test_metrics_diff_add(
1102        #[case] metric: EitherOrBoth<u64>,
1103        #[case] other_metric: EitherOrBoth<u64>,
1104        #[case] expected: EitherOrBoth<u64>,
1105    ) {
1106        let new_diff = MetricsDiff::new(metric.map(Metric::Int));
1107        let old_diff = MetricsDiff::new(other_metric.map(Metric::Int));
1108        let expected = MetricsDiff::new(expected.map(Metric::Int));
1109
1110        assert_eq!(new_diff.add(&old_diff), expected);
1111        assert_eq!(old_diff.add(&new_diff), expected);
1112    }
1113
1114    #[rstest]
1115    #[case::new_ir(&[0], &[], &[(EitherOrBoth::Left(Metric::Int(0)), None)])]
1116    #[case::new_is_summarized(&[10, 20, 30, 1, 2, 3, 4, 2, 0], &[],
1117        &[
1118            (EitherOrBoth::Left(Metric::Int(10)), None),
1119            (EitherOrBoth::Left(Metric::Int(20)), None),
1120            (EitherOrBoth::Left(Metric::Int(30)), None),
1121            (EitherOrBoth::Left(Metric::Int(1)), None),
1122            (EitherOrBoth::Left(Metric::Int(2)), None),
1123            (EitherOrBoth::Left(Metric::Int(3)), None),
1124            (EitherOrBoth::Left(Metric::Int(4)), None),
1125            (EitherOrBoth::Left(Metric::Int(2)), None),
1126            (EitherOrBoth::Left(Metric::Int(0)), None),
1127            (EitherOrBoth::Left(Metric::Int(54)), None),
1128            (EitherOrBoth::Left(Metric::Int(0)), None),
1129            (EitherOrBoth::Left(Metric::Int(6)), None),
1130            (EitherOrBoth::Left(Metric::Int(60)), None),
1131            (EitherOrBoth::Left(Metric::Int(264)), None),
1132            (EitherOrBoth::Left(Metric::Float(10f64)), None),
1133            (EitherOrBoth::Left(Metric::Float(10f64)), None),
1134            (EitherOrBoth::Left(Metric::Float(40f64)), None),
1135            (EitherOrBoth::Left(Metric::Float(4f64)), None),
1136            (EitherOrBoth::Left(Metric::Float(10f64)), None),
1137            (EitherOrBoth::Left(Metric::Float(90f64)), None),
1138            (EitherOrBoth::Left(Metric::Float(0f64)), None),
1139            (EitherOrBoth::Left(Metric::Float(10f64)), None),
1140        ]
1141    )]
1142    #[case::old_ir(&[], &[0], &[(EitherOrBoth::Right(Metric::Int(0)), None)])]
1143    #[case::old_is_summarized(&[], &[5, 10, 15, 1, 2, 3, 4, 1, 0],
1144        &[
1145            (EitherOrBoth::Right(Metric::Int(5)), None),
1146            (EitherOrBoth::Right(Metric::Int(10)), None),
1147            (EitherOrBoth::Right(Metric::Int(15)), None),
1148            (EitherOrBoth::Right(Metric::Int(1)), None),
1149            (EitherOrBoth::Right(Metric::Int(2)), None),
1150            (EitherOrBoth::Right(Metric::Int(3)), None),
1151            (EitherOrBoth::Right(Metric::Int(4)), None),
1152            (EitherOrBoth::Right(Metric::Int(1)), None),
1153            (EitherOrBoth::Right(Metric::Int(0)), None),
1154            (EitherOrBoth::Right(Metric::Int(24)), None),
1155            (EitherOrBoth::Right(Metric::Int(1)), None),
1156            (EitherOrBoth::Right(Metric::Int(5)), None),
1157            (EitherOrBoth::Right(Metric::Int(30)), None),
1158            (EitherOrBoth::Right(Metric::Int(204)), None),
1159            (EitherOrBoth::Right(Metric::Float(20f64)), None),
1160            (EitherOrBoth::Right(Metric::Float(20f64)), None),
1161            (EitherOrBoth::Right(Metric::Float(80f64)), None),
1162            (EitherOrBoth::Right(Metric::Float(4f64)), None),
1163            (EitherOrBoth::Right(Metric::Float(16.666_666_666_666_664_f64)), None),
1164            (EitherOrBoth::Right(Metric::Float(80f64)), None),
1165            (EitherOrBoth::Right(Metric::Float(3.333_333_333_333_333_5_f64)), None),
1166            (EitherOrBoth::Right(Metric::Float(16.666_666_666_666_664_f64)), None),
1167        ]
1168    )]
1169    #[case::new_and_old_ir_zero(&[0], &[0], &[
1170        (EitherOrBoth::Both(Metric::Int(0), Metric::Int(0)), (0f64, 1f64))
1171    ])]
1172    #[case::new_and_old_summarized_when_equal(
1173        &[10, 20, 30, 1, 2, 3, 4, 2, 0],
1174        &[10, 20, 30, 1, 2, 3, 4, 2, 0],
1175        &[
1176            (EitherOrBoth::Both(Metric::Int(10), Metric::Int(10)), (0f64, 1f64)),
1177            (EitherOrBoth::Both(Metric::Int(20), Metric::Int(20)), (0f64, 1f64)),
1178            (EitherOrBoth::Both(Metric::Int(30), Metric::Int(30)), (0f64, 1f64)),
1179            (EitherOrBoth::Both(Metric::Int(1), Metric::Int(1)), (0f64, 1f64)),
1180            (EitherOrBoth::Both(Metric::Int(2), Metric::Int(2)), (0f64, 1f64)),
1181            (EitherOrBoth::Both(Metric::Int(3), Metric::Int(3)), (0f64, 1f64)),
1182            (EitherOrBoth::Both(Metric::Int(4), Metric::Int(4)), (0f64, 1f64)),
1183            (EitherOrBoth::Both(Metric::Int(2), Metric::Int(2)), (0f64, 1f64)),
1184            (EitherOrBoth::Both(Metric::Int(0), Metric::Int(0)), (0f64, 1f64)),
1185            (EitherOrBoth::Both(Metric::Int(54), Metric::Int(54)), (0f64, 1f64)),
1186            (EitherOrBoth::Both(Metric::Int(0), Metric::Int(0)), (0f64, 1f64)),
1187            (EitherOrBoth::Both(Metric::Int(6), Metric::Int(6)), (0f64, 1f64)),
1188            (EitherOrBoth::Both(Metric::Int(60), Metric::Int(60)), (0f64, 1f64)),
1189            (EitherOrBoth::Both(Metric::Int(264), Metric::Int(264)), (0f64, 1f64)),
1190            (EitherOrBoth::Both(Metric::Float(10f64), Metric::Float(10f64)), (0f64, 1f64)),
1191            (EitherOrBoth::Both(Metric::Float(10f64), Metric::Float(10f64)), (0f64, 1f64)),
1192            (EitherOrBoth::Both(Metric::Float(40f64), Metric::Float(40f64)), (0f64, 1f64)),
1193            (EitherOrBoth::Both(Metric::Float(4f64), Metric::Float(4f64)), (0f64, 1f64)),
1194            (EitherOrBoth::Both(Metric::Float(10f64), Metric::Float(10f64)), (0f64, 1f64)),
1195            (EitherOrBoth::Both(Metric::Float(90f64), Metric::Float(90f64)), (0f64, 1f64)),
1196            (EitherOrBoth::Both(Metric::Float(0f64), Metric::Float(0f64)), (0f64, 1f64)),
1197            (EitherOrBoth::Both(Metric::Float(10f64), Metric::Float(10f64)), (0f64, 1f64)),
1198        ]
1199    )]
1200    #[case::new_and_old_summarized_when_not_equal(
1201        &[10, 20, 30, 1, 2, 3, 4, 2, 0],
1202        &[5, 10, 15, 1, 2, 3, 4, 1, 0],
1203        &[
1204            (EitherOrBoth::Both(Metric::Int(10), Metric::Int(5)), (100f64, 2f64)),
1205            (EitherOrBoth::Both(Metric::Int(20), Metric::Int(10)), (100f64, 2f64)),
1206            (EitherOrBoth::Both(Metric::Int(30), Metric::Int(15)), (100f64, 2f64)),
1207            (EitherOrBoth::Both(Metric::Int(1), Metric::Int(1)), (0f64, 1f64)),
1208            (EitherOrBoth::Both(Metric::Int(2), Metric::Int(2)), (0f64, 1f64)),
1209            (EitherOrBoth::Both(Metric::Int(3), Metric::Int(3)), (0f64, 1f64)),
1210            (EitherOrBoth::Both(Metric::Int(4), Metric::Int(4)), (0f64, 1f64)),
1211            (EitherOrBoth::Both(Metric::Int(2), Metric::Int(1)), (100f64, 2f64)),
1212            (EitherOrBoth::Both(Metric::Int(0), Metric::Int(0)), (0f64, 1f64)),
1213            (EitherOrBoth::Both(Metric::Int(54), Metric::Int(24)), (125f64, 2.25f64)),
1214            (EitherOrBoth::Both(Metric::Int(0), Metric::Int(1)), (-100f64, f64::NEG_INFINITY)),
1215            (EitherOrBoth::Both(Metric::Int(6), Metric::Int(5)), (20f64, 1.2f64)),
1216            (EitherOrBoth::Both(Metric::Int(60), Metric::Int(30)), (100f64, 2f64)),
1217            (EitherOrBoth::Both(Metric::Int(264), Metric::Int(204)),
1218                (29.411_764_705_882_355_f64, 1.294_117_647_058_823_6_f64)
1219            ),
1220            (EitherOrBoth::Both(Metric::Float(10f64), Metric::Float(20f64)), (-50f64, -2f64)),
1221            (EitherOrBoth::Both(Metric::Float(10f64), Metric::Float(20f64)), (-50f64, -2f64)),
1222            (EitherOrBoth::Both(Metric::Float(40f64), Metric::Float(80f64)), (-50f64, -2f64)),
1223            (EitherOrBoth::Both(Metric::Float(4f64), Metric::Float(4f64)), (0f64, 1f64)),
1224            (EitherOrBoth::Both(Metric::Float(10f64), Metric::Float(16.666_666_666_666_664_f64)),
1225                (-39.999_999_999_999_99_f64, -1.666_666_666_666_666_5_f64)
1226            ),
1227            (EitherOrBoth::Both(Metric::Float(90f64), Metric::Float(80f64)), (12.5f64, 1.125f64)),
1228            (EitherOrBoth::Both(Metric::Float(0f64), Metric::Float(3.333_333_333_333_333_5_f64)),
1229                (-100f64, f64::NEG_INFINITY)
1230            ),
1231            (EitherOrBoth::Both(Metric::Float(10f64), Metric::Float(16.666_666_666_666_664_f64)),
1232                (-39.999_999_999_999_99_f64, -1.666_666_666_666_666_5_f64)
1233            ),
1234        ]
1235    )]
1236    fn test_metrics_summary_new<V>(
1237        #[case] new_metrics: &[u64],
1238        #[case] old_metrics: &[u64],
1239        #[case] expected: &[(EitherOrBoth<Metric>, V)],
1240    ) where
1241        V: Into<Option<(f64, f64)>> + Clone,
1242    {
1243        use crate::util::EitherOrBoth;
1244
1245        let expected_metrics_summary =
1246            metrics_summary_fixture(expected.iter().map(|(e, v)| (e.clone(), v.clone())));
1247        let actual = match (
1248            (!new_metrics.is_empty()).then_some(new_metrics),
1249            (!old_metrics.is_empty()).then_some(old_metrics),
1250        ) {
1251            (None, None) => unreachable!(),
1252            (Some(new), None) => MetricsSummary::new(EitherOrBoth::Left(metrics_fixture(new))),
1253            (None, Some(old)) => MetricsSummary::new(EitherOrBoth::Right(metrics_fixture(old))),
1254            (Some(new), Some(old)) => MetricsSummary::new(EitherOrBoth::Both(
1255                metrics_fixture(new),
1256                metrics_fixture(old),
1257            )),
1258        };
1259
1260        assert_eq!(actual, expected_metrics_summary);
1261    }
1262}