macro_rules! runtime_benchmarks {
(
{ $runtime:ident, $pallet:ident }
$( $rest:tt )*
) => { ... };
}
Expand description
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:
// 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 parenthesised.
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 expresions 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:
use path_to_node_runtime::MyRuntime;
use path_to_account_id::AccountId;
use frame_benchmarking::account;
use orml_benchmarking::runtime_benchmarks;
runtime_benchmarks! {
// The constructed runtime struct, and the pallet to benchmark.
{ MyRuntime, my_pallet }
// first dispatchable: foo; this is a user dispatchable and operates on a `u8` vector of
// size `l`, which we allow to be initialized as usual.
foo {
let caller = account::<AccountId>(b"caller", 0, benchmarks_seed);
let l in 1 .. MAX_LENGTH => initialize_l(l);
}: _(Origin::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(Origin::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::<AccountId>(b"caller", 0, benchmarks_seed);
let c = 0 .. 10 => setup_c(&caller, c);
}: baz(Origin::Signed(caller))
// this is a second benchmark of the baz dispatchable with a different setup.
baz2 {
let caller = account::<AccountId>(b"caller", 0, benchmarks_seed);
let c = 0 .. 10 => setup_c_in_some_other_way(&caller, c);
}: baz(Origin::Signed(caller))
// this is benchmarking some code that is not a dispatchable.
populate_a_set {
let x in 0 .. 10_000;
let mut m = Vec::<u32>::new();
for i in 0..x {
m.insert(i);
}
}: { m.into_iter().collect::<BTreeSet>() }
}
Test functions are automatically generated for each benchmark and are
accessible to you when you run cargo test
. All tests are named
test_benchmark_<benchmark_name>
, expect you to pass them the Runtime
Config, 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.
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:
sort_vector {
let x in 1 .. 10000;
let mut m = Vec::<u32>::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 tests like so:
#[test]
fn test_benchmarks() {
new_test_ext().execute_with(|| {
assert_ok!(test_benchmark_dummy());
assert_err!(test_benchmark_other_name(), "Bad origin");
assert_ok!(test_benchmark_sort_vector());
assert_err!(test_benchmark_broken_benchmark(), "You forgot to sort!");
});
}