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 = pov_modes.get(&(pallet, benchmark)).cloned().unwrap_or_default();
897
898				let comments = writer::process_storage_results(
899					&mut storage_per_prefix,
900					&batch.db_results,
901					storage_info,
902					&pov_mode,
903					self.default_pov_mode,
904					self.worst_case_map_values,
905					self.additional_trie_layers,
906				);
907				println!("Raw Storage Info\n========");
908				for comment in comments {
909					println!("{}", comment);
910				}
911				println!();
912			}
913
914			// Conduct analysis.
915			if !self.no_median_slopes {
916				println!("Median Slopes Analysis\n========");
917				if let Some(analysis) =
918					Analysis::median_slopes(&batch.time_results, BenchmarkSelector::ExtrinsicTime)
919				{
920					println!("-- Extrinsic Time --\n{}", analysis);
921				}
922				if let Some(analysis) =
923					Analysis::median_slopes(&batch.db_results, BenchmarkSelector::Reads)
924				{
925					println!("Reads = {:?}", analysis);
926				}
927				if let Some(analysis) =
928					Analysis::median_slopes(&batch.db_results, BenchmarkSelector::Writes)
929				{
930					println!("Writes = {:?}", analysis);
931				}
932				if let Some(analysis) =
933					Analysis::median_slopes(&batch.db_results, BenchmarkSelector::ProofSize)
934				{
935					println!("Recorded proof Size = {:?}", analysis);
936				}
937				println!();
938			}
939			if !self.no_min_squares {
940				println!("Min Squares Analysis\n========");
941				if let Some(analysis) =
942					Analysis::min_squares_iqr(&batch.time_results, BenchmarkSelector::ExtrinsicTime)
943				{
944					println!("-- Extrinsic Time --\n{}", analysis);
945				}
946				if let Some(analysis) =
947					Analysis::min_squares_iqr(&batch.db_results, BenchmarkSelector::Reads)
948				{
949					println!("Reads = {:?}", analysis);
950				}
951				if let Some(analysis) =
952					Analysis::min_squares_iqr(&batch.db_results, BenchmarkSelector::Writes)
953				{
954					println!("Writes = {:?}", analysis);
955				}
956				if let Some(analysis) =
957					Analysis::min_squares_iqr(&batch.db_results, BenchmarkSelector::ProofSize)
958				{
959					println!("Recorded proof Size = {:?}", analysis);
960				}
961				println!();
962			}
963		}
964	}
965
966	/// Parses the PoV modes per benchmark that were specified by the `#[pov_mode]` attribute.
967	fn parse_pov_modes(
968		benchmarks: &Vec<SelectedBenchmark>,
969		storage_info: &[StorageInfo],
970		ignore_unknown_pov_mode: bool,
971	) -> Result<PovModesMap> {
972		use std::collections::hash_map::Entry;
973		let mut parsed = PovModesMap::new();
974
975		for SelectedBenchmark { pallet, extrinsic, pov_modes, .. } in benchmarks {
976			for (pallet_storage, mode) in pov_modes {
977				let mode = PovEstimationMode::from_str(&mode)?;
978				let pallet_storage = pallet_storage.replace(" ", "");
979				let splits = pallet_storage.split("::").collect::<Vec<_>>();
980
981				if splits.is_empty() || splits.len() > 2 {
982					return Err(format!(
983						"Expected 'Pallet::Storage' as storage name but got: {}",
984						pallet_storage
985					)
986					.into());
987				}
988				let (pov_pallet, pov_storage) =
989					(splits[0].trim(), splits.get(1).unwrap_or(&"ALL").trim());
990
991				match parsed
992					.entry((pallet.clone(), extrinsic.clone()))
993					.or_default()
994					.entry((pov_pallet.to_string(), pov_storage.to_string()))
995				{
996					Entry::Occupied(_) => {
997						return Err(format!(
998							"Cannot specify pov_mode tag twice for the same key: {}",
999							pallet_storage
1000						)
1001						.into())
1002					},
1003					Entry::Vacant(e) => {
1004						e.insert(mode);
1005					},
1006				}
1007			}
1008		}
1009		log::debug!("Parsed PoV modes: {:?}", parsed);
1010		Self::check_pov_modes(&parsed, storage_info, ignore_unknown_pov_mode)?;
1011
1012		Ok(parsed)
1013	}
1014
1015	fn check_pov_modes(
1016		pov_modes: &PovModesMap,
1017		storage_info: &[StorageInfo],
1018		ignore_unknown_pov_mode: bool,
1019	) -> Result<()> {
1020		// Check that all PoV modes are valid pallet storage keys
1021		for (pallet, storage) in pov_modes.values().flat_map(|i| i.keys()) {
1022			let (mut found_pallet, mut found_storage) = (false, false);
1023
1024			for info in storage_info {
1025				if pallet == "ALL" || info.pallet_name == pallet.as_bytes() {
1026					found_pallet = true;
1027				}
1028				if storage == "ALL" || info.storage_name == storage.as_bytes() {
1029					found_storage = true;
1030				}
1031			}
1032			if !found_pallet || !found_storage {
1033				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);
1034
1035				if ignore_unknown_pov_mode {
1036					log::warn!(target: LOG_TARGET, "Error demoted to warning due to `--ignore-unknown-pov-mode`: {}", err);
1037				} else {
1038					return Err(err.into());
1039				}
1040			}
1041		}
1042
1043		Ok(())
1044	}
1045
1046	/// Sanity check the CLI arguments.
1047	fn check_args(
1048		&self,
1049		chain_spec: &Option<Box<dyn ChainSpec>>,
1050	) -> std::result::Result<(), (ErrorKind, String)> {
1051		if self.runtime.is_some() && self.shared_params.chain.is_some() {
1052			unreachable!("Clap should not allow both `--runtime` and `--chain` to be provided.")
1053		}
1054
1055		if self.external_repeat.is_some() {
1056			log::warn!(target: LOG_TARGET, "The `--external-repeat` argument is deprecated and will be removed in a future release.");
1057		}
1058
1059		if chain_spec.is_none() && self.runtime.is_none() && self.shared_params.chain.is_none() {
1060			return Err((
1061				ErrorKind::MissingRequiredArgument,
1062				"Provide either a runtime via `--runtime` or a chain spec via `--chain`"
1063					.to_string(),
1064			));
1065		}
1066
1067		match self.genesis_builder {
1068			Some(GenesisBuilderPolicy::SpecGenesis | GenesisBuilderPolicy::SpecRuntime) => {
1069				if chain_spec.is_none() && self.shared_params.chain.is_none() {
1070					return Err((
1071						ErrorKind::MissingRequiredArgument,
1072						"Provide a chain spec via `--chain`.".to_string(),
1073					));
1074				}
1075			},
1076			_ => {},
1077		}
1078
1079		if let Some(output_path) = &self.output {
1080			if !output_path.is_dir() && output_path.file_name().is_none() {
1081				return Err((
1082					ErrorKind::InvalidValue,
1083					format!("Output path is neither a directory nor a file: {output_path:?}"),
1084				));
1085			}
1086		}
1087
1088		if let Some(header_file) = &self.header {
1089			if !header_file.is_file() {
1090				return Err((
1091					ErrorKind::InvalidValue,
1092					format!("Header file could not be found: {header_file:?}"),
1093				));
1094			};
1095		}
1096
1097		if let Some(handlebars_template_file) = &self.template {
1098			if !handlebars_template_file.is_file() {
1099				return Err((
1100					ErrorKind::InvalidValue,
1101					format!(
1102						"Handlebars template file could not be found: {handlebars_template_file:?}"
1103					),
1104				));
1105			};
1106		}
1107		Ok(())
1108	}
1109}
1110
1111impl CliConfiguration for PalletCmd {
1112	fn shared_params(&self) -> &SharedParams {
1113		&self.shared_params
1114	}
1115
1116	fn chain_id(&self, _is_dev: bool) -> Result<String> {
1117		Ok(match self.shared_params.chain {
1118			Some(ref chain) => chain.clone(),
1119			None => "dev".into(),
1120		})
1121	}
1122}
1123
1124/// List the benchmarks available in the runtime, in a CSV friendly format.
1125fn list_benchmark(
1126	benchmarks_to_run: Vec<SelectedBenchmark>,
1127	list_output: ListOutput,
1128	no_csv_header: bool,
1129) {
1130	let mut benchmarks = BTreeMap::new();
1131
1132	// Sort and de-dub by pallet and function name.
1133	benchmarks_to_run.iter().for_each(|bench| {
1134		benchmarks
1135			.entry(&bench.pallet)
1136			.or_insert_with(BTreeSet::new)
1137			.insert(&bench.extrinsic);
1138	});
1139
1140	match list_output {
1141		ListOutput::All => {
1142			if !no_csv_header {
1143				println!("pallet, extrinsic");
1144			}
1145			for (pallet, extrinsics) in benchmarks {
1146				for extrinsic in extrinsics {
1147					println!("{pallet}, {extrinsic}");
1148				}
1149			}
1150		},
1151		ListOutput::Pallets => {
1152			if !no_csv_header {
1153				println!("pallet");
1154			};
1155			for pallet in benchmarks.keys() {
1156				println!("{pallet}");
1157			}
1158		},
1159	}
1160}
1161#[cfg(test)]
1162mod tests {
1163	use crate::pallet::PalletCmd;
1164	use clap::Parser;
1165
1166	fn cli_succeed(args: &[&str]) -> Result<(), clap::Error> {
1167		let cmd = PalletCmd::try_parse_from(args)?;
1168		assert!(cmd.check_args(&None).is_ok());
1169		Ok(())
1170	}
1171
1172	fn cli_fail(args: &[&str]) {
1173		let cmd = PalletCmd::try_parse_from(args);
1174		if let Ok(cmd) = cmd {
1175			assert!(cmd.check_args(&None).is_err());
1176		}
1177	}
1178
1179	#[test]
1180	fn test_cli_conflicts() -> Result<(), clap::Error> {
1181		// Runtime tests
1182		cli_succeed(&[
1183			"test",
1184			"--extrinsic",
1185			"",
1186			"--pallet",
1187			"",
1188			"--runtime",
1189			"path/to/runtime",
1190			"--genesis-builder",
1191			"runtime",
1192		])?;
1193		cli_succeed(&[
1194			"test",
1195			"--extrinsic",
1196			"",
1197			"--pallet",
1198			"",
1199			"--runtime",
1200			"path/to/runtime",
1201			"--genesis-builder",
1202			"none",
1203		])?;
1204		cli_succeed(&["test", "--extrinsic", "", "--pallet", "", "--runtime", "path/to/runtime"])?;
1205		cli_succeed(&[
1206			"test",
1207			"--extrinsic",
1208			"",
1209			"--pallet",
1210			"",
1211			"--runtime",
1212			"path/to/runtime",
1213			"--genesis-builder-preset",
1214			"preset",
1215		])?;
1216		cli_succeed(&[
1217			"test",
1218			"--extrinsic",
1219			"",
1220			"--pallet",
1221			"",
1222			"--runtime",
1223			"path/to/runtime",
1224			"--genesis-patch",
1225			"path/to/patch.json",
1226		])?;
1227		cli_fail(&[
1228			"test",
1229			"--extrinsic",
1230			"",
1231			"--pallet",
1232			"",
1233			"--runtime",
1234			"path/to/runtime",
1235			"--genesis-builder",
1236			"spec",
1237		]);
1238		cli_fail(&[
1239			"test",
1240			"--extrinsic",
1241			"",
1242			"--pallet",
1243			"",
1244			"--runtime",
1245			"path/to/spec",
1246			"--genesis-builder",
1247			"spec-genesis",
1248		]);
1249		cli_fail(&[
1250			"test",
1251			"--extrinsic",
1252			"",
1253			"--pallet",
1254			"",
1255			"--runtime",
1256			"path/to/spec",
1257			"--genesis-builder",
1258			"spec-runtime",
1259		]);
1260		cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec-genesis"]);
1261
1262		// Spec tests
1263		cli_succeed(&["test", "--extrinsic", "", "--pallet", "", "--chain", "path/to/spec"])?;
1264		cli_succeed(&[
1265			"test",
1266			"--extrinsic",
1267			"",
1268			"--pallet",
1269			"",
1270			"--chain",
1271			"path/to/spec",
1272			"--genesis-builder",
1273			"spec",
1274		])?;
1275		cli_succeed(&[
1276			"test",
1277			"--extrinsic",
1278			"",
1279			"--pallet",
1280			"",
1281			"--chain",
1282			"path/to/spec",
1283			"--genesis-builder",
1284			"spec-genesis",
1285		])?;
1286		cli_succeed(&[
1287			"test",
1288			"--extrinsic",
1289			"",
1290			"--pallet",
1291			"",
1292			"--chain",
1293			"path/to/spec",
1294			"--genesis-builder",
1295			"spec-runtime",
1296		])?;
1297		cli_succeed(&[
1298			"test",
1299			"--extrinsic",
1300			"",
1301			"--pallet",
1302			"",
1303			"--chain",
1304			"path/to/spec",
1305			"--genesis-builder",
1306			"none",
1307		])?;
1308		cli_fail(&[
1309			"test",
1310			"--extrinsic",
1311			"",
1312			"--pallet",
1313			"",
1314			"--chain",
1315			"path/to/spec",
1316			"--genesis-builder",
1317			"runtime",
1318		]);
1319		cli_fail(&[
1320			"test",
1321			"--extrinsic",
1322			"",
1323			"--pallet",
1324			"",
1325			"--chain",
1326			"path/to/spec",
1327			"--genesis-builder",
1328			"runtime",
1329			"--genesis-builder-preset",
1330			"preset",
1331		]);
1332		Ok(())
1333	}
1334}