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;
5
6use anyhow::Result;
7use indexmap::{indexmap, IndexMap};
8use serde::{Deserialize, Serialize};
9
10use super::CacheSummary;
11use crate::api::EventKind;
12use crate::runner::metrics::Summarize;
13
14pub type Metrics = crate::runner::metrics::Metrics<EventKind>;
15
16#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
17pub struct Calls {
18    amount: u64,
19    positions: Positions,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub enum PositionType {
24    Instr,
25    Line,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct Positions(IndexMap<PositionType, u64>);
30
31impl Calls {
32    pub fn from<I>(mut iter: impl Iterator<Item = I>, mut positions: Positions) -> Self
33    where
34        I: AsRef<str>,
35    {
36        let amount = iter.next().unwrap().as_ref().parse().unwrap();
37        positions.set_iter_str(iter);
38        Self { amount, positions }
39    }
40}
41
42impl Metrics {
43    /// Calculate and add derived summary events (i.e. estimated cycles) in-place
44    ///
45    /// Additional calls to this function will overwrite the metrics for derived summary events.
46    ///
47    /// # Errors
48    ///
49    /// If the necessary cache simulation events (when running callgrind with --cache-sim) were not
50    /// present.
51    pub fn make_summary(&mut self) -> Result<()> {
52        let CacheSummary {
53            l1_hits,
54            l3_hits,
55            ram_hits,
56            total_memory_rw,
57            cycles,
58        } = (&*self).try_into()?;
59
60        self.insert(EventKind::L1hits, l1_hits);
61        self.insert(EventKind::LLhits, l3_hits);
62        self.insert(EventKind::RamHits, ram_hits);
63        self.insert(EventKind::TotalRW, total_memory_rw);
64        self.insert(EventKind::EstimatedCycles, cycles);
65
66        Ok(())
67    }
68
69    /// Return true if costs are already summarized
70    ///
71    /// This method just probes for [`EventKind::EstimatedCycles`] to detect the summarized state.
72    pub fn is_summarized(&self) -> bool {
73        self.metric_by_kind(&EventKind::EstimatedCycles).is_some()
74    }
75
76    /// Return true if costs can be summarized
77    ///
78    /// This method probes for [`EventKind::I1mr`] which is present if callgrind was run with the
79    /// cache simulation (`--cache-sim=yes`) enabled.
80    pub fn can_summarize(&self) -> bool {
81        self.metric_by_kind(&EventKind::I1mr).is_some()
82    }
83}
84
85impl Default for Metrics {
86    fn default() -> Self {
87        Self(indexmap! {EventKind::Ir => 0})
88    }
89}
90
91impl Summarize for EventKind {
92    fn summarize(costs: &mut Cow<Metrics>) {
93        if !costs.is_summarized() {
94            let _ = costs.to_mut().make_summary();
95        }
96    }
97}
98
99impl Positions {
100    pub fn set_iter_str<I, T>(&mut self, iter: T)
101    where
102        I: AsRef<str>,
103        T: IntoIterator<Item = I>,
104    {
105        for ((_, old), pos) in self.0.iter_mut().zip(iter.into_iter()) {
106            let pos = pos.as_ref();
107            *old = if let Some(hex) = pos.strip_prefix("0x") {
108                u64::from_str_radix(hex, 16).unwrap()
109            } else {
110                pos.parse::<u64>().unwrap()
111            }
112        }
113    }
114
115    pub fn len(&self) -> usize {
116        self.0.len()
117    }
118
119    pub fn is_empty(&self) -> bool {
120        self.0.is_empty()
121    }
122}
123
124impl Default for Positions {
125    fn default() -> Self {
126        Self(indexmap! {PositionType::Line => 0})
127    }
128}
129
130impl<I> FromIterator<I> for Positions
131where
132    I: AsRef<str>,
133{
134    fn from_iter<T>(iter: T) -> Self
135    where
136        T: IntoIterator<Item = I>,
137    {
138        Self(
139            iter.into_iter()
140                .map(|p| (PositionType::from(p), 0))
141                .collect::<IndexMap<_, _>>(),
142        )
143    }
144}
145
146impl<T> From<T> for PositionType
147where
148    T: AsRef<str>,
149{
150    fn from(value: T) -> Self {
151        let value = value.as_ref();
152        // "addr" is taken from the callgrind_annotate script although not officially documented
153        match value.to_lowercase().as_str() {
154            "instr" | "addr" => Self::Instr,
155            "line" => Self::Line,
156            _ => panic!("Unknown positions type: '{value}"),
157        }
158    }
159}