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}