iai_callgrind_runner/runner/
metrics.rs

1use std::borrow::Cow;
2use std::fmt::Display;
3use std::hash::Hash;
4
5use anyhow::{Context, Result};
6use indexmap::map::Iter;
7use indexmap::{IndexMap, IndexSet};
8#[cfg(feature = "schema")]
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12pub trait Summarize: Hash + Eq + Clone {
13    fn summarize(_: &mut Cow<Metrics<Self>>) {}
14}
15
16/// The `Metrics` backed by an [`indexmap::IndexMap`]
17///
18/// The insertion order is preserved.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[cfg_attr(feature = "schema", derive(JsonSchema))]
21pub struct Metrics<K: Hash + Eq>(pub IndexMap<K, u64>);
22
23impl<K: Hash + Eq + Display + Clone> Metrics<K> {
24    /// Return empty `Metrics`
25    pub fn empty() -> Self {
26        Metrics(IndexMap::new())
27    }
28
29    // The order matters. The index is derived from the insertion order
30    pub fn with_metric_kinds<T>(kinds: T) -> Self
31    where
32        T: IntoIterator<Item = (K, u64)>,
33    {
34        Self(kinds.into_iter().collect())
35    }
36
37    /// Add metrics from an iterator over strings
38    ///
39    /// Adding metrics stops as soon as there are no more keys in this `Metrics` or no more values
40    /// in the iterator. This property is especially important for the metrics from the callgrind
41    /// output files. From the documentation of the callgrind format:
42    ///
43    /// > If a cost line specifies less event counts than given in the "events" line, the
44    /// > rest is assumed to be zero.
45    ///
46    /// # Errors
47    ///
48    /// If one of the strings in the iterator is not parsable as u64
49    pub fn add_iter_str<I, T>(&mut self, iter: T) -> Result<()>
50    where
51        I: AsRef<str>,
52        T: IntoIterator<Item = I>,
53    {
54        for (this, other) in self.0.values_mut().zip(iter.into_iter()) {
55            *this += other
56                .as_ref()
57                .parse::<u64>()
58                .context("A metric must be an integer type")?;
59        }
60
61        Ok(())
62    }
63
64    /// Sum this `Metric` with another `Metric`
65    ///
66    /// Do not use this method if both `Metrics` can differ in their keys order.
67    pub fn add(&mut self, other: &Self) {
68        for (this, other) in self.0.values_mut().zip(other.0.values()) {
69            *this += other;
70        }
71    }
72
73    /// Return the metric of the kind at index (of insertion order) if present
74    ///
75    /// This operation is O(1)
76    pub fn metric_by_index(&self, index: usize) -> Option<u64> {
77        self.0.get_index(index).map(|(_, c)| *c)
78    }
79
80    /// Return the metric of the `kind` if present
81    ///
82    /// This operation is O(1)
83    pub fn metric_by_kind(&self, kind: &K) -> Option<u64> {
84        self.0.get_key_value(kind).map(|(_, c)| *c)
85    }
86
87    /// Return the metric kind or an error
88    ///
89    /// # Errors
90    ///
91    /// If the metric kind is not present
92    pub fn try_metric_by_kind(&self, kind: &K) -> Result<u64> {
93        self.metric_by_kind(kind)
94            .with_context(|| format!("Missing event type '{kind}"))
95    }
96
97    pub fn metric_kinds(&self) -> Vec<K> {
98        self.0.iter().map(|(k, _)| k.clone()).collect()
99    }
100
101    /// Create the union set of the keys of this and another `Metrics`
102    ///
103    /// The order of the keys is preserved. New keys from the `other` Metrics are appended in their
104    /// original order.
105    pub fn metric_kinds_union<'a>(&'a self, other: &'a Self) -> IndexSet<&'a K> {
106        let set = self.0.keys().collect::<IndexSet<_>>();
107        let other_set = other.0.keys().collect::<IndexSet<_>>();
108        set.union(&other_set).copied().collect()
109    }
110
111    /// Return an iterator over the metrics in insertion order
112    pub fn iter(&self) -> Iter<'_, K, u64> {
113        self.0.iter()
114    }
115
116    /// Return true if there are no metrics present
117    pub fn is_empty(&self) -> bool {
118        self.0.is_empty()
119    }
120
121    /// Insert a single metric
122    ///
123    /// If an equivalent key already exists in the map: the key remains and retains in its place in
124    /// the order, its corresponding value is updated with `value`, and the older value is returned
125    /// inside `Some(_)`.
126    ///
127    /// If no equivalent key existed in the map: the new key-value pair is inserted, last in order,
128    /// and `None` is returned.
129    pub fn insert(&mut self, key: K, value: u64) -> Option<u64> {
130        self.0.insert(key, value)
131    }
132
133    /// Insert all metrics
134    ///
135    /// See also [`Metrics::insert`]
136    pub fn insert_all(&mut self, entries: &[(K, u64)]) {
137        for (key, value) in entries {
138            self.insert(key.clone(), *value);
139        }
140    }
141}
142
143impl<'a, K: Hash + Eq + Display + Clone> IntoIterator for &'a Metrics<K> {
144    type Item = (&'a K, &'a u64);
145
146    type IntoIter = Iter<'a, K, u64>;
147
148    fn into_iter(self) -> Self::IntoIter {
149        self.iter()
150    }
151}
152
153impl<I, K: Hash + Eq + From<I>> FromIterator<I> for Metrics<K> {
154    fn from_iter<T>(iter: T) -> Self
155    where
156        T: IntoIterator<Item = I>,
157    {
158        Self(
159            iter.into_iter()
160                .map(|s| (K::from(s), 0))
161                .collect::<IndexMap<_, _>>(),
162        )
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use std::iter;
169
170    use rstest::rstest;
171
172    use super::*;
173    use crate::api::EventKind::{self, *};
174
175    fn expected_metrics<T>(events: T) -> Metrics<EventKind>
176    where
177        T: IntoIterator<Item = (EventKind, u64)>,
178    {
179        Metrics(IndexMap::from_iter(events))
180    }
181
182    #[rstest]
183    #[case::single_zero(&[Ir], &["0"], expected_metrics([(Ir, 0)]))]
184    #[case::single_one(&[Ir], &["1"], expected_metrics([(Ir, 1)]))]
185    #[case::single_u64_max(&[Ir], &[u64::MAX.to_string()], expected_metrics([(Ir, u64::MAX)]))]
186    #[case::more_values_than_kinds(&[Ir], &["1", "2"], expected_metrics([(Ir, 1)]))]
187    #[case::more_kinds_than_values(&[Ir, I1mr], &["1"], expected_metrics([(Ir, 1), (I1mr, 0)]))]
188    fn test_metrics_add_iter_str<I>(
189        #[case] event_kinds: &[EventKind],
190        #[case] to_add: &[I],
191        #[case] expected_metrics: Metrics<EventKind>,
192    ) where
193        I: AsRef<str>,
194    {
195        let mut metrics =
196            Metrics::with_metric_kinds(event_kinds.iter().copied().zip(iter::repeat(0)));
197        metrics.add_iter_str(to_add).unwrap();
198
199        assert_eq!(metrics, expected_metrics);
200    }
201
202    #[rstest]
203    #[case::float(&[Ir], &["0.0"])]
204    #[case::word(&[Ir], &["abc"])]
205    #[case::empty(&[Ir], &[""])]
206    #[case::one_more_than_max_u64(&[Ir], &["18446744073709551616"])]
207    fn test_metrics_add_iter_str_when_error<I>(
208        #[case] event_kinds: &[EventKind],
209        #[case] to_add: &[I],
210    ) where
211        I: AsRef<str>,
212    {
213        let mut metrics =
214            Metrics::with_metric_kinds(event_kinds.iter().copied().zip(iter::repeat(0)));
215        assert!(metrics.add_iter_str(to_add).is_err());
216    }
217}