use std::{
alloc::{GlobalAlloc, Layout, System},
sync::atomic::{AtomicUsize, Ordering},
};
#[cfg(target_env = "sgx")]
unsafe extern "C" {
static HEAP_SIZE: usize;
}
pub struct InstrumentedSystemAllocator {
_private: (),
}
struct Counters {
current: AtomicUsize,
max: AtomicUsize,
}
static COUNTERS: Counters = Counters::new();
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct AllocatorStats {
pub current_bytes: usize,
pub heap_size: Option<usize>,
pub max_bytes: usize,
}
pub fn stats() -> AllocatorStats {
let (current_bytes, max_bytes) = COUNTERS.snapshot();
AllocatorStats {
current_bytes,
heap_size: heap_size(),
max_bytes,
}
}
fn heap_size() -> Option<usize> {
cfg_if::cfg_if! {
if #[cfg(target_env = "sgx")] {
Some(unsafe { HEAP_SIZE })
} else {
None
}
}
}
impl InstrumentedSystemAllocator {
pub const fn new() -> Self {
Self { _private: () }
}
}
unsafe impl GlobalAlloc for InstrumentedSystemAllocator {
#[inline]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ptr = unsafe { System.alloc(layout) };
COUNTERS.record_allocation(ptr, layout.size());
ptr
}
#[inline]
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
let ptr = unsafe { System.alloc_zeroed(layout) };
COUNTERS.record_allocation(ptr, layout.size());
ptr
}
#[inline]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) };
COUNTERS.remove(layout.size());
}
#[inline]
unsafe fn realloc(
&self,
ptr: *mut u8,
layout: Layout,
new_size: usize,
) -> *mut u8 {
let new_ptr = unsafe { System.realloc(ptr, layout, new_size) };
COUNTERS.record_reallocation(new_ptr, layout.size(), new_size);
new_ptr
}
}
impl Counters {
const fn new() -> Self {
Self {
current: AtomicUsize::new(0),
max: AtomicUsize::new(0),
}
}
#[inline]
fn record_allocation(&self, ptr: *mut u8, size: usize) {
if !ptr.is_null() {
self.add(size);
}
}
#[inline]
fn record_reallocation(
&self,
ptr: *mut u8,
old_size: usize,
new_size: usize,
) {
if ptr.is_null() {
return;
}
if new_size > old_size {
self.add(new_size - old_size);
} else if new_size < old_size {
self.remove(old_size - new_size);
}
}
#[inline]
fn add(&self, size: usize) {
let current = self.current.fetch_add(size, Ordering::Relaxed) + size;
self.max.fetch_max(current, Ordering::Relaxed);
}
#[inline]
fn remove(&self, size: usize) {
self.current.fetch_sub(size, Ordering::Relaxed);
}
fn snapshot(&self) -> (usize, usize) {
let current = self.current.load(Ordering::Relaxed);
let max = self.max.load(Ordering::Relaxed).max(current);
(current, max)
}
}
#[cfg(test)]
mod tests {
use std::ptr::{self, NonNull};
use super::*;
#[test]
fn tracks_allocation_lifecycle() {
let counters = Counters::new();
let ptr = NonNull::<u8>::dangling().as_ptr();
counters.record_allocation(ptr::null_mut(), 100);
assert_eq!(counters.snapshot(), (0, 0));
counters.record_allocation(ptr, 20);
counters.record_allocation(ptr, 30);
assert_eq!(counters.snapshot(), (50, 50));
counters.remove(20);
assert_eq!(counters.snapshot(), (30, 50));
counters.record_reallocation(ptr::null_mut(), 30, 100);
assert_eq!(counters.snapshot(), (30, 50));
counters.record_reallocation(ptr, 30, 60);
assert_eq!(counters.snapshot(), (60, 60));
counters.record_reallocation(ptr, 60, 10);
assert_eq!(counters.snapshot(), (10, 60));
counters.remove(10);
assert_eq!(counters.snapshot(), (0, 60));
}
}