#![doc = include_str!("../README.md")]
use std::ffi::CString;
use std::io::BufReader;
use std::sync::Arc;
use std::time::Instant;
use libc::size_t;
use once_cell::sync::Lazy;
use mappings::MAPPINGS;
use tempfile::NamedTempFile;
use tikv_jemalloc_ctl::raw;
use tokio::sync::Mutex;
#[cfg(feature = "flamegraph")]
pub use util::FlamegraphOptions;
use util::{parse_jeheap, ProfStartTime};
pub async fn activate_jemalloc_profiling() {
let Some(ctl) = PROF_CTL.as_ref() else {
tracing::warn!("jemalloc profiling is disabled and cannot be activated");
return;
};
let mut ctl = ctl.lock().await;
if ctl.activated() {
return;
}
match ctl.activate() {
Ok(()) => tracing::info!("jemalloc profiling activated"),
Err(err) => tracing::warn!("could not activate jemalloc profiling: {err}"),
}
}
pub async fn deactivate_jemalloc_profiling() {
let Some(ctl) = PROF_CTL.as_ref() else {
return; };
let mut ctl = ctl.lock().await;
if !ctl.activated() {
return;
}
match ctl.deactivate() {
Ok(()) => tracing::info!("jemalloc profiling deactivated"),
Err(err) => tracing::warn!("could not deactivate jemalloc profiling: {err}"),
}
}
pub static PROF_CTL: Lazy<Option<Arc<Mutex<JemallocProfCtl>>>> =
Lazy::new(|| JemallocProfCtl::get().map(|ctl| Arc::new(Mutex::new(ctl))));
#[derive(Copy, Clone, Debug)]
pub struct JemallocProfMetadata {
pub start_time: Option<ProfStartTime>,
}
#[derive(Debug)]
pub struct JemallocProfCtl {
md: JemallocProfMetadata,
}
impl JemallocProfCtl {
fn get() -> Option<Self> {
let prof_enabled: bool = unsafe { raw::read(b"opt.prof\0") }.unwrap();
if prof_enabled {
let prof_active: bool = unsafe { raw::read(b"opt.prof_active\0") }.unwrap();
let start_time = if prof_active {
Some(ProfStartTime::TimeImmemorial)
} else {
None
};
let md = JemallocProfMetadata { start_time };
Some(Self { md })
} else {
None
}
}
pub fn lg_sample(&self) -> size_t {
unsafe { raw::read(b"prof.lg_sample\0") }.unwrap()
}
pub fn get_md(&self) -> JemallocProfMetadata {
self.md
}
pub fn activated(&self) -> bool {
self.md.start_time.is_some()
}
pub fn activate(&mut self) -> Result<(), tikv_jemalloc_ctl::Error> {
unsafe { raw::write(b"prof.active\0", true) }?;
if self.md.start_time.is_none() {
self.md.start_time = Some(ProfStartTime::Instant(Instant::now()));
}
Ok(())
}
pub fn deactivate(&mut self) -> Result<(), tikv_jemalloc_ctl::Error> {
unsafe { raw::write(b"prof.active\0", false) }?;
let rate = self.lg_sample();
unsafe { raw::write(b"prof.reset\0", rate) }?;
self.md.start_time = None;
Ok(())
}
pub fn dump(&mut self) -> anyhow::Result<std::fs::File> {
let f = NamedTempFile::new()?;
let path = CString::new(f.path().as_os_str().as_encoded_bytes()).unwrap();
unsafe { raw::write(b"prof.dump\0", path.as_ptr()) }?;
Ok(f.into_file())
}
pub fn dump_pprof(&mut self) -> anyhow::Result<Vec<u8>> {
let f = self.dump()?;
let dump_reader = BufReader::new(f);
let profile = parse_jeheap(dump_reader, MAPPINGS.as_deref())?;
let pprof = profile.to_pprof(("inuse_space", "bytes"), ("space", "bytes"), None);
Ok(pprof)
}
#[cfg(feature = "flamegraph")]
pub fn dump_flamegraph(&mut self) -> anyhow::Result<Vec<u8>> {
let mut opts = FlamegraphOptions::default();
opts.title = "inuse_space".to_string();
opts.count_name = "bytes".to_string();
self.dump_flamegraph_with_options(&mut opts)
}
#[cfg(feature = "flamegraph")]
pub fn dump_flamegraph_with_options(
&mut self,
opts: &mut FlamegraphOptions,
) -> anyhow::Result<Vec<u8>> {
let f = self.dump()?;
let dump_reader = BufReader::new(f);
let profile = parse_jeheap(dump_reader, MAPPINGS.as_deref())?;
profile.to_flamegraph(opts)
}
}