1use 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#[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#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
78#[derive(Encode, Decode, Clone, PartialEq, Debug, TypeInfo)]
79pub struct BenchmarkBatch {
80 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
82 pub pallet: Vec<u8>,
83 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
85 pub instance: Vec<u8>,
86 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
88 pub benchmark: Vec<u8>,
89 pub results: Vec<BenchmarkResult>,
91}
92
93#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
96#[derive(Encode, Decode, Clone, PartialEq, Debug)]
97pub struct BenchmarkBatchSplitResults {
98 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
100 pub pallet: Vec<u8>,
101 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
103 pub instance: Vec<u8>,
104 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
106 pub benchmark: Vec<u8>,
107 pub time_results: Vec<BenchmarkResult>,
109 pub db_results: Vec<BenchmarkResult>,
111}
112
113#[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#[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#[derive(Clone, PartialEq, Debug)]
159pub enum BenchmarkError {
160 Stop(&'static str),
162 Override(BenchmarkResult),
165 Skip,
168 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#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)]
212pub struct BenchmarkConfig {
213 pub pallet: Vec<u8>,
215 pub instance: Vec<u8>,
217 pub benchmark: Vec<u8>,
219 pub selected_components: Vec<(BenchmarkParameter, u32)>,
221 pub verify: bool,
223 pub internal_repeats: u32,
225}
226
227#[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 #[api_version(2)]
247 pub trait Benchmark {
248 fn benchmark_metadata(extra: bool) -> (Vec<BenchmarkList>, Vec<StorageInfo>);
254
255 fn dispatch_benchmark(config: BenchmarkConfig) -> Result<Vec<BenchmarkBatch>, alloc::string::String>;
257 }
258}
259
260pub fn current_time() -> u128 {
265 u128::from_le_bytes(self::benchmarking::current_time())
266}
267
268#[sp_runtime_interface::runtime_interface]
270pub trait Benchmarking {
271 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 fn wipe_db(&mut self) {
287 self.wipe()
288 }
289
290 fn commit_db(&mut self) {
292 self.commit();
293
294 const WARMUP_KEY: &[u8] = b":benchmark_warmup:";
299 let mut whitelist = self.get_whitelist();
300 if !whitelist.iter().any(|k| k.key == WARMUP_KEY) {
301 whitelist.push(WARMUP_KEY.to_vec().into());
302 self.set_whitelist(whitelist);
303 }
304 self.place_storage(WARMUP_KEY.to_vec(), Some(vec![0u8; 32]));
305 self.place_storage(WARMUP_KEY.to_vec(), None);
306 }
307
308 fn read_write_count(&self) -> AllocateAndReturnByCodec<(u32, u32, u32, u32)> {
310 self.read_write_count()
311 }
312
313 fn reset_read_write_count(&mut self) {
315 self.reset_read_write_count()
316 }
317
318 fn get_whitelist(&self) -> AllocateAndReturnByCodec<Vec<TrackedStorageKey>> {
320 self.get_whitelist()
321 }
322
323 fn set_whitelist(&mut self, new: PassFatPointerAndDecode<Vec<TrackedStorageKey>>) {
325 self.set_whitelist(new)
326 }
327
328 fn add_to_whitelist(&mut self, add: PassFatPointerAndDecode<TrackedStorageKey>) {
330 let mut whitelist = self.get_whitelist();
331 match whitelist.iter_mut().find(|x| x.key == add.key) {
332 Some(item) => {
335 item.reads += add.reads;
336 item.writes += add.writes;
337 item.whitelisted = item.whitelisted || add.whitelisted;
338 },
339 None => {
341 whitelist.push(add);
342 },
343 }
344 self.set_whitelist(whitelist);
345 }
346
347 fn remove_from_whitelist(&mut self, remove: PassFatPointerAndRead<Vec<u8>>) {
349 let mut whitelist = self.get_whitelist();
350 whitelist.retain(|x| x.key != remove);
351 self.set_whitelist(whitelist);
352 }
353
354 fn get_read_and_written_keys(
355 &self,
356 ) -> AllocateAndReturnByCodec<Vec<(Vec<u8>, u32, u32, bool)>> {
357 self.get_read_and_written_keys()
358 }
359
360 fn proof_size(&self) -> AllocateAndReturnByCodec<Option<u32>> {
362 self.proof_size()
363 }
364}
365
366pub trait Benchmarking {
368 fn benchmarks(extra: bool) -> Vec<BenchmarkMetadata>;
375
376 fn run_benchmark(
378 name: &[u8],
379 selected_components: &[(BenchmarkParameter, u32)],
380 whitelist: &[TrackedStorageKey],
381 verify: bool,
382 internal_repeats: u32,
383 ) -> Result<Vec<BenchmarkResult>, BenchmarkError>;
384}
385
386pub trait Recording {
388 fn start(&mut self) {}
390
391 fn stop(&mut self) {}
393}
394
395struct NoopRecording;
397impl Recording for NoopRecording {}
398
399struct TestRecording<'a> {
401 on_before_start: Option<&'a dyn Fn()>,
402}
403
404impl<'a> TestRecording<'a> {
405 fn new(on_before_start: &'a dyn Fn()) -> Self {
406 Self { on_before_start: Some(on_before_start) }
407 }
408}
409
410impl<'a> Recording for TestRecording<'a> {
411 fn start(&mut self) {
412 (self.on_before_start.take().expect("start called more than once"))();
413 }
414}
415
416pub struct BenchmarkRecording<'a> {
418 on_before_start: Option<&'a dyn Fn()>,
419 start_extrinsic: Option<u128>,
420 finish_extrinsic: Option<u128>,
421 start_pov: Option<u32>,
422 end_pov: Option<u32>,
423}
424
425impl<'a> BenchmarkRecording<'a> {
426 pub fn new(on_before_start: &'a dyn Fn()) -> Self {
427 Self {
428 on_before_start: Some(on_before_start),
429 start_extrinsic: None,
430 finish_extrinsic: None,
431 start_pov: None,
432 end_pov: None,
433 }
434 }
435}
436
437impl<'a> Recording for BenchmarkRecording<'a> {
438 fn start(&mut self) {
439 (self.on_before_start.take().expect("start called more than once"))();
440 self.start_pov = crate::benchmarking::proof_size();
441 self.start_extrinsic = Some(current_time());
442 }
443
444 fn stop(&mut self) {
445 self.finish_extrinsic = Some(current_time());
446 self.end_pov = crate::benchmarking::proof_size();
447 }
448}
449
450impl<'a> BenchmarkRecording<'a> {
451 pub fn start_pov(&self) -> Option<u32> {
452 self.start_pov
453 }
454
455 pub fn end_pov(&self) -> Option<u32> {
456 self.end_pov
457 }
458
459 pub fn diff_pov(&self) -> Option<u32> {
460 self.start_pov.zip(self.end_pov).map(|(start, end)| end.saturating_sub(start))
461 }
462
463 pub fn elapsed_extrinsic(&self) -> Option<u128> {
464 self.start_extrinsic
465 .zip(self.finish_extrinsic)
466 .map(|(start, end)| end.saturating_sub(start))
467 }
468}
469
470pub trait BenchmarkingSetup<T, I = ()> {
475 fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)>;
477
478 fn instance(
480 &self,
481 recording: &mut impl Recording,
482 components: &[(BenchmarkParameter, u32)],
483 verify: bool,
484 ) -> Result<(), BenchmarkError>;
485
486 fn test_instance(
488 &self,
489 components: &[(BenchmarkParameter, u32)],
490 on_before_start: &dyn Fn(),
491 ) -> Result<(), BenchmarkError> {
492 return self.instance(&mut TestRecording::new(on_before_start), components, true);
493 }
494
495 fn unit_test_instance(
497 &self,
498 components: &[(BenchmarkParameter, u32)],
499 ) -> Result<(), BenchmarkError> {
500 return self.instance(&mut NoopRecording {}, components, true);
501 }
502}
503
504pub fn account<AccountId: Decode>(name: &'static str, index: u32, seed: u32) -> AccountId {
506 let entropy = (name, index, seed).using_encoded(blake2_256);
507 Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
508 .expect("infinite length input; no invalid inputs for type; qed")
509}
510
511pub fn whitelisted_caller<AccountId: Decode>() -> AccountId {
513 account::<AccountId>("whitelisted_caller", 0, 0)
514}
515
516#[macro_export]
517macro_rules! whitelist_account {
518 ($acc:ident) => {
519 frame_benchmarking::benchmarking::add_to_whitelist(
520 frame_system::Account::<T>::hashed_key_for(&$acc).into(),
521 );
522 };
523}