Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Dynamic Benchmarking DB Whitelist #6815

Merged
20 commits merged into from
Aug 19, 2020
Merged

Dynamic Benchmarking DB Whitelist #6815

20 commits merged into from
Aug 19, 2020

Conversation

shawntabrizi
Copy link
Member

@shawntabrizi shawntabrizi commented Aug 4, 2020

polkadot companion: paritytech/polkadot#1612

This PR introduces a new get_whitelist host function. In combination with set_whitelist, we are able to create helper functions add_whitelist and remove_whitelist which can be called from a benchmark to dynamically introduce or remove items from the benchmarking whitelist.

Additionally, this PR updates the format of the whitelist to use a new struct TrackedStorageKey:

pub struct TrackedStorageKey {
	pub key: Vec<u8>,
	pub has_been_read: bool,
	pub has_been_written: bool,
}

Which includes the storage key, and whether this storage key has been read from or written to. This allows us to whitelist a key for a read, write, or both.

Here are some explicit examples of how you would use this:

Ignore Read, Track Write

Let's say that you have some storage value that is read each block, so you are able to ignore reads to that variable introduced by an extrinsic, but you still want to track the first write. For example, the LaunchPeriod of the democracy module may be placed as a storage item, and every on_initialize, we check if the block would trigger a launch period (reading it every block), but if we had an extrinsic to update the launch period, we would still want to track writes to this value. To your benchmark you would add the following:

// Ignore reads to `LaunchPeriod` since it read every `on_initialize`. But still track writes to this value.
let launch_period_key = crate::LaunchPeriod::hashed_key().to_vec();
frame_benchmarking::benchmarking::add_whitelist(TrackedStorageKey {
	key: launch_period_key,
	has_been_read: true,
	has_been_written: false,
});

Track Read, Ignore Write

In some more unique scenarios, you may want to track the first read of a storage item, but ignore any writes to it. For example the Timestamp pallet has the DidUpdate variable which is transient (created and destroyed within one block), but before we create the value, we make sure it doesn't already exist. This exist lookup should be counted as a storage read, but us writing to the value should not be counted because it will be removed at the end of the block, ultimately not committing anything to the DB. You would represent this like so:

// Ignore write to `DidUpdate` since it transient.
let did_update_key = crate::DidUpdate::hashed_key().to_vec();
frame_benchmarking::benchmarking::add_whitelist(TrackedStorageKey {
	key: did_update_key,
	has_been_read: false,
	has_been_written: true,
});

Ignore Read and Write

Finally, the most common scenario is to simply ignore both reads and writes to a storage key. For example, use a unique caller account, and want to make sure that reads/writes to the System.Account of that caller are not accounted for as they are already included in the base weight of an extrinsic. For this, we can use a shorthand to construct the TrackedStorageKey by simply converting with key.into(), like so:

// Whitelist caller account from further DB operations.
let caller_key = frame_system::Account::<T>::hashed_key_for(&caller);
frame_benchmarking::benchmarking::add_whitelist(caller_key.into());

fixes #6802

@github-actions github-actions bot added the A3-in_progress Pull request is in progress. No review needed at this stage. label Aug 4, 2020
@gui1117
Copy link
Contributor

gui1117 commented Aug 6, 2020

if you want to get some inspiration how to write a global variable in the runtime gav implemented one, it look like this:

#[macro_export]
macro_rules! impl_filter_stack {
($target:ty, $base:ty, $call:ty, $module:ident) => {
#[cfg(feature = "std")]
mod $module {
#[allow(unused_imports)]
use super::*;
use $crate::traits::{swap, take, RefCell, Vec, Box, Filter, FilterStack};
thread_local! {
static FILTER: RefCell<Vec<Box<dyn Fn(&$call) -> bool + 'static>>> = RefCell::new(Vec::new());
}
impl Filter<$call> for $target {
fn filter(call: &$call) -> bool {
<$base>::filter(call) &&
FILTER.with(|filter| filter.borrow().iter().all(|f| f(call)))
}
}
impl FilterStack<$call> for $target {
type Stack = Vec<Box<dyn Fn(&$call) -> bool + 'static>>;
fn push(f: impl Fn(&$call) -> bool + 'static) {
FILTER.with(|filter| filter.borrow_mut().push(Box::new(f)));
}
fn pop() {
FILTER.with(|filter| filter.borrow_mut().pop());
}
fn take() -> Self::Stack {
FILTER.with(|filter| take(filter.borrow_mut().as_mut()))
}
fn restore(mut s: Self::Stack) {
FILTER.with(|filter| swap(filter.borrow_mut().as_mut(), &mut s));
}
}
}
#[cfg(not(feature = "std"))]
mod $module {
#[allow(unused_imports)]
use super::*;
use $crate::traits::{swap, take, RefCell, Vec, Box, Filter, FilterStack};
struct ThisFilter(RefCell<Vec<Box<dyn Fn(&$call) -> bool + 'static>>>);
// NOTE: Safe only in wasm (guarded above) because there's only one thread.
unsafe impl Send for ThisFilter {}
unsafe impl Sync for ThisFilter {}
static FILTER: ThisFilter = ThisFilter(RefCell::new(Vec::new()));
impl Filter<$call> for $target {
fn filter(call: &$call) -> bool {
<$base>::filter(call) && FILTER.0.borrow().iter().all(|f| f(call))
}
}
impl FilterStack<$call> for $target {
type Stack = Vec<Box<dyn Fn(&$call) -> bool + 'static>>;
fn push(f: impl Fn(&$call) -> bool + 'static) {
FILTER.0.borrow_mut().push(Box::new(f));
}
fn pop() {
FILTER.0.borrow_mut().pop();
}
fn take() -> Self::Stack {
take(FILTER.0.borrow_mut().as_mut())
}
fn restore(mut s: Self::Stack) {
swap(FILTER.0.borrow_mut().as_mut(), &mut s);
}
}
}
}
}

Though you don't need to wrap in macro because you only need to have one in benchmarking.

@shawntabrizi shawntabrizi reopened this Aug 16, 2020
@xlc xlc mentioned this pull request Aug 16, 2020
5 tasks
@shawntabrizi shawntabrizi marked this pull request as ready for review August 16, 2020 23:17
@github-actions github-actions bot added A0-please_review Pull request needs code review. and removed A3-in_progress Pull request is in progress. No review needed at this stage. labels Aug 16, 2020
@shawntabrizi
Copy link
Member Author

Ideally, I wanted to make the whitelisting of calling account automatic. I wanted to extract the $origin from the macro parsing, and then return it in a seperate fn origin() from the Benchmarking trait. Then I can detect if the origin was Signed and then whitelist the caller

However, $origin is an $expr and has within the enum the caller value, which I believe does not evaluate anymore.

You can see my attempt here: 15ac408

It compiles and stuff, but you will notice I dont pass the $origin variable. Once you do, you will get a compile error that caller is not in scope...

Anyway, if @thiolliere you have an idea how to do this, all ears. Otherwise, this does do the functionality I want manually.

@shawntabrizi shawntabrizi added B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. labels Aug 17, 2020
Copy link
Contributor

@gui1117 gui1117 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me, I like that whitelisted caller account is now more explicit

frame/staking/src/benchmarking.rs Outdated Show resolved Hide resolved
} {
$( PRE { $( $pre_parsed:tt )* } )*
} { $eval:block } {
(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These macro changes are just superficial. I was trying to edit them at some point, and they were impossible to read to me. So I formatted them how I would expect to seem them. No logic actually changes in the macro parsing

Comment on lines +232 to +238
/// Get the whitelist for tracking db reads/writes
fn get_whitelist(&self) -> Vec<TrackedStorageKey> {
Default::default()
}

/// Update the whitelist for tracking db reads/writes
fn set_whitelist(&self, _: Vec<TrackedStorageKey>) {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than have them unimplemented I had them do nothing so they would work inside a test.

Otherwise, we would need to feature gate all get/set operations with #[cfg(not(test))]

@shawntabrizi
Copy link
Member Author

@thiolliere @apopiak I think this PR is in a good spot for a review. I got all my desired features in there, so please take a look and ask any questions you might have.

Comment on lines +197 to +200
let has_been_read = KeyTracker {
has_been_read: true,
has_been_written: tracker.has_been_written,
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic needed to be updated to support the scenario where we want to count a read, but ignore a write. (i.e. timestamp pallet)

Copy link
Contributor

@gui1117 gui1117 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me, with not nitpicks

client/db/src/bench.rs Outdated Show resolved Hide resolved
frame/benchmarking/src/lib.rs Outdated Show resolved Hide resolved
frame/benchmarking/src/lib.rs Outdated Show resolved Hide resolved
frame/benchmarking/src/lib.rs Show resolved Hide resolved
frame/benchmarking/src/lib.rs Show resolved Hide resolved
Copy link
Contributor

@apopiak apopiak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, some nits about naming

frame/benchmarking/src/utils.rs Outdated Show resolved Hide resolved
frame/benchmarking/src/utils.rs Outdated Show resolved Hide resolved
frame/timestamp/src/benchmarking.rs Outdated Show resolved Hide resolved
shawntabrizi and others added 5 commits August 18, 2020 14:14
Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
@shawntabrizi
Copy link
Member Author

bot merge

@ghost
Copy link

ghost commented Aug 18, 2020

Missing process info; check that the PR belongs to a project column.

Merge can be attempted if:

  • The PR has approval from two core-devs (or one if the PR is labelled insubstantial).
  • The PR is attached to a project column and has approval from the project owner.
  • The PR has approval from a member of substrateteamleads.

@shawntabrizi
Copy link
Member Author

bot merge

@ghost
Copy link

ghost commented Aug 18, 2020

Checks failed; merge aborted.

@shawntabrizi shawntabrizi removed the request for review from kianenigma August 18, 2020 20:42
@shawntabrizi
Copy link
Member Author

bot merge

@ghost
Copy link

ghost commented Aug 18, 2020

Checks failed; merge aborted.

@shawntabrizi
Copy link
Member Author

bot merge force

@ghost
Copy link

ghost commented Aug 19, 2020

Trying merge.

This pull request was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A0-please_review Pull request needs code review. B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders.
Projects
Development

Successfully merging this pull request may close these issues.

Allow DB Whitelisting from Benchmarks
4 participants