use std::fmt::{Formatter, Display, Debug};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::alloc::{System, GlobalAlloc, Layout};
use crate::metrics_allocator::ring_buffer::{RingBuffer, RingBufferConsumer, RingBufferOverflowError};
pub struct MetricsAllocatorStatistics<NumericType> {
pub allocations_count: NumericType,
pub deallocations_count: NumericType,
pub zeroed_allocations_count: NumericType,
pub reallocations_count: NumericType,
pub allocated_bytes: NumericType,
pub deallocated_bytes: NumericType,
pub zeroed_allocated_bytes: NumericType,
pub reallocated_originals_bytes: NumericType,
pub reallocated_news_bytes: NumericType,
pub current_used_memory: NumericType,
pub min_used_memory: NumericType,
pub max_used_memory: NumericType,
}
impl<NumericType> MetricsAllocatorStatistics<NumericType> {
fn fmt(&self, statistics: &MetricsAllocatorStatistics<usize>, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{{counts: {{allocations: {}, deallocations: {}, zeroed_allocations: {}, reallocations: {}}}, bytes: {{allocated: {}, deallocated: {}, zeroed: {}, reallocated: {{originals: {}, news: {}}}}}, current_used_memory: {}, min_used_memory: {}, max_used_memory: {}}}",
statistics.allocations_count, statistics.deallocations_count, statistics.zeroed_allocations_count, statistics.reallocations_count,
statistics.allocated_bytes, statistics.deallocated_bytes, statistics.zeroed_allocated_bytes,
statistics.reallocated_originals_bytes, statistics.reallocated_news_bytes,
statistics.current_used_memory, statistics.min_used_memory, statistics.max_used_memory)
}
}
impl Display for MetricsAllocatorStatistics<AtomicUsize> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt(&MetricsAllocatorStatistics::<usize> {
allocations_count: self.allocations_count. load(Ordering::Relaxed),
deallocations_count: self.deallocations_count. load(Ordering::Relaxed),
zeroed_allocations_count: self.zeroed_allocations_count. load(Ordering::Relaxed),
reallocations_count: self.reallocations_count. load(Ordering::Relaxed),
allocated_bytes: self.allocated_bytes. load(Ordering::Relaxed),
deallocated_bytes: self.deallocated_bytes. load(Ordering::Relaxed),
zeroed_allocated_bytes: self.zeroed_allocated_bytes. load(Ordering::Relaxed),
reallocated_originals_bytes: self.reallocated_originals_bytes.load(Ordering::Relaxed),
reallocated_news_bytes: self.reallocated_news_bytes. load(Ordering::Relaxed),
current_used_memory: self.current_used_memory. load(Ordering::Relaxed),
min_used_memory: self.min_used_memory. load(Ordering::Relaxed),
max_used_memory: self.max_used_memory. load(Ordering::Relaxed),
}, f)
}
}
impl Display for MetricsAllocatorStatistics<usize> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt(self, f)
}
}
impl Debug for MetricsAllocatorStatistics<AtomicUsize> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
impl Debug for MetricsAllocatorStatistics<usize> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
pub struct MetricsAllocatorSavePoint<'a, const RING_BUFFER_SIZE: usize> {
pub metrics: MetricsAllocatorStatistics<usize>,
used_memory_ring_buffer_consumer: RingBufferConsumer<'a, SavePointRingBufferSlot<usize>, RING_BUFFER_SIZE>,
}
struct SavePointRingBufferSlot<NumericType> {
min_used_memory: NumericType,
max_used_memory: NumericType,
}
impl Default for SavePointRingBufferSlot<usize> {
fn default() -> Self {
Self {
min_used_memory: 0,
max_used_memory: 0,
}
}
}
impl Default for SavePointRingBufferSlot<AtomicUsize> {
fn default() -> Self {
Self {
min_used_memory: AtomicUsize::new(0),
max_used_memory: AtomicUsize::new(0),
}
}
}
pub struct MetricsAllocator<'a, const RING_BUFFER_SIZE: usize> {
system_allocator: &'a System,
statistics: MetricsAllocatorStatistics<AtomicUsize>,
used_memory_ring_buffer: RingBuffer<SavePointRingBufferSlot<usize>, RING_BUFFER_SIZE>,
}
impl<const RING_BUFFER_SIZE: usize>
Default
for MetricsAllocator<'_, RING_BUFFER_SIZE> {
fn default() -> Self {
Self::new()
}
}
impl<const RING_BUFFER_SIZE: usize> MetricsAllocator<'_, RING_BUFFER_SIZE> {
pub const fn new() -> Self {
Self {
system_allocator: &System,
statistics: MetricsAllocatorStatistics {
allocations_count: AtomicUsize::new(0),
deallocations_count: AtomicUsize::new(0),
zeroed_allocations_count: AtomicUsize::new(0),
reallocations_count: AtomicUsize::new(0),
allocated_bytes: AtomicUsize::new(0),
deallocated_bytes: AtomicUsize::new(0),
zeroed_allocated_bytes: AtomicUsize::new(0),
reallocated_originals_bytes: AtomicUsize::new(0),
reallocated_news_bytes: AtomicUsize::new(0),
current_used_memory: AtomicUsize::new(0),
min_used_memory: AtomicUsize::new(0),
max_used_memory: AtomicUsize::new(0),
},
used_memory_ring_buffer: RingBuffer::new(),
}
}
pub fn save_point(&self) -> MetricsAllocatorSavePoint<RING_BUFFER_SIZE> {
self.used_memory_ring_buffer.enqueue(SavePointRingBufferSlot {
min_used_memory: self.statistics.min_used_memory.load(Ordering::Relaxed),
max_used_memory: self.statistics.max_used_memory.load(Ordering::Relaxed),
});
let used_memory_ring_buffer_consumer = self.used_memory_ring_buffer.consumer();
self.statistics.min_used_memory.store(self.statistics.current_used_memory.load(Ordering::Relaxed), Ordering::Relaxed);
self.statistics.max_used_memory.store(self.statistics.current_used_memory.load(Ordering::Relaxed), Ordering::Relaxed);
MetricsAllocatorSavePoint {
metrics: MetricsAllocatorStatistics {
allocations_count: self.statistics.allocations_count .load(Ordering::Relaxed),
deallocations_count: self.statistics.deallocations_count .load(Ordering::Relaxed),
zeroed_allocations_count: self.statistics.zeroed_allocations_count .load(Ordering::Relaxed),
reallocations_count: self.statistics.reallocations_count .load(Ordering::Relaxed),
allocated_bytes: self.statistics.allocated_bytes .load(Ordering::Relaxed),
deallocated_bytes: self.statistics.deallocated_bytes .load(Ordering::Relaxed),
zeroed_allocated_bytes: self.statistics.zeroed_allocated_bytes .load(Ordering::Relaxed),
reallocated_originals_bytes: self.statistics.reallocated_originals_bytes.load(Ordering::Relaxed),
reallocated_news_bytes: self.statistics.reallocated_news_bytes .load(Ordering::Relaxed),
current_used_memory: self.statistics.current_used_memory .load(Ordering::Relaxed),
min_used_memory: self.statistics.min_used_memory .load(Ordering::Relaxed),
max_used_memory: self.statistics.max_used_memory .load(Ordering::Relaxed),
},
used_memory_ring_buffer_consumer
}
}
pub fn delta_statistics(&self, save_point: &MetricsAllocatorSavePoint<RING_BUFFER_SIZE>) -> Result<MetricsAllocatorStatistics<usize>, RingBufferOverflowError> {
let mut min = usize::MAX;
let mut max = usize::MIN;
for peeked_chunk in save_point.used_memory_ring_buffer_consumer.peek_all()? {
for subsequent_save_point in peeked_chunk {
min = min.min(subsequent_save_point.min_used_memory);
max = max.max(subsequent_save_point.max_used_memory);
}
}
min = min.min(self.statistics.min_used_memory.load(Ordering::Relaxed));
max = max.max(self.statistics.max_used_memory.load(Ordering::Relaxed));
Ok(MetricsAllocatorStatistics::<usize> {
allocations_count: self.statistics.allocations_count .load(Ordering::Relaxed) - save_point.metrics.allocations_count,
deallocations_count: self.statistics.deallocations_count .load(Ordering::Relaxed) - save_point.metrics.deallocations_count,
zeroed_allocations_count: self.statistics.zeroed_allocations_count .load(Ordering::Relaxed) - save_point.metrics.zeroed_allocations_count,
reallocations_count: self.statistics.reallocations_count .load(Ordering::Relaxed) - save_point.metrics.reallocations_count,
allocated_bytes: self.statistics.allocated_bytes .load(Ordering::Relaxed) - save_point.metrics.allocated_bytes,
deallocated_bytes: self.statistics.deallocated_bytes .load(Ordering::Relaxed) - save_point.metrics.deallocated_bytes,
zeroed_allocated_bytes: self.statistics.zeroed_allocated_bytes .load(Ordering::Relaxed) - save_point.metrics.zeroed_allocated_bytes,
reallocated_originals_bytes: self.statistics.reallocated_originals_bytes.load(Ordering::Relaxed) - save_point.metrics.reallocated_originals_bytes,
reallocated_news_bytes: self.statistics.reallocated_news_bytes .load(Ordering::Relaxed) - save_point.metrics.reallocated_news_bytes,
current_used_memory: self.statistics.current_used_memory .load(Ordering::Relaxed),
min_used_memory: min,
max_used_memory: max,
})
}
fn compute_alloc_metrics(&self, layout: &Layout) {
self.statistics.allocations_count.fetch_add(1, Ordering::Relaxed);
self.statistics.allocated_bytes.fetch_add(layout.size(), Ordering::Relaxed);
self.statistics.current_used_memory.fetch_add(layout.size(), Ordering::Relaxed);
self.compute_min_and_max_used_memories();
}
fn compute_dealloc_metrics(&self, layout: &Layout) {
self.statistics.deallocations_count.fetch_add(1, Ordering::Relaxed);
self.statistics.deallocated_bytes.fetch_add(layout.size(), Ordering::Relaxed);
self.statistics.current_used_memory.fetch_sub(layout.size(), Ordering::Relaxed);
self.compute_min_and_max_used_memories();
}
fn compute_alloc_zeroed_metrics(&self, layout: &Layout) {
self.statistics.zeroed_allocations_count.fetch_add(1, Ordering::Relaxed);
self.statistics.zeroed_allocated_bytes.fetch_add(layout.size(), Ordering::Relaxed);
self.statistics.current_used_memory.fetch_add(layout.size(), Ordering::Relaxed);
self.compute_min_and_max_used_memories();
}
fn compute_realloc_metrics(&self, layout: &Layout, new_size: usize) {
self.statistics.reallocations_count.fetch_add(1, Ordering::Relaxed);
self.statistics.reallocated_originals_bytes.fetch_add(layout.size(), Ordering::Relaxed);
self.statistics.reallocated_news_bytes.fetch_add(new_size, Ordering::Relaxed);
if new_size > layout.size() {
self.statistics.current_used_memory.fetch_add(new_size-layout.size(), Ordering::Relaxed);
} else if new_size < layout.size() {
self.statistics.current_used_memory.fetch_sub(layout.size()-new_size, Ordering::Relaxed);
}
self.compute_min_and_max_used_memories();
}
fn compute_min_and_max_used_memories(&self) {
let current_used_memory = self.statistics.current_used_memory.load(Ordering::Relaxed);
let mut min_used_memory = self.statistics.min_used_memory.load(Ordering::Relaxed);
loop {
if current_used_memory < min_used_memory {
match self.statistics.min_used_memory.compare_exchange_weak(min_used_memory, current_used_memory, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => break,
Err(reloaded_val) => min_used_memory = reloaded_val,
}
} else {
break;
}
}
let mut max_used_memory = self.statistics.max_used_memory.load(Ordering::Relaxed);
loop {
if current_used_memory > max_used_memory {
match self.statistics.max_used_memory.compare_exchange_weak(max_used_memory, current_used_memory, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => break,
Err(reloaded_val) => max_used_memory = reloaded_val,
}
} else {
break;
}
}
}
}
unsafe impl<const RING_BUFFER_SIZE: usize> GlobalAlloc for MetricsAllocator<'_, RING_BUFFER_SIZE> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.compute_alloc_metrics(&layout);
self.system_allocator.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.compute_dealloc_metrics(&layout);
self.system_allocator.dealloc(ptr, layout)
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
self.compute_alloc_zeroed_metrics(&layout);
self.system_allocator.alloc_zeroed(layout)
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
self.compute_realloc_metrics(&layout, new_size);
self.system_allocator.realloc(ptr, layout, new_size)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn usage_example() {
use crate::features::ALLOC;
let save_point = ALLOC.save_point();
let _vec = Vec::<u32>::with_capacity(1024);
let metrics = ALLOC.delta_statistics(&save_point)
.expect("delta_statistics() failed");
println!("Allocator Metrics for the Vec allocation: {}", metrics);
}
#[test]
fn test_save_point_min_and_max_memory_usage() {
let allocator = MetricsAllocator::<16>::new();
let mut used_mem = 0usize;
let mut min_mem = usize::MAX;
let mut max_mem = usize::MIN;
let save_point = |used_mem: &usize, min_mem: &mut usize, max_mem: &mut usize| {
*min_mem = *used_mem;
*max_mem = *used_mem;
allocator.save_point()
};
let assert_current_min_and_max_memory = |save_point, expected_used_mem, expected_min_mem, expected_max_mem| {
let metrics = allocator.delta_statistics(save_point)
.expect("delta_statistics() failed");
assert_eq!(metrics.current_used_memory, expected_used_mem, "wrong used memory metrics");
assert_eq!(metrics.min_used_memory, expected_min_mem, "wrong min memory metrics");
assert_eq!(metrics.max_used_memory, expected_max_mem, "wrong max memory metrics");
};
let allocate_and_check = |size, save_point, used_mem: &mut usize, min_mem: &mut usize, max_mem: &mut usize| {
let layout = Layout::from_size_align(size, 4).unwrap();
allocator.compute_alloc_metrics(&layout);
*min_mem = *min_mem.min(used_mem);
*used_mem += size;
*max_mem = *max_mem.max(used_mem);
assert_current_min_and_max_memory(save_point, *used_mem, *min_mem, *max_mem);
};
let deallocate_and_check = |size, save_point, used_mem: &mut usize, min_mem: &mut usize, max_mem: &mut usize| {
let layout = Layout::from_size_align(size, 4).unwrap();
allocator.compute_dealloc_metrics(&layout);
*max_mem = *max_mem.max(used_mem);
*used_mem -= size;
*min_mem = *min_mem.min(used_mem);
assert_current_min_and_max_memory(save_point, *used_mem, *min_mem, *max_mem);
};
let save_point1 = save_point(&used_mem, &mut min_mem, &mut max_mem);
allocate_and_check(12345, &save_point1, &mut used_mem, &mut min_mem, &mut max_mem);
allocate_and_check(54321, &save_point1, &mut used_mem, &mut min_mem, &mut max_mem);
assert_current_min_and_max_memory(&save_point1, 66666, 0, 66666);
let save_point2 = save_point(&used_mem, &mut min_mem, &mut max_mem);
allocate_and_check(30303, &save_point2, &mut used_mem, &mut min_mem, &mut max_mem);
allocate_and_check(03030, &save_point2, &mut used_mem, &mut min_mem, &mut max_mem);
assert_current_min_and_max_memory(&save_point2, 99999, 66666, 99999);
assert_current_min_and_max_memory(&save_point1, 99999, 0, 99999);
let save_point3 = save_point(&used_mem, &mut min_mem, &mut max_mem);
deallocate_and_check(05050, &save_point3, &mut used_mem, &mut min_mem, &mut max_mem);
deallocate_and_check(50505, &save_point3, &mut used_mem, &mut min_mem, &mut max_mem);
assert_current_min_and_max_memory(&save_point3, 44444, 44444, 99999);
assert_current_min_and_max_memory(&save_point2, 44444, 44444, 99999);
assert_current_min_and_max_memory(&save_point1, 44444, 0, 99999);
let save_point4 = save_point(&used_mem, &mut min_mem, &mut max_mem);
deallocate_and_check(01010, &save_point4, &mut used_mem, &mut min_mem, &mut max_mem);
deallocate_and_check(10101, &save_point4, &mut used_mem, &mut min_mem, &mut max_mem);
assert_current_min_and_max_memory(&save_point4, 33333, 33333, 44444);
assert_current_min_and_max_memory(&save_point3, 33333, 33333, 99999);
assert_current_min_and_max_memory(&save_point2, 33333, 33333, 99999);
assert_current_min_and_max_memory(&save_point1, 33333, 0, 99999);
eprintln!("Final metrics for 'save_point1': {:?}", allocator.delta_statistics(&save_point1));
eprintln!("Final metrics for 'save_point2': {:?}", allocator.delta_statistics(&save_point2));
eprintln!("Final metrics for 'save_point3': {:?}", allocator.delta_statistics(&save_point3));
eprintln!("Final metrics for 'save_point4': {:?}", allocator.delta_statistics(&save_point4));
}
}