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 PutUpsert,
202 PutAppend,
204 Delete,
206 CursorUpsert,
208 CursorInsert,
210 CursorAppend,
212 CursorAppendDup,
214 CursorDeleteCurrent,
216 CursorDeleteCurrentDuplicates,
218}
219
220impl Operation {
221 pub(crate) const fn as_str(&self) -> &'static str {
223 match self {
224 Self::Get => "get",
225 Self::PutUpsert => "put-upsert",
226 Self::PutAppend => "put-append",
227 Self::Delete => "delete",
228 Self::CursorUpsert => "cursor-upsert",
229 Self::CursorInsert => "cursor-insert",
230 Self::CursorAppend => "cursor-append",
231 Self::CursorAppendDup => "cursor-append-dup",
232 Self::CursorDeleteCurrent => "cursor-delete-current",
233 Self::CursorDeleteCurrentDuplicates => "cursor-delete-current-duplicates",
234 }
235 }
236}
237
238enum Labels {
240 Table,
242 TransactionMode,
244 TransactionOutcome,
246 Operation,
248}
249
250impl Labels {
251 pub(crate) const fn as_str(&self) -> &'static str {
253 match self {
254 Self::Table => "table",
255 Self::TransactionMode => "mode",
256 Self::TransactionOutcome => "outcome",
257 Self::Operation => "operation",
258 }
259 }
260}
261
262#[derive(Metrics, Clone)]
263#[metrics(scope = "database.transaction")]
264pub(crate) struct TransactionMetrics {
265 opened_total: Counter,
267 closed_total: Counter,
269}
270
271impl TransactionMetrics {
272 pub(crate) fn record_open(&self) {
273 self.opened_total.increment(1);
274 }
275
276 pub(crate) fn record_close(&self) {
277 self.closed_total.increment(1);
278 }
279}
280
281#[derive(Metrics, Clone)]
282#[metrics(scope = "database.transaction")]
283pub(crate) struct TransactionOutcomeMetrics {
284 open_duration_seconds: Histogram,
286 close_duration_seconds: Histogram,
288 commit_preparation_duration_seconds: Histogram,
290 commit_gc_wallclock_duration_seconds: Histogram,
292 commit_audit_duration_seconds: Histogram,
294 commit_write_duration_seconds: Histogram,
297 commit_sync_duration_seconds: Histogram,
299 commit_ending_duration_seconds: Histogram,
301 commit_whole_duration_seconds: Histogram,
303 commit_gc_cputime_duration_seconds: Histogram,
305}
306
307impl TransactionOutcomeMetrics {
308 #[cfg(feature = "mdbx")]
311 pub(crate) fn record(
312 &self,
313 open_duration: Duration,
314 close_duration: Option<Duration>,
315 commit_latency: Option<reth_libmdbx::CommitLatency>,
316 ) {
317 self.open_duration_seconds.record(open_duration);
318
319 if let Some(close_duration) = close_duration {
320 self.close_duration_seconds.record(close_duration)
321 }
322
323 if let Some(commit_latency) = commit_latency {
324 self.commit_preparation_duration_seconds.record(commit_latency.preparation());
325 self.commit_gc_wallclock_duration_seconds.record(commit_latency.gc_wallclock());
326 self.commit_audit_duration_seconds.record(commit_latency.audit());
327 self.commit_write_duration_seconds.record(commit_latency.write());
328 self.commit_sync_duration_seconds.record(commit_latency.sync());
329 self.commit_ending_duration_seconds.record(commit_latency.ending());
330 self.commit_whole_duration_seconds.record(commit_latency.whole());
331 self.commit_gc_cputime_duration_seconds.record(commit_latency.gc_cputime());
332 }
333 }
334}
335
336#[derive(Metrics, Clone)]
337#[metrics(scope = "database.operation")]
338pub(crate) struct OperationMetrics {
339 calls_total: Counter,
341 large_value_duration_seconds: Histogram,
344}
345
346impl OperationMetrics {
347 pub(crate) fn record<R>(&self, value_size: Option<usize>, f: impl FnOnce() -> R) -> R {
352 self.calls_total.increment(1);
353
354 if value_size.is_some_and(|size| size > LARGE_VALUE_THRESHOLD_BYTES) {
357 let start = Instant::now();
358 let result = f();
359 self.large_value_duration_seconds.record(start.elapsed());
360 result
361 } else {
362 f()
363 }
364 }
365}