Skip to main content

pprof_alloc/
env.rs

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