1use crate::Tables;
2use metrics::Histogram;
3use reth_metrics::{metrics::Counter, Metrics};
4use reth_primitives_traits::FastInstant as Instant;
5use rustc_hash::FxHashMap;
6use std::{array, sync::Arc, 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: [TableOperationMetrics; Tables::COUNT],
21 transactions: FxHashMap<TransactionMode, TransactionMetrics>,
24 transaction_outcomes:
27 FxHashMap<(TransactionMode, TransactionOutcome), TransactionOutcomeMetrics>,
28}
29
30pub(crate) type TableOperationMetrics = Arc<[OperationMetrics; Operation::COUNT]>;
32
33impl DatabaseEnvMetrics {
34 pub(crate) fn new() -> Self {
35 Self {
38 operations: Self::generate_operation_handles(),
39 transactions: Self::generate_transaction_handles(),
40 transaction_outcomes: Self::generate_transaction_outcome_handles(),
41 }
42 }
43
44 fn generate_operation_handles() -> [TableOperationMetrics; Tables::COUNT] {
46 array::from_fn(|table_index| {
47 let table_name = Tables::ALL[table_index].name();
48 let metrics = array::from_fn(|index| {
49 let operation = Operation::from_index(index);
50 OperationMetrics::new_with_labels(&[
51 (Labels::Table.as_str(), table_name),
52 (Labels::Operation.as_str(), operation.as_str()),
53 ])
54 });
55
56 Arc::new(metrics)
57 })
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>(
102 &self,
103 table: Option<Tables>,
104 operation: Operation,
105 value_size: Option<usize>,
106 f: impl FnOnce() -> R,
107 ) -> R {
108 if let Some(table) = table {
109 self.operations[table as usize][operation.index()].record(value_size, f)
110 } else {
111 f()
112 }
113 }
114
115 pub(crate) fn table_operation_metrics(&self, table: Tables) -> TableOperationMetrics {
117 self.operations[table as usize].clone()
118 }
119
120 pub(crate) fn record_opened_transaction(&self, mode: TransactionMode) {
122 self.transactions
123 .get(&mode)
124 .expect("transaction mode metric handle not found")
125 .record_open();
126 }
127
128 #[cfg(feature = "mdbx")]
130 pub(crate) fn record_closed_transaction(
131 &self,
132 mode: TransactionMode,
133 outcome: TransactionOutcome,
134 open_duration: Duration,
135 close_duration: Option<Duration>,
136 commit_latency: Option<reth_libmdbx::CommitLatency>,
137 ) {
138 self.transactions
139 .get(&mode)
140 .expect("transaction mode metric handle not found")
141 .record_close();
142
143 self.transaction_outcomes
144 .get(&(mode, outcome))
145 .expect("transaction outcome metric handle not found")
146 .record(open_duration, close_duration, commit_latency);
147 }
148}
149
150#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)]
152pub(crate) enum TransactionMode {
153 ReadOnly,
155 ReadWrite,
157}
158
159impl TransactionMode {
160 pub(crate) const fn as_str(&self) -> &'static str {
162 match self {
163 Self::ReadOnly => "read-only",
164 Self::ReadWrite => "read-write",
165 }
166 }
167
168 pub(crate) const fn is_read_only(&self) -> bool {
170 matches!(self, Self::ReadOnly)
171 }
172}
173
174#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)]
176pub(crate) enum TransactionOutcome {
177 Commit,
179 Abort,
181 Drop,
183}
184
185impl TransactionOutcome {
186 pub(crate) const fn as_str(&self) -> &'static str {
188 match self {
189 Self::Commit => "commit",
190 Self::Abort => "abort",
191 Self::Drop => "drop",
192 }
193 }
194
195 pub(crate) const fn is_commit(&self) -> bool {
197 matches!(self, Self::Commit)
198 }
199}
200
201#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)]
203pub(crate) enum Operation {
204 Get,
206 PutUpsert,
208 PutAppend,
210 Delete,
212 CursorUpsert,
214 CursorInsert,
216 CursorAppend,
218 CursorAppendDup,
220 CursorDeleteCurrent,
222 CursorDeleteCurrentDuplicates,
224}
225
226impl Operation {
227 pub(crate) const fn index(&self) -> usize {
229 match self {
230 Self::Get => 0,
231 Self::PutUpsert => 1,
232 Self::PutAppend => 2,
233 Self::Delete => 3,
234 Self::CursorUpsert => 4,
235 Self::CursorInsert => 5,
236 Self::CursorAppend => 6,
237 Self::CursorAppendDup => 7,
238 Self::CursorDeleteCurrent => 8,
239 Self::CursorDeleteCurrentDuplicates => 9,
240 }
241 }
242
243 const fn from_index(index: usize) -> Self {
245 match index {
246 0 => Self::Get,
247 1 => Self::PutUpsert,
248 2 => Self::PutAppend,
249 3 => Self::Delete,
250 4 => Self::CursorUpsert,
251 5 => Self::CursorInsert,
252 6 => Self::CursorAppend,
253 7 => Self::CursorAppendDup,
254 8 => Self::CursorDeleteCurrent,
255 9 => Self::CursorDeleteCurrentDuplicates,
256 _ => panic!("invalid operation index"),
257 }
258 }
259
260 pub(crate) const fn as_str(&self) -> &'static str {
262 match self {
263 Self::Get => "get",
264 Self::PutUpsert => "put-upsert",
265 Self::PutAppend => "put-append",
266 Self::Delete => "delete",
267 Self::CursorUpsert => "cursor-upsert",
268 Self::CursorInsert => "cursor-insert",
269 Self::CursorAppend => "cursor-append",
270 Self::CursorAppendDup => "cursor-append-dup",
271 Self::CursorDeleteCurrent => "cursor-delete-current",
272 Self::CursorDeleteCurrentDuplicates => "cursor-delete-current-duplicates",
273 }
274 }
275}
276
277enum Labels {
279 Table,
281 TransactionMode,
283 TransactionOutcome,
285 Operation,
287}
288
289impl Labels {
290 pub(crate) const fn as_str(&self) -> &'static str {
292 match self {
293 Self::Table => "table",
294 Self::TransactionMode => "mode",
295 Self::TransactionOutcome => "outcome",
296 Self::Operation => "operation",
297 }
298 }
299}
300
301#[derive(Metrics, Clone)]
302#[metrics(scope = "database.transaction")]
303pub(crate) struct TransactionMetrics {
304 opened_total: Counter,
306 closed_total: Counter,
308}
309
310impl TransactionMetrics {
311 pub(crate) fn record_open(&self) {
312 self.opened_total.increment(1);
313 }
314
315 pub(crate) fn record_close(&self) {
316 self.closed_total.increment(1);
317 }
318}
319
320#[derive(Metrics, Clone)]
321#[metrics(scope = "database.transaction")]
322pub(crate) struct TransactionOutcomeMetrics {
323 open_duration_seconds: Histogram,
325 close_duration_seconds: Histogram,
327 commit_preparation_duration_seconds: Histogram,
329 commit_gc_wallclock_duration_seconds: Histogram,
331 commit_audit_duration_seconds: Histogram,
333 commit_write_duration_seconds: Histogram,
336 commit_sync_duration_seconds: Histogram,
338 commit_ending_duration_seconds: Histogram,
340 commit_whole_duration_seconds: Histogram,
342 commit_gc_cputime_duration_seconds: Histogram,
344}
345
346impl TransactionOutcomeMetrics {
347 #[cfg(feature = "mdbx")]
350 pub(crate) fn record(
351 &self,
352 open_duration: Duration,
353 close_duration: Option<Duration>,
354 commit_latency: Option<reth_libmdbx::CommitLatency>,
355 ) {
356 self.open_duration_seconds.record(open_duration);
357
358 if let Some(close_duration) = close_duration {
359 self.close_duration_seconds.record(close_duration)
360 }
361
362 if let Some(commit_latency) = commit_latency {
363 self.commit_preparation_duration_seconds.record(commit_latency.preparation());
364 self.commit_gc_wallclock_duration_seconds.record(commit_latency.gc_wallclock());
365 self.commit_audit_duration_seconds.record(commit_latency.audit());
366 self.commit_write_duration_seconds.record(commit_latency.write());
367 self.commit_sync_duration_seconds.record(commit_latency.sync());
368 self.commit_ending_duration_seconds.record(commit_latency.ending());
369 self.commit_whole_duration_seconds.record(commit_latency.whole());
370 self.commit_gc_cputime_duration_seconds.record(commit_latency.gc_cputime());
371 }
372 }
373}
374
375#[derive(Metrics, Clone)]
376#[metrics(scope = "database.operation")]
377pub(crate) struct OperationMetrics {
378 calls_total: Counter,
380 large_value_duration_seconds: Histogram,
383}
384
385impl OperationMetrics {
386 pub(crate) fn record<R>(&self, value_size: Option<usize>, f: impl FnOnce() -> R) -> R {
391 self.calls_total.increment(1);
392
393 if value_size.is_some_and(|size| size > LARGE_VALUE_THRESHOLD_BYTES) {
396 let start = Instant::now();
397 let result = f();
398 self.large_value_duration_seconds.record(start.elapsed());
399 result
400 } else {
401 f()
402 }
403 }
404}