Skip to main content

pprof_alloc/
env.rs

1use crate::allocator;
2use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
3
4/// Environment variable read by `PprofAlloc::with_pprof_sample_rate_from_env`.
5///
6/// The value must be an unsigned integer byte rate. Missing or invalid values
7/// use the default passed to `with_pprof_sample_rate_from_env`.
8pub const PPROF_SAMPLE_RATE_ENV: &str = "PPROF_ALLOC_SAMPLE_RATE";
9
10/// Environment variable that selects the pprof backend when native jemalloc
11/// profiling support is compiled in.
12///
13/// Set this before process startup. Supported wrapper values are `wrapper`,
14/// `pprof-alloc`, and `rust`; any other value, including an unset variable,
15/// selects native jemalloc profiling when the `allocator-jemalloc` feature is
16/// enabled and the active allocator is jemalloc.
17pub const PPROF_BACKEND_ENV: &str = "PPROF_ALLOC_BACKEND";
18
19/// Environment variable read by `PprofAlloc` to select the allocator backend.
20///
21/// Supported values are `system`/`glibc`, `jemalloc`, and `mimalloc`. The
22/// selected allocator is read once on first allocator use and remains fixed for
23/// the process. `ALLOCATOR` is also accepted as a compatibility fallback.
24pub const ALLOCATOR_ENV: &str = "PPROF_ALLOC_ALLOCATOR";
25
26const PPROF_SAMPLE_RATE_ENV_CSTR: &[u8] = b"PPROF_ALLOC_SAMPLE_RATE\0";
27const PPROF_BACKEND_ENV_CSTR: &[u8] = b"PPROF_ALLOC_BACKEND\0";
28const ALLOCATOR_ENV_CSTR: &[u8] = b"PPROF_ALLOC_ALLOCATOR\0";
29const ALLOCATOR_COMPAT_ENV_CSTR: &[u8] = b"ALLOCATOR\0";
30const ENV_SAMPLE_RATE_UNINITIALIZED: usize = usize::MAX;
31const ENV_SAMPLE_RATE_UNSET: usize = usize::MAX - 1;
32
33#[repr(u8)]
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub(crate) enum PprofBackend {
36	Uninitialized = 0,
37	Wrapper = 1,
38	Native = 2,
39}
40
41impl PprofBackend {
42	const fn as_u8(self) -> u8 {
43		self as u8
44	}
45
46	const fn from_u8(value: u8) -> Self {
47		match value {
48			1 => Self::Wrapper,
49			2 => Self::Native,
50			_ => Self::Uninitialized,
51		}
52	}
53}
54
55#[repr(u8)]
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum Allocator {
58	System = 1,
59	Jemalloc = 2,
60	Mimalloc = 3,
61}
62
63impl Allocator {
64	const fn as_selection(self) -> AllocatorSelection {
65		match self {
66			Self::System => AllocatorSelection::System,
67			Self::Jemalloc => AllocatorSelection::Jemalloc,
68			Self::Mimalloc => AllocatorSelection::Mimalloc,
69		}
70	}
71}
72
73#[repr(u8)]
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub(crate) enum AllocatorSelection {
76	Uninitialized = 0,
77	System = 1,
78	Jemalloc = 2,
79	Mimalloc = 3,
80}
81
82impl AllocatorSelection {
83	const fn as_u8(self) -> u8 {
84		self as u8
85	}
86
87	const fn from_u8(value: u8) -> Self {
88		match value {
89			1 => Self::System,
90			2 => Self::Jemalloc,
91			3 => Self::Mimalloc,
92			_ => Self::Uninitialized,
93		}
94	}
95}
96
97static ENV_PPROF_SAMPLE_RATE: AtomicUsize = AtomicUsize::new(ENV_SAMPLE_RATE_UNINITIALIZED);
98static ENV_PPROF_BACKEND: AtomicU8 = AtomicU8::new(PprofBackend::Uninitialized.as_u8());
99static ENV_ALLOCATOR: AtomicU8 = AtomicU8::new(AllocatorSelection::Uninitialized.as_u8());
100
101pub(crate) fn pprof_sample_rate(default_rate: usize) -> usize {
102	match ENV_PPROF_SAMPLE_RATE.load(Ordering::Relaxed) {
103		ENV_SAMPLE_RATE_UNINITIALIZED => {},
104		ENV_SAMPLE_RATE_UNSET => return default_rate,
105		value => return value,
106	}
107
108	if let Some(sample_rate) = read_pprof_sample_rate_env() {
109		ENV_PPROF_SAMPLE_RATE.store(sample_rate, Ordering::Relaxed);
110		sample_rate
111	} else {
112		ENV_PPROF_SAMPLE_RATE.store(ENV_SAMPLE_RATE_UNSET, Ordering::Relaxed);
113		default_rate
114	}
115}
116
117pub(crate) fn selected_pprof_backend() -> PprofBackend {
118	match PprofBackend::from_u8(ENV_PPROF_BACKEND.load(Ordering::Relaxed)) {
119		PprofBackend::Uninitialized => {
120			let backend = read_pprof_backend_env();
121			ENV_PPROF_BACKEND.store(backend.as_u8(), Ordering::Relaxed);
122			backend
123		},
124		backend => backend,
125	}
126}
127
128pub(crate) fn selected_allocator(default: Allocator) -> AllocatorSelection {
129	let selected = AllocatorSelection::from_u8(ENV_ALLOCATOR.load(Ordering::Relaxed));
130	match selected {
131		AllocatorSelection::System | AllocatorSelection::Jemalloc | AllocatorSelection::Mimalloc => {
132			selected
133		},
134		AllocatorSelection::Uninitialized => {
135			let selected = read_allocator_env_override()
136				.unwrap_or_else(|| validate_allocator_selection(default.as_selection(), false));
137			match selected {
138				AllocatorSelection::Jemalloc => allocator::configure(allocator::AllocatorKind::Jemalloc),
139				AllocatorSelection::Mimalloc => allocator::configure(allocator::AllocatorKind::Mimalloc),
140				_ => allocator::configure(allocator::AllocatorKind::Glibc),
141			}
142			ENV_ALLOCATOR.store(selected.as_u8(), Ordering::Relaxed);
143			selected
144		},
145	}
146}
147
148pub(crate) fn cached_allocator() -> Option<AllocatorSelection> {
149	match AllocatorSelection::from_u8(ENV_ALLOCATOR.load(Ordering::Relaxed)) {
150		AllocatorSelection::Uninitialized => None,
151		selected => Some(selected),
152	}
153}
154
155fn read_pprof_sample_rate_env() -> Option<usize> {
156	let ptr = unsafe { libc::getenv(PPROF_SAMPLE_RATE_ENV_CSTR.as_ptr().cast()) };
157	if ptr.is_null() {
158		return None;
159	}
160
161	let mut value = 0usize;
162	let mut cursor = ptr.cast::<u8>();
163	let mut saw_digit = false;
164	loop {
165		let byte = unsafe { *cursor };
166		if byte == 0 {
167			break;
168		}
169		if !byte.is_ascii_digit() {
170			return None;
171		}
172		saw_digit = true;
173		value = value
174			.saturating_mul(10)
175			.saturating_add((byte - b'0') as usize);
176		cursor = unsafe { cursor.add(1) };
177	}
178
179	saw_digit.then_some(value)
180}
181
182fn read_pprof_backend_env() -> PprofBackend {
183	let ptr = unsafe { libc::getenv(PPROF_BACKEND_ENV_CSTR.as_ptr().cast()) };
184	if ptr.is_null() {
185		return PprofBackend::Native;
186	}
187	let ptr = ptr.cast();
188	if cstr_eq_ignore_ascii(ptr, b"wrapper")
189		|| cstr_eq_ignore_ascii(ptr, b"pprof-alloc")
190		|| cstr_eq_ignore_ascii(ptr, b"rust")
191	{
192		PprofBackend::Wrapper
193	} else {
194		PprofBackend::Native
195	}
196}
197
198fn read_allocator_env_override() -> Option<AllocatorSelection> {
199	let mut ptr = unsafe { libc::getenv(ALLOCATOR_ENV_CSTR.as_ptr().cast()) };
200	if ptr.is_null() {
201		ptr = unsafe { libc::getenv(ALLOCATOR_COMPAT_ENV_CSTR.as_ptr().cast()) };
202	}
203	if ptr.is_null() {
204		return None;
205	}
206
207	let ptr = ptr.cast();
208	if cstr_eq_ignore_ascii(ptr, b"jemalloc") {
209		return Some(validate_allocator_selection(
210			AllocatorSelection::Jemalloc,
211			true,
212		));
213	}
214	if cstr_eq_ignore_ascii(ptr, b"mimalloc") {
215		return Some(validate_allocator_selection(
216			AllocatorSelection::Mimalloc,
217			true,
218		));
219	}
220	Some(AllocatorSelection::System)
221}
222
223fn validate_allocator_selection(
224	selection: AllocatorSelection,
225	from_env: bool,
226) -> AllocatorSelection {
227	match selection {
228		AllocatorSelection::Jemalloc if !cfg!(feature = "allocator-jemalloc") => {
229			if from_env {
230				unavailable_allocator_selected(
231					b"PPROF_ALLOC_ALLOCATOR=jemalloc requires the allocator-jemalloc feature\n",
232				);
233			}
234			unavailable_allocator_selected(
235				b"PprofAlloc default allocator jemalloc requires the allocator-jemalloc feature\n",
236			);
237		},
238		AllocatorSelection::Mimalloc if !cfg!(feature = "allocator-mimalloc") => {
239			if from_env {
240				unavailable_allocator_selected(
241					b"PPROF_ALLOC_ALLOCATOR=mimalloc requires the allocator-mimalloc feature\n",
242				);
243			}
244			unavailable_allocator_selected(
245				b"PprofAlloc default allocator mimalloc requires the allocator-mimalloc feature\n",
246			);
247		},
248		selection => selection,
249	}
250}
251
252fn unavailable_allocator_selected(message: &'static [u8]) -> ! {
253	unsafe {
254		let _ = libc::write(libc::STDERR_FILENO, message.as_ptr().cast(), message.len());
255		libc::_exit(1);
256	}
257}
258
259fn cstr_eq_ignore_ascii(mut ptr: *const u8, expected: &[u8]) -> bool {
260	for expected_byte in expected {
261		let byte = unsafe { *ptr };
262		if byte == 0 || !byte.eq_ignore_ascii_case(expected_byte) {
263			return false;
264		}
265		ptr = unsafe { ptr.add(1) };
266	}
267	unsafe { *ptr == 0 }
268}
269
270#[cfg(test)]
271pub(crate) fn reset_for_tests() {
272	ENV_PPROF_SAMPLE_RATE.store(ENV_SAMPLE_RATE_UNINITIALIZED, Ordering::Relaxed);
273	ENV_PPROF_BACKEND.store(PprofBackend::Uninitialized.as_u8(), Ordering::Relaxed);
274	ENV_ALLOCATOR.store(AllocatorSelection::Uninitialized.as_u8(), Ordering::Relaxed);
275	unsafe {
276		std::env::remove_var(ALLOCATOR_ENV);
277		std::env::remove_var("ALLOCATOR");
278		std::env::remove_var(PPROF_BACKEND_ENV);
279	}
280}
281
282#[cfg(all(test, feature = "allocator-jemalloc"))]
283pub(crate) fn reset_allocator_for_tests() {
284	ENV_ALLOCATOR.store(AllocatorSelection::Uninitialized.as_u8(), Ordering::Relaxed);
285}
286
287#[cfg(all(test, feature = "allocator-jemalloc"))]
288pub(crate) fn reset_pprof_backend_for_tests() {
289	ENV_PPROF_BACKEND.store(PprofBackend::Uninitialized.as_u8(), Ordering::Relaxed);
290}