Skip to main content

gungraun_runner/
api.rs

1//! Public API types for benchmark configuration and execution.
2//!
3//! This module defines the data structures that form the interface between the macro layer in
4//! `gungraun` and the benchmark runner. All types usable in `gungraun` are serializable and
5//! deserializable, enabling the benchmark harness to communicate the benchmark configuration to
6//! the runner through a binary encoding.
7//!
8//! # Architecture
9//!
10//! Gungraun follows a two-stage execution model:
11//!
12//! 1. **Compile time**: Procedural macros in `gungraun-macros` parse attribute annotations like
13//!    `#[library_benchmark]` and `#[binary_benchmark]`. The `gungraun` crate assembles these into a
14//!    benchmarking harness and serializes the configuration into a binary format using `bincode`.
15//!
16//! 2. **Runtime**: The runner (`gungraun-runner`) receives this serialized data via stdin,
17//!    deserializes it into the types defined here, and executes the benchmarks according to the
18//!    configuration.
19//!
20//! Library benchmarks create [`LibraryBenchmarkGroups`], and binary benchmarks create
21//! [`BinaryBenchmarkGroups`].
22//!
23//! # Data Contract
24//!
25//! The types in this module constitute a **data contract** between two independently compiled
26//! components: the macro crate and the runner crate. This contract has several implications:
27//!
28//! - **Version coupling**: The macro crate and runner must use compatible versions of these types.
29//!   The runner performs a version check at startup to ensure alignment.
30//! - **Serialization stability**: All types derive `Serialize` and `Deserialize` via serde. The
31//!   binary encoding must remain stable across compatible versions.
32//! - **Feature-gated visibility**: Some implementations are only needed by the runner and are gated
33//!   behind the `runner` feature. Users writing benchmarks do not need this feature enabled.
34//! - **Schema generation**: The `schema` feature enables deriving `JsonSchema` for the json summary
35//!   file validation.
36//!
37//! Because this module serves as a stable interface, users interact with some of these types
38//! indirectly through attribute macros rather than constructing them directly. The macro syntax in
39//! `gungraun` is the stable user-facing API; the types themselves are an implementation detail that
40//! may evolve.
41//!
42//! # Stability
43//!
44//! Changes to this API can be considered breaking if they affect how `gungraun` uses these types.
45//! Since this API facilitates communication between internal components, it does not follow semver.
46//! However, every notable change requires a version bump.
47
48#[cfg(feature = "runner")]
49macro_rules! impl_from_str_metric {
50    ($type:ty, $error_msg:literal, { $($alias:pat => $variant:ident),* $(,)? }) => {
51        impl FromStr for $type {
52            type Err = anyhow::Error;
53
54            fn from_str(string: &str) -> Result<Self, Self::Err> {
55                let lower = string.to_lowercase();
56                let value = match lower.as_str() {
57                    $($alias => Self::$variant,)*
58                    _ => return Err(anyhow!($error_msg, string)),
59                };
60                Ok(value)
61            }
62        }
63    };
64}
65
66#[cfg(feature = "runner")]
67macro_rules! impl_from_str_metric_groups {
68    (
69        $type:ty,
70        $inner_type:ty,
71        $inner_variant:ident,
72        $error_msg:literal,
73        { $($alias:pat => $variant:ident),* $(,)? }
74    ) => {
75        impl FromStr for $type {
76            type Err = anyhow::Error;
77
78            fn from_str(string: &str) -> Result<Self, Self::Err> {
79                let lower = string.to_lowercase();
80                match lower.as_str().strip_prefix('@') {
81                    Some(suffix) => match suffix {
82                        $($alias => Ok(Self::$variant),)*
83                        _ => Err(anyhow!($error_msg, string)),
84                    },
85                    None => <$inner_type>::from_str(string).map(Self::$inner_variant),
86                }
87            }
88        }
89    };
90}
91
92#[cfg(feature = "runner")]
93use std::borrow::Cow;
94#[cfg(feature = "runner")]
95use std::collections::HashMap;
96use std::ffi::OsString;
97use std::fmt::Display;
98#[cfg(feature = "runner")]
99use std::fs::File;
100use std::net::SocketAddr;
101use std::path::{Path, PathBuf};
102#[cfg(feature = "runner")]
103use std::process::{Child, Command as StdCommand, Stdio as StdStdio};
104#[cfg(feature = "runner")]
105use std::str::FromStr;
106use std::time::Duration;
107
108#[cfg(feature = "runner")]
109use anyhow::anyhow;
110#[cfg(feature = "runner")]
111use indexmap::IndexSet;
112#[cfg(feature = "runner")]
113use indexmap::indexset;
114#[cfg(feature = "schema")]
115use schemars::JsonSchema;
116use serde::{Deserialize, Serialize};
117#[cfg(feature = "runner")]
118use strum::{EnumIter, IntoEnumIterator};
119
120#[cfg(feature = "runner")]
121use crate::runner;
122#[cfg(feature = "runner")]
123use crate::runner::metrics::Summarize;
124#[cfg(feature = "runner")]
125use crate::runner::metrics::TypeChecker;
126#[cfg(feature = "runner")]
127use crate::util;
128
129/// All metrics which cachegrind produces and additionally some derived events
130///
131/// Depending on the options passed to Cachegrind, these are the events that Cachegrind can produce.
132/// See the [Cachegrind
133/// documentation](https://valgrind.org/docs/manual/cg-manual.html#cg-manual.cgopts) for details.
134#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
135#[cfg_attr(feature = "schema", derive(JsonSchema))]
136#[cfg_attr(feature = "runner", derive(EnumIter))]
137pub enum CachegrindMetric {
138    /// The default event. I cache reads (which equals the number of instructions executed)
139    Ir,
140    /// D Cache reads (which equals the number of memory reads) (--cache-sim=yes)
141    Dr,
142    /// D Cache writes (which equals the number of memory writes) (--cache-sim=yes)
143    Dw,
144    /// I1 cache read misses (--cache-sim=yes)
145    I1mr,
146    /// D1 cache read misses (--cache-sim=yes)
147    D1mr,
148    /// D1 cache write misses (--cache-sim=yes)
149    D1mw,
150    /// LL cache instruction read misses (--cache-sim=yes)
151    ILmr,
152    /// LL cache data read misses (--cache-sim=yes)
153    DLmr,
154    /// LL cache data write misses (--cache-sim=yes)
155    DLmw,
156    /// I1 cache miss rate (--cache-sim=yes)
157    I1MissRate,
158    /// LL/L2 instructions cache miss rate (--cache-sim=yes)
159    LLiMissRate,
160    /// D1 cache miss rate (--cache-sim=yes)
161    D1MissRate,
162    /// LL/L2 data cache miss rate (--cache-sim=yes)
163    LLdMissRate,
164    /// LL/L2 cache miss rate (--cache-sim=yes)
165    LLMissRate,
166    /// Derived event showing the L1 hits (--cache-sim=yes)
167    L1hits,
168    /// Derived event showing the LL hits (--cache-sim=yes)
169    LLhits,
170    /// Derived event showing the RAM hits (--cache-sim=yes)
171    RamHits,
172    /// L1 cache hit rate (--cache-sim=yes)
173    L1HitRate,
174    /// LL/L2 cache hit rate (--cache-sim=yes)
175    LLHitRate,
176    /// RAM hit rate (--cache-sim=yes)
177    RamHitRate,
178    /// Derived event showing the total amount of cache reads and writes (--cache-sim=yes)
179    TotalRW,
180    /// Derived event showing estimated CPU cycles (--cache-sim=yes)
181    EstimatedCycles,
182    /// Conditional branches executed (--branch-sim=yes)
183    Bc,
184    /// Conditional branches mispredicted (--branch-sim=yes)
185    Bcm,
186    /// Indirect branches executed (--branch-sim=yes)
187    Bi,
188    /// Indirect branches mispredicted (--branch-sim=yes)
189    Bim,
190}
191
192/// A collection of groups of [`CachegrindMetric`]s
193///
194/// The members of each group are fully documented in the docs of each variant of this enum
195#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
196#[non_exhaustive]
197pub enum CachegrindMetrics {
198    /// The default group contains all metrics except the [`CachegrindMetrics::CacheMisses`],
199    /// [`CachegrindMetrics::CacheMissRates`], [`CachegrindMetrics::CacheHitRates`] and
200    /// [`EventKind::Dr`], [`EventKind::Dw`]. More specifically, the following event kinds and
201    /// groups in this order:
202    ///
203    /// ```rust
204    /// # pub mod gungraun {
205    /// # pub use gungraun_runner::api::{CachegrindMetrics, CachegrindMetric};
206    /// # }
207    /// use gungraun::{CachegrindMetric, CachegrindMetrics};
208    ///
209    /// let metrics: Vec<CachegrindMetrics> = vec![
210    ///     CachegrindMetric::Ir.into(),
211    ///     CachegrindMetrics::CacheHits,
212    ///     CachegrindMetric::TotalRW.into(),
213    ///     CachegrindMetric::EstimatedCycles.into(),
214    ///     CachegrindMetrics::BranchSim,
215    /// ];
216    /// ```
217    #[default]
218    Default,
219
220    /// The `CacheMisses` produced by `--cache-sim=yes` contain the following [`CachegrindMetric`]s
221    /// in this order:
222    ///
223    /// ```rust
224    /// # pub mod gungraun {
225    /// # pub use gungraun_runner::api::{CachegrindMetric, CachegrindMetrics};
226    /// # }
227    /// use gungraun::{CachegrindMetric, CachegrindMetrics};
228    ///
229    /// let metrics: Vec<CachegrindMetrics> = vec![
230    ///     CachegrindMetric::I1mr.into(),
231    ///     CachegrindMetric::D1mr.into(),
232    ///     CachegrindMetric::D1mw.into(),
233    ///     CachegrindMetric::ILmr.into(),
234    ///     CachegrindMetric::DLmr.into(),
235    ///     CachegrindMetric::DLmw.into(),
236    /// ];
237    /// ```
238    CacheMisses,
239
240    /// The cache miss rates calculated from the [`CallgrindMetrics::CacheMisses`] produced by
241    /// `--cache-sim`:
242    ///
243    /// ```rust
244    /// # pub mod gungraun {
245    /// # pub use gungraun_runner::api::{CachegrindMetric, CachegrindMetrics};
246    /// # }
247    /// use gungraun::{CachegrindMetric, CachegrindMetrics};
248    ///
249    /// let metrics: Vec<CachegrindMetrics> = vec![
250    ///     CachegrindMetric::I1MissRate.into(),
251    ///     CachegrindMetric::LLiMissRate.into(),
252    ///     CachegrindMetric::D1MissRate.into(),
253    ///     CachegrindMetric::LLdMissRate.into(),
254    ///     CachegrindMetric::LLMissRate.into(),
255    /// ];
256    /// ```
257    CacheMissRates,
258
259    /// `CacheHits` are gungraun specific and calculated from the metrics produced by
260    /// `--cache-sim=yes` in this order:
261    ///
262    /// ```
263    /// # pub mod gungraun {
264    /// # pub use gungraun_runner::api::{CachegrindMetric, CachegrindMetrics};
265    /// # }
266    /// use gungraun::{CachegrindMetric, CachegrindMetrics};
267    ///
268    /// let metrics: Vec<CachegrindMetrics> = vec![
269    ///     CachegrindMetric::L1hits.into(),
270    ///     CachegrindMetric::LLhits.into(),
271    ///     CachegrindMetric::RamHits.into(),
272    /// ];
273    /// ```
274    CacheHits,
275
276    /// The cache hit rates calculated from the [`CachegrindMetrics::CacheHits`]:
277    ///
278    /// ```
279    /// # pub mod gungraun {
280    /// # pub use gungraun_runner::api::{CachegrindMetric, CachegrindMetrics};
281    /// # }
282    /// use gungraun::{CachegrindMetric, CachegrindMetrics};
283    ///
284    /// let metrics: Vec<CachegrindMetrics> = vec![
285    ///     CachegrindMetric::L1HitRate.into(),
286    ///     CachegrindMetric::LLHitRate.into(),
287    ///     CachegrindMetric::RamHitRate.into(),
288    /// ];
289    /// ```
290    CacheHitRates,
291
292    /// All metrics produced by `--cache-sim=yes` including the gungraun specific metrics
293    /// [`CachegrindMetric::L1hits`], [`CachegrindMetric::LLhits`], [`CachegrindMetric::RamHits`],
294    /// [`CachegrindMetric::TotalRW`], [`CachegrindMetric::EstimatedCycles`],
295    /// [`CachegrindMetrics::CacheMissRates`] and [`CachegrindMetrics::CacheHitRates`] in this
296    /// order:
297    ///
298    /// ```rust
299    /// # pub mod gungraun {
300    /// # pub use gungraun_runner::api::{CachegrindMetric, CachegrindMetrics};
301    /// # }
302    /// use gungraun::{CachegrindMetric, CachegrindMetrics};
303    ///
304    /// let metrics: Vec<CachegrindMetrics> = vec![
305    ///     CachegrindMetric::Dr.into(),
306    ///     CachegrindMetric::Dw.into(),
307    ///     CachegrindMetrics::CacheMisses,
308    ///     CachegrindMetrics::CacheMissRates,
309    ///     CachegrindMetrics::CacheHits,
310    ///     CachegrindMetrics::CacheHitRates,
311    ///     CachegrindMetric::TotalRW.into(),
312    ///     CachegrindMetric::EstimatedCycles.into(),
313    /// ];
314    /// ```
315    CacheSim,
316
317    /// The metrics produced by `--branch-sim=yes` in this order:
318    ///
319    /// ```rust
320    /// # pub mod gungraun {
321    /// # pub use gungraun_runner::api::{CachegrindMetric, CachegrindMetrics};
322    /// # }
323    /// use gungraun::{CachegrindMetric, CachegrindMetrics};
324    ///
325    /// let metrics: Vec<CachegrindMetrics> = vec![
326    ///     CachegrindMetric::Bc.into(),
327    ///     CachegrindMetric::Bcm.into(),
328    ///     CachegrindMetric::Bi.into(),
329    ///     CachegrindMetric::Bim.into(),
330    /// ];
331    /// ```
332    BranchSim,
333
334    /// All possible [`CachegrindMetric`]s in this order:
335    ///
336    /// ```rust
337    /// # pub mod gungraun {
338    /// # pub use gungraun_runner::api::{CachegrindMetric, CachegrindMetrics};
339    /// # }
340    /// use gungraun::{CachegrindMetric, CachegrindMetrics};
341    ///
342    /// let metrics: Vec<CachegrindMetrics> = vec![
343    ///     CachegrindMetric::Ir.into(),
344    ///     CachegrindMetrics::CacheSim,
345    ///     CachegrindMetrics::BranchSim,
346    /// ];
347    /// ```
348    All,
349
350    /// Selection of no [`CachegrindMetric`] at all
351    None,
352
353    /// Specify a single [`CachegrindMetric`].
354    ///
355    /// Note that [`CachegrindMetric`] implements the necessary traits to convert to the
356    /// `CachegrindMetrics::SingleEvent` variant.
357    ///
358    /// # Examples
359    ///
360    /// ```rust
361    /// # pub mod gungraun {
362    /// # pub use gungraun_runner::api::{CachegrindMetric, CachegrindMetrics};
363    /// # }
364    /// use gungraun::{CachegrindMetric, CachegrindMetrics};
365    ///
366    /// assert_eq!(
367    ///     CachegrindMetrics::SingleEvent(CachegrindMetric::Ir),
368    ///     CachegrindMetric::Ir.into()
369    /// );
370    /// ```
371    SingleEvent(CachegrindMetric),
372}
373
374/// A collection of groups of [`EventKind`]s
375///
376/// `Callgrind` supports a large amount of metrics and their collection can be enabled with various
377/// command-line flags. [`CallgrindMetrics`] groups these metrics to make it less cumbersome to
378/// specify multiple [`EventKind`]s at once if necessary.
379#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord)]
380#[non_exhaustive]
381pub enum CallgrindMetrics {
382    /// The default group contains all event kinds except the [`CallgrindMetrics::CacheMisses`],
383    /// [`CallgrindMetrics::CacheMissRates`], [`CallgrindMetrics::CacheHitRates`] and
384    /// [`EventKind::Dr`], [`EventKind::Dw`]. More specifically, the following event kinds and
385    /// groups in this order:
386    ///
387    /// ```rust
388    /// # pub mod gungraun {
389    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
390    /// # }
391    /// use gungraun::{CallgrindMetrics, EventKind};
392    ///
393    /// let metrics: Vec<CallgrindMetrics> = vec![
394    ///     EventKind::Ir.into(),
395    ///     CallgrindMetrics::CacheHits,
396    ///     EventKind::TotalRW.into(),
397    ///     EventKind::EstimatedCycles.into(),
398    ///     CallgrindMetrics::SystemCalls,
399    ///     EventKind::Ge.into(),
400    ///     CallgrindMetrics::BranchSim,
401    ///     CallgrindMetrics::WriteBackBehaviour,
402    ///     CallgrindMetrics::CacheUse,
403    /// ];
404    /// ```
405    #[default]
406    Default,
407
408    /// The `CacheMisses` produced by `--cache-sim=yes` contain the following [`EventKind`]s in
409    /// this order:
410    ///
411    /// ```rust
412    /// # pub mod gungraun {
413    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
414    /// # }
415    /// use gungraun::{CallgrindMetrics, EventKind};
416    ///
417    /// let metrics: Vec<CallgrindMetrics> = vec![
418    ///     EventKind::I1mr.into(),
419    ///     EventKind::D1mr.into(),
420    ///     EventKind::D1mw.into(),
421    ///     EventKind::ILmr.into(),
422    ///     EventKind::DLmr.into(),
423    ///     EventKind::DLmw.into(),
424    /// ];
425    /// ```
426    CacheMisses,
427
428    /// The cache miss rates calculated from the [`CallgrindMetrics::CacheMisses`] produced by
429    /// `--cache-sim`:
430    ///
431    /// ```rust
432    /// # pub mod gungraun {
433    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
434    /// # }
435    /// use gungraun::{CallgrindMetrics, EventKind};
436    ///
437    /// let metrics: Vec<CallgrindMetrics> = vec![
438    ///     EventKind::I1MissRate.into(),
439    ///     EventKind::D1MissRate.into(),
440    ///     EventKind::LLiMissRate.into(),
441    ///     EventKind::LLdMissRate.into(),
442    ///     EventKind::LLMissRate.into(),
443    /// ];
444    /// ```
445    CacheMissRates,
446
447    /// `CacheHits` are gungraun specific and calculated from the metrics produced by
448    /// `--cache-sim=yes` in this order:
449    ///
450    /// ```
451    /// # pub mod gungraun {
452    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
453    /// # }
454    /// use gungraun::{CallgrindMetrics, EventKind};
455    ///
456    /// let metrics: Vec<CallgrindMetrics> = vec![
457    ///     EventKind::L1hits.into(),
458    ///     EventKind::LLhits.into(),
459    ///     EventKind::RamHits.into(),
460    /// ];
461    /// ```
462    CacheHits,
463
464    /// The cache hit rates calculated from the [`CallgrindMetrics::CacheHits`]:
465    ///
466    /// ```
467    /// # pub mod gungraun {
468    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
469    /// # }
470    /// use gungraun::{CallgrindMetrics, EventKind};
471    ///
472    /// let metrics: Vec<CallgrindMetrics> = vec![
473    ///     EventKind::L1HitRate.into(),
474    ///     EventKind::LLHitRate.into(),
475    ///     EventKind::RamHitRate.into(),
476    /// ];
477    /// ```
478    CacheHitRates,
479
480    /// All metrics produced by `--cache-sim=yes` including the gungraun specific metrics
481    /// [`EventKind::L1hits`], [`EventKind::LLhits`], [`EventKind::RamHits`],
482    /// [`EventKind::TotalRW`], [`EventKind::EstimatedCycles`] and miss/hit rates in this order:
483    ///
484    /// ```rust
485    /// # pub mod gungraun {
486    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
487    /// # }
488    /// use gungraun::{CallgrindMetrics, EventKind};
489    ///
490    /// let metrics: Vec<CallgrindMetrics> = vec![
491    ///     EventKind::Dr.into(),
492    ///     EventKind::Dw.into(),
493    ///     CallgrindMetrics::CacheMisses,
494    ///     CallgrindMetrics::CacheMissRates,
495    ///     CallgrindMetrics::CacheHits,
496    ///     EventKind::TotalRW.into(),
497    ///     CallgrindMetrics::CacheHitRates,
498    ///     EventKind::EstimatedCycles.into(),
499    /// ];
500    /// ```
501    CacheSim,
502
503    /// The metrics produced by `--cacheuse=yes` in this order:
504    ///
505    /// ```rust
506    /// # pub mod gungraun {
507    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
508    /// # }
509    /// use gungraun::{CallgrindMetrics, EventKind};
510    ///
511    /// let metrics: Vec<CallgrindMetrics> = vec![
512    ///     EventKind::AcCost1.into(),
513    ///     EventKind::AcCost2.into(),
514    ///     EventKind::SpLoss1.into(),
515    ///     EventKind::SpLoss2.into(),
516    /// ];
517    /// ```
518    CacheUse,
519
520    /// `SystemCalls` are events of the `--collect-systime=yes` option in this order:
521    ///
522    /// ```rust
523    /// # pub mod gungraun {
524    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
525    /// # }
526    /// use gungraun::{CallgrindMetrics, EventKind};
527    ///
528    /// let metrics: Vec<CallgrindMetrics> = vec![
529    ///     EventKind::SysCount.into(),
530    ///     EventKind::SysTime.into(),
531    ///     EventKind::SysCpuTime.into(),
532    /// ];
533    /// ```
534    SystemCalls,
535
536    /// The metrics produced by `--branch-sim=yes` in this order:
537    ///
538    /// ```rust
539    /// # pub mod gungraun {
540    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
541    /// # }
542    /// use gungraun::{CallgrindMetrics, EventKind};
543    ///
544    /// let metrics: Vec<CallgrindMetrics> = vec![
545    ///     EventKind::Bc.into(),
546    ///     EventKind::Bcm.into(),
547    ///     EventKind::Bi.into(),
548    ///     EventKind::Bim.into(),
549    /// ];
550    /// ```
551    BranchSim,
552
553    /// All metrics of `--simulate-wb=yes` in this order:
554    ///
555    /// ```rust
556    /// # pub mod gungraun {
557    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
558    /// # }
559    /// use gungraun::{CallgrindMetrics, EventKind};
560    ///
561    /// let metrics: Vec<CallgrindMetrics> = vec![
562    ///     EventKind::ILdmr.into(),
563    ///     EventKind::DLdmr.into(),
564    ///     EventKind::DLdmw.into(),
565    /// ];
566    /// ```
567    WriteBackBehaviour,
568
569    /// All possible [`EventKind`]s in this order:
570    ///
571    /// ```rust
572    /// # pub mod gungraun {
573    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
574    /// # }
575    /// use gungraun::{CallgrindMetrics, EventKind};
576    ///
577    /// let metrics: Vec<CallgrindMetrics> = vec![
578    ///     EventKind::Ir.into(),
579    ///     CallgrindMetrics::CacheSim,
580    ///     CallgrindMetrics::SystemCalls,
581    ///     EventKind::Ge.into(),
582    ///     CallgrindMetrics::BranchSim,
583    ///     CallgrindMetrics::WriteBackBehaviour,
584    ///     CallgrindMetrics::CacheUse,
585    /// ];
586    /// ```
587    All,
588
589    /// Selection of no [`EventKind`] at all
590    None,
591
592    /// Specify a single [`EventKind`].
593    ///
594    /// Note that [`EventKind`] implements the necessary traits to convert to the
595    /// `CallgrindMetrics::SingleEvent` variant which is shorter to write.
596    ///
597    /// # Examples
598    ///
599    /// ```rust
600    /// # pub mod gungraun {
601    /// # pub use gungraun_runner::api::{CallgrindMetrics, EventKind};
602    /// # }
603    /// use gungraun::{CallgrindMetrics, EventKind};
604    ///
605    /// assert_eq!(
606    ///     CallgrindMetrics::SingleEvent(EventKind::Ir),
607    ///     EventKind::Ir.into()
608    /// );
609    /// ```
610    SingleEvent(EventKind),
611}
612
613/// For internal use only: Used to differentiate between the `iter` and other `#[benches]` arguments
614#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
615pub enum CommandKind {
616    /// The default mode when `iter` was not used
617    Default(Box<Command>),
618    /// The mode when `iter` was used
619    Iter(Vec<Command>),
620}
621
622/// The kind of `Delay`
623#[non_exhaustive]
624#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
625pub enum DelayKind {
626    /// Delay the `Command` for a fixed [`Duration`]
627    DurationElapse(Duration),
628    /// Delay the `Command` until a successful tcp connection can be established
629    TcpConnect(SocketAddr),
630    /// Delay the `Command` until a successful udp response was received
631    UdpResponse(SocketAddr, Vec<u8>),
632    /// Delay the `Command` until the specified path exists
633    PathExists(PathBuf),
634}
635
636/// The metrics collected by DHAT
637#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
638#[cfg_attr(feature = "schema", derive(JsonSchema))]
639#[cfg_attr(feature = "runner", derive(EnumIter))]
640pub enum DhatMetric {
641    /// In ad-hoc mode, Total units measured over the entire execution
642    TotalUnits,
643    /// Total ad-hoc events over the entire execution
644    TotalEvents,
645    /// Total bytes allocated over the entire execution
646    TotalBytes,
647    /// Total heap blocks allocated over the entire execution
648    TotalBlocks,
649    /// The bytes alive at t-gmax, the time when the heap size reached its global maximum
650    AtTGmaxBytes,
651    /// The blocks alive at t-gmax
652    AtTGmaxBlocks,
653    /// The amount of bytes at the end of the execution.
654    ///
655    /// This is the amount of bytes which were not explicitly freed.
656    AtTEndBytes,
657    /// The amount of blocks at the end of the execution.
658    ///
659    /// This is the amount of heap blocks which were not explicitly freed.
660    AtTEndBlocks,
661    /// The amount of bytes read during the entire execution
662    ReadsBytes,
663    /// The amount of bytes written during the entire execution
664    WritesBytes,
665    /// The total lifetimes of all heap blocks allocated
666    TotalLifetimes,
667    /// The maximum amount of bytes
668    MaximumBytes,
669    /// The maximum amount of heap blocks
670    MaximumBlocks,
671}
672
673/// A collection of groups of [`DhatMetric`]s
674///
675/// The members of each group are fully documented in the docs of each variant of this enum
676#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
677pub enum DhatMetrics {
678    /// The default group in this order
679    ///
680    /// ```rust
681    /// # pub mod gungraun {
682    /// # pub use gungraun_runner::api::{DhatMetrics, DhatMetric};
683    /// # }
684    /// use gungraun::{DhatMetric, DhatMetrics};
685    ///
686    /// let metrics: Vec<DhatMetrics> = vec![
687    ///     DhatMetric::TotalUnits.into(),
688    ///     DhatMetric::TotalEvents.into(),
689    ///     DhatMetric::TotalBytes.into(),
690    ///     DhatMetric::TotalBlocks.into(),
691    ///     DhatMetric::AtTGmaxBytes.into(),
692    ///     DhatMetric::AtTGmaxBlocks.into(),
693    ///     DhatMetric::AtTEndBytes.into(),
694    ///     DhatMetric::AtTEndBlocks.into(),
695    ///     DhatMetric::ReadsBytes.into(),
696    ///     DhatMetric::WritesBytes.into(),
697    /// ];
698    /// ```
699    #[default]
700    Default,
701
702    /// All [`DhatMetric`]s in this order
703    ///
704    /// ```rust
705    /// # pub mod gungraun {
706    /// # pub use gungraun_runner::api::{DhatMetrics, DhatMetric};
707    /// # }
708    /// use gungraun::{DhatMetric, DhatMetrics};
709    ///
710    /// let metrics: Vec<DhatMetrics> = vec![
711    ///     DhatMetrics::Default,
712    ///     DhatMetric::TotalLifetimes.into(),
713    ///     DhatMetric::MaximumBytes.into(),
714    ///     DhatMetric::MaximumBlocks.into(),
715    /// ];
716    /// ```
717    All,
718
719    /// A single [`DhatMetric`]
720    ///
721    /// ```rust
722    /// # pub mod gungraun {
723    /// # pub use gungraun_runner::api::{DhatMetrics, DhatMetric};
724    /// # }
725    /// use gungraun::{DhatMetric, DhatMetrics};
726    ///
727    /// assert_eq!(
728    ///     DhatMetrics::SingleMetric(DhatMetric::TotalBytes),
729    ///     DhatMetric::TotalBytes.into()
730    /// );
731    /// ```
732    SingleMetric(DhatMetric),
733}
734
735/// The `Direction` in which the flamegraph should grow.
736///
737/// The default is `TopToBottom`.
738#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
739pub enum Direction {
740    /// Grow from top to bottom with the highest event costs at the top
741    TopToBottom,
742    /// Grow from bottom to top with the highest event costs at the bottom
743    #[default]
744    BottomToTop,
745}
746
747/// The `EntryPoint` of a benchmark
748#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
749pub enum EntryPoint {
750    /// Disable the entry point
751    None,
752    /// The default entry point is the benchmark function
753    #[default]
754    Default,
755    /// A custom entry point. The argument allows the same glob patterns as the
756    /// [`--toggle-collect`](https://valgrind.org/docs/manual/cl-manual.html#cl-manual.options)
757    /// argument of callgrind. These are the wildcards `*` (match any amount of arbitrary
758    /// characters) and `?` (match a single arbitrary character)
759    Custom(String),
760}
761
762/// The error metrics from a tool which reports errors
763///
764/// The tools which report only errors are `helgrind`, `drd` and `memcheck`. The order in which the
765/// variants are defined in this enum determines the order of the metrics in the benchmark terminal
766/// output.
767#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
768#[cfg_attr(feature = "schema", derive(JsonSchema))]
769#[cfg_attr(feature = "runner", derive(EnumIter))]
770pub enum ErrorMetric {
771    /// The amount of detected unsuppressed errors
772    Errors,
773    /// The amount of detected unsuppressed error contexts
774    Contexts,
775    /// The amount of suppressed errors
776    SuppressedErrors,
777    /// The amount of suppressed error contexts
778    SuppressedContexts,
779}
780
781/// All `EventKind`s callgrind produces and additionally some derived events
782///
783/// Depending on the options passed to Callgrind, these are the events that Callgrind can produce.
784/// See the [Callgrind
785/// documentation](https://valgrind.org/docs/manual/cl-manual.html#cl-manual.options) for details.
786#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
787#[cfg_attr(feature = "schema", derive(JsonSchema))]
788#[cfg_attr(feature = "runner", derive(EnumIter))]
789pub enum EventKind {
790    /// The default event. I cache reads (which equals the number of instructions executed)
791    Ir,
792    /// D Cache reads (which equals the number of memory reads) (--cache-sim=yes)
793    Dr,
794    /// D Cache writes (which equals the number of memory writes) (--cache-sim=yes)
795    Dw,
796    /// I1 cache read misses (--cache-sim=yes)
797    I1mr,
798    /// D1 cache read misses (--cache-sim=yes)
799    D1mr,
800    /// D1 cache write misses (--cache-sim=yes)
801    D1mw,
802    /// LL cache instruction read misses (--cache-sim=yes)
803    ILmr,
804    /// LL cache data read misses (--cache-sim=yes)
805    DLmr,
806    /// LL cache data write misses (--cache-sim=yes)
807    DLmw,
808    /// I1 cache miss rate (--cache-sim=yes)
809    I1MissRate,
810    /// LL/L2 instructions cache miss rate (--cache-sim=yes)
811    LLiMissRate,
812    /// D1 cache miss rate (--cache-sim=yes)
813    D1MissRate,
814    /// LL/L2 data cache miss rate (--cache-sim=yes)
815    LLdMissRate,
816    /// LL/L2 cache miss rate (--cache-sim=yes)
817    LLMissRate,
818    /// Derived event showing the L1 hits (--cache-sim=yes)
819    L1hits,
820    /// Derived event showing the LL hits (--cache-sim=yes)
821    LLhits,
822    /// Derived event showing the RAM hits (--cache-sim=yes)
823    RamHits,
824    /// L1 cache hit rate (--cache-sim=yes)
825    L1HitRate,
826    /// LL/L2 cache hit rate (--cache-sim=yes)
827    LLHitRate,
828    /// RAM hit rate (--cache-sim=yes)
829    RamHitRate,
830    /// Derived event showing the total amount of cache reads and writes (--cache-sim=yes)
831    TotalRW,
832    /// Derived event showing estimated CPU cycles (--cache-sim=yes)
833    EstimatedCycles,
834    /// The number of system calls done (--collect-systime=yes)
835    SysCount,
836    /// The elapsed time spent in system calls (--collect-systime=yes)
837    SysTime,
838    /// The cpu time spent during system calls (--collect-systime=nsec)
839    SysCpuTime,
840    /// The number of global bus events (--collect-bus=yes)
841    Ge,
842    /// Conditional branches executed (--branch-sim=yes)
843    Bc,
844    /// Conditional branches mispredicted (--branch-sim=yes)
845    Bcm,
846    /// Indirect branches executed (--branch-sim=yes)
847    Bi,
848    /// Indirect branches mispredicted (--branch-sim=yes)
849    Bim,
850    /// Dirty miss because of instruction read (--simulate-wb=yes)
851    ILdmr,
852    /// Dirty miss because of data read (--simulate-wb=yes)
853    DLdmr,
854    /// Dirty miss because of data write (--simulate-wb=yes)
855    DLdmw,
856    /// Counter showing bad temporal locality for L1 caches (--cachuse=yes)
857    AcCost1,
858    /// Counter showing bad temporal locality for LL caches (--cachuse=yes)
859    AcCost2,
860    /// Counter showing bad spatial locality for L1 caches (--cachuse=yes)
861    SpLoss1,
862    /// Counter showing bad spatial locality for LL caches (--cachuse=yes)
863    SpLoss2,
864}
865
866/// Set the expected exit status of a binary benchmark
867///
868/// By default, the benchmarked binary is expected to succeed, but if a benchmark is expected to
869/// fail, setting this option is required.
870///
871/// # Examples
872///
873/// ```rust,ignore
874/// use gungraun::prelude::*;
875/// use gungraun::ExitWith;
876///
877/// # fn main() {
878/// main!(
879///     config = BinaryBenchmarkConfig::default().exit_with(ExitWith::Code(1)),
880///     binary_benchmark_groups = my_group
881/// );
882/// # }
883/// ```
884#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
885pub enum ExitWith {
886    /// Exit with success is similar to `ExitCode(0)`
887    Success,
888    /// Exit with failure is similar to setting the `ExitCode` to something different from `0`
889    /// without having to rely on a specific exit code
890    Failure,
891    /// The exact `ExitCode` of the benchmark run
892    Code(i32),
893}
894
895/// The kind of `Flamegraph` which is going to be constructed
896#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
897pub enum FlamegraphKind {
898    /// The regular flamegraph for the new callgrind run
899    Regular,
900    /// A differential flamegraph showing the differences between the new and old callgrind run
901    Differential,
902    /// All flamegraph kinds that can be constructed (`Regular` and `Differential`). This
903    /// is the default.
904    All,
905    /// Do not produce any flamegraphs
906    None,
907}
908
909/// A `Limit` which can be either an integer or a float
910///
911/// Depending on the metric the type of the hard limit is a float or an integer. For example
912/// [`EventKind::Ir`] is an integer and [`EventKind::L1HitRate`] is a percentage and therefore a
913/// float.
914///
915/// The type of the metric can be seen in the terminal output of Gungraun: Floats always
916/// contain a `.` and integers do not.
917#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
918pub enum Limit {
919    /// An integer `Limit`. For example [`EventKind::Ir`]
920    Int(u64),
921    /// A float `Limit`. For example [`EventKind::L1HitRate`] or [`EventKind::I1MissRate`]
922    Float(f64),
923}
924
925/// Configure the `Stream` which should be used as pipe in [`Stdin::Setup`]
926///
927/// The default is [`Pipe::Stdout`]
928#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
929pub enum Pipe {
930    /// The `Stdout` default `Stream`
931    #[default]
932    Stdout,
933    /// The `Stderr` error `Stream`
934    Stderr,
935}
936
937/// Rewrite the output to match the configured [entry point][EntryPoint] and frame filters.
938#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
939pub enum SanitizeOutput {
940    /// Leave the output files unchanged.
941    No,
942    /// Like [`Yes`][SanitizeOutput::Yes], but back up the original file with an `.orig` extension.
943    KeepOrig,
944    /// Rewrite the output to match the configured [entry point][EntryPoint] and frame filters.
945    Yes,
946}
947
948/// This is a special `Stdio` for the stdin method of [`Command`]
949///
950/// Contains all the standard [`Stdio`] options and the [`Stdin::Setup`] option
951#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
952pub enum Stdin {
953    /// Using this in [`Command::stdin`] pipes the stream specified with [`Pipe`] of the `setup`
954    /// function into the `Stdin` of the [`Command`]. In this case the `setup` and [`Command`] are
955    /// executed in parallel instead of sequentially. See [`Command::stdin`] for more details.
956    Setup(Pipe),
957    #[default]
958    /// See [`Stdio::Inherit`]
959    Inherit,
960    /// See [`Stdio::Null`]
961    Null,
962    /// See [`Stdio::File`]
963    File(PathBuf),
964    /// See [`Stdio::Pipe`]
965    Pipe,
966}
967
968/// Configure the `Stdio` of `Stdin`, `Stdout` and `Stderr`
969///
970/// Describes what to do with a standard I/O stream for the [`Command`] when passed to the stdin,
971/// stdout, and stderr methods of [`Command`].
972#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
973pub enum Stdio {
974    /// The [`Command`]'s `Stream` inherits from the benchmark runner.
975    #[default]
976    Inherit,
977    /// This stream will be ignored. This is the equivalent of attaching the stream to `/dev/null`
978    Null,
979    /// Redirect the content of a file into this `Stream`. This is equivalent to a redirection in a
980    /// shell for example for the `Stdout` of `my-command`: `my-command > some_file`
981    File(PathBuf),
982    /// A new pipe should be arranged to connect the benchmark runner and the [`Command`]
983    Pipe,
984}
985
986/// We use this enum only internally in the benchmark runner
987#[cfg(feature = "runner")]
988#[derive(Debug, Clone, Copy, PartialEq, Eq)]
989pub enum Stream {
990    /// The standard input stream of a spawned process.
991    Stdin,
992    /// The standard error stream of a spawned process.
993    Stderr,
994    /// The standard output stream of a spawned process.
995    Stdout,
996}
997
998/// The tool specific flamegraph configuration
999#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1000pub enum ToolFlamegraphConfig {
1001    /// The callgrind configuration
1002    Callgrind(FlamegraphConfig),
1003    /// The option for tools which can't create flamegraphs
1004    None,
1005}
1006
1007/// The tool specific metrics to show in the terminal output
1008#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1009pub enum ToolOutputFormat {
1010    /// The Callgrind configuration
1011    Callgrind(Vec<CallgrindMetrics>),
1012    /// The Cachegrind configuration
1013    Cachegrind(Vec<CachegrindMetrics>),
1014    /// The DHAT configuration
1015    DHAT(Vec<DhatMetric>),
1016    /// The Memcheck configuration
1017    Memcheck(Vec<ErrorMetric>),
1018    /// The Helgrind configuration
1019    Helgrind(Vec<ErrorMetric>),
1020    /// The DRD configuration
1021    DRD(Vec<ErrorMetric>),
1022    /// If there is no configuration
1023    None,
1024}
1025
1026/// The tool specific regression check configuration
1027#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1028pub enum ToolRegressionConfig {
1029    /// The cachegrind configuration
1030    Cachegrind(CachegrindRegressionConfig),
1031    /// The callgrind configuration
1032    Callgrind(CallgrindRegressionConfig),
1033    /// The dhat configuration
1034    Dhat(DhatRegressionConfig),
1035    /// The option for tools which don't perform regression checks
1036    None,
1037}
1038
1039/// The valgrind tools which can be run
1040///
1041/// Note the default changes from `Callgrind` to `Cachegrind` if the `cachegrind` feature is
1042/// selected.
1043#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1044#[cfg_attr(feature = "schema", derive(JsonSchema))]
1045pub enum ValgrindTool {
1046    /// Callgrind: a call-graph generating cache and branch prediction profiler
1047    /// <https://valgrind.org/docs/manual/cl-manual.html>
1048    Callgrind,
1049    /// Cachegrind: a high-precision tracing profiler
1050    /// <https://valgrind.org/docs/manual/cg-manual.html>
1051    Cachegrind,
1052    /// DHAT: a dynamic heap analysis tool
1053    /// <https://valgrind.org/docs/manual/dh-manual.html>
1054    DHAT,
1055    /// Memcheck: a memory error detector
1056    /// <https://valgrind.org/docs/manual/mc-manual.html>
1057    Memcheck,
1058    /// Helgrind: a thread error detector
1059    /// <https://valgrind.org/docs/manual/hg-manual.html>
1060    Helgrind,
1061    /// DRD: a thread error detector
1062    /// <https://valgrind.org/docs/manual/drd-manual.html>
1063    DRD,
1064    /// Massif: a heap profiler
1065    /// <https://valgrind.org/docs/manual/ms-manual.html>
1066    Massif,
1067    /// BBV: an experimental basic block vector generation tool
1068    /// <https://valgrind.org/docs/manual/bbv-manual.html>
1069    BBV,
1070}
1071
1072/// The model for the `#[binary_benchmark]` attribute or the equivalent from the low level api
1073///
1074/// For internal use only
1075#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1076pub struct BinaryBenchmark {
1077    /// The extracted binary benchmarks
1078    pub benches: Vec<BinaryBenchmarkBench>,
1079    /// The configuration at `#[binary_benchmark]` level
1080    pub config: Option<BinaryBenchmarkConfig>,
1081}
1082
1083/// The model for the `#[bench]` attribute or the low level equivalent
1084///
1085/// For internal use only
1086#[derive(Debug, Clone, Serialize, Deserialize)]
1087pub struct BinaryBenchmarkBench {
1088    /// The arguments to the function
1089    pub args: Option<String>,
1090    /// The returned [`Command`]
1091    pub command: CommandKind,
1092    /// The configuration at `#[bench]` or `#[benches]` level
1093    pub config: Option<BinaryBenchmarkConfig>,
1094    /// The consts arguments for the benchmark function as single string
1095    pub consts_display: Option<String>,
1096    /// The name of the annotated function
1097    pub function_name: String,
1098    /// True if there is a `setup` function
1099    pub has_setup: bool,
1100    /// True if there is a `teardown` function
1101    pub has_teardown: bool,
1102    /// The `id` of the benchmark as in `#[bench::id]`
1103    pub id: Option<String>,
1104}
1105
1106/// The model for the configuration in binary benchmarks
1107///
1108/// This is the configuration which is built from the configuration of the UI and for internal use
1109/// only.
1110#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1111pub struct BinaryBenchmarkConfig {
1112    /// If some, set the working directory of the selected binary benchmark to this path
1113    pub current_dir: Option<PathBuf>,
1114    /// The valgrind tool to run instead of the default callgrind
1115    pub default_tool: Option<ValgrindTool>,
1116    /// True if the environment variables should be cleared
1117    pub env_clear: Option<bool>,
1118    /// The environment variables to set or pass through to the binary
1119    pub envs: Vec<(OsString, Option<OsString>)>,
1120    /// The [`ExitWith`] to set the expected exit code/signal of the benchmarked binary
1121    pub exit_with: Option<ExitWith>,
1122    /// The configuration of the output format
1123    pub output_format: Option<OutputFormat>,
1124    /// Run the benchmarked binary in a [`Sandbox`] or not
1125    pub sandbox: Option<Sandbox>,
1126    /// Run the `setup` function parallel to the benchmarked binary
1127    pub setup_parallel: Option<bool>,
1128    /// The valgrind tools to run in addition to the default tool
1129    pub tools: Tools,
1130    /// The tool override at this configuration level
1131    pub tools_override: Option<Tools>,
1132    /// The arguments to pass to all tools
1133    pub valgrind_args: RawToolArgs,
1134}
1135
1136/// The model for the `binary_benchmark_group` macro
1137#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1138pub struct BinaryBenchmarkGroup {
1139    /// The actual data and the benchmarks of this group
1140    pub binary_benchmarks: Vec<BinaryBenchmark>,
1141    /// If true compare the benchmarks in this group
1142    pub compare_by_id: Option<bool>,
1143    /// The configuration at this level
1144    pub config: Option<BinaryBenchmarkConfig>,
1145    /// True if there is a `setup` function
1146    pub has_setup: bool,
1147    /// True if there is a `teardown` function
1148    pub has_teardown: bool,
1149    /// The name or id of the `binary_benchmark_group!`
1150    pub id: String,
1151    /// The maximum amount of parallelism for this group (0 = no limit, 1 = serial, N >= 2 = limit
1152    /// to N)
1153    pub max_parallel: Option<usize>,
1154}
1155
1156/// The model for the main! macro
1157#[derive(Debug, Clone, Serialize, Deserialize)]
1158pub struct BinaryBenchmarkGroups {
1159    /// The command line arguments as we receive them from `cargo bench`
1160    pub command_line_args: Vec<String>,
1161    /// The configuration of this level
1162    pub config: BinaryBenchmarkConfig,
1163    /// The default tool changed by the `cachegrind` feature
1164    pub default_tool: ValgrindTool,
1165    /// All groups of this benchmark
1166    pub groups: Vec<BinaryBenchmarkGroup>,
1167    /// True if there is a `setup` function
1168    pub has_setup: bool,
1169    /// True if there is a `teardown` function
1170    pub has_teardown: bool,
1171}
1172
1173/// The model for the regression check configuration of Cachegrind
1174#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1175pub struct CachegrindRegressionConfig {
1176    /// True if the benchmarks should fail on the first occurrence of a regression
1177    pub fail_fast: Option<bool>,
1178    /// The hard limits
1179    pub hard_limits: Vec<(CachegrindMetrics, Limit)>,
1180    /// The soft limits
1181    pub soft_limits: Vec<(CachegrindMetrics, f64)>,
1182}
1183
1184/// The model for the regression check configuration of Callgrind
1185#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1186pub struct CallgrindRegressionConfig {
1187    /// True if the benchmarks should fail on the first occurrence of a regression
1188    pub fail_fast: Option<bool>,
1189    /// The hard limits
1190    pub hard_limits: Vec<(CallgrindMetrics, Limit)>,
1191    /// The soft limits
1192    pub soft_limits: Vec<(CallgrindMetrics, f64)>,
1193}
1194
1195/// The model for the command returned by the binary benchmark function
1196#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1197pub struct Command {
1198    /// The arguments for the executable
1199    pub args: Vec<OsString>,
1200    /// The configuration at this level
1201    pub config: BinaryBenchmarkConfig,
1202    /// If present the command is delayed as configured in [`Delay`]
1203    pub delay: Option<Delay>,
1204    /// The path to the executable
1205    pub path: PathBuf,
1206    /// The command's stderr
1207    pub stderr: Option<Stdio>,
1208    /// The command's stdin
1209    pub stdin: Option<Stdin>,
1210    /// The command's stdout
1211    pub stdout: Option<Stdio>,
1212}
1213
1214/// The delay of the [`Command`]
1215#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
1216pub struct Delay {
1217    /// The kind of delay
1218    pub kind: DelayKind,
1219    /// The polling time to check the delay condition
1220    pub poll: Option<Duration>,
1221    /// The timeout for the delay
1222    pub timeout: Option<Duration>,
1223}
1224
1225/// The model for the regression check configuration of DHAT
1226#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1227pub struct DhatRegressionConfig {
1228    /// True if the benchmarks should fail on the first occurrence of a regression
1229    pub fail_fast: Option<bool>,
1230    /// The hard limits
1231    pub hard_limits: Vec<(DhatMetrics, Limit)>,
1232    /// The soft limits
1233    pub soft_limits: Vec<(DhatMetrics, f64)>,
1234}
1235
1236/// The fixtures to copy into the [`Sandbox`]
1237#[derive(Debug, Clone, Serialize, Deserialize)]
1238pub struct Fixtures {
1239    /// If true, follow symlinks
1240    pub follow_symlinks: bool,
1241    /// The path to the fixtures
1242    pub path: PathBuf,
1243}
1244
1245/// The model for the configuration of flamegraphs
1246#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1247pub struct FlamegraphConfig {
1248    /// The direction of the flamegraph. Top to bottom or vice versa
1249    pub direction: Option<Direction>,
1250    /// The event kinds for which a flamegraph should be generated
1251    pub event_kinds: Option<Vec<EventKind>>,
1252    /// The flamegraph kind
1253    pub kind: Option<FlamegraphKind>,
1254    /// The minimum width which should be displayed
1255    pub min_width: Option<f64>,
1256    /// If true, negate a differential flamegraph
1257    pub negate_differential: Option<bool>,
1258    /// If true, normalize a differential flamegraph
1259    pub normalize_differential: Option<bool>,
1260    /// The subtitle to use for the flamegraphs
1261    pub subtitle: Option<String>,
1262    /// The title to use for the flamegraphs
1263    pub title: Option<String>,
1264}
1265
1266/// The model for the `#[library_benchmark]` attribute
1267#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1268pub struct LibraryBenchmark {
1269    /// The extracted benchmarks of the annotated function
1270    pub benches: Vec<LibraryBenchmarkBench>,
1271    /// The configuration at this level
1272    pub config: Option<LibraryBenchmarkConfig>,
1273}
1274
1275/// The model for the `#[bench]` attribute in a `#[library_benchmark]`
1276#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1277pub struct LibraryBenchmarkBench {
1278    /// The arguments for the function
1279    pub args: Option<String>,
1280    /// The configuration at this level
1281    pub config: Option<LibraryBenchmarkConfig>,
1282    /// The consts for the function as a display string
1283    pub consts_display: Option<String>,
1284    /// The name of the function
1285    pub function_name: String,
1286    /// The id of the attribute as in `#[bench::id]`
1287    pub id: Option<String>,
1288    /// The amount of elements in the iterator of the `#[benches::id(iter = ITERATOR)]` if present
1289    pub iter_count: Option<usize>,
1290}
1291
1292/// The model for the configuration in library benchmarks
1293///
1294/// This is the configuration which is built from the configuration of the UI and for internal use
1295/// only.
1296#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1297pub struct LibraryBenchmarkConfig {
1298    /// If some, set the working directory of the library benchmark to this path
1299    pub current_dir: Option<PathBuf>,
1300    /// The valgrind tool to run instead of the default callgrind
1301    pub default_tool: Option<ValgrindTool>,
1302    /// True if the environment variables should be cleared
1303    pub env_clear: Option<bool>,
1304    /// The environment variables to set or pass through to the binary
1305    pub envs: Vec<(OsString, Option<OsString>)>,
1306    /// The configuration of the output format
1307    pub output_format: Option<OutputFormat>,
1308    /// Run the selected library benchmark in a [`Sandbox`] or not.
1309    pub sandbox: Option<Sandbox>,
1310    /// The valgrind tools to run in addition to the default tool
1311    pub tools: Tools,
1312    /// The tool override at this configuration level
1313    pub tools_override: Option<Tools>,
1314    /// The arguments to pass to all tools
1315    pub valgrind_args: RawToolArgs,
1316}
1317
1318/// The model for the `library_benchmark_group` macro
1319#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1320pub struct LibraryBenchmarkGroup {
1321    /// If true compare the benchmarks in this group
1322    pub compare_by_id: Option<bool>,
1323    /// The configuration at this level
1324    pub config: Option<LibraryBenchmarkConfig>,
1325    /// True if there is a `setup` function
1326    pub has_setup: bool,
1327    /// True if there is a `teardown` function
1328    pub has_teardown: bool,
1329    /// The name or id of the `library_benchmark_group!`
1330    pub id: String,
1331    /// The actual data and the benchmarks of this group
1332    pub library_benchmarks: Vec<LibraryBenchmark>,
1333    /// The maximum amount of parallelism for this group (0 = no limit, 1 = serial, N >= 2 = limit
1334    /// to N)
1335    pub max_parallel: Option<usize>,
1336}
1337
1338/// The model for the `main` macro
1339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1340pub struct LibraryBenchmarkGroups {
1341    /// The command line args as we receive them from `cargo bench`
1342    pub command_line_args: Vec<String>,
1343    /// The configuration of this level
1344    pub config: LibraryBenchmarkConfig,
1345    /// The default tool changed by the `cachegrind` feature
1346    pub default_tool: ValgrindTool,
1347    /// All groups of this benchmark
1348    pub groups: Vec<LibraryBenchmarkGroup>,
1349    /// True if there is a `setup` function
1350    pub has_setup: bool,
1351    /// True if there is a `teardown` function
1352    pub has_teardown: bool,
1353}
1354
1355/// The configuration values for the output format
1356#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1357pub struct OutputFormat {
1358    /// Show a grid instead of spaces in the terminal output
1359    pub show_grid: Option<bool>,
1360    /// Show intermediate results, for example in benchmarks for multi-threaded applications
1361    pub show_intermediate: Option<bool>,
1362    /// Don't show differences within the tolerance margin
1363    pub tolerance: Option<f64>,
1364    /// If set, truncate the description
1365    pub truncate_description: Option<Option<usize>>,
1366}
1367
1368/// The raw arguments to pass to a valgrind tool
1369#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
1370pub struct RawToolArgs(Vec<String>);
1371
1372/// The sandbox to run the benchmarks in
1373#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
1374pub struct Sandbox {
1375    /// If this sandbox is enabled or not
1376    pub enabled: Option<bool>,
1377    /// The fixtures to copy into the sandbox
1378    pub fixtures: Vec<PathBuf>,
1379    /// If true follow symlinks when copying the fixtures
1380    pub follow_symlinks: Option<bool>,
1381}
1382
1383/// The tool configuration
1384#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1385pub struct Tool {
1386    /// If true the tool is run. Ignored for the default tool which always runs
1387    pub enable: Option<bool>,
1388    /// The entry point for the tool
1389    pub entry_point: Option<EntryPoint>,
1390    /// The configuration for flamegraphs
1391    pub flamegraph_config: Option<ToolFlamegraphConfig>,
1392    /// Any frames in the call stack which should be considered in addition to the entry point
1393    pub frames: Option<Vec<String>>,
1394    /// The valgrind tool this configuration is for
1395    pub kind: ValgrindTool,
1396    /// The configuration of the output format
1397    pub output_format: Option<ToolOutputFormat>,
1398    /// The arguments to pass to the tool
1399    pub raw_tool_args: RawToolArgs,
1400    /// The configuration for regression checks of tools which perform regression checks
1401    pub regression_config: Option<ToolRegressionConfig>,
1402    /// Whether this tool's output files should be sanitized after parsing.
1403    pub sanitize_output: Option<SanitizeOutput>,
1404    /// If true show the logging output of Valgrind (not Gungraun)
1405    pub show_log: Option<bool>,
1406}
1407
1408/// The configurations of all tools to run in addition to the default tool
1409#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1410pub struct Tools(pub Vec<Tool>);
1411
1412#[cfg(feature = "runner")]
1413impl BinaryBenchmarkConfig {
1414    /// Update this configuration with all other configurations in the given order
1415    #[must_use]
1416    pub fn update_from_all<'a, T>(mut self, others: T) -> Self
1417    where
1418        T: IntoIterator<Item = Option<&'a Self>>,
1419    {
1420        for other in others.into_iter().flatten() {
1421            self.default_tool = update_option(&self.default_tool, &other.default_tool);
1422            self.env_clear = update_option(&self.env_clear, &other.env_clear);
1423            self.current_dir = update_option(&self.current_dir, &other.current_dir);
1424            self.exit_with = update_option(&self.exit_with, &other.exit_with);
1425
1426            self.valgrind_args
1427                .extend_ignore_flag(other.valgrind_args.0.iter());
1428
1429            self.envs.extend_from_slice(&other.envs);
1430
1431            if let Some(other_tools) = &other.tools_override {
1432                self.tools = other_tools.clone();
1433            } else if !other.tools.is_empty() {
1434                self.tools.update_from_other(&other.tools);
1435            } else {
1436                // do nothing
1437            }
1438
1439            self.sandbox = update_option(&self.sandbox, &other.sandbox);
1440            self.setup_parallel = update_option(&self.setup_parallel, &other.setup_parallel);
1441            self.output_format = update_option(&self.output_format, &other.output_format);
1442        }
1443        self
1444    }
1445
1446    /// Resolves the environment variables and create key, value pairs out of them.
1447    ///
1448    /// This is done especially for pass-through environment variables which have a `None` value at
1449    /// first.
1450    pub fn resolve_envs(&self) -> HashMap<OsString, OsString> {
1451        util::resolve_envs(self.envs.clone())
1452    }
1453
1454    /// Collects all environment variables which don't have a `None` value.
1455    ///
1456    /// Pass-through variables have a `None` value.
1457    pub fn collect_envs(&self) -> Vec<(OsString, OsString)> {
1458        self.envs
1459            .iter()
1460            .filter_map(|(key, option)| option.as_ref().map(|value| (key.clone(), value.clone())))
1461            .collect()
1462    }
1463}
1464
1465impl CachegrindMetric {
1466    /// Returns `true` if this `EventKind` is a derived event.
1467    ///
1468    /// Derived events are calculated from Cachegrind's native event types the same ways as for
1469    /// callgrind's [`EventKind`]
1470    ///
1471    /// * [`CachegrindMetric::L1hits`]
1472    /// * [`CachegrindMetric::LLhits`]
1473    /// * [`CachegrindMetric::RamHits`]
1474    /// * [`CachegrindMetric::TotalRW`]
1475    /// * [`CachegrindMetric::EstimatedCycles`]
1476    /// * [`CachegrindMetric::I1MissRate`]
1477    /// * [`CachegrindMetric::D1MissRate`]
1478    /// * [`CachegrindMetric::LLiMissRate`]
1479    /// * [`CachegrindMetric::LLdMissRate`]
1480    /// * [`CachegrindMetric::LLMissRate`]
1481    /// * [`CachegrindMetric::L1HitRate`]
1482    /// * [`CachegrindMetric::LLHitRate`]
1483    /// * [`CachegrindMetric::RamHitRate`]
1484    pub fn is_derived(&self) -> bool {
1485        matches!(
1486            self,
1487            Self::L1hits
1488                | Self::LLhits
1489                | Self::RamHits
1490                | Self::TotalRW
1491                | Self::EstimatedCycles
1492                | Self::I1MissRate
1493                | Self::D1MissRate
1494                | Self::LLiMissRate
1495                | Self::LLdMissRate
1496                | Self::LLMissRate
1497                | Self::L1HitRate
1498                | Self::LLHitRate
1499                | Self::RamHitRate
1500        )
1501    }
1502
1503    /// Returns the name of the metric which is the exact name of the enum variant.
1504    pub fn to_name(&self) -> String {
1505        format!("{:?}", *self)
1506    }
1507}
1508
1509impl Display for CachegrindMetric {
1510    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1511        match self {
1512            key @ (Self::Ir
1513            | Self::L1hits
1514            | Self::LLhits
1515            | Self::RamHits
1516            | Self::TotalRW
1517            | Self::EstimatedCycles
1518            | Self::I1MissRate
1519            | Self::D1MissRate
1520            | Self::LLiMissRate
1521            | Self::LLdMissRate
1522            | Self::LLMissRate
1523            | Self::L1HitRate
1524            | Self::LLHitRate
1525            | Self::RamHitRate) => write!(f, "{}", EventKind::from(*key)),
1526            _ => write!(f, "{self:?}"),
1527        }
1528    }
1529}
1530
1531#[cfg(feature = "runner")]
1532impl_from_str_metric!(
1533    CachegrindMetric,
1534    "Unknown cachegrind metric: '{}'",
1535    {
1536        "instructions" | "ir" => Ir,
1537        "dr" => Dr,
1538        "dw" => Dw,
1539        "i1mr" => I1mr,
1540        "ilmr" => ILmr,
1541        "d1mr" => D1mr,
1542        "dlmr" => DLmr,
1543        "d1mw" => D1mw,
1544        "dlmw" => DLmw,
1545        "bc" => Bc,
1546        "bcm" => Bcm,
1547        "bi" => Bi,
1548        "bim" => Bim,
1549        "l1hits" => L1hits,
1550        "llhits" => LLhits,
1551        "ramhits" => RamHits,
1552        "totalrw" => TotalRW,
1553        "estimatedcycles" => EstimatedCycles,
1554        "i1missrate" => I1MissRate,
1555        "d1missrate" => D1MissRate,
1556        "llimissrate" => LLiMissRate,
1557        "lldmissrate" => LLdMissRate,
1558        "llmissrate" => LLMissRate,
1559        "l1hitrate" => L1HitRate,
1560        "llhitrate" => LLHitRate,
1561        "ramhitrate" => RamHitRate,
1562    }
1563);
1564
1565#[cfg(feature = "runner")]
1566impl TypeChecker for CachegrindMetric {
1567    fn is_int(&self) -> bool {
1568        match self {
1569            Self::Ir
1570            | Self::Dr
1571            | Self::Dw
1572            | Self::I1mr
1573            | Self::D1mr
1574            | Self::D1mw
1575            | Self::ILmr
1576            | Self::DLmr
1577            | Self::DLmw
1578            | Self::L1hits
1579            | Self::LLhits
1580            | Self::RamHits
1581            | Self::TotalRW
1582            | Self::EstimatedCycles
1583            | Self::Bc
1584            | Self::Bcm
1585            | Self::Bi
1586            | Self::Bim => true,
1587            Self::I1MissRate
1588            | Self::LLiMissRate
1589            | Self::D1MissRate
1590            | Self::LLdMissRate
1591            | Self::LLMissRate
1592            | Self::L1HitRate
1593            | Self::LLHitRate
1594            | Self::RamHitRate => false,
1595        }
1596    }
1597
1598    fn is_float(&self) -> bool {
1599        !self.is_int()
1600    }
1601}
1602
1603impl From<CachegrindMetric> for CachegrindMetrics {
1604    fn from(value: CachegrindMetric) -> Self {
1605        Self::SingleEvent(value)
1606    }
1607}
1608
1609#[cfg(feature = "runner")]
1610impl_from_str_metric_groups!(
1611    CachegrindMetrics,
1612    CachegrindMetric,
1613    SingleEvent,
1614    "Invalid cachegrind metric group: '{}'",
1615    {
1616        "default" | "def" => Default,
1617        "all" => All,
1618        "cachemisses" | "misses" | "ms" => CacheMisses,
1619        "cachemissrates" | "missrates" | "mr" => CacheMissRates,
1620        "cachehits" | "hits" | "hs" => CacheHits,
1621        "cachehitrates" | "hitrates" | "hr" => CacheHitRates,
1622        "cachesim" | "cs" => CacheSim,
1623        "branchsim" | "bs" => BranchSim,
1624    }
1625);
1626
1627impl From<EventKind> for CallgrindMetrics {
1628    fn from(value: EventKind) -> Self {
1629        Self::SingleEvent(value)
1630    }
1631}
1632
1633#[cfg(feature = "runner")]
1634impl_from_str_metric_groups!(
1635    CallgrindMetrics,
1636    EventKind,
1637    SingleEvent,
1638    "Invalid event group: '{}'",
1639    {
1640        "default" | "def" => Default,
1641        "all" => All,
1642        "cachemisses" | "misses" | "ms" => CacheMisses,
1643        "cachemissrates" | "missrates" | "mr" => CacheMissRates,
1644        "cachehits" | "hits" | "hs" => CacheHits,
1645        "cachehitrates" | "hitrates" | "hr" => CacheHitRates,
1646        "cachesim" | "cs" => CacheSim,
1647        "cacheuse" | "cu" => CacheUse,
1648        "systemcalls" | "syscalls" | "sc" => SystemCalls,
1649        "branchsim" | "bs" => BranchSim,
1650        "writebackbehaviour" | "writeback" | "wb" => WriteBackBehaviour,
1651    }
1652);
1653
1654impl Default for DelayKind {
1655    fn default() -> Self {
1656        Self::DurationElapse(Duration::from_secs(60))
1657    }
1658}
1659
1660impl Display for DhatMetric {
1661    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1662        match self {
1663            Self::TotalUnits => f.write_str("Total units"),
1664            Self::TotalEvents => f.write_str("Total events"),
1665            Self::TotalBytes => f.write_str("Total bytes"),
1666            Self::TotalBlocks => f.write_str("Total blocks"),
1667            Self::AtTGmaxBytes => f.write_str("At t-gmax bytes"),
1668            Self::AtTGmaxBlocks => f.write_str("At t-gmax blocks"),
1669            Self::AtTEndBytes => f.write_str("At t-end bytes"),
1670            Self::AtTEndBlocks => f.write_str("At t-end blocks"),
1671            Self::ReadsBytes => f.write_str("Reads bytes"),
1672            Self::WritesBytes => f.write_str("Writes bytes"),
1673            Self::TotalLifetimes => f.write_str("Total lifetimes"),
1674            Self::MaximumBytes => f.write_str("Maximum bytes"),
1675            Self::MaximumBlocks => f.write_str("Maximum blocks"),
1676        }
1677    }
1678}
1679
1680#[cfg(feature = "runner")]
1681impl_from_str_metric!(
1682    DhatMetric,
1683    "Unknown dhat metric: '{}'",
1684    {
1685        "totalunits" | "tun" => TotalUnits,
1686        "totalevents" | "tev" => TotalEvents,
1687        "totalbytes" | "tb" => TotalBytes,
1688        "totalblocks" | "tbk" => TotalBlocks,
1689        "attgmaxbytes" | "gb" => AtTGmaxBytes,
1690        "attgmaxblocks" | "gbk" => AtTGmaxBlocks,
1691        "attendbytes" | "eb" => AtTEndBytes,
1692        "attendblocks" | "ebk" => AtTEndBlocks,
1693        "readsbytes" | "rb" => ReadsBytes,
1694        "writesbytes" | "wb" => WritesBytes,
1695        "totallifetimes" | "tl" => TotalLifetimes,
1696        "maximumbytes" | "mb" => MaximumBytes,
1697        "maximumblocks" | "mbk" => MaximumBlocks,
1698    }
1699);
1700
1701#[cfg(feature = "runner")]
1702impl Summarize for DhatMetric {}
1703
1704#[cfg(feature = "runner")]
1705impl TypeChecker for DhatMetric {
1706    fn is_int(&self) -> bool {
1707        true
1708    }
1709
1710    fn is_float(&self) -> bool {
1711        false
1712    }
1713}
1714
1715impl From<DhatMetric> for DhatMetrics {
1716    fn from(value: DhatMetric) -> Self {
1717        Self::SingleMetric(value)
1718    }
1719}
1720
1721#[cfg(feature = "runner")]
1722impl_from_str_metric_groups!(
1723    DhatMetrics,
1724    DhatMetric,
1725    SingleMetric,
1726    "Invalid dhat metrics group: '{}'",
1727    {
1728        "default" | "def" => Default,
1729        "all" => All,
1730    }
1731);
1732
1733impl<T> From<T> for EntryPoint
1734where
1735    T: Into<String>,
1736{
1737    fn from(value: T) -> Self {
1738        Self::Custom(value.into())
1739    }
1740}
1741
1742impl Display for ErrorMetric {
1743    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1744        match self {
1745            Self::Errors => f.write_str("Errors"),
1746            Self::Contexts => f.write_str("Contexts"),
1747            Self::SuppressedErrors => f.write_str("Suppressed Errors"),
1748            Self::SuppressedContexts => f.write_str("Suppressed Contexts"),
1749        }
1750    }
1751}
1752
1753#[cfg(feature = "runner")]
1754impl_from_str_metric!(
1755    ErrorMetric,
1756    "Unknown error metric: '{}'",
1757    {
1758        "errors" | "err" => Errors,
1759        "contexts" | "ctx" => Contexts,
1760        "suppressederrors" | "serr" => SuppressedErrors,
1761        "suppressedcontexts" | "sctx" => SuppressedContexts,
1762    }
1763);
1764
1765#[cfg(feature = "runner")]
1766impl Summarize for ErrorMetric {}
1767
1768impl EventKind {
1769    /// Returns `true` if this `EventKind` is a derived event.
1770    ///
1771    /// Derived events are calculated from Callgrind's native event types. See also
1772    /// [`crate::runner::callgrind::model::Metrics::make_summary`]. Currently all derived events
1773    /// are:
1774    ///
1775    /// * [`EventKind::L1hits`]
1776    /// * [`EventKind::LLhits`]
1777    /// * [`EventKind::RamHits`]
1778    /// * [`EventKind::TotalRW`]
1779    /// * [`EventKind::EstimatedCycles`]
1780    /// * [`EventKind::I1MissRate`]
1781    /// * [`EventKind::D1MissRate`]
1782    /// * [`EventKind::LLiMissRate`]
1783    /// * [`EventKind::LLdMissRate`]
1784    /// * [`EventKind::LLMissRate`]
1785    /// * [`EventKind::L1HitRate`]
1786    /// * [`EventKind::LLHitRate`]
1787    /// * [`EventKind::RamHitRate`]
1788    pub fn is_derived(&self) -> bool {
1789        matches!(
1790            self,
1791            Self::L1hits
1792                | Self::LLhits
1793                | Self::RamHits
1794                | Self::TotalRW
1795                | Self::EstimatedCycles
1796                | Self::I1MissRate
1797                | Self::D1MissRate
1798                | Self::LLiMissRate
1799                | Self::LLdMissRate
1800                | Self::LLMissRate
1801                | Self::L1HitRate
1802                | Self::LLHitRate
1803                | Self::RamHitRate
1804        )
1805    }
1806
1807    /// Returns the name of the metric which is the exact name of the enum variant.
1808    pub fn to_name(&self) -> String {
1809        format!("{:?}", *self)
1810    }
1811}
1812
1813impl Display for EventKind {
1814    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1815        match self {
1816            Self::Ir => f.write_str("Instructions"),
1817            Self::L1hits => f.write_str("L1 Hits"),
1818            Self::LLhits => f.write_str("LL Hits"),
1819            Self::RamHits => f.write_str("RAM Hits"),
1820            Self::TotalRW => f.write_str("Total read+write"),
1821            Self::EstimatedCycles => f.write_str("Estimated Cycles"),
1822            Self::I1MissRate => f.write_str("I1 Miss Rate"),
1823            Self::D1MissRate => f.write_str("D1 Miss Rate"),
1824            Self::LLiMissRate => f.write_str("LLi Miss Rate"),
1825            Self::LLdMissRate => f.write_str("LLd Miss Rate"),
1826            Self::LLMissRate => f.write_str("LL Miss Rate"),
1827            Self::L1HitRate => f.write_str("L1 Hit Rate"),
1828            Self::LLHitRate => f.write_str("LL Hit Rate"),
1829            Self::RamHitRate => f.write_str("RAM Hit Rate"),
1830            _ => write!(f, "{self:?}"),
1831        }
1832    }
1833}
1834
1835impl From<CachegrindMetric> for EventKind {
1836    fn from(value: CachegrindMetric) -> Self {
1837        match value {
1838            CachegrindMetric::Ir => Self::Ir,
1839            CachegrindMetric::Dr => Self::Dr,
1840            CachegrindMetric::Dw => Self::Dw,
1841            CachegrindMetric::I1mr => Self::I1mr,
1842            CachegrindMetric::D1mr => Self::D1mr,
1843            CachegrindMetric::D1mw => Self::D1mw,
1844            CachegrindMetric::ILmr => Self::ILmr,
1845            CachegrindMetric::DLmr => Self::DLmr,
1846            CachegrindMetric::DLmw => Self::DLmw,
1847            CachegrindMetric::L1hits => Self::L1hits,
1848            CachegrindMetric::LLhits => Self::LLhits,
1849            CachegrindMetric::RamHits => Self::RamHits,
1850            CachegrindMetric::TotalRW => Self::TotalRW,
1851            CachegrindMetric::EstimatedCycles => Self::EstimatedCycles,
1852            CachegrindMetric::Bc => Self::Bc,
1853            CachegrindMetric::Bcm => Self::Bcm,
1854            CachegrindMetric::Bi => Self::Bi,
1855            CachegrindMetric::Bim => Self::Bim,
1856            CachegrindMetric::I1MissRate => Self::I1MissRate,
1857            CachegrindMetric::D1MissRate => Self::D1MissRate,
1858            CachegrindMetric::LLiMissRate => Self::LLiMissRate,
1859            CachegrindMetric::LLdMissRate => Self::LLdMissRate,
1860            CachegrindMetric::LLMissRate => Self::LLMissRate,
1861            CachegrindMetric::L1HitRate => Self::L1HitRate,
1862            CachegrindMetric::LLHitRate => Self::LLHitRate,
1863            CachegrindMetric::RamHitRate => Self::RamHitRate,
1864        }
1865    }
1866}
1867
1868#[cfg(feature = "runner")]
1869impl_from_str_metric!(
1870    EventKind,
1871    "Unknown event kind: '{}'",
1872    {
1873        "instructions" | "ir" => Ir,
1874        "dr" => Dr,
1875        "dw" => Dw,
1876        "i1mr" => I1mr,
1877        "d1mr" => D1mr,
1878        "d1mw" => D1mw,
1879        "ilmr" => ILmr,
1880        "dlmr" => DLmr,
1881        "dlmw" => DLmw,
1882        "syscount" => SysCount,
1883        "systime" => SysTime,
1884        "syscputime" => SysCpuTime,
1885        "ge" => Ge,
1886        "bc" => Bc,
1887        "bcm" => Bcm,
1888        "bi" => Bi,
1889        "bim" => Bim,
1890        "ildmr" => ILdmr,
1891        "dldmr" => DLdmr,
1892        "dldmw" => DLdmw,
1893        "accost1" => AcCost1,
1894        "accost2" => AcCost2,
1895        "sploss1" => SpLoss1,
1896        "sploss2" => SpLoss2,
1897        "l1hits" => L1hits,
1898        "llhits" => LLhits,
1899        "ramhits" => RamHits,
1900        "totalrw" => TotalRW,
1901        "estimatedcycles" => EstimatedCycles,
1902        "i1missrate" => I1MissRate,
1903        "d1missrate" => D1MissRate,
1904        "llimissrate" => LLiMissRate,
1905        "lldmissrate" => LLdMissRate,
1906        "llmissrate" => LLMissRate,
1907        "l1hitrate" => L1HitRate,
1908        "llhitrate" => LLHitRate,
1909        "ramhitrate" => RamHitRate,
1910    }
1911);
1912
1913#[cfg(feature = "runner")]
1914impl TypeChecker for EventKind {
1915    fn is_int(&self) -> bool {
1916        match self {
1917            Self::Ir
1918            | Self::Dr
1919            | Self::Dw
1920            | Self::I1mr
1921            | Self::D1mr
1922            | Self::D1mw
1923            | Self::ILmr
1924            | Self::DLmr
1925            | Self::DLmw
1926            | Self::L1hits
1927            | Self::LLhits
1928            | Self::RamHits
1929            | Self::TotalRW
1930            | Self::EstimatedCycles
1931            | Self::SysCount
1932            | Self::SysTime
1933            | Self::SysCpuTime
1934            | Self::Ge
1935            | Self::Bc
1936            | Self::Bcm
1937            | Self::Bi
1938            | Self::Bim
1939            | Self::ILdmr
1940            | Self::DLdmr
1941            | Self::DLdmw
1942            | Self::AcCost1
1943            | Self::AcCost2
1944            | Self::SpLoss1
1945            | Self::SpLoss2 => true,
1946            Self::I1MissRate
1947            | Self::LLiMissRate
1948            | Self::D1MissRate
1949            | Self::LLdMissRate
1950            | Self::LLMissRate
1951            | Self::L1HitRate
1952            | Self::LLHitRate
1953            | Self::RamHitRate => false,
1954        }
1955    }
1956
1957    fn is_float(&self) -> bool {
1958        !self.is_int()
1959    }
1960}
1961
1962#[cfg(feature = "runner")]
1963impl From<CachegrindMetrics> for IndexSet<CachegrindMetric> {
1964    fn from(value: CachegrindMetrics) -> Self {
1965        let mut metrics = Self::new();
1966        match value {
1967            CachegrindMetrics::None => {}
1968            CachegrindMetrics::All => metrics.extend(CachegrindMetric::iter()),
1969            CachegrindMetrics::Default => {
1970                metrics.insert(CachegrindMetric::Ir);
1971                metrics.extend(Self::from(CachegrindMetrics::CacheHits));
1972                metrics.extend([CachegrindMetric::TotalRW, CachegrindMetric::EstimatedCycles]);
1973                metrics.extend(Self::from(CachegrindMetrics::BranchSim));
1974            }
1975            CachegrindMetrics::CacheMisses => metrics.extend([
1976                CachegrindMetric::I1mr,
1977                CachegrindMetric::D1mr,
1978                CachegrindMetric::D1mw,
1979                CachegrindMetric::ILmr,
1980                CachegrindMetric::DLmr,
1981                CachegrindMetric::DLmw,
1982            ]),
1983            CachegrindMetrics::CacheMissRates => metrics.extend([
1984                CachegrindMetric::I1MissRate,
1985                CachegrindMetric::LLiMissRate,
1986                CachegrindMetric::D1MissRate,
1987                CachegrindMetric::LLdMissRate,
1988                CachegrindMetric::LLMissRate,
1989            ]),
1990            CachegrindMetrics::CacheHits => {
1991                metrics.extend([
1992                    CachegrindMetric::L1hits,
1993                    CachegrindMetric::LLhits,
1994                    CachegrindMetric::RamHits,
1995                ]);
1996            }
1997            CachegrindMetrics::CacheHitRates => {
1998                metrics.extend([
1999                    CachegrindMetric::L1HitRate,
2000                    CachegrindMetric::LLHitRate,
2001                    CachegrindMetric::RamHitRate,
2002                ]);
2003            }
2004            CachegrindMetrics::CacheSim => {
2005                metrics.extend([CachegrindMetric::Dr, CachegrindMetric::Dw]);
2006                metrics.extend(Self::from(CachegrindMetrics::CacheMisses));
2007                metrics.extend(Self::from(CachegrindMetrics::CacheMissRates));
2008                metrics.extend(Self::from(CachegrindMetrics::CacheHits));
2009                metrics.extend(Self::from(CachegrindMetrics::CacheHitRates));
2010                metrics.insert(CachegrindMetric::TotalRW);
2011                metrics.insert(CachegrindMetric::EstimatedCycles);
2012            }
2013            CachegrindMetrics::BranchSim => {
2014                metrics.extend([
2015                    CachegrindMetric::Bc,
2016                    CachegrindMetric::Bcm,
2017                    CachegrindMetric::Bi,
2018                    CachegrindMetric::Bim,
2019                ]);
2020            }
2021            CachegrindMetrics::SingleEvent(metric) => {
2022                metrics.insert(metric);
2023            }
2024        }
2025
2026        metrics
2027    }
2028}
2029
2030#[cfg(feature = "runner")]
2031impl From<DhatMetrics> for IndexSet<DhatMetric> {
2032    fn from(value: DhatMetrics) -> Self {
2033        use DhatMetric::*;
2034        match value {
2035            DhatMetrics::All => DhatMetric::iter().collect(),
2036            DhatMetrics::Default => indexset! {
2037            TotalUnits,
2038            TotalEvents,
2039            TotalBytes,
2040            TotalBlocks,
2041            AtTGmaxBytes,
2042            AtTGmaxBlocks,
2043            AtTEndBytes,
2044            AtTEndBlocks,
2045            ReadsBytes,
2046            WritesBytes },
2047            DhatMetrics::SingleMetric(dhat_metric) => indexset! { dhat_metric },
2048        }
2049    }
2050}
2051
2052#[cfg(feature = "runner")]
2053impl From<CallgrindMetrics> for IndexSet<EventKind> {
2054    fn from(value: CallgrindMetrics) -> Self {
2055        let mut event_kinds = Self::new();
2056        match value {
2057            CallgrindMetrics::None => {}
2058            CallgrindMetrics::All => event_kinds.extend(EventKind::iter()),
2059            CallgrindMetrics::Default => {
2060                event_kinds.insert(EventKind::Ir);
2061                event_kinds.extend(Self::from(CallgrindMetrics::CacheHits));
2062                event_kinds.extend([EventKind::TotalRW, EventKind::EstimatedCycles]);
2063                event_kinds.extend(Self::from(CallgrindMetrics::SystemCalls));
2064                event_kinds.insert(EventKind::Ge);
2065                event_kinds.extend(Self::from(CallgrindMetrics::BranchSim));
2066                event_kinds.extend(Self::from(CallgrindMetrics::WriteBackBehaviour));
2067                event_kinds.extend(Self::from(CallgrindMetrics::CacheUse));
2068            }
2069            CallgrindMetrics::CacheMisses => event_kinds.extend([
2070                EventKind::I1mr,
2071                EventKind::D1mr,
2072                EventKind::D1mw,
2073                EventKind::ILmr,
2074                EventKind::DLmr,
2075                EventKind::DLmw,
2076            ]),
2077            CallgrindMetrics::CacheMissRates => event_kinds.extend([
2078                EventKind::I1MissRate,
2079                EventKind::LLiMissRate,
2080                EventKind::D1MissRate,
2081                EventKind::LLdMissRate,
2082                EventKind::LLMissRate,
2083            ]),
2084            CallgrindMetrics::CacheHits => {
2085                event_kinds.extend([EventKind::L1hits, EventKind::LLhits, EventKind::RamHits]);
2086            }
2087            CallgrindMetrics::CacheHitRates => {
2088                event_kinds.extend([
2089                    EventKind::L1HitRate,
2090                    EventKind::LLHitRate,
2091                    EventKind::RamHitRate,
2092                ]);
2093            }
2094            CallgrindMetrics::CacheSim => {
2095                event_kinds.extend([EventKind::Dr, EventKind::Dw]);
2096                event_kinds.extend(Self::from(CallgrindMetrics::CacheMisses));
2097                event_kinds.extend(Self::from(CallgrindMetrics::CacheMissRates));
2098                event_kinds.extend(Self::from(CallgrindMetrics::CacheHits));
2099                event_kinds.extend(Self::from(CallgrindMetrics::CacheHitRates));
2100                event_kinds.insert(EventKind::TotalRW);
2101                event_kinds.insert(EventKind::EstimatedCycles);
2102            }
2103            CallgrindMetrics::CacheUse => event_kinds.extend([
2104                EventKind::AcCost1,
2105                EventKind::AcCost2,
2106                EventKind::SpLoss1,
2107                EventKind::SpLoss2,
2108            ]),
2109            CallgrindMetrics::SystemCalls => {
2110                event_kinds.extend([
2111                    EventKind::SysCount,
2112                    EventKind::SysTime,
2113                    EventKind::SysCpuTime,
2114                ]);
2115            }
2116            CallgrindMetrics::BranchSim => {
2117                event_kinds.extend([EventKind::Bc, EventKind::Bcm, EventKind::Bi, EventKind::Bim]);
2118            }
2119            CallgrindMetrics::WriteBackBehaviour => {
2120                event_kinds.extend([EventKind::ILdmr, EventKind::DLdmr, EventKind::DLdmw]);
2121            }
2122            CallgrindMetrics::SingleEvent(event_kind) => {
2123                event_kinds.insert(event_kind);
2124            }
2125        }
2126
2127        event_kinds
2128    }
2129}
2130
2131#[cfg(feature = "runner")]
2132impl LibraryBenchmarkConfig {
2133    /// Update this configuration with all other configurations in the given order
2134    #[must_use]
2135    pub fn update_from_all<'a, T>(mut self, others: T) -> Self
2136    where
2137        T: IntoIterator<Item = Option<&'a Self>>,
2138    {
2139        for other in others.into_iter().flatten() {
2140            self.default_tool = update_option(&self.default_tool, &other.default_tool);
2141            self.env_clear = update_option(&self.env_clear, &other.env_clear);
2142
2143            self.valgrind_args
2144                .extend_ignore_flag(other.valgrind_args.0.iter());
2145
2146            self.envs.extend_from_slice(&other.envs);
2147            if let Some(other_tools) = &other.tools_override {
2148                self.tools = other_tools.clone();
2149            } else if !other.tools.is_empty() {
2150                self.tools.update_from_other(&other.tools);
2151            } else {
2152                // do nothing
2153            }
2154
2155            self.output_format = update_option(&self.output_format, &other.output_format);
2156            self.current_dir = update_option(&self.current_dir, &other.current_dir);
2157            self.sandbox = update_option(&self.sandbox, &other.sandbox);
2158        }
2159        self
2160    }
2161
2162    /// Resolves the environment variables and create key, value pairs out of them.
2163    ///
2164    /// Same as [`BinaryBenchmarkConfig::resolve_envs`]
2165    pub fn resolve_envs(&self) -> HashMap<OsString, OsString> {
2166        util::resolve_envs(self.envs.clone())
2167    }
2168
2169    /// Collects all environment variables which don't have a `None` value.
2170    ///
2171    /// Same as [`BinaryBenchmarkConfig::collect_envs`]
2172    pub fn collect_envs(&self) -> Vec<(OsString, OsString)> {
2173        self.envs
2174            .iter()
2175            .filter_map(|(key, option)| option.as_ref().map(|value| (key.clone(), value.clone())))
2176            .collect()
2177    }
2178}
2179
2180#[cfg(feature = "runner")]
2181impl From<runner::metrics::Metric> for Limit {
2182    fn from(value: runner::metrics::Metric) -> Self {
2183        match value {
2184            runner::metrics::Metric::Int(a) => Self::Int(a),
2185            runner::metrics::Metric::Float(b) => Self::Float(b),
2186        }
2187    }
2188}
2189
2190impl From<f64> for Limit {
2191    fn from(value: f64) -> Self {
2192        Self::Float(value)
2193    }
2194}
2195
2196impl From<u64> for Limit {
2197    fn from(value: u64) -> Self {
2198        Self::Int(value)
2199    }
2200}
2201
2202impl RawToolArgs {
2203    /// Returns a slice of the underlying argument strings
2204    pub fn as_slice(&self) -> &[String] {
2205        &self.0
2206    }
2207
2208    /// Creates new arguments for a valgrind tool.
2209    pub fn new<I, T>(args: T) -> Self
2210    where
2211        I: Into<String>,
2212        T: IntoIterator<Item = I>,
2213    {
2214        args.into_iter().map(Into::into).collect()
2215    }
2216
2217    /// Extends the arguments with the contents of an iterator.
2218    pub fn extend_ignore_flag<I, T>(&mut self, args: T)
2219    where
2220        I: AsRef<str>,
2221        T: IntoIterator<Item = I>,
2222    {
2223        self.0.extend(
2224            args.into_iter()
2225                .filter(|s| !s.as_ref().is_empty())
2226                .map(|s| {
2227                    let string = s.as_ref();
2228                    if string.starts_with('-') {
2229                        string.to_owned()
2230                    } else {
2231                        format!("--{string}")
2232                    }
2233                }),
2234        );
2235    }
2236
2237    /// Returns `true` if there are no tool arguments.
2238    pub fn is_empty(&self) -> bool {
2239        self.0.is_empty()
2240    }
2241
2242    /// Appends the arguments of another `RawArgs`.
2243    pub fn update(&mut self, other: &Self) {
2244        self.extend_ignore_flag(other.0.iter());
2245    }
2246
2247    /// Prepends the arguments of another `RawArgs`.
2248    pub fn prepend(&mut self, other: &Self) {
2249        if !other.is_empty() {
2250            let mut other = other.clone();
2251            other.update(self);
2252            *self = other;
2253        }
2254    }
2255}
2256
2257impl<I> FromIterator<I> for RawToolArgs
2258where
2259    I: AsRef<str>,
2260{
2261    fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
2262        let mut this = Self::default();
2263        this.extend_ignore_flag(iter);
2264        this
2265    }
2266}
2267
2268impl Stdin {
2269    /// Applies this [`Stdin`] configuration to a [`Command`] for the selected [`Stream`].
2270    ///
2271    /// This method configures the given [`Command`] according to this [`Stdin`], using the
2272    /// [`Stream`] to select which process stream is being configured. When this is
2273    /// [`Stdin::Setup`], it optionally pipes data from the provided [`Child`] and falls back to
2274    /// regular stdio handling for unsupported combinations. If `current_dir` is provided,
2275    /// file-based paths are resolved relative to that directory.
2276    ///
2277    /// The behavior varies by variant:
2278    /// - [`Stdin::Setup(Pipe::Stdout)`][`Stdin::Setup`] or
2279    ///   [`Stdin::Setup(Pipe::Stderr)`][`Stdin::Setup`]: Pipes the setup process's stdout or stderr
2280    ///   to this process's stdin
2281    /// - [`Stdin::Pipe`]: Creates a piped stdin stream
2282    /// - [`Stdin::Inherit`]: Inherits stdin from the parent process
2283    /// - [`Stdin::Null`]: Connects stdin to `/dev/null` or equivalent
2284    /// - [`Stdin::File(path)`][`Stdin::File`]: Reads stdin from the specified file
2285    ///
2286    /// # Errors
2287    ///
2288    /// Returns an error if:
2289    /// - Setup stream piping is requested but the expected setup stream handle is not available
2290    /// - Applying the underlying stdio configuration fails (e.g., file cannot be opened)
2291    ///
2292    /// # Examples
2293    ///
2294    /// Piping setup stdout to benchmark stdin:
2295    ///
2296    /// ```no_run
2297    /// # let mut setup_child = std::process::Command::new("something").spawn().unwrap();
2298    /// use std::process::Command;
2299    ///
2300    /// use gungraun_runner::api::{Pipe, Stdin, Stream};
2301    ///
2302    /// let mut command = Command::new("benchmark");
2303    /// let stdin = Stdin::Setup(Pipe::Stdout);
2304    /// stdin.apply(&mut command, Stream::Stdin, Some(&mut setup_child), None)?;
2305    ///
2306    /// # Ok::<(), String>(())
2307    /// ```
2308    ///
2309    /// Reading stdin from a file:
2310    ///
2311    /// ```no_run
2312    /// use std::path::Path;
2313    /// use std::process::Command;
2314    ///
2315    /// use gungraun_runner::api::{Stdin, Stream};
2316    ///
2317    /// let mut command = Command::new("benchmark");
2318    /// let stdin = Stdin::File("input.txt".into());
2319    /// stdin.apply(
2320    ///     &mut command,
2321    ///     Stream::Stdin,
2322    ///     None,
2323    ///     Some(Path::new("/workspace")),
2324    /// )?;
2325    /// # Ok::<(), String>(())
2326    /// ```
2327    ///
2328    /// [`Command`]: std::process::Command
2329    /// [`Child`]: std::process::Child
2330    /// [`Stdin::Setup`]: crate::api::Stdin::Setup
2331    /// [`Stdin::Pipe`]: crate::api::Stdin::Pipe
2332    /// [`Stdin::Inherit`]: crate::api::Stdin::Inherit
2333    /// [`Stdin::Null`]: crate::api::Stdin::Null
2334    /// [`Stdin::File`]: crate::api::Stdin::File
2335    #[cfg(feature = "runner")]
2336    pub fn apply(
2337        &self,
2338        command: &mut StdCommand,
2339        stream: Stream,
2340        child: Option<&mut Child>,
2341        current_dir: Option<&Path>,
2342    ) -> Result<(), String> {
2343        match (self, child) {
2344            (Self::Setup(Pipe::Stdout), Some(child)) => {
2345                command.stdin(
2346                    child
2347                        .stdout
2348                        .take()
2349                        .ok_or_else(|| "Error piping setup stdout".to_owned())?,
2350                );
2351                Ok(())
2352            }
2353            (Self::Setup(Pipe::Stderr), Some(child)) => {
2354                command.stdin(
2355                    child
2356                        .stderr
2357                        .take()
2358                        .ok_or_else(|| "Error piping setup stderr".to_owned())?,
2359                );
2360                Ok(())
2361            }
2362            (Self::Setup(_) | Self::Pipe, _) => Stdio::Pipe.apply(command, stream, current_dir),
2363            (Self::Inherit, _) => Stdio::Inherit.apply(command, stream, current_dir),
2364            (Self::Null, _) => Stdio::Null.apply(command, stream, current_dir),
2365            (Self::File(path), _) => Stdio::File(path.clone()).apply(command, stream, current_dir),
2366        }
2367    }
2368}
2369
2370impl From<Stdio> for Stdin {
2371    fn from(value: Stdio) -> Self {
2372        match value {
2373            Stdio::Inherit => Self::Inherit,
2374            Stdio::Null => Self::Null,
2375            Stdio::File(file) => Self::File(file),
2376            Stdio::Pipe => Self::Pipe,
2377        }
2378    }
2379}
2380
2381impl From<PathBuf> for Stdin {
2382    fn from(value: PathBuf) -> Self {
2383        Self::File(value)
2384    }
2385}
2386
2387impl From<&PathBuf> for Stdin {
2388    fn from(value: &PathBuf) -> Self {
2389        Self::File(value.to_owned())
2390    }
2391}
2392
2393impl From<&Path> for Stdin {
2394    fn from(value: &Path) -> Self {
2395        Self::File(value.to_path_buf())
2396    }
2397}
2398
2399impl Stdio {
2400    /// Applies this stdio configuration to the selected command stream.
2401    ///
2402    /// This method configures the given [`Command`] according to this [`Stdio`], using the
2403    /// [`Stream`] to select which process stream is being configured. For [`Stdio::File`], the
2404    /// file path is interpreted relative to `current_dir` when provided, otherwise it is used
2405    /// as-is.
2406    ///
2407    /// The behavior varies by variant:
2408    /// - [`Stdio::Pipe`]: Creates a piped stream for the selected process stream
2409    /// - [`Stdio::Inherit`]: Inherits the stream from the parent process
2410    /// - [`Stdio::Null`]: Connects the stream to `/dev/null` or equivalent
2411    /// - [`Stdio::File(path)`][`Stdio::File`]: Opens or creates the specified file for the stream
2412    ///
2413    /// # Errors
2414    ///
2415    /// Returns an error if:
2416    /// - A file cannot be opened for reading (when configuring stdin)
2417    /// - A file cannot be created for writing (when configuring stdout or stderr)
2418    ///
2419    /// # Examples
2420    ///
2421    /// Piping stdout to a file:
2422    ///
2423    /// ```no_run
2424    /// use std::path::Path;
2425    /// use std::process::Command;
2426    ///
2427    /// use gungraun_runner::api::{Stdio, Stream};
2428    ///
2429    /// let mut command = Command::new("benchmark");
2430    /// let stdout = Stdio::File("output.txt".into());
2431    /// stdout.apply(&mut command, Stream::Stdout, Some(Path::new("/workspace")))?;
2432    /// # Ok::<(), String>(())
2433    /// ```
2434    ///
2435    /// Inheriting stderr from parent:
2436    ///
2437    /// ```no_run
2438    /// use std::process::Command;
2439    ///
2440    /// use gungraun_runner::api::{Stdio, Stream};
2441    ///
2442    /// let mut command = Command::new("benchmark");
2443    /// Stdio::Inherit.apply(&mut command, Stream::Stderr, None)?;
2444    /// # Ok::<(), String>(())
2445    /// ```
2446    ///
2447    /// [`Command`]: std::process::Command
2448    /// [`Stdio::Pipe`]: crate::api::Stdio::Pipe
2449    /// [`Stdio::Inherit`]: crate::api::Stdio::Inherit
2450    /// [`Stdio::Null`]: crate::api::Stdio::Null
2451    /// [`Stdio::File`]: crate::api::Stdio::File
2452    #[cfg(feature = "runner")]
2453    pub fn apply(
2454        &self,
2455        command: &mut StdCommand,
2456        stream: Stream,
2457        current_dir: Option<&Path>,
2458    ) -> Result<(), String> {
2459        let stdio = match self {
2460            Self::Pipe => StdStdio::piped(),
2461            Self::Inherit => StdStdio::inherit(),
2462            Self::Null => StdStdio::null(),
2463            Self::File(path) => {
2464                let path = if let Some(current_dir) = current_dir {
2465                    Cow::Owned(current_dir.join(path))
2466                } else {
2467                    Cow::Borrowed(path)
2468                };
2469                match stream {
2470                    Stream::Stdin => {
2471                        StdStdio::from(File::open(path.as_path()).map_err(|error| {
2472                            format!(
2473                                "Failed to open file '{}' in read mode for {stream}: {error}",
2474                                path.display()
2475                            )
2476                        })?)
2477                    }
2478                    Stream::Stdout | Stream::Stderr => {
2479                        StdStdio::from(File::create(path.as_path()).map_err(|error| {
2480                            format!(
2481                                "Failed to create file '{}' for {stream}: {error}",
2482                                path.display()
2483                            )
2484                        })?)
2485                    }
2486                }
2487            }
2488        };
2489
2490        match stream {
2491            Stream::Stdin => command.stdin(stdio),
2492            Stream::Stdout => command.stdout(stdio),
2493            Stream::Stderr => command.stderr(stdio),
2494        };
2495
2496        Ok(())
2497    }
2498}
2499
2500impl From<PathBuf> for Stdio {
2501    fn from(value: PathBuf) -> Self {
2502        Self::File(value)
2503    }
2504}
2505
2506impl From<&PathBuf> for Stdio {
2507    fn from(value: &PathBuf) -> Self {
2508        Self::File(value.to_owned())
2509    }
2510}
2511
2512impl From<&Path> for Stdio {
2513    fn from(value: &Path) -> Self {
2514        Self::File(value.to_path_buf())
2515    }
2516}
2517
2518#[cfg(feature = "runner")]
2519impl Display for Stream {
2520    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2521        f.write_str(&format!("{self:?}").to_lowercase())
2522    }
2523}
2524
2525impl Tool {
2526    /// Creates a new `Tool` configuration.
2527    pub fn new(kind: ValgrindTool) -> Self {
2528        Self {
2529            kind,
2530            enable: None,
2531            raw_tool_args: RawToolArgs::default(),
2532            show_log: None,
2533            regression_config: None,
2534            flamegraph_config: None,
2535            output_format: None,
2536            entry_point: None,
2537            frames: None,
2538            sanitize_output: None,
2539        }
2540    }
2541
2542    /// Creates a new `Tool` configuration with the given command-line `args`.
2543    pub fn with_args<I, T>(kind: ValgrindTool, args: T) -> Self
2544    where
2545        I: AsRef<str>,
2546        T: IntoIterator<Item = I>,
2547    {
2548        let mut this = Self::new(kind);
2549        this.raw_tool_args = RawToolArgs::from_iter(args);
2550        this
2551    }
2552
2553    /// Update this tool configuration with another configuration
2554    pub fn update(&mut self, other: &Self) {
2555        if self.kind == other.kind {
2556            self.enable = update_option(&self.enable, &other.enable);
2557            self.show_log = update_option(&self.show_log, &other.show_log);
2558            self.regression_config =
2559                update_option(&self.regression_config, &other.regression_config);
2560            self.flamegraph_config =
2561                update_option(&self.flamegraph_config, &other.flamegraph_config);
2562            self.output_format = update_option(&self.output_format, &other.output_format);
2563            self.entry_point = update_option(&self.entry_point, &other.entry_point);
2564            self.frames = update_option(&self.frames, &other.frames);
2565
2566            self.raw_tool_args
2567                .extend_ignore_flag(other.raw_tool_args.0.iter());
2568            self.sanitize_output = update_option(&self.sanitize_output, &other.sanitize_output);
2569        }
2570    }
2571}
2572
2573impl Tools {
2574    /// Returns `true` if `Tools` is empty.
2575    pub fn is_empty(&self) -> bool {
2576        self.0.is_empty()
2577    }
2578
2579    /// Update `Tools`
2580    pub fn update(&mut self, other: Tool) {
2581        if let Some(tool) = self.0.iter_mut().find(|t| t.kind == other.kind) {
2582            tool.update(&other);
2583        } else {
2584            self.0.push(other);
2585        }
2586    }
2587
2588    /// Updates `Tools` with all [`Tool`]s from an iterator.
2589    pub fn update_all<T>(&mut self, tools: T)
2590    where
2591        T: IntoIterator<Item = Tool>,
2592    {
2593        for tool in tools {
2594            self.update(tool);
2595        }
2596    }
2597
2598    /// Updates `Tools` with another `Tools`.
2599    pub fn update_from_other(&mut self, tools: &Self) {
2600        self.update_all(tools.0.iter().cloned());
2601    }
2602
2603    /// Searches for the [`Tool`] with `kind` and if present remove it from this `Tools` and return
2604    /// it.
2605    pub fn consume(&mut self, kind: ValgrindTool) -> Option<Tool> {
2606        self.0
2607            .iter()
2608            .position(|p| p.kind == kind)
2609            .map(|position| self.0.remove(position))
2610    }
2611}
2612
2613impl ValgrindTool {
2614    /// Returns the id used by the `valgrind --tool` option.
2615    pub fn id(&self) -> String {
2616        match self {
2617            Self::DHAT => "dhat".to_owned(),
2618            Self::Callgrind => "callgrind".to_owned(),
2619            Self::Memcheck => "memcheck".to_owned(),
2620            Self::Helgrind => "helgrind".to_owned(),
2621            Self::DRD => "drd".to_owned(),
2622            Self::Massif => "massif".to_owned(),
2623            Self::BBV => "exp-bbv".to_owned(),
2624            Self::Cachegrind => "cachegrind".to_owned(),
2625        }
2626    }
2627
2628    /// Returns `true` if this tool has output files in addition to log files.
2629    pub fn has_output_file(&self) -> bool {
2630        matches!(
2631            self,
2632            Self::Callgrind | Self::DHAT | Self::BBV | Self::Massif | Self::Cachegrind
2633        )
2634    }
2635
2636    /// Returns `true` if this tool supports xtree memory files.
2637    pub fn has_xtree_file(&self) -> bool {
2638        matches!(self, Self::Memcheck | Self::Massif | Self::Helgrind)
2639    }
2640
2641    /// Returns `true` if this tool supports xleak files.
2642    pub fn has_xleak_file(&self) -> bool {
2643        *self == Self::Memcheck
2644    }
2645}
2646
2647impl Display for ValgrindTool {
2648    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2649        f.write_str(&self.id())
2650    }
2651}
2652
2653#[cfg(feature = "runner")]
2654impl FromStr for ValgrindTool {
2655    type Err = anyhow::Error;
2656
2657    fn from_str(s: &str) -> Result<Self, Self::Err> {
2658        Self::try_from(s.to_lowercase().as_str())
2659    }
2660}
2661
2662#[cfg(feature = "runner")]
2663impl TryFrom<&str> for ValgrindTool {
2664    type Error = anyhow::Error;
2665
2666    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
2667        match value {
2668            "callgrind" => Ok(Self::Callgrind),
2669            "cachegrind" => Ok(Self::Cachegrind),
2670            "dhat" => Ok(Self::DHAT),
2671            "memcheck" => Ok(Self::Memcheck),
2672            "helgrind" => Ok(Self::Helgrind),
2673            "drd" => Ok(Self::DRD),
2674            "massif" => Ok(Self::Massif),
2675            "exp-bbv" => Ok(Self::BBV),
2676            v => Err(anyhow!("Unknown tool '{v}'")),
2677        }
2678    }
2679}
2680
2681/// Updates the value of an [`Option`].
2682pub fn update_option<T: Clone>(first: &Option<T>, other: &Option<T>) -> Option<T> {
2683    other.clone().or_else(|| first.clone())
2684}
2685
2686#[cfg(test)]
2687mod tests {
2688    use indexmap::indexset;
2689    use pretty_assertions::assert_eq;
2690    use rstest::rstest;
2691
2692    use super::EventKind::*;
2693    use super::{CachegrindMetric as Cm, *};
2694
2695    #[test]
2696    fn test_cachegrind_metric_from_str_ignore_case() {
2697        for metric in CachegrindMetric::iter() {
2698            let string = format!("{metric:?}");
2699            let actual = CachegrindMetric::from_str(&string);
2700            assert_eq!(actual.unwrap(), metric);
2701        }
2702    }
2703
2704    #[test]
2705    fn test_event_kind_from_str_ignore_case() {
2706        for event_kind in EventKind::iter() {
2707            let string = format!("{event_kind:?}");
2708            let actual = EventKind::from_str(&string);
2709            assert_eq!(actual.unwrap(), event_kind);
2710        }
2711    }
2712
2713    #[test]
2714    fn test_library_benchmark_config_update_from_all_when_default() {
2715        assert_eq!(
2716            LibraryBenchmarkConfig::default()
2717                .update_from_all([Some(&LibraryBenchmarkConfig::default())]),
2718            LibraryBenchmarkConfig::default()
2719        );
2720    }
2721
2722    #[test]
2723    fn test_library_benchmark_config_update_from_all_when_no_tools_override() {
2724        let base = LibraryBenchmarkConfig::default();
2725        let other = LibraryBenchmarkConfig {
2726            current_dir: Some(PathBuf::from("/tmp")),
2727            env_clear: Some(true),
2728            valgrind_args: RawToolArgs(vec!["--valgrind-arg=yes".to_owned()]),
2729            envs: vec![(OsString::from("MY_ENV"), Some(OsString::from("value")))],
2730            tools: Tools(vec![Tool {
2731                kind: ValgrindTool::DHAT,
2732                enable: None,
2733                raw_tool_args: RawToolArgs(vec![]),
2734                show_log: None,
2735                regression_config: Some(ToolRegressionConfig::Callgrind(
2736                    CallgrindRegressionConfig::default(),
2737                )),
2738                flamegraph_config: Some(ToolFlamegraphConfig::Callgrind(
2739                    FlamegraphConfig::default(),
2740                )),
2741                entry_point: Some(EntryPoint::default()),
2742                output_format: Some(ToolOutputFormat::None),
2743                frames: Some(vec!["some::frame".to_owned()]),
2744                sanitize_output: Some(SanitizeOutput::KeepOrig),
2745            }]),
2746            tools_override: None,
2747            output_format: None,
2748            default_tool: Some(ValgrindTool::BBV),
2749            sandbox: Some(Sandbox::default()),
2750        };
2751
2752        assert_eq!(base.update_from_all([Some(&other.clone())]), other);
2753    }
2754
2755    #[test]
2756    fn test_library_benchmark_config_update_from_all_when_tools_override() {
2757        let base = LibraryBenchmarkConfig::default();
2758        let other = LibraryBenchmarkConfig {
2759            current_dir: Some(PathBuf::from("/tmp")),
2760            env_clear: Some(true),
2761            valgrind_args: RawToolArgs(vec!["--valgrind-arg=yes".to_owned()]),
2762            envs: vec![(OsString::from("MY_ENV"), Some(OsString::from("value")))],
2763            tools: Tools(vec![Tool {
2764                kind: ValgrindTool::DHAT,
2765                enable: None,
2766                raw_tool_args: RawToolArgs(vec![]),
2767                show_log: None,
2768                regression_config: Some(ToolRegressionConfig::Callgrind(
2769                    CallgrindRegressionConfig::default(),
2770                )),
2771                flamegraph_config: Some(ToolFlamegraphConfig::Callgrind(
2772                    FlamegraphConfig::default(),
2773                )),
2774                entry_point: Some(EntryPoint::default()),
2775                output_format: Some(ToolOutputFormat::None),
2776                frames: Some(vec!["some::frame".to_owned()]),
2777                sanitize_output: Some(SanitizeOutput::KeepOrig),
2778            }]),
2779            tools_override: Some(Tools(vec![])),
2780            output_format: Some(OutputFormat::default()),
2781            default_tool: Some(ValgrindTool::BBV),
2782            sandbox: Some(Sandbox::default()),
2783        };
2784        let expected = LibraryBenchmarkConfig {
2785            tools: other.tools_override.as_ref().unwrap().clone(),
2786            tools_override: None,
2787            ..other.clone()
2788        };
2789
2790        assert_eq!(base.update_from_all([Some(&other)]), expected);
2791    }
2792
2793    #[rstest]
2794    #[case::env_clear(
2795        LibraryBenchmarkConfig {
2796            env_clear: Some(true),
2797            ..Default::default()
2798        }
2799    )]
2800    fn test_library_benchmark_config_update_from_all_truncate_description(
2801        #[case] config: LibraryBenchmarkConfig,
2802    ) {
2803        let actual = LibraryBenchmarkConfig::default().update_from_all([Some(&config)]);
2804        assert_eq!(actual, config);
2805    }
2806
2807    #[rstest]
2808    #[case::all_none(None, None, None)]
2809    #[case::some_and_none(Some(true), None, Some(true))]
2810    #[case::none_and_some(None, Some(true), Some(true))]
2811    #[case::some_and_some(Some(false), Some(true), Some(true))]
2812    #[case::some_and_some_value_does_not_matter(Some(true), Some(false), Some(false))]
2813    fn test_update_option(
2814        #[case] first: Option<bool>,
2815        #[case] other: Option<bool>,
2816        #[case] expected: Option<bool>,
2817    ) {
2818        assert_eq!(update_option(&first, &other), expected);
2819    }
2820
2821    #[rstest]
2822    #[case::empty(vec![], &[], vec![])]
2823    #[case::empty_base(vec![], &["--a=yes"], vec!["--a=yes"])]
2824    #[case::no_flags(vec![], &["a=yes"], vec!["--a=yes"])]
2825    #[case::already_exists_single(vec!["--a=yes"], &["--a=yes"], vec!["--a=yes","--a=yes"])]
2826    #[case::already_exists_when_multiple(
2827    vec!["--a=yes", "--b=yes"],
2828    &["--a=yes"],
2829    vec!["--a=yes", "--b=yes", "--a=yes"]
2830)]
2831    fn test_raw_tool_args_extend_ignore_flags(
2832        #[case] base: Vec<&str>,
2833        #[case] data: &[&str],
2834        #[case] expected: Vec<&str>,
2835    ) {
2836        let mut base = RawToolArgs(base.iter().map(std::string::ToString::to_string).collect());
2837        base.extend_ignore_flag(data.iter().map(std::string::ToString::to_string));
2838
2839        assert_eq!(base.0.into_iter().collect::<Vec<String>>(), expected);
2840    }
2841
2842    #[rstest]
2843    #[case::none(CallgrindMetrics::None, indexset![])]
2844    #[case::all(CallgrindMetrics::All, indexset![Ir, Dr, Dw, I1mr, D1mr, D1mw, ILmr, DLmr,
2845        DLmw, I1MissRate, LLiMissRate, D1MissRate, LLdMissRate, LLMissRate, L1hits, LLhits, RamHits,
2846        TotalRW, L1HitRate, LLHitRate, RamHitRate, EstimatedCycles, SysCount, SysTime, SysCpuTime,
2847        Ge, Bc, Bcm, Bi, Bim, ILdmr, DLdmr, DLdmw, AcCost1, AcCost2, SpLoss1, SpLoss2]
2848    )]
2849    #[case::default(CallgrindMetrics::Default, indexset![Ir, L1hits, LLhits, RamHits, TotalRW,
2850        EstimatedCycles, SysCount, SysTime, SysCpuTime, Ge, Bc,
2851        Bcm, Bi, Bim, ILdmr, DLdmr, DLdmw, AcCost1, AcCost2, SpLoss1, SpLoss2]
2852    )]
2853    #[case::cache_misses(CallgrindMetrics::CacheMisses, indexset![I1mr, D1mr, D1mw, ILmr,
2854        DLmr, DLmw]
2855    )]
2856    #[case::cache_miss_rates(CallgrindMetrics::CacheMissRates, indexset![I1MissRate,
2857        D1MissRate, LLMissRate, LLiMissRate, LLdMissRate]
2858    )]
2859    #[case::cache_hits(CallgrindMetrics::CacheHits, indexset![L1hits, LLhits, RamHits])]
2860    #[case::cache_hit_rates(CallgrindMetrics::CacheHitRates, indexset![
2861        L1HitRate, LLHitRate, RamHitRate
2862    ])]
2863    #[case::cache_sim(CallgrindMetrics::CacheSim, indexset![Dr, Dw, I1mr, D1mr, D1mw, ILmr, DLmr,
2864        DLmw, I1MissRate, LLiMissRate, D1MissRate, LLdMissRate, LLMissRate, L1hits, LLhits, RamHits,
2865        TotalRW, L1HitRate, LLHitRate, RamHitRate, EstimatedCycles]
2866    )]
2867    #[case::cache_use(CallgrindMetrics::CacheUse, indexset![AcCost1, AcCost2, SpLoss1, SpLoss2])]
2868    #[case::system_calls(CallgrindMetrics::SystemCalls, indexset![SysCount, SysTime, SysCpuTime])]
2869    #[case::branch_sim(CallgrindMetrics::BranchSim, indexset![Bc, Bcm, Bi, Bim])]
2870    #[case::write_back(CallgrindMetrics::WriteBackBehaviour, indexset![ILdmr, DLdmr, DLdmw])]
2871    #[case::single_event(CallgrindMetrics::SingleEvent(Ir), indexset![Ir])]
2872    fn test_callgrind_metrics_into_index_set(
2873        #[case] callgrind_metrics: CallgrindMetrics,
2874        #[case] expected_metrics: IndexSet<EventKind>,
2875    ) {
2876        assert_eq!(IndexSet::from(callgrind_metrics), expected_metrics);
2877    }
2878
2879    #[rstest]
2880    #[case::none(CachegrindMetrics::None, indexset![])]
2881    #[case::all(CachegrindMetrics::All, indexset![Cm::Ir, Cm::Dr, Cm::Dw, Cm::I1mr, Cm::D1mr,
2882        Cm::D1mw, Cm::ILmr, Cm::DLmr, Cm::DLmw, Cm::I1MissRate, Cm::LLiMissRate, Cm::D1MissRate,
2883        Cm::LLdMissRate, Cm::LLMissRate, Cm::L1hits, Cm::LLhits, Cm::RamHits, Cm::TotalRW,
2884        Cm::L1HitRate, Cm::LLHitRate, Cm::RamHitRate, Cm::EstimatedCycles, Cm::Bc, Cm::Bcm, Cm::Bi,
2885        Cm::Bim,
2886    ])]
2887    #[case::default(CachegrindMetrics::Default, indexset![Cm::Ir, Cm::L1hits, Cm::LLhits,
2888        Cm::RamHits, Cm::TotalRW, Cm::EstimatedCycles, Cm::Bc, Cm::Bcm, Cm::Bi, Cm::Bim
2889    ])]
2890    #[case::cache_misses(CachegrindMetrics::CacheMisses, indexset![Cm::I1mr, Cm::D1mr, Cm::D1mw,
2891        Cm::ILmr, Cm::DLmr, Cm::DLmw
2892    ])]
2893    #[case::cache_miss_rates(CachegrindMetrics::CacheMissRates, indexset![Cm::I1MissRate,
2894        Cm::D1MissRate, Cm::LLMissRate, Cm::LLiMissRate, Cm::LLdMissRate
2895    ])]
2896    #[case::cache_hits(CachegrindMetrics::CacheHits, indexset![
2897        Cm::L1hits, Cm::LLhits, Cm::RamHits
2898    ])]
2899    #[case::cache_hit_rates(CachegrindMetrics::CacheHitRates, indexset![
2900        Cm::L1HitRate, Cm::LLHitRate, Cm::RamHitRate
2901    ])]
2902    #[case::cache_sim(CachegrindMetrics::CacheSim, indexset![Cm::Dr, Cm::Dw, Cm::I1mr, Cm::D1mr,
2903        Cm::D1mw, Cm::ILmr, Cm::DLmr, Cm::DLmw, Cm::I1MissRate, Cm::LLiMissRate, Cm::D1MissRate,
2904        Cm::LLdMissRate, Cm::LLMissRate, Cm::L1hits, Cm::LLhits, Cm::RamHits, Cm::TotalRW,
2905        Cm::L1HitRate, Cm::LLHitRate, Cm::RamHitRate, Cm::EstimatedCycles
2906    ])]
2907    #[case::branch_sim(CachegrindMetrics::BranchSim, indexset![
2908        Cm::Bc, Cm::Bcm, Cm::Bi, Cm::Bim
2909    ])]
2910    #[case::single_event(CachegrindMetrics::SingleEvent(Cm::Ir), indexset![Cm::Ir])]
2911    fn test_cachegrind_metrics_into_index_set(
2912        #[case] cachegrind_metrics: CachegrindMetrics,
2913        #[case] expected_metrics: IndexSet<CachegrindMetric>,
2914    ) {
2915        assert_eq!(IndexSet::from(cachegrind_metrics), expected_metrics);
2916    }
2917
2918    #[rstest]
2919    #[case::empty(&[], &[], &[])]
2920    #[case::prepend_empty(&["--some"], &[], &["--some"])]
2921    #[case::initial_empty(&[], &["--some"], &["--some"])]
2922    #[case::both_same_arg(&["--some"], &["--some"], &["--some", "--some"])]
2923    #[case::both_different_arg(&["--some"], &["--other"], &["--other", "--some"])]
2924    fn test_raw_tool_args_prepend(
2925        #[case] raw_args: &[&str],
2926        #[case] other: &[&str],
2927        #[case] expected: &[&str],
2928    ) {
2929        let mut raw_args = RawToolArgs::new(raw_args.iter().map(ToOwned::to_owned));
2930        let other = RawToolArgs::new(other.iter().map(ToOwned::to_owned));
2931        let expected = RawToolArgs::new(expected.iter().map(ToOwned::to_owned));
2932
2933        raw_args.prepend(&other);
2934        assert_eq!(raw_args, expected);
2935    }
2936
2937    #[test]
2938    fn test_tool_update_when_tools_match() {
2939        let mut base = Tool::new(ValgrindTool::Callgrind);
2940        let other = Tool {
2941            kind: ValgrindTool::Callgrind,
2942            enable: Some(true),
2943            raw_tool_args: RawToolArgs::new(["--some"]),
2944            show_log: Some(false),
2945            regression_config: Some(ToolRegressionConfig::None),
2946            flamegraph_config: Some(ToolFlamegraphConfig::None),
2947            output_format: Some(ToolOutputFormat::None),
2948            entry_point: Some(EntryPoint::Default),
2949            frames: Some(vec!["some::frame".to_owned()]),
2950            sanitize_output: Some(SanitizeOutput::KeepOrig),
2951        };
2952        let expected = other.clone();
2953        base.update(&other);
2954        assert_eq!(base, expected);
2955    }
2956
2957    #[test]
2958    fn test_tool_update_when_tools_not_match() {
2959        let mut base = Tool::new(ValgrindTool::Callgrind);
2960        let other = Tool {
2961            kind: ValgrindTool::DRD,
2962            enable: Some(true),
2963            raw_tool_args: RawToolArgs::new(["--some"]),
2964            show_log: Some(false),
2965            regression_config: Some(ToolRegressionConfig::None),
2966            flamegraph_config: Some(ToolFlamegraphConfig::None),
2967            output_format: Some(ToolOutputFormat::None),
2968            entry_point: Some(EntryPoint::Default),
2969            frames: Some(vec!["some::frame".to_owned()]),
2970            sanitize_output: Some(SanitizeOutput::KeepOrig),
2971        };
2972
2973        let expected = base.clone();
2974        base.update(&other);
2975
2976        assert_eq!(base, expected);
2977    }
2978}