1use 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#[derive(clap::ValueEnum, Debug, Eq, PartialEq, Clone, Copy)]
72pub enum PovEstimationMode {
73 MaxEncodedLen,
75 Measured,
77 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
94pub(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
107fn 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 let key = (pallet, instance, benchmark);
125
126 match all_benchmarks.get_mut(&key) {
127 Some(x) => x.1.extend(results),
129 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 let key = (pallet, instance, benchmark);
141
142 match all_benchmarks.get_mut(&key) {
143 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
157const 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 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 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 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 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 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 true,
270 true,
272 )?;
273
274 let state_without_tracking = BenchmarkingState::<Hasher>::new(
275 genesis_storage,
276 cache_size,
277 !self.disable_proof_recording,
279 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 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 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 let mut batches = Vec::new();
352 let mut batches_db = Vec::new();
353 let mut timer = time::SystemTime::now();
354 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 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 let component_value =
390 ((lowest as f32 + step_size * s as f32) as u32).clamp(lowest, highest);
391
392 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 if !self.no_verify {
441 let state = &state_without_tracking;
442 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 ¶ms(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 {
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 ¶ms(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 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, &mut Default::default(),
519 &executor,
520 "Benchmark_dispatch_benchmark",
521 ¶ms(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 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, 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 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 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 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 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 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 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 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 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 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 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 if !self.jsonify(&batches)? && !self.quiet {
758 self.print_summary(&batches, &storage_info, pov_modes.clone())
760 }
761
762 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 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 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 fn print_summary(
837 &self,
838 batches: &[BenchmarkBatchSplitResults],
839 storage_info: &[StorageInfo],
840 pov_modes: PovModesMap,
841 ) {
842 for batch in batches.iter() {
843 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 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 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 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 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 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
1086fn 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 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 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 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}