mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 02:07:56 +00:00
new proc-macro-based benchmarking syntax (#12924)
* add stub for new benchmark macro
* benchmark syntax
* add #[extrinsic call] separator
* parse #[benchmark] item as a function
* proper emission of error when #[extrinsic_call] annotation is missing
* clean up
* enclosing module via benchmarks! { } working
* use an attribute macro on the module instead of benchmarks! { }
* cargo fmt
* working component implementation
* WIP
* working
* add syntax for Linear<A, B>
* parsing of param ranges (still need to build tuple though)
* params parsing WIP
* clean up (don't need extrinsic call name)
* use proper Result syntax for BenchmarkDef parsing
* proper parsing of Linear<0, 1> style args
* successfully parse and make use of linear component ranges 💥
* rename support variable => home because eventually will be moved
* compile-time check that param range types implement ParamRange
* switch to using balances as example, failing on instance pallet
* successfully set up __origin and __call with balances 💥
* clean up
* use a module
* don't need a variable for transfer
* rename benchmark_transfer -> transfer because no longer conflicts
* clean up
* working with transfer_increasing_users as well 💥
* re-add BareBlock
* add comments for undocumented structs+functions+traits
* refactor in preparation for removing module requirements
* switch to a block instead of a module
* use the outer macro pattern to to enable #[benchmarks] aggregation
* successfully generate SelectedBenchmark 💥
* implement components for SelectedBenchmark
* implement instance for SelectedBenchmark
* properly track #[extra]
* working impl for fn benchmarks()
* run_benchmarks WIP
* finish run_benchmark! impl 💥
* import balances transfer_best_case benchmark
* import transfer_keep_alive balances pallet benchmark
* import set_balance_creating balances pallet benchmark
* import set_balance_killing balances pallet benchmark
* import force_transfer balances pallet benchmark
* add #[extra] annotation and docs to transfer_increasing_users
* import transfer_all balances pallet benchmark
* import force_unreserve balances pallet benchmark
* prepare to implement impl_benchmark_test_suite!
* ensure tests cover #[extra] before and after #[benchmark] tag
* refactor
* clean up
* fix
* move to outer
* switch to benchmarks/instance_benchmarks
* test impl almost done, strange compiler error
* benchmark test suites working 💥
* clean up
* add stub and basic parsing for where_clause
* working except where clause and extrinsic calls containing method chains
* assume option (2) for now wrt https://github.com/paritytech/substrate/pull/12924#issuecomment-1372938718
* clean up
* switch to attribute-style
* properly handle where clauses
* fix subtle missing where clause, now just MessageQueue issues
* fix block formatting in message-queue pallet
* switch to block vs non-block parsing of extrinsic call
* working now but some benchmark tests failing
* message-queue tests working (run order issue fixed) 🎉
* add comments and internal docs for fame_support_procedural::benchmark
* fix license years
* docs for lib.rs
* add docs to new support procedural macros
* don't allow #[benchmark] outside of benchmarking module
* add docs
* use benchmark(extra, skip_meta) style args
* update docs accordingly
* appease clippy
* bump ci
* add notes about `extra` and `skip_meta`
* fix doc tests
* re-run CI
* use `ignore` instead of `no_run` on doc examples
* bump CI
* replace some if-lets with if-elses
* more refactoring of if-let statements
* fix remaining if-lets in BenchmarkDef::from()
* fix if-lets in benchmarks()
* fix remaining if-lets, use nested find_map for extrinsic call
* switch to use #[extrinsic_call] or #[block] situationally
* refactor ExtrinsicCallDef => BenchmarkCallDef
* update docs with info about #[block]
* add macro stub for #[extrinsic_call]
* fix docs and add stub for #[block] as well
* remove unused extern crate line
* fix clippy nits
* Use V2 bench syntax in pallet-example-basic
Just testing the dev-ex...
Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
* carry over comment
* use curly-brace style for impl_benchmark_test_suite!
* remove unneeded parenthesis
* proper handling of _() extrinsic call style
* add docs for _() syntax
* fix crate access
* simplify keyword access
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
* simplify module content destructuring
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
* fix crate access "frame_benchmarking" => "frame-benchmarking", compiles
* use _() extrinsic call syntax where possible in balances
* simplify attr.path.segments.last()
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
* fix compile error being suppressed
* simplify extrinsic call keyword parsing
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
* use ? operator instead of return None
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
* rename generics => type_use_generics
rename full_generics => type_impl_generics
* simplify extrinsic call extraction with transpose
* bump CI
* nit
* proper handling of too many + too few block/extrinsic call annotations
* change to B >= A
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
* remove unneeded ignore
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
* remove another ignore
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
* add ui tests
* use _() style extrinsic call on accumulate_dummy
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
* add range check to ParamRange
* ui test for bad param ranges
* fix failing example
* add ignore back to other failing example
* tweak expr_call span
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
* fix typo
* eliminate a match
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
* change pub fn benchmarks to return Result<TokenStream>
* fix origin error span
* more informative error for invalid benchmark parameter name
* fix spans on a few benchmark errors
* remove unneeded clone
* refactor inner loop of benchmark function parsing
* preserve mod attributes
* refactor outer loop of benchmark def parsing code, greatly simplified
* simplify to use a ? operator when parsing benchmark attr path
* fix another ? operator
* further simplify benchmark function attr parsing with more ? ops
* refactor extrinsic call handling to use if let rather than match
* replace is_ok => is_err
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
* re-use name during expansion of benchmark def
* remove unneeded clone
* fix span for origin missing error
* fix missing semi
Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: parity-processbot <>
This commit is contained in:
Generated
+14
@@ -1614,6 +1614,17 @@ dependencies = [
|
||||
"rusticata-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-syn-parse"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e79116f119dd1dba1abf1f3405f03b9b0e79a27a3883864bfebded8a3dc768cd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.11.2"
|
||||
@@ -2326,6 +2337,7 @@ dependencies = [
|
||||
"sp-std",
|
||||
"sp-tracing",
|
||||
"sp-weights",
|
||||
"static_assertions",
|
||||
"tt-call",
|
||||
]
|
||||
|
||||
@@ -2335,6 +2347,7 @@ version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"cfg-expr",
|
||||
"derive-syn-parse",
|
||||
"frame-support-procedural-tools",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
@@ -2366,6 +2379,7 @@ dependencies = [
|
||||
name = "frame-support-test"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-support-test-pallet",
|
||||
"frame-system",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd.
|
||||
// Copyright (C) 2020-2023 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,22 +20,25 @@
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
|
||||
use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller};
|
||||
use frame_system::RawOrigin;
|
||||
use sp_runtime::traits::Bounded;
|
||||
|
||||
use crate::Pallet as Balances;
|
||||
|
||||
use frame_benchmarking::{account, impl_benchmark_test_suite, whitelisted_caller};
|
||||
use frame_support::benchmarking::*;
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
const SEED: u32 = 0;
|
||||
// existential deposit multiplier
|
||||
const ED_MULTIPLIER: u32 = 10;
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
#[instance_benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
// Benchmark `transfer` extrinsic with the worst possible conditions:
|
||||
// * Transfer will kill the sender account.
|
||||
// * Transfer will create the recipient account.
|
||||
transfer {
|
||||
#[benchmark]
|
||||
fn transfer() {
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let caller = whitelisted_caller();
|
||||
|
||||
@@ -47,53 +50,66 @@ benchmarks_instance_pallet! {
|
||||
// and reap this user.
|
||||
let recipient: T::AccountId = account("recipient", 0, SEED);
|
||||
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
|
||||
let transfer_amount = existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
|
||||
}: transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount)
|
||||
verify {
|
||||
let transfer_amount =
|
||||
existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
|
||||
|
||||
assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
|
||||
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
|
||||
}
|
||||
|
||||
// Benchmark `transfer` with the best possible condition:
|
||||
// * Both accounts exist and will continue to exist.
|
||||
#[extra]
|
||||
transfer_best_case {
|
||||
#[benchmark(extra)]
|
||||
fn transfer_best_case() {
|
||||
let caller = whitelisted_caller();
|
||||
let recipient: T::AccountId = account("recipient", 0, SEED);
|
||||
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
|
||||
|
||||
// Give the sender account max funds for transfer (their account will never reasonably be killed).
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
|
||||
// Give the sender account max funds for transfer (their account will never reasonably be
|
||||
// killed).
|
||||
let _ =
|
||||
<Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
|
||||
|
||||
// Give the recipient account existential deposit (thus their account already exists).
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&recipient, existential_deposit);
|
||||
let _ =
|
||||
<Balances<T, I> as Currency<_>>::make_free_balance_be(&recipient, existential_deposit);
|
||||
let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
|
||||
}: transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount)
|
||||
verify {
|
||||
|
||||
#[extrinsic_call]
|
||||
transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
|
||||
|
||||
assert!(!Balances::<T, I>::free_balance(&caller).is_zero());
|
||||
assert!(!Balances::<T, I>::free_balance(&recipient).is_zero());
|
||||
}
|
||||
|
||||
// Benchmark `transfer_keep_alive` with the worst possible condition:
|
||||
// * The recipient account is created.
|
||||
transfer_keep_alive {
|
||||
#[benchmark]
|
||||
fn transfer_keep_alive() {
|
||||
let caller = whitelisted_caller();
|
||||
let recipient: T::AccountId = account("recipient", 0, SEED);
|
||||
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
|
||||
|
||||
// Give the sender account max funds, thus a transfer will not kill account.
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
|
||||
let _ =
|
||||
<Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
|
||||
}: _(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount)
|
||||
verify {
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
|
||||
|
||||
assert!(!Balances::<T, I>::free_balance(&caller).is_zero());
|
||||
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
|
||||
}
|
||||
|
||||
// Benchmark `set_balance` coming from ROOT account. This always creates an account.
|
||||
set_balance_creating {
|
||||
#[benchmark]
|
||||
fn set_balance_creating() {
|
||||
let user: T::AccountId = account("user", 0, SEED);
|
||||
let user_lookup = T::Lookup::unlookup(user.clone());
|
||||
|
||||
@@ -101,14 +117,17 @@ benchmarks_instance_pallet! {
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance_amount);
|
||||
}: set_balance(RawOrigin::Root, user_lookup, balance_amount, balance_amount)
|
||||
verify {
|
||||
|
||||
#[extrinsic_call]
|
||||
set_balance(RawOrigin::Root, user_lookup, balance_amount, balance_amount);
|
||||
|
||||
assert_eq!(Balances::<T, I>::free_balance(&user), balance_amount);
|
||||
assert_eq!(Balances::<T, I>::reserved_balance(&user), balance_amount);
|
||||
}
|
||||
|
||||
// Benchmark `set_balance` coming from ROOT account. This always kills an account.
|
||||
set_balance_killing {
|
||||
#[benchmark]
|
||||
fn set_balance_killing() {
|
||||
let user: T::AccountId = account("user", 0, SEED);
|
||||
let user_lookup = T::Lookup::unlookup(user.clone());
|
||||
|
||||
@@ -116,15 +135,18 @@ benchmarks_instance_pallet! {
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance_amount);
|
||||
}: set_balance(RawOrigin::Root, user_lookup, Zero::zero(), Zero::zero())
|
||||
verify {
|
||||
|
||||
#[extrinsic_call]
|
||||
set_balance(RawOrigin::Root, user_lookup, Zero::zero(), Zero::zero());
|
||||
|
||||
assert!(Balances::<T, I>::free_balance(&user).is_zero());
|
||||
}
|
||||
|
||||
// Benchmark `force_transfer` extrinsic with the worst possible conditions:
|
||||
// * Transfer will kill the sender account.
|
||||
// * Transfer will create the recipient account.
|
||||
force_transfer {
|
||||
#[benchmark]
|
||||
fn force_transfer() {
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let source: T::AccountId = account("source", 0, SEED);
|
||||
let source_lookup = T::Lookup::unlookup(source.clone());
|
||||
@@ -133,12 +155,16 @@ benchmarks_instance_pallet! {
|
||||
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&source, balance);
|
||||
|
||||
// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, and reap this user.
|
||||
// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account,
|
||||
// and reap this user.
|
||||
let recipient: T::AccountId = account("recipient", 0, SEED);
|
||||
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
|
||||
let transfer_amount = existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
|
||||
}: force_transfer(RawOrigin::Root, source_lookup, recipient_lookup, transfer_amount)
|
||||
verify {
|
||||
let transfer_amount =
|
||||
existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, source_lookup, recipient_lookup, transfer_amount);
|
||||
|
||||
assert_eq!(Balances::<T, I>::free_balance(&source), Zero::zero());
|
||||
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
|
||||
}
|
||||
@@ -146,10 +172,9 @@ benchmarks_instance_pallet! {
|
||||
// This benchmark performs the same operation as `transfer` in the worst case scenario,
|
||||
// but additionally introduces many new users into the storage, increasing the the merkle
|
||||
// trie and PoV size.
|
||||
#[extra]
|
||||
transfer_increasing_users {
|
||||
#[benchmark(extra)]
|
||||
fn transfer_increasing_users(u: Linear<0, 1_000>) {
|
||||
// 1_000 is not very much, but this upper bound can be controlled by the CLI.
|
||||
let u in 0 .. 1_000;
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let caller = whitelisted_caller();
|
||||
|
||||
@@ -161,17 +186,20 @@ benchmarks_instance_pallet! {
|
||||
// and reap this user.
|
||||
let recipient: T::AccountId = account("recipient", 0, SEED);
|
||||
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
|
||||
let transfer_amount = existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
|
||||
let transfer_amount =
|
||||
existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
|
||||
|
||||
// Create a bunch of users in storage.
|
||||
for i in 0 .. u {
|
||||
for i in 0..u {
|
||||
// The `account` function uses `blake2_256` to generate unique accounts, so these
|
||||
// should be quite random and evenly distributed in the trie.
|
||||
let new_user: T::AccountId = account("new_user", i, SEED);
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&new_user, balance);
|
||||
}
|
||||
}: transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount)
|
||||
verify {
|
||||
|
||||
#[extrinsic_call]
|
||||
transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
|
||||
|
||||
assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
|
||||
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
|
||||
}
|
||||
@@ -179,7 +207,8 @@ benchmarks_instance_pallet! {
|
||||
// Benchmark `transfer_all` with the worst possible condition:
|
||||
// * The recipient account is created
|
||||
// * The sender is killed
|
||||
transfer_all {
|
||||
#[benchmark]
|
||||
fn transfer_all() {
|
||||
let caller = whitelisted_caller();
|
||||
let recipient: T::AccountId = account("recipient", 0, SEED);
|
||||
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
|
||||
@@ -188,13 +217,16 @@ benchmarks_instance_pallet! {
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
|
||||
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
|
||||
}: _(RawOrigin::Signed(caller.clone()), recipient_lookup, false)
|
||||
verify {
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), recipient_lookup, false);
|
||||
|
||||
assert!(Balances::<T, I>::free_balance(&caller).is_zero());
|
||||
assert_eq!(Balances::<T, I>::free_balance(&recipient), balance);
|
||||
}
|
||||
|
||||
force_unreserve {
|
||||
#[benchmark]
|
||||
fn force_unreserve() {
|
||||
let user: T::AccountId = account("user", 0, SEED);
|
||||
let user_lookup = T::Lookup::unlookup(user.clone());
|
||||
|
||||
@@ -208,15 +240,16 @@ benchmarks_instance_pallet! {
|
||||
assert_eq!(Balances::<T, I>::reserved_balance(&user), balance);
|
||||
assert!(Balances::<T, I>::free_balance(&user).is_zero());
|
||||
|
||||
}: _(RawOrigin::Root, user_lookup, balance)
|
||||
verify {
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, user_lookup, balance);
|
||||
|
||||
assert!(Balances::<T, I>::reserved_balance(&user).is_zero());
|
||||
assert_eq!(Balances::<T, I>::free_balance(&user), balance);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
impl_benchmark_test_suite! {
|
||||
Balances,
|
||||
crate::tests_composite::ExtBuilder::default().build(),
|
||||
crate::tests_composite::Test,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarking for pallet-example-basic.
|
||||
//! Benchmarking for `pallet-example-basic`.
|
||||
|
||||
// Only enable this module for benchmarking.
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::*;
|
||||
use frame_benchmarking::{benchmarks, whitelisted_caller};
|
||||
use frame_benchmarking::{impl_benchmark_test_suite, whitelisted_caller};
|
||||
use frame_support::benchmarking::{benchmarks, Linear};
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
// To actually run this benchmark on pallet-example-basic, we need to put this pallet into the
|
||||
@@ -33,14 +35,19 @@ use frame_system::RawOrigin;
|
||||
|
||||
// Details on using the benchmarks macro can be seen at:
|
||||
// https://paritytech.github.io/substrate/master/frame_benchmarking/trait.Benchmarking.html#tymethod.benchmarks
|
||||
benchmarks! {
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
// This will measure the execution time of `set_dummy`.
|
||||
set_dummy_benchmark {
|
||||
#[benchmark]
|
||||
fn set_dummy_benchmark() {
|
||||
// This is the benchmark setup phase.
|
||||
// `set_dummy` is a constant time function, hence we hard-code some random value here.
|
||||
let value = 1000u32.into();
|
||||
}: set_dummy(RawOrigin::Root, value) // The execution phase is just running `set_dummy` extrinsic call
|
||||
verify {
|
||||
#[extrinsic_call]
|
||||
set_dummy(RawOrigin::Root, value); // The execution phase is just running `set_dummy` extrinsic call
|
||||
|
||||
// This is the optional benchmark verification phase, asserting certain states.
|
||||
assert_eq!(Pallet::<T>::dummy(), Some(value))
|
||||
}
|
||||
@@ -49,22 +56,43 @@ benchmarks! {
|
||||
// The benchmark execution phase is shorthanded. When the name of the benchmark case is the same
|
||||
// as the extrinsic call. `_(...)` is used to represent the extrinsic name.
|
||||
// The benchmark verification phase is omitted.
|
||||
accumulate_dummy {
|
||||
#[benchmark]
|
||||
fn accumulate_dummy() {
|
||||
let value = 1000u32.into();
|
||||
// The caller account is whitelisted for DB reads/write by the benchmarking macro.
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
}: _(RawOrigin::Signed(caller), value)
|
||||
|
||||
// You can use `_` if the name of the Call matches the benchmark name.
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller), value);
|
||||
}
|
||||
|
||||
/// You can write helper functions in here since its a normal Rust module.
|
||||
fn setup_vector(len: u32) -> Vec<u32> {
|
||||
let mut vector = Vec::<u32>::new();
|
||||
for i in (0..len).rev() {
|
||||
vector.push(i);
|
||||
}
|
||||
vector
|
||||
}
|
||||
|
||||
// This will measure the execution time of sorting a vector.
|
||||
sort_vector {
|
||||
let x in 0 .. 10000;
|
||||
let mut m = Vec::<u32>::new();
|
||||
for i in (0..x).rev() {
|
||||
m.push(i);
|
||||
//
|
||||
// Define `x` as a linear component with range `[0, =10_000]`. This means that the benchmarking
|
||||
// will assume that the weight grows at a linear rate depending on `x`.
|
||||
#[benchmark]
|
||||
fn sort_vector(x: Linear<0, 10_000>) {
|
||||
let mut vector = setup_vector(x);
|
||||
|
||||
// The benchmark execution phase could also be a closure with custom code:
|
||||
#[block]
|
||||
{
|
||||
vector.sort();
|
||||
}
|
||||
}: {
|
||||
// The benchmark execution phase could also be a closure with custom code
|
||||
m.sort();
|
||||
|
||||
// Check that it was sorted correctly. This will not be benchmarked and is just for
|
||||
// verification.
|
||||
vector.windows(2).for_each(|w| assert!(w[0] <= w[1]));
|
||||
}
|
||||
|
||||
// This line generates test cases for benchmarking, and could be run by:
|
||||
@@ -75,5 +103,5 @@ benchmarks! {
|
||||
//
|
||||
// The line generates three steps per benchmark, with repeat=1 and the three steps are
|
||||
// [low, mid, high] of the range.
|
||||
impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test)
|
||||
impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
|
||||
// Copyright (C) 2023 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,68 +22,85 @@
|
||||
|
||||
use super::{mock_helpers::*, Pallet as MessageQueue, *};
|
||||
|
||||
use frame_benchmarking::{benchmarks, whitelisted_caller};
|
||||
use frame_support::traits::Get;
|
||||
use frame_benchmarking::{impl_benchmark_test_suite, whitelisted_caller};
|
||||
use frame_support::{benchmarking::*, traits::Get};
|
||||
use frame_system::RawOrigin;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
benchmarks! {
|
||||
where_clause {
|
||||
where
|
||||
// NOTE: We need to generate multiple origins, therefore Origin is `From<u32>`. The
|
||||
// `PartialEq` is for asserting the outcome of the ring (un)knitting and *could* be
|
||||
// removed if really necessary.
|
||||
<<T as Config>::MessageProcessor as ProcessMessage>::Origin: From<u32> + PartialEq,
|
||||
<T as Config>::Size: From<u32>,
|
||||
}
|
||||
#[benchmarks(
|
||||
where
|
||||
<<T as Config>::MessageProcessor as ProcessMessage>::Origin: From<u32> + PartialEq,
|
||||
<T as Config>::Size: From<u32>,
|
||||
// NOTE: We need to generate multiple origins, therefore Origin is `From<u32>`. The
|
||||
// `PartialEq` is for asserting the outcome of the ring (un)knitting and *could* be
|
||||
// removed if really necessary.
|
||||
)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
// Worst case path of `ready_ring_knit`.
|
||||
ready_ring_knit {
|
||||
let mid: MessageOriginOf::<T> = 1.into();
|
||||
#[benchmark]
|
||||
fn ready_ring_knit() {
|
||||
let mid: MessageOriginOf<T> = 1.into();
|
||||
build_ring::<T>(&[0.into(), mid.clone(), 2.into()]);
|
||||
unknit::<T>(&mid);
|
||||
assert_ring::<T>(&[0.into(), 2.into()]);
|
||||
let mut neighbours = None;
|
||||
}: {
|
||||
neighbours = MessageQueue::<T>::ready_ring_knit(&mid).ok();
|
||||
} verify {
|
||||
|
||||
#[block]
|
||||
{
|
||||
neighbours = MessageQueue::<T>::ready_ring_knit(&mid).ok();
|
||||
}
|
||||
|
||||
// The neighbours needs to be modified manually.
|
||||
BookStateFor::<T>::mutate(&mid, |b| { b.ready_neighbours = neighbours });
|
||||
BookStateFor::<T>::mutate(&mid, |b| b.ready_neighbours = neighbours);
|
||||
assert_ring::<T>(&[0.into(), 2.into(), mid]);
|
||||
}
|
||||
|
||||
// Worst case path of `ready_ring_unknit`.
|
||||
ready_ring_unknit {
|
||||
#[benchmark]
|
||||
fn ready_ring_unknit() {
|
||||
build_ring::<T>(&[0.into(), 1.into(), 2.into()]);
|
||||
assert_ring::<T>(&[0.into(), 1.into(), 2.into()]);
|
||||
let o: MessageOriginOf::<T> = 0.into();
|
||||
let o: MessageOriginOf<T> = 0.into();
|
||||
let neighbours = BookStateFor::<T>::get(&o).ready_neighbours.unwrap();
|
||||
}: {
|
||||
MessageQueue::<T>::ready_ring_unknit(&o, neighbours);
|
||||
} verify {
|
||||
|
||||
#[block]
|
||||
{
|
||||
MessageQueue::<T>::ready_ring_unknit(&o, neighbours);
|
||||
}
|
||||
|
||||
assert_ring::<T>(&[1.into(), 2.into()]);
|
||||
}
|
||||
|
||||
// `service_queues` without any queue processing.
|
||||
service_queue_base {
|
||||
}: {
|
||||
MessageQueue::<T>::service_queue(0.into(), &mut WeightMeter::max_limit(), Weight::MAX)
|
||||
#[benchmark]
|
||||
fn service_queue_base() {
|
||||
#[block]
|
||||
{
|
||||
MessageQueue::<T>::service_queue(0.into(), &mut WeightMeter::max_limit(), Weight::MAX);
|
||||
}
|
||||
}
|
||||
|
||||
// `service_page` without any message processing but with page completion.
|
||||
service_page_base_completion {
|
||||
#[benchmark]
|
||||
fn service_page_base_completion() {
|
||||
let origin: MessageOriginOf<T> = 0.into();
|
||||
let page = PageOf::<T>::default();
|
||||
Pages::<T>::insert(&origin, 0, &page);
|
||||
let mut book_state = single_page_book::<T>();
|
||||
let mut meter = WeightMeter::max_limit();
|
||||
let limit = Weight::MAX;
|
||||
}: {
|
||||
MessageQueue::<T>::service_page(&origin, &mut book_state, &mut meter, limit)
|
||||
|
||||
#[block]
|
||||
{
|
||||
MessageQueue::<T>::service_page(&origin, &mut book_state, &mut meter, limit);
|
||||
}
|
||||
}
|
||||
|
||||
// `service_page` without any message processing and without page completion.
|
||||
service_page_base_no_completion {
|
||||
#[benchmark]
|
||||
fn service_page_base_no_completion() {
|
||||
let origin: MessageOriginOf<T> = 0.into();
|
||||
let mut page = PageOf::<T>::default();
|
||||
// Mock the storage such that `is_complete` returns `false` but `peek_first` returns `None`.
|
||||
@@ -93,49 +110,73 @@ benchmarks! {
|
||||
let mut book_state = single_page_book::<T>();
|
||||
let mut meter = WeightMeter::max_limit();
|
||||
let limit = Weight::MAX;
|
||||
}: {
|
||||
MessageQueue::<T>::service_page(&origin, &mut book_state, &mut meter, limit)
|
||||
|
||||
#[block]
|
||||
{
|
||||
MessageQueue::<T>::service_page(&origin, &mut book_state, &mut meter, limit);
|
||||
}
|
||||
}
|
||||
|
||||
// Processing a single message from a page.
|
||||
service_page_item {
|
||||
#[benchmark]
|
||||
fn service_page_item() {
|
||||
let msg = vec![1u8; MaxMessageLenOf::<T>::get() as usize];
|
||||
let mut page = page::<T>(&msg.clone());
|
||||
let mut book = book_for::<T>(&page);
|
||||
assert!(page.peek_first().is_some(), "There is one message");
|
||||
let mut weight = WeightMeter::max_limit();
|
||||
}: {
|
||||
let status = MessageQueue::<T>::service_page_item(&0u32.into(), 0, &mut book, &mut page, &mut weight, Weight::MAX);
|
||||
assert_eq!(status, ItemExecutionStatus::Executed(true));
|
||||
} verify {
|
||||
|
||||
#[block]
|
||||
{
|
||||
let status = MessageQueue::<T>::service_page_item(
|
||||
&0u32.into(),
|
||||
0,
|
||||
&mut book,
|
||||
&mut page,
|
||||
&mut weight,
|
||||
Weight::MAX,
|
||||
);
|
||||
assert_eq!(status, ItemExecutionStatus::Executed(true));
|
||||
}
|
||||
|
||||
// Check that it was processed.
|
||||
assert_last_event::<T>(Event::Processed {
|
||||
hash: T::Hashing::hash(&msg), origin: 0.into(),
|
||||
weight_used: 1.into_weight(), success: true
|
||||
}.into());
|
||||
assert_last_event::<T>(
|
||||
Event::Processed {
|
||||
hash: T::Hashing::hash(&msg),
|
||||
origin: 0.into(),
|
||||
weight_used: 1.into_weight(),
|
||||
success: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let (_, processed, _) = page.peek_index(0).unwrap();
|
||||
assert!(processed);
|
||||
assert_eq!(book.message_count, 0);
|
||||
}
|
||||
|
||||
// Worst case for calling `bump_service_head`.
|
||||
bump_service_head {
|
||||
#[benchmark]
|
||||
fn bump_service_head() {
|
||||
setup_bump_service_head::<T>(0.into(), 10.into());
|
||||
let mut weight = WeightMeter::max_limit();
|
||||
}: {
|
||||
MessageQueue::<T>::bump_service_head(&mut weight);
|
||||
} verify {
|
||||
|
||||
#[block]
|
||||
{
|
||||
MessageQueue::<T>::bump_service_head(&mut weight);
|
||||
}
|
||||
|
||||
assert_eq!(ServiceHead::<T>::get().unwrap(), 10u32.into());
|
||||
assert_eq!(weight.consumed, T::WeightInfo::bump_service_head());
|
||||
}
|
||||
|
||||
reap_page {
|
||||
#[benchmark]
|
||||
fn reap_page() {
|
||||
// Mock the storage to get a *cullable* but not *reapable* page.
|
||||
let origin: MessageOriginOf<T> = 0.into();
|
||||
let mut book = single_page_book::<T>();
|
||||
let (page, msgs) = full_page::<T>();
|
||||
|
||||
for p in 0 .. T::MaxStale::get() * T::MaxStale::get() {
|
||||
for p in 0..T::MaxStale::get() * T::MaxStale::get() {
|
||||
if p == 0 {
|
||||
Pages::<T>::insert(&origin, p, &page);
|
||||
}
|
||||
@@ -148,16 +189,19 @@ benchmarks! {
|
||||
BookStateFor::<T>::insert(&origin, &book);
|
||||
assert!(Pages::<T>::contains_key(&origin, 0));
|
||||
|
||||
}: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::PageReaped{ origin: 0.into(), index: 0 }.into());
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0);
|
||||
|
||||
assert_last_event::<T>(Event::PageReaped { origin: 0.into(), index: 0 }.into());
|
||||
assert!(!Pages::<T>::contains_key(&origin, 0));
|
||||
}
|
||||
|
||||
// Worst case for `execute_overweight` where the page is removed as completed.
|
||||
//
|
||||
// The worst case occurs when executing the last message in a page of which all are skipped since it is using `peek_index` which has linear complexities.
|
||||
execute_overweight_page_removed {
|
||||
// The worst case occurs when executing the last message in a page of which all are skipped
|
||||
// since it is using `peek_index` which has linear complexities.
|
||||
#[benchmark]
|
||||
fn execute_overweight_page_removed() {
|
||||
let origin: MessageOriginOf<T> = 0.into();
|
||||
let (mut page, msgs) = full_page::<T>();
|
||||
// Skip all messages.
|
||||
@@ -168,19 +212,34 @@ benchmarks! {
|
||||
let book = book_for::<T>(&page);
|
||||
Pages::<T>::insert(&origin, 0, &page);
|
||||
BookStateFor::<T>::insert(&origin, &book);
|
||||
}: {
|
||||
MessageQueue::<T>::execute_overweight(RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX).unwrap()
|
||||
}
|
||||
verify {
|
||||
assert_last_event::<T>(Event::Processed {
|
||||
hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(),
|
||||
weight_used: Weight::from_parts(1, 1), success: true
|
||||
}.into());
|
||||
|
||||
#[block]
|
||||
{
|
||||
MessageQueue::<T>::execute_overweight(
|
||||
RawOrigin::Signed(whitelisted_caller()).into(),
|
||||
0u32.into(),
|
||||
0u32,
|
||||
((msgs - 1) as u32).into(),
|
||||
Weight::MAX,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::Processed {
|
||||
hash: T::Hashing::hash(&((msgs - 1) as u32).encode()),
|
||||
origin: 0.into(),
|
||||
weight_used: Weight::from_parts(1, 1),
|
||||
success: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert!(!Pages::<T>::contains_key(&origin, 0), "Page must be removed");
|
||||
}
|
||||
|
||||
// Worst case for `execute_overweight` where the page is updated.
|
||||
execute_overweight_page_updated {
|
||||
#[benchmark]
|
||||
fn execute_overweight_page_updated() {
|
||||
let origin: MessageOriginOf<T> = 0.into();
|
||||
let (mut page, msgs) = full_page::<T>();
|
||||
// Skip all messages.
|
||||
@@ -190,16 +249,34 @@ benchmarks! {
|
||||
let book = book_for::<T>(&page);
|
||||
Pages::<T>::insert(&origin, 0, &page);
|
||||
BookStateFor::<T>::insert(&origin, &book);
|
||||
}: {
|
||||
MessageQueue::<T>::execute_overweight(RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX).unwrap()
|
||||
}
|
||||
verify {
|
||||
assert_last_event::<T>(Event::Processed {
|
||||
hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(),
|
||||
weight_used: Weight::from_parts(1, 1), success: true
|
||||
}.into());
|
||||
|
||||
#[block]
|
||||
{
|
||||
MessageQueue::<T>::execute_overweight(
|
||||
RawOrigin::Signed(whitelisted_caller()).into(),
|
||||
0u32.into(),
|
||||
0u32,
|
||||
((msgs - 1) as u32).into(),
|
||||
Weight::MAX,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::Processed {
|
||||
hash: T::Hashing::hash(&((msgs - 1) as u32).encode()),
|
||||
origin: 0.into(),
|
||||
weight_used: Weight::from_parts(1, 1),
|
||||
success: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert!(Pages::<T>::contains_key(&origin, 0), "Page must be updated");
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::<crate::integration_test::Test>(), crate::integration_test::Test);
|
||||
impl_benchmark_test_suite! {
|
||||
MessageQueue,
|
||||
crate::mock::new_test_ext::<crate::integration_test::Test>(),
|
||||
crate::integration_test::Test
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../pri
|
||||
sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" }
|
||||
sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" }
|
||||
sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" }
|
||||
static_assertions = "1.1.0"
|
||||
tt-call = "1.0.8"
|
||||
frame-support-procedural = { version = "4.0.0-dev", default-features = false, path = "./procedural" }
|
||||
paste = "1.0"
|
||||
|
||||
@@ -15,6 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
derive-syn-parse = "0.1.5"
|
||||
Inflector = "0.11.4"
|
||||
cfg-expr = "0.10.3"
|
||||
itertools = "0.10.3"
|
||||
|
||||
@@ -0,0 +1,860 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2023 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.
|
||||
|
||||
//! Home of the parsing and expansion code for the new pallet benchmarking syntax
|
||||
|
||||
use derive_syn_parse::Parse;
|
||||
use frame_support_procedural_tools::generate_crate_access_2018;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::{
|
||||
parenthesized,
|
||||
parse::{Nothing, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token::{Colon2, Comma, Gt, Lt, Paren},
|
||||
Attribute, Error, Expr, ExprBlock, ExprCall, ExprPath, FnArg, Item, ItemFn, ItemMod, LitInt,
|
||||
Pat, Path, PathArguments, PathSegment, Result, Stmt, Token, Type, WhereClause,
|
||||
};
|
||||
|
||||
mod keywords {
|
||||
use syn::custom_keyword;
|
||||
|
||||
custom_keyword!(benchmark);
|
||||
custom_keyword!(benchmarks);
|
||||
custom_keyword!(block);
|
||||
custom_keyword!(extra);
|
||||
custom_keyword!(extrinsic_call);
|
||||
custom_keyword!(skip_meta);
|
||||
}
|
||||
|
||||
/// This represents the raw parsed data for a param definition such as `x: Linear<10, 20>`.
|
||||
#[derive(Clone)]
|
||||
struct ParamDef {
|
||||
name: String,
|
||||
typ: Type,
|
||||
start: u32,
|
||||
end: u32,
|
||||
}
|
||||
|
||||
/// Allows easy parsing of the `<10, 20>` component of `x: Linear<10, 20>`.
|
||||
#[derive(Parse)]
|
||||
struct RangeArgs {
|
||||
_lt_token: Lt,
|
||||
start: LitInt,
|
||||
_comma: Comma,
|
||||
end: LitInt,
|
||||
_gt_token: Gt,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct BenchmarkAttrs {
|
||||
skip_meta: bool,
|
||||
extra: bool,
|
||||
}
|
||||
|
||||
/// Represents a single benchmark option
|
||||
enum BenchmarkAttrKeyword {
|
||||
Extra,
|
||||
SkipMeta,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for BenchmarkAttrKeyword {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(keywords::extra) {
|
||||
let _extra: keywords::extra = input.parse()?;
|
||||
return Ok(BenchmarkAttrKeyword::Extra)
|
||||
} else if lookahead.peek(keywords::skip_meta) {
|
||||
let _skip_meta: keywords::skip_meta = input.parse()?;
|
||||
return Ok(BenchmarkAttrKeyword::SkipMeta)
|
||||
} else {
|
||||
return Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for BenchmarkAttrs {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if !lookahead.peek(Paren) {
|
||||
let _nothing: Nothing = input.parse()?;
|
||||
return Ok(BenchmarkAttrs { skip_meta: false, extra: false })
|
||||
}
|
||||
let content;
|
||||
let _paren: Paren = parenthesized!(content in input);
|
||||
let mut extra = false;
|
||||
let mut skip_meta = false;
|
||||
let args = Punctuated::<BenchmarkAttrKeyword, Token![,]>::parse_terminated(&content)?;
|
||||
for arg in args.into_iter() {
|
||||
match arg {
|
||||
BenchmarkAttrKeyword::Extra => {
|
||||
if extra {
|
||||
return Err(content.error("`extra` can only be specified once"))
|
||||
}
|
||||
extra = true;
|
||||
},
|
||||
BenchmarkAttrKeyword::SkipMeta => {
|
||||
if skip_meta {
|
||||
return Err(content.error("`skip_meta` can only be specified once"))
|
||||
}
|
||||
skip_meta = true;
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(BenchmarkAttrs { extra, skip_meta })
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the parsed extrinsic call for a benchmark
|
||||
#[derive(Clone)]
|
||||
enum BenchmarkCallDef {
|
||||
ExtrinsicCall { origin: Expr, expr_call: ExprCall, attr_span: Span }, // #[extrinsic_call]
|
||||
Block { block: ExprBlock, attr_span: Span }, // #[block]
|
||||
}
|
||||
|
||||
impl BenchmarkCallDef {
|
||||
/// Returns the `span()` for attribute
|
||||
fn attr_span(&self) -> Span {
|
||||
match self {
|
||||
BenchmarkCallDef::ExtrinsicCall { origin: _, expr_call: _, attr_span } => *attr_span,
|
||||
BenchmarkCallDef::Block { block: _, attr_span } => *attr_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a parsed `#[benchmark]` or `#[instance_banchmark]` item.
|
||||
#[derive(Clone)]
|
||||
struct BenchmarkDef {
|
||||
params: Vec<ParamDef>,
|
||||
setup_stmts: Vec<Stmt>,
|
||||
call_def: BenchmarkCallDef,
|
||||
verify_stmts: Vec<Stmt>,
|
||||
extra: bool,
|
||||
skip_meta: bool,
|
||||
}
|
||||
|
||||
impl BenchmarkDef {
|
||||
/// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node.
|
||||
pub fn from(item_fn: &ItemFn, extra: bool, skip_meta: bool) -> Result<BenchmarkDef> {
|
||||
let mut params: Vec<ParamDef> = Vec::new();
|
||||
|
||||
// parse params such as "x: Linear<0, 1>"
|
||||
for arg in &item_fn.sig.inputs {
|
||||
let invalid_param = |span| {
|
||||
return Err(Error::new(span, "Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.", ))
|
||||
};
|
||||
|
||||
let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) };
|
||||
let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) };
|
||||
|
||||
// check param name
|
||||
let var_span = ident.span();
|
||||
let invalid_param_name = || {
|
||||
return Err(Error::new(
|
||||
var_span,
|
||||
"Benchmark parameter names must consist of a single lowercase letter (a-z) and no other characters.",
|
||||
))
|
||||
};
|
||||
let name = ident.ident.to_token_stream().to_string();
|
||||
if name.len() > 1 {
|
||||
return invalid_param_name()
|
||||
};
|
||||
let Some(name_char) = name.chars().next() else { return invalid_param_name() };
|
||||
if !name_char.is_alphabetic() || !name_char.is_lowercase() {
|
||||
return invalid_param_name()
|
||||
}
|
||||
|
||||
// parse type
|
||||
let typ = &*arg.ty;
|
||||
let Type::Path(tpath) = typ else { return invalid_param(typ.span()) };
|
||||
let Some(segment) = tpath.path.segments.last() else { return invalid_param(typ.span()) };
|
||||
let args = segment.arguments.to_token_stream().into();
|
||||
let Ok(args) = syn::parse::<RangeArgs>(args) else { return invalid_param(typ.span()) };
|
||||
let Ok(start) = args.start.base10_parse::<u32>() else { return invalid_param(args.start.span()) };
|
||||
let Ok(end) = args.end.base10_parse::<u32>() else { return invalid_param(args.end.span()) };
|
||||
|
||||
if end < start {
|
||||
return Err(Error::new(
|
||||
args.start.span(),
|
||||
"The start of a `ParamRange` must be less than or equal to the end",
|
||||
))
|
||||
}
|
||||
|
||||
params.push(ParamDef { name, typ: typ.clone(), start, end });
|
||||
}
|
||||
|
||||
// #[extrinsic_call] / #[block] handling
|
||||
let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| {
|
||||
if let Stmt::Semi(Expr::Call(expr_call), _semi) = child {
|
||||
// #[extrinsic_call] case
|
||||
expr_call.attrs.iter().enumerate().find_map(|(k, attr)| {
|
||||
let segment = attr.path.segments.last()?;
|
||||
let _: keywords::extrinsic_call = syn::parse(segment.ident.to_token_stream().into()).ok()?;
|
||||
let mut expr_call = expr_call.clone();
|
||||
|
||||
// consume #[extrinsic_call] tokens
|
||||
expr_call.attrs.remove(k);
|
||||
|
||||
// extract origin from expr_call
|
||||
let Some(origin) = expr_call.args.first().cloned() else {
|
||||
return Some(Err(Error::new(expr_call.span(), "Single-item extrinsic calls must specify their origin as the first argument.")))
|
||||
};
|
||||
|
||||
Some(Ok((i, BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: attr.span() })))
|
||||
})
|
||||
} else if let Stmt::Expr(Expr::Block(block)) = child {
|
||||
// #[block] case
|
||||
block.attrs.iter().enumerate().find_map(|(k, attr)| {
|
||||
let segment = attr.path.segments.last()?;
|
||||
let _: keywords::block = syn::parse(segment.ident.to_token_stream().into()).ok()?;
|
||||
let mut block = block.clone();
|
||||
|
||||
// consume #[block] tokens
|
||||
block.attrs.remove(k);
|
||||
|
||||
Some(Ok((i, BenchmarkCallDef::Block { block, attr_span: attr.span() })))
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).collect::<Result<Vec<_>>>()?;
|
||||
let (i, call_def) = match &call_defs[..] {
|
||||
[(i, call_def)] => (*i, call_def.clone()), // = 1
|
||||
[] => return Err(Error::new( // = 0
|
||||
item_fn.block.brace_token.span,
|
||||
"No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body."
|
||||
)),
|
||||
_ => return Err(Error::new( // > 1
|
||||
call_defs[1].1.attr_span(),
|
||||
"Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark."
|
||||
)),
|
||||
};
|
||||
|
||||
Ok(BenchmarkDef {
|
||||
params,
|
||||
setup_stmts: Vec::from(&item_fn.block.stmts[0..i]),
|
||||
call_def,
|
||||
verify_stmts: Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len()]),
|
||||
extra,
|
||||
skip_meta,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses and expands a `#[benchmarks]` or `#[instance_benchmarks]` invocation
|
||||
pub fn benchmarks(
|
||||
attrs: TokenStream,
|
||||
tokens: TokenStream,
|
||||
instance: bool,
|
||||
) -> syn::Result<TokenStream> {
|
||||
// gather module info
|
||||
let module: ItemMod = syn::parse(tokens)?;
|
||||
let mod_span = module.span();
|
||||
let where_clause = match syn::parse::<Nothing>(attrs.clone()) {
|
||||
Ok(_) => quote!(),
|
||||
Err(_) => syn::parse::<WhereClause>(attrs)?.predicates.to_token_stream(),
|
||||
};
|
||||
let mod_vis = module.vis;
|
||||
let mod_name = module.ident;
|
||||
|
||||
// consume #[benchmarks] attribute by exclusing it from mod_attrs
|
||||
let mod_attrs: Vec<&Attribute> = module
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| syn::parse2::<keywords::benchmarks>(attr.to_token_stream()).is_err())
|
||||
.collect();
|
||||
|
||||
let mut benchmark_names: Vec<Ident> = Vec::new();
|
||||
let mut extra_benchmark_names: Vec<Ident> = Vec::new();
|
||||
let mut skip_meta_benchmark_names: Vec<Ident> = Vec::new();
|
||||
|
||||
let (_brace, mut content) =
|
||||
module.content.ok_or(syn::Error::new(mod_span, "Module cannot be empty!"))?;
|
||||
|
||||
// find all function defs marked with #[benchmark]
|
||||
let benchmark_fn_metas = content.iter_mut().filter_map(|stmt| {
|
||||
// parse as a function def first
|
||||
let Item::Fn(func) = stmt else { return None };
|
||||
|
||||
// find #[benchmark] attribute on function def
|
||||
let benchmark_attr = func.attrs.iter().find_map(|attr| {
|
||||
let seg = attr.path.segments.last()?;
|
||||
syn::parse::<keywords::benchmark>(seg.ident.to_token_stream().into()).ok()?;
|
||||
Some(attr)
|
||||
})?;
|
||||
|
||||
Some((benchmark_attr.clone(), func.clone(), stmt))
|
||||
});
|
||||
|
||||
// parse individual benchmark defs and args
|
||||
for (benchmark_attr, func, stmt) in benchmark_fn_metas {
|
||||
// parse any args provided to #[benchmark]
|
||||
let attr_tokens = benchmark_attr.tokens.to_token_stream().into();
|
||||
let benchmark_args: BenchmarkAttrs = syn::parse(attr_tokens)?;
|
||||
|
||||
// parse benchmark def
|
||||
let benchmark_def =
|
||||
BenchmarkDef::from(&func, benchmark_args.extra, benchmark_args.skip_meta)?;
|
||||
|
||||
// record benchmark name
|
||||
let name = &func.sig.ident;
|
||||
benchmark_names.push(name.clone());
|
||||
|
||||
// record name sets
|
||||
if benchmark_def.extra {
|
||||
extra_benchmark_names.push(name.clone());
|
||||
}
|
||||
if benchmark_def.skip_meta {
|
||||
skip_meta_benchmark_names.push(name.clone())
|
||||
}
|
||||
|
||||
// expand benchmark
|
||||
let expanded = expand_benchmark(benchmark_def, name, instance, where_clause.clone());
|
||||
|
||||
// replace original function def with expanded code
|
||||
*stmt = Item::Verbatim(expanded);
|
||||
}
|
||||
|
||||
// generics
|
||||
let type_use_generics = match instance {
|
||||
false => quote!(T),
|
||||
true => quote!(T, I),
|
||||
};
|
||||
let type_impl_generics = match instance {
|
||||
false => quote!(T: Config),
|
||||
true => quote!(T: Config<I>, I: 'static),
|
||||
};
|
||||
|
||||
let krate = generate_crate_access_2018("frame-benchmarking")?;
|
||||
let support = quote!(#krate::frame_support);
|
||||
|
||||
// benchmark name variables
|
||||
let benchmark_names_str: Vec<String> = benchmark_names.iter().map(|n| n.to_string()).collect();
|
||||
let extra_benchmark_names_str: Vec<String> =
|
||||
extra_benchmark_names.iter().map(|n| n.to_string()).collect();
|
||||
let skip_meta_benchmark_names_str: Vec<String> =
|
||||
skip_meta_benchmark_names.iter().map(|n| n.to_string()).collect();
|
||||
let mut selected_benchmark_mappings: Vec<TokenStream2> = Vec::new();
|
||||
let mut benchmarks_by_name_mappings: Vec<TokenStream2> = Vec::new();
|
||||
let test_idents: Vec<Ident> = benchmark_names_str
|
||||
.iter()
|
||||
.map(|n| Ident::new(format!("test_{}", n).as_str(), Span::call_site()))
|
||||
.collect();
|
||||
for i in 0..benchmark_names.len() {
|
||||
let name_ident = &benchmark_names[i];
|
||||
let name_str = &benchmark_names_str[i];
|
||||
let test_ident = &test_idents[i];
|
||||
selected_benchmark_mappings.push(quote!(#name_str => SelectedBenchmark::#name_ident));
|
||||
benchmarks_by_name_mappings.push(quote!(#name_str => Self::#test_ident()))
|
||||
}
|
||||
|
||||
// emit final quoted tokens
|
||||
let res = quote! {
|
||||
#(#mod_attrs)
|
||||
*
|
||||
#mod_vis mod #mod_name {
|
||||
#(#content)
|
||||
*
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
enum SelectedBenchmark {
|
||||
#(#benchmark_names),
|
||||
*
|
||||
}
|
||||
|
||||
impl<#type_impl_generics> #krate::BenchmarkingSetup<#type_use_generics> for SelectedBenchmark where #where_clause {
|
||||
fn components(&self) -> #krate::Vec<(#krate::BenchmarkParameter, u32, u32)> {
|
||||
match self {
|
||||
#(
|
||||
Self::#benchmark_names => {
|
||||
<#benchmark_names as #krate::BenchmarkingSetup<#type_use_generics>>::components(&#benchmark_names)
|
||||
}
|
||||
)
|
||||
*
|
||||
}
|
||||
}
|
||||
|
||||
fn instance(
|
||||
&self,
|
||||
components: &[(#krate::BenchmarkParameter, u32)],
|
||||
verify: bool,
|
||||
) -> Result<
|
||||
#krate::Box<dyn FnOnce() -> Result<(), #krate::BenchmarkError>>,
|
||||
#krate::BenchmarkError,
|
||||
> {
|
||||
match self {
|
||||
#(
|
||||
Self::#benchmark_names => {
|
||||
<#benchmark_names as #krate::BenchmarkingSetup<
|
||||
#type_use_generics
|
||||
>>::instance(&#benchmark_names, components, verify)
|
||||
}
|
||||
)
|
||||
*
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
impl<#type_impl_generics> #krate::Benchmarking for Pallet<#type_use_generics>
|
||||
where T: frame_system::Config, #where_clause
|
||||
{
|
||||
fn benchmarks(
|
||||
extra: bool,
|
||||
) -> #krate::Vec<#krate::BenchmarkMetadata> {
|
||||
let mut all_names = #krate::vec![
|
||||
#(#benchmark_names_str),
|
||||
*
|
||||
];
|
||||
if !extra {
|
||||
let extra = [
|
||||
#(#extra_benchmark_names_str),
|
||||
*
|
||||
];
|
||||
all_names.retain(|x| !extra.contains(x));
|
||||
}
|
||||
all_names.into_iter().map(|benchmark| {
|
||||
let selected_benchmark = match benchmark {
|
||||
#(#selected_benchmark_mappings),
|
||||
*,
|
||||
_ => panic!("all benchmarks should be selectable")
|
||||
};
|
||||
let components = <SelectedBenchmark as #krate::BenchmarkingSetup<#type_use_generics>>::components(&selected_benchmark);
|
||||
#krate::BenchmarkMetadata {
|
||||
name: benchmark.as_bytes().to_vec(),
|
||||
components,
|
||||
}
|
||||
}).collect::<#krate::Vec<_>>()
|
||||
}
|
||||
|
||||
fn run_benchmark(
|
||||
extrinsic: &[u8],
|
||||
c: &[(#krate::BenchmarkParameter, u32)],
|
||||
whitelist: &[#krate::TrackedStorageKey],
|
||||
verify: bool,
|
||||
internal_repeats: u32,
|
||||
) -> Result<#krate::Vec<#krate::BenchmarkResult>, #krate::BenchmarkError> {
|
||||
let extrinsic = #krate::str::from_utf8(extrinsic).map_err(|_| "`extrinsic` is not a valid utf-8 string!")?;
|
||||
let selected_benchmark = match extrinsic {
|
||||
#(#selected_benchmark_mappings),
|
||||
*,
|
||||
_ => return Err("Could not find extrinsic.".into()),
|
||||
};
|
||||
let mut whitelist = whitelist.to_vec();
|
||||
let whitelisted_caller_key = <frame_system::Account<
|
||||
T,
|
||||
> as #support::storage::StorageMap<_, _,>>::hashed_key_for(
|
||||
#krate::whitelisted_caller::<T::AccountId>()
|
||||
);
|
||||
whitelist.push(whitelisted_caller_key.into());
|
||||
let transactional_layer_key = #krate::TrackedStorageKey::new(
|
||||
#support::storage::transactional::TRANSACTION_LEVEL_KEY.into(),
|
||||
);
|
||||
whitelist.push(transactional_layer_key);
|
||||
#krate::benchmarking::set_whitelist(whitelist);
|
||||
let mut results: #krate::Vec<#krate::BenchmarkResult> = #krate::Vec::new();
|
||||
|
||||
// Always do at least one internal repeat...
|
||||
for _ in 0 .. internal_repeats.max(1) {
|
||||
// Always reset the state after the benchmark.
|
||||
#krate::defer!(#krate::benchmarking::wipe_db());
|
||||
|
||||
// Set up the externalities environment for the setup we want to
|
||||
// benchmark.
|
||||
let closure_to_benchmark = <
|
||||
SelectedBenchmark as #krate::BenchmarkingSetup<#type_use_generics>
|
||||
>::instance(&selected_benchmark, c, verify)?;
|
||||
|
||||
// Set the block number to at least 1 so events are deposited.
|
||||
if #krate::Zero::is_zero(&frame_system::Pallet::<T>::block_number()) {
|
||||
frame_system::Pallet::<T>::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.
|
||||
#krate::benchmarking::commit_db();
|
||||
|
||||
// Reset the read/write counter so we don't count operations in the setup process.
|
||||
#krate::benchmarking::reset_read_write_count();
|
||||
|
||||
// Time the extrinsic logic.
|
||||
#krate::log::trace!(
|
||||
target: "benchmark",
|
||||
"Start Benchmark: {} ({:?})",
|
||||
extrinsic,
|
||||
c
|
||||
);
|
||||
|
||||
let start_pov = #krate::benchmarking::proof_size();
|
||||
let start_extrinsic = #krate::benchmarking::current_time();
|
||||
|
||||
closure_to_benchmark()?;
|
||||
|
||||
let finish_extrinsic = #krate::benchmarking::current_time();
|
||||
let end_pov = #krate::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
|
||||
#krate::benchmarking::commit_db();
|
||||
#krate::log::trace!(
|
||||
target: "benchmark",
|
||||
"End Benchmark: {} ns", elapsed_extrinsic
|
||||
);
|
||||
let read_write_count = #krate::benchmarking::read_write_count();
|
||||
#krate::log::trace!(
|
||||
target: "benchmark",
|
||||
"Read/Write Count {:?}", read_write_count
|
||||
);
|
||||
|
||||
// Time the storage root recalculation.
|
||||
let start_storage_root = #krate::benchmarking::current_time();
|
||||
#krate::storage_root(#krate::StateVersion::V1);
|
||||
let finish_storage_root = #krate::benchmarking::current_time();
|
||||
let elapsed_storage_root = finish_storage_root - start_storage_root;
|
||||
|
||||
let skip_meta = [ #(#skip_meta_benchmark_names_str),* ];
|
||||
let read_and_written_keys = if skip_meta.contains(&extrinsic) {
|
||||
#krate::vec![(b"Skipped Metadata".to_vec(), 0, 0, false)]
|
||||
} else {
|
||||
#krate::benchmarking::get_read_and_written_keys()
|
||||
};
|
||||
|
||||
results.push(#krate::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<#type_impl_generics> Pallet<#type_use_generics> 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<(), #krate::BenchmarkError> {
|
||||
let name = #krate::str::from_utf8(name)
|
||||
.map_err(|_| -> #krate::BenchmarkError { "`name` is not a valid utf8 string!".into() })?;
|
||||
match name {
|
||||
#(#benchmarks_by_name_mappings),
|
||||
*,
|
||||
_ => Err("Could not find test for requested benchmark.".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#mod_vis use #mod_name::*;
|
||||
};
|
||||
Ok(res.into())
|
||||
}
|
||||
|
||||
/// Prepares a [`Vec<ParamDef>`] to be interpolated by [`quote!`] by creating easily-iterable
|
||||
/// arrays formatted in such a way that they can be interpolated directly.
|
||||
struct UnrolledParams {
|
||||
param_ranges: Vec<TokenStream2>,
|
||||
param_names: Vec<TokenStream2>,
|
||||
param_types: Vec<TokenStream2>,
|
||||
}
|
||||
|
||||
impl UnrolledParams {
|
||||
/// Constructs an [`UnrolledParams`] from a [`Vec<ParamDef>`]
|
||||
fn from(params: &Vec<ParamDef>) -> UnrolledParams {
|
||||
let param_ranges: Vec<TokenStream2> = params
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let name = Ident::new(&p.name, Span::call_site());
|
||||
let start = p.start;
|
||||
let end = p.end;
|
||||
quote!(#name, #start, #end)
|
||||
})
|
||||
.collect();
|
||||
let param_names: Vec<TokenStream2> = params
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let name = Ident::new(&p.name, Span::call_site());
|
||||
quote!(#name)
|
||||
})
|
||||
.collect();
|
||||
let param_types: Vec<TokenStream2> = params
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let typ = &p.typ;
|
||||
quote!(#typ)
|
||||
})
|
||||
.collect();
|
||||
UnrolledParams { param_ranges, param_names, param_types }
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs expansion of an already-parsed [`BenchmarkDef`].
|
||||
fn expand_benchmark(
|
||||
benchmark_def: BenchmarkDef,
|
||||
name: &Ident,
|
||||
is_instance: bool,
|
||||
where_clause: TokenStream2,
|
||||
) -> TokenStream2 {
|
||||
// set up variables needed during quoting
|
||||
let krate = match generate_crate_access_2018("frame-benchmarking") {
|
||||
Ok(ident) => ident,
|
||||
Err(err) => return err.to_compile_error().into(),
|
||||
};
|
||||
let home = quote!(#krate::frame_support::benchmarking);
|
||||
let codec = quote!(#krate::frame_support::codec);
|
||||
let traits = quote!(#krate::frame_support::traits);
|
||||
let setup_stmts = benchmark_def.setup_stmts;
|
||||
let verify_stmts = benchmark_def.verify_stmts;
|
||||
let test_ident = Ident::new(format!("test_{}", name.to_string()).as_str(), Span::call_site());
|
||||
|
||||
// unroll params (prepare for quoting)
|
||||
let unrolled = UnrolledParams::from(&benchmark_def.params);
|
||||
let param_names = unrolled.param_names;
|
||||
let param_ranges = unrolled.param_ranges;
|
||||
let param_types = unrolled.param_types;
|
||||
|
||||
let type_use_generics = match is_instance {
|
||||
false => quote!(T),
|
||||
true => quote!(T, I),
|
||||
};
|
||||
|
||||
let type_impl_generics = match is_instance {
|
||||
false => quote!(T: Config),
|
||||
true => quote!(T: Config<I>, I: 'static),
|
||||
};
|
||||
|
||||
let (pre_call, post_call) = match benchmark_def.call_def {
|
||||
BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: _ } => {
|
||||
let mut expr_call = expr_call.clone();
|
||||
|
||||
// remove first arg from expr_call
|
||||
let mut final_args = Punctuated::<Expr, Comma>::new();
|
||||
let args: Vec<&Expr> = expr_call.args.iter().collect();
|
||||
for arg in &args[1..] {
|
||||
final_args.push((*(*arg)).clone());
|
||||
}
|
||||
expr_call.args = final_args;
|
||||
|
||||
// determine call name (handles `_` and normal call syntax)
|
||||
let expr_span = expr_call.span();
|
||||
let call_err = || {
|
||||
quote_spanned!(expr_span=> "Extrinsic call must be a function call or `_`".to_compile_error()).into()
|
||||
};
|
||||
let call_name = match *expr_call.func {
|
||||
Expr::Path(expr_path) => {
|
||||
// normal function call
|
||||
let Some(segment) = expr_path.path.segments.last() else { return call_err(); };
|
||||
segment.ident.to_string()
|
||||
},
|
||||
Expr::Verbatim(tokens) => {
|
||||
// `_` style
|
||||
// replace `_` with fn name
|
||||
let Ok(_) = syn::parse::<Token![_]>(tokens.to_token_stream().into()) else { return call_err(); };
|
||||
name.to_string()
|
||||
},
|
||||
_ => return call_err(),
|
||||
};
|
||||
|
||||
// modify extrinsic call to be prefixed with "new_call_variant"
|
||||
let call_name = format!("new_call_variant_{}", call_name);
|
||||
let mut punct: Punctuated<PathSegment, Colon2> = Punctuated::new();
|
||||
punct.push(PathSegment {
|
||||
arguments: PathArguments::None,
|
||||
ident: Ident::new(call_name.as_str(), Span::call_site()),
|
||||
});
|
||||
*expr_call.func = Expr::Path(ExprPath {
|
||||
attrs: vec![],
|
||||
qself: None,
|
||||
path: Path { leading_colon: None, segments: punct },
|
||||
});
|
||||
|
||||
(
|
||||
// (pre_call, post_call):
|
||||
quote! {
|
||||
let __call = Call::<#type_use_generics>::#expr_call;
|
||||
let __benchmarked_call_encoded = #codec::Encode::encode(&__call);
|
||||
},
|
||||
quote! {
|
||||
let __call_decoded = <Call<#type_use_generics> as #codec::Decode>
|
||||
::decode(&mut &__benchmarked_call_encoded[..])
|
||||
.expect("call is encoded above, encoding must be correct");
|
||||
let __origin = #origin.into();
|
||||
<Call<#type_use_generics> as #traits::UnfilteredDispatchable>::dispatch_bypass_filter(
|
||||
__call_decoded,
|
||||
__origin,
|
||||
)?;
|
||||
},
|
||||
)
|
||||
},
|
||||
BenchmarkCallDef::Block { block, attr_span: _ } => (quote!(), quote!(#block)),
|
||||
};
|
||||
|
||||
// generate final quoted tokens
|
||||
let res = quote! {
|
||||
// compile-time assertions that each referenced param type implements ParamRange
|
||||
#(
|
||||
#home::assert_impl_all!(#param_types: #home::ParamRange);
|
||||
)*
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
struct #name;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
impl<#type_impl_generics> #krate::BenchmarkingSetup<#type_use_generics>
|
||||
for #name where #where_clause {
|
||||
fn components(&self) -> #krate::Vec<(#krate::BenchmarkParameter, u32, u32)> {
|
||||
#krate::vec! [
|
||||
#(
|
||||
(#krate::BenchmarkParameter::#param_ranges)
|
||||
),*
|
||||
]
|
||||
}
|
||||
|
||||
fn instance(
|
||||
&self,
|
||||
components: &[(#krate::BenchmarkParameter, u32)],
|
||||
verify: bool
|
||||
) -> Result<#krate::Box<dyn FnOnce() -> Result<(), #krate::BenchmarkError>>, #krate::BenchmarkError> {
|
||||
#(
|
||||
// prepare instance #param_names
|
||||
let #param_names = components.iter()
|
||||
.find(|&c| c.0 == #krate::BenchmarkParameter::#param_names)
|
||||
.ok_or("Could not find component during benchmark preparation.")?
|
||||
.1;
|
||||
)*
|
||||
|
||||
// benchmark setup code
|
||||
#(
|
||||
#setup_stmts
|
||||
)*
|
||||
#pre_call
|
||||
Ok(#krate::Box::new(move || -> Result<(), #krate::BenchmarkError> {
|
||||
#post_call
|
||||
if verify {
|
||||
#(
|
||||
#verify_stmts
|
||||
)*
|
||||
}
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<#type_impl_generics> Pallet<#type_use_generics> where T: ::frame_system::Config, #where_clause {
|
||||
#[allow(unused)]
|
||||
fn #test_ident() -> Result<(), #krate::BenchmarkError> {
|
||||
let selected_benchmark = SelectedBenchmark::#name;
|
||||
let components = <
|
||||
SelectedBenchmark as #krate::BenchmarkingSetup<T, _>
|
||||
>::components(&selected_benchmark);
|
||||
let execute_benchmark = |
|
||||
c: #krate::Vec<(#krate::BenchmarkParameter, u32)>
|
||||
| -> Result<(), #krate::BenchmarkError> {
|
||||
// Always reset the state after the benchmark.
|
||||
#krate::defer!(#krate::benchmarking::wipe_db());
|
||||
|
||||
// Set up the benchmark, return execution + verification function.
|
||||
let closure_to_verify = <
|
||||
SelectedBenchmark as #krate::BenchmarkingSetup<T, _>
|
||||
>::instance(&selected_benchmark, &c, true)?;
|
||||
|
||||
// Set the block number to at least 1 so events are deposited.
|
||||
if #krate::Zero::is_zero(&frame_system::Pallet::<T>::block_number()) {
|
||||
frame_system::Pallet::<T>::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(|_| {
|
||||
#krate::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 = #krate::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: #krate::Vec<(#krate::BenchmarkParameter, u32)> = components
|
||||
.iter()
|
||||
.map(|(n, _, h)|
|
||||
if *n == name {
|
||||
(*n, component_value)
|
||||
} else {
|
||||
(*n, *h)
|
||||
}
|
||||
)
|
||||
.collect();
|
||||
|
||||
execute_benchmark(c)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
};
|
||||
res
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#![recursion_limit = "512"]
|
||||
|
||||
mod benchmark;
|
||||
mod clone_no_bound;
|
||||
mod construct_runtime;
|
||||
mod crate_version;
|
||||
@@ -479,6 +480,69 @@ pub fn pallet(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pallet::pallet(attr, item)
|
||||
}
|
||||
|
||||
/// An attribute macro that can be attached to a (non-empty) module declaration. Doing so will
|
||||
/// designate that module as a benchmarking module.
|
||||
///
|
||||
/// See `frame_support::benchmarking` for more info.
|
||||
#[proc_macro_attribute]
|
||||
pub fn benchmarks(attr: TokenStream, tokens: TokenStream) -> TokenStream {
|
||||
match benchmark::benchmarks(attr, tokens, false) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// An attribute macro that can be attached to a (non-empty) module declaration. Doing so will
|
||||
/// designate that module as an instance benchmarking module.
|
||||
///
|
||||
/// See `frame_support::benchmarking` for more info.
|
||||
#[proc_macro_attribute]
|
||||
pub fn instance_benchmarks(attr: TokenStream, tokens: TokenStream) -> TokenStream {
|
||||
match benchmark::benchmarks(attr, tokens, true) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// An attribute macro used to declare a benchmark within a benchmarking module. Must be
|
||||
/// attached to a function definition containing an `#[extrinsic_call]` or `#[block]`
|
||||
/// attribute.
|
||||
///
|
||||
/// See `frame_support::benchmarking` for more info.
|
||||
#[proc_macro_attribute]
|
||||
pub fn benchmark(_attrs: TokenStream, _tokens: TokenStream) -> TokenStream {
|
||||
quote!(compile_error!(
|
||||
"`#[benchmark]` must be in a module labeled with #[benchmarks] or #[instance_benchmarks]."
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
/// An attribute macro used to specify the extrinsic call inside a benchmark function, and also
|
||||
/// used as a boundary designating where the benchmark setup code ends, and the benchmark
|
||||
/// verification code begins.
|
||||
///
|
||||
/// See `frame_support::benchmarking` for more info.
|
||||
#[proc_macro_attribute]
|
||||
pub fn extrinsic_call(_attrs: TokenStream, _tokens: TokenStream) -> TokenStream {
|
||||
quote!(compile_error!(
|
||||
"`#[extrinsic_call]` must be in a benchmark function definition labeled with `#[benchmark]`."
|
||||
);)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// An attribute macro used to specify that a block should be the measured portion of the
|
||||
/// enclosing benchmark function, This attribute is also used as a boundary designating where
|
||||
/// the benchmark setup code ends, and the benchmark verification code begins.
|
||||
///
|
||||
/// See `frame_support::benchmarking` for more info.
|
||||
#[proc_macro_attribute]
|
||||
pub fn block(_attrs: TokenStream, _tokens: TokenStream) -> TokenStream {
|
||||
quote!(compile_error!(
|
||||
"`#[block]` must be in a benchmark function definition labeled with `#[benchmark]`."
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Execute the annotated function in a new storage transaction.
|
||||
///
|
||||
/// The return type of the annotated function must be `Result`. All changes to storage performed
|
||||
|
||||
@@ -2745,5 +2745,228 @@ pub mod pallet_macros {
|
||||
};
|
||||
}
|
||||
|
||||
/// Contains macros, structs, and traits associated with v2 of the pallet benchmarking syntax.
|
||||
/// This module contains macros, structs, and traits associated with v2 of the pallet
|
||||
/// benchmarking syntax.
|
||||
///
|
||||
/// The [`benchmarking::benchmarks`] and [`benchmarking::instance_benchmarks`] macros can be
|
||||
/// used to designate a module as a benchmarking module that can contain benchmarks and
|
||||
/// benchmark tests. The `#[benchmarks]` variant will set up a regular, non-instance
|
||||
/// benchmarking module, and the `#[instance_benchmarks]` variant will set up the module in
|
||||
/// instance benchmarking mode.
|
||||
///
|
||||
/// Benchmarking modules should be gated behind a `#[cfg(feature = "runtime-benchmarks")]`
|
||||
/// feature gate to ensure benchmarking code that is only compiled when the
|
||||
/// `runtime-benchmarks` feature is enabled is not referenced.
|
||||
///
|
||||
/// The following is the general syntax for a benchmarks (or instance benchmarks) module:
|
||||
///
|
||||
/// ## General Syntax
|
||||
///
|
||||
/// ```ignore
|
||||
/// #![cfg(feature = "runtime-benchmarks")]
|
||||
///
|
||||
/// use super::{mock_helpers::*, Pallet as MyPallet};
|
||||
/// use frame_support::benchmarking::*;
|
||||
/// use frame_benchmarking::whitelisted_caller;
|
||||
///
|
||||
/// #[benchmarks]
|
||||
/// mod benchmarks {
|
||||
/// use super::*;
|
||||
///
|
||||
/// #[benchmark]
|
||||
/// fn bench_name_1(x: Linear<7, 1_000>, y: Linear<1_000, 100_0000>) {
|
||||
/// // setup code
|
||||
/// let z = x + y;
|
||||
/// let caller = whitelisted_caller();
|
||||
///
|
||||
/// #[extrinsic_call]
|
||||
/// extrinsic_name(SystemOrigin::Signed(caller), other, arguments);
|
||||
///
|
||||
/// // verification code
|
||||
/// assert_eq!(MyPallet::<T>::my_var(), z);
|
||||
/// }
|
||||
///
|
||||
/// #[benchmark]
|
||||
/// fn bench_name_2() {
|
||||
/// // setup code
|
||||
/// let caller = whitelisted_caller();
|
||||
///
|
||||
/// #[block]
|
||||
/// {
|
||||
/// something(some, thing);
|
||||
/// my_extrinsic(RawOrigin::Signed(caller), some, argument);
|
||||
/// something_else(foo, bar);
|
||||
/// }
|
||||
///
|
||||
/// // verification code
|
||||
/// assert_eq!(MyPallet::<T>::something(), 37);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Benchmark Definitions
|
||||
///
|
||||
/// Within a `#[benchmarks]` or `#[instance_benchmarks]` module, you can define individual
|
||||
/// benchmarks using the `#[benchmark]` attribute, as shown in the example above.
|
||||
///
|
||||
/// The `#[benchmark]` attribute expects a function definition with a blank return type and
|
||||
/// zero or more arguments whose names are valid `frame_benchmarking::BenchmarkParamater`
|
||||
/// parameters, such as `x`, `y`, `a`, `b`, etc., and whose param types must implement
|
||||
/// [`benchmarking::ParamRange`]. At the moment the only valid type that implements
|
||||
/// [`benchmarking::ParamRange`] is [`benchmarking::Linear`].
|
||||
///
|
||||
/// The valid syntax for defining a `Linear` is `Linear<A, B>` where `A`, and `B` are
|
||||
/// valid integer literals (that fit in a `u32`), such that `B` >= `A`.
|
||||
///
|
||||
/// Note that the benchmark function definition does not actually expand as a function
|
||||
/// definition, but rather is used to automatically create a number of impls and structs
|
||||
/// required by the benchmarking engine. For this reason, the visibility of the function
|
||||
/// definition as well as the return type are not used for any purpose and are discarded by the
|
||||
/// expansion code.
|
||||
///
|
||||
/// Also note that the `// setup code` and `// verification code` comments shown above are not
|
||||
/// required and are included simply for demonstration purposes.
|
||||
///
|
||||
/// ### `#[extrinsic_call]` and `#[block]`
|
||||
///
|
||||
/// Within the benchmark function body, either an `#[extrinsic_call]` or a `#[block]`
|
||||
/// annotation is required. These attributes should be attached to a block (shown in
|
||||
/// `bench_name_2` above) or a one-line function call (shown in `bench_name_1` above, in `syn`
|
||||
/// parlance this should be an `ExprCall`), respectively.
|
||||
///
|
||||
/// The `#[block]` syntax is broad and will benchmark any code contained within the block the
|
||||
/// attribute is attached to. If `#[block]` is attached to something other than a block, a
|
||||
/// compiler error will be emitted.
|
||||
///
|
||||
/// The one-line `#[extrinsic_call]` syntax must consist of a function call to an extrinsic,
|
||||
/// where the first argument is the origin. If `#[extrinsic_call]` is attached to an item that
|
||||
/// doesn't meet these requirements, a compiler error will be emitted.
|
||||
///
|
||||
/// As a short-hand, you may substitute the name of the extrinsic call with `_`, such as the
|
||||
/// following:
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[extrinsic_call]
|
||||
/// _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0);
|
||||
/// ```
|
||||
///
|
||||
/// The underscore will be substituted with the name of the benchmark (i.e. the name of the
|
||||
/// function in the benchmark function definition).
|
||||
///
|
||||
/// Regardless of whether `#[extrinsic_call]` or `#[block]` is used, this attribute also serves
|
||||
/// the purpose of designating the boundary between the setup code portion of the benchmark
|
||||
/// (everything before the `#[extrinsic_call]` or `#[block]` attribute) and the verification
|
||||
/// stage (everything after the item that the `#[extrinsic_call]` or `#[block]` attribute is
|
||||
/// attached to). The setup code section should contain any code that needs to execute before
|
||||
/// the measured portion of the benchmark executes. The verification section is where you can
|
||||
/// perform assertions to verify that the extrinsic call (or whatever is happening in your
|
||||
/// block, if you used the `#[block]` syntax) executed successfully.
|
||||
///
|
||||
/// Note that neither `#[extrinsic_call]` nor `#[block]` are real attribute macros and are
|
||||
/// instead consumed by the outer macro pattern as part of the enclosing benchmark function
|
||||
/// definition. This is why we are able to use `#[extrinsic_call]` and `#[block]` within a
|
||||
/// function definition even though this behavior has not been stabilized
|
||||
/// yet—`#[extrinsic_call]` and `#[block]` are parsed and consumed as part of the benchmark
|
||||
/// definition parsing code, so they never expand as their own attribute macros.
|
||||
///
|
||||
/// ### Optional Attributes
|
||||
///
|
||||
/// The keywords `extra` and `skip_meta` can be provided as optional arguments to the
|
||||
/// `#[benchmark]` attribute, i.e. `#[benchmark(extra, skip_meta)]`. Including either of these
|
||||
/// will enable the `extra` or `skip_meta` option, respectively. These options enable the same
|
||||
/// behavior they did in the old benchmarking syntax in `frame_benchmarking`, namely:
|
||||
///
|
||||
/// #### `extra`
|
||||
///
|
||||
/// Specifies that this benchmark should not normally run. To run benchmarks marked with
|
||||
/// `extra`, you will need to invoke the `frame-benchmarking-cli` with `--extra`.
|
||||
///
|
||||
/// #### `skip_meta`
|
||||
///
|
||||
/// Specifies that the benchmarking framework should not analyze the storage keys that
|
||||
/// benchmarked code read or wrote. This useful to suppress the prints in the form of unknown
|
||||
/// 0x… in case a storage key that does not have metadata. Note that this skips the analysis
|
||||
/// of all accesses, not just ones without metadata.
|
||||
///
|
||||
/// ## Where Clause
|
||||
///
|
||||
/// Some pallets require a where clause specifying constraints on their generics to make
|
||||
/// writing benchmarks feasible. To accomodate this situation, you can provide such a where
|
||||
/// clause as the (only) argument to the `#[benchmarks]` or `#[instance_benchmarks]` attribute
|
||||
/// macros. Below is an example of this taken from the `message-queue` pallet.
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[benchmarks(
|
||||
/// where
|
||||
/// <<T as Config>::MessageProcessor as ProcessMessage>::Origin: From<u32> + PartialEq,
|
||||
/// <T as Config>::Size: From<u32>,
|
||||
/// )]
|
||||
/// mod benchmarks {
|
||||
/// use super::*;
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Benchmark Tests
|
||||
///
|
||||
/// Benchmark tests can be generated using the old syntax in `frame_benchmarking`,
|
||||
/// including the `frame_benchmarking::impl_benchmark_test_suite` macro.
|
||||
///
|
||||
/// An example is shown below (taken from the `message-queue` pallet's `benchmarking` module):
|
||||
/// ```ignore
|
||||
/// #[benchmarks]
|
||||
/// mod benchmarks {
|
||||
/// use super::*;
|
||||
/// // ...
|
||||
/// impl_benchmark_test_suite!(
|
||||
/// MessageQueue,
|
||||
/// crate::mock::new_test_ext::<crate::integration_test::Test>(),
|
||||
/// crate::integration_test::Test
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub mod benchmarking {
|
||||
pub use frame_support_procedural::{
|
||||
benchmark, benchmarks, block, extrinsic_call, instance_benchmarks,
|
||||
};
|
||||
|
||||
// Used in #[benchmark] implementation to ensure that benchmark function arguments
|
||||
// implement [`ParamRange`].
|
||||
#[doc(hidden)]
|
||||
pub use static_assertions::assert_impl_all;
|
||||
|
||||
/// Used by the new benchmarking code to specify that a benchmarking variable is linear
|
||||
/// over some specified range, i.e. `Linear<0, 1_000>` means that the corresponding variable
|
||||
/// is allowed to range from `0` to `1000`, inclusive.
|
||||
///
|
||||
/// See [`frame_support::benchmarking`] for more info.
|
||||
pub struct Linear<const A: u32, const B: u32>;
|
||||
|
||||
/// Trait that must be implemented by all structs that can be used as parameter range types
|
||||
/// in the new benchmarking code (i.e. `Linear<0, 1_000>`). Right now there is just
|
||||
/// [`Linear`] but this could later be extended to support additional non-linear parameter
|
||||
/// ranges.
|
||||
///
|
||||
/// See [`frame_support::benchmarking`] for more info.
|
||||
pub trait ParamRange {
|
||||
/// Represents the (inclusive) starting number of this [`ParamRange`].
|
||||
fn start(&self) -> u32;
|
||||
|
||||
/// Represents the (inclusive) ending number of this [`ParamRange`]
|
||||
fn end(&self) -> u32;
|
||||
}
|
||||
|
||||
impl<const A: u32, const B: u32> ParamRange for Linear<A, B> {
|
||||
fn start(&self) -> u32 {
|
||||
return A
|
||||
}
|
||||
|
||||
fn end(&self) -> u32 {
|
||||
return B
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a macro that will enable/disable code based on `std` feature being active.
|
||||
sp_core::generate_feature_enabled_macro!(std_enabled, feature = "std", $);
|
||||
|
||||
@@ -19,6 +19,7 @@ sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../../
|
||||
sp-io = { version = "7.0.0", path = "../../../primitives/io", default-features = false }
|
||||
sp-state-machine = { version = "0.13.0", optional = true, path = "../../../primitives/state-machine" }
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../" }
|
||||
frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" }
|
||||
sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" }
|
||||
sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" }
|
||||
sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" }
|
||||
@@ -36,6 +37,7 @@ std = [
|
||||
"serde/std",
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"sp-core/std",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020-2023 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.
|
||||
|
||||
#[rustversion::attr(not(stable), ignore)]
|
||||
#[cfg(not(feature = "disable-ui-tests"))]
|
||||
#[test]
|
||||
fn benchmark_ui() {
|
||||
// Only run the ui tests when `RUN_UI_TESTS` is set.
|
||||
if std::env::var("RUN_UI_TESTS").is_err() {
|
||||
return
|
||||
}
|
||||
|
||||
// As trybuild is using `cargo check`, we don't need the real WASM binaries.
|
||||
std::env::set_var("SKIP_WASM_BUILD", "1");
|
||||
|
||||
// Deny all warnings since we emit warnings as part of a Pallet's UI.
|
||||
std::env::set_var("RUSTFLAGS", "--deny warnings");
|
||||
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/benchmark_ui/*.rs");
|
||||
t.pass("tests/benchmark_ui/pass/*.rs");
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench(winton: Linear<1, 2>) {
|
||||
let a = 2 + 2;
|
||||
#[block]
|
||||
{}
|
||||
assert_eq!(a, 4);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Benchmark parameter names must consist of a single lowercase letter (a-z) and no other characters.
|
||||
--> tests/benchmark_ui/bad_param_name.rs:10:11
|
||||
|
|
||||
10 | fn bench(winton: Linear<1, 2>) {
|
||||
| ^^^^^^
|
||||
@@ -0,0 +1,14 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
#[benchmark]
|
||||
fn bench(xx: Linear<1, 2>) {
|
||||
#[block]
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Benchmark parameter names must consist of a single lowercase letter (a-z) and no other characters.
|
||||
--> tests/benchmark_ui/bad_param_name_too_long.rs:8:11
|
||||
|
|
||||
8 | fn bench(xx: Linear<1, 2>) {
|
||||
| ^^
|
||||
@@ -0,0 +1,14 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
#[benchmark]
|
||||
fn bench(D: Linear<1, 2>) {
|
||||
#[block]
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Benchmark parameter names must consist of a single lowercase letter (a-z) and no other characters.
|
||||
--> tests/benchmark_ui/bad_param_name_upper_case.rs:8:11
|
||||
|
|
||||
8 | fn bench(D: Linear<1, 2>) {
|
||||
| ^
|
||||
@@ -0,0 +1,16 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench(x: Linear<3, 1>) {
|
||||
#[block]
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: The start of a `ParamRange` must be less than or equal to the end
|
||||
--> tests/benchmark_ui/bad_param_range.rs:10:21
|
||||
|
|
||||
10 | fn bench(x: Linear<3, 1>) {
|
||||
| ^
|
||||
@@ -0,0 +1,18 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench(y: Linear<1, 2>, x: u32) {
|
||||
let a = 2 + 2;
|
||||
#[block]
|
||||
{}
|
||||
assert_eq!(a, 4);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.
|
||||
--> tests/benchmark_ui/bad_params.rs:10:31
|
||||
|
|
||||
10 | fn bench(y: Linear<1, 2>, x: u32) {
|
||||
| ^^^
|
||||
@@ -0,0 +1,20 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() {
|
||||
let a = 2 + 2;
|
||||
#[block]
|
||||
{}
|
||||
#[block]
|
||||
{}
|
||||
assert_eq!(a, 4);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark.
|
||||
--> tests/benchmark_ui/dup_block.rs:14:3
|
||||
|
|
||||
14 | #[block]
|
||||
| ^
|
||||
@@ -0,0 +1,20 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() {
|
||||
let a = 2 + 2;
|
||||
#[extrinsic_call]
|
||||
_(stuff);
|
||||
#[extrinsic_call]
|
||||
_(other_stuff);
|
||||
assert_eq!(a, 4);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark.
|
||||
--> tests/benchmark_ui/dup_extrinsic_call.rs:14:3
|
||||
|
|
||||
14 | #[extrinsic_call]
|
||||
| ^
|
||||
@@ -0,0 +1,16 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark(extra, extra)]
|
||||
fn bench() {
|
||||
#[block]
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: unexpected end of input, `extra` can only be specified once
|
||||
--> tests/benchmark_ui/extra_extra.rs:9:26
|
||||
|
|
||||
9 | #[benchmark(extra, extra)]
|
||||
| ^
|
||||
@@ -0,0 +1,16 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark(skip_meta, skip_meta)]
|
||||
fn bench() {
|
||||
#[block]
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: unexpected end of input, `skip_meta` can only be specified once
|
||||
--> tests/benchmark_ui/extra_skip_meta.rs:9:34
|
||||
|
|
||||
9 | #[benchmark(skip_meta, skip_meta)]
|
||||
| ^
|
||||
@@ -0,0 +1,6 @@
|
||||
use frame_support::benchmarking::*;
|
||||
|
||||
#[extrinsic_call]
|
||||
mod stuff {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,7 @@
|
||||
error: `#[extrinsic_call]` must be in a benchmark function definition labeled with `#[benchmark]`.
|
||||
--> tests/benchmark_ui/extrinsic_call_out_of_fn.rs:3:1
|
||||
|
|
||||
3 | #[extrinsic_call]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the attribute macro `extrinsic_call` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
@@ -0,0 +1,13 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body.
|
||||
--> tests/benchmark_ui/missing_call.rs:10:13
|
||||
|
|
||||
10 | fn bench() {}
|
||||
| ^^
|
||||
@@ -0,0 +1,16 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn bench() {
|
||||
#[extrinsic_call]
|
||||
thing();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Single-item extrinsic calls must specify their origin as the first argument.
|
||||
--> tests/benchmark_ui/missing_origin.rs:12:3
|
||||
|
|
||||
12 | thing();
|
||||
| ^^^^^
|
||||
@@ -0,0 +1,17 @@
|
||||
use frame_support::benchmarking::*;
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark(skip_meta, extra)]
|
||||
fn bench() {
|
||||
let a = 2 + 2;
|
||||
#[block]
|
||||
{}
|
||||
assert_eq!(a, 4);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,16 @@
|
||||
use frame_support::benchmarking::*;
|
||||
#[allow(unused_imports)]
|
||||
use frame_support_test::Config;
|
||||
|
||||
#[benchmarks]
|
||||
mod benches {
|
||||
use super::*;
|
||||
|
||||
#[benchmark(skip_meta, extra, bad)]
|
||||
fn bench() {
|
||||
#[block]
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: expected `extra` or `skip_meta`
|
||||
--> tests/benchmark_ui/unrecognized_option.rs:9:32
|
||||
|
|
||||
9 | #[benchmark(skip_meta, extra, bad)]
|
||||
| ^^^
|
||||
Reference in New Issue
Block a user