diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index a8c3f77008..406aabd373 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -653,6 +653,12 @@ where trie_metrics .into_trie_for_reuse_duration_histogram .record(start.elapsed().as_secs_f64()); + trie_metrics + .sparse_trie_retained_memory_bytes + .set(trie.memory_size() as f64); + trie_metrics + .sparse_trie_retained_storage_tries + .set(trie.retained_storage_tries_count() as f64); guard.store(PreservedSparseTrie::anchored(trie, result.state_root)); deferred } else { diff --git a/crates/engine/tree/src/tree/payload_processor/multiproof.rs b/crates/engine/tree/src/tree/payload_processor/multiproof.rs index 1539258588..2d79c9b3e6 100644 --- a/crates/engine/tree/src/tree/payload_processor/multiproof.rs +++ b/crates/engine/tree/src/tree/payload_processor/multiproof.rs @@ -190,6 +190,11 @@ pub(crate) struct MultiProofTaskMetrics { pub into_trie_for_reuse_duration_histogram: Histogram, /// Time spent waiting for preserved sparse trie cache to become available. pub sparse_trie_cache_wait_duration_histogram: Histogram, + + /// Retained memory of the preserved sparse trie cache in bytes. + pub sparse_trie_retained_memory_bytes: Gauge, + /// Number of storage tries retained in the preserved sparse trie cache. + pub sparse_trie_retained_storage_tries: Gauge, } /// Dispatches work items as a single unit or in chunks based on target size and worker diff --git a/crates/trie/sparse/src/parallel.rs b/crates/trie/sparse/src/parallel.rs index 1cb48b12a7..ac1efebff5 100644 --- a/crates/trie/sparse/src/parallel.rs +++ b/crates/trie/sparse/src/parallel.rs @@ -1153,6 +1153,10 @@ impl SparseTrie for ParallelSparseTrie { upper_count + lower_count } + fn memory_size(&self) -> usize { + self.memory_size() + } + fn prune(&mut self, max_depth: usize) -> usize { #[cfg(feature = "trie-debug")] self.debug_recorder.reset(); diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index 2a4eeafacc..c39ab0e5d3 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -819,6 +819,50 @@ where self.account_rlp_buf.clear(); } + /// Returns a heuristic for the total in-memory size of this state trie in bytes. + /// + /// This aggregates the memory usage of the account trie, all revealed storage tries + /// (including cleared ones retained for allocation reuse), and auxiliary data structures. + pub fn memory_size(&self) -> usize { + let mut size = core::mem::size_of::(); + + size += match &self.state { + RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => { + t.memory_size() + } + RevealableSparseTrie::Blind(None) => 0, + }; + + for trie in self.storage.tries.values() { + size += match trie { + RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => { + t.memory_size() + } + RevealableSparseTrie::Blind(None) => 0, + }; + } + for trie in &self.storage.cleared_tries { + size += match trie { + RevealableSparseTrie::Revealed(t) | RevealableSparseTrie::Blind(Some(t)) => { + t.memory_size() + } + RevealableSparseTrie::Blind(None) => 0, + }; + } + + size += self.revealed_account_paths.capacity() * core::mem::size_of::(); + for paths in self.storage.revealed_paths.values() { + size += paths.capacity() * core::mem::size_of::(); + } + + size + } + + /// Returns the number of storage tries currently retained (active + cleared). + pub fn retained_storage_tries_count(&self) -> usize { + self.storage.tries.len() + self.storage.cleared_tries.len() + } + /// Shrinks the capacity of the sparse trie to the given node and value sizes. /// /// This helps reduce memory usage when the trie has excess capacity. diff --git a/crates/trie/sparse/src/traits.rs b/crates/trie/sparse/src/traits.rs index f1d6ce3609..c0e9eceee1 100644 --- a/crates/trie/sparse/src/traits.rs +++ b/crates/trie/sparse/src/traits.rs @@ -288,6 +288,12 @@ pub trait SparseTrie: Sized + Debug + Send + Sync { /// during pruning. Larger values indicate larger tries that are more valuable to preserve. fn size_hint(&self) -> usize; + /// Returns a heuristic for the in-memory size of this trie in bytes. + /// + /// This is an approximation that accounts for the trie's nodes, values, + /// and auxiliary data structures. + fn memory_size(&self) -> usize; + /// Replaces nodes beyond `max_depth` with hash stubs and removes their descendants. /// /// Depth counts nodes traversed (not nibbles), so extension nodes count as 1 depth