Skip to main content

frame_benchmarking_cli/pallet/
command.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use super::{
19	types::{ComponentRange, ComponentRangeMap},
20	writer, ListOutput, PalletCmd, LOG_TARGET,
21};
22use crate::{
23	pallet::{types::FetchedCode, GenesisBuilderPolicy},
24	shared::{
25		genesis_state,
26		genesis_state::{GenesisStateHandler, SpecGenesisSource, WARN_SPEC_GENESIS_CTOR},
27	},
28};
29use clap::{error::ErrorKind, CommandFactory};
30use codec::{Decode, DecodeWithMemTracking, Encode};
31use frame_benchmarking::{
32	Analysis, BenchmarkBatch, BenchmarkBatchSplitResults, BenchmarkList, BenchmarkParameter,
33	BenchmarkResult, BenchmarkSelector,
34};
35use frame_support::traits::StorageInfo;
36use linked_hash_map::LinkedHashMap;
37use sc_cli::{execution_method_from_cli, ChainSpec, CliConfiguration, Result, SharedParams};
38use sc_client_db::BenchmarkingState;
39use sc_executor::{HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY};
40use serde_json::Value;
41use sp_core::{
42	offchain::{
43		testing::{TestOffchainExt, TestTransactionPoolExt},
44		OffchainDbExt, OffchainWorkerExt, TransactionPoolExt,
45	},
46	traits::{CallContext, CodeExecutor, ReadRuntimeVersionExt, WrappedRuntimeCode},
47	Hasher,
48};
49use sp_externalities::Extensions;
50use sp_keystore::{testing::MemoryKeystore, KeystoreExt};
51use sp_runtime::traits::Hash;
52use sp_state_machine::{backend::TryPendingCode, StateMachine};
53use sp_trie::{proof_size_extension::ProofSizeExt, recorder::Recorder};
54use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions};
55use std::{
56	borrow::Cow,
57	collections::{BTreeMap, BTreeSet, HashMap},
58	fmt::Debug,
59	fs,
60	str::FromStr,
61	time,
62	time::Duration,
63};
64
65type SubstrateAndExtraHF<T> = (
66	ExtendedHostFunctions<
67		(sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions),
68		super::logging::logging::HostFunctions,
69	>,
70	T,
71);
72/// How the PoV size of a storage item should be estimated.
73#[derive(clap::ValueEnum, Debug, Eq, PartialEq, Clone, Copy)]
74pub enum PovEstimationMode {
75	/// Use the maximal encoded length as provided by [`codec::MaxEncodedLen`].
76	MaxEncodedLen,
77	/// Measure the accessed value size in the pallet benchmarking and add some trie overhead.
78	Measured,
79	/// Do not estimate the PoV size for this storage item or benchmark.
80	Ignored,
81}
82
83impl FromStr for PovEstimationMode {
84	type Err = &'static str;
85
86	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
87		match s {
88			"MaxEncodedLen" => Ok(Self::MaxEncodedLen),
89			"Measured" => Ok(Self::Measured),
90			"Ignored" => Ok(Self::Ignored),
91			_ => unreachable!("The benchmark! macro should have prevented this"),
92		}
93	}
94}
95
96/// Maps (pallet, benchmark) -> ((pallet, storage) -> PovEstimationMode)
97pub(crate) type PovModesMap =
98	HashMap<(String, String), HashMap<(String, String), PovEstimationMode>>;
99
100#[derive(Debug, Clone)]
101struct SelectedBenchmark {
102	pallet: String,
103	instance: String,
104	extrinsic: String,
105	components: Vec<(BenchmarkParameter, u32, u32)>,
106	pov_modes: Vec<(String, String)>,
107}
108
109// This takes multiple benchmark batches and combines all the results where the pallet, instance,
110// and benchmark are the same.
111fn combine_batches(
112	time_batches: Vec<BenchmarkBatch>,
113	db_batches: Vec<BenchmarkBatch>,
114) -> Vec<BenchmarkBatchSplitResults> {
115	if time_batches.is_empty() && db_batches.is_empty() {
116		return Default::default();
117	}
118
119	let mut all_benchmarks =
120		LinkedHashMap::<_, (Vec<BenchmarkResult>, Vec<BenchmarkResult>)>::new();
121
122	db_batches
123		.into_iter()
124		.for_each(|BenchmarkBatch { pallet, instance, benchmark, results }| {
125			// We use this key to uniquely identify a benchmark among batches.
126			let key = (pallet, instance, benchmark);
127
128			match all_benchmarks.get_mut(&key) {
129				// We already have this benchmark, so we extend the results.
130				Some(x) => x.1.extend(results),
131				// New benchmark, so we add a new entry with the initial results.
132				None => {
133					all_benchmarks.insert(key, (Vec::new(), results));
134				},
135			}
136		});
137
138	time_batches
139		.into_iter()
140		.for_each(|BenchmarkBatch { pallet, instance, benchmark, results }| {
141			// We use this key to uniquely identify a benchmark among batches.
142			let key = (pallet, instance, benchmark);
143
144			match all_benchmarks.get_mut(&key) {
145				// We already have this benchmark, so we extend the results.
146				Some(x) => x.0.extend(results),
147				None => panic!("all benchmark keys should have been populated by db batches"),
148			}
149		});
150
151	all_benchmarks
152		.into_iter()
153		.map(|((pallet, instance, benchmark), (time_results, db_results))| {
154			BenchmarkBatchSplitResults { pallet, instance, benchmark, time_results, db_results }
155		})
156		.collect::<Vec<_>>()
157}
158
159/// Explains possible reasons why the metadata for the benchmarking could not be found.
160const ERROR_API_NOT_FOUND: &'static str = "Did not find the benchmarking runtime api. \
161This could mean that you either did not build the node correctly with the \
162`--features runtime-benchmarks` flag, or the chain spec that you are using was \
163not created by a node that was compiled with the flag";
164
165impl PalletCmd {
166	fn state_handler_from_cli<HF: HostFunctions>(
167		&self,
168		chain_spec_from_api: Option<Box<dyn ChainSpec>>,
169	) -> Result<GenesisStateHandler> {
170		let genesis_builder_to_source = || match self.genesis_builder {
171			Some(GenesisBuilderPolicy::Runtime) | Some(GenesisBuilderPolicy::SpecRuntime) => {
172				SpecGenesisSource::Runtime(self.genesis_builder_preset.clone())
173			},
174			Some(GenesisBuilderPolicy::SpecGenesis) | None => {
175				log::warn!(target: LOG_TARGET, "{WARN_SPEC_GENESIS_CTOR}");
176				SpecGenesisSource::SpecJson
177			},
178			Some(GenesisBuilderPolicy::None) => SpecGenesisSource::None,
179		};
180
181		// First handle chain-spec passed in via API parameter.
182		if let Some(chain_spec) = chain_spec_from_api {
183			log::debug!("Initializing state handler with chain-spec from API: {:?}", chain_spec);
184
185			let source = genesis_builder_to_source();
186			return Ok(GenesisStateHandler::ChainSpec(chain_spec, source));
187		};
188
189		// Handle chain-spec passed in via CLI.
190		if let Some(chain_spec_path) = &self.shared_params.chain {
191			log::debug!(
192				"Initializing state handler with chain-spec from path: {:?}",
193				chain_spec_path
194			);
195			let (chain_spec, _) =
196				genesis_state::chain_spec_from_path::<HF>(chain_spec_path.to_string().into())?;
197
198			let source = genesis_builder_to_source();
199
200			return Ok(GenesisStateHandler::ChainSpec(chain_spec, source));
201		};
202
203		// Check for runtimes. In general, we make sure that `--runtime` and `--chain` are
204		// incompatible on the CLI level.
205		if let Some(runtime_path) = &self.runtime {
206			log::debug!("Initializing state handler with runtime from path: {:?}", runtime_path);
207
208			let runtime_blob = fs::read(runtime_path)?;
209			return if let Some(GenesisBuilderPolicy::None) = self.genesis_builder {
210				Ok(GenesisStateHandler::Runtime(runtime_blob, None))
211			} else {
212				Ok(GenesisStateHandler::Runtime(
213					runtime_blob,
214					Some(self.genesis_builder_preset.clone()),
215				))
216			};
217		};
218
219		Err("Neither a runtime nor a chain-spec were specified".to_string().into())
220	}
221
222	/// Runs the pallet benchmarking command.
223	pub fn run_with_spec<Hasher, ExtraHostFunctions>(
224		&self,
225		chain_spec: Option<Box<dyn ChainSpec>>,
226	) -> Result<()>
227	where
228		Hasher: Hash,
229		<Hasher as Hash>::Output: DecodeWithMemTracking,
230		ExtraHostFunctions: HostFunctions,
231	{
232		if let Err((error_kind, msg)) = self.check_args(&chain_spec) {
233			let mut cmd = PalletCmd::command();
234			cmd.error(error_kind, msg).exit();
235		};
236
237		let _d = self.execution.as_ref().map(|exec| {
238			// We print the error at the end, since there is often A LOT of output.
239			sp_core::defer::DeferGuard::new(move || {
240				log::error!(
241					target: LOG_TARGET,
242					"⚠️  Argument `--execution` is deprecated. Its value of `{exec}` has on effect.",
243				)
244			})
245		});
246
247		if let Some(json_input) = &self.json_input {
248			let raw_data = match std::fs::read(json_input) {
249				Ok(raw_data) => raw_data,
250				Err(error) => {
251					return Err(format!("Failed to read {:?}: {}", json_input, error).into())
252				},
253			};
254			let batches: Vec<BenchmarkBatchSplitResults> = match serde_json::from_slice(&raw_data) {
255				Ok(batches) => batches,
256				Err(error) => {
257					return Err(format!("Failed to deserialize {:?}: {}", json_input, error).into())
258				},
259			};
260			return self.output_from_results(&batches);
261		}
262		super::logging::init(self.runtime_log.clone());
263
264		let state_handler =
265			self.state_handler_from_cli::<SubstrateAndExtraHF<ExtraHostFunctions>>(chain_spec)?;
266
267		let genesis_patcher = if let Some(ref patch_path) = self.genesis_patch {
268			let patch_content = fs::read_to_string(patch_path)
269				.map_err(|e| format!("Failed to read genesis patch file: {}", e))?;
270			let patch_value: serde_json::Value = serde_json::from_str(&patch_content)
271				.map_err(|e| format!("Failed to parse genesis patch JSON: {}", e))?;
272
273			Some(Box::new(move |mut value| {
274				sc_chain_spec::json_patch::merge(&mut value, patch_value);
275				value
276			}) as Box<dyn FnOnce(Value) -> Value + 'static>)
277		} else {
278			None
279		};
280
281		let genesis_storage = state_handler
282			.build_storage::<SubstrateAndExtraHF<ExtraHostFunctions>>(genesis_patcher)?;
283
284		let cache_size = Some(self.database_cache_size as usize);
285		let state_with_tracking = BenchmarkingState::<Hasher>::new(
286			genesis_storage.clone(),
287			cache_size,
288			// Record proof size
289			true,
290			// Enable storage tracking
291			true,
292		)?;
293
294		let state_without_tracking = BenchmarkingState::<Hasher>::new(
295			genesis_storage,
296			cache_size,
297			// Proof recording depends on CLI settings
298			!self.disable_proof_recording,
299			// Do not enable storage tracking
300			false,
301		)?;
302
303		let method =
304			execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy);
305
306		let state = &state_without_tracking;
307		let runtime = self.runtime_blob(&state_without_tracking)?;
308		let runtime_code = runtime.code()?;
309		let alloc_strategy = self.alloc_strategy(runtime_code.heap_pages);
310
311		let executor = WasmExecutor::<SubstrateAndExtraHF<ExtraHostFunctions>>::builder()
312			.with_execution_method(method)
313			.with_allow_missing_host_functions(self.allow_missing_host_functions)
314			.with_onchain_heap_alloc_strategy(alloc_strategy)
315			.with_offchain_heap_alloc_strategy(alloc_strategy)
316			.with_max_runtime_instances(2)
317			.with_runtime_cache_size(2)
318			.build();
319
320		let runtime_version: sp_version::RuntimeVersion = Self::exec_state_machine(
321			StateMachine::new(
322				state,
323				&mut Default::default(),
324				&executor,
325				"Core_version",
326				&[],
327				&mut Self::build_extensions(executor.clone(), state.recorder()),
328				&runtime_code,
329				CallContext::Offchain,
330			),
331			"Could not find `Core::version` runtime api.",
332		)?;
333
334		let benchmark_api_version = runtime_version
335			.api_version(
336				&<dyn frame_benchmarking::Benchmark<
337					// We need to use any kind of `Block` type to make the compiler happy, not
338					// relevant for the `ID`.
339					sp_runtime::generic::Block<
340						sp_runtime::generic::Header<u32, Hasher>,
341						sp_runtime::generic::UncheckedExtrinsic<(), (), (), ()>,
342					>,
343				> as sp_api::RuntimeApiInfo>::ID,
344			)
345			.ok_or_else(|| ERROR_API_NOT_FOUND)?;
346
347		let (list, storage_info): (Vec<BenchmarkList>, Vec<StorageInfo>) =
348			Self::exec_state_machine(
349				StateMachine::new(
350					state,
351					&mut Default::default(),
352					&executor,
353					"Benchmark_benchmark_metadata",
354					&(self.extra).encode(),
355					&mut Self::build_extensions(executor.clone(), state.recorder()),
356					&runtime_code,
357					CallContext::Offchain,
358				),
359				ERROR_API_NOT_FOUND,
360			)?;
361
362		// Use the benchmark list and the user input to determine the set of benchmarks to run.
363		let benchmarks_to_run = self.select_benchmarks_to_run(list)?;
364
365		if let Some(list_output) = self.list {
366			list_benchmark(benchmarks_to_run, list_output, self.no_csv_header);
367			return Ok(());
368		}
369
370		// Run the benchmarks
371		let mut batches = Vec::new();
372		let mut batches_db = Vec::new();
373		let mut progress_timer = time::Instant::now();
374		// Maps (pallet, extrinsic) to its component ranges.
375		let mut component_ranges = HashMap::<(String, String), Vec<ComponentRange>>::new();
376		let pov_modes =
377			Self::parse_pov_modes(&benchmarks_to_run, &storage_info, self.ignore_unknown_pov_mode)?;
378		let mut failed = Vec::<(String, String)>::new();
379
380		'outer: for (i, SelectedBenchmark { pallet, instance, extrinsic, components, .. }) in
381			benchmarks_to_run.clone().into_iter().enumerate()
382		{
383			log::info!(
384				target: LOG_TARGET,
385				"[{: >3} % ] Starting benchmark: {pallet}::{extrinsic}",
386				(i * 100) / benchmarks_to_run.len(),
387			);
388			let all_components = if components.is_empty() {
389				vec![Default::default()]
390			} else {
391				let mut all_components = Vec::new();
392				for (idx, (name, low, high)) in components.iter().enumerate() {
393					let lowest = self.lowest_range_values.get(idx).cloned().unwrap_or(*low);
394					let highest = self.highest_range_values.get(idx).cloned().unwrap_or(*high);
395
396					let diff =
397						highest.checked_sub(lowest).ok_or("`low` cannot be higher than `high`")?;
398
399					// The slope logic needs at least two points
400					// to compute a slope.
401					if self.steps < 2 {
402						return Err("`steps` must be at least 2.".into());
403					}
404
405					let step_size = (diff as f32 / (self.steps - 1) as f32).max(0.0);
406
407					for s in 0..self.steps {
408						// This is the value we will be testing for component `name`
409						let component_value =
410							((lowest as f32 + step_size * s as f32) as u32).clamp(lowest, highest);
411
412						// Select the max value for all the other components.
413						let c: Vec<(BenchmarkParameter, u32)> = components
414							.iter()
415							.enumerate()
416							.map(|(idx, (n, _, h))| {
417								if n == name {
418									(*n, component_value)
419								} else {
420									(*n, *self.highest_range_values.get(idx).unwrap_or(h))
421								}
422							})
423							.collect();
424						all_components.push(c);
425					}
426
427					component_ranges
428						.entry((pallet.clone(), extrinsic.clone()))
429						.or_default()
430						.push(ComponentRange { name: name.to_string(), min: lowest, max: highest });
431				}
432				all_components
433			};
434
435			// Ensure each benchmark runs for at least its minimum duration.
436			let start = time::Instant::now();
437			let mut first = true;
438
439			loop {
440				for (s, selected_components) in all_components.iter().enumerate() {
441					let params = |verify: bool, repeats: u32| -> Vec<u8> {
442						if benchmark_api_version >= 2 {
443							(
444								pallet.as_bytes(),
445								instance.as_bytes(),
446								extrinsic.as_bytes(),
447								&selected_components.clone(),
448								verify,
449								repeats,
450							)
451								.encode()
452						} else {
453							(
454								pallet.as_bytes(),
455								extrinsic.as_bytes(),
456								&selected_components.clone(),
457								verify,
458								repeats,
459							)
460								.encode()
461						}
462					};
463
464					// Maybe run a verification if we are the first iteration.
465					if !self.no_verify && first {
466						let state = &state_without_tracking;
467						// Don't use these results since verification code will add overhead.
468						let _batch: Vec<BenchmarkBatch> = match Self::exec_state_machine::<
469							std::result::Result<Vec<BenchmarkBatch>, String>,
470							_,
471							_,
472						>(
473							StateMachine::new(
474								state,
475								&mut Default::default(),
476								&executor,
477								"Benchmark_dispatch_benchmark",
478								&params(true, 1),
479								&mut Self::build_extensions(executor.clone(), state.recorder()),
480								&runtime_code,
481								CallContext::Offchain,
482							),
483							"dispatch a benchmark",
484						) {
485							Err(e) => {
486								log::error!(target: LOG_TARGET, "Benchmark {pallet}::{extrinsic} failed: {e}");
487								failed.push((pallet.clone(), extrinsic.clone()));
488								continue 'outer;
489							},
490							Ok(Err(e)) => {
491								log::error!(target: LOG_TARGET, "Benchmark {pallet}::{extrinsic} failed: {e}");
492								failed.push((pallet.clone(), extrinsic.clone()));
493								continue 'outer;
494							},
495							Ok(Ok(b)) => b,
496						};
497					}
498					// Do one loop of DB tracking.
499					{
500						let state = &state_with_tracking;
501						let batch: Vec<BenchmarkBatch> = match Self::exec_state_machine::<
502							std::result::Result<Vec<BenchmarkBatch>, String>,
503							_,
504							_,
505						>(
506							StateMachine::new(
507								state,
508								&mut Default::default(),
509								&executor,
510								"Benchmark_dispatch_benchmark",
511								&params(false, 1),
512								&mut Self::build_extensions(executor.clone(), state.recorder()),
513								&runtime_code,
514								CallContext::Offchain,
515							),
516							"dispatch a benchmark",
517						) {
518							Err(e) => {
519								log::error!(target: LOG_TARGET, "Benchmark {pallet}::{extrinsic} failed: {e}");
520								failed.push((pallet.clone(), extrinsic.clone()));
521								continue 'outer;
522							},
523							Ok(Err(e)) => {
524								log::error!(target: LOG_TARGET, "Benchmark {pallet}::{extrinsic} failed: {e}");
525								failed.push((pallet.clone(), extrinsic.clone()));
526								continue 'outer;
527							},
528							Ok(Ok(b)) => b,
529						};
530
531						batches_db.extend(batch);
532					}
533
534					// Finally run a bunch of loops to get extrinsic timing information.
535					let state = &state_without_tracking;
536					let batch = match Self::exec_state_machine::<
537						std::result::Result<Vec<BenchmarkBatch>, String>,
538						_,
539						_,
540					>(
541						StateMachine::new(
542							state,
543							&mut Default::default(),
544							&executor,
545							"Benchmark_dispatch_benchmark",
546							&params(false, self.repeat),
547							&mut Self::build_extensions(executor.clone(), state.recorder()),
548							&runtime_code,
549							CallContext::Offchain,
550						),
551						"dispatch a benchmark",
552					) {
553						Err(e) => {
554							return Err(
555								format!("Benchmark {pallet}::{extrinsic} failed: {e}").into()
556							);
557						},
558						Ok(Err(e)) => {
559							return Err(
560								format!("Benchmark {pallet}::{extrinsic} failed: {e}").into()
561							);
562						},
563						Ok(Ok(b)) => b,
564					};
565
566					batches.extend(batch);
567
568					// Show progress information at most every 5 seconds.
569					if progress_timer.elapsed() >= time::Duration::from_secs(5) {
570						progress_timer = time::Instant::now();
571
572						let msg = if first {
573							format!(
574								"{}/{}",
575								s + 1, // s starts at 0.
576								all_components.len()
577							)
578						} else {
579							"(overtime)".to_string()
580						};
581
582						log::info!(
583							target: LOG_TARGET,
584							"[{: >3} % ] Running  benchmark: {pallet}::{extrinsic} {msg}",
585							(i * 100) / benchmarks_to_run.len()
586						);
587					}
588				}
589
590				first = false;
591				if start.elapsed() >= Duration::from_secs(self.min_duration) {
592					break;
593				}
594			}
595		}
596
597		if !failed.is_empty() {
598			failed.sort();
599			eprintln!(
600				"The following {} benchmarks failed:\n{}",
601				failed.len(),
602				failed.iter().map(|(p, e)| format!("- {p}::{e}")).collect::<Vec<_>>().join("\n")
603			);
604			return Err(format!("{} benchmarks failed", failed.len()).into());
605		}
606
607		// Combine all of the benchmark results, so that benchmarks of the same pallet/function
608		// are together.
609		let batches = combine_batches(batches, batches_db);
610		self.output(&batches, &storage_info, &component_ranges, pov_modes)
611	}
612
613	fn select_benchmarks_to_run(&self, list: Vec<BenchmarkList>) -> Result<Vec<SelectedBenchmark>> {
614		// Use the benchmark list and the user input to determine the set of benchmarks to run.
615		let mut benchmarks_to_run = Vec::new();
616		list.iter().filter(|item| self.pallet_selected(&item.pallet)).for_each(|item| {
617			for benchmark in &item.benchmarks {
618				if self.extrinsic_selected(&item.pallet, &benchmark.name) {
619					benchmarks_to_run.push((
620						item.pallet.clone(),
621						item.instance.clone(),
622						benchmark.name.clone(),
623						benchmark.components.clone(),
624						benchmark.pov_modes.clone(),
625					))
626				}
627			}
628		});
629		// Convert `Vec<u8>` to `String` for better readability.
630		let benchmarks_to_run: Vec<_> = benchmarks_to_run
631			.into_iter()
632			.map(|(pallet, instance, extrinsic, components, pov_modes)| {
633				let pallet = String::from_utf8(pallet).expect("Encoded from String; qed");
634				let instance = String::from_utf8(instance).expect("Encoded from String; qed");
635				let extrinsic =
636					String::from_utf8(extrinsic.clone()).expect("Encoded from String; qed");
637
638				SelectedBenchmark {
639					pallet,
640					instance,
641					extrinsic,
642					components,
643					pov_modes: pov_modes
644						.into_iter()
645						.map(|(p, s)| {
646							(String::from_utf8(p).unwrap(), String::from_utf8(s).unwrap())
647						})
648						.collect(),
649				}
650			})
651			.collect();
652
653		if benchmarks_to_run.is_empty() {
654			return Err("No benchmarks found which match your input. Try `--list --all` to list all available benchmarks. Make sure pallet is in `define_benchmarks!`".into());
655		}
656
657		Ok(benchmarks_to_run)
658	}
659
660	/// Whether this pallet should be run.
661	fn pallet_selected(&self, pallet: &Vec<u8>) -> bool {
662		let include = self.pallets.clone();
663
664		let included = include.is_empty() ||
665			include.iter().any(|p| p.as_bytes() == pallet) ||
666			include.iter().any(|p| p == "*") ||
667			include.iter().any(|p| p == "all");
668		let excluded = self.exclude_pallets.iter().any(|p| p.as_bytes() == pallet);
669
670		included && !excluded
671	}
672
673	/// Whether this extrinsic should be run.
674	fn extrinsic_selected(&self, pallet: &Vec<u8>, extrinsic: &Vec<u8>) -> bool {
675		if !self.pallet_selected(pallet) {
676			return false;
677		}
678
679		let extrinsic_filter = self.extrinsic.clone().unwrap_or_default();
680		let extrinsic_split: Vec<&str> = extrinsic_filter.split(',').collect();
681		let extrinsics: Vec<_> = extrinsic_split.iter().map(|x| x.trim().as_bytes()).collect();
682
683		let included = extrinsic_filter.is_empty() ||
684			extrinsic_filter == "*" ||
685			extrinsic_filter == "all" ||
686			extrinsics.contains(&&extrinsic[..]);
687
688		let excluded = self
689			.excluded_extrinsics()
690			.iter()
691			.any(|(p, e)| p.as_bytes() == pallet && e.as_bytes() == extrinsic);
692
693		included && !excluded
694	}
695
696	/// All `(pallet, extrinsic)` tuples that are excluded from the benchmarks.
697	fn excluded_extrinsics(&self) -> Vec<(String, String)> {
698		let mut excluded = Vec::new();
699
700		for e in &self.exclude_extrinsics {
701			let splits = e.split("::").collect::<Vec<_>>();
702			if splits.len() != 2 {
703				panic!("Invalid argument for '--exclude-extrinsics'. Expected format: 'pallet::extrinsic' but got '{}'", e);
704			}
705			excluded.push((splits[0].to_string(), splits[1].to_string()));
706		}
707
708		excluded
709	}
710
711	/// Execute a state machine and decode its return value as `R`.
712	fn exec_state_machine<R: Decode, H: Hash, Exec: CodeExecutor>(
713		mut machine: StateMachine<BenchmarkingState<H>, H, Exec>,
714		hint: &str,
715	) -> Result<R> {
716		let res = machine
717			.execute()
718			.map_err(|e| format!("Could not call runtime API to {hint}: {}", e))?;
719		let res = R::decode(&mut &res[..])
720			.map_err(|e| format!("Failed to decode runtime API result to {hint}: {:?}", e))?;
721		Ok(res)
722	}
723
724	/// Build the extension that are available for pallet benchmarks.
725	fn build_extensions<E: CodeExecutor, H: Hasher + 'static>(
726		exe: E,
727		maybe_recorder: Option<Recorder<H>>,
728	) -> Extensions {
729		let mut extensions = Extensions::default();
730		let (offchain, _) = TestOffchainExt::new();
731		let (pool, _) = TestTransactionPoolExt::new();
732		let keystore = MemoryKeystore::new();
733		extensions.register(KeystoreExt::new(keystore));
734		extensions.register(OffchainWorkerExt::new(offchain.clone()));
735		extensions.register(OffchainDbExt::new(offchain));
736		extensions.register(TransactionPoolExt::new(pool));
737		extensions.register(ReadRuntimeVersionExt::new(exe));
738		if let Some(recorder) = maybe_recorder {
739			extensions.register(ProofSizeExt::new(recorder));
740		}
741		extensions
742	}
743
744	/// Load the runtime blob for this benchmark.
745	///
746	/// The blob will either be loaded from the `:code` key out of the chain spec, or from a file
747	/// when specified with `--runtime`.
748	fn runtime_blob<'a, H: Hash>(
749		&self,
750		state: &'a BenchmarkingState<H>,
751	) -> Result<FetchedCode<'a, BenchmarkingState<H>, H>> {
752		if let Some(runtime) = self.runtime.as_ref() {
753			log::debug!(target: LOG_TARGET, "Loading WASM from file {}", runtime.display());
754			let code = fs::read(runtime).map_err(|e| {
755				format!(
756					"Could not load runtime file from path: {}, error: {}",
757					runtime.display(),
758					e
759				)
760			})?;
761			let hash = sp_core::blake2_256(&code).to_vec();
762			let wrapped_code = WrappedRuntimeCode(Cow::Owned(code));
763
764			Ok(FetchedCode::FromFile { wrapped_code, heap_pages: self.heap_pages, hash })
765		} else {
766			log::info!(target: LOG_TARGET, "Loading WASM from state");
767			let state =
768				sp_state_machine::backend::BackendRuntimeCode::new(state, TryPendingCode::No);
769
770			Ok(FetchedCode::FromGenesis { state })
771		}
772	}
773
774	/// Allocation strategy for pallet benchmarking.
775	fn alloc_strategy(&self, runtime_heap_pages: Option<u64>) -> HeapAllocStrategy {
776		self.heap_pages.or(runtime_heap_pages).map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |p| {
777			HeapAllocStrategy::Static { extra_pages: p as _ }
778		})
779	}
780
781	fn output(
782		&self,
783		batches: &[BenchmarkBatchSplitResults],
784		storage_info: &[StorageInfo],
785		component_ranges: &ComponentRangeMap,
786		pov_modes: PovModesMap,
787	) -> Result<()> {
788		// Jsonify the result and write it to a file or stdout if desired.
789		if !self.jsonify(&batches)? && !self.quiet {
790			// Print the summary only if `jsonify` did not write to stdout.
791			self.print_summary(&batches, &storage_info, pov_modes.clone())
792		}
793
794		// Create the weights.rs file.
795		if let Some(output_path) = &self.output {
796			writer::write_results(
797				&batches,
798				&storage_info,
799				&component_ranges,
800				pov_modes,
801				self.default_pov_mode,
802				output_path,
803				self,
804			)?;
805		}
806
807		Ok(())
808	}
809
810	/// Re-analyze a batch historic benchmark timing data. Will not take the PoV into account.
811	fn output_from_results(&self, batches: &[BenchmarkBatchSplitResults]) -> Result<()> {
812		let mut component_ranges = HashMap::<(String, String), HashMap<String, (u32, u32)>>::new();
813		for batch in batches {
814			let range = component_ranges
815				.entry((
816					String::from_utf8(batch.pallet.clone()).unwrap(),
817					String::from_utf8(batch.benchmark.clone()).unwrap(),
818				))
819				.or_default();
820			for result in &batch.time_results {
821				for (param, value) in &result.components {
822					let name = param.to_string();
823					let (ref mut min, ref mut max) = range.entry(name).or_insert((*value, *value));
824					if *value < *min {
825						*min = *value;
826					}
827					if *value > *max {
828						*max = *value;
829					}
830				}
831			}
832		}
833
834		let component_ranges: HashMap<_, _> = component_ranges
835			.into_iter()
836			.map(|(key, ranges)| {
837				let ranges = ranges
838					.into_iter()
839					.map(|(name, (min, max))| ComponentRange { name, min, max })
840					.collect();
841				(key, ranges)
842			})
843			.collect();
844
845		self.output(batches, &[], &component_ranges, Default::default())
846	}
847
848	/// Jsonifies the passed batches and writes them to stdout or into a file.
849	/// Can be configured via `--json` and `--json-file`.
850	/// Returns whether it wrote to stdout.
851	fn jsonify(&self, batches: &[BenchmarkBatchSplitResults]) -> Result<bool> {
852		if self.json_output || self.json_file.is_some() {
853			let json = serde_json::to_string_pretty(&batches)
854				.map_err(|e| format!("Serializing into JSON: {:?}", e))?;
855
856			if let Some(path) = &self.json_file {
857				fs::write(path, json)?;
858			} else {
859				print!("{json}");
860				return Ok(true);
861			}
862		}
863
864		Ok(false)
865	}
866
867	/// Prints the results as human-readable summary without raw timing data.
868	fn print_summary(
869		&self,
870		batches: &[BenchmarkBatchSplitResults],
871		storage_info: &[StorageInfo],
872		pov_modes: PovModesMap,
873	) {
874		for batch in batches.iter() {
875			// Print benchmark metadata
876			let pallet = String::from_utf8(batch.pallet.clone()).expect("Encoded from String; qed");
877			let benchmark =
878				String::from_utf8(batch.benchmark.clone()).expect("Encoded from String; qed");
879			println!(
880					"Pallet: {:?}, Extrinsic: {:?}, Lowest values: {:?}, Highest values: {:?}, Steps: {:?}, Repeat: {:?}",
881					pallet,
882					benchmark,
883					self.lowest_range_values,
884					self.highest_range_values,
885					self.steps,
886					self.repeat,
887				);
888
889			// Skip raw data + analysis if there are no results
890			if batch.time_results.is_empty() {
891				continue;
892			}
893
894			if !self.no_storage_info {
895				let mut storage_per_prefix = HashMap::<Vec<u8>, Vec<BenchmarkResult>>::new();
896				let pov_mode =
897					pov_modes.get(&(pallet, benchmark.clone())).cloned().unwrap_or_default();
898
899				let comments = writer::process_storage_results(
900					&mut storage_per_prefix,
901					&batch.db_results,
902					storage_info,
903					&pov_mode,
904					self.default_pov_mode,
905					self.worst_case_map_values,
906					self.additional_trie_layers,
907				);
908				println!("Raw Storage Info\n========");
909				for comment in comments {
910					println!("{}", comment);
911				}
912				println!();
913			}
914
915			// Conduct analysis.
916			if !self.no_median_slopes {
917				println!("Median Slopes Analysis\n========");
918				match Analysis::median_slopes(&batch.time_results, BenchmarkSelector::ExtrinsicTime)
919				{
920					Ok(analysis) => println!("-- Extrinsic Time --\n{}", analysis),
921					Err(err) => println!("-- Extrinsic Time --\nError: {:?}", err),
922				}
923				match Analysis::median_slopes(&batch.db_results, BenchmarkSelector::Reads) {
924					Ok(analysis) => println!("Reads = {:?}", analysis),
925					Err(err) => println!("Reads: Error: {:?}", err),
926				}
927				match Analysis::median_slopes(&batch.db_results, BenchmarkSelector::Writes) {
928					Ok(analysis) => println!("Writes = {:?}", analysis),
929					Err(err) => println!("Writes: Error: {:?}", err),
930				}
931				match Analysis::median_slopes(&batch.db_results, BenchmarkSelector::ProofSize) {
932					Ok(analysis) => println!("Recorded proof Size = {:?}", analysis),
933					Err(err) => println!("Recorded proof Size: Error: {:?}", err),
934				}
935				println!();
936			}
937			if !self.no_min_squares {
938				println!("Min Squares Analysis\n========");
939				match Analysis::min_squares_iqr(
940					&batch.time_results,
941					BenchmarkSelector::ExtrinsicTime,
942				) {
943					Ok(analysis) => println!("-- Extrinsic Time --\n{}", analysis),
944					Err(err) => println!("-- Extrinsic Time --\nError: {:?}", err),
945				}
946				match Analysis::min_squares_iqr(&batch.db_results, BenchmarkSelector::Reads) {
947					Ok(analysis) => println!("Reads = {:?}", analysis),
948					Err(err) => println!("Reads: Error: {:?}", err),
949				}
950				match Analysis::min_squares_iqr(&batch.db_results, BenchmarkSelector::Writes) {
951					Ok(analysis) => println!("Writes = {:?}", analysis),
952					Err(err) => println!("Writes: Error: {:?}", err),
953				}
954				match Analysis::min_squares_iqr(&batch.db_results, BenchmarkSelector::ProofSize) {
955					Ok(analysis) => println!("Recorded proof Size = {:?}", analysis),
956					Err(err) => println!("Recorded proof Size: Error: {:?}", err),
957				}
958				println!();
959			}
960		}
961	}
962
963	/// Parses the PoV modes per benchmark that were specified by the `#[pov_mode]` attribute.
964	fn parse_pov_modes(
965		benchmarks: &Vec<SelectedBenchmark>,
966		storage_info: &[StorageInfo],
967		ignore_unknown_pov_mode: bool,
968	) -> Result<PovModesMap> {
969		use std::collections::hash_map::Entry;
970		let mut parsed = PovModesMap::new();
971
972		for SelectedBenchmark { pallet, extrinsic, pov_modes, .. } in benchmarks {
973			for (pallet_storage, mode) in pov_modes {
974				let mode = PovEstimationMode::from_str(&mode)?;
975				let pallet_storage = pallet_storage.replace(" ", "");
976				let splits = pallet_storage.split("::").collect::<Vec<_>>();
977
978				if splits.is_empty() || splits.len() > 2 {
979					return Err(format!(
980						"Expected 'Pallet::Storage' as storage name but got: {}",
981						pallet_storage
982					)
983					.into());
984				}
985				let (pov_pallet, pov_storage) =
986					(splits[0].trim(), splits.get(1).unwrap_or(&"ALL").trim());
987
988				match parsed
989					.entry((pallet.clone(), extrinsic.clone()))
990					.or_default()
991					.entry((pov_pallet.to_string(), pov_storage.to_string()))
992				{
993					Entry::Occupied(_) => {
994						return Err(format!(
995							"Cannot specify pov_mode tag twice for the same key: {}",
996							pallet_storage
997						)
998						.into())
999					},
1000					Entry::Vacant(e) => {
1001						e.insert(mode);
1002					},
1003				}
1004			}
1005		}
1006		log::debug!("Parsed PoV modes: {:?}", parsed);
1007		Self::check_pov_modes(&parsed, storage_info, ignore_unknown_pov_mode)?;
1008
1009		Ok(parsed)
1010	}
1011
1012	fn check_pov_modes(
1013		pov_modes: &PovModesMap,
1014		storage_info: &[StorageInfo],
1015		ignore_unknown_pov_mode: bool,
1016	) -> Result<()> {
1017		// Check that all PoV modes are valid pallet storage keys
1018		for (pallet, storage) in pov_modes.values().flat_map(|i| i.keys()) {
1019			let (mut found_pallet, mut found_storage) = (false, false);
1020
1021			for info in storage_info {
1022				if pallet == "ALL" || info.pallet_name == pallet.as_bytes() {
1023					found_pallet = true;
1024				}
1025				if storage == "ALL" || info.storage_name == storage.as_bytes() {
1026					found_storage = true;
1027				}
1028			}
1029			if !found_pallet || !found_storage {
1030				let err = format!("The PoV mode references an unknown storage item or pallet: `{}::{}`. You can ignore this warning by specifying `--ignore-unknown-pov-mode`", pallet, storage);
1031
1032				if ignore_unknown_pov_mode {
1033					log::warn!(target: LOG_TARGET, "Error demoted to warning due to `--ignore-unknown-pov-mode`: {}", err);
1034				} else {
1035					return Err(err.into());
1036				}
1037			}
1038		}
1039
1040		Ok(())
1041	}
1042
1043	/// Sanity check the CLI arguments.
1044	fn check_args(
1045		&self,
1046		chain_spec: &Option<Box<dyn ChainSpec>>,
1047	) -> std::result::Result<(), (ErrorKind, String)> {
1048		if self.runtime.is_some() && self.shared_params.chain.is_some() {
1049			unreachable!("Clap should not allow both `--runtime` and `--chain` to be provided.")
1050		}
1051
1052		if self.external_repeat.is_some() {
1053			log::warn!(target: LOG_TARGET, "The `--external-repeat` argument is deprecated and will be removed in a future release.");
1054		}
1055
1056		if chain_spec.is_none() && self.runtime.is_none() && self.shared_params.chain.is_none() {
1057			return Err((
1058				ErrorKind::MissingRequiredArgument,
1059				"Provide either a runtime via `--runtime` or a chain spec via `--chain`"
1060					.to_string(),
1061			));
1062		}
1063
1064		match self.genesis_builder {
1065			Some(GenesisBuilderPolicy::SpecGenesis | GenesisBuilderPolicy::SpecRuntime) => {
1066				if chain_spec.is_none() && self.shared_params.chain.is_none() {
1067					return Err((
1068						ErrorKind::MissingRequiredArgument,
1069						"Provide a chain spec via `--chain`.".to_string(),
1070					));
1071				}
1072			},
1073			_ => {},
1074		}
1075
1076		if let Some(output_path) = &self.output {
1077			if !output_path.is_dir() && output_path.file_name().is_none() {
1078				return Err((
1079					ErrorKind::InvalidValue,
1080					format!("Output path is neither a directory nor a file: {output_path:?}"),
1081				));
1082			}
1083		}
1084
1085		if let Some(header_file) = &self.header {
1086			if !header_file.is_file() {
1087				return Err((
1088					ErrorKind::InvalidValue,
1089					format!("Header file could not be found: {header_file:?}"),
1090				));
1091			};
1092		}
1093
1094		if let Some(handlebars_template_file) = &self.template {
1095			if !handlebars_template_file.is_file() {
1096				return Err((
1097					ErrorKind::InvalidValue,
1098					format!(
1099						"Handlebars template file could not be found: {handlebars_template_file:?}"
1100					),
1101				));
1102			};
1103		}
1104		Ok(())
1105	}
1106}
1107
1108impl CliConfiguration for PalletCmd {
1109	fn shared_params(&self) -> &SharedParams {
1110		&self.shared_params
1111	}
1112
1113	fn chain_id(&self, _is_dev: bool) -> Result<String> {
1114		Ok(match self.shared_params.chain {
1115			Some(ref chain) => chain.clone(),
1116			None => "dev".into(),
1117		})
1118	}
1119}
1120
1121/// List the benchmarks available in the runtime, in a CSV friendly format.
1122fn list_benchmark(
1123	benchmarks_to_run: Vec<SelectedBenchmark>,
1124	list_output: ListOutput,
1125	no_csv_header: bool,
1126) {
1127	let mut benchmarks = BTreeMap::new();
1128
1129	// Sort and de-dub by pallet and function name.
1130	benchmarks_to_run.iter().for_each(|bench| {
1131		benchmarks
1132			.entry(&bench.pallet)
1133			.or_insert_with(BTreeSet::new)
1134			.insert(&bench.extrinsic);
1135	});
1136
1137	match list_output {
1138		ListOutput::All => {
1139			if !no_csv_header {
1140				println!("pallet, extrinsic");
1141			}
1142			for (pallet, extrinsics) in benchmarks {
1143				for extrinsic in extrinsics {
1144					println!("{pallet}, {extrinsic}");
1145				}
1146			}
1147		},
1148		ListOutput::Pallets => {
1149			if !no_csv_header {
1150				println!("pallet");
1151			};
1152			for pallet in benchmarks.keys() {
1153				println!("{pallet}");
1154			}
1155		},
1156	}
1157}
1158#[cfg(test)]
1159mod tests {
1160	use crate::pallet::PalletCmd;
1161	use clap::Parser;
1162
1163	fn cli_succeed(args: &[&str]) -> Result<(), clap::Error> {
1164		let cmd = PalletCmd::try_parse_from(args)?;
1165		assert!(cmd.check_args(&None).is_ok());
1166		Ok(())
1167	}
1168
1169	fn cli_fail(args: &[&str]) {
1170		let cmd = PalletCmd::try_parse_from(args);
1171		if let Ok(cmd) = cmd {
1172			assert!(cmd.check_args(&None).is_err());
1173		}
1174	}
1175
1176	#[test]
1177	fn test_cli_conflicts() -> Result<(), clap::Error> {
1178		// Runtime tests
1179		cli_succeed(&[
1180			"test",
1181			"--extrinsic",
1182			"",
1183			"--pallet",
1184			"",
1185			"--runtime",
1186			"path/to/runtime",
1187			"--genesis-builder",
1188			"runtime",
1189		])?;
1190		cli_succeed(&[
1191			"test",
1192			"--extrinsic",
1193			"",
1194			"--pallet",
1195			"",
1196			"--runtime",
1197			"path/to/runtime",
1198			"--genesis-builder",
1199			"none",
1200		])?;
1201		cli_succeed(&["test", "--extrinsic", "", "--pallet", "", "--runtime", "path/to/runtime"])?;
1202		cli_succeed(&[
1203			"test",
1204			"--extrinsic",
1205			"",
1206			"--pallet",
1207			"",
1208			"--runtime",
1209			"path/to/runtime",
1210			"--genesis-builder-preset",
1211			"preset",
1212		])?;
1213		cli_succeed(&[
1214			"test",
1215			"--extrinsic",
1216			"",
1217			"--pallet",
1218			"",
1219			"--runtime",
1220			"path/to/runtime",
1221			"--genesis-patch",
1222			"path/to/patch.json",
1223		])?;
1224		cli_fail(&[
1225			"test",
1226			"--extrinsic",
1227			"",
1228			"--pallet",
1229			"",
1230			"--runtime",
1231			"path/to/runtime",
1232			"--genesis-builder",
1233			"spec",
1234		]);
1235		cli_fail(&[
1236			"test",
1237			"--extrinsic",
1238			"",
1239			"--pallet",
1240			"",
1241			"--runtime",
1242			"path/to/spec",
1243			"--genesis-builder",
1244			"spec-genesis",
1245		]);
1246		cli_fail(&[
1247			"test",
1248			"--extrinsic",
1249			"",
1250			"--pallet",
1251			"",
1252			"--runtime",
1253			"path/to/spec",
1254			"--genesis-builder",
1255			"spec-runtime",
1256		]);
1257		cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec-genesis"]);
1258
1259		// Spec tests
1260		cli_succeed(&["test", "--extrinsic", "", "--pallet", "", "--chain", "path/to/spec"])?;
1261		cli_succeed(&[
1262			"test",
1263			"--extrinsic",
1264			"",
1265			"--pallet",
1266			"",
1267			"--chain",
1268			"path/to/spec",
1269			"--genesis-builder",
1270			"spec",
1271		])?;
1272		cli_succeed(&[
1273			"test",
1274			"--extrinsic",
1275			"",
1276			"--pallet",
1277			"",
1278			"--chain",
1279			"path/to/spec",
1280			"--genesis-builder",
1281			"spec-genesis",
1282		])?;
1283		cli_succeed(&[
1284			"test",
1285			"--extrinsic",
1286			"",
1287			"--pallet",
1288			"",
1289			"--chain",
1290			"path/to/spec",
1291			"--genesis-builder",
1292			"spec-runtime",
1293		])?;
1294		cli_succeed(&[
1295			"test",
1296			"--extrinsic",
1297			"",
1298			"--pallet",
1299			"",
1300			"--chain",
1301			"path/to/spec",
1302			"--genesis-builder",
1303			"none",
1304		])?;
1305		cli_fail(&[
1306			"test",
1307			"--extrinsic",
1308			"",
1309			"--pallet",
1310			"",
1311			"--chain",
1312			"path/to/spec",
1313			"--genesis-builder",
1314			"runtime",
1315		]);
1316		cli_fail(&[
1317			"test",
1318			"--extrinsic",
1319			"",
1320			"--pallet",
1321			"",
1322			"--chain",
1323			"path/to/spec",
1324			"--genesis-builder",
1325			"runtime",
1326			"--genesis-builder-preset",
1327			"preset",
1328		]);
1329		Ok(())
1330	}
1331}