iai_callgrind_runner/runner/callgrind/
args.rs

1use std::collections::VecDeque;
2use std::ffi::OsString;
3use std::path::PathBuf;
4use std::str::FromStr;
5
6use anyhow::Result;
7use log::{log_enabled, warn};
8
9use crate::api::RawArgs;
10use crate::error::Error;
11use crate::runner::tool;
12use crate::runner::tool::args::FairSched;
13use crate::util::{bool_to_yesno, yesno_to_bool};
14
15#[allow(clippy::struct_excessive_bools)]
16#[derive(Debug, Clone)]
17pub struct Args {
18    i1: String,
19    d1: String,
20    ll: String,
21    cache_sim: bool,
22    other: Vec<String>,
23    toggle_collect: VecDeque<String>,
24    compress_strings: bool,
25    compress_pos: bool,
26    verbose: bool,
27    dump_instr: bool,
28    dump_line: bool,
29    /// --combine-dumps is currently not supported by the callgrind parsers, so we print a warning
30    combine_dumps: bool,
31    callgrind_out_file: Option<PathBuf>,
32    log_arg: Option<OsString>,
33    trace_children: bool,
34    separate_threads: bool,
35    fair_sched: FairSched,
36}
37
38impl Args {
39    pub fn try_from_raw_args(args: &[&RawArgs]) -> Result<Self> {
40        let mut default = Self::default();
41        default.try_update(args.iter().flat_map(|s| &s.0))?;
42        Ok(default)
43    }
44
45    pub fn try_update<'a, T: Iterator<Item = &'a String>>(&mut self, args: T) -> Result<()> {
46        for arg in args {
47            match arg
48                .trim()
49                .split_once('=')
50                .map(|(k, v)| (k.trim(), v.trim()))
51            {
52                Some(("--I1", value)) => value.clone_into(&mut self.i1),
53                Some(("--D1", value)) => value.clone_into(&mut self.d1),
54                Some(("--LL", value)) => value.clone_into(&mut self.ll),
55                Some((key @ "--cache-sim", value)) => {
56                    self.cache_sim = yesno_to_bool(value).ok_or_else(|| {
57                        Error::InvalidBoolArgument((key.to_owned(), value.to_owned()))
58                    })?;
59                }
60                Some(("--toggle-collect", value)) => {
61                    self.toggle_collect.push_back(value.to_owned());
62                }
63                Some((key @ "--dump-instr", value)) => {
64                    self.dump_instr = yesno_to_bool(value).ok_or_else(|| {
65                        Error::InvalidBoolArgument((key.to_owned(), value.to_owned()))
66                    })?;
67                }
68                Some((key @ "--dump-line", value)) => {
69                    self.dump_line = yesno_to_bool(value).ok_or_else(|| {
70                        Error::InvalidBoolArgument((key.to_owned(), value.to_owned()))
71                    })?;
72                }
73                Some((key @ "--trace-children", value)) => {
74                    self.trace_children = yesno_to_bool(value).ok_or_else(|| {
75                        Error::InvalidBoolArgument((key.to_owned(), value.to_owned()))
76                    })?;
77                }
78                Some((key @ "--separate-threads", value)) => {
79                    self.separate_threads = yesno_to_bool(value).ok_or_else(|| {
80                        Error::InvalidBoolArgument((key.to_owned(), value.to_owned()))
81                    })?;
82                }
83                Some(("--fair-sched", value)) => {
84                    self.fair_sched = FairSched::from_str(value)?;
85                }
86                Some((
87                    key @ ("--combine-dumps"
88                    | "--callgrind-out-file"
89                    | "--compress-strings"
90                    | "--compress-pos"
91                    | "--log-file"
92                    | "--log-fd"
93                    | "--log-socket"
94                    | "--xml"
95                    | "--xml-file"
96                    | "--xml-fd"
97                    | "--xml-socket"
98                    | "--xml-user-comment"
99                    | "--tool"),
100                    value,
101                )) => {
102                    warn!("Ignoring callgrind argument: '{}={}'", key, value);
103                }
104                Some(_) => self.other.push(arg.clone()),
105                None if arg == "-v" || arg == "--verbose" => self.verbose = true,
106                None if matches!(
107                    arg.trim(),
108                    "-h" | "--help"
109                        | "--help-dyn-options"
110                        | "--help-debug"
111                        | "--version"
112                        | "-q"
113                        | "--quiet"
114                ) =>
115                {
116                    warn!("Ignoring callgrind argument: '{arg}'");
117                }
118                None if arg.starts_with('-') => self.other.push(arg.clone()),
119                // ignore positional arguments for now. It might be a filtering argument for cargo
120                // bench
121                None => {}
122            }
123        }
124        Ok(())
125    }
126
127    // Insert the --toggle-collect argument at the start
128    //
129    // This is pure cosmetics, since callgrind doesn't prioritize the toggles by any order
130    pub fn insert_toggle_collect(&mut self, arg: &str) {
131        self.toggle_collect.push_front(arg.to_owned());
132    }
133}
134
135impl Default for Args {
136    fn default() -> Self {
137        Self {
138            // Set some reasonable cache sizes. The exact sizes matter less than having fixed sizes,
139            // since otherwise callgrind would take them from the CPU and make benchmark runs
140            // even more incomparable between machines.
141            i1: String::from("32768,8,64"),
142            d1: String::from("32768,8,64"),
143            ll: String::from("8388608,16,64"),
144            cache_sim: true,
145            compress_pos: false,
146            compress_strings: false,
147            combine_dumps: false,
148            verbose: log_enabled!(log::Level::Debug),
149            dump_line: true,
150            dump_instr: false,
151            toggle_collect: VecDeque::default(),
152            callgrind_out_file: Option::default(),
153            log_arg: Option::default(),
154            other: Vec::default(),
155            trace_children: true,
156            separate_threads: true,
157            fair_sched: FairSched::Try,
158        }
159    }
160}
161
162impl From<Args> for tool::args::ToolArgs {
163    fn from(mut value: Args) -> Self {
164        let mut other = vec![
165            format!("--I1={}", &value.i1),
166            format!("--D1={}", &value.d1),
167            format!("--LL={}", &value.ll),
168            format!("--cache-sim={}", bool_to_yesno(value.cache_sim)),
169            format!(
170                "--compress-strings={}",
171                bool_to_yesno(value.compress_strings)
172            ),
173            format!("--compress-pos={}", bool_to_yesno(value.compress_pos)),
174            format!("--dump-line={}", bool_to_yesno(value.dump_line)),
175            format!("--dump-instr={}", bool_to_yesno(value.dump_instr)),
176            format!("--combine-dumps={}", bool_to_yesno(value.combine_dumps)),
177            format!(
178                "--separate-threads={}",
179                bool_to_yesno(value.separate_threads)
180            ),
181        ];
182        other.append(
183            &mut value
184                .toggle_collect
185                .iter()
186                .map(|s| format!("--toggle-collect={s}"))
187                .collect::<Vec<String>>(),
188        );
189        other.append(&mut value.other);
190
191        Self {
192            tool: tool::ValgrindTool::Callgrind,
193            output_paths: value
194                .callgrind_out_file
195                .map_or_else(Vec::new, |o| vec![o.into()]),
196            log_path: value.log_arg,
197            error_exitcode: "0".to_owned(),
198            verbose: value.verbose,
199            trace_children: value.trace_children,
200            fair_sched: value.fair_sched,
201            other,
202        }
203    }
204}