1#![doc = include_str!("../README.md")]
17
18use std::ffi::CString;
19
20use std::io::BufReader;
21use std::sync::Arc;
22use std::time::Instant;
23
24use libc::size_t;
25use once_cell::sync::Lazy;
26
27use mappings::MAPPINGS;
28use tempfile::NamedTempFile;
29use tikv_jemalloc_ctl::raw;
30use tokio::sync::Mutex;
31
32#[cfg(feature = "flamegraph")]
33pub use util::FlamegraphOptions;
34use util::{parse_jeheap, ProfStartTime};
35
36pub async fn activate_jemalloc_profiling() {
38 let Some(ctl) = PROF_CTL.as_ref() else {
39 tracing::warn!("jemalloc profiling is disabled and cannot be activated");
40 return;
41 };
42
43 let mut ctl = ctl.lock().await;
44 if ctl.activated() {
45 return;
46 }
47
48 match ctl.activate() {
49 Ok(()) => tracing::info!("jemalloc profiling activated"),
50 Err(err) => tracing::warn!("could not activate jemalloc profiling: {err}"),
51 }
52}
53
54pub async fn deactivate_jemalloc_profiling() {
56 let Some(ctl) = PROF_CTL.as_ref() else {
57 return; };
59
60 let mut ctl = ctl.lock().await;
61 if !ctl.activated() {
62 return;
63 }
64
65 match ctl.deactivate() {
66 Ok(()) => tracing::info!("jemalloc profiling deactivated"),
67 Err(err) => tracing::warn!("could not deactivate jemalloc profiling: {err}"),
68 }
69}
70
71pub static PROF_CTL: Lazy<Option<Arc<Mutex<JemallocProfCtl>>>> =
73 Lazy::new(|| JemallocProfCtl::get().map(|ctl| Arc::new(Mutex::new(ctl))));
74
75#[derive(Copy, Clone, Debug)]
77pub struct JemallocProfMetadata {
78 pub start_time: Option<ProfStartTime>,
79}
80
81#[derive(Debug)]
83pub struct JemallocProfCtl {
84 md: JemallocProfMetadata,
85}
86
87impl JemallocProfCtl {
88 fn get() -> Option<Self> {
90 let prof_enabled: bool = unsafe { raw::read(b"opt.prof\0") }.unwrap();
93 if prof_enabled {
94 let prof_active: bool = unsafe { raw::read(b"opt.prof_active\0") }.unwrap();
97 let start_time = if prof_active {
98 Some(ProfStartTime::TimeImmemorial)
99 } else {
100 None
101 };
102 let md = JemallocProfMetadata { start_time };
103 Some(Self { md })
104 } else {
105 None
106 }
107 }
108
109 pub fn lg_sample(&self) -> size_t {
111 unsafe { raw::read(b"prof.lg_sample\0") }.unwrap()
114 }
115
116 pub fn get_md(&self) -> JemallocProfMetadata {
118 self.md
119 }
120
121 pub fn activated(&self) -> bool {
123 self.md.start_time.is_some()
124 }
125
126 pub fn activate(&mut self) -> Result<(), tikv_jemalloc_ctl::Error> {
128 unsafe { raw::write(b"prof.active\0", true) }?;
131 if self.md.start_time.is_none() {
132 self.md.start_time = Some(ProfStartTime::Instant(Instant::now()));
133 }
134 Ok(())
135 }
136
137 pub fn deactivate(&mut self) -> Result<(), tikv_jemalloc_ctl::Error> {
139 unsafe { raw::write(b"prof.active\0", false) }?;
142 let rate = self.lg_sample();
143 unsafe { raw::write(b"prof.reset\0", rate) }?;
146
147 self.md.start_time = None;
148 Ok(())
149 }
150
151 pub fn dump(&mut self) -> anyhow::Result<std::fs::File> {
153 let f = NamedTempFile::new()?;
154 let path = CString::new(f.path().as_os_str().as_encoded_bytes()).unwrap();
155
156 unsafe { raw::write(b"prof.dump\0", path.as_ptr()) }?;
159 Ok(f.into_file())
160 }
161
162 pub fn dump_pprof(&mut self) -> anyhow::Result<Vec<u8>> {
165 let f = self.dump()?;
166 let dump_reader = BufReader::new(f);
167 let profile = parse_jeheap(dump_reader, MAPPINGS.as_deref())?;
168 let pprof = profile.to_pprof(("inuse_space", "bytes"), ("space", "bytes"), None);
169 Ok(pprof)
170 }
171
172 #[cfg(feature = "flamegraph")]
174 pub fn dump_flamegraph(&mut self) -> anyhow::Result<Vec<u8>> {
175 let mut opts = FlamegraphOptions::default();
176 opts.title = "inuse_space".to_string();
177 opts.count_name = "bytes".to_string();
178 self.dump_flamegraph_with_options(&mut opts)
179 }
180
181 #[cfg(feature = "flamegraph")]
183 pub fn dump_flamegraph_with_options(
184 &mut self,
185 opts: &mut FlamegraphOptions,
186 ) -> anyhow::Result<Vec<u8>> {
187 let f = self.dump()?;
188 let dump_reader = BufReader::new(f);
189 let profile = parse_jeheap(dump_reader, MAPPINGS.as_deref())?;
190 profile.to_flamegraph(opts)
191 }
192}