Skip to content

Commit e1a7178

Browse files
committed
Add jemalloc-profiling feature to enable heap profiling features.
1 parent 309c301 commit e1a7178

File tree

7 files changed

+152
-1
lines changed

7 files changed

+152
-1
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

beacon_node/http_api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ lighthouse_network = { workspace = true }
2323
lighthouse_version = { workspace = true }
2424
logging = { workspace = true }
2525
lru = { workspace = true }
26+
malloc_utils = { workspace = true }
2627
metrics = { workspace = true }
2728
network = { workspace = true }
2829
operation_pool = { workspace = true }

beacon_node/http_api/src/lib.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4631,6 +4631,53 @@ pub fn serve<T: BeaconChainTypes>(
46314631
},
46324632
);
46334633

4634+
// POST lighthouse/malloc/prof_dump
4635+
let post_lighthouse_malloc_prof_dump = warp::path("lighthouse")
4636+
.and(warp::path("malloc"))
4637+
.and(warp::path("prof_dump"))
4638+
.and(warp::body::json())
4639+
.and(warp::path::end())
4640+
// Skip the `BeaconProcessor` for memory dumps so we can execute them as
4641+
// quickly as possible. Memory dumps should be uncommon and very
4642+
// deliberate.
4643+
.then(|filename: String| {
4644+
let dump = || {
4645+
let path = PathBuf::from_str(&filename).map_err(|e| {
4646+
warp_utils::reject::custom_bad_request(format!(
4647+
"Unable to parse {filename} as path: {e:?}"
4648+
))
4649+
})?;
4650+
if path.exists() {
4651+
Err(warp_utils::reject::custom_bad_request(format!(
4652+
"{filename} already exists"
4653+
)))
4654+
} else {
4655+
malloc_utils::prof_dump(&filename)
4656+
.map(|()| warp::reply::json(&filename).into_response())
4657+
.map_err(warp_utils::reject::custom_bad_request)
4658+
}
4659+
};
4660+
4661+
convert_rejection(dump())
4662+
});
4663+
4664+
// POST lighthouse/malloc/prof_active
4665+
let post_lighthouse_malloc_prof_active = warp::path("lighthouse")
4666+
.and(warp::path("malloc"))
4667+
.and(warp::path("prof_active"))
4668+
.and(warp::body::json())
4669+
.and(warp::path::end())
4670+
// Skip the `BeaconProcessor` for profiling so we can execute it as
4671+
// quickly as possible. Memory dumps should be uncommon and very
4672+
// deliberate.
4673+
.then(|enable: bool| {
4674+
let result = malloc_utils::prof_active(enable)
4675+
.map(|()| warp::reply::json(&enable).into_response())
4676+
.map_err(warp_utils::reject::custom_bad_request);
4677+
4678+
convert_rejection(result)
4679+
});
4680+
46344681
let get_events = eth_v1
46354682
.and(warp::path("events"))
46364683
.and(warp::path::end())
@@ -4908,6 +4955,8 @@ pub fn serve<T: BeaconChainTypes>(
49084955
.uor(post_lighthouse_compaction)
49094956
.uor(post_lighthouse_add_peer)
49104957
.uor(post_lighthouse_remove_peer)
4958+
.uor(post_lighthouse_malloc_prof_dump)
4959+
.uor(post_lighthouse_malloc_prof_active)
49114960
.recover(warp_utils::reject::handle_rejection),
49124961
),
49134962
)

common/malloc_utils/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ edition = { workspace = true }
77
[features]
88
mallinfo2 = []
99
jemalloc = ["tikv-jemallocator", "tikv-jemalloc-ctl"]
10-
jemalloc-profiling = ["tikv-jemallocator/profiling"]
10+
jemalloc-profiling = ["jemalloc", "tikv-jemallocator/profiling", "tikv-jemalloc-sys"]
1111

1212
[dependencies]
1313
libc = "0.2.79"
1414
metrics = { workspace = true }
1515
parking_lot = { workspace = true }
1616
tikv-jemalloc-ctl = { version = "0.6.0", optional = true, features = ["stats"] }
17+
tikv-jemalloc-sys = { version = "0.6.0", optional = true }
1718

1819
[target.'cfg(not(target_os = "linux"))'.dependencies]
1920
tikv-jemallocator = { version = "0.6.0", optional = true, features = ["stats"] }

common/malloc_utils/src/jemalloc.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77
//!
88
//! A) `JEMALLOC_SYS_WITH_MALLOC_CONF` at compile-time.
99
//! B) `_RJEM_MALLOC_CONF` at runtime.
10+
1011
use metrics::{
1112
set_gauge, set_gauge_vec, try_create_int_gauge, try_create_int_gauge_vec, IntGauge, IntGaugeVec,
1213
};
14+
use std::ffi::{c_char, c_int};
1315
use std::sync::LazyLock;
16+
use std::{mem, ptr};
1417
use tikv_jemalloc_ctl::{arenas, epoch, raw, stats, Access, AsName, Error};
1518

1619
#[global_allocator]
@@ -124,6 +127,62 @@ pub fn page_size() -> Result<usize, Error> {
124127
"arenas.page\0".name().read()
125128
}
126129

130+
/// A convenience wrapper around `mallctl` for writing `value` to `name`.
131+
#[cfg(feature = "jemalloc-profiling")]
132+
pub unsafe fn mallctl_write<T>(name: &[u8], mut value: T) -> Result<(), c_int> {
133+
// Use `tikv_jemalloc_sys::mallctl` directly since the `jemalloc_ctl::raw`
134+
// functions artifically limit the `name` values.
135+
let status = tikv_jemalloc_sys::mallctl(
136+
name as *const _ as *const c_char,
137+
ptr::null_mut(),
138+
ptr::null_mut(),
139+
&mut value as *mut _ as *mut _,
140+
mem::size_of::<T>(),
141+
);
142+
143+
if status == 0 {
144+
Ok(())
145+
} else {
146+
Err(status)
147+
}
148+
}
149+
150+
/// Add a C-style `0x00` terminator to the string and return it as a `Vec` of
151+
/// bytes.
152+
#[allow(dead_code)]
153+
fn terminate_string_for_c(s: &str) -> Vec<u8> {
154+
let mut terminated = vec![0x00_u8; s.len() + 1];
155+
terminated[..s.len()].copy_from_slice(s.as_ref());
156+
terminated
157+
}
158+
159+
/// Uses `mallctl` to call `"prof.dump"`.
160+
///
161+
/// This generates a heap profile at `filename`.
162+
#[allow(dead_code)]
163+
#[cfg(feature = "jemalloc-profiling")]
164+
pub fn prof_dump(filename: &str) -> Result<(), String> {
165+
let terminated_filename = terminate_string_for_c(filename);
166+
167+
unsafe {
168+
mallctl_write(
169+
"prof.dump\0".as_ref(),
170+
terminated_filename.as_ptr() as *const c_char,
171+
)
172+
}
173+
.map_err(|e| format!("Failed to call prof.dump on mallctl: {e:?}"))
174+
}
175+
176+
/// Uses `mallctl` to call `"prof.enable"`.
177+
///
178+
/// Controls wether profile sampling is active.
179+
#[allow(dead_code)]
180+
#[cfg(feature = "jemalloc-profiling")]
181+
pub fn prof_active(enable: bool) -> Result<(), String> {
182+
unsafe { mallctl_write("prof.active\0".as_ref(), enable) }
183+
.map_err(|e| format!("Failed to call prof.active on mallctl with code {e:?}"))
184+
}
185+
127186
#[cfg(test)]
128187
mod test {
129188
use super::*;

common/malloc_utils/src/lib.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ pub use interface::*;
4444
mod interface {
4545
pub use crate::glibc::configure_glibc_malloc as configure_memory_allocator;
4646
pub use crate::glibc::scrape_mallinfo_metrics as scrape_allocator_metrics;
47+
48+
#[allow(dead_code)]
49+
pub use super::prof_dump_unsupported as prof_dump;
4750
}
4851

4952
#[cfg(feature = "jemalloc")]
@@ -54,6 +57,16 @@ mod interface {
5457
}
5558

5659
pub use crate::jemalloc::scrape_jemalloc_metrics as scrape_allocator_metrics;
60+
61+
#[cfg(not(feature = "jemalloc-profiling"))]
62+
pub use super::prof_dump_unsupported as prof_dump;
63+
#[cfg(feature = "jemalloc-profiling")]
64+
pub use crate::jemalloc::prof_dump;
65+
66+
#[cfg(not(feature = "jemalloc-profiling"))]
67+
pub use super::prof_active_unsupported as prof_active;
68+
#[cfg(feature = "jemalloc-profiling")]
69+
pub use crate::jemalloc::prof_active;
5770
}
5871

5972
#[cfg(all(
@@ -68,4 +81,28 @@ mod interface {
6881

6982
#[allow(dead_code)]
7083
pub fn scrape_allocator_metrics() {}
84+
85+
#[allow(dead_code)]
86+
pub use super::prof_dump_unsupported as prof_dump;
87+
88+
#[allow(dead_code)]
89+
pub use super::prof_active_unsupported as prof_active;
90+
}
91+
92+
#[allow(dead_code)]
93+
pub fn prof_dump_unsupported(_: &str) -> Result<(), String> {
94+
Err(
95+
"Profile dumps are only supported when Lighthouse is built for Linux \
96+
using the `jemalloc` and `jemalloc-profiling` features."
97+
.to_string(),
98+
)
99+
}
100+
101+
#[allow(dead_code)]
102+
pub fn prof_active_unsupported(_: bool) -> Result<(), String> {
103+
Err(
104+
"Enabling profiling is only supported when Lighthouse is built for Linux \
105+
using the `jemalloc` and `jemalloc-profiling` features."
106+
.to_string(),
107+
)
71108
}

lighthouse/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ console-subscriber = ["console-subscriber/default"]
3838

3939
# Deprecated. This is now enabled by default on non windows targets.
4040
jemalloc = []
41+
# Enable profiling in jemalloc.
42+
jemalloc-profiling = ["malloc_utils/jemalloc-profiling"]
4143

4244
[dependencies]
4345
account_manager = { "path" = "../account_manager" }

0 commit comments

Comments
 (0)