diff --git a/Cargo.lock b/Cargo.lock index 13e49335790b..a60ee2e9f2b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -450,6 +450,36 @@ dependencies = [ "url", ] +[[package]] +name = "attribute-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0053e96dd3bec5b4879c23a138d6ef26f2cb936c9cdc96274ac2b9ed44b5bb54" +dependencies = [ + "attribute-derive-macro", + "derive-where", + "manyhow", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463b53ad0fd5b460af4b1915fe045ff4d946d025fb6c4dc3337752eaa980f71b" +dependencies = [ + "collection_literals", + "interpolator", + "manyhow", + "proc-macro-utils", + "proc-macro2", + "quote", + "quote-use", + "syn 2.0.104", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -1276,6 +1306,12 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "collection_literals" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b3f65b8fb8e88ba339f7d23a390fe1b0896217da05e2a66c584c9b29a91df8" + [[package]] name = "colorchoice" version = "1.0.4" @@ -1834,9 +1870,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.2.0" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373b7c5dbd637569a2cca66e8d66b8c446a1e7bf064ea321d265d7b3dfe7c97e" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", @@ -1917,7 +1953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.104", + "syn 1.0.109", ] [[package]] @@ -1990,6 +2026,17 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "derive-where" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "derive_arbitrary" version = "1.4.1" @@ -2502,9 +2549,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.3.0" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "fickle" @@ -3092,6 +3139,7 @@ dependencies = [ "fvm_shared 2.11.1", "fvm_shared 3.13.1", "fvm_shared 4.7.2", + "get-size2", "gethostname", "git-version", "glob", @@ -3836,6 +3884,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "get-size-derive2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f3cfad7c3e3b1d8d04ef0a1c03576f2d62800803fe1301a4cd262849f2dea" +dependencies = [ + "attribute-derive", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "get-size2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a09c2043819a3def7bfbb4927e7df96aab0da4cfd8824484b22d0c94e84458e" +dependencies = [ + "get-size-derive2", +] + [[package]] name = "gethostname" version = "1.0.2" @@ -4647,6 +4715,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + [[package]] name = "io-uring" version = "0.7.8" @@ -5733,6 +5807,29 @@ dependencies = [ "libc", ] +[[package]] +name = "manyhow" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "manyhow-macros" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + [[package]] name = "matchers" version = "0.1.0" @@ -6927,6 +7024,17 @@ dependencies = [ "toml_edit 0.22.27", ] +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -7142,6 +7250,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quote-use" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9619db1197b497a36178cfc736dc96b271fe918875fbf1344c436a7e93d0321e" +dependencies = [ + "quote", + "quote-use-macros", +] + +[[package]] +name = "quote-use-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "r-efi" version = "5.3.0" diff --git a/Cargo.toml b/Cargo.toml index e7c77be8a576..2dc666d4a386 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ fvm_ipld_encoding = "0.5.3" fvm_shared2 = { package = "fvm_shared", version = "~2.11" } fvm_shared3 = { package = "fvm_shared", version = "~3.13", features = ["proofs"] } fvm_shared4 = { package = "fvm_shared", version = "~4.7", features = ["proofs"] } +get-size2 = { version = "0.5", features = ["derive"] } gethostname = "1" git-version = "0.3" group = "0.13" diff --git a/src/chain_sync/bad_block_cache.rs b/src/chain_sync/bad_block_cache.rs index 8bd78db1f121..8ddd7e373c79 100644 --- a/src/chain_sync/bad_block_cache.rs +++ b/src/chain_sync/bad_block_cache.rs @@ -4,16 +4,16 @@ use std::num::NonZeroUsize; use cid::Cid; -use lru::LruCache; use nonzero_ext::nonzero; -use parking_lot::Mutex; + +use crate::utils::{cache::SizeTrackingLruCache, get_size}; /// Thread-safe cache for tracking bad blocks. /// This cache is checked before validating a block, to ensure no duplicate /// work. #[derive(Debug)] pub struct BadBlockCache { - cache: Mutex>, + cache: SizeTrackingLruCache, } impl Default for BadBlockCache { @@ -25,17 +25,20 @@ impl Default for BadBlockCache { impl BadBlockCache { pub fn new(cap: NonZeroUsize) -> Self { Self { - cache: Mutex::new(LruCache::new(cap)), + cache: SizeTrackingLruCache::new_with_default_metrics_registry( + "bad_block_cache".into(), + cap, + ), } } - pub fn put(&self, c: Cid) { - self.cache.lock().put(c, ()); + pub fn push(&self, c: Cid) { + self.cache.push(c.into(), ()); } /// Returns `Some` if the block CID is in bad block cache. /// This function does not update the head position of the `Cid` key. pub fn peek(&self, c: &Cid) -> Option<()> { - self.cache.lock().peek(c).cloned() + self.cache.peek_cloned(&(*c).into()) } } diff --git a/src/chain_sync/chain_follower.rs b/src/chain_sync/chain_follower.rs index 7b798c756f78..ffa8a347b31a 100644 --- a/src/chain_sync/chain_follower.rs +++ b/src/chain_sync/chain_follower.rs @@ -694,7 +694,7 @@ impl SyncStateMachine { // Mark all blocks in the tipset as bad if let Some(bad_block_cache) = &self.bad_block_cache { for block in tipset.blocks() { - bad_block_cache.put(*block.cid()); + bad_block_cache.push(*block.cid()); } } diff --git a/src/db/blockstore_with_read_cache.rs b/src/db/blockstore_with_read_cache.rs index 2a184d028361..ef64a8757482 100644 --- a/src/db/blockstore_with_read_cache.rs +++ b/src/db/blockstore_with_read_cache.rs @@ -3,70 +3,34 @@ use cid::Cid; use fvm_ipld_blockstore::Blockstore; -use lru::LruCache; -use parking_lot::Mutex; -use std::{ - num::NonZeroUsize, - sync::{ - Arc, - atomic::{self, AtomicUsize}, - }, +use std::sync::{ + Arc, + atomic::{self, AtomicUsize}, }; +use crate::utils::{cache::SizeTrackingLruCache, get_size}; + pub trait BlockstoreReadCache { fn get(&self, k: &Cid) -> Option>; fn put(&self, k: Cid, block: Vec); fn len(&self) -> usize; - - fn size_in_bytes(&self) -> usize; } -pub struct LruBlockstoreReadCache { - lru: Mutex>>, - size_in_bytes: AtomicUsize, -} +pub type LruBlockstoreReadCache = SizeTrackingLruCache>; -impl LruBlockstoreReadCache { - pub fn new(cap: NonZeroUsize) -> Self { - Self { - lru: Mutex::new(LruCache::new(cap)), - size_in_bytes: AtomicUsize::default(), - } - } -} - -impl BlockstoreReadCache for LruBlockstoreReadCache { +impl BlockstoreReadCache for SizeTrackingLruCache> { fn get(&self, k: &Cid) -> Option> { - self.lru.lock().get(k).cloned() + self.get_cloned(&(*k).into()) } fn put(&self, k: Cid, block: Vec) { - let block_size = block.len(); - if let Some((_, old_block)) = self.lru.lock().push(k, block) { - let old_block_size = old_block.len(); - if block_size >= old_block_size { - self.size_in_bytes - .fetch_add(block_size - old_block_size, atomic::Ordering::Relaxed); - } else { - self.size_in_bytes - .fetch_sub(old_block_size - block_size, atomic::Ordering::Relaxed); - } - } else { - self.size_in_bytes.fetch_add( - std::mem::size_of::() + block_size, - atomic::Ordering::Relaxed, - ); - } + self.push(k.into(), block); } fn len(&self) -> usize { - self.lru.lock().len() - } - - fn size_in_bytes(&self) -> usize { - self.size_in_bytes.load(atomic::Ordering::Relaxed) + self.len() } } @@ -83,10 +47,6 @@ impl BlockstoreReadCache for VoidBlockstoreReadCache { fn len(&self) -> usize { 0 } - - fn size_in_bytes(&self) -> usize { - 0 - } } impl BlockstoreReadCache for Arc { @@ -101,10 +61,6 @@ impl BlockstoreReadCache for Arc { fn len(&self) -> usize { self.as_ref().len() } - - fn size_in_bytes(&self) -> usize { - self.as_ref().size_in_bytes() - } } pub trait BlockstoreReadCacheStats { @@ -213,7 +169,10 @@ mod tests { mem_db.put_keyed(&key, &record).unwrap(); records.push((key, record)); } - let cache = Arc::new(LruBlockstoreReadCache::new(CACHE_SIZE.try_into().unwrap())); + let cache = Arc::new(LruBlockstoreReadCache::new_without_metrics_registry( + "test_blockstore_read_cache".into(), + CACHE_SIZE.try_into().unwrap(), + )); let db = BlockstoreWithReadCache::new( mem_db.clone(), cache.clone(), diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index e2dfffaf91a5..acdb1dfa0bc8 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -20,8 +20,8 @@ use crate::{ chain::index::ResolveNullTipset, chain_sync::TipsetValidator, db::{ - BlockstoreReadCache as _, BlockstoreReadCacheStats as _, BlockstoreWithReadCache, - DefaultBlockstoreReadCacheStats, LruBlockstoreReadCache, + BlockstoreReadCacheStats as _, BlockstoreWithReadCache, DefaultBlockstoreReadCacheStats, + LruBlockstoreReadCache, }, libp2p::{NetRPCMethods, NetworkMessage}, lotus_json::HasLotusJson as _, @@ -166,10 +166,11 @@ impl GetPowerTable { ) -> anyhow::Result> { // The RAM overhead on mainnet is ~14MiB const BLOCKSTORE_CACHE_CAP: usize = 65536; - static BLOCKSTORE_CACHE: LazyLock> = LazyLock::new(|| { - Arc::new(LruBlockstoreReadCache::new( + static BLOCKSTORE_CACHE: LazyLock = LazyLock::new(|| { + LruBlockstoreReadCache::new_with_default_metrics_registry( + "get_powertable_cache".into(), BLOCKSTORE_CACHE_CAP.try_into().expect("Infallible"), - )) + ) }); let db = BlockstoreWithReadCache::new( ctx.store_owned(), @@ -452,7 +453,7 @@ impl GetPowerTable { power_entries.sort(); if let Some(stats) = db.stats() { - tracing::debug!(epoch=%ts.epoch(), hit=%stats.hit(), miss=%stats.miss(),cache_len=%BLOCKSTORE_CACHE.len(), cache_size=%human_bytes::human_bytes(BLOCKSTORE_CACHE.size_in_bytes() as f64), "F3.GetPowerTable blockstore read cache"); + tracing::debug!(epoch=%ts.epoch(), hit=%stats.hit(), miss=%stats.miss(),cache_len=%BLOCKSTORE_CACHE.len(), "F3.GetPowerTable blockstore read cache"); } Ok(power_entries) diff --git a/src/rpc/methods/sync.rs b/src/rpc/methods/sync.rs index 84ceb99a14bf..da60d5b6e23a 100644 --- a/src/rpc/methods/sync.rs +++ b/src/rpc/methods/sync.rs @@ -58,7 +58,7 @@ impl RpcMethod<1> for SyncMarkBad { ctx.bad_blocks .as_ref() .context("bad block cache is disabled")? - .put(cid); + .push(cid); Ok(()) } } diff --git a/src/utils/cache/lru.rs b/src/utils/cache/lru.rs new file mode 100644 index 000000000000..a1a2951e3317 --- /dev/null +++ b/src/utils/cache/lru.rs @@ -0,0 +1,183 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +use std::{ + borrow::{Borrow, Cow}, + fmt::Debug, + hash::Hash, + num::NonZeroUsize, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, +}; + +use get_size2::GetSize; +use lru::LruCache; +use parking_lot::RwLock; +use prometheus_client::{ + collector::Collector, + encoding::{DescriptorEncoder, EncodeMetric}, + metrics::gauge::Gauge, + registry::Registry, + registry::Unit, +}; + +use crate::metrics::default_registry; + +pub trait KeyConstraints: + GetSize + Debug + Send + Sync + Hash + PartialEq + Eq + Clone + 'static +{ +} + +impl KeyConstraints for T where + T: GetSize + Debug + Send + Sync + Hash + PartialEq + Eq + Clone + 'static +{ +} + +pub trait ValueConstraints: GetSize + Debug + Send + Sync + Clone + 'static {} + +impl ValueConstraints for T where T: GetSize + Debug + Send + Sync + Clone + 'static {} + +#[derive(Debug, Clone)] +pub struct SizeTrackingLruCache +where + K: KeyConstraints, + V: ValueConstraints, +{ + cache_id: usize, + cache_name: Cow<'static, str>, + cache: Arc>>, +} + +impl SizeTrackingLruCache +where + K: KeyConstraints, + V: ValueConstraints, +{ + pub fn register_metrics(&self, registry: &mut Registry) { + registry.register_collector(Box::new(self.clone())); + } + + pub fn new_without_metrics_registry( + cache_name: Cow<'static, str>, + capacity: NonZeroUsize, + ) -> Self { + static ID_GENERATOR: AtomicUsize = AtomicUsize::new(0); + + Self { + cache_id: ID_GENERATOR.fetch_add(1, Ordering::Relaxed), + cache_name, + cache: Arc::new(RwLock::new(LruCache::new(capacity))), + } + } + + pub fn new_with_metrics_registry( + cache_name: Cow<'static, str>, + capacity: NonZeroUsize, + metrics_registry: &mut Registry, + ) -> Self { + let c = Self::new_without_metrics_registry(cache_name, capacity); + c.register_metrics(metrics_registry); + c + } + + pub fn new_with_default_metrics_registry( + cache_name: Cow<'static, str>, + capacity: NonZeroUsize, + ) -> Self { + Self::new_with_metrics_registry(cache_name, capacity, &mut default_registry()) + } + + pub fn push(&self, k: K, v: V) -> Option<(K, V)> { + self.cache.write().push(k, v) + } + + pub fn get_cloned(&self, k: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.cache.write().get(k).cloned() + } + + pub fn peek_cloned(&self, k: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.cache.write().peek(k).cloned() + } + + pub fn len(&self) -> usize { + self.cache.read().len() + } + + fn size_in_bytes(&self) -> usize { + let mut size = 0_usize; + for (k, v) in self.cache.read().iter() { + size = size + .saturating_add(k.get_size()) + .saturating_add(v.get_size()); + } + size + } +} + +impl Collector for SizeTrackingLruCache +where + K: KeyConstraints, + V: ValueConstraints, +{ + fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> { + { + let size_in_bytes = { + let g: Gauge = Default::default(); + g.set(self.size_in_bytes() as _); + g + }; + let size_metric_name = format!("{}_{}_size", self.cache_name, self.cache_id); + let size_metric_help = format!( + "Size of LruCache {}_{} in bytes", + self.cache_name, self.cache_id + ); + let size_metric_encoder = encoder.encode_descriptor( + &size_metric_name, + &size_metric_help, + Some(&Unit::Bytes), + size_in_bytes.metric_type(), + )?; + size_in_bytes.encode(size_metric_encoder)?; + } + { + let len_metric_name = format!("{}_{}_len", self.cache_name, self.cache_id); + let len_metric_help = + format!("Length of LruCache {}_{}", self.cache_name, self.cache_id); + let len: Gauge = Default::default(); + len.set(self.len() as _); + let len_metric_encoder = encoder.encode_descriptor( + &len_metric_name, + &len_metric_help, + None, + len.metric_type(), + )?; + len.encode(len_metric_encoder)?; + } + { + let cap_metric_name = format!("{}_{}_cap", self.cache_name, self.cache_id); + let cap_metric_help = + format!("Capacity of LruCache {}_{}", self.cache_name, self.cache_id); + let cap: Gauge = Default::default(); + cap.set(self.cache.read().cap().get() as _); + let cap_metric_encoder = encoder.encode_descriptor( + &cap_metric_name, + &cap_metric_help, + None, + cap.metric_type(), + )?; + cap.encode(cap_metric_encoder)?; + } + + Ok(()) + } +} diff --git a/src/utils/cache/mod.rs b/src/utils/cache/mod.rs new file mode 100644 index 000000000000..805a3b31e897 --- /dev/null +++ b/src/utils/cache/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +mod lru; +pub use lru::SizeTrackingLruCache; diff --git a/src/utils/get_size/mod.rs b/src/utils/get_size/mod.rs new file mode 100644 index 000000000000..53f2da889e75 --- /dev/null +++ b/src/utils/get_size/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +use cid::Cid; +use derive_more::{From, Into}; +use get_size2::GetSize; + +#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, From, Into)] +pub struct CidWrapper(pub Cid); +impl GetSize for CidWrapper {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::multihash::MultihashCode; + use fvm_ipld_encoding::DAG_CBOR; + use multihash_derive::MultihashDigest as _; + + #[test] + fn test_cid() { + let cid = Cid::new_v1(DAG_CBOR, MultihashCode::Blake2b256.digest(&[0, 1, 2, 3])); + let wrapper = CidWrapper(cid); + assert_eq!(std::mem::size_of_val(&cid), wrapper.get_size()); + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 782acf9d0070..5f8b75e14b21 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,10 +1,12 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +pub mod cache; pub mod cid; pub mod db; pub mod encoding; pub mod flume; +pub mod get_size; pub mod io; pub mod misc; pub mod monitoring;