feat(evm, trie): more metrics (#11613)

This commit is contained in:
Alexey Shekhirin
2024-10-10 09:27:01 +01:00
committed by GitHub
parent a4b8150201
commit 58bfa60cea
11 changed files with 116 additions and 37 deletions

View File

@ -4,6 +4,7 @@ use reth_metrics::{
metrics::{Counter, Gauge, Histogram},
Metrics,
};
use reth_trie::updates::TrieUpdates;
/// Metrics for the `EngineApi`.
#[derive(Debug, Default)]
@ -49,6 +50,8 @@ pub(crate) struct EngineMetrics {
#[derive(Metrics)]
#[metrics(scope = "sync.block_validation")]
pub(crate) struct BlockValidationMetrics {
/// Total number of storage tries updated in the state root calculation
pub(crate) state_root_storage_tries_updated_total: Counter,
/// Histogram of state root duration
pub(crate) state_root_histogram: Histogram,
/// Latest state root duration
@ -57,7 +60,9 @@ pub(crate) struct BlockValidationMetrics {
impl BlockValidationMetrics {
/// Records a new state root time, updating both the histogram and state root gauge
pub(crate) fn record_state_root(&self, elapsed_as_secs: f64) {
pub(crate) fn record_state_root(&self, trie_output: &TrieUpdates, elapsed_as_secs: f64) {
self.state_root_storage_tries_updated_total
.increment(trie_output.storage_tries_ref().len() as u64);
self.state_root_duration.set(elapsed_as_secs);
self.state_root_histogram.record(elapsed_as_secs);
}

View File

@ -28,7 +28,7 @@ use reth_chainspec::EthereumHardforks;
use reth_consensus::{Consensus, PostExecutionInput};
use reth_engine_primitives::EngineTypes;
use reth_errors::{ConsensusError, ProviderResult};
use reth_evm::execute::{BlockExecutorProvider, Executor};
use reth_evm::execute::BlockExecutorProvider;
use reth_payload_builder::PayloadBuilderHandle;
use reth_payload_primitives::{PayloadAttributes, PayloadBuilder, PayloadBuilderAttributes};
use reth_payload_validator::ExecutionPayloadValidator;
@ -2160,10 +2160,7 @@ where
let block = block.unseal();
let exec_time = Instant::now();
let output = self
.metrics
.executor
.metered((&block, U256::MAX).into(), |input| executor.execute(input))?;
let output = self.metrics.executor.execute_metered(executor, (&block, U256::MAX).into())?;
trace!(target: "engine::tree", elapsed=?exec_time.elapsed(), ?block_number, "Executed block");
if let Err(err) = self.consensus.validate_block_post_execution(
@ -2227,7 +2224,7 @@ where
}
let root_elapsed = root_time.elapsed();
self.metrics.block_validation.record_state_root(root_elapsed.as_secs_f64());
self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64());
debug!(target: "engine::tree", ?root_elapsed, ?block_number, "Calculated state root");
let executed = ExecutedBlock {

View File

@ -372,7 +372,7 @@ where
Ok(BlockExecutionOutput { state: self.state.take_bundle(), receipts, requests, gas_used })
}
fn execute_with_state_witness<F>(
fn execute_with_state_closure<F>(
mut self,
input: Self::Input<'_>,
mut witness: F,

View File

@ -77,7 +77,7 @@ where
}
}
fn execute_with_state_witness<F>(
fn execute_with_state_closure<F>(
self,
input: Self::Input<'_>,
witness: F,
@ -86,8 +86,8 @@ where
F: FnMut(&State<DB>),
{
match self {
Self::Left(a) => a.execute_with_state_witness(input, witness),
Self::Right(b) => b.execute_with_state_witness(input, witness),
Self::Left(a) => a.execute_with_state_closure(input, witness),
Self::Right(b) => b.execute_with_state_closure(input, witness),
}
}

View File

@ -38,12 +38,12 @@ pub trait Executor<DB> {
/// The output of the block execution.
fn execute(self, input: Self::Input<'_>) -> Result<Self::Output, Self::Error>;
/// Executes the EVM with the given input and accepts a witness closure that is invoked with the
/// EVM state after execution.
fn execute_with_state_witness<F>(
/// Executes the EVM with the given input and accepts a state closure that is invoked with
/// the EVM state after execution.
fn execute_with_state_closure<F>(
self,
input: Self::Input<'_>,
witness: F,
state: F,
) -> Result<Self::Output, Self::Error>
where
F: FnMut(&State<DB>);
@ -203,7 +203,7 @@ mod tests {
Err(BlockExecutionError::msg("execution unavailable for tests"))
}
fn execute_with_state_witness<F>(
fn execute_with_state_closure<F>(
self,
_: Self::Input<'_>,
_: F,

View File

@ -1,16 +1,18 @@
//! Executor metrics.
//!
//! Block processing related to syncing should take care to update the metrics by using e.g.
//! [`ExecutorMetrics::metered`].
//! Block processing related to syncing should take care to update the metrics by using either
//! [`ExecutorMetrics::execute_metered`] or [`ExecutorMetrics::metered_one`].
use std::time::Instant;
use metrics::{Counter, Gauge, Histogram};
use reth_execution_types::BlockExecutionInput;
use reth_execution_types::{BlockExecutionInput, BlockExecutionOutput};
use reth_metrics::Metrics;
use reth_primitives::BlockWithSenders;
use crate::execute::Executor;
/// Executor metrics.
// TODO(onbjerg): add sload/sstore, acc load/acc change, bytecode metrics
// TODO(onbjerg): add sload/sstore
#[derive(Metrics, Clone)]
#[metrics(scope = "sync.execution")]
pub struct ExecutorMetrics {
@ -18,31 +20,106 @@ pub struct ExecutorMetrics {
pub gas_processed_total: Counter,
/// The instantaneous amount of gas processed per second.
pub gas_per_second: Gauge,
/// The Histogram for amount of time taken to execute blocks.
pub execution_histogram: Histogram,
/// The total amount of time it took to execute the latest block.
pub execution_duration: Gauge,
/// The Histogram for number of accounts loaded when executing the latest block.
pub accounts_loaded_histogram: Histogram,
/// The Histogram for number of storage slots loaded when executing the latest block.
pub storage_slots_loaded_histogram: Histogram,
/// The Histogram for number of bytecodes loaded when executing the latest block.
pub bytecodes_loaded_histogram: Histogram,
/// The Histogram for number of accounts updated when executing the latest block.
pub accounts_updated_histogram: Histogram,
/// The Histogram for number of storage slots updated when executing the latest block.
pub storage_slots_updated_histogram: Histogram,
/// The Histogram for number of bytecodes updated when executing the latest block.
pub bytecodes_updated_histogram: Histogram,
}
impl ExecutorMetrics {
/// Execute the given block and update metrics for the execution.
pub fn metered<F, R>(&self, input: BlockExecutionInput<'_, BlockWithSenders>, f: F) -> R
fn metered<F, R>(&self, block: &BlockWithSenders, f: F) -> R
where
F: FnOnce(BlockExecutionInput<'_, BlockWithSenders>) -> R,
F: FnOnce() -> R,
{
let gas_used = input.block.gas_used;
// Execute the block and record the elapsed time.
let execute_start = Instant::now();
let output = f(input);
let output = f();
let execution_duration = execute_start.elapsed().as_secs_f64();
// Update gas metrics.
self.gas_processed_total.increment(gas_used);
self.gas_per_second.set(gas_used as f64 / execution_duration);
self.gas_processed_total.increment(block.gas_used);
self.gas_per_second.set(block.gas_used as f64 / execution_duration);
self.execution_histogram.record(execution_duration);
self.execution_duration.set(execution_duration);
output
}
/// Execute the given block using the provided [`Executor`] and update metrics for the
/// execution.
///
/// Compared to [`Self::metered_one`], this method additionally updates metrics for the number
/// of accounts, storage slots and bytecodes loaded and updated.
pub fn execute_metered<'a, E, DB, O, Error>(
&self,
executor: E,
input: BlockExecutionInput<'a, BlockWithSenders>,
) -> Result<BlockExecutionOutput<O>, Error>
where
E: Executor<
DB,
Input<'a> = BlockExecutionInput<'a, BlockWithSenders>,
Output = BlockExecutionOutput<O>,
Error = Error,
>,
{
let output = self.metered(input.block, || {
executor.execute_with_state_closure(input, |state: &revm::db::State<DB>| {
// Update the metrics for the number of accounts, storage slots and bytecodes
// loaded
let accounts = state.cache.accounts.len();
let storage_slots = state
.cache
.accounts
.values()
.filter_map(|account| {
account.account.as_ref().map(|account| account.storage.len())
})
.sum::<usize>();
let bytecodes = state.cache.contracts.len();
// Record all state present in the cache state as loaded even though some might have
// been newly created.
// TODO: Consider spitting these into loaded and newly created.
self.accounts_loaded_histogram.record(accounts as f64);
self.storage_slots_loaded_histogram.record(storage_slots as f64);
self.bytecodes_loaded_histogram.record(bytecodes as f64);
})
})?;
// Update the metrics for the number of accounts, storage slots and bytecodes updated
let accounts = output.state.state.len();
let storage_slots =
output.state.state.values().map(|account| account.storage.len()).sum::<usize>();
let bytecodes = output.state.contracts.len();
self.accounts_updated_histogram.record(accounts as f64);
self.storage_slots_updated_histogram.record(storage_slots as f64);
self.bytecodes_updated_histogram.record(bytecodes as f64);
Ok(output)
}
/// Execute the given block and update metrics for the execution.
pub fn metered_one<F, R>(&self, input: BlockExecutionInput<'_, BlockWithSenders>, f: F) -> R
where
F: FnOnce(BlockExecutionInput<'_, BlockWithSenders>) -> R,
{
self.metered(input.block, || f(input))
}
}

View File

@ -51,7 +51,7 @@ impl<DB> Executor<DB> for NoopBlockExecutorProvider {
Err(BlockExecutionError::msg(UNAVAILABLE_FOR_NOOP))
}
fn execute_with_state_witness<F>(
fn execute_with_state_closure<F>(
self,
_: Self::Input<'_>,
_: F,

View File

@ -66,26 +66,26 @@ impl<DB> Executor<DB> for MockExecutorProvider {
})
}
fn execute_with_state_witness<F>(
fn execute_with_state_closure<F>(
self,
_: Self::Input<'_>,
input: Self::Input<'_>,
_: F,
) -> Result<Self::Output, Self::Error>
where
F: FnMut(&State<DB>),
{
unimplemented!()
<Self as Executor<DB>>::execute(self, input)
}
fn execute_with_state_hook<F>(
self,
_: Self::Input<'_>,
input: Self::Input<'_>,
_: F,
) -> Result<Self::Output, Self::Error>
where
F: OnStateHook,
{
unimplemented!()
<Self as Executor<DB>>::execute(self, input)
}
}

View File

@ -381,7 +381,7 @@ where
})
}
fn execute_with_state_witness<F>(
fn execute_with_state_closure<F>(
mut self,
input: Self::Input<'_>,
mut witness: F,

View File

@ -612,7 +612,7 @@ where
let mut codes = HashMap::default();
let _ = block_executor
.execute_with_state_witness(
.execute_with_state_closure(
(&block.clone().unseal(), block.difficulty).into(),
|statedb| {
codes = statedb

View File

@ -275,7 +275,7 @@ where
// Execute the block
let execute_start = Instant::now();
self.metrics.metered((&block, td).into(), |input| {
self.metrics.metered_one((&block, td).into(), |input| {
let sealed = block.header.clone().seal_slow();
let (header, seal) = sealed.into_parts();