Skip to main content

reth_trie/trie_cursor/
metrics.rs

1use super::{TrieCursor, TrieCursorFactory, TrieStorageCursor};
2use crate::{BranchNodeCompact, Nibbles, TrieType};
3use alloy_primitives::B256;
4use reth_metrics::metrics::{self, Histogram};
5use reth_primitives_traits::FastInstant as Instant;
6use reth_storage_errors::db::DatabaseError;
7use std::time::Duration;
8
9/// Prometheus metrics for trie cursor operations.
10///
11/// Tracks the number of cursor operations for monitoring and performance analysis.
12#[derive(Clone, Debug)]
13pub struct TrieCursorMetrics {
14    /// Histogram tracking overall time spent in database operations, in seconds.
15    overall_duration_seconds: Histogram,
16    /// Histogram for `next()` operations
17    next_histogram: Histogram,
18    /// Histogram for `seek()` operations
19    seek_histogram: Histogram,
20    /// Histogram for `seek_exact()` operations
21    seek_exact_histogram: Histogram,
22}
23
24impl TrieCursorMetrics {
25    /// Create a new metrics instance with the specified trie type label.
26    pub fn new(trie_type: TrieType) -> Self {
27        let trie_type_str = trie_type.as_str();
28
29        Self {
30            overall_duration_seconds: metrics::histogram!(
31                "trie.cursor.overall_duration_seconds",
32                "type" => trie_type_str
33            ),
34            next_histogram: metrics::histogram!(
35                "trie.cursor.operations",
36                "type" => trie_type_str,
37                "operation" => "next"
38            ),
39            seek_histogram: metrics::histogram!(
40                "trie.cursor.operations",
41                "type" => trie_type_str,
42                "operation" => "seek"
43            ),
44            seek_exact_histogram: metrics::histogram!(
45                "trie.cursor.operations",
46                "type" => trie_type_str,
47                "operation" => "seek_exact"
48            ),
49        }
50    }
51
52    /// Wraps a cursor with owned counters that are recorded when the cursor is dropped.
53    pub fn instrument<C>(&self, cursor: C) -> InstrumentedTrieCursor<C> {
54        InstrumentedTrieCursor {
55            cursor,
56            metrics: self.clone(),
57            next_count: 0,
58            seek_count: 0,
59            seek_exact_count: 0,
60            total_duration: Duration::ZERO,
61        }
62    }
63}
64
65/// A wrapper around a [`TrieCursor`] that tracks metrics for cursor operations.
66///
67/// Counters are recorded into the supplied metric handles when the cursor is dropped.
68///
69/// This implementation counts the number of times each cursor operation is called:
70/// - `next()` - Move to the next entry
71/// - `seek()` - Seek to a key or the next greater key
72/// - `seek_exact()` - Seek to an exact key match
73#[derive(Debug)]
74pub struct InstrumentedTrieCursor<C> {
75    /// The underlying cursor being wrapped.
76    cursor: C,
77    /// Metric handles used to record counters when the cursor is dropped.
78    metrics: TrieCursorMetrics,
79    /// Counter for `next()` calls.
80    next_count: usize,
81    /// Counter for `seek()` calls.
82    seek_count: usize,
83    /// Counter for `seek_exact` calls.
84    seek_exact_count: usize,
85    /// Total duration spent in cursor operations.
86    total_duration: Duration,
87}
88
89impl<C: TrieCursor> TrieCursor for InstrumentedTrieCursor<C> {
90    fn seek_exact(
91        &mut self,
92        key: Nibbles,
93    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
94        let start = Instant::now();
95        self.seek_exact_count += 1;
96        let result = self.cursor.seek_exact(key);
97        self.total_duration += start.elapsed();
98        result
99    }
100
101    fn seek(
102        &mut self,
103        key: Nibbles,
104    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
105        let start = Instant::now();
106        self.seek_count += 1;
107        let result = self.cursor.seek(key);
108        self.total_duration += start.elapsed();
109        result
110    }
111
112    fn next(&mut self) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
113        let start = Instant::now();
114        self.next_count += 1;
115        let result = self.cursor.next();
116        self.total_duration += start.elapsed();
117        result
118    }
119
120    fn current(&mut self) -> Result<Option<Nibbles>, DatabaseError> {
121        self.cursor.current()
122    }
123
124    fn reset(&mut self) {
125        self.cursor.reset()
126    }
127}
128
129impl<C: TrieStorageCursor> TrieStorageCursor for InstrumentedTrieCursor<C> {
130    fn set_hashed_address(&mut self, hashed_address: B256) {
131        self.cursor.set_hashed_address(hashed_address)
132    }
133}
134
135impl<C> Drop for InstrumentedTrieCursor<C> {
136    fn drop(&mut self) {
137        self.metrics.next_histogram.record(self.next_count as f64);
138        self.metrics.seek_histogram.record(self.seek_count as f64);
139        self.metrics.seek_exact_histogram.record(self.seek_exact_count as f64);
140        self.metrics.overall_duration_seconds.record(self.total_duration.as_secs_f64());
141    }
142}
143
144/// Instruments every account and storage cursor opened by the wrapped factory.
145///
146/// Counters are owned by each cursor and recorded into the supplied handles on drop. Wrap the
147/// outermost overlay factory to include both overlay and database operations in these counters.
148#[derive(Clone, Debug)]
149pub struct InstrumentedTrieCursorFactory<F> {
150    factory: F,
151    account_metrics: TrieCursorMetrics,
152    storage_metrics: TrieCursorMetrics,
153}
154
155impl<F> InstrumentedTrieCursorFactory<F> {
156    /// Wraps a factory with account and storage metric handles shared by its cursors.
157    pub const fn new(
158        factory: F,
159        account_metrics: TrieCursorMetrics,
160        storage_metrics: TrieCursorMetrics,
161    ) -> Self {
162        Self { factory, account_metrics, storage_metrics }
163    }
164}
165
166impl<F: TrieCursorFactory> TrieCursorFactory for InstrumentedTrieCursorFactory<F> {
167    type AccountTrieCursor<'a>
168        = InstrumentedTrieCursor<F::AccountTrieCursor<'a>>
169    where
170        Self: 'a;
171    type StorageTrieCursor<'a>
172        = InstrumentedTrieCursor<F::StorageTrieCursor<'a>>
173    where
174        Self: 'a;
175
176    fn account_trie_cursor(&self) -> Result<Self::AccountTrieCursor<'_>, DatabaseError> {
177        Ok(self.account_metrics.instrument(self.factory.account_trie_cursor()?))
178    }
179
180    fn storage_trie_cursor(
181        &self,
182        hashed_address: B256,
183    ) -> Result<Self::StorageTrieCursor<'_>, DatabaseError> {
184        Ok(self.storage_metrics.instrument(self.factory.storage_trie_cursor(hashed_address)?))
185    }
186}