#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "rustdoc", feature(external_doc))]
#![cfg_attr(feature = "rustdoc", doc(include = "../README.md"))]
#![cfg_attr(not(feature = "rustdoc"), doc = "external documentation in README.md")]
#![warn(missing_docs)]
extern crate alloc;
use alloc::alloc::{GlobalAlloc, Layout};
use core::{
cell::Cell,
future::Future,
pin::Pin,
task::{Context, Poll},
};
#[cfg(feature = "macros")]
pub use alloc_counter_macro::{count_alloc, no_alloc};
thread_local!(static COUNTERS: Cell<Counters> = Cell::default());
thread_local!(static MODE: Cell<AllocMode> = Cell::new(AllocMode::Count));
pub type Counters = (usize, usize, usize);
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum AllocMode {
Ignore,
Count,
CountAll,
}
pub struct AllocCounter<A>(pub A);
#[cfg(feature = "std")]
pub type AllocCounterSystem = AllocCounter<std::alloc::System>;
#[cfg(feature = "std")]
#[allow(non_upper_case_globals)]
pub const AllocCounterSystem: AllocCounterSystem = AllocCounter(std::alloc::System);
unsafe impl<A> GlobalAlloc for AllocCounter<A>
where
A: GlobalAlloc,
{
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if MODE.with(Cell::get) != AllocMode::Ignore {
COUNTERS.with(|x| {
let mut c = x.get();
c.0 += 1;
x.set(c);
});
}
self.0.alloc(layout)
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if MODE.with(Cell::get) != AllocMode::Ignore {
COUNTERS.with(|x| {
let mut c = x.get();
c.1 += 1;
x.set(c);
});
}
self.0.realloc(ptr, layout, new_size)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
if MODE.with(Cell::get) != AllocMode::Ignore {
COUNTERS.with(|x| {
let mut c = x.get();
c.2 += 1;
x.set(c);
});
}
self.0.dealloc(ptr, layout);
}
}
struct Guard(AllocMode);
impl Drop for Guard {
fn drop(&mut self) {
MODE.with(|x| x.set(self.0))
}
}
impl Guard {
fn new(mode: AllocMode) -> Self {
Guard(MODE.with(|x| {
if x.get() != AllocMode::CountAll {
x.replace(mode)
} else {
AllocMode::CountAll
}
}))
}
}
pub fn count_alloc<F, R>(f: F) -> (Counters, R)
where
F: FnOnce() -> R,
{
let (a1, r1, d1) = COUNTERS.with(Cell::get);
let r = f();
let (a2, r2, d2) = COUNTERS.with(Cell::get);
((a2 - a1, r2 - r1, d2 - d1), r)
}
pub fn count_alloc_future<F>(future: F) -> AsyncGuard<F> {
AsyncGuard {
future,
counts: Default::default(),
}
}
pub struct AsyncGuard<F> {
counts: Counters,
future: F,
}
impl<F> AsyncGuard<F>
where
F: Future,
{
pin_utils::unsafe_pinned!(future: F);
pin_utils::unsafe_pinned!(counts: Counters);
}
impl<F> Future for AsyncGuard<F>
where
F: Future,
{
type Output = (Counters, F::Output);
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let ((a, r, d), x) = count_alloc(|| self.as_mut().future().poll(cx));
let counts = self.counts().get_mut();
counts.0 += a;
counts.1 += r;
counts.2 += d;
match x {
Poll::Ready(x) => Poll::Ready((*counts, x)),
Poll::Pending => Poll::Pending,
}
}
}
pub fn guard_fn<F, R>(mode: AllocMode, f: F) -> R
where
F: FnOnce() -> R,
{
let _guard = Guard::new(mode);
let ((allocations, reallocations, deallocations), x) = count_alloc(f);
if mode != AllocMode::Ignore && (allocations, reallocations, deallocations) != (0, 0, 0) {
panic!(
"allocations: {}, reallocations: {}, deallocations: {}",
allocations, reallocations, deallocations,
)
}
x
}
pub async fn guard_future<F>(mode: AllocMode, f: F) -> F::Output
where
F: Future,
{
let _guard = Guard::new(mode);
let ((allocations, reallocations, deallocations), x) = count_alloc_future(f).await;
if mode != AllocMode::Ignore && (allocations, reallocations, deallocations) != (0, 0, 0) {
panic!(
"allocations: {}, reallocations: {}, deallocations: {}",
allocations, reallocations, deallocations,
)
}
x
}
pub fn allow_alloc<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
guard_fn(AllocMode::Ignore, f)
}
pub fn deny_alloc<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
guard_fn(AllocMode::Count, f)
}
pub fn forbid_alloc<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
guard_fn(AllocMode::CountAll, f)
}