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
32use util::parse_jeheap;
33#[cfg(feature = "flamegraph")]
34pub use util::FlamegraphOptions;
35pub use util::{BuildId, Mapping, ProfStartTime, StackProfile, StackProfileIter, WeightedStack};
36
37pub async fn activate_jemalloc_profiling() {
39 let Some(ctl) = PROF_CTL.as_ref() else {
40 tracing::warn!("jemalloc profiling is disabled and cannot be activated");
41 return;
42 };
43
44 let mut ctl = ctl.lock().await;
45 if ctl.activated() {
46 return;
47 }
48
49 match ctl.activate() {
50 Ok(()) => tracing::info!("jemalloc profiling activated"),
51 Err(err) => tracing::warn!("could not activate jemalloc profiling: {err}"),
52 }
53}
54
55pub async fn deactivate_jemalloc_profiling() {
57 let Some(ctl) = PROF_CTL.as_ref() else {
58 return; };
60
61 let mut ctl = ctl.lock().await;
62 if !ctl.activated() {
63 return;
64 }
65
66 match ctl.deactivate() {
67 Ok(()) => tracing::info!("jemalloc profiling deactivated"),
68 Err(err) => tracing::warn!("could not deactivate jemalloc profiling: {err}"),
69 }
70}
71
72pub static PROF_CTL: Lazy<Option<Arc<Mutex<JemallocProfCtl>>>> =
74 Lazy::new(|| JemallocProfCtl::get().map(|ctl| Arc::new(Mutex::new(ctl))));
75
76#[derive(Copy, Clone, Debug)]
78pub struct JemallocProfMetadata {
79 pub start_time: Option<ProfStartTime>,
80}
81
82#[derive(Debug)]
84pub struct JemallocProfCtl {
85 md: JemallocProfMetadata,
86}
87
88impl JemallocProfCtl {
89 fn get() -> Option<Self> {
91 let prof_enabled: bool = unsafe { raw::read(b"opt.prof\0") }.unwrap();
94 if prof_enabled {
95 let prof_active: bool = unsafe { raw::read(b"opt.prof_active\0") }.unwrap();
98 let start_time = if prof_active {
99 Some(ProfStartTime::TimeImmemorial)
100 } else {
101 None
102 };
103 let md = JemallocProfMetadata { start_time };
104 Some(Self { md })
105 } else {
106 None
107 }
108 }
109
110 pub fn lg_sample(&self) -> size_t {
112 unsafe { raw::read(b"prof.lg_sample\0") }.unwrap()
115 }
116
117 pub fn get_md(&self) -> JemallocProfMetadata {
119 self.md
120 }
121
122 pub fn activated(&self) -> bool {
124 self.md.start_time.is_some()
125 }
126
127 pub fn activate(&mut self) -> Result<(), tikv_jemalloc_ctl::Error> {
129 unsafe { raw::write(b"prof.active\0", true) }?;
132 if self.md.start_time.is_none() {
133 self.md.start_time = Some(ProfStartTime::Instant(Instant::now()));
134 }
135 Ok(())
136 }
137
138 pub fn deactivate(&mut self) -> Result<(), tikv_jemalloc_ctl::Error> {
140 unsafe { raw::write(b"prof.active\0", false) }?;
143 let rate = self.lg_sample();
144 unsafe { raw::write(b"prof.reset\0", rate) }?;
147
148 self.md.start_time = None;
149 Ok(())
150 }
151
152 pub fn dump(&mut self) -> anyhow::Result<std::fs::File> {
154 let f = NamedTempFile::new()?;
155 let path = CString::new(f.path().as_os_str().as_encoded_bytes()).unwrap();
156
157 unsafe { raw::write(b"prof.dump\0", path.as_ptr()) }?;
160 Ok(f.into_file())
161 }
162
163 pub fn dump_profile(&mut self) -> anyhow::Result<StackProfile> {
165 let f = self.dump()?;
166 let dump_reader = BufReader::new(f);
167 parse_jeheap(dump_reader, MAPPINGS.as_deref())
168 }
169
170 pub fn dump_pprof(&mut self) -> anyhow::Result<Vec<u8>> {
173 let profile = self.dump_profile()?;
174 let pprof = profile.to_pprof(("inuse_space", "bytes"), ("space", "bytes"), None);
175 Ok(pprof)
176 }
177
178 #[cfg(feature = "flamegraph")]
180 pub fn dump_flamegraph(&mut self) -> anyhow::Result<Vec<u8>> {
181 let mut opts = FlamegraphOptions::default();
182 opts.title = "inuse_space".to_string();
183 opts.count_name = "bytes".to_string();
184 self.dump_flamegraph_with_options(&mut opts)
185 }
186
187 #[cfg(feature = "flamegraph")]
189 pub fn dump_flamegraph_with_options(
190 &mut self,
191 opts: &mut FlamegraphOptions,
192 ) -> anyhow::Result<Vec<u8>> {
193 let profile = self.dump_profile()?;
194 profile.to_flamegraph(opts)
195 }
196}