diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index dd67adc53..b7b4c520c 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -38,7 +38,7 @@ use reth_config::{ config::{PruneConfig, StageConfig}, Config, }; -use reth_db::{database::Database, init_db, DatabaseEnv}; +use reth_db::{database::Database, database_metrics::DatabaseMetrics, init_db}; use reth_downloaders::{ bodies::bodies::BodiesDownloaderBuilder, headers::reverse_headers::ReverseHeadersDownloaderBuilder, @@ -693,11 +693,14 @@ impl NodeCommand { prometheus_exporter::install_recorder() } - async fn start_metrics_endpoint( + async fn start_metrics_endpoint( &self, prometheus_handle: PrometheusHandle, - db: Arc, - ) -> eyre::Result<()> { + db: Metrics, + ) -> eyre::Result<()> + where + Metrics: DatabaseMetrics + 'static + Send + Sync, + { if let Some(listen_addr) = self.metrics { info!(target: "reth::cli", addr = %listen_addr, "Starting metrics endpoint"); prometheus_exporter::serve( diff --git a/bin/reth/src/prometheus_exporter.rs b/bin/reth/src/prometheus_exporter.rs index 4fa622bff..0ec5352c9 100644 --- a/bin/reth/src/prometheus_exporter.rs +++ b/bin/reth/src/prometheus_exporter.rs @@ -7,7 +7,7 @@ use hyper::{ use metrics::{describe_gauge, gauge}; use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle}; use metrics_util::layers::{PrefixLayer, Stack}; -use reth_db::{database::Database, tables, DatabaseEnv}; +use reth_db::database_metrics::DatabaseMetrics; use reth_metrics::metrics::Unit; use std::{convert::Infallible, net::SocketAddr, sync::Arc}; use tracing::error; @@ -74,54 +74,21 @@ async fn start_endpoint( } /// Serves Prometheus metrics over HTTP with database and process metrics. -pub(crate) async fn serve( +pub(crate) async fn serve( listen_addr: SocketAddr, handle: PrometheusHandle, - db: Arc, + db: Metrics, process: metrics_process::Collector, -) -> eyre::Result<()> { - let db_stats = move || { - // TODO: A generic stats abstraction for other DB types to deduplicate this and `reth db - // stats` - let _ = db.view(|tx| { - for table in tables::Tables::ALL.iter().map(|table| table.name()) { - let table_db = - tx.inner.open_db(Some(table)).wrap_err("Could not open db.")?; - - let stats = tx - .inner - .db_stat(&table_db) - .wrap_err(format!("Could not find table: {table}"))?; - - let page_size = stats.page_size() as usize; - let leaf_pages = stats.leaf_pages(); - let branch_pages = stats.branch_pages(); - let overflow_pages = stats.overflow_pages(); - let num_pages = leaf_pages + branch_pages + overflow_pages; - let table_size = page_size * num_pages; - let entries = stats.entries(); - - gauge!("db.table_size", table_size as f64, "table" => table); - gauge!("db.table_pages", leaf_pages as f64, "table" => table, "type" => "leaf"); - gauge!("db.table_pages", branch_pages as f64, "table" => table, "type" => "branch"); - gauge!("db.table_pages", overflow_pages as f64, "table" => table, "type" => "overflow"); - gauge!("db.table_entries", entries as f64, "table" => table); - } - - Ok::<(), eyre::Report>(()) - }).map_err(|error| error!(?error, "Failed to read db table stats")); - - if let Ok(freelist) = - db.freelist().map_err(|error| error!(?error, "Failed to read db.freelist")) - { - gauge!("db.freelist", freelist as f64); - } - }; +) -> eyre::Result<()> +where + Metrics: DatabaseMetrics + 'static + Send + Sync, +{ + let db_metrics_hook = move || db.report_metrics(); // Clone `process` to move it into the hook and use the original `process` for describe below. let cloned_process = process.clone(); let hooks: Vec>> = vec![ - Box::new(db_stats), + Box::new(db_metrics_hook), Box::new(move || cloned_process.collect()), Box::new(collect_memory_stats), ]; diff --git a/crates/storage/db/src/abstraction/database_metrics.rs b/crates/storage/db/src/abstraction/database_metrics.rs new file mode 100644 index 000000000..717e1b342 --- /dev/null +++ b/crates/storage/db/src/abstraction/database_metrics.rs @@ -0,0 +1,14 @@ +use std::sync::Arc; + +/// Represents a type that can report metrics, used mainly with the database. The `report_metrics` +/// method can be used as a prometheus hook. +pub trait DatabaseMetrics { + /// Reports metrics for the database. + fn report_metrics(&self); +} + +impl DatabaseMetrics for Arc { + fn report_metrics(&self) { + ::report_metrics(self) + } +} diff --git a/crates/storage/db/src/abstraction/mod.rs b/crates/storage/db/src/abstraction/mod.rs index c1cdc3680..1f2963d17 100644 --- a/crates/storage/db/src/abstraction/mod.rs +++ b/crates/storage/db/src/abstraction/mod.rs @@ -4,6 +4,8 @@ pub mod common; pub mod cursor; /// Database traits. pub mod database; +/// Database metrics trait extensions. +pub mod database_metrics; /// mock pub mod mock; /// Table traits diff --git a/crates/storage/db/src/implementation/mdbx/mod.rs b/crates/storage/db/src/implementation/mdbx/mod.rs index 91a56ca44..e4bed3059 100644 --- a/crates/storage/db/src/implementation/mdbx/mod.rs +++ b/crates/storage/db/src/implementation/mdbx/mod.rs @@ -2,14 +2,18 @@ use crate::{ database::Database, + database_metrics::DatabaseMetrics, tables::{TableType, Tables}, utils::default_page_size, DatabaseError, }; +use eyre::Context; +use metrics::gauge; use reth_interfaces::db::LogLevel; use reth_libmdbx::{ DatabaseFlags, Environment, EnvironmentFlags, Geometry, Mode, PageSize, SyncMode, RO, RW, }; +use reth_tracing::tracing::error; use std::{ops::Deref, path::Path}; use tx::Tx; @@ -59,6 +63,44 @@ impl Database for DatabaseEnv { } } +impl DatabaseMetrics for DatabaseEnv { + fn report_metrics(&self) { + let _ = self.view(|tx| { + for table in Tables::ALL.iter().map(|table| table.name()) { + let table_db = + tx.inner.open_db(Some(table)).wrap_err("Could not open db.")?; + + let stats = tx + .inner + .db_stat(&table_db) + .wrap_err(format!("Could not find table: {table}"))?; + + let page_size = stats.page_size() as usize; + let leaf_pages = stats.leaf_pages(); + let branch_pages = stats.branch_pages(); + let overflow_pages = stats.overflow_pages(); + let num_pages = leaf_pages + branch_pages + overflow_pages; + let table_size = page_size * num_pages; + let entries = stats.entries(); + + gauge!("db.table_size", table_size as f64, "table" => table); + gauge!("db.table_pages", leaf_pages as f64, "table" => table, "type" => "leaf"); + gauge!("db.table_pages", branch_pages as f64, "table" => table, "type" => "branch"); + gauge!("db.table_pages", overflow_pages as f64, "table" => table, "type" => "overflow"); + gauge!("db.table_entries", entries as f64, "table" => table); + } + + Ok::<(), eyre::Report>(()) + }).map_err(|error| error!(?error, "Failed to read db table stats")); + + if let Ok(freelist) = + self.freelist().map_err(|error| error!(?error, "Failed to read db.freelist")) + { + gauge!("db.freelist", freelist as f64); + } + } +} + impl DatabaseEnv { /// Opens the database at the specified path with the given `EnvKind`. /// diff --git a/crates/storage/db/src/lib.rs b/crates/storage/db/src/lib.rs index 0c8078a69..a7ff4b365 100644 --- a/crates/storage/db/src/lib.rs +++ b/crates/storage/db/src/lib.rs @@ -153,7 +153,7 @@ pub fn open_db(path: &Path, log_level: Option) -> eyre::Result DatabaseMetrics for TempDatabase { + fn report_metrics(&self) { + self.db().report_metrics() + } + } + /// Create read/write database for testing pub fn create_test_rw_db() -> Arc> { let path = tempfile::TempDir::new().expect(ERROR_TEMPDIR).into_path();