diff --git a/crates/engine/tree/src/tree/metrics.rs b/crates/engine/tree/src/tree/metrics.rs index d46c2f05a..dba80a179 100644 --- a/crates/engine/tree/src/tree/metrics.rs +++ b/crates/engine/tree/src/tree/metrics.rs @@ -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); } diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 5a39bc990..a4491fde2 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -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 { diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 8c84fafc2..6513cf75f 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -372,7 +372,7 @@ where Ok(BlockExecutionOutput { state: self.state.take_bundle(), receipts, requests, gas_used }) } - fn execute_with_state_witness( + fn execute_with_state_closure( mut self, input: Self::Input<'_>, mut witness: F, diff --git a/crates/evm/src/either.rs b/crates/evm/src/either.rs index e28ec3887..8022c68c4 100644 --- a/crates/evm/src/either.rs +++ b/crates/evm/src/either.rs @@ -77,7 +77,7 @@ where } } - fn execute_with_state_witness( + fn execute_with_state_closure( self, input: Self::Input<'_>, witness: F, @@ -86,8 +86,8 @@ where F: FnMut(&State), { 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), } } diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index 1bd793781..145eca29c 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -38,12 +38,12 @@ pub trait Executor { /// The output of the block execution. fn execute(self, input: Self::Input<'_>) -> Result; - /// 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( + /// 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( self, input: Self::Input<'_>, - witness: F, + state: F, ) -> Result where F: FnMut(&State); @@ -203,7 +203,7 @@ mod tests { Err(BlockExecutionError::msg("execution unavailable for tests")) } - fn execute_with_state_witness( + fn execute_with_state_closure( self, _: Self::Input<'_>, _: F, diff --git a/crates/evm/src/metrics.rs b/crates/evm/src/metrics.rs index d6ffe0d79..fbb2b858b 100644 --- a/crates/evm/src/metrics.rs +++ b/crates/evm/src/metrics.rs @@ -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(&self, input: BlockExecutionInput<'_, BlockWithSenders>, f: F) -> R + fn metered(&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, Error> + where + E: Executor< + DB, + Input<'a> = BlockExecutionInput<'a, BlockWithSenders>, + Output = BlockExecutionOutput, + Error = Error, + >, + { + let output = self.metered(input.block, || { + executor.execute_with_state_closure(input, |state: &revm::db::State| { + // 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::(); + 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::(); + 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(&self, input: BlockExecutionInput<'_, BlockWithSenders>, f: F) -> R + where + F: FnOnce(BlockExecutionInput<'_, BlockWithSenders>) -> R, + { + self.metered(input.block, || f(input)) + } } diff --git a/crates/evm/src/noop.rs b/crates/evm/src/noop.rs index 3e01bfc4c..4fdc6d367 100644 --- a/crates/evm/src/noop.rs +++ b/crates/evm/src/noop.rs @@ -51,7 +51,7 @@ impl Executor for NoopBlockExecutorProvider { Err(BlockExecutionError::msg(UNAVAILABLE_FOR_NOOP)) } - fn execute_with_state_witness( + fn execute_with_state_closure( self, _: Self::Input<'_>, _: F, diff --git a/crates/evm/src/test_utils.rs b/crates/evm/src/test_utils.rs index 45ab2e977..fc620bb42 100644 --- a/crates/evm/src/test_utils.rs +++ b/crates/evm/src/test_utils.rs @@ -66,26 +66,26 @@ impl Executor for MockExecutorProvider { }) } - fn execute_with_state_witness( + fn execute_with_state_closure( self, - _: Self::Input<'_>, + input: Self::Input<'_>, _: F, ) -> Result where F: FnMut(&State), { - unimplemented!() + >::execute(self, input) } fn execute_with_state_hook( self, - _: Self::Input<'_>, + input: Self::Input<'_>, _: F, ) -> Result where F: OnStateHook, { - unimplemented!() + >::execute(self, input) } } diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 4e5918831..2491e99b5 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -381,7 +381,7 @@ where }) } - fn execute_with_state_witness( + fn execute_with_state_closure( mut self, input: Self::Input<'_>, mut witness: F, diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index fb0407039..b26459c4f 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -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 diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index a99d8a572..df234e542 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -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();