diff --git a/Cargo.lock b/Cargo.lock index 5fd50950..721eb7ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -767,6 +767,8 @@ dependencies = [ "rand 0.8.5", "serde_json", "tempfile", + "tikv-jemalloc-ctl", + "tikv-jemallocator", ] [[package]] @@ -4167,6 +4169,37 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "tikv-jemalloc-ctl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21f216790c8df74ce3ab25b534e0718da5a1916719771d3fec23315c99e468b" +dependencies = [ + "libc", + "paste", + "tikv-jemalloc-sys", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "tiny-keccak" version = "2.0.2" diff --git a/light-client-bin/Cargo.toml b/light-client-bin/Cargo.toml index fe1d7cf5..56f8112f 100644 --- a/light-client-bin/Cargo.toml +++ b/light-client-bin/Cargo.toml @@ -32,6 +32,8 @@ rocksdb = { package = "ckb-rocksdb", version = "=0.21.1", features = [ ], default-features = false } env_logger = "0.11" anyhow = "1.0.56" +tikv-jemallocator = { version = "0.6.0", features = ["profiling", "unprefixed_malloc_on_supported_platforms"] } +tikv-jemalloc-ctl = { version = "0.6.0", features = ["stats"] } [dev-dependencies] rand = "0.8" diff --git a/light-client-bin/src/main.rs b/light-client-bin/src/main.rs index 24f93719..c66369dd 100644 --- a/light-client-bin/src/main.rs +++ b/light-client-bin/src/main.rs @@ -9,6 +9,16 @@ mod tests; use cli::AppConfig; use env_logger::{Builder, Env, Target}; +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +#[cfg(not(target_env = "msvc"))] +#[allow(non_upper_case_globals)] +#[export_name = "malloc_conf"] +pub static malloc_conf: &[u8] = + b"prof:true,prof_active:true,lg_prof_sample:19,prof_prefix:/tmp/jeprof\0"; + fn main() -> anyhow::Result<()> { let mut builder = Builder::from_env(Env::default()); builder.target(Target::Stdout); diff --git a/light-client-bin/src/rpc.rs b/light-client-bin/src/rpc.rs index 6522057f..2c7c77be 100644 --- a/light-client-bin/src/rpc.rs +++ b/light-client-bin/src/rpc.rs @@ -110,6 +110,14 @@ pub trait NetRpc { fn get_peers(&self) -> Result>; } +#[rpc(server)] +pub trait DebugRpc { + /// Dumps jemalloc profiling data to a file + /// Returns the path to the generated profile file + #[rpc(name = "jemalloc_profiling_dump")] + fn jemalloc_profiling_dump(&self) -> Result; +} + pub struct BlockFilterRpcImpl { pub(crate) swc: StorageWithChainData, } @@ -129,6 +137,8 @@ pub struct NetRpcImpl { peers: Arc, } +pub struct DebugRpcImpl; + impl BlockFilterRpc for BlockFilterRpcImpl { fn set_scripts( &self, @@ -802,6 +812,47 @@ impl NetRpc for NetRpcImpl { } } +impl DebugRpc for DebugRpcImpl { + fn jemalloc_profiling_dump(&self) -> Result { + #[cfg(not(target_env = "msvc"))] + { + use std::ffi::CString; + use tikv_jemalloc_ctl::{epoch, raw}; + + // Trigger an epoch update to ensure current statistics + epoch::mib() + .map_err(|_| Error::invalid_params("Failed to get epoch mib"))? + .advance() + .map_err(|_| Error::invalid_params("Failed to advance epoch"))?; + + // Generate a unique filename with timestamp + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + let filename = format!("/tmp/jeprof.{}.{}.heap", std::process::id(), timestamp); + + // Dump the heap profile using prof.dump + let c_filename = CString::new(filename.clone()) + .map_err(|_| Error::invalid_params("Invalid filename"))?; + + let prof_dump_name = b"prof.dump\0"; + unsafe { + raw::write(prof_dump_name, c_filename.as_ptr() as *const _) + .map_err(|_| Error::invalid_params("Failed to dump heap profile"))?; + } + + Ok(filename) + } + #[cfg(target_env = "msvc")] + { + Err(Error::invalid_params( + "Jemalloc profiling is not available on MSVC builds", + )) + } + } +} + impl TransactionRpc for TransactionRpcImpl { fn send_transaction(&self, tx: Transaction) -> Result { let tx: packed::Transaction = tx.into(); @@ -983,10 +1034,12 @@ impl Service { network_controller, peers, }; + let debug_rpc_impl = DebugRpcImpl; io_handler.extend_with(block_filter_rpc_impl.to_delegate()); io_handler.extend_with(chain_rpc_impl.to_delegate()); io_handler.extend_with(transaction_rpc_impl.to_delegate()); io_handler.extend_with(net_rpc_impl.to_delegate()); + io_handler.extend_with(debug_rpc_impl.to_delegate()); ServerBuilder::new(io_handler) .cors(DomainsValidation::AllowOnly(vec![