1use crate::Tables;
2use metrics::Histogram;
3use quanta::Instant;
4use reth_metrics::{metrics::Counter, Metrics};
5use rustc_hash::FxHashMap;
6use std::time::Duration;
7use strum::{EnumCount, EnumIter, IntoEnumIterator};
8
9const LARGE_VALUE_THRESHOLD_BYTES: usize = 4096;
10
11#[derive(Debug)]
17pub(crate) struct DatabaseEnvMetrics {
18 operations: FxHashMap<(&'static str, Operation), OperationMetrics>,
20 transactions: FxHashMap<TransactionMode, TransactionMetrics>,
23 transaction_outcomes:
26 FxHashMap<(TransactionMode, TransactionOutcome), TransactionOutcomeMetrics>,
27}
28
29impl DatabaseEnvMetrics {
30 pub(crate) fn new() -> Self {
31 Self {
34 operations: Self::generate_operation_handles(),
35 transactions: Self::generate_transaction_handles(),
36 transaction_outcomes: Self::generate_transaction_outcome_handles(),
37 }
38 }
39
40 fn generate_operation_handles() -> FxHashMap<(&'static str, Operation), OperationMetrics> {
43 let mut operations = FxHashMap::with_capacity_and_hasher(
44 Tables::COUNT * Operation::COUNT,
45 Default::default(),
46 );
47 for table in Tables::ALL {
48 for operation in Operation::iter() {
49 operations.insert(
50 (table.name(), operation),
51 OperationMetrics::new_with_labels(&[
52 (Labels::Table.as_str(), table.name()),
53 (Labels::Operation.as_str(), operation.as_str()),
54 ]),
55 );
56 }
57 }
58 operations
59 }
60
61 fn generate_transaction_handles() -> FxHashMap<TransactionMode, TransactionMetrics> {
64 TransactionMode::iter()
65 .map(|mode| {
66 (
67 mode,
68 TransactionMetrics::new_with_labels(&[(
69 Labels::TransactionMode.as_str(),
70 mode.as_str(),
71 )]),
72 )
73 })
74 .collect()
75 }
76
77 fn generate_transaction_outcome_handles(
80 ) -> FxHashMap<(TransactionMode, TransactionOutcome), TransactionOutcomeMetrics> {
81 let mut transaction_outcomes = FxHashMap::with_capacity_and_hasher(
82 TransactionMode::COUNT * TransactionOutcome::COUNT,
83 Default::default(),
84 );
85 for mode in TransactionMode::iter() {
86 for outcome in TransactionOutcome::iter() {
87 transaction_outcomes.insert(
88 (mode, outcome),
89 TransactionOutcomeMetrics::new_with_labels(&[
90 (Labels::TransactionMode.as_str(), mode.as_str()),
91 (Labels::TransactionOutcome.as_str(), outcome.as_str()),
92 ]),
93 );
94 }
95 }
96 transaction_outcomes
97 }
98
99 pub(crate) fn record_operation<R>(
102 &self,
103 table: &'static str,
104 operation: Operation,
105 value_size: Option<usize>,
106 f: impl FnOnce() -> R,
107 ) -> R {
108 if let Some(metrics) = self.operations.get(&(table, operation)) {
109 metrics.record(value_size, f)
110 } else {
111 f()
112 }
113 }
114
115 pub(crate) fn record_opened_transaction(&self, mode: TransactionMode) {
117 self.transactions
118 .get(&mode)
119 .expect("transaction mode metric handle not found")
120 .record_open();
121 }
122
123 #[cfg(feature = "mdbx")]
125 pub(crate) fn record_closed_transaction(
126 &self,
127 mode: TransactionMode,
128 outcome: TransactionOutcome,
129 open_duration: Duration,
130 close_duration: Option<Duration>,
131 commit_latency: Option<reth_libmdbx::CommitLatency>,
132 ) {
133 self.transactions
134 .get(&mode)
135 .expect("transaction mode metric handle not found")
136 .record_close();
137
138 self.transaction_outcomes
139 .get(&(mode, outcome))
140 .expect("transaction outcome metric handle not found")
141 .record(open_duration, close_duration, commit_latency);
142 }
143}
144
145#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)]
147pub(crate) enum TransactionMode {
148 ReadOnly,
150 ReadWrite,
152}
153
154impl TransactionMode {
155 pub(crate) const fn as_str(&self) -> &'static str {
157 match self {
158 Self::ReadOnly => "read-only",
159 Self::ReadWrite => "read-write",
160 }
161 }
162
163 pub(crate) const fn is_read_only(&self) -> bool {
165 matches!(self, Self::ReadOnly)
166 }
167}
168
169#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)]
171pub(crate) enum TransactionOutcome {
172 Commit,
174 Abort,
176 Drop,
178}
179
180impl TransactionOutcome {
181 pub(crate) const fn as_str(&self) -> &'static str {
183 match self {
184 Self::Commit => "commit",
185 Self::Abort => "abort",
186 Self::Drop => "drop",
187 }
188 }
189
190 pub(crate) const fn is_commit(&self) -> bool {
192 matches!(self, Self::Commit)
193 }
194}
195
196#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)]
198pub(crate) enum Operation {
199 Get,
201 PutUpsert,
203 PutAppend,
205 Delete,
207 CursorUpsert,
209 CursorInsert,
211 CursorAppend,
213 CursorAppendDup,
215 CursorDeleteCurrent,
217 CursorDeleteCurrentDuplicates,
219}
220
221impl Operation {
222 pub(crate) const fn as_str(&self) -> &'static str {
224 match self {
225 Self::Get => "get",
226 Self::PutUpsert => "put-upsert",
227 Self::PutAppend => "put-append",
228 Self::Delete => "delete",
229 Self::CursorUpsert => "cursor-upsert",
230 Self::CursorInsert => "cursor-insert",
231 Self::CursorAppend => "cursor-append",
232 Self::CursorAppendDup => "cursor-append-dup",
233 Self::CursorDeleteCurrent => "cursor-delete-current",
234 Self::CursorDeleteCurrentDuplicates => "cursor-delete-current-duplicates",
235 }
236 }
237}
238
239enum Labels {
241 Table,
243 TransactionMode,
245 TransactionOutcome,
247 Operation,
249}
250
251impl Labels {
252 pub(crate) const fn as_str(&self) -> &'static str {
254 match self {
255 Self::Table => "table",
256 Self::TransactionMode => "mode",
257 Self::TransactionOutcome => "outcome",
258 Self::Operation => "operation",
259 }
260 }
261}
262
263#[derive(Metrics, Clone)]
264#[metrics(scope = "database.transaction")]
265pub(crate) struct TransactionMetrics {
266 opened_total: Counter,
268 closed_total: Counter,
270}
271
272impl TransactionMetrics {
273 pub(crate) fn record_open(&self) {
274 self.opened_total.increment(1);
275 }
276
277 pub(crate) fn record_close(&self) {
278 self.closed_total.increment(1);
279 }
280}
281
282#[derive(Metrics, Clone)]
283#[metrics(scope = "database.transaction")]
284pub(crate) struct TransactionOutcomeMetrics {
285 open_duration_seconds: Histogram,
287 close_duration_seconds: Histogram,
289 commit_preparation_duration_seconds: Histogram,
291 commit_gc_wallclock_duration_seconds: Histogram,
293 commit_audit_duration_seconds: Histogram,
295 commit_write_duration_seconds: Histogram,
298 commit_sync_duration_seconds: Histogram,
300 commit_ending_duration_seconds: Histogram,
302 commit_whole_duration_seconds: Histogram,
304 commit_gc_cputime_duration_seconds: Histogram,
306}
307
308impl TransactionOutcomeMetrics {
309 #[cfg(feature = "mdbx")]
312 pub(crate) fn record(
313 &self,
314 open_duration: Duration,
315 close_duration: Option<Duration>,
316 commit_latency: Option<reth_libmdbx::CommitLatency>,
317 ) {
318 self.open_duration_seconds.record(open_duration);
319
320 if let Some(close_duration) = close_duration {
321 self.close_duration_seconds.record(close_duration)
322 }
323
324 if let Some(commit_latency) = commit_latency {
325 self.commit_preparation_duration_seconds.record(commit_latency.preparation());
326 self.commit_gc_wallclock_duration_seconds.record(commit_latency.gc_wallclock());
327 self.commit_audit_duration_seconds.record(commit_latency.audit());
328 self.commit_write_duration_seconds.record(commit_latency.write());
329 self.commit_sync_duration_seconds.record(commit_latency.sync());
330 self.commit_ending_duration_seconds.record(commit_latency.ending());
331 self.commit_whole_duration_seconds.record(commit_latency.whole());
332 self.commit_gc_cputime_duration_seconds.record(commit_latency.gc_cputime());
333 }
334 }
335}
336
337#[derive(Metrics, Clone)]
338#[metrics(scope = "database.operation")]
339pub(crate) struct OperationMetrics {
340 calls_total: Counter,
342 large_value_duration_seconds: Histogram,
345}
346
347impl OperationMetrics {
348 pub(crate) fn record<R>(&self, value_size: Option<usize>, f: impl FnOnce() -> R) -> R {
353 self.calls_total.increment(1);
354
355 if value_size.is_some_and(|size| size > LARGE_VALUE_THRESHOLD_BYTES) {
358 let start = Instant::now();
359 let result = f();
360 self.large_value_duration_seconds.record(start.elapsed());
361 result
362 } else {
363 f()
364 }
365 }
366}