diff --git a/Cargo.lock b/Cargo.lock index 2e7fbc720d4231..9decfc0bf1f8c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,6 +356,7 @@ dependencies = [ "solana-inline-spl", "solana-ledger", "solana-logger", + "solana-memory-management", "solana-metrics", "solana-net-utils", "solana-perf", @@ -809,7 +810,7 @@ checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -1017,7 +1018,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -1167,7 +1168,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -1298,7 +1299,7 @@ checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -1449,7 +1450,7 @@ checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -1942,7 +1943,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -1966,7 +1967,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -1977,7 +1978,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -2039,7 +2040,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -2050,7 +2051,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -2174,7 +2175,7 @@ checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -2280,7 +2281,7 @@ checksum = "03cdc46ec28bd728e67540c528013c6a10eb69a02eb31078a1bda695438cbfb8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -2592,7 +2593,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -3291,7 +3292,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -3878,9 +3879,9 @@ checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" [[package]] name = "memchr" -version = "2.6.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -4147,7 +4148,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -4219,7 +4220,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -4302,7 +4303,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -4853,7 +4854,7 @@ checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -5654,7 +5655,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -5720,7 +5721,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -5770,7 +5771,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -7646,7 +7647,7 @@ version = "2.2.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -8205,6 +8206,14 @@ version = "2.2.0" [[package]] name = "solana-memory-management" version = "2.2.0" +dependencies = [ + "arrayvec", + "libc", + "log", + "memchr", + "solana-metrics", + "tikv-jemallocator", +] [[package]] name = "solana-merkle-root-bench" @@ -8402,7 +8411,7 @@ version = "2.2.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", "toml 0.8.12", ] @@ -9424,7 +9433,7 @@ dependencies = [ "bs58", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -10969,7 +10978,7 @@ checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -10981,7 +10990,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.96", + "syn 2.0.95", "thiserror 1.0.69", ] @@ -11069,7 +11078,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -11378,9 +11387,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.96" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", @@ -11419,7 +11428,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -11588,7 +11597,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -11600,7 +11609,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", "test-case-core", ] @@ -11645,7 +11654,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -11656,7 +11665,7 @@ checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -11816,7 +11825,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -12076,7 +12085,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -12404,7 +12413,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", "wasm-bindgen-shared", ] @@ -12438,7 +12447,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -12834,7 +12843,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", "synstructure 0.13.1", ] @@ -12855,7 +12864,7 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -12875,7 +12884,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", "synstructure 0.13.1", ] @@ -12896,7 +12905,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] @@ -12918,7 +12927,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.95", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 69a0caa4942c8c..3d462100c764ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -452,6 +452,7 @@ soketto = "0.7" solana-account = { path = "sdk/account", version = "=2.2.0" } solana-account-decoder = { path = "account-decoder", version = "=2.2.0" } solana-account-decoder-client-types = { path = "account-decoder-client-types", version = "=2.2.0" } +solana-memory-management = { path = "memory-management", version = "=2.2.0" } solana-account-info = { path = "sdk/account-info", version = "=2.2.0" } solana-accounts-db = { path = "accounts-db", version = "=2.2.0" } solana-address-lookup-table-interface = { path = "sdk/address-lookup-table-interface", version = "=2.2.0" } diff --git a/memory-management/Cargo.toml b/memory-management/Cargo.toml index bac18aaea0e791..26de3994146c37 100644 --- a/memory-management/Cargo.toml +++ b/memory-management/Cargo.toml @@ -7,3 +7,13 @@ repository = { workspace = true } homepage = { workspace = true } license = { workspace = true } edition = { workspace = true } + +[dependencies] +arrayvec = { workspace = true } +libc = { workspace = true } +log = { workspace = true } +memchr = "2.7.4" +solana-metrics = { workspace = true } + +[target.'cfg(not(any(target_env = "msvc", target_os = "freebsd")))'.dependencies] +jemallocator = { workspace = true } diff --git a/memory-management/src/jemalloc_monitor.rs b/memory-management/src/jemalloc_monitor.rs new file mode 100644 index 00000000000000..9038031e1d402e --- /dev/null +++ b/memory-management/src/jemalloc_monitor.rs @@ -0,0 +1,217 @@ +#![cfg(not(any(target_env = "msvc", target_os = "freebsd")))] +use { + jemallocator::Jemalloc, + std::{ + alloc::{GlobalAlloc, Layout}, + cell::RefCell, + sync::{ + atomic::{AtomicUsize, Ordering}, + RwLock, + }, + }, +}; + +const NAME_LEN: usize = 16; +type ThreadName = arrayvec::ArrayVec; + +static SELF: JemWrapStats = JemWrapStats { + named_thread_stats: RwLock::new(None), + unnamed_thread_stats: Counters::new(), + process_stats: Counters::new(), +}; + +#[derive(Debug)] +pub struct Counters { + allocations_total: AtomicUsize, + deallocations_total: AtomicUsize, + bytes_allocated_total: AtomicUsize, + bytes_deallocated_total: AtomicUsize, +} + +pub struct CountersView { + pub allocations_total: usize, + pub deallocations_total: usize, + pub bytes_allocated_total: usize, + pub bytes_deallocated_total: usize, +} + +impl Default for Counters { + fn default() -> Self { + Self::new() + } +} + +impl Counters { + pub fn view(&self) -> CountersView { + CountersView { + allocations_total: self.allocations_total.load(Ordering::Relaxed), + deallocations_total: self.deallocations_total.load(Ordering::Relaxed), + bytes_allocated_total: self.bytes_allocated_total.load(Ordering::Relaxed), + bytes_deallocated_total: self.bytes_deallocated_total.load(Ordering::Relaxed), + } + } + + const fn new() -> Self { + Self { + allocations_total: AtomicUsize::new(0), + deallocations_total: AtomicUsize::new(0), + bytes_allocated_total: AtomicUsize::new(0), + bytes_deallocated_total: AtomicUsize::new(0), + } + } +} + +impl Counters { + pub fn alloc(&self, size: usize) { + self.bytes_allocated_total + .fetch_add(size, Ordering::Relaxed); + self.allocations_total.fetch_add(1, Ordering::Relaxed); + } + pub fn dealloc(&self, size: usize) { + self.bytes_deallocated_total + .fetch_add(size, Ordering::Relaxed); + self.deallocations_total.fetch_add(1, Ordering::Relaxed); + } +} + +#[repr(C, align(4096))] +pub struct JemWrapAllocator { + jemalloc: Jemalloc, +} + +impl JemWrapAllocator { + pub const fn new() -> Self { + Self { jemalloc: Jemalloc } + } +} + +impl Default for JemWrapAllocator { + fn default() -> Self { + Self::new() + } +} + +struct JemWrapStats { + pub named_thread_stats: RwLock>, + pub unnamed_thread_stats: Counters, + pub process_stats: Counters, +} + +pub fn view_allocations(f: impl FnOnce(&MemPoolStats)) { + let lock_guard = &SELF.named_thread_stats.read().unwrap(); + if let Some(stats) = lock_guard.as_ref() { + f(stats); + } +} +pub fn view_global_allocations() -> (CountersView, CountersView) { + (SELF.unnamed_thread_stats.view(), SELF.process_stats.view()) +} + +#[derive(Debug, Default)] +pub struct MemPoolStats { + pub data: Vec<(ThreadName, Counters)>, +} + +impl MemPoolStats { + pub fn add(&mut self, prefix: &str) { + let key: ThreadName = prefix + .as_bytes() + .try_into() + .unwrap_or_else(|_| panic!("Prefix can not be over {} bytes long", NAME_LEN)); + + self.data.push((key, Counters::default())); + // keep data sorted with longest prefixes first (this avoids short-circuiting) + // no need for this to be efficient since we do not run time in a tight loop and vec is typically short + self.data.sort_unstable_by(|a, b| b.0.len().cmp(&a.0.len())); + } +} + +pub fn init_allocator(mps: MemPoolStats) { + SELF.named_thread_stats.write().unwrap().replace(mps); +} + +pub fn deinit_allocator() -> MemPoolStats { + SELF.named_thread_stats.write().unwrap().take().unwrap() +} + +unsafe impl Sync for JemWrapAllocator {} + +unsafe impl GlobalAlloc for JemWrapAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let alloc = self.jemalloc.alloc(layout); + if alloc.is_null() { + return alloc; + } + SELF.process_stats.alloc(layout.size()); + if let Ok(stats) = SELF.named_thread_stats.try_read() { + if let Some(stats) = stats.as_ref() { + if let Some(stats) = match_thread_name_safely(stats, true) { + stats.alloc(layout.size()); + } + } + } else { + SELF.unnamed_thread_stats.alloc(layout.size()); + } + alloc + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.jemalloc.dealloc(ptr, layout); + if ptr.is_null() { + return; + } + SELF.process_stats.dealloc(layout.size()); + if let Ok(stats) = SELF.named_thread_stats.try_read() { + if let Some(stats) = stats.as_ref() { + if let Some(stats) = match_thread_name_safely(stats, false) { + stats.dealloc(layout.size()); + } + } + } else { + SELF.unnamed_thread_stats.dealloc(layout.size()); + } + } +} + +thread_local! ( + static THREAD_NAME: RefCell = RefCell::new(ThreadName::new()) +); + +fn match_thread_name_safely(stats: &MemPoolStats, insert_if_missing: bool) -> Option<&Counters> { + let name: Option = THREAD_NAME + .try_with(|v| { + let mut name = v.borrow_mut(); + if name.is_empty() { + if insert_if_missing { + unsafe { + name.set_len(NAME_LEN); + let res = libc::pthread_getname_np( + libc::pthread_self(), + name.as_mut_ptr() as *mut i8, + name.capacity(), + ); + if res == 0 { + let name_len = memchr::memchr(0, &name).unwrap_or(name.len()); + name.set_len(name_len); + } + } + } else { + return None; + } + } + Some(name.clone()) + }) + .ok() + .flatten(); + match name { + Some(name) => { + for (prefix, stats) in stats.data.iter() { + if !name.starts_with(prefix) { + continue; + } + return Some(stats); + } + None + } + None => None, + } +} diff --git a/memory-management/src/jemalloc_monitor_metrics.rs b/memory-management/src/jemalloc_monitor_metrics.rs new file mode 100644 index 00000000000000..e4b21175daab00 --- /dev/null +++ b/memory-management/src/jemalloc_monitor_metrics.rs @@ -0,0 +1,90 @@ +#![cfg(not(any(target_env = "msvc", target_os = "freebsd")))] +use { + crate::jemalloc_monitor::*, log::Level, solana_metrics::datapoint::DataPoint, + std::time::Duration, +}; + +fn watcher_thread() { + fn extend_lifetime<'b>(r: &'b str) -> &'static str { + // SAFETY: it is safe to extend lifetimes here since we can never write any metrics beyond the point + // where allocator is deinitialized. The function is private so can not be called from outside + // Metrics can not work with non-static strings due to design limitations. + unsafe { std::mem::transmute::<&'b str, &'static str>(r) } + } + let mut exit = false; + while !exit { + view_allocations(|stats| { + if stats.data.is_empty() { + exit = true; + } + let mut datapoint = DataPoint::new("MemoryBytesAllocatedTotal"); + for (name, counters) in stats.data.iter() { + let s = counters.view(); + let name = extend_lifetime(std::str::from_utf8(name).unwrap()); + datapoint.add_field_i64(name, s.bytes_allocated_total as i64); + } + solana_metrics::submit(datapoint, Level::Info); + let mut datapoint = DataPoint::new("MemoryBytesDeallocated"); + for (name, counters) in stats.data.iter() { + let s = counters.view(); + let name = extend_lifetime(std::str::from_utf8(name).unwrap()); + datapoint.add_field_i64(name, s.bytes_deallocated_total as i64); + } + solana_metrics::submit(datapoint, Level::Info); + }); + let (cunnamed, _cproc) = view_global_allocations(); + let mut datapoint = solana_metrics::datapoint::DataPoint::new("MemoryUnnamedThreads"); + datapoint.add_field_i64( + "bytes_allocated_total", + cunnamed.bytes_allocated_total as i64, + ); + datapoint.add_field_i64( + "bytes_deallocated_total", + cunnamed.bytes_deallocated_total as i64, + ); + solana_metrics::submit(datapoint, Level::Info); + + std::thread::sleep(Duration::from_millis(1000)); + } +} + +//Agave specific helper to watch for memory usage +pub fn setup_watch_memory_usage() { + let mut mps = MemPoolStats::default(); + // this list is brittle but there does not appear to be a better way + // Order of entries matters here, as first matching prefix will be used + // So solGossip will match solGossipConsume as well + for thread in [ + "solPohTickProd", + "solSigVerTpuVot", + "solRcvrGossip", + "solSigVerTpu", + "solClusterSlots", + "solGossipCons", + "solGossipWork", + "solGossip", + "solRepairSvc", + "solRepairListen", + "solReplayTx", + "solReplayFork", + "solRayonGlob", + "solSvrfyShred", + "solSigVerify", + "solRetransmit", + "solRunGossip", + "solWinInsert", + "solAccountsLo", + "solAccounts", + "solAcctHash", + "solQuicClientRt", + "solQuicTVo", + "solQuicTpu", + "solQuicTpuFwd", + "solRepairQuic", + "solTurbineQuic", + ] { + mps.add(thread); + } + init_allocator(mps); + std::thread::spawn(watcher_thread); +} diff --git a/memory-management/src/lib.rs b/memory-management/src/lib.rs index 95caf247cd5bb0..d1479da2dd71c6 100644 --- a/memory-management/src/lib.rs +++ b/memory-management/src/lib.rs @@ -1,6 +1,11 @@ #![deny(clippy::arithmetic_side_effects)] pub mod aligned_memory; +#[cfg(not(any(target_env = "msvc", target_os = "freebsd")))] +pub mod jemalloc_monitor; +#[cfg(not(any(target_env = "msvc", target_os = "freebsd")))] +pub mod jemalloc_monitor_metrics; + /// Returns true if `ptr` is aligned to `align`. pub fn is_memory_aligned(ptr: usize, align: usize) -> bool { ptr.checked_rem(align) diff --git a/memory-management/tests/jemalloc_wrap.rs b/memory-management/tests/jemalloc_wrap.rs new file mode 100644 index 00000000000000..c2a75d096dc65c --- /dev/null +++ b/memory-management/tests/jemalloc_wrap.rs @@ -0,0 +1,48 @@ +#![cfg(not(any(target_env = "msvc", target_os = "freebsd")))] +use {solana_memory_management::jemalloc_monitor::*, std::time::Duration}; + +#[global_allocator] +static GLOBAL_ALLOC_WRAP: JemWrapAllocator = JemWrapAllocator::new(); + +pub fn print_allocations() { + view_allocations(|stats| { + println!("allocated so far: {:?}", stats); + }); +} + +// This is not really a test as such, more a canary to check if the logic works and does not deadlock. +// None of the reported data is "exact science" +fn main() { + let mut mps = MemPoolStats::default(); + mps.add("Foo"); + mps.add("Boo"); + init_allocator(mps); + + let _s = "allocating a string!".to_owned(); + print_allocations(); + + let jh1 = std::thread::Builder::new() + .name("Foo thread 1".to_string()) + .spawn(|| { + let _s2 = "allocating a string!".to_owned(); + let _s3 = "allocating a string!".to_owned(); + let _s4 = "allocating a string!".to_owned(); + let jh2 = std::thread::Builder::new() + .name("Boo thread 1".to_string()) + .spawn(|| { + let _s2 = "allocating a string!".to_owned(); + let _s3 = "allocating a string!".to_owned(); + let _s4 = "allocating a string!".to_owned(); + std::thread::sleep(Duration::from_millis(200)); + }) + .unwrap(); + std::thread::sleep(Duration::from_millis(200)); + jh2.join().unwrap(); + }) + .unwrap(); + std::thread::sleep(Duration::from_millis(100)); + print_allocations(); + jh1.join().unwrap(); + print_allocations(); + deinit_allocator(); +} diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 2cc576844a028d..924d025bf723a5 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -166,6 +166,7 @@ dependencies = [ "solana-gossip", "solana-ledger", "solana-logger", + "solana-memory-management", "solana-metrics", "solana-net-utils", "solana-perf", @@ -3111,9 +3112,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.6.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -6393,6 +6394,18 @@ dependencies = [ name = "solana-measure" version = "2.2.0" +[[package]] +name = "solana-memory-management" +version = "2.2.0" +dependencies = [ + "arrayvec", + "libc", + "log", + "memchr", + "solana-metrics", + "tikv-jemallocator", +] + [[package]] name = "solana-merkle-tree" version = "2.2.0" diff --git a/validator/Cargo.toml b/validator/Cargo.toml index 4cd77b0b1c807a..606b427d88499e 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -79,6 +79,7 @@ tempfile = { workspace = true } [target.'cfg(not(any(target_env = "msvc", target_os = "freebsd")))'.dependencies] jemallocator = { workspace = true } +solana-memory-management = { workspace = true } [target."cfg(unix)".dependencies] libc = { workspace = true } @@ -86,3 +87,6 @@ signal-hook = { workspace = true } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] + +[features] +alloc_monitor = [] diff --git a/validator/src/main.rs b/validator/src/main.rs index 03abae853aa9f2..080daffb65174b 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -1,6 +1,15 @@ #![allow(clippy::arithmetic_side_effects)] + #[cfg(not(any(target_env = "msvc", target_os = "freebsd")))] use jemallocator::Jemalloc; +#[cfg(all( + feature = "alloc_monitor", + not(any(target_env = "msvc", target_os = "freebsd")) +))] +use solana_memory_management::{ + jemalloc_monitor::deinit_allocator, jemalloc_monitor::JemWrapAllocator, + jemalloc_monitor_metrics::setup_watch_memory_usage, +}; use { agave_validator::{ admin_rpc_service, @@ -90,7 +99,17 @@ use { }, }; -#[cfg(not(any(target_env = "msvc", target_os = "freebsd")))] +#[cfg(all( + feature = "alloc_monitor", + not(any(target_env = "msvc", target_os = "freebsd")) +))] +#[global_allocator] +static GLOBAL: JemWrapAllocator = JemWrapAllocator::new(); + +#[cfg(all( + not(feature = "alloc_monitor"), + not(any(target_env = "msvc", target_os = "freebsd")) +))] #[global_allocator] static GLOBAL: Jemalloc = Jemalloc; @@ -452,6 +471,11 @@ fn configure_banking_trace_dir_byte_limit( } pub fn main() { + #[cfg(all( + feature = "alloc_monitor", + not(any(target_env = "msvc", target_os = "freebsd")) + ))] + setup_watch_memory_usage(); let default_args = DefaultArgs::new(); let solana_version = solana_version::version!(); let cli_app = app(solana_version, &default_args); @@ -2177,6 +2201,11 @@ pub fn main() { info!("Validator initialized"); validator.join(); info!("Validator exiting.."); + #[cfg(all( + feature = "alloc_monitor", + not(any(target_env = "msvc", target_os = "freebsd")) + ))] + deinit_allocator(); } fn process_account_indexes(matches: &ArgMatches) -> AccountSecondaryIndexes {