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