iai_callgrind_runner/
api.rs

1//! The api contains all elements which the `runner` can understand
2use std::ffi::OsString;
3use std::fmt::Display;
4#[cfg(feature = "runner")]
5use std::fs::File;
6use std::net::SocketAddr;
7use std::path::{Path, PathBuf};
8#[cfg(feature = "runner")]
9use std::process::{Child, Command as StdCommand, Stdio as StdStdio};
10use std::time::Duration;
11
12#[cfg(feature = "schema")]
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15
16#[cfg(feature = "runner")]
17use crate::runner::metrics::Summarize;
18
19/// The model for the `#[binary_benchmark]` attribute or the equivalent from the low level api
20#[derive(Debug, Clone, Default, Serialize, Deserialize)]
21pub struct BinaryBenchmark {
22    pub config: Option<BinaryBenchmarkConfig>,
23    pub benches: Vec<BinaryBenchmarkBench>,
24}
25
26/// The model for the `#[bench]` attribute or the low level equivalent
27#[derive(Debug, Clone, Default, Serialize, Deserialize)]
28pub struct BinaryBenchmarkBench {
29    pub id: Option<String>,
30    pub function_name: String,
31    pub args: Option<String>,
32    pub command: Command,
33    pub config: Option<BinaryBenchmarkConfig>,
34    pub has_setup: bool,
35    pub has_teardown: bool,
36}
37
38#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
39pub struct BinaryBenchmarkConfig {
40    pub env_clear: Option<bool>,
41    pub current_dir: Option<PathBuf>,
42    pub entry_point: Option<String>,
43    pub exit_with: Option<ExitWith>,
44    pub callgrind_args: RawArgs,
45    pub valgrind_args: RawArgs,
46    pub envs: Vec<(OsString, Option<OsString>)>,
47    pub flamegraph_config: Option<FlamegraphConfig>,
48    pub regression_config: Option<RegressionConfig>,
49    pub tools: Tools,
50    pub tools_override: Option<Tools>,
51    pub sandbox: Option<Sandbox>,
52    pub setup_parallel: Option<bool>,
53    pub output_format: Option<OutputFormat>,
54}
55
56/// The model for the `binary_benchmark_group` macro
57#[derive(Debug, Clone, Default, Serialize, Deserialize)]
58pub struct BinaryBenchmarkGroup {
59    pub id: String,
60    pub config: Option<BinaryBenchmarkConfig>,
61    pub has_setup: bool,
62    pub has_teardown: bool,
63    pub binary_benchmarks: Vec<BinaryBenchmark>,
64    pub compare_by_id: Option<bool>,
65}
66
67/// The model for the main! macro
68#[derive(Debug, Clone, Default, Serialize, Deserialize)]
69pub struct BinaryBenchmarkGroups {
70    pub config: BinaryBenchmarkConfig,
71    pub groups: Vec<BinaryBenchmarkGroup>,
72    /// The command line arguments as we receive them from `cargo bench`
73    pub command_line_args: Vec<String>,
74    pub has_setup: bool,
75    pub has_teardown: bool,
76}
77
78#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
79pub struct Delay {
80    pub poll: Option<Duration>,
81    pub timeout: Option<Duration>,
82    pub kind: DelayKind,
83}
84
85/// The kind of `Delay`
86#[non_exhaustive]
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub enum DelayKind {
89    /// Delay the `Command` for a fixed [`Duration`]
90    DurationElapse(Duration),
91    /// Delay the `Command` until a successful tcp connection can be established
92    TcpConnect(SocketAddr),
93    /// Delay the `Command` until a successful udp response was received
94    UdpResponse(SocketAddr, Vec<u8>),
95    /// Delay the `Command` until the specified path exists
96    PathExists(PathBuf),
97}
98
99#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
100pub struct Command {
101    pub path: PathBuf,
102    pub args: Vec<OsString>,
103    pub stdin: Option<Stdin>,
104    pub stdout: Option<Stdio>,
105    pub stderr: Option<Stdio>,
106    pub config: BinaryBenchmarkConfig,
107    pub delay: Option<Delay>,
108}
109
110/// The `Direction` in which the flamegraph should grow.
111///
112/// The default is `TopToBottom`.
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114pub enum Direction {
115    /// Grow from top to bottom with the highest event costs at the top
116    TopToBottom,
117    /// Grow from bottom to top with the highest event costs at the bottom
118    BottomToTop,
119}
120
121/// The metric kinds collected by DHAT
122#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
123#[cfg_attr(feature = "schema", derive(JsonSchema))]
124pub enum DhatMetricKind {
125    /// Total bytes allocated over the entire execution
126    TotalBytes,
127    /// Total heap blocks allocated over the entire execution
128    TotalBlocks,
129    /// The bytes alive at t-gmax, the time when the heap size reached its global maximum
130    AtTGmaxBytes,
131    /// The blocks alive at t-gmax
132    AtTGmaxBlocks,
133    /// The amount of bytes at the end of the execution.
134    ///
135    /// This is the amount of bytes which were not explicitly freed.
136    AtTEndBytes,
137    /// The amount of blocks at the end of the execution.
138    ///
139    /// This is the amount of heap blocks which were not explicitly freed.
140    AtTEndBlocks,
141    /// The amount of bytes read during the entire execution
142    ReadsBytes,
143    /// The amount of bytes written during the entire execution
144    WritesBytes,
145    /// The total lifetimes of all heap blocks allocated
146    TotalLifetimes,
147    /// The maximum amount of bytes
148    MaximumBytes,
149    /// The maximum amount of heap blocks
150    MaximumBlocks,
151}
152
153/// The `EntryPoint` of a library benchmark
154#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
155pub enum EntryPoint {
156    /// Disable the entry point and default toggle entirely
157    None,
158    /// The default entry point is the benchmark function
159    #[default]
160    Default,
161    /// A custom entry point. The argument allows the same glob patterns as the
162    /// [`--toggle-collect`](https://valgrind.org/docs/manual/cl-manual.html#cl-manual.options)
163    /// argument of callgrind.
164    Custom(String),
165}
166
167// The error metrics from a tool which reports errors
168//
169// The tools which report only errors are `helgrind`, `drd` and `memcheck`. The order in which the
170// variants are defined in this enum determines the order of the metrics in the benchmark terminal
171// output.
172#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
173#[cfg_attr(feature = "schema", derive(JsonSchema))]
174pub enum ErrorMetricKind {
175    /// The amount of detected unsuppressed errors
176    Errors,
177    /// The amount of detected unsuppressed error contexts
178    Contexts,
179    /// The amount of suppressed errors
180    SuppressedErrors,
181    /// The amount of suppressed error contexts
182    SuppressedContexts,
183}
184
185/// All `EventKind`s callgrind produces and additionally some derived events
186///
187/// Depending on the options passed to Callgrind, these are the events that Callgrind can produce.
188/// See the [Callgrind
189/// documentation](https://valgrind.org/docs/manual/cl-manual.html#cl-manual.options) for details.
190#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
191#[cfg_attr(feature = "schema", derive(JsonSchema))]
192pub enum EventKind {
193    /// The default event. I cache reads (which equals the number of instructions executed)
194    Ir,
195    /// The number of system calls done (--collect-systime=yes)
196    SysCount,
197    /// The elapsed time spent in system calls (--collect-systime=yes)
198    SysTime,
199    /// The cpu time spent during system calls (--collect-systime=nsec)
200    SysCpuTime,
201    /// The number of global bus events (--collect-bus=yes)
202    Ge,
203    /// D Cache reads (which equals the number of memory reads) (--cache-sim=yes)
204    Dr,
205    /// D Cache writes (which equals the number of memory writes) (--cache-sim=yes)
206    Dw,
207    /// I1 cache read misses (--cache-sim=yes)
208    I1mr,
209    /// D1 cache read misses (--cache-sim=yes)
210    D1mr,
211    /// D1 cache write misses (--cache-sim=yes)
212    D1mw,
213    /// LL cache instruction read misses (--cache-sim=yes)
214    ILmr,
215    /// LL cache data read misses (--cache-sim=yes)
216    DLmr,
217    /// LL cache data write misses (--cache-sim=yes)
218    DLmw,
219    /// Derived event showing the L1 hits (--cache-sim=yes)
220    L1hits,
221    /// Derived event showing the LL hits (--cache-sim=yes)
222    LLhits,
223    /// Derived event showing the RAM hits (--cache-sim=yes)
224    RamHits,
225    /// Derived event showing the total amount of cache reads and writes (--cache-sim=yes)
226    TotalRW,
227    /// Derived event showing estimated CPU cycles (--cache-sim=yes)
228    EstimatedCycles,
229    /// Conditional branches executed (--branch-sim=yes)
230    Bc,
231    /// Conditional branches mispredicted (--branch-sim=yes)
232    Bcm,
233    /// Indirect branches executed (--branch-sim=yes)
234    Bi,
235    /// Indirect branches mispredicted (--branch-sim=yes)
236    Bim,
237    /// Dirty miss because of instruction read (--simulate-wb=yes)
238    ILdmr,
239    /// Dirty miss because of data read (--simulate-wb=yes)
240    DLdmr,
241    /// Dirty miss because of data write (--simulate-wb=yes)
242    DLdmw,
243    /// Counter showing bad temporal locality for L1 caches (--cachuse=yes)
244    AcCost1,
245    /// Counter showing bad temporal locality for LL caches (--cachuse=yes)
246    AcCost2,
247    /// Counter showing bad spatial locality for L1 caches (--cachuse=yes)
248    SpLoss1,
249    /// Counter showing bad spatial locality for LL caches (--cachuse=yes)
250    SpLoss2,
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
254pub enum ExitWith {
255    Success,
256    Failure,
257    Code(i32),
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct Fixtures {
262    pub path: PathBuf,
263    pub follow_symlinks: bool,
264}
265
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
267pub struct FlamegraphConfig {
268    pub kind: Option<FlamegraphKind>,
269    pub negate_differential: Option<bool>,
270    pub normalize_differential: Option<bool>,
271    pub event_kinds: Option<Vec<EventKind>>,
272    pub direction: Option<Direction>,
273    pub title: Option<String>,
274    pub subtitle: Option<String>,
275    pub min_width: Option<f64>,
276}
277
278/// The kind of `Flamegraph` which is going to be constructed
279#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
280pub enum FlamegraphKind {
281    /// The regular flamegraph for the new callgrind run
282    Regular,
283    /// A differential flamegraph showing the differences between the new and old callgrind run
284    Differential,
285    /// All flamegraph kinds that can be constructed (`Regular` and `Differential`). This
286    /// is the default.
287    All,
288    /// Do not produce any flamegraphs
289    None,
290}
291
292/// The model for the `#[library_benchmark]` attribute
293#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
294pub struct LibraryBenchmark {
295    pub config: Option<LibraryBenchmarkConfig>,
296    pub benches: Vec<LibraryBenchmarkBench>,
297}
298
299/// The model for the `#[bench]` attribute in a `#[library_benchmark]`
300#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
301pub struct LibraryBenchmarkBench {
302    pub id: Option<String>,
303    pub function_name: String,
304    pub args: Option<String>,
305    pub config: Option<LibraryBenchmarkConfig>,
306}
307
308#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
309pub struct LibraryBenchmarkConfig {
310    pub env_clear: Option<bool>,
311    pub callgrind_args: RawArgs,
312    pub valgrind_args: RawArgs,
313    pub envs: Vec<(OsString, Option<OsString>)>,
314    pub flamegraph_config: Option<FlamegraphConfig>,
315    pub regression_config: Option<RegressionConfig>,
316    pub tools: Tools,
317    pub tools_override: Option<Tools>,
318    pub entry_point: Option<EntryPoint>,
319    pub output_format: Option<OutputFormat>,
320}
321
322/// The model for the `library_benchmark_group` macro
323#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
324pub struct LibraryBenchmarkGroup {
325    pub id: String,
326    pub config: Option<LibraryBenchmarkConfig>,
327    pub compare_by_id: Option<bool>,
328    pub library_benchmarks: Vec<LibraryBenchmark>,
329    pub has_setup: bool,
330    pub has_teardown: bool,
331}
332
333/// The model for the `main` macro
334#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
335pub struct LibraryBenchmarkGroups {
336    pub config: LibraryBenchmarkConfig,
337    pub groups: Vec<LibraryBenchmarkGroup>,
338    /// The command line args as we receive them from `cargo bench`
339    pub command_line_args: Vec<String>,
340    pub has_setup: bool,
341    pub has_teardown: bool,
342}
343
344/// The configuration values for the output format
345#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
346pub struct OutputFormat {
347    pub truncate_description: Option<Option<usize>>,
348    pub show_intermediate: Option<bool>,
349    pub show_grid: Option<bool>,
350}
351
352#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
353pub struct RawArgs(pub Vec<String>);
354
355#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
356pub struct RegressionConfig {
357    pub limits: Vec<(EventKind, f64)>,
358    pub fail_fast: Option<bool>,
359}
360
361#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
362pub struct Sandbox {
363    pub enabled: Option<bool>,
364    pub fixtures: Vec<PathBuf>,
365    pub follow_symlinks: Option<bool>,
366}
367
368/// Configure the `Stream` which should be used as pipe in [`Stdin::Setup`]
369///
370/// The default is [`Pipe::Stdout`]
371#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
372pub enum Pipe {
373    /// The `Stdout` default `Stream`
374    #[default]
375    Stdout,
376    /// The `Stderr` error `Stream`
377    Stderr,
378}
379
380/// We use this enum only internally in the benchmark runner
381#[cfg(feature = "runner")]
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub(crate) enum Stream {
384    Stdin,
385    Stdout,
386    Stderr,
387}
388
389/// Configure the `Stdio` of `Stdin`, `Stdout` and `Stderr`
390///
391/// Describes what to do with a standard I/O stream for the [`Command`] when passed to the stdin,
392/// stdout, and stderr methods of [`Command`].
393#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
394pub enum Stdio {
395    /// The [`Command`]'s `Stream` inherits from the benchmark runner.
396    #[default]
397    Inherit,
398    /// This stream will be ignored. This is the equivalent of attaching the stream to `/dev/null`
399    Null,
400    /// Redirect the content of a file into this `Stream`. This is equivalent to a redirection in a
401    /// shell for example for the `Stdout` of `my-command`: `my-command > some_file`
402    File(PathBuf),
403    /// A new pipe should be arranged to connect the benchmark runner and the [`Command`]
404    Pipe,
405}
406
407/// This is a special `Stdio` for the stdin method of [`Command`]
408///
409/// Contains all the standard [`Stdio`] options and the [`Stdin::Setup`] option
410#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
411pub enum Stdin {
412    /// Using this in [`Command::stdin`] pipes the stream specified with [`Pipe`] of the `setup`
413    /// function into the `Stdin` of the [`Command`]. In this case the `setup` and [`Command`] are
414    /// executed in parallel instead of sequentially. See [`Command::stdin`] for more details.
415    Setup(Pipe),
416    #[default]
417    /// See [`Stdio::Inherit`]
418    Inherit,
419    /// See [`Stdio::Null`]
420    Null,
421    /// See [`Stdio::File`]
422    File(PathBuf),
423    /// See [`Stdio::Pipe`]
424    Pipe,
425}
426
427#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
428pub struct Tool {
429    pub kind: ValgrindTool,
430    pub enable: Option<bool>,
431    pub raw_args: RawArgs,
432    pub show_log: Option<bool>,
433}
434
435#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
436pub struct Tools(pub Vec<Tool>);
437
438/// The valgrind tools which can be run in addition to callgrind
439#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
440pub enum ValgrindTool {
441    /// [Memcheck: a memory error detector](https://valgrind.org/docs/manual/mc-manual.html)
442    Memcheck,
443    /// [Helgrind: a thread error detector](https://valgrind.org/docs/manual/hg-manual.html)
444    Helgrind,
445    /// [DRD: a thread error detector](https://valgrind.org/docs/manual/drd-manual.html)
446    DRD,
447    /// [Massif: a heap profiler](https://valgrind.org/docs/manual/ms-manual.html)
448    Massif,
449    /// [DHAT: a dynamic heap analysis tool](https://valgrind.org/docs/manual/dh-manual.html)
450    DHAT,
451    /// [BBV: an experimental basic block vector generation tool](https://valgrind.org/docs/manual/bbv-manual.html)
452    BBV,
453}
454
455impl BinaryBenchmarkConfig {
456    pub fn update_from_all<'a, T>(mut self, others: T) -> Self
457    where
458        T: IntoIterator<Item = Option<&'a Self>>,
459    {
460        for other in others.into_iter().flatten() {
461            self.env_clear = update_option(&self.env_clear, &other.env_clear);
462            self.current_dir = update_option(&self.current_dir, &other.current_dir);
463            self.entry_point = update_option(&self.entry_point, &other.entry_point);
464            self.exit_with = update_option(&self.exit_with, &other.exit_with);
465
466            self.callgrind_args
467                .extend_ignore_flag(other.callgrind_args.0.iter());
468
469            self.valgrind_args
470                .extend_ignore_flag(other.valgrind_args.0.iter());
471
472            self.envs.extend_from_slice(&other.envs);
473            self.flamegraph_config =
474                update_option(&self.flamegraph_config, &other.flamegraph_config);
475            self.regression_config =
476                update_option(&self.regression_config, &other.regression_config);
477            if let Some(other_tools) = &other.tools_override {
478                self.tools = other_tools.clone();
479            } else if !other.tools.is_empty() {
480                self.tools.update_from_other(&other.tools);
481            } else {
482                // do nothing
483            }
484
485            self.sandbox = update_option(&self.sandbox, &other.sandbox);
486            self.setup_parallel = update_option(&self.setup_parallel, &other.setup_parallel);
487            self.output_format = update_option(&self.output_format, &other.output_format);
488        }
489        self
490    }
491
492    pub fn resolve_envs(&self) -> Vec<(OsString, OsString)> {
493        self.envs
494            .iter()
495            .filter_map(|(key, value)| match value {
496                Some(value) => Some((key.clone(), value.clone())),
497                None => std::env::var_os(key).map(|value| (key.clone(), value)),
498            })
499            .collect()
500    }
501
502    pub fn collect_envs(&self) -> Vec<(OsString, OsString)> {
503        self.envs
504            .iter()
505            .filter_map(|(key, option)| option.as_ref().map(|value| (key.clone(), value.clone())))
506            .collect()
507    }
508}
509
510impl Default for DelayKind {
511    fn default() -> Self {
512        Self::DurationElapse(Duration::from_secs(60))
513    }
514}
515
516impl Default for Direction {
517    fn default() -> Self {
518        Self::BottomToTop
519    }
520}
521
522impl Display for DhatMetricKind {
523    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524        match self {
525            DhatMetricKind::TotalBytes => f.write_str("Total bytes"),
526            DhatMetricKind::TotalBlocks => f.write_str("Total blocks"),
527            DhatMetricKind::AtTGmaxBytes => f.write_str("At t-gmax bytes"),
528            DhatMetricKind::AtTGmaxBlocks => f.write_str("At t-gmax blocks"),
529            DhatMetricKind::AtTEndBytes => f.write_str("At t-end bytes"),
530            DhatMetricKind::AtTEndBlocks => f.write_str("At t-end blocks"),
531            DhatMetricKind::ReadsBytes => f.write_str("Reads bytes"),
532            DhatMetricKind::WritesBytes => f.write_str("Writes bytes"),
533            DhatMetricKind::TotalLifetimes => f.write_str("Total lifetimes"),
534            DhatMetricKind::MaximumBytes => f.write_str("Maximum bytes"),
535            DhatMetricKind::MaximumBlocks => f.write_str("Maximum blocks"),
536        }
537    }
538}
539
540#[cfg(feature = "runner")]
541impl Summarize for DhatMetricKind {}
542
543impl<T> From<T> for EntryPoint
544where
545    T: Into<String>,
546{
547    fn from(value: T) -> Self {
548        EntryPoint::Custom(value.into())
549    }
550}
551
552#[cfg(feature = "runner")]
553impl Summarize for ErrorMetricKind {}
554
555impl Display for ErrorMetricKind {
556    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557        match self {
558            ErrorMetricKind::Errors => f.write_str("Errors"),
559            ErrorMetricKind::Contexts => f.write_str("Contexts"),
560            ErrorMetricKind::SuppressedErrors => f.write_str("Suppressed Errors"),
561            ErrorMetricKind::SuppressedContexts => f.write_str("Suppressed Contexts"),
562        }
563    }
564}
565
566impl EventKind {
567    /// Return true if this `EventKind` is a derived event
568    ///
569    /// Derived events are calculated from Callgrind's native event types. See also
570    /// [`crate::runner::callgrind::model::Metrics::make_summary`]. Currently all derived events
571    /// are:
572    ///
573    /// * [`EventKind::L1hits`]
574    /// * [`EventKind::LLhits`]
575    /// * [`EventKind::RamHits`]
576    /// * [`EventKind::TotalRW`]
577    /// * [`EventKind::EstimatedCycles`]
578    pub fn is_derived(&self) -> bool {
579        matches!(
580            self,
581            EventKind::L1hits
582                | EventKind::LLhits
583                | EventKind::RamHits
584                | EventKind::TotalRW
585                | EventKind::EstimatedCycles
586        )
587    }
588
589    pub fn from_str_ignore_case(value: &str) -> Option<Self> {
590        match value.to_lowercase().as_str() {
591            "ir" => Some(Self::Ir),
592            "dr" => Some(Self::Dr),
593            "dw" => Some(Self::Dw),
594            "i1mr" => Some(Self::I1mr),
595            "ilmr" => Some(Self::ILmr),
596            "d1mr" => Some(Self::D1mr),
597            "dlmr" => Some(Self::DLmr),
598            "d1mw" => Some(Self::D1mw),
599            "dlmw" => Some(Self::DLmw),
600            "syscount" => Some(Self::SysCount),
601            "systime" => Some(Self::SysTime),
602            "syscputime" => Some(Self::SysCpuTime),
603            "ge" => Some(Self::Ge),
604            "bc" => Some(Self::Bc),
605            "bcm" => Some(Self::Bcm),
606            "bi" => Some(Self::Bi),
607            "bim" => Some(Self::Bim),
608            "ildmr" => Some(Self::ILdmr),
609            "dldmr" => Some(Self::DLdmr),
610            "dldmw" => Some(Self::DLdmw),
611            "accost1" => Some(Self::AcCost1),
612            "accost2" => Some(Self::AcCost2),
613            "sploss1" => Some(Self::SpLoss1),
614            "sploss2" => Some(Self::SpLoss2),
615            "l1hits" => Some(Self::L1hits),
616            "llhits" => Some(Self::LLhits),
617            "ramhits" => Some(Self::RamHits),
618            "totalrw" => Some(Self::TotalRW),
619            "estimatedcycles" => Some(Self::EstimatedCycles),
620            _ => None,
621        }
622    }
623
624    pub fn to_name(&self) -> String {
625        format!("{:?}", *self)
626    }
627}
628
629impl Display for EventKind {
630    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631        match self {
632            EventKind::Ir => f.write_str("Instructions"),
633            EventKind::L1hits => f.write_str("L1 Hits"),
634            EventKind::LLhits => f.write_str("L2 Hits"),
635            EventKind::RamHits => f.write_str("RAM Hits"),
636            EventKind::TotalRW => f.write_str("Total read+write"),
637            EventKind::EstimatedCycles => f.write_str("Estimated Cycles"),
638            _ => f.write_fmt(format_args!("{self:?}")),
639        }
640    }
641}
642
643impl<T> From<T> for EventKind
644where
645    T: AsRef<str>,
646{
647    fn from(value: T) -> Self {
648        match value.as_ref() {
649            "Ir" => Self::Ir,
650            "Dr" => Self::Dr,
651            "Dw" => Self::Dw,
652            "I1mr" => Self::I1mr,
653            "ILmr" => Self::ILmr,
654            "D1mr" => Self::D1mr,
655            "DLmr" => Self::DLmr,
656            "D1mw" => Self::D1mw,
657            "DLmw" => Self::DLmw,
658            "sysCount" => Self::SysCount,
659            "sysTime" => Self::SysTime,
660            "sysCpuTime" => Self::SysCpuTime,
661            "Ge" => Self::Ge,
662            "Bc" => Self::Bc,
663            "Bcm" => Self::Bcm,
664            "Bi" => Self::Bi,
665            "Bim" => Self::Bim,
666            "ILdmr" => Self::ILdmr,
667            "DLdmr" => Self::DLdmr,
668            "DLdmw" => Self::DLdmw,
669            "AcCost1" => Self::AcCost1,
670            "AcCost2" => Self::AcCost2,
671            "SpLoss1" => Self::SpLoss1,
672            "SpLoss2" => Self::SpLoss2,
673            "L1hits" => Self::L1hits,
674            "LLhits" => Self::LLhits,
675            "RamHits" => Self::RamHits,
676            "TotalRW" => Self::TotalRW,
677            "EstimatedCycles" => Self::EstimatedCycles,
678            unknown => panic!("Unknown event type: {unknown}"),
679        }
680    }
681}
682
683impl LibraryBenchmarkConfig {
684    pub fn update_from_all<'a, T>(mut self, others: T) -> Self
685    where
686        T: IntoIterator<Item = Option<&'a Self>>,
687    {
688        for other in others.into_iter().flatten() {
689            self.env_clear = update_option(&self.env_clear, &other.env_clear);
690
691            self.callgrind_args
692                .extend_ignore_flag(other.callgrind_args.0.iter());
693            self.valgrind_args
694                .extend_ignore_flag(other.valgrind_args.0.iter());
695
696            self.envs.extend_from_slice(&other.envs);
697            self.flamegraph_config =
698                update_option(&self.flamegraph_config, &other.flamegraph_config);
699            self.regression_config =
700                update_option(&self.regression_config, &other.regression_config);
701            if let Some(other_tools) = &other.tools_override {
702                self.tools = other_tools.clone();
703            } else if !other.tools.is_empty() {
704                self.tools.update_from_other(&other.tools);
705            } else {
706                // do nothing
707            }
708
709            self.entry_point = update_option(&self.entry_point, &other.entry_point);
710            self.output_format = update_option(&self.output_format, &other.output_format);
711        }
712        self
713    }
714
715    pub fn resolve_envs(&self) -> Vec<(OsString, OsString)> {
716        self.envs
717            .iter()
718            .filter_map(|(key, value)| match value {
719                Some(value) => Some((key.clone(), value.clone())),
720                None => std::env::var_os(key).map(|value| (key.clone(), value)),
721            })
722            .collect()
723    }
724
725    pub fn collect_envs(&self) -> Vec<(OsString, OsString)> {
726        self.envs
727            .iter()
728            .filter_map(|(key, option)| option.as_ref().map(|value| (key.clone(), value.clone())))
729            .collect()
730    }
731}
732
733impl RawArgs {
734    pub fn new(args: Vec<String>) -> Self {
735        Self(args)
736    }
737
738    pub fn extend_ignore_flag<I, T>(&mut self, args: T)
739    where
740        I: AsRef<str>,
741        T: IntoIterator<Item = I>,
742    {
743        self.0.extend(
744            args.into_iter()
745                .filter(|s| !s.as_ref().is_empty())
746                .map(|s| {
747                    let string = s.as_ref();
748                    if string.starts_with('-') {
749                        string.to_owned()
750                    } else {
751                        format!("--{string}")
752                    }
753                }),
754        );
755    }
756
757    pub fn from_command_line_args(args: Vec<String>) -> Self {
758        let mut this = Self(Vec::default());
759        if !args.is_empty() {
760            let mut iter = args.into_iter();
761            // This unwrap is safe. We just checked that `args` is not empty.
762            let mut last = iter.next().unwrap();
763            for elem in iter {
764                this.0.push(last);
765                last = elem;
766            }
767            if last.as_str() != "--bench" {
768                this.0.push(last);
769            }
770        }
771        this
772    }
773
774    pub fn is_empty(&self) -> bool {
775        self.0.is_empty()
776    }
777}
778
779impl<I> FromIterator<I> for RawArgs
780where
781    I: AsRef<str>,
782{
783    fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
784        let mut this = Self::default();
785        this.extend_ignore_flag(iter);
786        this
787    }
788}
789
790impl Stdin {
791    #[cfg(feature = "runner")]
792    pub(crate) fn apply(
793        &self,
794        command: &mut StdCommand,
795        stream: Stream,
796        child: Option<&mut Child>,
797    ) -> Result<(), String> {
798        match (self, child) {
799            (Self::Setup(Pipe::Stdout), Some(child)) => {
800                command.stdin(
801                    child
802                        .stdout
803                        .take()
804                        .ok_or_else(|| "Error piping setup stdout".to_owned())?,
805                );
806                Ok(())
807            }
808            (Self::Setup(Pipe::Stderr), Some(child)) => {
809                command.stdin(
810                    child
811                        .stderr
812                        .take()
813                        .ok_or_else(|| "Error piping setup stderr".to_owned())?,
814                );
815                Ok(())
816            }
817            (Self::Setup(_) | Stdin::Pipe, _) => Stdio::Pipe.apply(command, stream),
818            (Self::Inherit, _) => Stdio::Inherit.apply(command, stream),
819            (Self::Null, _) => Stdio::Null.apply(command, stream),
820            (Self::File(path), _) => Stdio::File(path.clone()).apply(command, stream),
821        }
822    }
823}
824
825impl From<Stdio> for Stdin {
826    fn from(value: Stdio) -> Self {
827        match value {
828            Stdio::Inherit => Stdin::Inherit,
829            Stdio::Null => Stdin::Null,
830            Stdio::File(file) => Stdin::File(file),
831            Stdio::Pipe => Stdin::Pipe,
832        }
833    }
834}
835
836impl From<PathBuf> for Stdin {
837    fn from(value: PathBuf) -> Self {
838        Self::File(value)
839    }
840}
841
842impl From<&PathBuf> for Stdin {
843    fn from(value: &PathBuf) -> Self {
844        Self::File(value.to_owned())
845    }
846}
847
848impl From<&Path> for Stdin {
849    fn from(value: &Path) -> Self {
850        Self::File(value.to_path_buf())
851    }
852}
853
854impl Stdio {
855    #[cfg(feature = "runner")]
856    pub(crate) fn apply(&self, command: &mut StdCommand, stream: Stream) -> Result<(), String> {
857        let stdio = match self {
858            Stdio::Pipe => StdStdio::piped(),
859            Stdio::Inherit => StdStdio::inherit(),
860            Stdio::Null => StdStdio::null(),
861            Stdio::File(path) => match stream {
862                Stream::Stdin => StdStdio::from(File::open(path).map_err(|error| {
863                    format!(
864                        "Failed to open file '{}' in read mode for {stream}: {error}",
865                        path.display()
866                    )
867                })?),
868                Stream::Stdout | Stream::Stderr => {
869                    StdStdio::from(File::create(path).map_err(|error| {
870                        format!(
871                            "Failed to create file '{}' for {stream}: {error}",
872                            path.display()
873                        )
874                    })?)
875                }
876            },
877        };
878
879        match stream {
880            Stream::Stdin => command.stdin(stdio),
881            Stream::Stdout => command.stdout(stdio),
882            Stream::Stderr => command.stderr(stdio),
883        };
884
885        Ok(())
886    }
887
888    #[cfg(feature = "runner")]
889    pub(crate) fn is_pipe(&self) -> bool {
890        match self {
891            Stdio::Inherit => false,
892            Stdio::Null | Stdio::File(_) | Stdio::Pipe => true,
893        }
894    }
895}
896
897impl From<PathBuf> for Stdio {
898    fn from(value: PathBuf) -> Self {
899        Self::File(value)
900    }
901}
902
903impl From<&PathBuf> for Stdio {
904    fn from(value: &PathBuf) -> Self {
905        Self::File(value.to_owned())
906    }
907}
908
909impl From<&Path> for Stdio {
910    fn from(value: &Path) -> Self {
911        Self::File(value.to_path_buf())
912    }
913}
914
915#[cfg(feature = "runner")]
916impl Display for Stream {
917    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
918        f.write_str(&format!("{self:?}").to_lowercase())
919    }
920}
921
922impl Tools {
923    /// Return true if `Tools` is empty
924    pub fn is_empty(&self) -> bool {
925        self.0.is_empty()
926    }
927
928    /// Update `Tools`
929    ///
930    /// Adds the [`Tool`] to `Tools`. If a [`Tool`] is already present, it will be removed.
931    ///
932    /// This method is inefficient (computes in worst case O(n^2)) but since `Tools` has a
933    /// manageable size with a maximum of 6 members we can spare us an `IndexSet` or similar and the
934    /// dependency on it in `iai-callgrind`.
935    pub fn update(&mut self, tool: Tool) {
936        if let Some(pos) = self.0.iter().position(|t| t.kind == tool.kind) {
937            self.0.remove(pos);
938        }
939        self.0.push(tool);
940    }
941
942    /// Update `Tools` with all [`Tool`]s from an iterator
943    pub fn update_all<T>(&mut self, tools: T)
944    where
945        T: IntoIterator<Item = Tool>,
946    {
947        for tool in tools {
948            self.update(tool);
949        }
950    }
951
952    /// Update `Tools` with another `Tools`
953    pub fn update_from_other(&mut self, tools: &Tools) {
954        self.update_all(tools.0.iter().cloned());
955    }
956}
957
958pub fn update_option<T: Clone>(first: &Option<T>, other: &Option<T>) -> Option<T> {
959    other.clone().or_else(|| first.clone())
960}
961
962#[cfg(test)]
963mod tests {
964    use pretty_assertions::assert_eq;
965    use rstest::rstest;
966
967    use super::*;
968
969    #[test]
970    fn test_library_benchmark_config_update_from_all_when_default() {
971        assert_eq!(
972            LibraryBenchmarkConfig::default()
973                .update_from_all([Some(&LibraryBenchmarkConfig::default())]),
974            LibraryBenchmarkConfig::default()
975        );
976    }
977
978    #[test]
979    fn test_library_benchmark_config_update_from_all_when_no_tools_override() {
980        let base = LibraryBenchmarkConfig::default();
981        let other = LibraryBenchmarkConfig {
982            env_clear: Some(true),
983            callgrind_args: RawArgs(vec!["--just-testing=yes".to_owned()]),
984            valgrind_args: RawArgs(vec!["--valgrind-arg=yes".to_owned()]),
985            envs: vec![(OsString::from("MY_ENV"), Some(OsString::from("value")))],
986            flamegraph_config: Some(FlamegraphConfig::default()),
987            regression_config: Some(RegressionConfig::default()),
988            tools: Tools(vec![Tool {
989                kind: ValgrindTool::DHAT,
990                enable: None,
991                raw_args: RawArgs(vec![]),
992                show_log: None,
993            }]),
994            tools_override: None,
995            entry_point: None,
996            output_format: None,
997        };
998
999        assert_eq!(base.update_from_all([Some(&other.clone())]), other);
1000    }
1001
1002    #[test]
1003    fn test_library_benchmark_config_update_from_all_when_tools_override() {
1004        let base = LibraryBenchmarkConfig::default();
1005        let other = LibraryBenchmarkConfig {
1006            env_clear: Some(true),
1007            callgrind_args: RawArgs(vec!["--just-testing=yes".to_owned()]),
1008            valgrind_args: RawArgs(vec!["--valgrind-arg=yes".to_owned()]),
1009            envs: vec![(OsString::from("MY_ENV"), Some(OsString::from("value")))],
1010            flamegraph_config: Some(FlamegraphConfig::default()),
1011            regression_config: Some(RegressionConfig::default()),
1012            tools: Tools(vec![Tool {
1013                kind: ValgrindTool::DHAT,
1014                enable: None,
1015                raw_args: RawArgs(vec![]),
1016                show_log: None,
1017            }]),
1018            tools_override: Some(Tools(vec![])),
1019            entry_point: Some(EntryPoint::default()),
1020            output_format: Some(OutputFormat::default()),
1021        };
1022        let expected = LibraryBenchmarkConfig {
1023            tools: other.tools_override.as_ref().unwrap().clone(),
1024            tools_override: None,
1025            ..other.clone()
1026        };
1027
1028        assert_eq!(base.update_from_all([Some(&other)]), expected);
1029    }
1030
1031    #[rstest]
1032    #[case::env_clear(
1033        LibraryBenchmarkConfig {
1034            env_clear: Some(true),
1035            ..Default::default()
1036        }
1037    )]
1038    fn test_library_benchmark_config_update_from_all_truncate_description(
1039        #[case] config: LibraryBenchmarkConfig,
1040    ) {
1041        let actual = LibraryBenchmarkConfig::default().update_from_all([Some(&config)]);
1042        assert_eq!(actual, config);
1043    }
1044
1045    #[rstest]
1046    #[case::all_none(None, None, None)]
1047    #[case::some_and_none(Some(true), None, Some(true))]
1048    #[case::none_and_some(None, Some(true), Some(true))]
1049    #[case::some_and_some(Some(false), Some(true), Some(true))]
1050    #[case::some_and_some_value_does_not_matter(Some(true), Some(false), Some(false))]
1051    fn test_update_option(
1052        #[case] first: Option<bool>,
1053        #[case] other: Option<bool>,
1054        #[case] expected: Option<bool>,
1055    ) {
1056        assert_eq!(update_option(&first, &other), expected);
1057    }
1058
1059    #[rstest]
1060    #[case::empty(vec![], &[], vec![])]
1061    #[case::empty_base(vec![], &["--a=yes"], vec!["--a=yes"])]
1062    #[case::no_flags(vec![], &["a=yes"], vec!["--a=yes"])]
1063    #[case::already_exists_single(vec!["--a=yes"], &["--a=yes"], vec!["--a=yes","--a=yes"])]
1064    #[case::already_exists_when_multiple(vec!["--a=yes", "--b=yes"], &["--a=yes"], vec!["--a=yes", "--b=yes", "--a=yes"])]
1065    fn test_raw_args_extend_ignore_flags(
1066        #[case] base: Vec<&str>,
1067        #[case] data: &[&str],
1068        #[case] expected: Vec<&str>,
1069    ) {
1070        let mut base = RawArgs(base.iter().map(std::string::ToString::to_string).collect());
1071        base.extend_ignore_flag(data.iter().map(std::string::ToString::to_string));
1072
1073        assert_eq!(base.0.into_iter().collect::<Vec<String>>(), expected);
1074    }
1075}