referrerpolicy=no-referrer-when-downgrade

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