jemalloc_pprof/
lib.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![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, ProfStartTime};
33
34/// Activate jemalloc profiling.
35pub async fn activate_jemalloc_profiling() {
36    let Some(ctl) = PROF_CTL.as_ref() else {
37        tracing::warn!("jemalloc profiling is disabled and cannot be activated");
38        return;
39    };
40
41    let mut ctl = ctl.lock().await;
42    if ctl.activated() {
43        return;
44    }
45
46    match ctl.activate() {
47        Ok(()) => tracing::info!("jemalloc profiling activated"),
48        Err(err) => tracing::warn!("could not activate jemalloc profiling: {err}"),
49    }
50}
51
52/// Deactivate jemalloc profiling.
53pub async fn deactivate_jemalloc_profiling() {
54    let Some(ctl) = PROF_CTL.as_ref() else {
55        return; // jemalloc not enabled
56    };
57
58    let mut ctl = ctl.lock().await;
59    if !ctl.activated() {
60        return;
61    }
62
63    match ctl.deactivate() {
64        Ok(()) => tracing::info!("jemalloc profiling deactivated"),
65        Err(err) => tracing::warn!("could not deactivate jemalloc profiling: {err}"),
66    }
67}
68
69/// Per-process singleton for controlling jemalloc profiling.
70pub static PROF_CTL: Lazy<Option<Arc<Mutex<JemallocProfCtl>>>> =
71    Lazy::new(|| JemallocProfCtl::get().map(|ctl| Arc::new(Mutex::new(ctl))));
72
73/// Metadata about a jemalloc heap profiler.
74#[derive(Copy, Clone, Debug)]
75pub struct JemallocProfMetadata {
76    pub start_time: Option<ProfStartTime>,
77}
78
79/// A handle to control jemalloc profiling.
80#[derive(Debug)]
81pub struct JemallocProfCtl {
82    md: JemallocProfMetadata,
83}
84
85impl JemallocProfCtl {
86    // Creates and returns the global singleton.
87    fn get() -> Option<Self> {
88        // SAFETY: "opt.prof" is documented as being readable and returning a bool:
89        // http://jemalloc.net/jemalloc.3.html#opt.prof
90        let prof_enabled: bool = unsafe { raw::read(b"opt.prof\0") }.unwrap();
91        if prof_enabled {
92            // SAFETY: "opt.prof_active" is documented as being readable and returning a bool:
93            // http://jemalloc.net/jemalloc.3.html#opt.prof_active
94            let prof_active: bool = unsafe { raw::read(b"opt.prof_active\0") }.unwrap();
95            let start_time = if prof_active {
96                Some(ProfStartTime::TimeImmemorial)
97            } else {
98                None
99            };
100            let md = JemallocProfMetadata { start_time };
101            Some(Self { md })
102        } else {
103            None
104        }
105    }
106
107    /// Returns the base 2 logarithm of the sample rate (average interval, in bytes, between allocation samples).
108    pub fn lg_sample(&self) -> size_t {
109        // SAFETY: "prof.lg_sample" is documented as being readable and returning size_t:
110        // https://jemalloc.net/jemalloc.3.html#opt.lg_prof_sample
111        unsafe { raw::read(b"prof.lg_sample\0") }.unwrap()
112    }
113
114    /// Returns the metadata of the profiler.
115    pub fn get_md(&self) -> JemallocProfMetadata {
116        self.md
117    }
118
119    /// Returns whether the profiler is active.
120    pub fn activated(&self) -> bool {
121        self.md.start_time.is_some()
122    }
123
124    /// Activate the profiler and if unset, set the start time to the current time.
125    pub fn activate(&mut self) -> Result<(), tikv_jemalloc_ctl::Error> {
126        // SAFETY: "prof.active" is documented as being writable and taking a bool:
127        // http://jemalloc.net/jemalloc.3.html#prof.active
128        unsafe { raw::write(b"prof.active\0", true) }?;
129        if self.md.start_time.is_none() {
130            self.md.start_time = Some(ProfStartTime::Instant(Instant::now()));
131        }
132        Ok(())
133    }
134
135    /// Deactivate the profiler.
136    pub fn deactivate(&mut self) -> Result<(), tikv_jemalloc_ctl::Error> {
137        // SAFETY: "prof.active" is documented as being writable and taking a bool:
138        // http://jemalloc.net/jemalloc.3.html#prof.active
139        unsafe { raw::write(b"prof.active\0", false) }?;
140        let rate = self.lg_sample();
141        // SAFETY: "prof.reset" is documented as being writable and taking a size_t:
142        // http://jemalloc.net/jemalloc.3.html#prof.reset
143        unsafe { raw::write(b"prof.reset\0", rate) }?;
144
145        self.md.start_time = None;
146        Ok(())
147    }
148
149    /// Dump a profile into a temporary file and return it.
150    pub fn dump(&mut self) -> anyhow::Result<std::fs::File> {
151        let f = NamedTempFile::new()?;
152        let path = CString::new(f.path().as_os_str().as_encoded_bytes()).unwrap();
153
154        // SAFETY: "prof.dump" is documented as being writable and taking a C string as input:
155        // http://jemalloc.net/jemalloc.3.html#prof.dump
156        unsafe { raw::write(b"prof.dump\0", path.as_ptr()) }?;
157        Ok(f.into_file())
158    }
159
160    /// Dump a profile in pprof format (gzipped protobuf) and
161    /// return a buffer with its contents.
162    pub fn dump_pprof(&mut self) -> anyhow::Result<Vec<u8>> {
163        let f = self.dump()?;
164        let dump_reader = BufReader::new(f);
165        let profile = parse_jeheap(dump_reader, MAPPINGS.as_deref())?;
166        let pprof = profile.to_pprof(("inuse_space", "bytes"), ("space", "bytes"), None);
167        Ok(pprof)
168    }
169}