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.
1516#![doc = include_str!("../README.md")]
1718use std::ffi::CString;
1920use std::io::BufReader;
21use std::sync::Arc;
22use std::time::Instant;
2324use libc::size_t;
25use once_cell::sync::Lazy;
2627use mappings::MAPPINGS;
28use tempfile::NamedTempFile;
29use tikv_jemalloc_ctl::raw;
30use tokio::sync::Mutex;
3132use util::{parse_jeheap, ProfStartTime};
3334/// Activate jemalloc profiling.
35pub async fn activate_jemalloc_profiling() {
36let Some(ctl) = PROF_CTL.as_ref() else {
37tracing::warn!("jemalloc profiling is disabled and cannot be activated");
38return;
39 };
4041let mut ctl = ctl.lock().await;
42if ctl.activated() {
43return;
44 }
4546match ctl.activate() {
47Ok(()) => tracing::info!("jemalloc profiling activated"),
48Err(err) => tracing::warn!("could not activate jemalloc profiling: {err}"),
49 }
50}
5152/// Deactivate jemalloc profiling.
53pub async fn deactivate_jemalloc_profiling() {
54let Some(ctl) = PROF_CTL.as_ref() else {
55return; // jemalloc not enabled
56};
5758let mut ctl = ctl.lock().await;
59if !ctl.activated() {
60return;
61 }
6263match ctl.deactivate() {
64Ok(()) => tracing::info!("jemalloc profiling deactivated"),
65Err(err) => tracing::warn!("could not deactivate jemalloc profiling: {err}"),
66 }
67}
6869/// 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))));
7273/// Metadata about a jemalloc heap profiler.
74#[derive(Copy, Clone, Debug)]
75pub struct JemallocProfMetadata {
76pub start_time: Option<ProfStartTime>,
77}
7879/// A handle to control jemalloc profiling.
80#[derive(Debug)]
81pub struct JemallocProfCtl {
82 md: JemallocProfMetadata,
83}
8485impl JemallocProfCtl {
86// Creates and returns the global singleton.
87fn 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
90let prof_enabled: bool = unsafe { raw::read(b"opt.prof\0") }.unwrap();
91if 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
94let prof_active: bool = unsafe { raw::read(b"opt.prof_active\0") }.unwrap();
95let start_time = if prof_active {
96Some(ProfStartTime::TimeImmemorial)
97 } else {
98None
99};
100let md = JemallocProfMetadata { start_time };
101Some(Self { md })
102 } else {
103None
104}
105 }
106107/// Returns the base 2 logarithm of the sample rate (average interval, in bytes, between allocation samples).
108pub 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
111unsafe { raw::read(b"prof.lg_sample\0") }.unwrap()
112 }
113114/// Returns the metadata of the profiler.
115pub fn get_md(&self) -> JemallocProfMetadata {
116self.md
117 }
118119/// Returns whether the profiler is active.
120pub fn activated(&self) -> bool {
121self.md.start_time.is_some()
122 }
123124/// Activate the profiler and if unset, set the start time to the current time.
125pub 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
128unsafe { raw::write(b"prof.active\0", true) }?;
129if self.md.start_time.is_none() {
130self.md.start_time = Some(ProfStartTime::Instant(Instant::now()));
131 }
132Ok(())
133 }
134135/// Deactivate the profiler.
136pub 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
139unsafe { raw::write(b"prof.active\0", false) }?;
140let 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
143unsafe { raw::write(b"prof.reset\0", rate) }?;
144145self.md.start_time = None;
146Ok(())
147 }
148149/// Dump a profile into a temporary file and return it.
150pub fn dump(&mut self) -> anyhow::Result<std::fs::File> {
151let f = NamedTempFile::new()?;
152let path = CString::new(f.path().as_os_str().as_encoded_bytes()).unwrap();
153154// SAFETY: "prof.dump" is documented as being writable and taking a C string as input:
155 // http://jemalloc.net/jemalloc.3.html#prof.dump
156unsafe { raw::write(b"prof.dump\0", path.as_ptr()) }?;
157Ok(f.into_file())
158 }
159160/// Dump a profile in pprof format (gzipped protobuf) and
161 /// return a buffer with its contents.
162pub fn dump_pprof(&mut self) -> anyhow::Result<Vec<u8>> {
163let f = self.dump()?;
164let dump_reader = BufReader::new(f);
165let profile = parse_jeheap(dump_reader, MAPPINGS.as_deref())?;
166let pprof = profile.to_pprof(("inuse_space", "bytes"), ("space", "bytes"), None);
167Ok(pprof)
168 }
169}