1use crate::Tables;
2use metrics::Histogram;
3use reth_metrics::{metrics::Counter, Metrics};
4use rustc_hash::FxHashMap;
5use std::time::{Duration, Instant};
6use strum::{EnumCount, EnumIter, IntoEnumIterator};
7
8const LARGE_VALUE_THRESHOLD_BYTES: usize = 4096;
9
10#[derive(Debug)]
16pub(crate) struct DatabaseEnvMetrics {
17 operations: FxHashMap<(&'static str, Operation), OperationMetrics>,
19 transactions: FxHashMap<TransactionMode, TransactionMetrics>,
22 transaction_outcomes:
25 FxHashMap<(TransactionMode, TransactionOutcome), TransactionOutcomeMetrics>,
26}
27
28impl DatabaseEnvMetrics {
29 pub(crate) fn new() -> Self {
30 Self {
33 operations: Self::generate_operation_handles(),
34 transactions: Self::generate_transaction_handles(),
35 transaction_outcomes: Self::generate_transaction_outcome_handles(),
36 }
37 }
38
39 fn generate_operation_handles() -> FxHashMap<(&'static str, Operation), OperationMetrics> {
42 let mut operations = FxHashMap::with_capacity_and_hasher(
43 Tables::COUNT * Operation::COUNT,
44 Default::default(),
45 );
46 for table in Tables::ALL {
47 for operation in Operation::iter() {
48 operations.insert(
49 (table.name(), operation),
50 OperationMetrics::new_with_labels(&[
51 (Labels::Table.as_str(), table.name()),
52 (Labels::Operation.as_str(), operation.as_str()),
53 ]),
54 );
55 }
56 }
57 operations
58 }
59
60 fn generate_transaction_handles() -> FxHashMap<TransactionMode, TransactionMetrics> {
63 TransactionMode::iter()
64 .map(|mode| {
65 (
66 mode,
67 TransactionMetrics::new_with_labels(&[(
68 Labels::TransactionMode.as_str(),
69 mode.as_str(),
70 )]),
71 )
72 })
73 .collect()
74 }
75
76 fn generate_transaction_outcome_handles(
79 ) -> FxHashMap<(TransactionMode, TransactionOutcome), TransactionOutcomeMetrics> {
80 let mut transaction_outcomes = FxHashMap::with_capacity_and_hasher(
81 TransactionMode::COUNT * TransactionOutcome::COUNT,
82 Default::default(),
83 );
84 for mode in TransactionMode::iter() {
85 for outcome in TransactionOutcome::iter() {
86 transaction_outcomes.insert(
87 (mode, outcome),
88 TransactionOutcomeMetrics::new_with_labels(&[
89 (Labels::TransactionMode.as_str(), mode.as_str()),
90 (Labels::TransactionOutcome.as_str(), outcome.as_str()),
91 ]),
92 );
93 }
94 }
95 transaction_outcomes
96 }
97
98 pub(crate) fn record_operation<R>(
101 &self,
102 table: &'static str,
103 operation: Operation,
104 value_size: Option<usize>,
105 f: impl FnOnce() -> R,
106 ) -> R {
107 if let Some(metrics) = self.operations.get(&(table, operation)) {
108 metrics.record(value_size, f)
109 } else {
110 f()
111 }
112 }
113
114 pub(crate) fn record_opened_transaction(&self, mode: TransactionMode) {
116 self.transactions
117 .get(&mode)
118 .expect("transaction mode metric handle not found")
119 .record_open();
120 }
121
122 #[cfg(feature = "mdbx")]
124 pub(crate) fn record_closed_transaction(
125 &self,
126 mode: TransactionMode,
127 outcome: TransactionOutcome,
128 open_duration: Duration,
129 close_duration: Option<Duration>,
130 commit_latency: Option<reth_libmdbx::CommitLatency>,
131 ) {
132 self.transactions
133 .get(&mode)
134 .expect("transaction mode metric handle not found")
135 .record_close();
136
137 self.transaction_outcomes
138 .get(&(mode, outcome))
139 .expect("transaction outcome metric handle not found")
140 .record(open_duration, close_duration, commit_latency);
141 }
142}
143
144#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)]
146pub(crate) enum TransactionMode {
147 ReadOnly,
149 ReadWrite,
151}
152
153impl TransactionMode {
154 pub(crate) const fn as_str(&self) -> &'static str {
156 match self {
157 Self::ReadOnly => "read-only",
158 Self::ReadWrite => "read-write",
159 }
160 }
161
162 pub(crate) const fn is_read_only(&self) -> bool {
164 matches!(self, Self::ReadOnly)
165 }
166}
167
168#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)]
170pub(crate) enum TransactionOutcome {
171 Commit,
173 Abort,
175 Drop,
177}
178
179impl TransactionOutcome {
180 pub(crate) const fn as_str(&self) -> &'static str {
182 match self {
183 Self::Commit => "commit",
184 Self::Abort => "abort",
185 Self::Drop => "drop",
186 }
187 }
188
189 pub(crate) const fn is_commit(&self) -> bool {
191 matches!(self, Self::Commit)
192 }
193}
194
195#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)]
197pub(crate) enum Operation {
198 Get,
200 Put,
202 Delete,
204 CursorUpsert,
206 CursorInsert,
208 CursorAppend,
210 CursorAppendDup,
212 CursorDeleteCurrent,
214 CursorDeleteCurrentDuplicates,
216}
217
218impl Operation {
219 pub(crate) const fn as_str(&self) -> &'static str {
221 match self {
222 Self::Get => "get",
223 Self::Put => "put",
224 Self::Delete => "delete",
225 Self::CursorUpsert => "cursor-upsert",
226 Self::CursorInsert => "cursor-insert",
227 Self::CursorAppend => "cursor-append",
228 Self::CursorAppendDup => "cursor-append-dup",
229 Self::CursorDeleteCurrent => "cursor-delete-current",
230 Self::CursorDeleteCurrentDuplicates => "cursor-delete-current-duplicates",
231 }
232 }
233}
234
235enum Labels {
237 Table,
239 TransactionMode,
241 TransactionOutcome,
243 Operation,
245}
246
247impl Labels {
248 pub(crate) const fn as_str(&self) -> &'static str {
250 match self {
251 Self::Table => "table",
252 Self::TransactionMode => "mode",
253 Self::TransactionOutcome => "outcome",
254 Self::Operation => "operation",
255 }
256 }
257}
258
259#[derive(Metrics, Clone)]
260#[metrics(scope = "database.transaction")]
261pub(crate) struct TransactionMetrics {
262 opened_total: Counter,
264 closed_total: Counter,
266}
267
268impl TransactionMetrics {
269 pub(crate) fn record_open(&self) {
270 self.opened_total.increment(1);
271 }
272
273 pub(crate) fn record_close(&self) {
274 self.closed_total.increment(1);
275 }
276}
277
278#[derive(Metrics, Clone)]
279#[metrics(scope = "database.transaction")]
280pub(crate) struct TransactionOutcomeMetrics {
281 open_duration_seconds: Histogram,
283 close_duration_seconds: Histogram,
285 commit_preparation_duration_seconds: Histogram,
287 commit_gc_wallclock_duration_seconds: Histogram,
289 commit_audit_duration_seconds: Histogram,
291 commit_write_duration_seconds: Histogram,
294 commit_sync_duration_seconds: Histogram,
296 commit_ending_duration_seconds: Histogram,
298 commit_whole_duration_seconds: Histogram,
300 commit_gc_cputime_duration_seconds: Histogram,
302}
303
304impl TransactionOutcomeMetrics {
305 #[cfg(feature = "mdbx")]
308 pub(crate) fn record(
309 &self,
310 open_duration: Duration,
311 close_duration: Option<Duration>,
312 commit_latency: Option<reth_libmdbx::CommitLatency>,
313 ) {
314 self.open_duration_seconds.record(open_duration);
315
316 if let Some(close_duration) = close_duration {
317 self.close_duration_seconds.record(close_duration)
318 }
319
320 if let Some(commit_latency) = commit_latency {
321 self.commit_preparation_duration_seconds.record(commit_latency.preparation());
322 self.commit_gc_wallclock_duration_seconds.record(commit_latency.gc_wallclock());
323 self.commit_audit_duration_seconds.record(commit_latency.audit());
324 self.commit_write_duration_seconds.record(commit_latency.write());
325 self.commit_sync_duration_seconds.record(commit_latency.sync());
326 self.commit_ending_duration_seconds.record(commit_latency.ending());
327 self.commit_whole_duration_seconds.record(commit_latency.whole());
328 self.commit_gc_cputime_duration_seconds.record(commit_latency.gc_cputime());
329 }
330 }
331}
332
333#[derive(Metrics, Clone)]
334#[metrics(scope = "database.operation")]
335pub(crate) struct OperationMetrics {
336 calls_total: Counter,
338 large_value_duration_seconds: Histogram,
341}
342
343impl OperationMetrics {
344 pub(crate) fn record<R>(&self, value_size: Option<usize>, f: impl FnOnce() -> R) -> R {
349 self.calls_total.increment(1);
350
351 if value_size.is_some_and(|size| size > LARGE_VALUE_THRESHOLD_BYTES) {
354 let start = Instant::now();
355 let result = f();
356 self.large_value_duration_seconds.record(start.elapsed());
357 result
358 } else {
359 f()
360 }
361 }
362}