diff --git a/.gitignore b/.gitignore index 9376efc768e..3335ffe5e3b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ perf.data* *.tar.gz /bin genesis.ssz +jeprof* diff --git a/Cargo.lock b/Cargo.lock index 59a7ea7d9f2..851daabbf5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2346,6 +2346,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -2857,7 +2863,9 @@ dependencies = [ "fork_choice", "futures 0.3.13", "hex", + "jemalloc-sys", "lazy_static", + "libc", "lighthouse_metrics", "lighthouse_version", "network", @@ -3233,6 +3241,38 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +[[package]] +name = "jemalloc-ctl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c502a5ff9dd2924f1ed32ba96e3b65735d837b4bfd978d3161b1702e66aca4b7" +dependencies = [ + "jemalloc-sys", + "libc", + "paste", +] + +[[package]] +name = "jemalloc-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45" +dependencies = [ + "cc", + "fs_extra", + "libc", +] + +[[package]] +name = "jemallocator" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69" +dependencies = [ + "jemalloc-sys", + "libc", +] + [[package]] name = "js-sys" version = "0.3.48" @@ -3721,7 +3761,10 @@ dependencies = [ "environment", "eth2_network_config", "futures 0.3.13", + "jemalloc-sys", + "jemallocator", "lazy_static", + "libc", "lighthouse_metrics", "lighthouse_version", "logging", @@ -4468,6 +4511,25 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", +] + [[package]] name = "pbkdf2" version = "0.4.0" @@ -7214,6 +7276,7 @@ dependencies = [ "beacon_chain", "eth2", "headers", + "jemalloc-ctl", "lazy_static", "lighthouse_metrics", "safe_arith", diff --git a/Cargo.toml b/Cargo.toml index 8fd60b5ab64..311896fce08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,3 +91,6 @@ eth2_ssz = { path = "consensus/ssz" } eth2_ssz_derive = { path = "consensus/ssz_derive" } eth2_ssz_types = { path = "consensus/ssz_types" } eth2_hashing = { path = "crypto/eth2_hashing" } + +#[profile.release] +# debug = true diff --git a/Makefile b/Makefile index d843e81cd6f..a576ab803ad 100644 --- a/Makefile +++ b/Makefile @@ -12,14 +12,20 @@ BUILD_PATH_AARCH64 = "target/$(AARCH64_TAG)/release" PINNED_NIGHTLY ?= nightly +ifeq ($(PROFILING), true) + PROFILING_FEATURE=jemalloc-profiling +else + PROFILING_FEATURE= +endif + # Builds the Lighthouse binary in release (optimized). # # Binaries will most likely be found in `./target/release` install: ifeq ($(PORTABLE), true) - cargo install --path lighthouse --force --locked --features portable + cargo install --path lighthouse --force --locked --features $(PROFILING_FEATURE),portable else - cargo install --path lighthouse --force --locked + cargo install --path lighthouse --force --locked --features $(PROFILING_FEATURE) endif # Builds the lcli binary in release (optimized). diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index aa8e648a334..7e4c33c3f10 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -13,6 +13,8 @@ node_test_rig = { path = "../testing/node_test_rig" } [features] write_ssz_files = ["beacon_chain/write_ssz_files"] # Writes debugging .ssz files to /tmp during block processing. +# Use the system allocator instead of jemalloc. This replicates default Rust behaviour. +sysalloc = ["client/sysalloc"] [dependencies] eth2_config = { path = "../common/eth2_config" } diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 0386958579c..88c769db567 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -690,6 +690,7 @@ impl SignatureVerifiedBlock { /// Returns an error if the block is invalid, or if the block was unable to be verified. pub fn new( block: SignedBeaconBlock, + block_root: Hash256, chain: &BeaconChain, ) -> Result> { let (mut parent, block) = load_parent(block, chain)?; @@ -697,8 +698,6 @@ impl SignatureVerifiedBlock { // Reject any block that exceeds our limit on skipped slots. check_block_skip_slots(chain, parent.beacon_block.slot(), &block.message)?; - let block_root = get_block_root(&block); - let state = cheap_state_advance_to_obtain_committees( &mut parent.pre_state, parent.beacon_state_root, @@ -726,10 +725,11 @@ impl SignatureVerifiedBlock { /// As for `new` above but producing `BlockSlashInfo`. pub fn check_slashable( block: SignedBeaconBlock, + block_root: Hash256, chain: &BeaconChain, ) -> Result>> { let header = block.signed_block_header(); - Self::new(block, chain).map_err(|e| BlockSlashInfo::from_early_error(header, e)) + Self::new(block, block_root, chain).map_err(|e| BlockSlashInfo::from_early_error(header, e)) } /// Finishes signature verification on the provided `GossipVerifedBlock`. Does not re-verify @@ -814,7 +814,11 @@ impl IntoFullyVerifiedBlock for SignedBeaconBlock, ) -> Result, BlockSlashInfo>> { - SignatureVerifiedBlock::check_slashable(self, chain)? + // Perform an early check to prevent wasting time on irrelevant blocks. + let block_root = check_block_relevancy(&self, None, chain) + .map_err(|e| BlockSlashInfo::SignatureValid(self.signed_block_header(), e))?; + + SignatureVerifiedBlock::check_slashable(self, block_root, chain)? .into_fully_verified_block_slashable(chain) } diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 28dea4055d6..1c40ca67d74 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -44,3 +44,7 @@ http_api = { path = "../http_api" } http_metrics = { path = "../http_metrics" } slasher = { path = "../../slasher" } slasher_service = { path = "../../slasher/service" } + +[features] +# Use the system allocator instead of jemalloc. This replicates default Rust behaviour. +sysalloc = ["http_metrics/sysalloc"] diff --git a/beacon_node/http_api/Cargo.toml b/beacon_node/http_api/Cargo.toml index 4d5d88d405d..d0cba8397b1 100644 --- a/beacon_node/http_api/Cargo.toml +++ b/beacon_node/http_api/Cargo.toml @@ -4,6 +4,10 @@ version = "0.1.0" authors = ["Paul Hauner "] edition = "2018" +[features] +# Use the system allocator instead of jemalloc. This replicates default Rust behaviour. +sysalloc = [] + [dependencies] warp = { git = "https://github.com/paulhauner/warp ", branch = "cors-wildcard" } serde = { version = "1.0.116", features = ["derive"] } @@ -29,6 +33,8 @@ slot_clock = { path = "../../common/slot_clock" } eth2_ssz = { path = "../../consensus/ssz" } bs58 = "0.4.0" futures = "0.3.8" +jemalloc-sys = { version = "0.3.2" } +libc = { version = "0.2.86" } [dev-dependencies] store = { path = "../store" } diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 653820367c1..c4ad88c0195 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -2127,6 +2127,49 @@ pub fn serve( }) }); + // GET lighthouse/jemalloc_prof_dump + let get_lighthouse_jemalloc_prof_dump = warp::path("lighthouse") + .and(warp::path("jemalloc_prof_dump")) + .and(warp::path::end()) + .and_then(|| { + blocking_json_task(move || { + // This endpoint has no purpose without jemalloc. + #[cfg(feature = "sysalloc")] + { + return Err::(warp_utils::reject::custom_not_found( + "jemalloc not enabled, see the sysalloc compile feature".to_string(), + )); + } + + // When using jemalloc, create profile dump as per: + // + // https://github.com/jemalloc/jemalloc/wiki/Use-Case%3A-Heap-Profiling + #[cfg(not(feature = "sysalloc"))] + { + let result = unsafe { + let opt_name = "prof.dump"; + let opt_c_name = std::ffi::CString::new(opt_name).unwrap(); + jemalloc_sys::mallctl( + opt_c_name.as_ptr(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::mem::size_of::<*mut std::ffi::c_void>(), + ) + }; + + if result == 0 { + Ok("profile dumped") + } else { + Err(warp_utils::reject::custom_server_error(format!( + "profiling dump failed with code {}, is the jemalloc-profiling feature enabled?", + result + ))) + } + } + }) + }); + let get_events = eth1_v1 .and(warp::path("events")) .and(warp::path::end()) @@ -2233,6 +2276,7 @@ pub fn serve( .or(get_lighthouse_eth1_deposit_cache.boxed()) .or(get_lighthouse_beacon_states_ssz.boxed()) .or(get_lighthouse_staking.boxed()) + .or(get_lighthouse_jemalloc_prof_dump.boxed()) .or(get_events.boxed()), ) .or(warp::post().and( diff --git a/beacon_node/http_metrics/Cargo.toml b/beacon_node/http_metrics/Cargo.toml index 78394874648..77c61a4fdeb 100644 --- a/beacon_node/http_metrics/Cargo.toml +++ b/beacon_node/http_metrics/Cargo.toml @@ -26,3 +26,7 @@ tokio = { version = "1.1.0", features = ["sync"] } reqwest = { version = "0.11.0", features = ["json"] } environment = { path = "../../lighthouse/environment" } types = { path = "../../consensus/types" } + +[features] +# Use the system allocator instead of jemalloc. This replicates default Rust behaviour. +sysalloc = ["warp_utils/sysalloc"] diff --git a/common/warp_utils/Cargo.toml b/common/warp_utils/Cargo.toml index 7b2ab637398..e5729db382c 100644 --- a/common/warp_utils/Cargo.toml +++ b/common/warp_utils/Cargo.toml @@ -18,3 +18,8 @@ tokio = { version = "1.1.0", features = ["sync"] } headers = "0.3.2" lighthouse_metrics = { path = "../lighthouse_metrics" } lazy_static = "1.4.0" +jemalloc-ctl = { version = "0.3.3" } + +[features] +# Use the system allocator instead of jemalloc. This replicates default Rust behaviour. +sysalloc = [] diff --git a/common/warp_utils/src/metrics.rs b/common/warp_utils/src/metrics.rs index dc42aa6b357..0ae2f32abf1 100644 --- a/common/warp_utils/src/metrics.rs +++ b/common/warp_utils/src/metrics.rs @@ -1,6 +1,9 @@ use eth2::lighthouse::Health; use lighthouse_metrics::*; +#[cfg(not(feature = "sysalloc"))] +use jemalloc_ctl::{arenas, epoch, stats}; + lazy_static::lazy_static! { pub static ref PROCESS_NUM_THREADS: Result = try_create_int_gauge( "process_num_threads", @@ -34,6 +37,24 @@ lazy_static::lazy_static! { try_create_float_gauge("system_loadavg_5", "Loadavg over 5 minutes"); pub static ref SYSTEM_LOADAVG_15: Result = try_create_float_gauge("system_loadavg_15", "Loadavg over 15 minutes"); + + /* + * jemalloc + */ + pub static ref JEMALLOC_RESIDENT: Result = + try_create_int_gauge("jemalloc_resident", "Total number of bytes in physically resident data pages mapped by the allocator."); + pub static ref JEMALLOC_ALLOCATED: Result = + try_create_int_gauge("jemalloc_allocated", "Total number of bytes allocated by the application."); + pub static ref JEMALLOC_MAPPED: Result = + try_create_int_gauge("jemalloc_mapped", "Total number of bytes in active extents mapped by the allocator."); + pub static ref JEMALLOC_METADATA: Result = + try_create_int_gauge("jemalloc_metadata", "Total number of bytes dedicated to jemalloc metadata."); + pub static ref JEMALLOC_RETAINED: Result = + try_create_int_gauge("jemalloc_retained", "Total number of bytes in virtual memory mappings that were retained rather than being returned to the operating system."); + pub static ref JEMALLOC_ACTIVE: Result = + try_create_int_gauge("jemalloc_active", "Total number of bytes in active pages allocated by the application."); + pub static ref JEMALLOC_ARENAS: Result = + try_create_int_gauge("jemalloc_arenas", "Current limit on the number of arenas."); } pub fn scrape_health_metrics() { @@ -58,4 +79,38 @@ pub fn scrape_health_metrics() { set_float_gauge(&SYSTEM_LOADAVG_5, health.sys_loadavg_5); set_float_gauge(&SYSTEM_LOADAVG_15, health.sys_loadavg_15); } + + scrape_jemalloc_metrics(); +} + +#[cfg(not(feature = "sysalloc"))] +pub fn scrape_jemalloc_metrics() { + if epoch::advance().is_ok() { + if let Ok(allocated) = stats::allocated::read() { + set_gauge(&JEMALLOC_ALLOCATED, allocated as i64); + } + if let Ok(resident) = stats::resident::read() { + set_gauge(&JEMALLOC_RESIDENT, resident as i64); + } + if let Ok(mapped) = stats::mapped::read() { + set_gauge(&JEMALLOC_MAPPED, mapped as i64); + } + if let Ok(metadata) = stats::metadata::read() { + set_gauge(&JEMALLOC_METADATA, metadata as i64); + } + if let Ok(retained) = stats::retained::read() { + set_gauge(&JEMALLOC_RETAINED, retained as i64); + } + if let Ok(active) = stats::active::read() { + set_gauge(&JEMALLOC_ACTIVE, active as i64); + } + if let Ok(narenas) = arenas::narenas::read() { + set_gauge(&JEMALLOC_ARENAS, narenas as i64); + } + } +} + +#[cfg(feature = "sysalloc")] +pub fn scrape_jemalloc_metrics() { + // NO OP } diff --git a/lighthouse/Cargo.toml b/lighthouse/Cargo.toml index a599717c8a6..525b409b322 100644 --- a/lighthouse/Cargo.toml +++ b/lighthouse/Cargo.toml @@ -17,6 +17,10 @@ milagro = ["bls/milagro"] spec-minimal = [] # Support spec v0.12 (used by Medalla testnet). spec-v12 = [] +# Use the system allocator instead of jemalloc. This replicates default Rust behaviour. +sysalloc = ["beacon_node/sysalloc"] +# Jemalloc profiling +jemalloc-profiling = ["jemallocator/profiling"] [dependencies] beacon_node = { "path" = "../beacon_node" } @@ -43,6 +47,9 @@ account_utils = { path = "../common/account_utils" } remote_signer = { "path" = "../remote_signer" } lighthouse_metrics = { path = "../common/lighthouse_metrics" } lazy_static = "1.4.0" +jemallocator = { version = "0.3.2" } +jemalloc-sys = { version = "0.3.2" } +libc = { version = "0.2.86" } [dev-dependencies] tempfile = "3.1.0" diff --git a/lighthouse/src/main.rs b/lighthouse/src/main.rs index acbc80a8468..8b3581ac6a8 100644 --- a/lighthouse/src/main.rs +++ b/lighthouse/src/main.rs @@ -12,6 +12,45 @@ use std::process::exit; use types::{EthSpec, EthSpecId}; use validator_client::ProductionValidatorClient; +/// Global allocator +#[cfg(not(feature = "sysalloc"))] +#[global_allocator] +pub static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; + +#[cfg(not(feature = "sysalloc"))] +union U { + x: &'static u8, + y: &'static libc::c_char, +} + +#[cfg(not(feature = "jemalloc-profiling"))] +const JEMALLOC_CONFIG: &[u8] = b"narenas:1,\ + lg_tcache_max:13,\ + dirty_decay_ms:1000,\ + muzzy_decay_ms:0,\ + retain:false,\ + \0"; + +#[cfg(feature = "jemalloc-profiling")] +const JEMALLOC_CONFIG: &[u8] = b"narenas:1,\ + lg_tcache_max:13,\ + dirty_decay_ms:1000,\ + muzzy_decay_ms:0,\ + retain:false,\ + prof:true,\ + prof_prefix:jeprof.out\ + \0"; + +#[cfg(not(feature = "sysalloc"))] +#[allow(non_upper_case_globals)] +#[export_name = "_rjem_malloc_conf"] +pub static malloc_conf: Option<&'static libc::c_char> = Some(unsafe { + U { + x: &JEMALLOC_CONFIG[0], + } + .y +}); + pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml"; fn bls_library_name() -> &'static str {