1use crate::allocator;
2use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
3
4pub const PPROF_SAMPLE_RATE_ENV: &str = "PPROF_ALLOC_SAMPLE_RATE";
9
10pub const PPROF_BACKEND_ENV: &str = "PPROF_ALLOC_BACKEND";
18
19pub 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}