# alloc_counter ## Alloc counters A redesign of the [Quick and Dirty Allocation Profiling Tool](https://github.com/bspeice/qadapt). ### Features * Although `#[no_std]` is intended, it isn't currently supported due to the lack of portable `thread_local` implementation. This may be "fixed" with a feature flag to use a global atomic under the contract that the program is single threaded. * Count allocations, reallocations and deallocations individually with `count_alloc`. * Allow, deny, and forbid use of the global allocator with `allow_alloc`, `deny_alloc` and `forbid_alloc`. * `#[no_alloc]` function attribute to deny and `#[no_alloc(forbid)]` to forbid use of the global allocator. * `#[count_alloc]` function attribute to print the counts to stderr. Alternatively use `#[count_alloc(func = "my_function")]` where `my_function` accepts a triple of `usize`s and returns `()` to redirect the output. ### Limitations and known issues * Methods must either take a reference to `self` or `Self` must be a `Copy` type. * Ordinary and async functions must be treated differently. Use `count_alloc` for functions and `count_alloc_future` for futures. ### Usage An `AllocCounter` wraps an allocator `A` to individually count the number of calls to `alloc`, `realloc`, and `dealloc`. ```rust use alloc_counter::AllocCounter; type MyAllocator = std::alloc::System; const MyAllocator: MyAllocator = std::alloc::System; #[global_allocator] static A: AllocCounter = AllocCounter(MyAllocator); ``` Std-users may prefer to inherit their system's allocator. ```rust use alloc_counter::AllocCounterSystem; #[global_allocator] static A: AllocCounterSystem = AllocCounterSystem; ``` To count the allocations of an expression, use `count_alloc`. ```rust # use alloc_counter::{count_alloc, AllocCounterSystem}; # #[global_allocator] # static A: AllocCounterSystem = AllocCounterSystem; let (counts, v) = count_alloc(|| { // no alloc let mut v = Vec::new(); // alloc v.push(0); // realloc v.push(1); // return the vector without deallocating v }); assert_eq!(counts, (1, 1, 0)); ``` To deny allocations for an expression use `deny_alloc`. ```rust,should_panic # use alloc_counter::{deny_alloc, AllocCounterSystem}; # #[global_allocator] # static A: AllocCounterSystem = AllocCounterSystem; fn foo(b: Box) { // dropping causes a panic deny_alloc(|| drop(b)) } foo(Box::new(0)); ``` Similar to Rust's lints, you can still allow allocation inside a deny block. ```rust # use alloc_counter::{allow_alloc, deny_alloc, AllocCounterSystem}; # #[global_allocator] # static A: AllocCounterSystem = AllocCounterSystem; fn foo(b: Box) { deny_alloc(|| allow_alloc(|| drop(b))) } foo(Box::new(0)); ``` Forbidding allocations forces a panic even when `allow_alloc` is used. ```rust,should_panic # use alloc_counter::{allow_alloc, forbid_alloc, AllocCounterSystem}; # #[global_allocator] # static A: AllocCounterSystem = AllocCounterSystem; fn foo(b: Box) { // panics because of outer `forbid`, even though drop happens in an allow block forbid_alloc(|| allow_alloc(|| drop(b))) } foo(Box::new(0)); ``` For added sugar you may use the `#[no_alloc]` attribute on functions, including methods with self-binds. `#[no_alloc]` expands to calling `deny_alloc` and forcefully moves the parameters into the checked block. `#[no_alloc(forbid)]` calls `forbid_alloc`. ```rust,should_panic # #[cfg(not(feature = "macros"))] # panic!("macros feature disabled"); # #[cfg(feature = "macros")] { # use alloc_counter::{allow_alloc, no_alloc, AllocCounterSystem}; # #[global_allocator] # static A: AllocCounterSystem = AllocCounterSystem; #[no_alloc(forbid)] fn foo(b: Box) { allow_alloc(|| drop(b)) } foo(Box::new(0)); # } ``` License: MIT OR Apache-2.0