diff --git a/crates/engine/tree/src/tree/metrics.rs b/crates/engine/tree/src/tree/metrics.rs index 1496a5deea..d08b42123c 100644 --- a/crates/engine/tree/src/tree/metrics.rs +++ b/crates/engine/tree/src/tree/metrics.rs @@ -34,6 +34,10 @@ pub struct EngineApiMetrics { /// Metrics for EIP-7928 Block-Level Access Lists (BAL). #[allow(dead_code)] pub(crate) bal: BalMetrics, + /// Gas-bucketed execution sub-phase metrics. + pub(crate) execution_gas_buckets: ExecutionGasBucketMetrics, + /// Gas-bucketed block validation sub-phase metrics. + pub(crate) block_validation_gas_buckets: BlockValidationGasBucketMetrics, } impl EngineApiMetrics { @@ -82,6 +86,22 @@ impl EngineApiMetrics { self.executor.post_execution_histogram.record(elapsed); } + /// Records execution duration into the gas-bucketed execution histogram. + pub fn record_block_execution_gas_bucket(&self, gas_used: u64, elapsed: Duration) { + let idx = GasBucketMetrics::bucket_index(gas_used); + self.execution_gas_buckets.buckets[idx] + .execution_gas_bucket_histogram + .record(elapsed.as_secs_f64()); + } + + /// Records state root duration into the gas-bucketed block validation histogram. + pub fn record_state_root_gas_bucket(&self, gas_used: u64, elapsed_secs: f64) { + let idx = GasBucketMetrics::bucket_index(gas_used); + self.block_validation_gas_buckets.buckets[idx] + .state_root_gas_bucket_histogram + .record(elapsed_secs); + } + /// Records the time spent waiting for the next transaction from the iterator. pub fn record_transaction_wait(&self, elapsed: Duration) { self.executor.transaction_wait_histogram.record(elapsed); @@ -280,7 +300,8 @@ impl GasBucketMetrics { .record(gas_used as f64 / elapsed.as_secs_f64()); } - fn bucket_index(gas_used: u64) -> usize { + /// Returns the bucket index for a given gas value. + pub(crate) fn bucket_index(gas_used: u64) -> usize { GAS_BUCKET_THRESHOLDS .iter() .position(|&threshold| gas_used < threshold) @@ -288,7 +309,7 @@ impl GasBucketMetrics { } /// Returns a human-readable label like `<5M`, `5-10M`, … `>40M`. - fn bucket_label(index: usize) -> String { + pub(crate) fn bucket_label(index: usize) -> String { if index == 0 { let hi = GAS_BUCKET_THRESHOLDS[0] / MEGAGAS; format!("<{hi}M") @@ -303,6 +324,56 @@ impl GasBucketMetrics { } } +/// Per-gas-bucket execution duration metric. +#[derive(Clone, Metrics)] +#[metrics(scope = "sync.execution")] +pub(crate) struct ExecutionGasBucketSeries { + /// Gas-bucketed EVM execution duration. + pub(crate) execution_gas_bucket_histogram: Histogram, +} + +/// Holds pre-initialized [`ExecutionGasBucketSeries`] instances, one per gas bucket. +#[derive(Debug)] +pub(crate) struct ExecutionGasBucketMetrics { + buckets: [ExecutionGasBucketSeries; NUM_GAS_BUCKETS], +} + +impl Default for ExecutionGasBucketMetrics { + fn default() -> Self { + Self { + buckets: std::array::from_fn(|i| { + let label = GasBucketMetrics::bucket_label(i); + ExecutionGasBucketSeries::new_with_labels(&[("gas_bucket", label)]) + }), + } + } +} + +/// Per-gas-bucket block validation metrics (state root). +#[derive(Clone, Metrics)] +#[metrics(scope = "sync.block_validation")] +pub(crate) struct BlockValidationGasBucketSeries { + /// Gas-bucketed state root computation duration. + pub(crate) state_root_gas_bucket_histogram: Histogram, +} + +/// Holds pre-initialized [`BlockValidationGasBucketSeries`] instances, one per gas bucket. +#[derive(Debug)] +pub(crate) struct BlockValidationGasBucketMetrics { + buckets: [BlockValidationGasBucketSeries; NUM_GAS_BUCKETS], +} + +impl Default for BlockValidationGasBucketMetrics { + fn default() -> Self { + Self { + buckets: std::array::from_fn(|i| { + let label = GasBucketMetrics::bucket_label(i); + BlockValidationGasBucketSeries::new_with_labels(&[("gas_bucket", label)]) + }), + } + } +} + /// Metrics for engine newPayload responses. #[derive(Metrics)] #[metrics(scope = "consensus.engine.beacon")] diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index 33759d65f2..d82bbbf5c1 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -597,6 +597,8 @@ where }; self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64()); + self.metrics + .record_state_root_gas_bucket(block.header().gas_used(), root_elapsed.as_secs_f64()); debug!(target: "engine::tree::payload_validator", ?root_elapsed, "Calculated state root"); // ensure state root matches @@ -765,6 +767,7 @@ where let execution_duration = execution_start.elapsed(); self.metrics.record_block_execution(&output, execution_duration); + self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration); debug!(target: "engine::tree::payload_validator", elapsed = ?execution_duration, "Executed block"); Ok((output, senders, result_rx))