pezframe_benchmarking/
utils.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Interfaces, types and utils for benchmarking a FRAME runtime.
19use alloc::vec::Vec;
20use codec::{Decode, Encode};
21use pezframe_support::{
22	dispatch::DispatchErrorWithPostInfo, pezpallet_prelude::*, traits::StorageInfo,
23};
24use pezsp_io::hashing::blake2_256;
25use pezsp_runtime::{
26	traits::TrailingZeroInput, transaction_validity::TransactionValidityError, DispatchError,
27};
28use pezsp_runtime_interface::pass_by::{
29	AllocateAndReturnByCodec, AllocateAndReturnPointer, PassFatPointerAndDecode,
30	PassFatPointerAndRead,
31};
32use pezsp_storage::TrackedStorageKey;
33use scale_info::TypeInfo;
34#[cfg(feature = "std")]
35use serde::{Deserialize, Serialize};
36
37/// An alphabet of possible parameters to use for benchmarking.
38#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
39#[derive(Encode, Decode, Clone, Copy, PartialEq, Debug, TypeInfo)]
40#[allow(missing_docs)]
41#[allow(non_camel_case_types)]
42pub enum BenchmarkParameter {
43	a,
44	b,
45	c,
46	d,
47	e,
48	f,
49	g,
50	h,
51	i,
52	j,
53	k,
54	l,
55	m,
56	n,
57	o,
58	p,
59	q,
60	r,
61	s,
62	t,
63	u,
64	v,
65	w,
66	x,
67	y,
68	z,
69}
70
71#[cfg(feature = "std")]
72impl std::fmt::Display for BenchmarkParameter {
73	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74		write!(f, "{:?}", self)
75	}
76}
77
78/// The results of a single of benchmark.
79#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
80#[derive(Encode, Decode, Clone, PartialEq, Debug, TypeInfo)]
81pub struct BenchmarkBatch {
82	/// The pezpallet containing this benchmark.
83	#[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
84	pub pezpallet: Vec<u8>,
85	/// The instance of this pezpallet being benchmarked.
86	#[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
87	pub instance: Vec<u8>,
88	/// The extrinsic (or benchmark name) of this benchmark.
89	#[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
90	pub benchmark: Vec<u8>,
91	/// The results from this benchmark.
92	pub results: Vec<BenchmarkResult>,
93}
94
95// TODO: could probably make API cleaner here.
96/// The results of a single of benchmark, where time and db results are separated.
97#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
98#[derive(Encode, Decode, Clone, PartialEq, Debug)]
99pub struct BenchmarkBatchSplitResults {
100	/// The pezpallet containing this benchmark.
101	#[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
102	pub pezpallet: Vec<u8>,
103	/// The instance of this pezpallet being benchmarked.
104	#[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
105	pub instance: Vec<u8>,
106	/// The extrinsic (or benchmark name) of this benchmark.
107	#[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
108	pub benchmark: Vec<u8>,
109	/// The extrinsic timing results from this benchmark.
110	pub time_results: Vec<BenchmarkResult>,
111	/// The db tracking results from this benchmark.
112	pub db_results: Vec<BenchmarkResult>,
113}
114
115/// Result from running benchmarks on a FRAME pezpallet.
116/// Contains duration of the function call in nanoseconds along with the benchmark parameters
117/// used for that benchmark result.
118#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
119#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)]
120pub struct BenchmarkResult {
121	pub components: Vec<(BenchmarkParameter, u32)>,
122	pub extrinsic_time: u128,
123	pub storage_root_time: u128,
124	pub reads: u32,
125	pub repeat_reads: u32,
126	pub writes: u32,
127	pub repeat_writes: u32,
128	pub proof_size: u32,
129	#[cfg_attr(feature = "std", serde(skip))]
130	pub keys: Vec<(Vec<u8>, u32, u32, bool)>,
131}
132
133impl BenchmarkResult {
134	pub fn from_weight(w: Weight) -> Self {
135		Self { extrinsic_time: (w.ref_time() / 1_000) as u128, ..Default::default() }
136	}
137}
138
139/// Helper module to make serde serialize `Vec<u8>` as strings.
140#[cfg(feature = "std")]
141mod serde_as_str {
142	pub fn serialize<S>(value: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
143	where
144		S: serde::Serializer,
145	{
146		let s = std::str::from_utf8(value).map_err(serde::ser::Error::custom)?;
147		serializer.collect_str(s)
148	}
149
150	pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
151	where
152		D: serde::de::Deserializer<'de>,
153	{
154		let s: &str = serde::de::Deserialize::deserialize(deserializer)?;
155		Ok(s.into())
156	}
157}
158
159/// Possible errors returned from the benchmarking pipeline.
160#[derive(Clone, PartialEq, Debug)]
161pub enum BenchmarkError {
162	/// The benchmarking pipeline should stop and return the inner string.
163	Stop(&'static str),
164	/// The benchmarking pipeline is allowed to fail here, and we should use the
165	/// included weight instead.
166	Override(BenchmarkResult),
167	/// The benchmarking pipeline is allowed to fail here, and we should simply
168	/// skip processing these results.
169	Skip,
170	/// No weight can be determined; set the weight of this call to zero.
171	///
172	/// You can also use `Override` instead, but this is easier to use since `Override` expects the
173	/// correct components to be present.
174	Weightless,
175}
176
177impl From<BenchmarkError> for &'static str {
178	fn from(e: BenchmarkError) -> Self {
179		match e {
180			BenchmarkError::Stop(s) => s,
181			BenchmarkError::Override(_) => "benchmark override",
182			BenchmarkError::Skip => "benchmark skip",
183			BenchmarkError::Weightless => "benchmark weightless",
184		}
185	}
186}
187
188impl From<&'static str> for BenchmarkError {
189	fn from(s: &'static str) -> Self {
190		Self::Stop(s)
191	}
192}
193
194impl From<DispatchErrorWithPostInfo> for BenchmarkError {
195	fn from(e: DispatchErrorWithPostInfo) -> Self {
196		Self::Stop(e.into())
197	}
198}
199
200impl From<DispatchError> for BenchmarkError {
201	fn from(e: DispatchError) -> Self {
202		Self::Stop(e.into())
203	}
204}
205
206impl From<TransactionValidityError> for BenchmarkError {
207	fn from(e: TransactionValidityError) -> Self {
208		Self::Stop(e.into())
209	}
210}
211
212/// Configuration used to setup and run runtime benchmarks.
213#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)]
214pub struct BenchmarkConfig {
215	/// The encoded name of the pezpallet to benchmark.
216	pub pezpallet: Vec<u8>,
217	/// The encoded name of the pezpallet instance to benchmark.
218	pub instance: Vec<u8>,
219	/// The encoded name of the benchmark/extrinsic to run.
220	pub benchmark: Vec<u8>,
221	/// The selected component values to use when running the benchmark.
222	pub selected_components: Vec<(BenchmarkParameter, u32)>,
223	/// Enable an extra benchmark iteration which runs the verification logic for a benchmark.
224	pub verify: bool,
225	/// Number of times to repeat benchmark within the Wasm environment. (versus in the client)
226	pub internal_repeats: u32,
227}
228
229/// A list of benchmarks available for a particular pezpallet and instance.
230///
231/// All `Vec<u8>` must be valid utf8 strings.
232#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)]
233pub struct BenchmarkList {
234	pub pezpallet: Vec<u8>,
235	pub instance: Vec<u8>,
236	pub benchmarks: Vec<BenchmarkMetadata>,
237}
238
239#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)]
240pub struct BenchmarkMetadata {
241	pub name: Vec<u8>,
242	pub components: Vec<(BenchmarkParameter, u32, u32)>,
243	pub pov_modes: Vec<(Vec<u8>, Vec<u8>)>,
244}
245
246pezsp_api::decl_runtime_apis! {
247	/// Runtime api for benchmarking a FRAME runtime.
248	#[api_version(2)]
249	pub trait Benchmark {
250		/// Get the benchmark metadata available for this runtime.
251		///
252		/// Parameters
253		/// - `extra`: Also list benchmarks marked "extra" which would otherwise not be
254		///            needed for weight calculation.
255		fn benchmark_metadata(extra: bool) -> (Vec<BenchmarkList>, Vec<StorageInfo>);
256
257		/// Dispatch the given benchmark.
258		fn dispatch_benchmark(config: BenchmarkConfig) -> Result<Vec<BenchmarkBatch>, alloc::string::String>;
259	}
260}
261
262/// Get the number of nanoseconds passed since the UNIX epoch
263///
264/// WARNING! This is a non-deterministic call. Do not use this within
265/// consensus critical logic.
266pub fn current_time() -> u128 {
267	u128::from_le_bytes(self::benchmarking::current_time())
268}
269
270/// Interface that provides functions for benchmarking the runtime.
271#[pezsp_runtime_interface::runtime_interface]
272pub trait Benchmarking {
273	/// Get the number of nanoseconds passed since the UNIX epoch, as u128 le-bytes.
274	///
275	/// You may want to use the standalone function [`current_time`].
276	///
277	/// WARNING! This is a non-deterministic call. Do not use this within
278	/// consensus critical logic.
279	fn current_time() -> AllocateAndReturnPointer<[u8; 16], 16> {
280		std::time::SystemTime::now()
281			.duration_since(std::time::SystemTime::UNIX_EPOCH)
282			.expect("Unix time doesn't go backwards; qed")
283			.as_nanos()
284			.to_le_bytes()
285	}
286
287	/// Reset the trie database to the genesis state.
288	fn wipe_db(&mut self) {
289		self.wipe()
290	}
291
292	/// Commit pending storage changes to the trie database and clear the database cache.
293	fn commit_db(&mut self) {
294		self.commit()
295	}
296
297	/// Get the read/write count.
298	fn read_write_count(&self) -> AllocateAndReturnByCodec<(u32, u32, u32, u32)> {
299		self.read_write_count()
300	}
301
302	/// Reset the read/write count.
303	fn reset_read_write_count(&mut self) {
304		self.reset_read_write_count()
305	}
306
307	/// Get the DB whitelist.
308	fn get_whitelist(&self) -> AllocateAndReturnByCodec<Vec<TrackedStorageKey>> {
309		self.get_whitelist()
310	}
311
312	/// Set the DB whitelist.
313	fn set_whitelist(&mut self, new: PassFatPointerAndDecode<Vec<TrackedStorageKey>>) {
314		self.set_whitelist(new)
315	}
316
317	// Add a new item to the DB whitelist.
318	fn add_to_whitelist(&mut self, add: PassFatPointerAndDecode<TrackedStorageKey>) {
319		let mut whitelist = self.get_whitelist();
320		match whitelist.iter_mut().find(|x| x.key == add.key) {
321			// If we already have this key in the whitelist, update to be the most constrained
322			// value.
323			Some(item) => {
324				item.reads += add.reads;
325				item.writes += add.writes;
326				item.whitelisted = item.whitelisted || add.whitelisted;
327			},
328			// If the key does not exist, add it.
329			None => {
330				whitelist.push(add);
331			},
332		}
333		self.set_whitelist(whitelist);
334	}
335
336	// Remove an item from the DB whitelist.
337	fn remove_from_whitelist(&mut self, remove: PassFatPointerAndRead<Vec<u8>>) {
338		let mut whitelist = self.get_whitelist();
339		whitelist.retain(|x| x.key != remove);
340		self.set_whitelist(whitelist);
341	}
342
343	fn get_read_and_written_keys(
344		&self,
345	) -> AllocateAndReturnByCodec<Vec<(Vec<u8>, u32, u32, bool)>> {
346		self.get_read_and_written_keys()
347	}
348
349	/// Get current estimated proof size.
350	fn proof_size(&self) -> AllocateAndReturnByCodec<Option<u32>> {
351		self.proof_size()
352	}
353}
354
355/// The pezpallet benchmarking trait.
356pub trait Benchmarking {
357	/// Get the benchmarks available for this pezpallet. Generally there is one benchmark per
358	/// extrinsic, so these are sometimes just called "extrinsics".
359	///
360	/// Parameters
361	/// - `extra`: Also return benchmarks marked "extra" which would otherwise not be needed for
362	///   weight calculation.
363	fn benchmarks(extra: bool) -> Vec<BenchmarkMetadata>;
364
365	/// Run the benchmarks for this pezpallet.
366	fn run_benchmark(
367		name: &[u8],
368		selected_components: &[(BenchmarkParameter, u32)],
369		whitelist: &[TrackedStorageKey],
370		verify: bool,
371		internal_repeats: u32,
372	) -> Result<Vec<BenchmarkResult>, BenchmarkError>;
373}
374
375/// The recording trait used to mark the start and end of a benchmark.
376pub trait Recording {
377	/// Start the benchmark.
378	fn start(&mut self) {}
379
380	// Stop the benchmark.
381	fn stop(&mut self) {}
382}
383
384/// A no-op recording, used for unit test.
385struct NoopRecording;
386impl Recording for NoopRecording {}
387
388/// A no-op recording, used for tests that should setup some state before running the benchmark.
389struct TestRecording<'a> {
390	on_before_start: Option<&'a dyn Fn()>,
391}
392
393impl<'a> TestRecording<'a> {
394	fn new(on_before_start: &'a dyn Fn()) -> Self {
395		Self { on_before_start: Some(on_before_start) }
396	}
397}
398
399impl<'a> Recording for TestRecording<'a> {
400	fn start(&mut self) {
401		(self.on_before_start.take().expect("start called more than once"))();
402	}
403}
404
405/// Records the time and proof size of a single benchmark iteration.
406pub struct BenchmarkRecording<'a> {
407	on_before_start: Option<&'a dyn Fn()>,
408	start_extrinsic: Option<u128>,
409	finish_extrinsic: Option<u128>,
410	start_pov: Option<u32>,
411	end_pov: Option<u32>,
412}
413
414impl<'a> BenchmarkRecording<'a> {
415	pub fn new(on_before_start: &'a dyn Fn()) -> Self {
416		Self {
417			on_before_start: Some(on_before_start),
418			start_extrinsic: None,
419			finish_extrinsic: None,
420			start_pov: None,
421			end_pov: None,
422		}
423	}
424}
425
426impl<'a> Recording for BenchmarkRecording<'a> {
427	fn start(&mut self) {
428		(self.on_before_start.take().expect("start called more than once"))();
429		self.start_pov = crate::benchmarking::proof_size();
430		self.start_extrinsic = Some(current_time());
431	}
432
433	fn stop(&mut self) {
434		self.finish_extrinsic = Some(current_time());
435		self.end_pov = crate::benchmarking::proof_size();
436	}
437}
438
439impl<'a> BenchmarkRecording<'a> {
440	pub fn start_pov(&self) -> Option<u32> {
441		self.start_pov
442	}
443
444	pub fn end_pov(&self) -> Option<u32> {
445		self.end_pov
446	}
447
448	pub fn diff_pov(&self) -> Option<u32> {
449		self.start_pov.zip(self.end_pov).map(|(start, end)| end.saturating_sub(start))
450	}
451
452	pub fn elapsed_extrinsic(&self) -> Option<u128> {
453		self.start_extrinsic
454			.zip(self.finish_extrinsic)
455			.map(|(start, end)| end.saturating_sub(start))
456	}
457}
458
459/// The required setup for creating a benchmark.
460///
461/// Instance generic parameter is optional and can be used in order to capture unused generics for
462/// instantiable pallets.
463pub trait BenchmarkingSetup<T, I = ()> {
464	/// Return the components and their ranges which should be tested in this benchmark.
465	fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)>;
466
467	/// Set up the storage, and prepare a closure to run the benchmark.
468	fn instance(
469		&self,
470		recording: &mut impl Recording,
471		components: &[(BenchmarkParameter, u32)],
472		verify: bool,
473	) -> Result<(), BenchmarkError>;
474
475	/// Same as `instance` but passing a closure to run before the benchmark starts.
476	fn test_instance(
477		&self,
478		components: &[(BenchmarkParameter, u32)],
479		on_before_start: &dyn Fn(),
480	) -> Result<(), BenchmarkError> {
481		return self.instance(&mut TestRecording::new(on_before_start), components, true);
482	}
483
484	/// Same as `instance` but passing a no-op recording for unit tests.
485	fn unit_test_instance(
486		&self,
487		components: &[(BenchmarkParameter, u32)],
488	) -> Result<(), BenchmarkError> {
489		return self.instance(&mut NoopRecording {}, components, true);
490	}
491}
492
493/// Grab an account, seeded by a name and index.
494pub fn account<AccountId: Decode>(name: &'static str, index: u32, seed: u32) -> AccountId {
495	let entropy = (name, index, seed).using_encoded(blake2_256);
496	Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
497		.expect("infinite length input; no invalid inputs for type; qed")
498}
499
500/// This caller account is automatically whitelisted for DB reads/writes by the benchmarking macro.
501pub fn whitelisted_caller<AccountId: Decode>() -> AccountId {
502	account::<AccountId>("whitelisted_caller", 0, 0)
503}
504
505#[macro_export]
506macro_rules! whitelist_account {
507	($acc:ident) => {
508		pezframe_benchmarking::benchmarking::add_to_whitelist(
509			pezframe_system::Account::<T>::hashed_key_for(&$acc).into(),
510		);
511	};
512}