harness_cli/configs/harness.rs
1//! The harness benchmarking configs
2//!
3//! This should be placed in the `[package.metadata.harness]` section of the `Cargo.toml` file.
4//!
5//! If the `harness` section is not present, a default config will be created, which contains
6//! a default profile, with two builds: `HEAD` pointing to the current commit, and `HEAD~1` pointing to the previous commit.
7//!
8//! # Example:
9//!
10//! The following example defines a `default` profile.
11//!
12//! Note that the `default` profile will be used by the runner by default,
13//! if no profile name is specified when running `cargo harness run`.
14//!
15//! ```toml
16//! [package.metadata.harness.profiles.default]
17//! iterations = 3 # Optional. Default to 5
18//! invocations = 40 # Optional. Default to 10
19//! # Additional environment variables to set for all builds and benchmarks
20//! # Optional. Default to no additional environment variables
21//! env = { BAR = "BAZ" }
22//!
23//! # The list of builds to evaluate.
24//! # If not specified, two builds `HEAD` and `HEAD~1` will be evaluated by default.
25//! [package.metadata.harness.profiles.default.builds]
26//! # No extra build configurations.
27//! # Default cargo features and the current git commit will be used to produce the build.
28//! # No extra environment variables will be set.
29//! foo = {}
30//! # Another build with extra cargo features, and disabled default features
31//! bar = { features = ["unstable"], default-features = false }
32//! # Extra environment variables only for this build.
33//! baz = { env = { "FOO" = "BAR" } }
34//! # Compile this build with a specific git commit.
35//! qux = { commit = "a1b2c3d4e5f6" }
36//! ````
37use std::{collections::HashMap, path::PathBuf};
38
39use serde::{Deserialize, Serialize};
40use toml::Table;
41
42/// The information we care in a Cargo.toml
43#[derive(Deserialize)]
44pub(crate) struct CargoConfig {
45 /// The package section of the Cargo.toml
46 package: CargoConfigPackage,
47 /// The bench list of the Cargo.toml
48 #[serde(default)]
49 bench: Vec<CargoBenchConfig>,
50 /// Other fields
51 #[serde(flatten)]
52 _others: HashMap<String, toml::Value>,
53}
54
55impl CargoConfig {
56 /// Load the Cargo.toml file
57 fn load_cargo_toml() -> anyhow::Result<CargoConfig> {
58 if !PathBuf::from("./Cargo.toml").is_file() {
59 anyhow::bail!("Failed to load ./Cargo.toml");
60 }
61 let s = std::fs::read_to_string("./Cargo.toml")?;
62 Ok(toml::from_str::<CargoConfig>(&s)?)
63 }
64
65 pub(crate) fn load_benches() -> anyhow::Result<Vec<String>> {
66 Ok(Self::load_cargo_toml()?
67 .bench
68 .iter()
69 .filter_map(|b| {
70 if !b.harness {
71 Some(b.name.clone())
72 } else {
73 None
74 }
75 })
76 .collect())
77 }
78}
79
80/// The package section of the Cargo.toml
81#[derive(Deserialize)]
82struct CargoConfigPackage {
83 /// The custom metadata section of the Cargo.toml
84 metadata: Option<CargoConfigPackageMetadata>,
85 /// Other fields
86 #[serde(flatten)]
87 _others: HashMap<String, toml::Value>,
88}
89
90/// The bench item of the bench list in the Cargo.toml
91#[derive(Deserialize)]
92struct CargoBenchConfig {
93 /// bench name
94 name: String,
95 /// we only care about benches with `harness=false`
96 #[serde(default)]
97 harness: bool,
98 /// Other fields
99 #[serde(flatten)]
100 _others: HashMap<String, toml::Value>,
101}
102
103/// The custom metadata section of the Cargo.toml
104#[derive(Deserialize)]
105struct CargoConfigPackageMetadata {
106 /// The harness config
107 harness: Option<HarnessConfig>,
108 #[serde(flatten)]
109 _others: HashMap<String, toml::Value>,
110}
111
112/// The harness configuration.
113///
114/// This should be placed in the `[package.metadata.harness]` section of the `Cargo.toml` file.
115///
116#[derive(Serialize, Deserialize, Debug)]
117pub struct HarnessConfig {
118 /// Custom project name. Default to the crate name.
119 pub project: Option<String>,
120 /// Evaluation profiles
121 pub profiles: HashMap<String, Profile>,
122}
123
124impl HarnessConfig {
125 /// Load the harness configuration from the `Cargo.toml` file
126 /// If the `harness` section is not present, a default config with a default profile is returned.
127 pub fn load_from_cargo_toml() -> anyhow::Result<HarnessConfig> {
128 if !PathBuf::from("./Cargo.toml").is_file() {
129 anyhow::bail!("Failed to load ./Cargo.toml");
130 }
131 let s = std::fs::read_to_string("./Cargo.toml")?;
132 let mut harness = toml::from_str::<CargoConfig>(&s)?
133 .package
134 .metadata
135 .and_then(|m| m.harness)
136 .unwrap_or_default();
137 if harness.profiles.is_empty() {
138 harness
139 .profiles
140 .insert("default".to_owned(), Default::default());
141 }
142 Ok(harness)
143 }
144}
145
146impl Default for HarnessConfig {
147 fn default() -> Self {
148 Self {
149 project: None,
150 profiles: [("default".to_owned(), Default::default())]
151 .into_iter()
152 .collect(),
153 }
154 }
155}
156
157fn default_iterations() -> usize {
158 5
159}
160
161fn default_invocations() -> usize {
162 10
163}
164
165/// The benchmarking profile.
166///
167/// A harness config can contain multiple profiles, each with a unique name.
168///
169/// The `default` profile will be used by the runner by default.
170#[derive(Serialize, Deserialize, Debug, Clone)]
171pub struct Profile {
172 /// Enabled probes and their configurations. The configuration must be a TOML table (e.g. `example_probe = { param = "42" }`).
173 #[serde(default)]
174 pub probes: HashMap<String, Table>,
175 /// Environment variables to set to all builds and benchmarks
176 #[serde(default)]
177 pub env: HashMap<String, String>,
178 /// Builds to evaluate
179 #[serde(default)]
180 pub builds: HashMap<String, BuildConfig>,
181 /// Number of iterations. Default is 5
182 #[serde(default = "default_iterations")]
183 pub iterations: usize,
184 /// Number of invocations. Default is 10
185 #[serde(default = "default_invocations")]
186 pub invocations: usize,
187}
188
189impl Default for Profile {
190 fn default() -> Self {
191 Self {
192 probes: HashMap::new(),
193 env: HashMap::new(),
194 builds: HashMap::new(),
195 iterations: default_iterations(),
196 invocations: default_invocations(),
197 }
198 }
199}
200
201fn default_true() -> bool {
202 true
203}
204
205/// The build configuration used for evaluation
206#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
207pub struct BuildConfig {
208 /// Extra cargo features used for compilation. Default to no extra features.
209 #[serde(default)]
210 pub features: Vec<String>,
211 /// Whether to use default features. Default to `true`
212 #[serde(default = "default_true", rename = "default-features")]
213 pub default_features: bool,
214 /// Environment variables to set. Default to no extra environment variables.
215 #[serde(default)]
216 pub env: HashMap<String, String>,
217 /// The commit used to produce the build. Default to the current commit.
218 #[serde(default)]
219 pub commit: Option<String>,
220}
221
222impl Default for BuildConfig {
223 fn default() -> Self {
224 Self {
225 features: Vec::new(),
226 default_features: true,
227 env: HashMap::new(),
228 commit: None,
229 }
230 }
231}