Skip to main content

reth_trie/hashed_cursor/
metrics.rs

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