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