use crate::error::{GraphError, Result};
use bumpalo::Bump;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[derive(Debug)]
pub struct BatchMemoryTracker {
allocated: AtomicU64,
peak: AtomicU64,
batch_count: AtomicU64,
limit_bytes: u64,
}
impl BatchMemoryTracker {
pub fn new(limit_bytes: u64) -> Self {
BatchMemoryTracker {
allocated: AtomicU64::new(0),
peak: AtomicU64::new(0),
batch_count: AtomicU64::new(0),
limit_bytes,
}
}
pub fn record_alloc(&self, bytes: u64) -> Result<()> {
let current = self.allocated.fetch_add(bytes, Ordering::SeqCst) + bytes;
if current > self.limit_bytes {
self.allocated.fetch_sub(bytes, Ordering::SeqCst);
return Err(GraphError::Memory(format!(
"Batch memory limit exceeded: {} > {} bytes",
current, self.limit_bytes
)));
}
let mut peak = self.peak.load(Ordering::SeqCst);
while current > peak {
match self.peak.compare_exchange_weak(
peak,
current,
Ordering::SeqCst,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(p) => peak = p,
}
}
Ok(())
}
pub fn record_dealloc(&self, bytes: u64) {
self.allocated.fetch_sub(bytes, Ordering::SeqCst);
}
pub fn record_batch_complete(&self) {
self.batch_count.fetch_add(1, Ordering::SeqCst);
}
pub fn current_usage(&self) -> u64 {
self.allocated.load(Ordering::SeqCst)
}
pub fn peak_usage(&self) -> u64 {
self.peak.load(Ordering::SeqCst)
}
pub fn batch_count(&self) -> u64 {
self.batch_count.load(Ordering::SeqCst)
}
pub fn usage_percent(&self) -> f64 {
(self.current_usage() as f64 / self.limit_bytes as f64) * 100.0
}
}
impl Default for BatchMemoryTracker {
fn default() -> Self {
Self::new(256 * 1024 * 1024)
}
}
pub struct BatchContext {
arena: Bump,
allocated_bytes: u64,
tracker: Arc<BatchMemoryTracker>,
}
impl BatchContext {
pub fn new(tracker: Arc<BatchMemoryTracker>) -> Self {
BatchContext {
arena: Bump::new(),
allocated_bytes: 0,
tracker,
}
}
pub fn alloc<T>(&mut self, value: T) -> Result<&mut T> {
let size = std::mem::size_of::<T>() as u64;
self.tracker.record_alloc(size)?;
self.allocated_bytes += size;
Ok(self.arena.alloc(value))
}
pub fn alloc_slice<T: Copy>(&mut self, values: &[T]) -> Result<&mut [T]> {
let size = std::mem::size_of_val(values) as u64;
self.tracker.record_alloc(size)?;
self.allocated_bytes += size;
Ok(self.arena.alloc_slice_copy(values))
}
pub fn allocated_bytes(&self) -> u64 {
self.allocated_bytes
}
pub fn finish(self) {
self.tracker.record_dealloc(self.allocated_bytes);
self.tracker.record_batch_complete();
}
}
pub struct BatchManager {
tracker: Arc<BatchMemoryTracker>,
}
impl BatchManager {
pub fn new(limit_bytes: u64) -> Self {
BatchManager {
tracker: Arc::new(BatchMemoryTracker::new(limit_bytes)),
}
}
pub fn start_batch(&self) -> BatchContext {
BatchContext::new(self.tracker.clone())
}
pub fn get_stats(&self) -> BatchStats {
BatchStats {
current_usage: self.tracker.current_usage(),
peak_usage: self.tracker.peak_usage(),
batch_count: self.tracker.batch_count(),
usage_percent: self.tracker.usage_percent(),
}
}
}
impl Default for BatchManager {
fn default() -> Self {
Self::new(256 * 1024 * 1024) }
}
#[derive(Debug, Clone)]
pub struct BatchStats {
pub current_usage: u64,
pub peak_usage: u64,
pub batch_count: u64,
pub usage_percent: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_batch_context() {
let manager = BatchManager::new(1024 * 1024);
let mut batch = manager.start_batch();
{
let val1 = batch.alloc(42u64).unwrap();
assert_eq!(*val1, 42);
}
{
let val2 = batch.alloc(100u64).unwrap();
assert_eq!(*val2, 100);
}
assert!(batch.allocated_bytes() > 0);
batch.finish();
let stats = manager.get_stats();
assert_eq!(stats.current_usage, 0);
assert_eq!(stats.batch_count, 1);
}
#[test]
fn test_batch_memory_limit() {
let manager = BatchManager::new(100);
let mut batch = manager.start_batch();
batch.alloc([0u8; 50]).unwrap();
let result = batch.alloc([0u8; 100]);
assert!(result.is_err());
}
#[test]
fn test_multiple_batches() {
let manager = BatchManager::new(1024);
let batch1 = manager.start_batch();
batch1.finish();
let batch2 = manager.start_batch();
batch2.finish();
let stats = manager.get_stats();
assert_eq!(stats.batch_count, 2);
}
}