#![cfg_attr(nightly_tls_active, feature(thread_local))]
#![deny(missing_docs)]
use core::ffi::c_void;
use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
mod sampler;
#[cfg(test)]
mod tests;
mod tls;
pub use sampler::{Sample, StackId, dump_leaks, dump_profile};
pub(crate) use tls::{enter_hook, exit_hook, sample_debit, should_skip_alloc_fast_path};
#[cfg(not(nightly_tls_active))]
pub(crate) use tls::get_profiler_state;
#[cfg(nightly_tls_active)]
pub(crate) use tls::{get_bytes_until_sample, set_bytes_until_sample};
static ALLOC_HOOK: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());
static FREE_HOOK: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());
static PROFILING_ACTIVE: AtomicBool = AtomicBool::new(false);
static LEAK_DETECTOR_ACTIVE: AtomicBool = AtomicBool::new(false);
static PROFILING_OR_HOOKS_ACTIVE: AtomicBool = AtomicBool::new(false);
static SAMPLE_INTERVAL: AtomicUsize = AtomicUsize::new(512 * 1024);
static UPDATE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn store_flags_then_update_active(store: impl FnOnce()) {
let _guard = UPDATE_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
store();
let active = PROFILING_ACTIVE.load(Ordering::Acquire)
|| LEAK_DETECTOR_ACTIVE.load(Ordering::Acquire)
|| !ALLOC_HOOK.load(Ordering::Acquire).is_null()
|| !FREE_HOOK.load(Ordering::Acquire).is_null();
PROFILING_OR_HOOKS_ACTIVE.store(active, Ordering::Release);
}
pub fn register_alloc_hook(hook: Option<unsafe extern "C" fn(*mut core::ffi::c_void, usize)>) {
let ptr = match hook {
Some(f) => f as *mut c_void,
None => core::ptr::null_mut(),
};
store_flags_then_update_active(|| ALLOC_HOOK.store(ptr, Ordering::Release));
}
pub fn register_free_hook(hook: Option<unsafe extern "C" fn(*mut core::ffi::c_void, usize)>) {
let ptr = match hook {
Some(f) => f as *mut c_void,
None => core::ptr::null_mut(),
};
store_flags_then_update_active(|| FREE_HOOK.store(ptr, Ordering::Release));
}
pub fn enable_profiling(sample_interval: usize) {
store_flags_then_update_active(|| {
SAMPLE_INTERVAL.store(sample_interval, Ordering::Release);
PROFILING_ACTIVE.store(true, Ordering::Release);
});
}
pub fn disable_profiling() {
store_flags_then_update_active(|| PROFILING_ACTIVE.store(false, Ordering::Release));
}
pub fn is_profiling_enabled() -> bool {
PROFILING_ACTIVE.load(Ordering::Acquire)
}
pub fn reset_profiler_for_testing() {
store_flags_then_update_active(|| {
PROFILING_ACTIVE.store(false, Ordering::Release);
LEAK_DETECTOR_ACTIVE.store(false, Ordering::Release);
ALLOC_HOOK.store(core::ptr::null_mut(), Ordering::Release);
FREE_HOOK.store(core::ptr::null_mut(), Ordering::Release);
SAMPLE_INTERVAL.store(512 * 1024, Ordering::Release);
});
sampler::reset_sampler_state();
}
pub fn enable_leak_detector() {
store_flags_then_update_active(|| LEAK_DETECTOR_ACTIVE.store(true, Ordering::Release));
}
pub fn disable_leak_detector() {
store_flags_then_update_active(|| LEAK_DETECTOR_ACTIVE.store(false, Ordering::Release));
}
pub fn is_leak_detector_enabled() -> bool {
LEAK_DETECTOR_ACTIVE.load(Ordering::Acquire)
}
#[inline(always)]
pub fn is_active() -> bool {
PROFILING_OR_HOOKS_ACTIVE.load(Ordering::Relaxed)
}
#[inline(always)]
pub fn on_alloc(ptr: *mut u8, size: usize) {
if !PROFILING_OR_HOOKS_ACTIVE.load(Ordering::Relaxed) {
return;
}
let hook_ptr = ALLOC_HOOK.load(Ordering::Relaxed);
let leak_inactive = !LEAK_DETECTOR_ACTIVE.load(Ordering::Relaxed);
if should_skip_alloc_fast_path(size, hook_ptr.is_null(), leak_inactive) {
return;
}
on_alloc_cold(ptr, size);
}
#[inline(never)]
fn on_alloc_cold(ptr: *mut u8, size: usize) {
if ptr.is_null() {
return;
}
let hook_ptr = ALLOC_HOOK.load(Ordering::Relaxed);
let active = PROFILING_ACTIVE.load(Ordering::Relaxed);
let leak_active = LEAK_DETECTOR_ACTIVE.load(Ordering::Relaxed);
if hook_ptr.is_null() && !active && !leak_active {
return;
}
let in_hook = enter_hook();
if in_hook {
return;
}
if !hook_ptr.is_null() {
let hook: unsafe extern "C" fn(*mut core::ffi::c_void, usize) =
unsafe { core::mem::transmute(hook_ptr) };
unsafe { hook(ptr as *mut core::ffi::c_void, size) };
}
if active || leak_active {
sampler::sample_alloc_inner(ptr, size, leak_active);
}
exit_hook();
}
#[inline(always)]
pub fn on_free(ptr: *mut u8, size: usize) {
if FREE_HOOK.load(Ordering::Relaxed).is_null() && !sampler::has_active_sample_for(ptr as usize)
{
return;
}
on_free_cold(ptr, size);
}
#[inline(never)]
fn on_free_cold(ptr: *mut u8, size: usize) {
if ptr.is_null() {
return;
}
let hook_ptr = FREE_HOOK.load(Ordering::Relaxed);
let samples_resident = sampler::has_active_sample_for(ptr as usize);
if hook_ptr.is_null() && !samples_resident {
return;
}
let in_hook = enter_hook();
if in_hook {
return;
}
if !hook_ptr.is_null() {
let hook: unsafe extern "C" fn(*mut core::ffi::c_void, usize) =
unsafe { core::mem::transmute(hook_ptr) };
unsafe { hook(ptr as *mut core::ffi::c_void, size) };
}
if samples_resident {
sampler::sample_free_inner(ptr);
}
exit_hook();
}