Skip to main content

reth_trie/trie_cursor/
metrics.rs

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