1use crate::{Error, utils::helpers::HostFunctions};
4use clap::Parser;
5use duct::cmd;
6use frame_benchmarking_cli::PalletCmd;
7pub use frame_benchmarking_cli::{BlockCmd, MachineCmd, OverheadCmd, StorageCmd};
8use serde::{Deserialize, Serialize};
9use sp_runtime::traits::BlakeTwo256;
10use std::{
11 collections::BTreeMap,
12 fmt::Display,
13 io::Read,
14 path::{Path, PathBuf},
15};
16use strum_macros::{EnumIter, EnumMessage as EnumMessageDerive};
17use tempfile::NamedTempFile;
18
19pub mod binary;
21
22pub const GENESIS_BUILDER_DEV_PRESET: &str = "development";
27
28pub type PalletExtrinsicsRegistry = BTreeMap<String, Vec<String>>;
31
32pub enum BenchmarkingCliCommand {
34 Pallet,
36 Overhead,
38 Storage,
40 Machine,
42 Block,
44}
45
46impl Display for BenchmarkingCliCommand {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 let s = match self {
49 BenchmarkingCliCommand::Pallet => "pallet",
50 BenchmarkingCliCommand::Overhead => "overhead",
51 BenchmarkingCliCommand::Storage => "storage",
52 BenchmarkingCliCommand::Machine => "machine",
53 BenchmarkingCliCommand::Block => "block",
54 };
55 write!(f, "{}", s)
56 }
57}
58
59#[derive(
61 clap::ValueEnum,
62 Debug,
63 Eq,
64 PartialEq,
65 Clone,
66 Copy,
67 EnumIter,
68 EnumMessageDerive,
69 Serialize,
70 Deserialize,
71)]
72#[clap(rename_all = "kebab-case")]
73pub enum GenesisBuilderPolicy {
74 None,
76 Runtime,
78}
79
80impl Display for GenesisBuilderPolicy {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 let s = match self {
83 GenesisBuilderPolicy::None => "none",
84 GenesisBuilderPolicy::Runtime => "runtime",
85 };
86 write!(f, "{}", s)
87 }
88}
89
90impl TryFrom<String> for GenesisBuilderPolicy {
91 type Error = String;
92
93 fn try_from(s: String) -> Result<Self, Self::Error> {
94 match s.as_str() {
95 "none" => Ok(GenesisBuilderPolicy::None),
96 "runtime" => Ok(GenesisBuilderPolicy::Runtime),
97 _ => Err(format!("Invalid genesis builder policy: {}", s)),
98 }
99 }
100}
101
102pub fn get_runtime_path(parent: &Path) -> Result<PathBuf, Error> {
107 ["runtime", "runtimes"]
108 .iter()
109 .map(|f| parent.join(f))
110 .find(|path| path.exists())
111 .ok_or_else(|| Error::RuntimeNotFound(parent.to_str().unwrap().to_string()))
112}
113
114pub fn generate_pallet_benchmarks(args: Vec<String>) -> Result<(), Error> {
119 let cmd = PalletCmd::try_parse_from(std::iter::once("".to_string()).chain(args.into_iter()))
120 .map_err(|e| Error::ParamParsingError(e.to_string()))?;
121
122 cmd.run_with_spec::<BlakeTwo256, HostFunctions>(None)
123 .map_err(|e| Error::BenchmarkingError(e.to_string()))
124}
125
126pub fn generate_binary_benchmarks<F>(
134 binary_path: &PathBuf,
135 command: BenchmarkingCliCommand,
136 update_args: F,
137 excluded_args: &[&str],
138) -> Result<(), Error>
139where
140 F: Fn(Vec<String>) -> Vec<String>,
141{
142 let mut args = update_args(std::env::args().skip(3).collect::<Vec<String>>());
144 args = args
145 .into_iter()
146 .filter(|arg| !excluded_args.iter().any(|a| arg.starts_with(a)))
147 .collect::<Vec<String>>();
148 let mut cmd_args = vec!["benchmark".to_string(), command.to_string()];
149 cmd_args.append(&mut args);
150
151 if let Err(e) = cmd(binary_path, cmd_args).stderr_capture().run() {
152 return Err(Error::BenchmarkingError(e.to_string()));
153 }
154 Ok(())
155}
156
157pub async fn load_pallet_extrinsics(
163 runtime_path: &Path,
164 binary_path: &Path,
165) -> Result<PalletExtrinsicsRegistry, Error> {
166 let output = generate_omni_bencher_benchmarks(
167 binary_path,
168 BenchmarkingCliCommand::Pallet,
169 vec![
170 format!("--runtime={}", runtime_path.display()),
171 "--genesis-builder=none".to_string(),
172 "--list=all".to_string(),
173 ],
174 false,
175 )?;
176 Ok(process_pallet_extrinsics(output))
178}
179
180fn process_pallet_extrinsics(output: String) -> PalletExtrinsicsRegistry {
181 let mut registry = PalletExtrinsicsRegistry::new();
183 let lines: Vec<String> = output.split("\n").map(String::from).skip(1).collect();
184 for line in lines {
185 if line.is_empty() {
186 continue;
187 }
188 let record: Vec<String> = line.split(", ").map(String::from).collect();
189 let pallet = record[0].trim().to_string();
190 let extrinsic = record[1].trim().to_string();
191 registry.entry(pallet).or_default().push(extrinsic);
192 }
193
194 for extrinsics in registry.values_mut() {
196 extrinsics.sort();
197 }
198 registry
199}
200
201pub fn generate_omni_bencher_benchmarks(
209 binary_path: &Path,
210 command: BenchmarkingCliCommand,
211 args: Vec<String>,
212 log_enabled: bool,
213) -> Result<String, Error> {
214 let stdout_file = NamedTempFile::new()?;
215 let stdout_path = stdout_file.path().to_owned();
216
217 let stderror_file = NamedTempFile::new()?;
218 let stderror_path = stderror_file.path().to_owned();
219
220 let mut cmd_args = vec!["v1".to_string(), "benchmark".to_string(), command.to_string()];
221 cmd_args.extend(args);
222
223 let cmd = cmd(binary_path, cmd_args)
224 .env("RUST_LOG", if log_enabled { "info" } else { "none" })
225 .stderr_path(&stderror_path)
226 .stdout_path(&stdout_path);
227
228 if let Err(e) = cmd.run() {
229 let mut error_output = String::new();
230 std::fs::File::open(&stderror_path)?.read_to_string(&mut error_output)?;
231 return Err(Error::BenchmarkingError(
232 if error_output.is_empty() { e.to_string() } else { error_output }
233 .trim()
234 .to_string(),
235 ));
236 }
237
238 let mut stdout_output = String::new();
239 std::fs::File::open(&stdout_path)?.read_to_string(&mut stdout_output)?;
240 Ok(stdout_output)
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use binary::omni_bencher_generator;
247 use std::fs;
248 use tempfile::tempdir;
249
250 #[test]
251 fn get_runtime_path_works() -> Result<(), Error> {
252 let temp_dir = tempdir()?;
253 let path = temp_dir.path();
254 let path_str = path.to_str().unwrap().to_string();
255
256 assert_eq!(
257 get_runtime_path(path).unwrap_err().to_string(),
258 format!("Failed to find the runtime {}", path_str)
259 );
260 for name in ["runtime", "runtimes"] {
261 fs::create_dir(path.join(name))?;
262 }
263 assert!(get_runtime_path(path).is_ok());
264 Ok(())
265 }
266
267 #[tokio::test]
268 async fn load_pallet_extrinsics_works() -> Result<(), Error> {
269 let temp_dir = tempdir()?;
270 let runtime_path = get_mock_runtime_path(true);
271 let binary = omni_bencher_generator(temp_dir.path().to_path_buf(), None).await?;
272 binary.source(false, &(), true).await?;
273
274 let registry = load_pallet_extrinsics(&runtime_path, &binary.path()).await?;
275 let pallets: Vec<String> = registry.keys().cloned().collect();
276 assert_eq!(
277 pallets,
278 vec![
279 "cumulus_pallet_parachain_system",
280 "cumulus_pallet_xcmp_queue",
281 "frame_system",
282 "pallet_balances",
283 "pallet_collator_selection",
284 "pallet_message_queue",
285 "pallet_session",
286 "pallet_sudo",
287 "pallet_timestamp"
288 ]
289 );
290 assert_eq!(
291 registry.get("pallet_timestamp").cloned().unwrap_or_default(),
292 ["on_finalize", "set"]
293 );
294 assert_eq!(
295 registry.get("pallet_sudo").cloned().unwrap_or_default(),
296 ["check_only_sudo_account", "remove_key", "set_key", "sudo", "sudo_as"]
297 );
298 Ok(())
299 }
300
301 #[tokio::test]
302 async fn load_pallet_extrinsics_missing_runtime_benchmarks_fails() -> Result<(), Error> {
303 let temp_dir = tempdir()?;
304 let runtime_path = get_mock_runtime_path(false);
305 let binary = omni_bencher_generator(temp_dir.path().to_path_buf(), None).await?;
306 binary.source(false, &(), true).await?;
307
308 assert_eq!(
309 load_pallet_extrinsics(&runtime_path, &binary.path())
310 .await
311 .err()
312 .unwrap()
313 .to_string(),
314 "Failed to run benchmarking: Error: Input(\"Did not find the benchmarking runtime api. This could mean that you either did not build the node correctly with the `--features runtime-benchmarks` flag, or the chain spec that you are using was not created by a node that was compiled with the flag\")"
315 );
316 Ok(())
317 }
318
319 fn get_mock_runtime_path(with_runtime_benchmarks: bool) -> PathBuf {
320 let binary_path = format!(
321 "../../tests/runtimes/{}.wasm",
322 if with_runtime_benchmarks { "base_parachain_benchmark" } else { "base_parachain" }
323 );
324 std::env::current_dir().unwrap().join(binary_path).canonicalize().unwrap()
325 }
326}