diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index 56a10d49..d9652455 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -3,7 +3,7 @@ use std::time::{Duration, SystemTime}; use ethlambda_network_api::{BlockChainToP2PRef, InitP2P}; use ethlambda_state_transition::is_proposer; -use ethlambda_storage::Store; +use ethlambda_storage::{ALL_TABLES, Store}; use ethlambda_types::{ ShortRoot, attestation::{Attestation, AttestationData, SignedAggregatedAttestation, SignedAttestation}, @@ -285,6 +285,9 @@ impl BlockChainServer { metrics::update_latest_justified_slot(self.store.latest_justified().slot); metrics::update_latest_finalized_slot(self.store.latest_finalized().slot); metrics::update_validators_count(self.key_manager.validator_ids().len() as u64); + for table in ALL_TABLES { + metrics::update_table_bytes(table.name(), self.store.estimate_table_bytes(table)); + } Ok(()) } diff --git a/crates/storage/src/api/tables.rs b/crates/storage/src/api/tables.rs index 7f7d7a3c..35fb687d 100644 --- a/crates/storage/src/api/tables.rs +++ b/crates/storage/src/api/tables.rs @@ -37,3 +37,19 @@ pub const ALL_TABLES: [Table; 8] = [ Table::Metadata, Table::LiveChain, ]; + +impl Table { + /// Human-readable name for metrics labels. + pub fn name(self) -> &'static str { + match self { + Table::BlockHeaders => "block_headers", + Table::BlockBodies => "block_bodies", + Table::BlockSignatures => "block_signatures", + Table::States => "states", + Table::GossipSignatures => "gossip_signatures", + Table::AttestationDataByRoot => "attestation_data_by_root", + Table::Metadata => "metadata", + Table::LiveChain => "live_chain", + } + } +} diff --git a/crates/storage/src/api/traits.rs b/crates/storage/src/api/traits.rs index 5ab82601..b1b15d4c 100644 --- a/crates/storage/src/api/traits.rs +++ b/crates/storage/src/api/traits.rs @@ -13,6 +13,13 @@ pub trait StorageBackend: Send + Sync { /// Begin a write batch. fn begin_write(&self) -> Result, Error>; + + /// Estimated live data size in bytes for a table. + /// Returns 0 if the backend does not support this (e.g. in-memory). + fn estimate_table_bytes(&self, table: Table) -> u64 { + let _ = table; + 0 + } } /// A read-only view of the storage. diff --git a/crates/storage/src/backend/rocksdb.rs b/crates/storage/src/backend/rocksdb.rs index b1338052..160ea4cc 100644 --- a/crates/storage/src/backend/rocksdb.rs +++ b/crates/storage/src/backend/rocksdb.rs @@ -61,6 +61,25 @@ impl StorageBackend for RocksDBBackend { batch: WriteBatch::default(), })) } + + fn estimate_table_bytes(&self, table: Table) -> u64 { + let Some(cf) = self.db.cf_handle(cf_name(table)) else { + return 0; + }; + let sst_bytes = self + .db + .property_int_value_cf(&cf, "rocksdb.estimate-live-data-size") + .ok() + .flatten() + .unwrap_or(0); + let memtable_bytes = self + .db + .property_int_value_cf(&cf, "rocksdb.cur-size-all-mem-tables") + .ok() + .flatten() + .unwrap_or(0); + sst_bytes + memtable_bytes + } } /// Read-only view into RocksDB. diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 5cc90805..01ac010f 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -3,6 +3,6 @@ pub mod backend; mod store; mod types; -pub use api::{StorageBackend, StorageReadView, StorageWriteBatch, Table}; +pub use api::{ALL_TABLES, StorageBackend, StorageReadView, StorageWriteBatch, Table}; pub use store::{ForkCheckpoints, SignatureKey, Store}; pub use types::{StoredAggregatedPayload, StoredSignature}; diff --git a/crates/storage/src/store.rs b/crates/storage/src/store.rs index 4dd57538..2e3e6bec 100644 --- a/crates/storage/src/store.rs +++ b/crates/storage/src/store.rs @@ -1014,6 +1014,11 @@ impl Store { self.gossip_signatures_count.load(Ordering::Relaxed) } + /// Estimated live data size in bytes for a table, as reported by the backend. + pub fn estimate_table_bytes(&self, table: Table) -> u64 { + self.backend.estimate_table_bytes(table) + } + /// Delete specific gossip signatures by key. pub fn delete_gossip_signatures(&mut self, keys: &[SignatureKey]) { if keys.is_empty() {