1use 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#[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 pezpallet: 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.into_iter().for_each(
121 |BenchmarkBatch { pezpallet, instance, benchmark, results }| {
122 let key = (pezpallet, instance, benchmark);
124
125 match all_benchmarks.get_mut(&key) {
126 Some(x) => x.1.extend(results),
128 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 let key = (pezpallet, instance, benchmark);
140
141 match all_benchmarks.get_mut(&key) {
142 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
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 },
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 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 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 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 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 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 true,
273 true,
275 )?;
276
277 let state_without_tracking = BenchmarkingState::<Hasher>::new(
278 genesis_storage,
279 cache_size,
280 !self.disable_proof_recording,
282 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 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 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 let mut batches = Vec::new();
355 let mut batches_db = Vec::new();
356 let mut timer = time::SystemTime::now();
357 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 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 let component_value =
393 ((lowest as f32 + step_size * s as f32) as u32).clamp(lowest, highest);
394
395 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 if !self.no_verify {
444 let state = &state_without_tracking;
445 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 ¶ms(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 {
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 ¶ms(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 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, &mut Default::default(),
522 &executor,
523 "Benchmark_dispatch_benchmark",
524 ¶ms(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 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, 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 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 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 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 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 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 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 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 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 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 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 if !self.jsonify(&batches)? && !self.quiet {
763 self.print_summary(&batches, &storage_info, pov_modes.clone())
765 }
766
767 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 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 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 fn print_summary(
842 &self,
843 batches: &[BenchmarkBatchSplitResults],
844 storage_info: &[StorageInfo],
845 pov_modes: PovModesMap,
846 ) {
847 for batch in batches.iter() {
848 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 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 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 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 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 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
1094fn 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 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 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 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}