Skip to main content

pezframe_benchmarking_cli/pezpallet/
command.rs

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