diff --git a/rust/crates/monad-triedb/include/ffi.h b/rust/crates/monad-triedb/include/ffi.h index fe991e1a49..671ec1caac 100644 --- a/rust/crates/monad-triedb/include/ffi.h +++ b/rust/crates/monad-triedb/include/ffi.h @@ -62,6 +62,30 @@ typedef struct triedb_storage_stats void triedb_storage_stats_read(TriedbRoInner *, triedb_storage_stats *out); +// Trie-node LRU counters for this handle. Totals since the handle was opened; +// reading does not reset them. `used_bytes` is against the node_lru_max_mem +// the handle was opened with, and `entries` against the slot count derived +// from it — compare the two to see which bound is binding. +// +// Covers the async paths only: triedb_async_read, triedb_async_traverse and +// triedb_async_ranged_get. triedb_read and triedb_traverse are blocking and +// consult no cache, so a caller using only those sees zeros here — which is +// not the same as an unused cache. +// +// Returns false without writing `out` if no counters are available; all-zero +// is a legitimate reading for an idle cache, so it cannot double as an error. +typedef struct triedb_node_cache_stats +{ + uint64_t hits; + uint64_t misses; + uint64_t evictions; + uint64_t used_bytes; + uint64_t entries; +} triedb_node_cache_stats; + +bool triedb_node_cache_stats_read( + TriedbRoInner *, triedb_node_cache_stats *out); + // Compute the storage page key for a 32-byte slot key on a page-encoded db: // page_key = slot >> 7. Writes the 32-byte big-endian page key (the key the // storage trie is looked up by) to out_page_key. diff --git a/rust/crates/monad-triedb/src/ffi.cpp b/rust/crates/monad-triedb/src/ffi.cpp index c14d5407a8..410b397533 100644 --- a/rust/crates/monad-triedb/src/ffi.cpp +++ b/rust/crates/monad-triedb/src/ffi.cpp @@ -193,6 +193,23 @@ void triedb_storage_stats_read( out->disk_used_bytes = stats.disk_used_bytes; } +bool triedb_node_cache_stats_read( + TriedbRoInner *const db, triedb_node_cache_stats *const out) +{ + if (out == nullptr || db == nullptr) { + return false; + } + *out = {}; + auto const &cache = db->async_ctx.node_cache; + auto const stats = cache.stats(); + out->hits = stats.hits; + out->misses = stats.misses; + out->evictions = stats.evictions; + out->used_bytes = static_cast(cache.used_bytes()); + out->entries = static_cast(cache.size()); + return true; +} + void triedb_compute_page_key( uint8_t const *const slot_key, uint8_t *const out_page_key) { diff --git a/rust/crates/monad-triedb/src/ffi.rs b/rust/crates/monad-triedb/src/ffi.rs index c50bf275c9..18fec8ce20 100644 --- a/rust/crates/monad-triedb/src/ffi.rs +++ b/rust/crates/monad-triedb/src/ffi.rs @@ -23,9 +23,9 @@ pub(crate) use self::bindings::{ triedb_earliest_version, triedb_finalize, triedb_free_valset, triedb_is_page_encoded, triedb_latest_finalized_version, triedb_latest_proposed_block_id, triedb_latest_proposed_version, triedb_latest_verified_version, triedb_latest_voted_block_id, - triedb_latest_voted_version, triedb_migration_phase, triedb_open, triedb_poll, triedb_read, - triedb_read_valset, triedb_storage_stats, triedb_storage_stats_read, triedb_traverse, - TriedbRoInner, + triedb_latest_voted_version, triedb_migration_phase, triedb_node_cache_stats, + triedb_node_cache_stats_read, triedb_open, triedb_poll, triedb_read, triedb_read_valset, + triedb_storage_stats, triedb_storage_stats_read, triedb_traverse, TriedbRoInner, }; pub use self::bindings::{validator_data, validator_set}; diff --git a/rust/crates/monad-triedb/src/lib.rs b/rust/crates/monad-triedb/src/lib.rs index e60f3c09ee..c04a43557c 100644 --- a/rust/crates/monad-triedb/src/lib.rs +++ b/rust/crates/monad-triedb/src/lib.rs @@ -72,6 +72,20 @@ pub struct StorageStats { pub disk_used_bytes: u64, } +/// Trie-node LRU counters for one handle. Totals since the handle was opened; +/// reading does not reset them, so a scraper derives its own rates. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct NodeCacheStats { + pub hits: u64, + pub misses: u64, + pub evictions: u64, + /// Bytes of cached nodes, against the handle's `node_lru_max_mem`. + pub used_bytes: u64, + /// Cached nodes, against the slot count derived from `node_lru_max_mem`. + /// Compare with `used_bytes` to see which of the two bounds is binding. + pub entries: u64, +} + struct SenderContext { sender: Sender>>, completed_counter: Arc, @@ -303,6 +317,32 @@ impl TriedbHandle { } } + /// Trie-node LRU counters for this handle. Each handle owns an independent + /// cache, so these are per-handle and not comparable across handles. + /// + /// Only the async paths consult the cache. Both [`Self::read`] and + /// synchronous traversal are blocking and uncached, so a caller using only + /// those sees zeros. + pub fn node_cache_stats(&self) -> Option { + let mut out = ffi::triedb_node_cache_stats { + hits: 0, + misses: 0, + evictions: 0, + used_bytes: 0, + entries: 0, + }; + if !unsafe { ffi::triedb_node_cache_stats_read(self.db_ptr, &mut out) } { + return None; + } + Some(NodeCacheStats { + hits: out.hits, + misses: out.misses, + evictions: out.evictions, + used_bytes: out.used_bytes, + entries: out.entries, + }) + } + pub fn read(&self, key: &[u8], key_len_nibbles: u8, block_id: u64) -> Option> { validate_nibble_key(key, key_len_nibbles, "Key")?;