Skip to content

Commit 89a8ff3

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

File tree

7 files changed

+149
-1
lines changed

7 files changed

+149
-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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,62 @@ pub fn page_size() -> Result<usize, Error> {
124124
"arenas.page\0".name().read()
125125
}
126126

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