1#[cfg(not(windows))]
2use crate::allocator;
3use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
4
5pub const PPROF_SAMPLE_RATE_ENV: &str = "PPROF_ALLOC_SAMPLE_RATE";
10
11pub const PPROF_BACKEND_ENV: &str = "PPROF_ALLOC_BACKEND";
19
20pub 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}