1use 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#[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#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
80#[derive(Encode, Decode, Clone, PartialEq, Debug, TypeInfo)]
81pub struct BenchmarkBatch {
82 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
84 pub pezpallet: Vec<u8>,
85 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
87 pub instance: Vec<u8>,
88 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
90 pub benchmark: Vec<u8>,
91 pub results: Vec<BenchmarkResult>,
93}
94
95#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
98#[derive(Encode, Decode, Clone, PartialEq, Debug)]
99pub struct BenchmarkBatchSplitResults {
100 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
102 pub pezpallet: Vec<u8>,
103 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
105 pub instance: Vec<u8>,
106 #[cfg_attr(feature = "std", serde(with = "serde_as_str"))]
108 pub benchmark: Vec<u8>,
109 pub time_results: Vec<BenchmarkResult>,
111 pub db_results: Vec<BenchmarkResult>,
113}
114
115#[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#[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#[derive(Clone, PartialEq, Debug)]
161pub enum BenchmarkError {
162 Stop(&'static str),
164 Override(BenchmarkResult),
167 Skip,
170 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#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)]
214pub struct BenchmarkConfig {
215 pub pezpallet: Vec<u8>,
217 pub instance: Vec<u8>,
219 pub benchmark: Vec<u8>,
221 pub selected_components: Vec<(BenchmarkParameter, u32)>,
223 pub verify: bool,
225 pub internal_repeats: u32,
227}
228
229#[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 #[api_version(2)]
249 pub trait Benchmark {
250 fn benchmark_metadata(extra: bool) -> (Vec<BenchmarkList>, Vec<StorageInfo>);
256
257 fn dispatch_benchmark(config: BenchmarkConfig) -> Result<Vec<BenchmarkBatch>, alloc::string::String>;
259 }
260}
261
262pub fn current_time() -> u128 {
267 u128::from_le_bytes(self::benchmarking::current_time())
268}
269
270#[pezsp_runtime_interface::runtime_interface]
272pub trait Benchmarking {
273 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 fn wipe_db(&mut self) {
289 self.wipe()
290 }
291
292 fn commit_db(&mut self) {
294 self.commit()
295 }
296
297 fn read_write_count(&self) -> AllocateAndReturnByCodec<(u32, u32, u32, u32)> {
299 self.read_write_count()
300 }
301
302 fn reset_read_write_count(&mut self) {
304 self.reset_read_write_count()
305 }
306
307 fn get_whitelist(&self) -> AllocateAndReturnByCodec<Vec<TrackedStorageKey>> {
309 self.get_whitelist()
310 }
311
312 fn set_whitelist(&mut self, new: PassFatPointerAndDecode<Vec<TrackedStorageKey>>) {
314 self.set_whitelist(new)
315 }
316
317 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 Some(item) => {
324 item.reads += add.reads;
325 item.writes += add.writes;
326 item.whitelisted = item.whitelisted || add.whitelisted;
327 },
328 None => {
330 whitelist.push(add);
331 },
332 }
333 self.set_whitelist(whitelist);
334 }
335
336 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 fn proof_size(&self) -> AllocateAndReturnByCodec<Option<u32>> {
351 self.proof_size()
352 }
353}
354
355pub trait Benchmarking {
357 fn benchmarks(extra: bool) -> Vec<BenchmarkMetadata>;
364
365 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
375pub trait Recording {
377 fn start(&mut self) {}
379
380 fn stop(&mut self) {}
382}
383
384struct NoopRecording;
386impl Recording for NoopRecording {}
387
388struct 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
405pub 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
459pub trait BenchmarkingSetup<T, I = ()> {
464 fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)>;
466
467 fn instance(
469 &self,
470 recording: &mut impl Recording,
471 components: &[(BenchmarkParameter, u32)],
472 verify: bool,
473 ) -> Result<(), BenchmarkError>;
474
475 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 fn unit_test_instance(
486 &self,
487 components: &[(BenchmarkParameter, u32)],
488 ) -> Result<(), BenchmarkError> {
489 return self.instance(&mut NoopRecording {}, components, true);
490 }
491}
492
493pub 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
500pub 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}