reth_trie/trie_cursor/
metrics.rs

1use super::{TrieCursor, TrieStorageCursor};
2use crate::{BranchNodeCompact, Nibbles};
3use alloy_primitives::B256;
4use reth_storage_errors::db::DatabaseError;
5use std::time::{Duration, Instant};
6use tracing::debug_span;
7
8#[cfg(feature = "metrics")]
9use crate::TrieType;
10#[cfg(feature = "metrics")]
11use reth_metrics::metrics::{self, Histogram};
12
13/// Prometheus metrics for trie 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 TrieCursorMetrics {
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 `seek_exact()` operations
26    seek_exact_histogram: Histogram,
27}
28
29#[cfg(feature = "metrics")]
30impl TrieCursorMetrics {
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.cursor.overall_duration",
38                "type" => trie_type_str
39            ),
40            next_histogram: metrics::histogram!(
41                "trie.cursor.operations",
42                "type" => trie_type_str,
43                "operation" => "next"
44            ),
45            seek_histogram: metrics::histogram!(
46                "trie.cursor.operations",
47                "type" => trie_type_str,
48                "operation" => "seek"
49            ),
50            seek_exact_histogram: metrics::histogram!(
51                "trie.cursor.operations",
52                "type" => trie_type_str,
53                "operation" => "seek_exact"
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 TrieCursorMetricsCache) {
63        self.next_histogram.record(cache.next_count as f64);
64        self.seek_histogram.record(cache.seek_count as f64);
65        self.seek_exact_histogram.record(cache.seek_exact_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 trie cursor operations.
72#[derive(Debug, Copy, Clone)]
73pub struct TrieCursorMetricsCache {
74    /// Counter for `next()` calls
75    pub next_count: usize,
76    /// Counter for `seek()` calls
77    pub seek_count: usize,
78    /// Counter for `seek_exact()` calls
79    pub seek_exact_count: usize,
80    /// Total duration spent in database operations
81    pub total_duration: Duration,
82}
83
84impl Default for TrieCursorMetricsCache {
85    fn default() -> Self {
86        Self { next_count: 0, seek_count: 0, seek_exact_count: 0, total_duration: Duration::ZERO }
87    }
88}
89
90impl TrieCursorMetricsCache {
91    /// Reset all counters to zero.
92    pub const fn reset(&mut self) {
93        self.next_count = 0;
94        self.seek_count = 0;
95        self.seek_exact_count = 0;
96        self.total_duration = Duration::ZERO;
97    }
98
99    /// Extend this cache by adding the counts from another cache.
100    ///
101    /// This accumulates the counter values from `other` into this cache.
102    pub fn extend(&mut self, other: &Self) {
103        self.next_count += other.next_count;
104        self.seek_count += other.seek_count;
105        self.seek_exact_count += other.seek_exact_count;
106        self.total_duration += other.total_duration;
107    }
108
109    /// Record the span for metrics.
110    pub fn record_span(&self, name: &'static str) {
111        let _span = debug_span!(
112            target: "trie::trie_cursor",
113            "Trie cursor metrics",
114            name,
115            next_count = self.next_count,
116            seek_count = self.seek_count,
117            seek_exact_count = self.seek_exact_count,
118            total_duration = self.total_duration.as_secs_f64(),
119        )
120        .entered();
121    }
122}
123
124/// A wrapper around a [`TrieCursor`] that tracks metrics for cursor operations.
125///
126/// This implementation counts the number of times each cursor operation is called:
127/// - `next()` - Move to the next entry
128/// - `seek()` - Seek to a key or the next greater key
129/// - `seek_exact()` - Seek to an exact key match
130#[derive(Debug)]
131pub struct InstrumentedTrieCursor<'metrics, C> {
132    /// The underlying cursor being wrapped
133    cursor: C,
134    /// Cached metrics counters
135    metrics: &'metrics mut TrieCursorMetricsCache,
136}
137
138impl<'metrics, C> InstrumentedTrieCursor<'metrics, C> {
139    /// Create a new metrics cursor wrapping the given cursor.
140    pub const fn new(cursor: C, metrics: &'metrics mut TrieCursorMetricsCache) -> Self {
141        Self { cursor, metrics }
142    }
143}
144
145impl<'metrics, C: TrieCursor> TrieCursor for InstrumentedTrieCursor<'metrics, C> {
146    fn seek_exact(
147        &mut self,
148        key: Nibbles,
149    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
150        let start = Instant::now();
151        self.metrics.seek_exact_count += 1;
152        let result = self.cursor.seek_exact(key);
153        self.metrics.total_duration += start.elapsed();
154        result
155    }
156
157    fn seek(
158        &mut self,
159        key: Nibbles,
160    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
161        let start = Instant::now();
162        self.metrics.seek_count += 1;
163        let result = self.cursor.seek(key);
164        self.metrics.total_duration += start.elapsed();
165        result
166    }
167
168    fn next(&mut self) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
169        let start = Instant::now();
170        self.metrics.next_count += 1;
171        let result = self.cursor.next();
172        self.metrics.total_duration += start.elapsed();
173        result
174    }
175
176    fn current(&mut self) -> Result<Option<Nibbles>, DatabaseError> {
177        self.cursor.current()
178    }
179
180    fn reset(&mut self) {
181        self.cursor.reset()
182    }
183}
184
185impl<'metrics, C: TrieStorageCursor> TrieStorageCursor for InstrumentedTrieCursor<'metrics, C> {
186    fn set_hashed_address(&mut self, hashed_address: B256) {
187        self.cursor.set_hashed_address(hashed_address)
188    }
189}