use std::sync::atomic::{AtomicI64, Ordering};
pub struct MemoryBudget {
max_memory: i64,
tree_memory_usage: AtomicI64,
lock_memory_usage: AtomicI64,
admin_memory_usage: AtomicI64,
log_buffer_budget: i64,
}
pub struct MemoryOverhead;
impl MemoryOverhead {
pub const LOCKIMPL_OVERHEAD: i64 = 96;
pub const THINLOCKIMPL_OVERHEAD: i64 = 32;
pub const LOCKINFO_OVERHEAD: i64 = 32;
pub const HASHMAP_ENTRY_OVERHEAD: i64 = 48;
pub const LONG_OVERHEAD: i64 = 16;
pub const IN_OVERHEAD: i64 = 400;
pub const BIN_OVERHEAD: i64 = 500;
pub const LN_OVERHEAD: i64 = 48;
pub const TXN_OVERHEAD: i64 = 200;
pub const BASICLOCKER_OVERHEAD: i64 = 80;
}
impl MemoryBudget {
pub fn new(max_memory: i64) -> Self {
let log_buffer_budget = max_memory * 7 / 100;
MemoryBudget {
max_memory,
tree_memory_usage: AtomicI64::new(0),
lock_memory_usage: AtomicI64::new(0),
admin_memory_usage: AtomicI64::new(0),
log_buffer_budget,
}
}
pub fn max_memory(&self) -> i64 {
self.max_memory
}
pub fn log_buffer_budget(&self) -> i64 {
self.log_buffer_budget
}
pub fn total_usage(&self) -> i64 {
self.tree_memory_usage.load(Ordering::Relaxed)
+ self.lock_memory_usage.load(Ordering::Relaxed)
+ self.admin_memory_usage.load(Ordering::Relaxed)
}
pub fn available_memory(&self) -> i64 {
self.max_memory - self.total_usage()
}
pub fn is_over_budget(&self) -> bool {
self.total_usage() > self.max_memory
}
pub fn get_tree_memory_usage(&self) -> i64 {
self.tree_memory_usage.load(Ordering::Relaxed)
}
pub fn update_tree_memory_usage(&self, delta: i64) {
self.tree_memory_usage.fetch_add(delta, Ordering::Relaxed);
}
pub fn get_lock_memory_usage(&self) -> i64 {
self.lock_memory_usage.load(Ordering::Relaxed)
}
pub fn update_lock_memory_usage(&self, delta: i64) {
self.lock_memory_usage.fetch_add(delta, Ordering::Relaxed);
}
pub fn get_admin_memory_usage(&self) -> i64 {
self.admin_memory_usage.load(Ordering::Relaxed)
}
pub fn update_admin_memory_usage(&self, delta: i64) {
self.admin_memory_usage.fetch_add(delta, Ordering::Relaxed);
}
pub fn reset(&self) {
self.tree_memory_usage.store(0, Ordering::Relaxed);
self.lock_memory_usage.store(0, Ordering::Relaxed);
self.admin_memory_usage.store(0, Ordering::Relaxed);
}
pub fn get_stats(&self) -> MemoryBudgetStats {
MemoryBudgetStats {
max_memory: self.max_memory,
total_usage: self.total_usage(),
tree_memory: self.get_tree_memory_usage(),
lock_memory: self.get_lock_memory_usage(),
admin_memory: self.get_admin_memory_usage(),
log_buffer_budget: self.log_buffer_budget,
}
}
}
#[derive(Debug, Clone)]
pub struct MemoryBudgetStats {
pub max_memory: i64,
pub total_usage: i64,
pub tree_memory: i64,
pub lock_memory: i64,
pub admin_memory: i64,
pub log_buffer_budget: i64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_total_usage_is_sum() {
let budget = MemoryBudget::new(1000);
budget.update_tree_memory_usage(100);
budget.update_lock_memory_usage(50);
budget.update_admin_memory_usage(25);
assert_eq!(budget.total_usage(), 175);
}
#[test]
fn test_available_memory_calculation() {
let budget = MemoryBudget::new(1000);
assert_eq!(budget.available_memory(), 1000);
budget.update_tree_memory_usage(300);
assert_eq!(budget.available_memory(), 700);
budget.update_lock_memory_usage(200);
assert_eq!(budget.available_memory(), 500);
}
#[test]
fn test_is_over_budget() {
let budget = MemoryBudget::new(1000);
assert!(!budget.is_over_budget());
budget.update_tree_memory_usage(500);
assert!(!budget.is_over_budget());
budget.update_lock_memory_usage(500);
assert!(!budget.is_over_budget());
budget.update_admin_memory_usage(1);
assert!(budget.is_over_budget());
}
#[test]
fn test_update_with_positive_and_negative_deltas() {
let budget = MemoryBudget::new(1000);
budget.update_tree_memory_usage(500);
assert_eq!(budget.get_tree_memory_usage(), 500);
budget.update_tree_memory_usage(-200);
assert_eq!(budget.get_tree_memory_usage(), 300);
budget.update_tree_memory_usage(-300);
assert_eq!(budget.get_tree_memory_usage(), 0);
}
#[test]
fn test_reset_clears_all() {
let budget = MemoryBudget::new(1000);
budget.update_tree_memory_usage(100);
budget.update_lock_memory_usage(200);
budget.update_admin_memory_usage(300);
budget.reset();
assert_eq!(budget.get_tree_memory_usage(), 0);
assert_eq!(budget.get_lock_memory_usage(), 0);
assert_eq!(budget.get_admin_memory_usage(), 0);
assert_eq!(budget.total_usage(), 0);
}
#[test]
fn test_log_buffer_budget_is_7_percent() {
let budget = MemoryBudget::new(10000);
assert_eq!(budget.log_buffer_budget(), 700);
}
#[test]
fn test_memory_overhead_constants_exist() {
assert_eq!(MemoryOverhead::LOCKIMPL_OVERHEAD, 96);
assert_eq!(MemoryOverhead::THINLOCKIMPL_OVERHEAD, 32);
assert_eq!(MemoryOverhead::LOCKINFO_OVERHEAD, 32);
assert_eq!(MemoryOverhead::HASHMAP_ENTRY_OVERHEAD, 48);
assert_eq!(MemoryOverhead::LONG_OVERHEAD, 16);
assert_eq!(MemoryOverhead::IN_OVERHEAD, 400);
assert_eq!(MemoryOverhead::BIN_OVERHEAD, 500);
assert_eq!(MemoryOverhead::LN_OVERHEAD, 48);
assert_eq!(MemoryOverhead::TXN_OVERHEAD, 200);
assert_eq!(MemoryOverhead::BASICLOCKER_OVERHEAD, 80);
}
#[test]
fn test_get_stats() {
let budget = MemoryBudget::new(5000);
budget.update_tree_memory_usage(1000);
budget.update_lock_memory_usage(500);
budget.update_admin_memory_usage(250);
let stats = budget.get_stats();
assert_eq!(stats.max_memory, 5000);
assert_eq!(stats.total_usage, 1750);
assert_eq!(stats.tree_memory, 1000);
assert_eq!(stats.lock_memory, 500);
assert_eq!(stats.admin_memory, 250);
assert_eq!(stats.log_buffer_budget, 350); }
}