1#![cfg_attr(nightly_tls_active, feature(thread_local))]
7#![deny(missing_docs)]
8
9use core::ffi::c_void;
10use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
11
12mod sampler;
13#[cfg(test)]
14mod tests;
15mod tls;
16
17pub use sampler::{Sample, StackId, dump_leaks, dump_profile};
18
19pub(crate) use tls::{enter_hook, exit_hook, sample_debit, should_skip_alloc_fast_path};
20#[cfg(not(nightly_tls_active))]
26pub(crate) use tls::get_profiler_state;
27#[cfg(nightly_tls_active)]
28pub(crate) use tls::{get_bytes_until_sample, set_bytes_until_sample};
29
30static ALLOC_HOOK: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());
31static FREE_HOOK: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());
32
33static PROFILING_ACTIVE: AtomicBool = AtomicBool::new(false);
34static LEAK_DETECTOR_ACTIVE: AtomicBool = AtomicBool::new(false);
35static PROFILING_OR_HOOKS_ACTIVE: AtomicBool = AtomicBool::new(false);
36static SAMPLE_INTERVAL: AtomicUsize = AtomicUsize::new(512 * 1024); static UPDATE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
52
53fn store_flags_then_update_active(store: impl FnOnce()) {
56 let _guard = UPDATE_LOCK
57 .lock()
58 .unwrap_or_else(std::sync::PoisonError::into_inner);
59 store();
60 let active = PROFILING_ACTIVE.load(Ordering::Acquire)
61 || LEAK_DETECTOR_ACTIVE.load(Ordering::Acquire)
62 || !ALLOC_HOOK.load(Ordering::Acquire).is_null()
63 || !FREE_HOOK.load(Ordering::Acquire).is_null();
64 PROFILING_OR_HOOKS_ACTIVE.store(active, Ordering::Release);
65}
66
67pub fn register_alloc_hook(hook: Option<unsafe extern "C" fn(*mut core::ffi::c_void, usize)>) {
69 let ptr = match hook {
70 Some(f) => f as *mut c_void,
71 None => core::ptr::null_mut(),
72 };
73 store_flags_then_update_active(|| ALLOC_HOOK.store(ptr, Ordering::Release));
74}
75
76pub fn register_free_hook(hook: Option<unsafe extern "C" fn(*mut core::ffi::c_void, usize)>) {
78 let ptr = match hook {
79 Some(f) => f as *mut c_void,
80 None => core::ptr::null_mut(),
81 };
82 store_flags_then_update_active(|| FREE_HOOK.store(ptr, Ordering::Release));
83}
84
85pub fn enable_profiling(sample_interval: usize) {
87 store_flags_then_update_active(|| {
88 SAMPLE_INTERVAL.store(sample_interval, Ordering::Release);
89 PROFILING_ACTIVE.store(true, Ordering::Release);
90 });
91}
92
93pub fn disable_profiling() {
95 store_flags_then_update_active(|| PROFILING_ACTIVE.store(false, Ordering::Release));
96}
97
98pub fn is_profiling_enabled() -> bool {
100 PROFILING_ACTIVE.load(Ordering::Acquire)
101}
102
103pub fn reset_profiler_for_testing() {
105 store_flags_then_update_active(|| {
106 PROFILING_ACTIVE.store(false, Ordering::Release);
107 LEAK_DETECTOR_ACTIVE.store(false, Ordering::Release);
108 ALLOC_HOOK.store(core::ptr::null_mut(), Ordering::Release);
109 FREE_HOOK.store(core::ptr::null_mut(), Ordering::Release);
110 SAMPLE_INTERVAL.store(512 * 1024, Ordering::Release);
111 });
112 sampler::reset_sampler_state();
115}
116
117pub fn enable_leak_detector() {
119 store_flags_then_update_active(|| LEAK_DETECTOR_ACTIVE.store(true, Ordering::Release));
120}
121
122pub fn disable_leak_detector() {
124 store_flags_then_update_active(|| LEAK_DETECTOR_ACTIVE.store(false, Ordering::Release));
125}
126
127pub fn is_leak_detector_enabled() -> bool {
129 LEAK_DETECTOR_ACTIVE.load(Ordering::Acquire)
130}
131
132#[inline(always)]
135pub fn is_active() -> bool {
136 PROFILING_OR_HOOKS_ACTIVE.load(Ordering::Relaxed)
137}
138
139#[inline(always)]
144pub fn on_alloc(ptr: *mut u8, size: usize) {
145 if !PROFILING_OR_HOOKS_ACTIVE.load(Ordering::Relaxed) {
146 return;
147 }
148
149 let hook_ptr = ALLOC_HOOK.load(Ordering::Relaxed);
150 let leak_inactive = !LEAK_DETECTOR_ACTIVE.load(Ordering::Relaxed);
154 if should_skip_alloc_fast_path(size, hook_ptr.is_null(), leak_inactive) {
155 return;
156 }
157
158 on_alloc_cold(ptr, size);
159}
160
161#[inline(never)]
162fn on_alloc_cold(ptr: *mut u8, size: usize) {
163 if ptr.is_null() {
164 return;
165 }
166
167 let hook_ptr = ALLOC_HOOK.load(Ordering::Relaxed);
168 let active = PROFILING_ACTIVE.load(Ordering::Relaxed);
169 let leak_active = LEAK_DETECTOR_ACTIVE.load(Ordering::Relaxed);
170 if hook_ptr.is_null() && !active && !leak_active {
171 return;
172 }
173
174 let in_hook = enter_hook();
175 if in_hook {
176 return;
177 }
178
179 if !hook_ptr.is_null() {
180 let hook: unsafe extern "C" fn(*mut core::ffi::c_void, usize) =
186 unsafe { core::mem::transmute(hook_ptr) };
187 unsafe { hook(ptr as *mut core::ffi::c_void, size) };
190 }
191
192 if active || leak_active {
193 sampler::sample_alloc_inner(ptr, size, leak_active);
194 }
195
196 exit_hook();
197}
198
199#[inline(always)]
207pub fn on_free(ptr: *mut u8, size: usize) {
208 if FREE_HOOK.load(Ordering::Relaxed).is_null() && !sampler::has_active_sample_for(ptr as usize)
212 {
213 return;
214 }
215
216 on_free_cold(ptr, size);
217}
218
219#[inline(never)]
220fn on_free_cold(ptr: *mut u8, size: usize) {
221 if ptr.is_null() {
222 return;
223 }
224
225 let hook_ptr = FREE_HOOK.load(Ordering::Relaxed);
226 let samples_resident = sampler::has_active_sample_for(ptr as usize);
234 if hook_ptr.is_null() && !samples_resident {
235 return;
236 }
237
238 let in_hook = enter_hook();
239 if in_hook {
240 return;
241 }
242
243 if !hook_ptr.is_null() {
244 let hook: unsafe extern "C" fn(*mut core::ffi::c_void, usize) =
249 unsafe { core::mem::transmute(hook_ptr) };
250 unsafe { hook(ptr as *mut core::ffi::c_void, size) };
253 }
254
255 if samples_resident {
256 sampler::sample_free_inner(ptr);
257 }
258
259 exit_hook();
260}