From 748085de9a7726ddbb7d5516d1fb090da6961963 Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Mon, 6 Feb 2023 14:58:38 +0100 Subject: [PATCH 01/14] Introduce jemalloc-stats feature flag --- Cargo.toml | 1 + node/overseer/src/lib.rs | 58 ++++++++++++++++--------------- node/overseer/src/memory_stats.rs | 11 +++--- node/overseer/src/metrics.rs | 7 ++-- src/main.rs | 1 + 5 files changed, 44 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8c8f1435abad..b7b4fb84ec9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -202,6 +202,7 @@ try-runtime = [ "polkadot-cli/try-runtime" ] fast-runtime = [ "polkadot-cli/fast-runtime" ] runtime-metrics = [ "polkadot-cli/runtime-metrics" ] pyroscope = ["polkadot-cli/pyroscope"] +jemalloc-stats = [] # Configuration for building a .deb package - for use with `cargo-deb` [package.metadata.deb] diff --git a/node/overseer/src/lib.rs b/node/overseer/src/lib.rs index 6b63235e12a1..7e8c4a57abf7 100644 --- a/node/overseer/src/lib.rs +++ b/node/overseer/src/lib.rs @@ -123,8 +123,6 @@ mod tests; use sp_core::traits::SpawnNamed; -use memory_stats::MemoryAllocationTracker; - /// Glue to connect `trait orchestra::Spawner` and `SpawnNamed` from `substrate`. pub struct SpawnGlue(pub S); @@ -631,6 +629,36 @@ pub struct Overseer { pub metrics: OverseerMetrics, } +// This version of collect_memory_stats is active by default on linux, or when the jemalloc-stats feature flag is +// enabled. +#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] +fn collect_memory_stats(metrics: &OverseerMetrics) -> () { + use memory_stats::MemoryAllocationTracker; + match MemoryAllocationTracker::new() { + Ok(memory_stats) => match memory_stats.snapshot() { + Ok(memory_stats_snapshot) => { + gum::trace!(target: LOG_TARGET, "memory_stats: {:?}", &memory_stats_snapshot); + metrics.memory_stats_snapshot(memory_stats_snapshot); + }, + Err(e) => gum::debug!(target: LOG_TARGET, "Failed to obtain memory stats: {:?}", e), + }, + Err(_) => { + gum::debug!( + target: LOG_TARGET, + "Memory allocation tracking is not supported by the allocator.", + ); + () + }, + } +} + +// This version of collect_memory_stats is enabled on any non linux platform by default, or when the jemalloc-stats +// feature flag is enabled. It effectively disabled the jemalloc based metric collection. +#[cfg(not(any(target_os = "linux", feature = "jemalloc-stats")))] +fn collect_memory_stats(_metrics: &OverseerMetrics) -> () { + () +} + /// Spawn the metrics metronome task. pub fn spawn_metronome_metrics( overseer: &mut Overseer, @@ -653,32 +681,6 @@ where } } let subsystem_meters = overseer.map_subsystems(ExtractNameAndMeters); - - let collect_memory_stats: Box = - match MemoryAllocationTracker::new() { - Ok(memory_stats) => - Box::new(move |metrics: &OverseerMetrics| match memory_stats.snapshot() { - Ok(memory_stats_snapshot) => { - gum::trace!( - target: LOG_TARGET, - "memory_stats: {:?}", - &memory_stats_snapshot - ); - metrics.memory_stats_snapshot(memory_stats_snapshot); - }, - Err(e) => - gum::debug!(target: LOG_TARGET, "Failed to obtain memory stats: {:?}", e), - }), - Err(_) => { - gum::debug!( - target: LOG_TARGET, - "Memory allocation tracking is not supported by the allocator.", - ); - - Box::new(|_| {}) - }, - }; - let metronome = Metronome::new(std::time::Duration::from_millis(950)).for_each(move |_| { collect_memory_stats(&metronome_metrics); diff --git a/node/overseer/src/memory_stats.rs b/node/overseer/src/memory_stats.rs index 670762a4935c..5ace40587c31 100644 --- a/node/overseer/src/memory_stats.rs +++ b/node/overseer/src/memory_stats.rs @@ -14,7 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use tikv_jemalloc_ctl::{epoch, stats, Error}; +// Allow unused code as they might be enabled only for the jemalloc-stats feature flag. +#![allow(dead_code)] + +use tikv_jemalloc_ctl::stats; #[derive(Clone)] pub struct MemoryAllocationTracker { @@ -24,15 +27,15 @@ pub struct MemoryAllocationTracker { } impl MemoryAllocationTracker { - pub fn new() -> Result { + pub fn new() -> Result { Ok(Self { - epoch: epoch::mib()?, + epoch: tikv_jemalloc_ctl::epoch::mib()?, allocated: stats::allocated::mib()?, resident: stats::resident::mib()?, }) } - pub fn snapshot(&self) -> Result { + pub fn snapshot(&self) -> Result { // update stats by advancing the allocation epoch self.epoch.advance()?; diff --git a/node/overseer/src/metrics.rs b/node/overseer/src/metrics.rs index bb7d98a68f2e..7d00581b5569 100644 --- a/node/overseer/src/metrics.rs +++ b/node/overseer/src/metrics.rs @@ -15,12 +15,11 @@ // along with Polkadot. If not, see . //! Prometheus metrics related to the overseer and its channels. +//! use super::*; pub use polkadot_node_metrics::metrics::{self, prometheus, Metrics as MetricsTrait}; -use memory_stats::MemoryAllocationSnapshot; - /// Overseer Prometheus metrics. #[derive(Clone)] struct MetricsInner { @@ -40,7 +39,10 @@ struct MetricsInner { signals_sent: prometheus::GaugeVec, signals_received: prometheus::GaugeVec, + // Allow unused code as they might be enabled only for the jemalloc-stats feature flag. + #[allow(dead_code)] memory_stats_resident: prometheus::Gauge, + #[allow(dead_code)] memory_stats_allocated: prometheus::Gauge, } @@ -67,6 +69,7 @@ impl Metrics { } } + #[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] pub(crate) fn memory_stats_snapshot(&self, memory_stats: MemoryAllocationSnapshot) { if let Some(metrics) = &self.0 { metrics.memory_stats_allocated.set(memory_stats.allocated); diff --git a/src/main.rs b/src/main.rs index 13c17df851f1..4473c7fb748c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ use color_eyre::eyre; /// Global allocator. Changing it to another allocator will require changing /// `memory_stats::MemoryAllocationTracker`. +#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] #[global_allocator] pub static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; From 414f5feec96dd1c9307a2ec52428808485c2d31c Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Mon, 6 Feb 2023 15:09:21 +0100 Subject: [PATCH 02/14] remove unneeded space --- node/overseer/src/metrics.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/node/overseer/src/metrics.rs b/node/overseer/src/metrics.rs index 7d00581b5569..e0773d6310b4 100644 --- a/node/overseer/src/metrics.rs +++ b/node/overseer/src/metrics.rs @@ -15,7 +15,6 @@ // along with Polkadot. If not, see . //! Prometheus metrics related to the overseer and its channels. -//! use super::*; pub use polkadot_node_metrics::metrics::{self, prometheus, Metrics as MetricsTrait}; From f18011bfb881fa9615d88bfbe9c36e1511e7e714 Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Mon, 6 Feb 2023 17:38:12 +0100 Subject: [PATCH 03/14] Update node/overseer/src/lib.rs Co-authored-by: Marcin S. --- node/overseer/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/overseer/src/lib.rs b/node/overseer/src/lib.rs index 7e8c4a57abf7..93a4b3d63653 100644 --- a/node/overseer/src/lib.rs +++ b/node/overseer/src/lib.rs @@ -653,7 +653,7 @@ fn collect_memory_stats(metrics: &OverseerMetrics) -> () { } // This version of collect_memory_stats is enabled on any non linux platform by default, or when the jemalloc-stats -// feature flag is enabled. It effectively disabled the jemalloc based metric collection. +// feature flag is disabled. It effectively disabled the jemalloc based metric collection. #[cfg(not(any(target_os = "linux", feature = "jemalloc-stats")))] fn collect_memory_stats(_metrics: &OverseerMetrics) -> () { () From ccec741dc9192c52eaa4575bb99d44739f5b38f4 Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Mon, 6 Feb 2023 17:38:37 +0100 Subject: [PATCH 04/14] Update Cargo.toml Co-authored-by: Marcin S. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b7b4fb84ec9d..2e8a5bd9dc9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -202,7 +202,7 @@ try-runtime = [ "polkadot-cli/try-runtime" ] fast-runtime = [ "polkadot-cli/fast-runtime" ] runtime-metrics = [ "polkadot-cli/runtime-metrics" ] pyroscope = ["polkadot-cli/pyroscope"] -jemalloc-stats = [] +jemalloc-stats = ["tikv-jemallocator"] # Configuration for building a .deb package - for use with `cargo-deb` [package.metadata.deb] From af996ba1e9bda3b573c0b47c129800fd51887a45 Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Mon, 6 Feb 2023 17:56:19 +0100 Subject: [PATCH 05/14] revert making tikv-jemallocator depend on jemalloc-stats --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2e8a5bd9dc9b..b7b4fb84ec9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -202,7 +202,7 @@ try-runtime = [ "polkadot-cli/try-runtime" ] fast-runtime = [ "polkadot-cli/fast-runtime" ] runtime-metrics = [ "polkadot-cli/runtime-metrics" ] pyroscope = ["polkadot-cli/pyroscope"] -jemalloc-stats = ["tikv-jemallocator"] +jemalloc-stats = [] # Configuration for building a .deb package - for use with `cargo-deb` [package.metadata.deb] From 3e611a34546b4c07787c5d45ccb5567c96eb04ea Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Mon, 6 Feb 2023 18:15:01 +0100 Subject: [PATCH 06/14] conditionally import memory_stats instead of using dead_code --- node/overseer/src/lib.rs | 1 + node/overseer/src/memory_stats.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/node/overseer/src/lib.rs b/node/overseer/src/lib.rs index 93a4b3d63653..9c6489ad12c3 100644 --- a/node/overseer/src/lib.rs +++ b/node/overseer/src/lib.rs @@ -117,6 +117,7 @@ pub const KNOWN_LEAVES_CACHE_SIZE: NonZeroUsize = match NonZeroUsize::new(2 * 24 None => panic!("Known leaves cache size must be non-zero"), }; +#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] mod memory_stats; #[cfg(test)] mod tests; diff --git a/node/overseer/src/memory_stats.rs b/node/overseer/src/memory_stats.rs index 5ace40587c31..270b1e4c760c 100644 --- a/node/overseer/src/memory_stats.rs +++ b/node/overseer/src/memory_stats.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . // Allow unused code as they might be enabled only for the jemalloc-stats feature flag. -#![allow(dead_code)] +// #![allow(dead_code)] use tikv_jemalloc_ctl::stats; From a0ece336ec2c05a68cdf45a22c29f621235097fb Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Mon, 6 Feb 2023 18:43:31 +0100 Subject: [PATCH 07/14] fix test via expllicit import --- node/overseer/src/metrics.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/node/overseer/src/metrics.rs b/node/overseer/src/metrics.rs index e0773d6310b4..06f8c7687dd5 100644 --- a/node/overseer/src/metrics.rs +++ b/node/overseer/src/metrics.rs @@ -69,7 +69,10 @@ impl Metrics { } #[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] - pub(crate) fn memory_stats_snapshot(&self, memory_stats: MemoryAllocationSnapshot) { + pub(crate) fn memory_stats_snapshot( + &self, + memory_stats: memory_stats::MemoryAllocationSnapshot, + ) { if let Some(metrics) = &self.0 { metrics.memory_stats_allocated.set(memory_stats.allocated); metrics.memory_stats_resident.set(memory_stats.resident); From e2b890a384c7b667f9260f176774ef45ed83b8b7 Mon Sep 17 00:00:00 2001 From: Marcin S Date: Tue, 7 Feb 2023 10:30:42 +0100 Subject: [PATCH 08/14] Add jemalloc-stats feature to crates, propagate it from root --- Cargo.lock | 2 ++ Cargo.toml | 10 +++++++--- node/core/pvf/Cargo.toml | 3 +++ node/core/pvf/src/prepare/memory_stats.rs | 1 + node/overseer/Cargo.toml | 1 + 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 376321bdbc23..4ab6741f2d35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6385,6 +6385,8 @@ dependencies = [ "nix 0.26.2", "polkadot-cli", "polkadot-core-primitives", + "polkadot-node-core-pvf", + "polkadot-overseer", "substrate-rpc-client", "tempfile", "tikv-jemallocator", diff --git a/Cargo.toml b/Cargo.toml index b7b4fb84ec9d..c43bc4040eb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,14 @@ repository = "https://github.com/paritytech/polkadot.git" version = "0.9.37" [dependencies] -polkadot-cli = { path = "cli", features = [ "kusama-native", "westend-native", "rococo-native" ] } color-eyre = { version = "0.6.1", default-features = false } tikv-jemallocator = "0.5.0" +# Crates in our workspace, defined as dependencies so we can pass them feature flags. +polkadot-cli = { path = "cli", features = [ "kusama-native", "westend-native", "rococo-native" ] } +polkadot-node-core-pvf = { path = "node/core/pvf" } +polkadot-overseer = { path = "node/overseer" } + [dev-dependencies] assert_cmd = "2.0.4" nix = { version = "0.26.1", features = ["signal"] } @@ -30,7 +34,7 @@ tempfile = "3.2.0" tokio = "1.24.2" substrate-rpc-client = { git = "https://github.com/paritytech/substrate", branch = "master" } polkadot-core-primitives = { path = "core-primitives" } - + [workspace] members = [ "cli", @@ -202,7 +206,7 @@ try-runtime = [ "polkadot-cli/try-runtime" ] fast-runtime = [ "polkadot-cli/fast-runtime" ] runtime-metrics = [ "polkadot-cli/runtime-metrics" ] pyroscope = ["polkadot-cli/pyroscope"] -jemalloc-stats = [] +jemalloc-stats = ["polkadot-node-core-pvf/jemalloc-stats", "polkadot-overseer/jemalloc-stats"] # Configuration for building a .deb package - for use with `cargo-deb` [package.metadata.deb] diff --git a/node/core/pvf/Cargo.toml b/node/core/pvf/Cargo.toml index e918c5f90fb2..391a3e17b426 100644 --- a/node/core/pvf/Cargo.toml +++ b/node/core/pvf/Cargo.toml @@ -47,3 +47,6 @@ adder = { package = "test-parachain-adder", path = "../../../parachain/test-para halt = { package = "test-parachain-halt", path = "../../../parachain/test-parachains/halt" } hex-literal = "0.3.4" tempfile = "3.3.0" + +[features] +jemalloc-stats = [] diff --git a/node/core/pvf/src/prepare/memory_stats.rs b/node/core/pvf/src/prepare/memory_stats.rs index 4765a196d54e..e49505e2f90b 100644 --- a/node/core/pvf/src/prepare/memory_stats.rs +++ b/node/core/pvf/src/prepare/memory_stats.rs @@ -60,6 +60,7 @@ pub struct MemoryAllocationStats { pub allocated: u64, } +#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] #[derive(Clone)] struct MemoryAllocationTracker { epoch: tikv_jemalloc_ctl::epoch_mib, diff --git a/node/overseer/Cargo.toml b/node/overseer/Cargo.toml index 262eddeec61e..4e9dcf4d3894 100644 --- a/node/overseer/Cargo.toml +++ b/node/overseer/Cargo.toml @@ -35,3 +35,4 @@ test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../pri default = [] expand = ["orchestra/expand"] dotgraph = ["orchestra/dotgraph"] +jemalloc-stats = [] From 326b44aa53fc3c5501da5f7b0bcfb05fd2e84085 Mon Sep 17 00:00:00 2001 From: Marcin S Date: Tue, 7 Feb 2023 11:07:17 +0100 Subject: [PATCH 09/14] Apply `jemalloc-stats` feature to prepare mem stats; small refactor --- node/core/pvf/src/prepare/memory_stats.rs | 323 +++++++++++----------- node/core/pvf/src/prepare/worker.rs | 15 +- 2 files changed, 173 insertions(+), 165 deletions(-) diff --git a/node/core/pvf/src/prepare/memory_stats.rs b/node/core/pvf/src/prepare/memory_stats.rs index e49505e2f90b..eb6310024c3e 100644 --- a/node/core/pvf/src/prepare/memory_stats.rs +++ b/node/core/pvf/src/prepare/memory_stats.rs @@ -29,16 +29,7 @@ use crate::{metrics::Metrics, LOG_TARGET}; use parity_scale_codec::{Decode, Encode}; -use std::{ - io, - sync::mpsc::{Receiver, RecvTimeoutError, Sender}, - time::Duration, -}; -use tikv_jemalloc_ctl::{epoch, stats, Error}; -use tokio::task::JoinHandle; - -#[cfg(target_os = "linux")] -use libc::{getrusage, rusage, timeval, RUSAGE_THREAD}; +use std::io; /// Helper struct to contain all the memory stats, including [`MemoryAllocationStats`] and, if /// supported by the OS, `ru_maxrss`. @@ -60,165 +51,17 @@ pub struct MemoryAllocationStats { pub allocated: u64, } -#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] -#[derive(Clone)] -struct MemoryAllocationTracker { - epoch: tikv_jemalloc_ctl::epoch_mib, - allocated: stats::allocated_mib, - resident: stats::resident_mib, -} - -impl MemoryAllocationTracker { - pub fn new() -> Result { - Ok(Self { - epoch: epoch::mib()?, - allocated: stats::allocated::mib()?, - resident: stats::resident::mib()?, - }) - } - - pub fn snapshot(&self) -> Result { - // update stats by advancing the allocation epoch - self.epoch.advance()?; - - // Convert to `u64`, as `usize` is not `Encode`able. - let allocated = self.allocated.read()? as u64; - let resident = self.resident.read()? as u64; - Ok(MemoryAllocationStats { allocated, resident }) - } -} - -/// Get the rusage stats for the current thread. -#[cfg(target_os = "linux")] -fn getrusage_thread() -> io::Result { - let mut result = rusage { - ru_utime: timeval { tv_sec: 0, tv_usec: 0 }, - ru_stime: timeval { tv_sec: 0, tv_usec: 0 }, - ru_maxrss: 0, - ru_ixrss: 0, - ru_idrss: 0, - ru_isrss: 0, - ru_minflt: 0, - ru_majflt: 0, - ru_nswap: 0, - ru_inblock: 0, - ru_oublock: 0, - ru_msgsnd: 0, - ru_msgrcv: 0, - ru_nsignals: 0, - ru_nvcsw: 0, - ru_nivcsw: 0, - }; - if unsafe { getrusage(RUSAGE_THREAD, &mut result) } == -1 { - return Err(io::Error::last_os_error()) - } - Ok(result) -} - /// Gets the `ru_maxrss` for the current thread if the OS supports `getrusage`. Otherwise, just /// returns `None`. pub fn get_max_rss_thread() -> Option> { // `c_long` is either `i32` or `i64` depending on architecture. `i64::from` always works. #[cfg(target_os = "linux")] - let max_rss = Some(getrusage_thread().map(|rusage| i64::from(rusage.ru_maxrss))); + let max_rss = Some(getrusage::getrusage_thread().map(|rusage| i64::from(rusage.ru_maxrss))); #[cfg(not(target_os = "linux"))] let max_rss = None; max_rss } -/// Runs a thread in the background that observes memory statistics. The goal is to try to get -/// accurate stats during preparation. -/// -/// # Algorithm -/// -/// 1. Create the memory tracker. -/// -/// 2. Sleep for some short interval. Whenever we wake up, take a snapshot by updating the -/// allocation epoch. -/// -/// 3. When we receive a signal that preparation has completed, take one last snapshot and return -/// the maximum observed values. -/// -/// # Errors -/// -/// For simplicity, any errors are returned as a string. As this is not a critical component, errors -/// are used for informational purposes (logging) only. -pub fn memory_tracker_loop(finished_rx: Receiver<()>) -> Result { - // This doesn't need to be too fine-grained since preparation currently takes 3-10s or more. - // Apart from that, there is not really a science to this number. - const POLL_INTERVAL: Duration = Duration::from_millis(100); - - let tracker = MemoryAllocationTracker::new().map_err(|err| err.to_string())?; - let mut max_stats = MemoryAllocationStats::default(); - - let mut update_stats = || -> Result<(), String> { - let current_stats = tracker.snapshot().map_err(|err| err.to_string())?; - if current_stats.resident > max_stats.resident { - max_stats.resident = current_stats.resident; - } - if current_stats.allocated > max_stats.allocated { - max_stats.allocated = current_stats.allocated; - } - Ok(()) - }; - - loop { - // Take a snapshot and update the max stats. - update_stats()?; - - // Sleep. - match finished_rx.recv_timeout(POLL_INTERVAL) { - // Received finish signal. - Ok(()) => { - update_stats()?; - return Ok(max_stats) - }, - // Timed out, restart loop. - Err(RecvTimeoutError::Timeout) => continue, - Err(RecvTimeoutError::Disconnected) => - return Err("memory_tracker_loop: finished_rx disconnected".into()), - } - } -} - -/// Helper function to terminate the memory tracker thread and get the stats. Helps isolate all this -/// error handling. -pub async fn get_memory_tracker_loop_stats( - fut: JoinHandle>, - tx: Sender<()>, -) -> Option { - // Signal to the memory tracker thread to terminate. - if let Err(err) = tx.send(()) { - gum::warn!( - target: LOG_TARGET, - worker_pid = %std::process::id(), - "worker: error sending signal to memory tracker_thread: {}", err - ); - None - } else { - // Join on the thread handle. - match fut.await { - Ok(Ok(stats)) => Some(stats), - Ok(Err(err)) => { - gum::warn!( - target: LOG_TARGET, - worker_pid = %std::process::id(), - "worker: error occurred in the memory tracker thread: {}", err - ); - None - }, - Err(err) => { - gum::warn!( - target: LOG_TARGET, - worker_pid = %std::process::id(), - "worker: error joining on memory tracker thread: {}", err - ); - None - }, - } - } -} - /// Helper function to send the memory metrics, if available, to prometheus. pub fn observe_memory_metrics(metrics: &Metrics, memory_stats: MemoryStats, pid: u32) { if let Some(max_rss) = memory_stats.max_rss { @@ -242,3 +85,165 @@ pub fn observe_memory_metrics(metrics: &Metrics, memory_stats: MemoryStats, pid: metrics.observe_preparation_max_allocated(allocated_kb); } } + +#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] +pub mod memory_tracker { + use super::*; + use std::{ + sync::mpsc::{Receiver, RecvTimeoutError, Sender}, + time::Duration, + }; + use tikv_jemalloc_ctl::{epoch, stats, Error}; + use tokio::task::JoinHandle; + + #[derive(Clone)] + struct MemoryAllocationTracker { + epoch: tikv_jemalloc_ctl::epoch_mib, + allocated: stats::allocated_mib, + resident: stats::resident_mib, + } + + impl MemoryAllocationTracker { + pub fn new() -> Result { + Ok(Self { + epoch: epoch::mib()?, + allocated: stats::allocated::mib()?, + resident: stats::resident::mib()?, + }) + } + + pub fn snapshot(&self) -> Result { + // update stats by advancing the allocation epoch + self.epoch.advance()?; + + // Convert to `u64`, as `usize` is not `Encode`able. + let allocated = self.allocated.read()? as u64; + let resident = self.resident.read()? as u64; + Ok(MemoryAllocationStats { allocated, resident }) + } + } + + /// Runs a thread in the background that observes memory statistics. The goal is to try to get + /// accurate stats during preparation. + /// + /// # Algorithm + /// + /// 1. Create the memory tracker. + /// + /// 2. Sleep for some short interval. Whenever we wake up, take a snapshot by updating the + /// allocation epoch. + /// + /// 3. When we receive a signal that preparation has completed, take one last snapshot and return + /// the maximum observed values. + /// + /// # Errors + /// + /// For simplicity, any errors are returned as a string. As this is not a critical component, errors + /// are used for informational purposes (logging) only. + pub fn memory_tracker_loop(finished_rx: Receiver<()>) -> Result { + // This doesn't need to be too fine-grained since preparation currently takes 3-10s or more. + // Apart from that, there is not really a science to this number. + const POLL_INTERVAL: Duration = Duration::from_millis(100); + + let tracker = MemoryAllocationTracker::new().map_err(|err| err.to_string())?; + let mut max_stats = MemoryAllocationStats::default(); + + let mut update_stats = || -> Result<(), String> { + let current_stats = tracker.snapshot().map_err(|err| err.to_string())?; + if current_stats.resident > max_stats.resident { + max_stats.resident = current_stats.resident; + } + if current_stats.allocated > max_stats.allocated { + max_stats.allocated = current_stats.allocated; + } + Ok(()) + }; + + loop { + // Take a snapshot and update the max stats. + update_stats()?; + + // Sleep. + match finished_rx.recv_timeout(POLL_INTERVAL) { + // Received finish signal. + Ok(()) => { + update_stats()?; + return Ok(max_stats) + }, + // Timed out, restart loop. + Err(RecvTimeoutError::Timeout) => continue, + Err(RecvTimeoutError::Disconnected) => + return Err("memory_tracker_loop: finished_rx disconnected".into()), + } + } + } + + /// Helper function to terminate the memory tracker thread and get the stats. Helps isolate all this + /// error handling. + pub async fn get_memory_tracker_loop_stats( + fut: JoinHandle>, + tx: Sender<()>, + ) -> Option { + // Signal to the memory tracker thread to terminate. + if let Err(err) = tx.send(()) { + gum::warn!( + target: LOG_TARGET, + worker_pid = %std::process::id(), + "worker: error sending signal to memory tracker_thread: {}", err + ); + None + } else { + // Join on the thread handle. + match fut.await { + Ok(Ok(stats)) => Some(stats), + Ok(Err(err)) => { + gum::warn!( + target: LOG_TARGET, + worker_pid = %std::process::id(), + "worker: error occurred in the memory tracker thread: {}", err + ); + None + }, + Err(err) => { + gum::warn!( + target: LOG_TARGET, + worker_pid = %std::process::id(), + "worker: error joining on memory tracker thread: {}", err + ); + None + }, + } + } + } +} + +#[cfg(target_os = "linux")] +mod getrusage { + use libc::{getrusage, rusage, timeval, RUSAGE_THREAD}; + + /// Get the rusage stats for the current thread. + fn getrusage_thread() -> io::Result { + let mut result = rusage { + ru_utime: timeval { tv_sec: 0, tv_usec: 0 }, + ru_stime: timeval { tv_sec: 0, tv_usec: 0 }, + ru_maxrss: 0, + ru_ixrss: 0, + ru_idrss: 0, + ru_isrss: 0, + ru_minflt: 0, + ru_majflt: 0, + ru_nswap: 0, + ru_inblock: 0, + ru_oublock: 0, + ru_msgsnd: 0, + ru_msgrcv: 0, + ru_nsignals: 0, + ru_nvcsw: 0, + ru_nivcsw: 0, + }; + if unsafe { getrusage(RUSAGE_THREAD, &mut result) } == -1 { + return Err(io::Error::last_os_error()) + } + Ok(result) + } +} diff --git a/node/core/pvf/src/prepare/worker.rs b/node/core/pvf/src/prepare/worker.rs index bb6e120a6691..6ec63ca3386c 100644 --- a/node/core/pvf/src/prepare/worker.rs +++ b/node/core/pvf/src/prepare/worker.rs @@ -14,10 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use super::memory_stats::{ - get_max_rss_thread, get_memory_tracker_loop_stats, memory_tracker_loop, observe_memory_metrics, - MemoryStats, -}; +#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] +use super::memory_stats::memory_tracker::{get_memory_tracker_loop_stats, memory_tracker_loop}; +use super::memory_stats::{get_max_rss_thread, observe_memory_metrics, MemoryStats}; use crate::{ artifacts::CompiledArtifact, error::{PrepareError, PrepareResult}, @@ -373,9 +372,10 @@ pub fn worker_entrypoint(socket_path: &str) { let cpu_time_start = ProcessTime::now(); // Run the memory tracker. + #[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] let (memory_tracker_tx, memory_tracker_rx) = channel::<()>(); - let memory_tracker_fut = - rt_handle.spawn_blocking(move || memory_tracker_loop(memory_tracker_rx)); + #[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] + let memory_tracker_fut = rt_handle.spawn_blocking(move || memory_tracker_loop(memory_tracker_rx)); // Spawn a new thread that runs the CPU time monitor. let (cpu_time_monitor_tx, cpu_time_monitor_rx) = channel::<()>(); @@ -431,8 +431,11 @@ pub fn worker_entrypoint(socket_path: &str) { }, (Ok(compiled_artifact), max_rss) => { // Stop the memory stats worker and get its observed memory stats. + #[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] let memory_tracker_stats = get_memory_tracker_loop_stats(memory_tracker_fut, memory_tracker_tx).await; + #[cfg(not(any(target_os = "linux", feature = "jemalloc-stats")))] + let memory_tracker_stats = None; let memory_stats = MemoryStats { memory_tracker_stats, max_rss: max_rss.map(|inner| inner.map_err(|e| e.to_string())), From 5fd13c792419a466ab3ae50fe6a9193a5ac5a8ab Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Tue, 7 Feb 2023 15:15:09 +0100 Subject: [PATCH 10/14] effect changes recommended on PR --- Cargo.toml | 2 +- node/core/pvf/Cargo.toml | 5 +++-- node/core/pvf/src/prepare/memory_stats.rs | 2 +- node/core/pvf/src/prepare/worker.rs | 10 +++++----- node/overseer/Cargo.toml | 7 +++++-- node/overseer/src/lib.rs | 10 +++++----- node/overseer/src/memory_stats.rs | 3 --- node/overseer/src/metrics.rs | 11 ++++++----- src/main.rs | 2 +- 9 files changed, 27 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c43bc4040eb7..db341c55315e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -206,7 +206,7 @@ try-runtime = [ "polkadot-cli/try-runtime" ] fast-runtime = [ "polkadot-cli/fast-runtime" ] runtime-metrics = [ "polkadot-cli/runtime-metrics" ] pyroscope = ["polkadot-cli/pyroscope"] -jemalloc-stats = ["polkadot-node-core-pvf/jemalloc-stats", "polkadot-overseer/jemalloc-stats"] +jemalloc-allocator = ["polkadot-node-core-pvf/jemalloc-allocator", "polkadot-overseer/jemalloc-allocator"] # Configuration for building a .deb package - for use with `cargo-deb` [package.metadata.deb] diff --git a/node/core/pvf/Cargo.toml b/node/core/pvf/Cargo.toml index 391a3e17b426..f19663bab98e 100644 --- a/node/core/pvf/Cargo.toml +++ b/node/core/pvf/Cargo.toml @@ -20,7 +20,7 @@ rand = "0.8.5" rayon = "1.5.1" slotmap = "1.0" tempfile = "3.3.0" -tikv-jemalloc-ctl = "0.5.0" +tikv-jemalloc-ctl = { version = "0.5.0", optional = true } tokio = { version = "1.24.2", features = ["fs", "process"] } parity-scale-codec = { version = "3.3.0", default-features = false, features = ["derive"] } @@ -41,6 +41,7 @@ sp-tracing = { git = "https://github.com/paritytech/substrate", branch = "master [target.'cfg(target_os = "linux")'.dependencies] libc = "0.2.139" +tikv-jemalloc-ctl = "0.5.0" [dev-dependencies] adder = { package = "test-parachain-adder", path = "../../../parachain/test-parachains/adder" } @@ -49,4 +50,4 @@ hex-literal = "0.3.4" tempfile = "3.3.0" [features] -jemalloc-stats = [] +jemalloc-allocator = ["dep:tikv-jemalloc-ctl"] diff --git a/node/core/pvf/src/prepare/memory_stats.rs b/node/core/pvf/src/prepare/memory_stats.rs index eb6310024c3e..0c17e94830b2 100644 --- a/node/core/pvf/src/prepare/memory_stats.rs +++ b/node/core/pvf/src/prepare/memory_stats.rs @@ -86,7 +86,7 @@ pub fn observe_memory_metrics(metrics: &Metrics, memory_stats: MemoryStats, pid: } } -#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] +#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] pub mod memory_tracker { use super::*; use std::{ diff --git a/node/core/pvf/src/prepare/worker.rs b/node/core/pvf/src/prepare/worker.rs index 6ec63ca3386c..69c1d3bb5557 100644 --- a/node/core/pvf/src/prepare/worker.rs +++ b/node/core/pvf/src/prepare/worker.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] +#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] use super::memory_stats::memory_tracker::{get_memory_tracker_loop_stats, memory_tracker_loop}; use super::memory_stats::{get_max_rss_thread, observe_memory_metrics, MemoryStats}; use crate::{ @@ -372,9 +372,9 @@ pub fn worker_entrypoint(socket_path: &str) { let cpu_time_start = ProcessTime::now(); // Run the memory tracker. - #[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] let (memory_tracker_tx, memory_tracker_rx) = channel::<()>(); - #[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] let memory_tracker_fut = rt_handle.spawn_blocking(move || memory_tracker_loop(memory_tracker_rx)); // Spawn a new thread that runs the CPU time monitor. @@ -431,10 +431,10 @@ pub fn worker_entrypoint(socket_path: &str) { }, (Ok(compiled_artifact), max_rss) => { // Stop the memory stats worker and get its observed memory stats. - #[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] let memory_tracker_stats = get_memory_tracker_loop_stats(memory_tracker_fut, memory_tracker_tx).await; - #[cfg(not(any(target_os = "linux", feature = "jemalloc-stats")))] + #[cfg(not(any(target_os = "linux", feature = "jemalloc-allocator")))] let memory_tracker_stats = None; let memory_stats = MemoryStats { memory_tracker_stats, diff --git a/node/overseer/Cargo.toml b/node/overseer/Cargo.toml index 4e9dcf4d3894..5d26c0b6e2bf 100644 --- a/node/overseer/Cargo.toml +++ b/node/overseer/Cargo.toml @@ -20,7 +20,7 @@ gum = { package = "tracing-gum", path = "../gum" } lru = "0.9" sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } async-trait = "0.1.57" -tikv-jemalloc-ctl = "0.5.0" +tikv-jemalloc-ctl = { version = "0.5.0", optional = true } [dev-dependencies] metered = { package = "prioritized-metered-channel", version = "0.2.0" } @@ -31,8 +31,11 @@ femme = "2.2.1" assert_matches = "1.4.0" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../primitives/test-helpers" } +[target.'cfg(target_os = "linux")'.dependencies] +tikv-jemalloc-ctl = "0.5.0" + [features] default = [] expand = ["orchestra/expand"] dotgraph = ["orchestra/dotgraph"] -jemalloc-stats = [] +jemalloc-allocator = ["dep:tikv-jemalloc-ctl"] diff --git a/node/overseer/src/lib.rs b/node/overseer/src/lib.rs index 9c6489ad12c3..a113579d9588 100644 --- a/node/overseer/src/lib.rs +++ b/node/overseer/src/lib.rs @@ -117,7 +117,7 @@ pub const KNOWN_LEAVES_CACHE_SIZE: NonZeroUsize = match NonZeroUsize::new(2 * 24 None => panic!("Known leaves cache size must be non-zero"), }; -#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] +#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] mod memory_stats; #[cfg(test)] mod tests; @@ -630,9 +630,9 @@ pub struct Overseer { pub metrics: OverseerMetrics, } -// This version of collect_memory_stats is active by default on linux, or when the jemalloc-stats feature flag is +// This version of collect_memory_stats is active by default on linux, or when the jemalloc-allocator feature flag is // enabled. -#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] +#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] fn collect_memory_stats(metrics: &OverseerMetrics) -> () { use memory_stats::MemoryAllocationTracker; match MemoryAllocationTracker::new() { @@ -653,9 +653,9 @@ fn collect_memory_stats(metrics: &OverseerMetrics) -> () { } } -// This version of collect_memory_stats is enabled on any non linux platform by default, or when the jemalloc-stats +// This version of collect_memory_stats is enabled on any non linux platform by default, or when the jemalloc-allocator // feature flag is disabled. It effectively disabled the jemalloc based metric collection. -#[cfg(not(any(target_os = "linux", feature = "jemalloc-stats")))] +#[cfg(not(any(target_os = "linux", feature = "jemalloc-allocator")))] fn collect_memory_stats(_metrics: &OverseerMetrics) -> () { () } diff --git a/node/overseer/src/memory_stats.rs b/node/overseer/src/memory_stats.rs index 4da3fc309d61..9d7ebdb943ea 100644 --- a/node/overseer/src/memory_stats.rs +++ b/node/overseer/src/memory_stats.rs @@ -14,9 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -// Allow unused code as they might be enabled only for the jemalloc-stats feature flag. -// #![allow(dead_code)] - use tikv_jemalloc_ctl::stats; #[derive(Clone)] diff --git a/node/overseer/src/metrics.rs b/node/overseer/src/metrics.rs index 94e259cbbb1c..ba700ec4ae8c 100644 --- a/node/overseer/src/metrics.rs +++ b/node/overseer/src/metrics.rs @@ -38,10 +38,10 @@ struct MetricsInner { signals_sent: prometheus::GaugeVec, signals_received: prometheus::GaugeVec, - // Allow unused code as they might be enabled only for the jemalloc-stats feature flag. - #[allow(dead_code)] + // Allow unused code as they might be enabled only for the jemalloc-allocator feature flag. + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] memory_stats_resident: prometheus::Gauge, - #[allow(dead_code)] + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] memory_stats_allocated: prometheus::Gauge, } @@ -68,7 +68,7 @@ impl Metrics { } } - #[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] pub(crate) fn memory_stats_snapshot( &self, memory_stats: memory_stats::MemoryAllocationSnapshot, @@ -251,7 +251,7 @@ impl MetricsTrait for Metrics { )?, registry, )?, - + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] memory_stats_allocated: prometheus::register( prometheus::Gauge::::new( "polkadot_memory_allocated", @@ -259,6 +259,7 @@ impl MetricsTrait for Metrics { )?, registry, )?, + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] memory_stats_resident: prometheus::register( prometheus::Gauge::::new( "polkadot_memory_resident", diff --git a/src/main.rs b/src/main.rs index 4473c7fb748c..2deb198ec773 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ use color_eyre::eyre; /// Global allocator. Changing it to another allocator will require changing /// `memory_stats::MemoryAllocationTracker`. -#[cfg(any(target_os = "linux", feature = "jemalloc-stats"))] +#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] #[global_allocator] pub static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; From c65001b232def43f66e641a5f5c0f8112c3fb916 Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Wed, 8 Feb 2023 12:58:55 +0100 Subject: [PATCH 11/14] Update node/overseer/src/metrics.rs Co-authored-by: Marcin S. --- node/overseer/src/metrics.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/node/overseer/src/metrics.rs b/node/overseer/src/metrics.rs index ba700ec4ae8c..57858fe98073 100644 --- a/node/overseer/src/metrics.rs +++ b/node/overseer/src/metrics.rs @@ -38,7 +38,6 @@ struct MetricsInner { signals_sent: prometheus::GaugeVec, signals_received: prometheus::GaugeVec, - // Allow unused code as they might be enabled only for the jemalloc-allocator feature flag. #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] memory_stats_resident: prometheus::Gauge, #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] From 496173ba014f67ec4082a5c5362f8e9d5bf83dfd Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Wed, 8 Feb 2023 13:33:26 +0100 Subject: [PATCH 12/14] fix compile error on in pipeline for linux. missing import --- node/core/pvf/src/prepare/memory_stats.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node/core/pvf/src/prepare/memory_stats.rs b/node/core/pvf/src/prepare/memory_stats.rs index 0c17e94830b2..013c0017e623 100644 --- a/node/core/pvf/src/prepare/memory_stats.rs +++ b/node/core/pvf/src/prepare/memory_stats.rs @@ -220,9 +220,10 @@ pub mod memory_tracker { #[cfg(target_os = "linux")] mod getrusage { use libc::{getrusage, rusage, timeval, RUSAGE_THREAD}; + use std::io; /// Get the rusage stats for the current thread. - fn getrusage_thread() -> io::Result { + pub fn getrusage_thread() -> io::Result { let mut result = rusage { ru_utime: timeval { tv_sec: 0, tv_usec: 0 }, ru_stime: timeval { tv_sec: 0, tv_usec: 0 }, From 5127ea70b46e241ac8096da0647b52966d985816 Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Wed, 8 Feb 2023 16:38:04 +0100 Subject: [PATCH 13/14] Update node/overseer/src/lib.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bastian Köcher --- node/overseer/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/overseer/src/lib.rs b/node/overseer/src/lib.rs index a113579d9588..5d5f3eef7805 100644 --- a/node/overseer/src/lib.rs +++ b/node/overseer/src/lib.rs @@ -656,7 +656,7 @@ fn collect_memory_stats(metrics: &OverseerMetrics) -> () { // This version of collect_memory_stats is enabled on any non linux platform by default, or when the jemalloc-allocator // feature flag is disabled. It effectively disabled the jemalloc based metric collection. #[cfg(not(any(target_os = "linux", feature = "jemalloc-allocator")))] -fn collect_memory_stats(_metrics: &OverseerMetrics) -> () { +fn collect_memory_stats(_metrics: &OverseerMetrics) { () } From be7c9504bb49680abbe85faad25fb2471a78a337 Mon Sep 17 00:00:00 2001 From: Anthony Alaribe Date: Wed, 8 Feb 2023 17:11:55 +0100 Subject: [PATCH 14/14] revert to defining collect_memory_stats inline --- node/overseer/src/lib.rs | 60 ++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/node/overseer/src/lib.rs b/node/overseer/src/lib.rs index a113579d9588..4d04ece70643 100644 --- a/node/overseer/src/lib.rs +++ b/node/overseer/src/lib.rs @@ -630,36 +630,6 @@ pub struct Overseer { pub metrics: OverseerMetrics, } -// This version of collect_memory_stats is active by default on linux, or when the jemalloc-allocator feature flag is -// enabled. -#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] -fn collect_memory_stats(metrics: &OverseerMetrics) -> () { - use memory_stats::MemoryAllocationTracker; - match MemoryAllocationTracker::new() { - Ok(memory_stats) => match memory_stats.snapshot() { - Ok(memory_stats_snapshot) => { - gum::trace!(target: LOG_TARGET, "memory_stats: {:?}", &memory_stats_snapshot); - metrics.memory_stats_snapshot(memory_stats_snapshot); - }, - Err(e) => gum::debug!(target: LOG_TARGET, "Failed to obtain memory stats: {:?}", e), - }, - Err(_) => { - gum::debug!( - target: LOG_TARGET, - "Memory allocation tracking is not supported by the allocator.", - ); - () - }, - } -} - -// This version of collect_memory_stats is enabled on any non linux platform by default, or when the jemalloc-allocator -// feature flag is disabled. It effectively disabled the jemalloc based metric collection. -#[cfg(not(any(target_os = "linux", feature = "jemalloc-allocator")))] -fn collect_memory_stats(_metrics: &OverseerMetrics) -> () { - () -} - /// Spawn the metrics metronome task. pub fn spawn_metronome_metrics( overseer: &mut Overseer, @@ -682,6 +652,36 @@ where } } let subsystem_meters = overseer.map_subsystems(ExtractNameAndMeters); + + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] + let collect_memory_stats: Box = + match memory_stats::MemoryAllocationTracker::new() { + Ok(memory_stats) => + Box::new(move |metrics: &OverseerMetrics| match memory_stats.snapshot() { + Ok(memory_stats_snapshot) => { + gum::trace!( + target: LOG_TARGET, + "memory_stats: {:?}", + &memory_stats_snapshot + ); + metrics.memory_stats_snapshot(memory_stats_snapshot); + }, + Err(e) => + gum::debug!(target: LOG_TARGET, "Failed to obtain memory stats: {:?}", e), + }), + Err(_) => { + gum::debug!( + target: LOG_TARGET, + "Memory allocation tracking is not supported by the allocator.", + ); + + Box::new(|_| {}) + }, + }; + + #[cfg(not(any(target_os = "linux", feature = "jemalloc-allocator")))] + let collect_memory_stats: Box = Box::new(|_| {}); + let metronome = Metronome::new(std::time::Duration::from_millis(950)).for_each(move |_| { collect_memory_stats(&metronome_metrics);