iai_callgrind_runner/runner/callgrind/
model.rs

1//! This module includes all the structs to model the callgrind output
2
3use std::borrow::Cow;
4use std::hash::Hash;
5use std::str::FromStr;
6
7use anyhow::{anyhow, Result};
8use indexmap::{indexmap, IndexMap};
9use serde::{Deserialize, Serialize};
10
11use super::CacheSummary;
12use crate::api::EventKind;
13use crate::runner::metrics::{Metric, Summarize};
14
15/// The callgrind specific `Metrics`
16pub type Metrics = crate::runner::metrics::Metrics<EventKind>;
17
18/// The [`Positions`] type
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub enum PositionType {
21    /// The address of an instruction
22    Instr,
23    /// The line number
24    Line,
25}
26
27/// The call relationship among functions
28#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
29pub struct Calls {
30    /// The call count
31    amount: u64,
32    /// The target [`Positions`]
33    positions: Positions,
34}
35
36/// The positions
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct Positions(pub IndexMap<PositionType, u64>);
39
40impl Calls {
41    /// Create new `Calls` struct
42    pub fn from<I, T>(mut iter: T, mut positions: Positions) -> Self
43    where
44        I: AsRef<str>,
45        T: Iterator<Item = I>,
46    {
47        let amount = iter.next().unwrap().as_ref().parse().unwrap();
48        positions.set_iter_str(iter);
49        Self { amount, positions }
50    }
51}
52
53impl Summarize for EventKind {
54    fn summarize(costs: &mut Cow<Metrics>) {
55        if !costs.is_summarized() {
56            let _ = costs.to_mut().make_summary();
57        }
58    }
59}
60
61impl Metrics {
62    /// Calculate and add derived summary events (i.e. estimated cycles) in-place
63    ///
64    /// Additional calls to this function will overwrite the metrics for derived summary events.
65    ///
66    /// # Errors
67    ///
68    /// If the necessary cache simulation events (when running callgrind with --cache-sim) were not
69    /// present.
70    pub fn make_summary(&mut self) -> Result<()> {
71        let CacheSummary {
72            l1_hits,
73            l3_hits,
74            ram_hits,
75            total_memory_rw,
76            cycles,
77            i1_miss_rate,
78            d1_miss_rate,
79            ll_miss_rate,
80            lli_miss_rate,
81            lld_miss_rate,
82            l1_hit_rate,
83            l3_hit_rate,
84            ram_hit_rate,
85        } = (&*self).try_into()?;
86
87        self.insert(EventKind::L1hits, l1_hits);
88        self.insert(EventKind::LLhits, l3_hits);
89        self.insert(EventKind::RamHits, ram_hits);
90        self.insert(EventKind::TotalRW, total_memory_rw);
91        self.insert(EventKind::EstimatedCycles, cycles);
92        self.insert(EventKind::I1MissRate, i1_miss_rate);
93        self.insert(EventKind::D1MissRate, d1_miss_rate);
94        self.insert(EventKind::LLiMissRate, lli_miss_rate);
95        self.insert(EventKind::LLdMissRate, lld_miss_rate);
96        self.insert(EventKind::LLMissRate, ll_miss_rate);
97        self.insert(EventKind::L1HitRate, l1_hit_rate);
98        self.insert(EventKind::LLHitRate, l3_hit_rate);
99        self.insert(EventKind::RamHitRate, ram_hit_rate);
100
101        Ok(())
102    }
103
104    /// Return true if costs are already summarized
105    ///
106    /// This method just probes for [`EventKind::EstimatedCycles`] to detect the summarized state.
107    pub fn is_summarized(&self) -> bool {
108        self.metric_by_kind(&EventKind::EstimatedCycles).is_some()
109    }
110
111    /// Return true if costs can be summarized
112    ///
113    /// This method probes for [`EventKind::I1mr`] which is present if callgrind was run with the
114    /// cache simulation (`--cache-sim=yes`) enabled.
115    pub fn can_summarize(&self) -> bool {
116        self.metric_by_kind(&EventKind::I1mr).is_some()
117    }
118}
119
120impl Default for Metrics {
121    fn default() -> Self {
122        Self(indexmap! {EventKind::Ir => Metric::Int(0)})
123    }
124}
125
126impl FromStr for PositionType {
127    type Err = anyhow::Error;
128
129    fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
130        // "addr" is taken from the callgrind_annotate script although not officially documented
131        match value.to_lowercase().as_str() {
132            "instr" | "addr" => Ok(Self::Instr),
133            "line" => Ok(Self::Line),
134            _ => Err(anyhow!("Unknown positions type: '{value}")),
135        }
136    }
137}
138
139impl Positions {
140    /// Create a new `Positions` from the content of an iterator
141    pub fn try_from_iter_str<'a, I>(iter: I) -> Result<Self>
142    where
143        I: Iterator<Item = &'a str>,
144    {
145        iter.map(|s| s.parse::<PositionType>().map(|p| (p, 0)))
146            .collect::<Result<IndexMap<_, _>>>()
147            .map(Positions)
148    }
149
150    /// Set the positions from the contents of an iterator
151    pub fn set_iter_str<I, T>(&mut self, iter: T)
152    where
153        I: AsRef<str>,
154        T: IntoIterator<Item = I>,
155    {
156        for ((_, old), pos) in self.0.iter_mut().zip(iter.into_iter()) {
157            let pos = pos.as_ref();
158            *old = if let Some(hex) = pos.strip_prefix("0x") {
159                u64::from_str_radix(hex, 16).unwrap()
160            } else {
161                pos.parse::<u64>().unwrap()
162            }
163        }
164    }
165
166    /// Return the length of the positions
167    pub fn len(&self) -> usize {
168        self.0.len()
169    }
170
171    /// Return true if positions is empty
172    pub fn is_empty(&self) -> bool {
173        self.0.is_empty()
174    }
175}
176
177impl Default for Positions {
178    fn default() -> Self {
179        Self(indexmap! {PositionType::Line => 0})
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    // Not testing here if the numbers make sense. Just if all metrics are present in the correct
188    // order
189    #[test]
190    fn test_metrics_make_summary_when_cache_sim() {
191        use EventKind::*;
192
193        let mut expected = Metrics::with_metric_kinds([
194            (Ir, 1),
195            (Dr, 2),
196            (Dw, 3),
197            (I1mr, 4),
198            (D1mr, 5),
199            (D1mw, 6),
200            (ILmr, 7),
201            (DLmr, 8),
202            (DLmw, 9),
203            (L1hits, 0),
204            (LLhits, 0),
205            (RamHits, 24),
206            (TotalRW, 6),
207            (EstimatedCycles, 840),
208        ]);
209
210        expected.insert_all(&[
211            (I1MissRate, Metric::Float(400.0f64)),
212            (D1MissRate, Metric::Float(220.000_000_000_000_03_f64)),
213            (LLiMissRate, Metric::Float(700.0f64)),
214            (LLdMissRate, Metric::Float(340.0f64)),
215            (LLMissRate, Metric::Float(400.0f64)),
216            (L1HitRate, Metric::Float(0.0f64)),
217            (LLHitRate, Metric::Float(0.0f64)),
218            (RamHitRate, Metric::Float(400.0f64)),
219        ]);
220
221        let mut metrics = Metrics::with_metric_kinds([
222            (Ir, 1),
223            (Dr, 2),
224            (Dw, 3),
225            (I1mr, 4),
226            (D1mr, 5),
227            (D1mw, 6),
228            (ILmr, 7),
229            (DLmr, 8),
230            (DLmw, 9),
231        ]);
232
233        metrics.make_summary().unwrap();
234
235        assert_eq!(metrics, expected);
236    }
237}