reth_trie/hashed_cursor/
metrics.rs

1use super::{HashedCursor, HashedStorageCursor};
2use alloy_primitives::B256;
3use reth_storage_errors::db::DatabaseError;
4use std::time::{Duration, Instant};
5use tracing::debug_span;
6
7#[cfg(feature = "metrics")]
8use crate::TrieType;
9#[cfg(feature = "metrics")]
10use reth_metrics::metrics::{self, Histogram};
11
12/// Prometheus metrics for hashed cursor operations.
13///
14/// Tracks the number of cursor operations for monitoring and performance analysis.
15#[cfg(feature = "metrics")]
16#[derive(Clone, Debug)]
17pub struct HashedCursorMetrics {
18    /// Histogram tracking overall time spent in database operations
19    overall_duration: Histogram,
20    /// Histogram for `next()` operations
21    next_histogram: Histogram,
22    /// Histogram for `seek()` operations
23    seek_histogram: Histogram,
24    /// Histogram for `is_storage_empty()` operations
25    is_storage_empty_histogram: Histogram,
26}
27
28#[cfg(feature = "metrics")]
29impl HashedCursorMetrics {
30    /// Create a new metrics instance with the specified trie type label.
31    pub fn new(trie_type: TrieType) -> Self {
32        let trie_type_str = trie_type.as_str();
33
34        Self {
35            overall_duration: metrics::histogram!(
36                "trie.hashed_cursor.overall_duration",
37                "type" => trie_type_str
38            ),
39            next_histogram: metrics::histogram!(
40                "trie.hashed_cursor.operations",
41                "type" => trie_type_str,
42                "operation" => "next"
43            ),
44            seek_histogram: metrics::histogram!(
45                "trie.hashed_cursor.operations",
46                "type" => trie_type_str,
47                "operation" => "seek"
48            ),
49            is_storage_empty_histogram: metrics::histogram!(
50                "trie.hashed_cursor.operations",
51                "type" => trie_type_str,
52                "operation" => "is_storage_empty"
53            ),
54        }
55    }
56
57    /// Record the cached metrics from the provided cache and reset the cache counters.
58    ///
59    /// This method adds the current counter values from the cache to the Prometheus metrics
60    /// and then resets all cache counters to zero.
61    pub fn record(&mut self, cache: &mut HashedCursorMetricsCache) {
62        self.next_histogram.record(cache.next_count as f64);
63        self.seek_histogram.record(cache.seek_count as f64);
64        self.is_storage_empty_histogram.record(cache.is_storage_empty_count as f64);
65        self.overall_duration.record(cache.total_duration.as_secs_f64());
66        cache.reset();
67    }
68}
69
70/// Cached metrics counters for hashed cursor operations.
71#[derive(Debug, Copy, Clone)]
72pub struct HashedCursorMetricsCache {
73    /// Counter for `next()` calls
74    pub next_count: usize,
75    /// Counter for `seek()` calls
76    pub seek_count: usize,
77    /// Counter for `is_storage_empty()` calls (if applicable)
78    pub is_storage_empty_count: usize,
79    /// Total duration spent in database operations
80    pub total_duration: Duration,
81}
82
83impl Default for HashedCursorMetricsCache {
84    fn default() -> Self {
85        Self {
86            next_count: 0,
87            seek_count: 0,
88            is_storage_empty_count: 0,
89            total_duration: Duration::ZERO,
90        }
91    }
92}
93
94impl HashedCursorMetricsCache {
95    /// Reset all counters to zero.
96    pub const fn reset(&mut self) {
97        self.next_count = 0;
98        self.seek_count = 0;
99        self.is_storage_empty_count = 0;
100        self.total_duration = Duration::ZERO;
101    }
102
103    /// Extend this cache by adding the counts from another cache.
104    ///
105    /// This accumulates the counter values from `other` into this cache.
106    pub fn extend(&mut self, other: &Self) {
107        self.next_count += other.next_count;
108        self.seek_count += other.seek_count;
109        self.is_storage_empty_count += other.is_storage_empty_count;
110        self.total_duration += other.total_duration;
111    }
112
113    /// Record the span for metrics.
114    pub fn record_span(&self, name: &'static str) {
115        let _span = debug_span!(
116            target: "trie::trie_cursor",
117            "Hashed cursor metrics",
118            name,
119            next_count = self.next_count,
120            seek_count = self.seek_count,
121            is_storage_empty_count = self.is_storage_empty_count,
122            total_duration = self.total_duration.as_secs_f64(),
123        )
124        .entered();
125    }
126}
127
128/// A wrapper around a [`HashedCursor`] that tracks metrics for cursor operations.
129///
130/// This implementation counts the number of times each cursor operation is called:
131/// - `next()` - Move to the next entry
132/// - `seek()` - Seek to a key or the next greater key
133#[derive(Debug)]
134pub struct InstrumentedHashedCursor<'metrics, C> {
135    /// The underlying cursor being wrapped
136    cursor: C,
137    /// Cached metrics counters
138    metrics: &'metrics mut HashedCursorMetricsCache,
139}
140
141impl<'metrics, C> InstrumentedHashedCursor<'metrics, C> {
142    /// Create a new metrics cursor wrapping the given cursor.
143    pub const fn new(cursor: C, metrics: &'metrics mut HashedCursorMetricsCache) -> Self {
144        Self { cursor, metrics }
145    }
146}
147
148impl<'metrics, C> HashedCursor for InstrumentedHashedCursor<'metrics, C>
149where
150    C: HashedCursor,
151{
152    type Value = C::Value;
153
154    fn seek(&mut self, key: B256) -> Result<Option<(B256, Self::Value)>, DatabaseError> {
155        let start = Instant::now();
156        self.metrics.seek_count += 1;
157        let result = self.cursor.seek(key);
158        self.metrics.total_duration += start.elapsed();
159        result
160    }
161
162    fn next(&mut self) -> Result<Option<(B256, Self::Value)>, DatabaseError> {
163        let start = Instant::now();
164        self.metrics.next_count += 1;
165        let result = self.cursor.next();
166        self.metrics.total_duration += start.elapsed();
167        result
168    }
169
170    fn reset(&mut self) {
171        self.cursor.reset()
172    }
173}
174
175impl<'metrics, C> HashedStorageCursor for InstrumentedHashedCursor<'metrics, C>
176where
177    C: HashedStorageCursor,
178{
179    fn is_storage_empty(&mut self) -> Result<bool, DatabaseError> {
180        let start = Instant::now();
181        self.metrics.is_storage_empty_count += 1;
182        let result = self.cursor.is_storage_empty();
183        self.metrics.total_duration += start.elapsed();
184        result
185    }
186
187    fn set_hashed_address(&mut self, hashed_address: B256) {
188        self.cursor.set_hashed_address(hashed_address)
189    }
190}