Skip to main content

frame_benchmarking/
utils.rs

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