// This file is part of Substrate. // Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Macro for benchmarking a FRAME runtime. #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "std")] mod analysis; #[cfg(test)] mod tests; #[cfg(test)] mod tests_instance; mod utils; pub mod baseline; #[cfg(feature = "std")] pub use analysis::{Analysis, AnalysisChoice, BenchmarkSelector}; #[doc(hidden)] pub use frame_support; #[doc(hidden)] pub use log; #[doc(hidden)] pub use paste; #[doc(hidden)] pub use sp_core::defer; #[doc(hidden)] pub use sp_io::storage::root as storage_root; #[doc(hidden)] pub use sp_runtime::traits::Zero; #[doc(hidden)] pub use sp_runtime::StateVersion; #[doc(hidden)] pub use sp_std::{self, boxed::Box, prelude::Vec, str, vec}; pub use sp_storage::TrackedStorageKey; pub use utils::*; /// Whitelist the given account. #[macro_export] macro_rules! whitelist { ($acc:ident) => { frame_benchmarking::benchmarking::add_to_whitelist( frame_system::Account::::hashed_key_for(&$acc).into(), ); }; } /// Construct pallet benchmarks for weighing dispatchables. /// /// Works around the idea of complexity parameters, named by a single letter (which is usually /// upper cased in complexity notation but is lower-cased for use in this macro). /// /// Complexity parameters ("parameters") have a range which is a `u32` pair. Every time a benchmark /// is prepared and run, this parameter takes a concrete value within the range. There is an /// associated instancing block, which is a single expression that is evaluated during /// preparation. It may use `?` (`i.e. `return Err(...)`) to bail with a string error. Here's a /// few examples: /// /// ```ignore /// // These two are equivalent: /// let x in 0 .. 10; /// let x in 0 .. 10 => (); /// // This one calls a setup function and might return an error (which would be terminal). /// let y in 0 .. 10 => setup(y)?; /// // This one uses a code block to do lots of stuff: /// let z in 0 .. 10 => { /// let a = z * z / 5; /// let b = do_something(a)?; /// combine_into(z, b); /// } /// ``` /// /// Note that due to parsing restrictions, if the `from` expression is not a single token (i.e. a /// literal or constant), then it must be parenthesized. /// /// The macro allows for a number of "arms", each representing an individual benchmark. Using the /// simple syntax, the associated dispatchable function maps 1:1 with the benchmark and the name of /// the benchmark is the same as that of the associated function. However, extended syntax allows /// for arbitrary expressions to be evaluated in a benchmark (including for example, /// `on_initialize`). /// /// Note that the ranges are *inclusive* on both sides. This is in contrast to ranges in Rust which /// are left-inclusive right-exclusive. /// /// Each arm may also have a block of code which is run prior to any instancing and a block of code /// which is run afterwards. All code blocks may draw upon the specific value of each parameter /// at any time. Local variables are shared between the two pre- and post- code blocks, but do not /// leak from the interior of any instancing expressions. /// /// Example: /// ```ignore /// benchmarks! { /// where_clause { where T::A: From } // Optional line to give additional bound on `T`. /// /// // first dispatchable: foo; this is a user dispatchable and operates on a `u8` vector of /// // size `l` /// foo { /// let caller = account::(b"caller", 0, benchmarks_seed); /// let l in 1 .. MAX_LENGTH => initialize_l(l); /// }: _(RuntimeOrigin::Signed(caller), vec![0u8; l]) /// /// // second dispatchable: bar; this is a root dispatchable and accepts a `u8` vector of size /// // `l`. /// // In this case, we explicitly name the call using `bar` instead of `_`. /// bar { /// let l in 1 .. MAX_LENGTH => initialize_l(l); /// }: bar(RuntimeOrigin::Root, vec![0u8; l]) /// /// // third dispatchable: baz; this is a user dispatchable. It isn't dependent on length like the /// // other two but has its own complexity `c` that needs setting up. It uses `caller` (in the /// // pre-instancing block) within the code block. This is only allowed in the param instancers /// // of arms. /// baz1 { /// let caller = account::(b"caller", 0, benchmarks_seed); /// let c = 0 .. 10 => setup_c(&caller, c); /// }: baz(RuntimeOrigin::Signed(caller)) /// /// // this is a second benchmark of the baz dispatchable with a different setup. /// baz2 { /// let caller = account::(b"caller", 0, benchmarks_seed); /// let c = 0 .. 10 => setup_c_in_some_other_way(&caller, c); /// }: baz(RuntimeOrigin::Signed(caller)) /// /// // You may optionally specify the origin type if it can't be determined automatically like /// // this. /// baz3 { /// let caller = account::(b"caller", 0, benchmarks_seed); /// let l in 1 .. MAX_LENGTH => initialize_l(l); /// }: baz(RuntimeOrigin::Signed(caller), vec![0u8; l]) /// /// // this is benchmarking some code that is not a dispatchable. /// populate_a_set { /// let x in 0 .. 10_000; /// let mut m = Vec::::new(); /// for i in 0..x { /// m.insert(i); /// } /// }: { m.into_iter().collect::() } /// } /// ``` /// /// Test functions are automatically generated for each benchmark and are accessible to you when you /// run `cargo test`. All tests are named `test_benchmark_`, implemented on the /// Pallet struct, and run them in a test externalities environment. The test function runs your /// benchmark just like a regular benchmark, but only testing at the lowest and highest values for /// each component. The function will return `Ok(())` if the benchmarks return no errors. /// /// It is also possible to generate one #[test] function per benchmark by calling the /// `impl_benchmark_test_suite` macro inside the `benchmarks` block. The functions will be named /// `bench_` and can be run via `cargo test`. /// You will see one line of output per benchmark. This approach will give you more understandable /// error messages and allows for parallel benchmark execution. /// /// You can optionally add a `verify` code block at the end of a benchmark to test any final state /// of your benchmark in a unit test. For example: /// /// ```ignore /// sort_vector { /// let x in 1 .. 10000; /// let mut m = Vec::::new(); /// for i in (0..x).rev() { /// m.push(i); /// } /// }: { /// m.sort(); /// } verify { /// ensure!(m[0] == 0, "You forgot to sort!") /// } /// ``` /// /// These `verify` blocks will not affect your benchmark results! /// /// You can construct benchmark by using the `impl_benchmark_test_suite` macro or /// by manually implementing them like so: /// /// ```ignore /// #[test] /// fn test_benchmarks() { /// new_test_ext().execute_with(|| { /// assert_ok!(Pallet::::test_benchmark_dummy()); /// assert_err!(Pallet::::test_benchmark_other_name(), "Bad origin"); /// assert_ok!(Pallet::::test_benchmark_sort_vector()); /// assert_err!(Pallet::::test_benchmark_broken_benchmark(), "You forgot to sort!"); /// }); /// } /// ``` #[macro_export] macro_rules! benchmarks { ( $( $rest:tt )* ) => { $crate::benchmarks_iter!( { } { } { } ( ) ( ) ( ) $( $rest )* ); } } /// Same as [`benchmarks`] but for instantiable module. /// /// NOTE: For pallet declared with [`frame_support::pallet`], use [`benchmarks_instance_pallet`]. #[macro_export] macro_rules! benchmarks_instance { ( $( $rest:tt )* ) => { $crate::benchmarks_iter!( { } { I: Instance } { } ( ) ( ) ( ) $( $rest )* ); } } /// Same as [`benchmarks`] but for instantiable pallet declared [`frame_support::pallet`]. /// /// NOTE: For pallet declared with `decl_module!`, use [`benchmarks_instance`]. #[macro_export] macro_rules! benchmarks_instance_pallet { ( $( $rest:tt )* ) => { $crate::benchmarks_iter!( { } { I: 'static } { } ( ) ( ) ( ) $( $rest )* ); } } #[macro_export] #[doc(hidden)] macro_rules! benchmarks_iter { // detect and extract `impl_benchmark_test_suite` call: // - with a semi-colon ( { } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) impl_benchmark_test_suite!( $bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?); $( $rest:tt )* ) => { $crate::benchmarks_iter! { { $bench_module, $new_test_ext, $test $(, $( $args )* )? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) $( $rest )* } }; // - without a semicolon ( { } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) impl_benchmark_test_suite!( $bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?) $( $rest:tt )* ) => { $crate::benchmarks_iter! { { $bench_module, $new_test_ext, $test $(, $( $args )* )? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) $( $rest )* } }; // detect and extract where clause: ( { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) where_clause { where $( $where_bound:tt )* } $( $rest:tt )* ) => { $crate::benchmarks_iter! { { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound)? } { $( $where_bound )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) $( $rest )* } }; // detect and extract `#[skip_meta]` tag: ( { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) #[skip_meta] $name:ident $( $rest:tt )* ) => { $crate::benchmarks_iter! { { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* $name ) $name $( $rest )* } }; // detect and extract `#[extra]` tag: ( { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) #[extra] $name:ident $( $rest:tt )* ) => { $crate::benchmarks_iter! { { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* $name ) ( $( $names_skip_meta )* ) $name $( $rest )* } }; // mutation arm: ( { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) // This contains $( $( { $instance } )? $name:ident )* ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) $name:ident { $( $code:tt )* }: _ $(< $origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) verify $postcode:block $( $rest:tt )* ) => { $crate::benchmarks_iter! { { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) $name { $( $code )* }: $name $(< $origin_type >)? ( $origin $( , $arg )* ) verify $postcode $( $rest )* } }; // mutation arm: ( { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) $name:ident { $( $code:tt )* }: $dispatch:ident $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) verify $postcode:block $( $rest:tt )* ) => { $crate::paste::paste! { $crate::benchmarks_iter! { { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) $name { $( $code )* let __call = Call::< T $( , $instance )? >:: [< new_call_variant_ $dispatch >] ( $($arg),* ); let __benchmarked_call_encoded = $crate::frame_support::codec::Encode::encode( &__call ); }: { let __call_decoded = < Call as $crate::frame_support::codec::Decode >::decode(&mut &__benchmarked_call_encoded[..]) .expect("call is encoded above, encoding must be correct"); let __origin = $crate::to_origin!($origin $(, $origin_type)?); as $crate::frame_support::traits::UnfilteredDispatchable >::dispatch_bypass_filter(__call_decoded, __origin)?; } verify $postcode $( $rest )* } } }; // iteration arm: ( { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) $name:ident { $( $code:tt )* }: $eval:block verify $postcode:block $( $rest:tt )* ) => { $crate::benchmark_backend! { { $( $instance: $instance_bound )? } $name { $( $where_clause )* } { } { $eval } { $( $code )* } $postcode } #[cfg(test)] $crate::impl_benchmark_test!( { $( $where_clause )* } { $( $instance: $instance_bound )? } $name ); $crate::benchmarks_iter!( { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* { $( $instance )? } $name ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) $( $rest )* ); }; // iteration-exit arm which generates a #[test] function for each case. ( { $bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) ) => { $crate::selected_benchmark!( { $( $where_clause)* } { $( $instance: $instance_bound )? } $( $names )* ); $crate::impl_benchmark!( { $( $where_clause )* } { $( $instance: $instance_bound )? } ( $( $names )* ) ( $( $names_extra ),* ) ( $( $names_skip_meta ),* ) ); $crate::impl_test_function!( ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) $bench_module, $new_test_ext, $test $(, $( $args )* )? ); }; // iteration-exit arm which doesn't generate a #[test] function for all cases. ( { } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) ) => { $crate::selected_benchmark!( { $( $where_clause)* } { $( $instance: $instance_bound )? } $( $names )* ); $crate::impl_benchmark!( { $( $where_clause )* } { $( $instance: $instance_bound )? } ( $( $names )* ) ( $( $names_extra ),* ) ( $( $names_skip_meta ),* ) ); }; // add verify block to _() format ( { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) $name:ident { $( $code:tt )* }: _ $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) $( $rest:tt )* ) => { $crate::benchmarks_iter! { { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) $name { $( $code )* }: _ $(<$origin_type>)? ( $origin $( , $arg )* ) verify { } $( $rest )* } }; // add verify block to name() format ( { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) $name:ident { $( $code:tt )* }: $dispatch:ident $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) $( $rest:tt )* ) => { $crate::benchmarks_iter! { { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) $name { $( $code )* }: $dispatch $(<$origin_type>)? ( $origin $( , $arg )* ) verify { } $( $rest )* } }; // add verify block to {} format ( { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } { $( $instance:ident: $instance_bound:tt )? } { $( $where_clause:tt )* } ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) $name:ident { $( $code:tt )* }: $(<$origin_type:ty>)? $eval:block $( $rest:tt )* ) => { $crate::benchmarks_iter!( { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } { $( $instance: $instance_bound )? } { $( $where_clause )* } ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) $name { $( $code )* }: $(<$origin_type>)? $eval verify { } $( $rest )* ); }; } #[macro_export] #[doc(hidden)] macro_rules! to_origin { ($origin:expr) => { $origin.into() }; ($origin:expr, $origin_type:ty) => { <::RuntimeOrigin as From<$origin_type>>::from($origin) }; } #[macro_export] #[doc(hidden)] macro_rules! benchmark_backend { // parsing arms ( { $( $instance:ident: $instance_bound:tt )? } $name:ident { $( $where_clause:tt )* } { $( PRE { $( $pre_parsed:tt )* } )* } { $eval:block } { let $pre_id:tt : $pre_ty:ty = $pre_ex:expr; $( $rest:tt )* } $postcode:block ) => { $crate::benchmark_backend! { { $( $instance: $instance_bound )? } $name { $( $where_clause )* } { $( PRE { $( $pre_parsed )* } )* PRE { $pre_id , $pre_ty , $pre_ex } } { $eval } { $( $rest )* } $postcode } }; ( { $( $instance:ident: $instance_bound:tt )? } $name:ident { $( $where_clause:tt )* } { $( $parsed:tt )* } { $eval:block } { let $param:ident in ( $param_from:expr ) .. $param_to:expr => $param_instancer:expr; $( $rest:tt )* } $postcode:block ) => { $crate::benchmark_backend! { { $( $instance: $instance_bound )? } $name { $( $where_clause )* } { $( $parsed )* PARAM { $param , $param_from , $param_to , $param_instancer } } { $eval } { $( $rest )* } $postcode } }; // mutation arm to look after a single tt for param_from. ( { $( $instance:ident: $instance_bound:tt )? } $name:ident { $( $where_clause:tt )* } { $( $parsed:tt )* } { $eval:block } { let $param:ident in $param_from:tt .. $param_to:expr => $param_instancer:expr ; $( $rest:tt )* } $postcode:block ) => { $crate::benchmark_backend! { { $( $instance: $instance_bound )? } $name { $( $where_clause )* } { $( $parsed )* } { $eval } { let $param in ( $param_from ) .. $param_to => $param_instancer; $( $rest )* } $postcode } }; // mutation arm to look after the default tail of `=> ()` ( { $( $instance:ident: $instance_bound:tt )? } $name:ident { $( $where_clause:tt )* } { $( $parsed:tt )* } { $eval:block } { let $param:ident in $param_from:tt .. $param_to:expr; $( $rest:tt )* } $postcode:block ) => { $crate::benchmark_backend! { { $( $instance: $instance_bound )? } $name { $( $where_clause )* } { $( $parsed )* } { $eval } { let $param in $param_from .. $param_to => (); $( $rest )* } $postcode } }; // mutation arm to look after `let _ =` ( { $( $instance:ident: $instance_bound:tt )? } $name:ident { $( $where_clause:tt )* } { $( $parsed:tt )* } { $eval:block } { let $pre_id:tt = $pre_ex:expr; $( $rest:tt )* } $postcode:block ) => { $crate::benchmark_backend! { { $( $instance: $instance_bound )? } $name { $( $where_clause )* } { $( $parsed )* } { $eval } { let $pre_id : _ = $pre_ex; $( $rest )* } $postcode } }; // actioning arm ( { $( $instance:ident: $instance_bound:tt )? } $name:ident { $( $where_clause:tt )* } { $( PRE { $pre_id:tt , $pre_ty:ty , $pre_ex:expr } )* $( PARAM { $param:ident , $param_from:expr , $param_to:expr , $param_instancer:expr } )* } { $eval:block } { $( $post:tt )* } $postcode:block ) => { #[allow(non_camel_case_types)] struct $name; #[allow(unused_variables)] impl, $instance: $instance_bound )? > $crate::BenchmarkingSetup for $name where $( $where_clause )* { fn components(&self) -> $crate::Vec<($crate::BenchmarkParameter, u32, u32)> { $crate::vec! [ $( ($crate::BenchmarkParameter::$param, $param_from, $param_to) ),* ] } fn instance( &self, components: &[($crate::BenchmarkParameter, u32)], verify: bool ) -> Result<$crate::Box Result<(), $crate::BenchmarkError>>, $crate::BenchmarkError> { $( // Prepare instance let $param = components.iter() .find(|&c| c.0 == $crate::BenchmarkParameter::$param) .ok_or("Could not find component in benchmark preparation.")? .1; )* $( let $pre_id : $pre_ty = $pre_ex; )* $( $param_instancer ; )* $( $post )* Ok($crate::Box::new(move || -> Result<(), $crate::BenchmarkError> { $eval; if verify { $postcode; } Ok(()) })) } } }; } // Creates #[test] functions for the given bench cases. #[macro_export] #[doc(hidden)] macro_rules! impl_bench_case_tests { ( { $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr } { $( $names_extra:tt )* } $( { $( $bench_inst:ident )? } $bench:ident )* ) => { $crate::impl_bench_name_tests!( $module, $new_test_exec, $exec_name, $test, $extra, { $( $names_extra )* }, $( { $bench } )+ ); } } // Creates a #[test] function for the given bench name. #[macro_export] #[doc(hidden)] macro_rules! impl_bench_name_tests { // recursion anchor ( $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr, { $( $names_extra:tt )* }, { $name:ident } ) => { $crate::paste::paste! { #[test] fn [] () { $new_test_exec.$exec_name(|| { // Skip all #[extra] benchmarks if $extra is false. if !($extra) { let disabled = $crate::vec![ $( stringify!($names_extra).as_ref() ),* ]; if disabled.contains(&stringify!($name)) { $crate::log::error!( "INFO: extra benchmark skipped - {}", stringify!($name), ); return (); } } // Same per-case logic as when all cases are run in the // same function. match std::panic::catch_unwind(|| { $module::<$test>::[< test_benchmark_ $name >] () }) { Err(err) => { panic!("{}: {:?}", stringify!($name), err); }, Ok(Err(err)) => { match err { $crate::BenchmarkError::Stop(err) => { panic!("{}: {:?}", stringify!($name), err); }, $crate::BenchmarkError::Override(_) => { // This is still considered a success condition. $crate::log::error!( "WARNING: benchmark error overrided - {}", stringify!($name), ); }, $crate::BenchmarkError::Skip => { // This is considered a success condition. $crate::log::error!( "WARNING: benchmark error skipped - {}", stringify!($name), ); } } }, Ok(Ok(())) => (), } }); } } }; // recursion tail ( $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr, { $( $names_extra:tt )* }, { $name:ident } $( { $rest:ident } )+ ) => { // car $crate::impl_bench_name_tests!($module, $new_test_exec, $exec_name, $test, $extra, { $( $names_extra )* }, { $name }); // cdr $crate::impl_bench_name_tests!($module, $new_test_exec, $exec_name, $test, $extra, { $( $names_extra )* }, $( { $rest } )+); }; } // Creates a `SelectedBenchmark` enum implementing `BenchmarkingSetup`. // // Every variant must implement [`BenchmarkingSetup`]. // // ```nocompile // // struct Transfer; // impl BenchmarkingSetup for Transfer { ... } // // struct SetBalance; // impl BenchmarkingSetup for SetBalance { ... } // // selected_benchmark!({} Transfer {} SetBalance); // ``` #[macro_export] #[doc(hidden)] macro_rules! selected_benchmark { ( { $( $where_clause:tt )* } { $( $instance:ident: $instance_bound:tt )? } $( { $( $bench_inst:ident )? } $bench:ident )* ) => { // The list of available benchmarks for this pallet. #[allow(non_camel_case_types)] enum SelectedBenchmark { $( $bench, )* } // Allow us to select a benchmark from the list of available benchmarks. impl, $instance: $instance_bound )? > $crate::BenchmarkingSetup for SelectedBenchmark where $( $where_clause )* { fn components(&self) -> $crate::Vec<($crate::BenchmarkParameter, u32, u32)> { match self { $( Self::$bench => < $bench as $crate::BenchmarkingSetup >::components(&$bench), )* } } fn instance( &self, components: &[($crate::BenchmarkParameter, u32)], verify: bool ) -> Result<$crate::Box Result<(), $crate::BenchmarkError>>, $crate::BenchmarkError> { match self { $( Self::$bench => < $bench as $crate::BenchmarkingSetup >::instance(&$bench, components, verify), )* } } } }; } #[macro_export] #[doc(hidden)] macro_rules! impl_benchmark { ( { $( $where_clause:tt )* } { $( $instance:ident: $instance_bound:tt )? } ( $( { $( $name_inst:ident )? } $name:ident )* ) ( $( $name_extra:ident ),* ) ( $( $name_skip_meta:ident ),* ) ) => { // We only need to implement benchmarks for the runtime-benchmarks feature or testing. #[cfg(any(feature = "runtime-benchmarks", test))] impl, $instance: $instance_bound )? > $crate::Benchmarking for Pallet where T: frame_system::Config, $( $where_clause )* { fn benchmarks(extra: bool) -> $crate::Vec<$crate::BenchmarkMetadata> { let mut all_names = $crate::vec![ $( stringify!($name).as_ref() ),* ]; if !extra { let extra = [ $( stringify!($name_extra).as_ref() ),* ]; all_names.retain(|x| !extra.contains(x)); } all_names.into_iter().map(|benchmark| { let selected_benchmark = match benchmark { $( stringify!($name) => SelectedBenchmark::$name, )* _ => panic!("all benchmarks should be selectable"), }; let components = < SelectedBenchmark as $crate::BenchmarkingSetup >::components(&selected_benchmark); $crate::BenchmarkMetadata { name: benchmark.as_bytes().to_vec(), components, } }).collect::<$crate::Vec<_>>() } fn run_benchmark( extrinsic: &[u8], c: &[($crate::BenchmarkParameter, u32)], whitelist: &[$crate::TrackedStorageKey], verify: bool, internal_repeats: u32, ) -> Result<$crate::Vec<$crate::BenchmarkResult>, $crate::BenchmarkError> { // Map the input to the selected benchmark. let extrinsic = $crate::str::from_utf8(extrinsic) .map_err(|_| "`extrinsic` is not a valid utf8 string!")?; let selected_benchmark = match extrinsic { $( stringify!($name) => SelectedBenchmark::$name, )* _ => return Err("Could not find extrinsic.".into()), }; // Add whitelist to DB including whitelisted caller let mut whitelist = whitelist.to_vec(); let whitelisted_caller_key = as $crate::frame_support::storage::StorageMap<_,_>>::hashed_key_for( $crate::whitelisted_caller::() ); whitelist.push(whitelisted_caller_key.into()); // Whitelist the transactional layer. let transactional_layer_key = $crate::TrackedStorageKey::new( $crate::frame_support::storage::transactional::TRANSACTION_LEVEL_KEY.into() ); whitelist.push(transactional_layer_key); $crate::benchmarking::set_whitelist(whitelist); let mut results: $crate::Vec<$crate::BenchmarkResult> = $crate::Vec::new(); // Always do at least one internal repeat... for _ in 0 .. internal_repeats.max(1) { // Always reset the state after the benchmark. $crate::defer!($crate::benchmarking::wipe_db()); // Set up the externalities environment for the setup we want to // benchmark. let closure_to_benchmark = < SelectedBenchmark as $crate::BenchmarkingSetup >::instance(&selected_benchmark, c, verify)?; // Set the block number to at least 1 so events are deposited. if $crate::Zero::is_zero(&frame_system::Pallet::::block_number()) { frame_system::Pallet::::set_block_number(1u32.into()); } // Commit the externalities to the database, flushing the DB cache. // This will enable worst case scenario for reading from the database. $crate::benchmarking::commit_db(); // Reset the read/write counter so we don't count operations in the setup process. $crate::benchmarking::reset_read_write_count(); // Time the extrinsic logic. $crate::log::trace!( target: "benchmark", "Start Benchmark: {} ({:?})", extrinsic, c ); let start_pov = $crate::benchmarking::proof_size(); let start_extrinsic = $crate::benchmarking::current_time(); closure_to_benchmark()?; let finish_extrinsic = $crate::benchmarking::current_time(); let end_pov = $crate::benchmarking::proof_size(); // Calculate the diff caused by the benchmark. let elapsed_extrinsic = finish_extrinsic.saturating_sub(start_extrinsic); let diff_pov = match (start_pov, end_pov) { (Some(start), Some(end)) => end.saturating_sub(start), _ => Default::default(), }; // Commit the changes to get proper write count $crate::benchmarking::commit_db(); $crate::log::trace!( target: "benchmark", "End Benchmark: {} ns", elapsed_extrinsic ); let read_write_count = $crate::benchmarking::read_write_count(); $crate::log::trace!( target: "benchmark", "Read/Write Count {:?}", read_write_count ); // Time the storage root recalculation. let start_storage_root = $crate::benchmarking::current_time(); $crate::storage_root($crate::StateVersion::V1); let finish_storage_root = $crate::benchmarking::current_time(); let elapsed_storage_root = finish_storage_root - start_storage_root; let skip_meta = [ $( stringify!($name_skip_meta).as_ref() ),* ]; let read_and_written_keys = if skip_meta.contains(&extrinsic) { $crate::vec![(b"Skipped Metadata".to_vec(), 0, 0, false)] } else { $crate::benchmarking::get_read_and_written_keys() }; results.push($crate::BenchmarkResult { components: c.to_vec(), extrinsic_time: elapsed_extrinsic, storage_root_time: elapsed_storage_root, reads: read_write_count.0, repeat_reads: read_write_count.1, writes: read_write_count.2, repeat_writes: read_write_count.3, proof_size: diff_pov, keys: read_and_written_keys, }); } return Ok(results); } } #[cfg(test)] impl, $instance: $instance_bound )? > Pallet where T: frame_system::Config, $( $where_clause )* { /// Test a particular benchmark by name. /// /// This isn't called `test_benchmark_by_name` just in case some end-user eventually /// writes a benchmark, itself called `by_name`; the function would be shadowed in /// that case. /// /// This is generally intended to be used by child test modules such as those created /// by the `impl_benchmark_test_suite` macro. However, it is not an error if a pallet /// author chooses not to implement benchmarks. #[allow(unused)] fn test_bench_by_name(name: &[u8]) -> Result<(), $crate::BenchmarkError> { let name = $crate::str::from_utf8(name) .map_err(|_| -> $crate::BenchmarkError { "`name` is not a valid utf8 string!".into() })?; match name { $( stringify!($name) => { $crate::paste::paste! { Self::[< test_benchmark_ $name >]() } } )* _ => Err("Could not find test for requested benchmark.".into()), } } } }; } // This creates a unit test for one benchmark of the main benchmark macro. // It runs the benchmark using the `high` and `low` value for each component // and ensure that everything completes successfully. // Instances each component with six values which can be controlled with the // env variable `VALUES_PER_COMPONENT`. #[macro_export] #[doc(hidden)] macro_rules! impl_benchmark_test { ( { $( $where_clause:tt )* } { $( $instance:ident: $instance_bound:tt )? } $name:ident ) => { $crate::paste::item! { #[cfg(test)] impl, $instance: $instance_bound )? > Pallet where T: frame_system::Config, $( $where_clause )* { #[allow(unused)] fn [] () -> Result<(), $crate::BenchmarkError> { let selected_benchmark = SelectedBenchmark::$name; let components = < SelectedBenchmark as $crate::BenchmarkingSetup >::components(&selected_benchmark); let execute_benchmark = | c: $crate::Vec<($crate::BenchmarkParameter, u32)> | -> Result<(), $crate::BenchmarkError> { // Always reset the state after the benchmark. $crate::defer!($crate::benchmarking::wipe_db()); // Set up the benchmark, return execution + verification function. let closure_to_verify = < SelectedBenchmark as $crate::BenchmarkingSetup >::instance(&selected_benchmark, &c, true)?; // Set the block number to at least 1 so events are deposited. if $crate::Zero::is_zero(&frame_system::Pallet::::block_number()) { frame_system::Pallet::::set_block_number(1u32.into()); } // Run execution + verification closure_to_verify() }; if components.is_empty() { execute_benchmark(Default::default())?; } else { let num_values: u32 = if let Ok(ev) = std::env::var("VALUES_PER_COMPONENT") { ev.parse().map_err(|_| { $crate::BenchmarkError::Stop( "Could not parse env var `VALUES_PER_COMPONENT` as u32." ) })? } else { 6 }; if num_values < 2 { return Err("`VALUES_PER_COMPONENT` must be at least 2".into()); } for (name, low, high) in components.clone().into_iter() { // Test the lowest, highest (if its different from the lowest) // and up to num_values-2 more equidistant values in between. // For 0..10 and num_values=6 this would mean: [0, 2, 4, 6, 8, 10] let mut values = $crate::vec![low]; let diff = (high - low).min(num_values - 1); let slope = (high - low) as f32 / diff as f32; for i in 1..=diff { let value = ((low as f32 + slope * i as f32) as u32) .clamp(low, high); values.push(value); } for component_value in values { // Select the max value for all the other components. let c: $crate::Vec<($crate::BenchmarkParameter, u32)> = components .iter() .map(|(n, _, h)| if *n == name { (*n, component_value) } else { (*n, *h) } ) .collect(); execute_benchmark(c)?; } } } Ok(()) } } } }; } /// This creates a test suite which runs the module's benchmarks. /// /// When called in `pallet_example_basic` as /// /// ```rust,ignore /// impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); /// ``` /// /// It expands to the equivalent of: /// /// ```rust,ignore /// #[cfg(test)] /// mod tests { /// use super::*; /// use crate::tests::{new_test_ext, Test}; /// use frame_support::assert_ok; /// /// #[test] /// fn test_benchmarks() { /// new_test_ext().execute_with(|| { /// assert_ok!(test_benchmark_accumulate_dummy::()); /// assert_ok!(test_benchmark_set_dummy::()); /// assert_ok!(test_benchmark_sort_vector::()); /// }); /// } /// } /// ``` /// /// When called inside the `benchmarks` macro of the `pallet_example_basic` as /// /// ```rust,ignore /// benchmarks! { /// // Benchmarks omitted for brevity /// /// impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); /// } /// ``` /// /// It expands to the equivalent of: /// /// ```rust,ignore /// #[cfg(test)] /// mod benchmarking { /// use super::*; /// use crate::tests::{new_test_ext, Test}; /// use frame_support::assert_ok; /// /// #[test] /// fn bench_accumulate_dummy() { /// new_test_ext().execute_with(|| { /// assert_ok!(test_benchmark_accumulate_dummy::()); /// }) /// } /// /// #[test] /// fn bench_set_dummy() { /// new_test_ext().execute_with(|| { /// assert_ok!(test_benchmark_set_dummy::()); /// }) /// } /// /// #[test] /// fn bench_sort_vector() { /// new_test_ext().execute_with(|| { /// assert_ok!(test_benchmark_sort_vector::()); /// }) /// } /// } /// ``` /// /// ## Arguments /// /// The first argument, `module`, must be the path to this crate's module. /// /// The second argument, `new_test_ext`, must be a function call which returns either a /// `sp_io::TestExternalities`, or some other type with a similar interface. /// /// Note that this function call is _not_ evaluated at compile time, but is instead copied textually /// into each appropriate invocation site. /// /// The third argument, `test`, must be the path to the runtime. The item to which this must refer /// will generally take the form: /// /// ```rust,ignore /// frame_support::construct_runtime!( /// pub enum Test where ... /// { ... } /// ); /// ``` /// /// There is an optional fourth argument, with keyword syntax: `benchmarks_path = /// path_to_benchmarks_invocation`. In the typical case in which this macro is in the same module as /// the `benchmarks!` invocation, you don't need to supply this. However, if the /// `impl_benchmark_test_suite!` invocation is in a different module than the `benchmarks!` /// invocation, then you should provide the path to the module containing the `benchmarks!` /// invocation: /// /// ```rust,ignore /// mod benches { /// benchmarks!{ /// ... /// } /// } /// /// mod tests { /// // because of macro syntax limitations, neither Pallet nor benches can be paths, but both have /// // to be idents in the scope of `impl_benchmark_test_suite`. /// use crate::{benches, Pallet}; /// /// impl_benchmark_test_suite!(Pallet, new_test_ext(), Test, benchmarks_path = benches); /// /// // new_test_ext and the Test item are defined later in this module /// } /// ``` /// /// There is an optional fifth argument, with keyword syntax: `extra = true` or `extra = false`. /// By default, this generates a test suite which iterates over all benchmarks, including those /// marked with the `#[extra]` annotation. Setting `extra = false` excludes those. /// /// There is an optional sixth argument, with keyword syntax: `exec_name = custom_exec_name`. /// By default, this macro uses `execute_with` for this parameter. This argument, if set, is subject /// to these restrictions: /// /// - It must be the name of a method applied to the output of the `new_test_ext` argument. /// - That method must have a signature capable of receiving a single argument of the form `impl /// FnOnce()`. // ## Notes (not for rustdoc) // // The biggest challenge for this macro is communicating the actual test functions to be run. We // can't just build an array of function pointers to each test function and iterate over it, because // the test functions are parameterized by the `Test` type. That's incompatible with // monomorphization: if it were legal, then even if the compiler detected and monomorphized the // functions into only the types of the callers, which implementation would the function pointer // point to? There would need to be some kind of syntax for selecting the destination of the pointer // according to a generic argument, and in general it would be a huge mess and not worth it. // // Instead, we're going to steal a trick from `fn run_benchmark`: generate a function which is // itself parametrized by `Test`, which accepts a `&[u8]` parameter containing the name of the // benchmark, and dispatches based on that to the appropriate real test implementation. Then, we can // just iterate over the `Benchmarking::benchmarks` list to run the actual implementations. #[macro_export] macro_rules! impl_benchmark_test_suite { ( $bench_module:ident, $new_test_ext:expr, $test:path $(, $( $rest:tt )* )? ) => { $crate::impl_test_function!( () () () $bench_module, $new_test_ext, $test $(, $( $rest )* )? ); } } // Takes all arguments from `impl_benchmark_test_suite` and three additional arguments. // // Can be configured to generate one #[test] fn per bench case or // one #[test] fn for all bench cases. // This depends on whether or not the first argument contains a non-empty list of bench names. #[macro_export] #[doc(hidden)] macro_rules! impl_test_function { // user might or might not have set some keyword arguments; set the defaults // // The weird syntax indicates that `rest` comes only after a comma, which is otherwise optional ( ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) $bench_module:ident, $new_test_ext:expr, $test:path $(, $( $rest:tt )* )? ) => { $crate::impl_test_function!( @cases: ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) @selected: $bench_module, $new_test_ext, $test, benchmarks_path = super, extra = true, exec_name = execute_with, @user: $( $( $rest )* )? ); }; // pick off the benchmarks_path keyword argument ( @cases: ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) @selected: $bench_module:ident, $new_test_ext:expr, $test:path, benchmarks_path = $old:ident, extra = $extra:expr, exec_name = $exec_name:ident, @user: benchmarks_path = $benchmarks_path:ident $(, $( $rest:tt )* )? ) => { $crate::impl_test_function!( @cases: ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) @selected: $bench_module, $new_test_ext, $test, benchmarks_path = $benchmarks_path, extra = $extra, exec_name = $exec_name, @user: $( $( $rest )* )? ); }; // pick off the extra keyword argument ( @cases: ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) @selected: $bench_module:ident, $new_test_ext:expr, $test:path, benchmarks_path = $benchmarks_path:ident, extra = $old:expr, exec_name = $exec_name:ident, @user: extra = $extra:expr $(, $( $rest:tt )* )? ) => { $crate::impl_test_function!( @cases: ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) @selected: $bench_module, $new_test_ext, $test, benchmarks_path = $benchmarks_path, extra = $extra, exec_name = $exec_name, @user: $( $( $rest )* )? ); }; // pick off the exec_name keyword argument ( @cases: ( $( $names:tt )* ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) @selected: $bench_module:ident, $new_test_ext:expr, $test:path, benchmarks_path = $benchmarks_path:ident, extra = $extra:expr, exec_name = $old:ident, @user: exec_name = $exec_name:ident $(, $( $rest:tt )* )? ) => { $crate::impl_test_function!( @cases: ( $( $names )* ) ( $( $names_extra )* ) ( $( $names_skip_meta )* ) @selected: $bench_module, $new_test_ext, $test, benchmarks_path = $benchmarks_path, extra = $extra, exec_name = $exec_name, @user: $( $( $rest )* )? ); }; // iteration-exit arm which generates a #[test] function for each case. ( @cases: ( $( $names:tt )+ ) ( $( $names_extra:tt )* ) ( $( $names_skip_meta:tt )* ) @selected: $bench_module:ident, $new_test_ext:expr, $test:path, benchmarks_path = $path_to_benchmarks_invocation:ident, extra = $extra:expr, exec_name = $exec_name:ident, @user: $(,)? ) => { $crate::impl_bench_case_tests!( { $bench_module, $new_test_ext, $exec_name, $test, $extra } { $( $names_extra:tt )* } $($names)+ ); }; // iteration-exit arm which generates one #[test] function for all cases. ( @cases: () () () @selected: $bench_module:ident, $new_test_ext:expr, $test:path, benchmarks_path = $path_to_benchmarks_invocation:ident, extra = $extra:expr, exec_name = $exec_name:ident, @user: $(,)? ) => { #[cfg(test)] mod benchmark_tests { use super::$bench_module; #[test] fn test_benchmarks() { $new_test_ext.$exec_name(|| { use $crate::Benchmarking; let mut anything_failed = false; println!("failing benchmark tests:"); for benchmark_metadata in $bench_module::<$test>::benchmarks($extra) { let benchmark_name = &benchmark_metadata.name; match std::panic::catch_unwind(|| { $bench_module::<$test>::test_bench_by_name(benchmark_name) }) { Err(err) => { println!( "{}: {:?}", $crate::str::from_utf8(benchmark_name) .expect("benchmark name is always a valid string!"), err, ); anything_failed = true; }, Ok(Err(err)) => { match err { $crate::BenchmarkError::Stop(err) => { println!( "{}: {:?}", $crate::str::from_utf8(benchmark_name) .expect("benchmark name is always a valid string!"), err, ); anything_failed = true; }, $crate::BenchmarkError::Override(_) => { // This is still considered a success condition. $crate::log::error!( "WARNING: benchmark error overrided - {}", $crate::str::from_utf8(benchmark_name) .expect("benchmark name is always a valid string!"), ); }, $crate::BenchmarkError::Skip => { // This is considered a success condition. $crate::log::error!( "WARNING: benchmark error skipped - {}", $crate::str::from_utf8(benchmark_name) .expect("benchmark name is always a valid string!"), ); } } }, Ok(Ok(())) => (), } } assert!(!anything_failed); }); } } }; } /// show error message and debugging info for the case of an error happening /// during a benchmark pub fn show_benchmark_debug_info( instance_string: &[u8], benchmark: &[u8], components: &[(BenchmarkParameter, u32)], verify: &bool, error_message: &str, ) -> sp_runtime::RuntimeString { sp_runtime::format_runtime_string!( "\n* Pallet: {}\n\ * Benchmark: {}\n\ * Components: {:?}\n\ * Verify: {:?}\n\ * Error message: {}", sp_std::str::from_utf8(instance_string) .expect("it's all just strings ran through the wasm interface. qed"), sp_std::str::from_utf8(benchmark) .expect("it's all just strings ran through the wasm interface. qed"), components, verify, error_message, ) } /// This macro adds pallet benchmarks to a `Vec` object. /// /// First create an object that holds in the input parameters for the benchmark: /// /// ```ignore /// let params = (&config, &whitelist); /// ``` /// /// The `whitelist` is a parameter you pass to control the DB read/write tracking. /// We use a vector of [TrackedStorageKey](./struct.TrackedStorageKey.html), which is a simple /// struct used to set if a key has been read or written to. /// /// For values that should be skipped entirely, we can just pass `key.into()`. For example: /// /// ``` /// use frame_benchmarking::TrackedStorageKey; /// let whitelist: Vec = vec![ /// // Block Number /// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac"), /// // Total Issuance /// array_bytes::hex_into_unchecked("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80"), /// // Execution Phase /// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a"), /// // Event Count /// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850"), /// ]; /// ``` /// /// Then define a mutable local variable to hold your `BenchmarkBatch` object: /// /// ```ignore /// let mut batches = Vec::::new(); /// ```` /// /// Then add the pallets you want to benchmark to this object, using their crate name and generated /// module struct: /// /// ```ignore /// add_benchmark!(params, batches, pallet_balances, Balances); /// add_benchmark!(params, batches, pallet_session, SessionBench::); /// add_benchmark!(params, batches, frame_system, SystemBench::); /// ... /// ``` /// /// At the end of `dispatch_benchmark`, you should return this batches object. /// /// In the case where you have multiple instances of a pallet that you need to separately benchmark, /// the name of your module struct will be used as a suffix to your outputted weight file. For /// example: /// /// ```ignore /// add_benchmark!(params, batches, pallet_balances, Balances); // pallet_balances.rs /// add_benchmark!(params, batches, pallet_collective, Council); // pallet_collective_council.rs /// add_benchmark!(params, batches, pallet_collective, TechnicalCommittee); // pallet_collective_technical_committee.rs /// ``` /// /// You can manipulate this suffixed string by using a type alias if needed. For example: /// /// ```ignore /// type Council2 = TechnicalCommittee; /// add_benchmark!(params, batches, pallet_collective, Council2); // pallet_collective_council_2.rs /// ``` #[macro_export] macro_rules! add_benchmark { ( $params:ident, $batches:ident, $name:path, $( $location:tt )* ) => ( let name_string = stringify!($name).as_bytes(); let instance_string = stringify!( $( $location )* ).as_bytes(); let (config, whitelist) = $params; let $crate::BenchmarkConfig { pallet, benchmark, selected_components, verify, internal_repeats, } = config; if &pallet[..] == &name_string[..] { let benchmark_result = $( $location )*::run_benchmark( &benchmark[..], &selected_components[..], whitelist, *verify, *internal_repeats, ); let final_results = match benchmark_result { Ok(results) => Some(results), Err($crate::BenchmarkError::Override(mut result)) => { // Insert override warning as the first storage key. $crate::log::error!( "WARNING: benchmark error overrided - {}", $crate::str::from_utf8(benchmark) .expect("benchmark name is always a valid string!") ); result.keys.insert(0, (b"Benchmark Override".to_vec(), 0, 0, false) ); Some($crate::vec![result]) }, Err($crate::BenchmarkError::Stop(e)) => { $crate::show_benchmark_debug_info( instance_string, benchmark, selected_components, verify, e, ); return Err(e.into()); }, Err($crate::BenchmarkError::Skip) => { $crate::log::error!( "WARNING: benchmark error skipped - {}", $crate::str::from_utf8(benchmark) .expect("benchmark name is always a valid string!") ); None } }; if let Some(final_results) = final_results { $batches.push($crate::BenchmarkBatch { pallet: name_string.to_vec(), instance: instance_string.to_vec(), benchmark: benchmark.clone(), results: final_results, }); } } ) } /// Callback for `define_benchmarks` to call `add_benchmark`. #[macro_export] macro_rules! cb_add_benchmarks { // anchor ( $params:ident, $batches:ident, [ $name:path, $( $location:tt )* ] ) => { add_benchmark!( $params, $batches, $name, $( $location )* ); }; // recursion tail ( $params:ident, $batches:ident, [ $name:path, $( $location:tt )* ] $([ $names:path, $( $locations:tt )* ])+ ) => { cb_add_benchmarks!( $params, $batches, [ $name, $( $location )* ] ); cb_add_benchmarks!( $params, $batches, $([ $names, $( $locations )* ])+ ); } } /// This macro allows users to easily generate a list of benchmarks for the pallets configured /// in the runtime. /// /// To use this macro, first create a an object to store the list: /// /// ```ignore /// let mut list = Vec::::new(); /// ``` /// /// Then pass this `list` to the macro, along with the `extra` boolean, the pallet crate, and /// pallet struct: /// /// ```ignore /// list_benchmark!(list, extra, pallet_balances, Balances); /// list_benchmark!(list, extra, pallet_session, SessionBench::); /// list_benchmark!(list, extra, frame_system, SystemBench::); /// ``` /// /// This should match what exists with the `add_benchmark!` macro. #[macro_export] macro_rules! list_benchmark { ( $list:ident, $extra:ident, $name:path, $( $location:tt )* ) => ( let pallet_string = stringify!($name).as_bytes(); let instance_string = stringify!( $( $location )* ).as_bytes(); let benchmarks = $( $location )*::benchmarks($extra); let pallet_benchmarks = BenchmarkList { pallet: pallet_string.to_vec(), instance: instance_string.to_vec(), benchmarks: benchmarks.to_vec(), }; $list.push(pallet_benchmarks) ) } /// Callback for `define_benchmarks` to call `list_benchmark`. #[macro_export] macro_rules! cb_list_benchmarks { // anchor ( $list:ident, $extra:ident, [ $name:path, $( $location:tt )* ] ) => { list_benchmark!( $list, $extra, $name, $( $location )* ); }; // recursion tail ( $list:ident, $extra:ident, [ $name:path, $( $location:tt )* ] $([ $names:path, $( $locations:tt )* ])+ ) => { cb_list_benchmarks!( $list, $extra, [ $name, $( $location )* ] ); cb_list_benchmarks!( $list, $extra, $([ $names, $( $locations )* ])+ ); } } /// Defines pallet configs that `add_benchmarks` and `list_benchmarks` use. /// Should be preferred instead of having a repetitive list of configs /// in `add_benchmark` and `list_benchmark`. #[macro_export] macro_rules! define_benchmarks { ( $([ $names:path, $( $locations:tt )* ])* ) => { /// Calls `list_benchmark` with all configs from `define_benchmarks` /// and passes the first two parameters on. /// /// Use as: /// ```ignore /// list_benchmarks!(list, extra); /// ``` #[macro_export] macro_rules! list_benchmarks { ( $list:ident, $extra:ident ) => { cb_list_benchmarks!( $list, $extra, $([ $names, $( $locations )* ])+ ); } } /// Calls `add_benchmark` with all configs from `define_benchmarks` /// and passes the first two parameters on. /// /// Use as: /// ```ignore /// add_benchmarks!(params, batches); /// ``` #[macro_export] macro_rules! add_benchmarks { ( $params:ident, $batches:ident ) => { cb_add_benchmarks!( $params, $batches, $([ $names, $( $locations )* ])+ ); } } } }