use std::any::TypeId;
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct ContextStats {
pub allocations: usize,
pub peak_bytes: usize,
pub current_bytes: usize,
pub typed_allocations: usize,
pub allocations_by_type: HashMap<TypeId, usize>,
}
impl ContextStats {
pub fn new() -> Self {
Self::default()
}
pub fn memory_efficiency(&self) -> f64 {
if self.peak_bytes > 0 {
self.current_bytes as f64 / self.peak_bytes as f64
} else {
1.0
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AllocationHandle(usize);
impl AllocationHandle {
pub fn index(&self) -> usize {
self.0
}
}
pub struct AnalyticsContext {
name: String,
allocations: Vec<Box<[u8]>>,
sizes: Vec<usize>,
stats: ContextStats,
}
impl AnalyticsContext {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
allocations: Vec::new(),
sizes: Vec::new(),
stats: ContextStats::new(),
}
}
pub fn with_capacity(name: impl Into<String>, expected_allocations: usize) -> Self {
Self {
name: name.into(),
allocations: Vec::with_capacity(expected_allocations),
sizes: Vec::with_capacity(expected_allocations),
stats: ContextStats::new(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn allocate(&mut self, size: usize) -> AllocationHandle {
let buf = vec![0u8; size].into_boxed_slice();
let handle = AllocationHandle(self.allocations.len());
self.allocations.push(buf);
self.sizes.push(size);
self.stats.allocations += 1;
self.stats.current_bytes += size;
self.stats.peak_bytes = self.stats.peak_bytes.max(self.stats.current_bytes);
handle
}
pub fn allocate_typed<T: Copy + Default + 'static>(
&mut self,
count: usize,
) -> AllocationHandle {
let size = count * std::mem::size_of::<T>();
let handle = self.allocate(size);
self.stats.typed_allocations += 1;
*self
.stats
.allocations_by_type
.entry(TypeId::of::<T>())
.or_insert(0) += 1;
handle
}
pub fn get(&self, handle: AllocationHandle) -> &[u8] {
&self.allocations[handle.0]
}
pub fn get_mut(&mut self, handle: AllocationHandle) -> &mut [u8] {
&mut self.allocations[handle.0]
}
pub fn get_typed<T: Copy>(&self, handle: AllocationHandle) -> &[T] {
let bytes = &self.allocations[handle.0];
let len = bytes.len() / std::mem::size_of::<T>();
unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const T, len) }
}
pub fn get_typed_mut<T: Copy>(&mut self, handle: AllocationHandle) -> &mut [T] {
let bytes = &mut self.allocations[handle.0];
let len = bytes.len() / std::mem::size_of::<T>();
unsafe { std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut T, len) }
}
pub fn try_get(&self, handle: AllocationHandle) -> Option<&[u8]> {
self.allocations.get(handle.0).map(|b| b.as_ref())
}
pub fn try_get_mut(&mut self, handle: AllocationHandle) -> Option<&mut [u8]> {
self.allocations.get_mut(handle.0).map(|b| b.as_mut())
}
pub fn allocation_size(&self, handle: AllocationHandle) -> usize {
self.sizes[handle.0]
}
pub fn allocation_count(&self) -> usize {
self.allocations.len()
}
pub fn stats(&self) -> &ContextStats {
&self.stats
}
pub fn release_all(&mut self) {
self.allocations.clear();
self.sizes.clear();
self.stats.current_bytes = 0;
}
pub fn sub_context(&self, name: impl Into<String>) -> Self {
Self::new(format!("{}::{}", self.name, name.into()))
}
}
impl Drop for AnalyticsContext {
fn drop(&mut self) {
}
}
pub struct AnalyticsContextBuilder {
name: String,
expected_allocations: Option<usize>,
preallocations: Vec<usize>,
}
impl AnalyticsContextBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
expected_allocations: None,
preallocations: Vec::new(),
}
}
pub fn with_expected_allocations(mut self, count: usize) -> Self {
self.expected_allocations = Some(count);
self
}
pub fn with_preallocation(mut self, size: usize) -> Self {
self.preallocations.push(size);
self
}
pub fn build(self) -> AnalyticsContext {
let mut ctx = match self.expected_allocations {
Some(cap) => {
AnalyticsContext::with_capacity(self.name, cap.max(self.preallocations.len()))
}
None => AnalyticsContext::new(self.name),
};
for size in self.preallocations {
ctx.allocate(size);
}
ctx
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_creation() {
let ctx = AnalyticsContext::new("test");
assert_eq!(ctx.name(), "test");
assert_eq!(ctx.allocation_count(), 0);
}
#[test]
fn test_context_with_capacity() {
let ctx = AnalyticsContext::with_capacity("test", 10);
assert_eq!(ctx.name(), "test");
assert_eq!(ctx.allocation_count(), 0);
}
#[test]
fn test_allocate() {
let mut ctx = AnalyticsContext::new("test");
let h1 = ctx.allocate(100);
let h2 = ctx.allocate(200);
assert_eq!(ctx.allocation_count(), 2);
assert_eq!(ctx.allocation_size(h1), 100);
assert_eq!(ctx.allocation_size(h2), 200);
assert_eq!(ctx.get(h1).len(), 100);
assert_eq!(ctx.get(h2).len(), 200);
}
#[test]
fn test_allocate_typed() {
let mut ctx = AnalyticsContext::new("test");
let h = ctx.allocate_typed::<u32>(10);
assert_eq!(ctx.allocation_size(h), 40); assert_eq!(ctx.get_typed::<u32>(h).len(), 10);
let stats = ctx.stats();
assert_eq!(stats.typed_allocations, 1);
}
#[test]
fn test_get_mut() {
let mut ctx = AnalyticsContext::new("test");
let h = ctx.allocate(10);
ctx.get_mut(h)[0] = 42;
assert_eq!(ctx.get(h)[0], 42);
}
#[test]
fn test_get_typed_mut() {
let mut ctx = AnalyticsContext::new("test");
let h = ctx.allocate_typed::<u32>(5);
ctx.get_typed_mut::<u32>(h)[2] = 12345;
assert_eq!(ctx.get_typed::<u32>(h)[2], 12345);
}
#[test]
fn test_try_get() {
let mut ctx = AnalyticsContext::new("test");
let h = ctx.allocate(10);
let invalid = AllocationHandle(999);
assert!(ctx.try_get(h).is_some());
assert!(ctx.try_get(invalid).is_none());
assert!(ctx.try_get_mut(h).is_some());
assert!(ctx.try_get_mut(invalid).is_none());
}
#[test]
fn test_stats_tracking() {
let mut ctx = AnalyticsContext::new("test");
ctx.allocate(100);
ctx.allocate(200);
ctx.allocate(50);
let stats = ctx.stats();
assert_eq!(stats.allocations, 3);
assert_eq!(stats.current_bytes, 350);
assert_eq!(stats.peak_bytes, 350);
}
#[test]
fn test_stats_peak_bytes() {
let mut ctx = AnalyticsContext::new("test");
ctx.allocate(100);
ctx.allocate(200);
let peak = ctx.stats().peak_bytes;
ctx.release_all();
assert_eq!(ctx.stats().current_bytes, 0);
assert_eq!(ctx.stats().peak_bytes, peak); }
#[test]
fn test_release_all() {
let mut ctx = AnalyticsContext::new("test");
let h1 = ctx.allocate(100);
let h2 = ctx.allocate(200);
assert_eq!(ctx.allocation_count(), 2);
ctx.release_all();
assert_eq!(ctx.allocation_count(), 0);
assert_eq!(ctx.stats().current_bytes, 0);
assert!(ctx.try_get(h1).is_none());
assert!(ctx.try_get(h2).is_none());
}
#[test]
fn test_sub_context() {
let parent = AnalyticsContext::new("parent");
let child = parent.sub_context("child");
assert_eq!(child.name(), "parent::child");
}
#[test]
fn test_builder() {
let ctx = AnalyticsContextBuilder::new("builder_test")
.with_expected_allocations(10)
.with_preallocation(100)
.with_preallocation(200)
.build();
assert_eq!(ctx.name(), "builder_test");
assert_eq!(ctx.allocation_count(), 2);
assert_eq!(ctx.stats().current_bytes, 300);
}
#[test]
fn test_context_stats_default() {
let stats = ContextStats::default();
assert_eq!(stats.allocations, 0);
assert_eq!(stats.peak_bytes, 0);
assert_eq!(stats.current_bytes, 0);
assert_eq!(stats.memory_efficiency(), 1.0);
}
#[test]
fn test_memory_efficiency() {
let mut ctx = AnalyticsContext::new("test");
ctx.allocate(100);
ctx.allocate(100);
assert_eq!(ctx.stats().memory_efficiency(), 1.0);
ctx.release_all();
assert_eq!(ctx.stats().memory_efficiency(), 0.0);
}
#[test]
fn test_handle_index() {
let mut ctx = AnalyticsContext::new("test");
let h0 = ctx.allocate(10);
let h1 = ctx.allocate(20);
let h2 = ctx.allocate(30);
assert_eq!(h0.index(), 0);
assert_eq!(h1.index(), 1);
assert_eq!(h2.index(), 2);
}
#[test]
fn test_zero_allocation() {
let mut ctx = AnalyticsContext::new("test");
let h = ctx.allocate(0);
assert_eq!(ctx.get(h).len(), 0);
assert_eq!(ctx.allocation_size(h), 0);
}
#[test]
fn test_large_allocation() {
let mut ctx = AnalyticsContext::new("test");
let h = ctx.allocate(1024 * 1024);
assert_eq!(ctx.get(h).len(), 1024 * 1024);
assert_eq!(ctx.stats().current_bytes, 1024 * 1024);
}
}