use std::sync::Arc;
use std::sync::atomic::{AtomicI64, Ordering};
pub struct MemoryBudget {
max_memory: i64,
tree_memory_usage: Arc<AtomicI64>,
lock_memory_usage: AtomicI64,
txn_memory_usage: AtomicI64,
admin_memory_usage: AtomicI64,
log_buffer_budget: i64,
}
pub struct MemoryOverhead;
impl MemoryOverhead {
pub const LOCKIMPL_OVERHEAD: i64 = 48;
pub const THINLOCKIMPL_OVERHEAD: i64 = 32;
pub const LOCKINFO_OVERHEAD: i64 = 32;
pub const WRITE_LOCKINFO_OVERHEAD: i64 = 72;
pub const HASHMAP_ENTRY_OVERHEAD: i64 = 52;
pub const LONG_OVERHEAD: i64 = 16;
pub const LN_OVERHEAD: i64 = 32;
pub const TXN_OVERHEAD: i64 = 361;
}
impl MemoryBudget {
pub fn new(max_memory: i64, tree_memory_usage: Arc<AtomicI64>) -> Self {
let log_buffer_budget = max_memory * 7 / 100;
MemoryBudget {
max_memory,
tree_memory_usage,
lock_memory_usage: AtomicI64::new(0),
txn_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.txn_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 tree_memory_counter(&self) -> Arc<AtomicI64> {
Arc::clone(&self.tree_memory_usage)
}
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_txn_memory_usage(&self) -> i64 {
self.txn_memory_usage.load(Ordering::Relaxed)
}
pub fn update_txn_memory_usage(&self, delta: i64) {
self.txn_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.lock_memory_usage.store(0, Ordering::Relaxed);
self.txn_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(),
txn_memory: self.get_txn_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 txn_memory: i64,
pub admin_memory: i64,
pub log_buffer_budget: i64,
}
#[cfg(test)]
mod tests {
use super::*;
fn budget(max: i64) -> MemoryBudget {
MemoryBudget::new(max, Arc::new(AtomicI64::new(0)))
}
#[test]
fn test_total_usage_is_sum() {
let budget = budget(1000);
budget.update_tree_memory_usage(100);
budget.update_lock_memory_usage(50);
budget.update_txn_memory_usage(15);
budget.update_admin_memory_usage(10);
assert_eq!(budget.total_usage(), 175);
}
#[test]
fn test_available_memory_calculation() {
let budget = budget(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_sees_all_categories() {
let budget = budget(1000);
assert!(!budget.is_over_budget());
budget.update_tree_memory_usage(500);
assert!(!budget.is_over_budget());
budget.update_lock_memory_usage(400);
budget.update_txn_memory_usage(100);
assert!(!budget.is_over_budget());
budget.update_admin_memory_usage(1);
assert!(
budget.is_over_budget(),
"lock + txn + admin must count toward over-budget"
);
}
#[test]
fn test_tree_category_shares_arc() {
let shared = Arc::new(AtomicI64::new(0));
let budget = MemoryBudget::new(1000, Arc::clone(&shared));
shared.fetch_add(250, Ordering::Relaxed); assert_eq!(budget.get_tree_memory_usage(), 250);
assert_eq!(budget.total_usage(), 250);
}
#[test]
fn test_update_with_positive_and_negative_deltas() {
let budget = budget(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_non_tree_categories() {
let budget = budget(1000);
budget.update_tree_memory_usage(100);
budget.update_lock_memory_usage(200);
budget.update_txn_memory_usage(50);
budget.update_admin_memory_usage(300);
budget.reset();
assert_eq!(budget.get_tree_memory_usage(), 100);
assert_eq!(budget.get_lock_memory_usage(), 0);
assert_eq!(budget.get_txn_memory_usage(), 0);
assert_eq!(budget.get_admin_memory_usage(), 0);
}
#[test]
fn test_log_buffer_budget_is_7_percent() {
let budget = budget(10000);
assert_eq!(budget.log_buffer_budget(), 700);
}
#[test]
fn test_memory_overhead_constants_match_je_64bit() {
assert_eq!(MemoryOverhead::LOCKIMPL_OVERHEAD, 48);
assert_eq!(MemoryOverhead::THINLOCKIMPL_OVERHEAD, 32);
assert_eq!(MemoryOverhead::LOCKINFO_OVERHEAD, 32);
assert_eq!(MemoryOverhead::WRITE_LOCKINFO_OVERHEAD, 72);
assert_eq!(MemoryOverhead::HASHMAP_ENTRY_OVERHEAD, 52);
assert_eq!(MemoryOverhead::LONG_OVERHEAD, 16);
assert_eq!(MemoryOverhead::LN_OVERHEAD, 32);
assert_eq!(MemoryOverhead::TXN_OVERHEAD, 361);
}
#[test]
fn test_get_stats() {
let budget = budget(5000);
budget.update_tree_memory_usage(1000);
budget.update_lock_memory_usage(500);
budget.update_txn_memory_usage(100);
budget.update_admin_memory_usage(250);
let stats = budget.get_stats();
assert_eq!(stats.max_memory, 5000);
assert_eq!(stats.total_usage, 1850);
assert_eq!(stats.tree_memory, 1000);
assert_eq!(stats.lock_memory, 500);
assert_eq!(stats.txn_memory, 100);
assert_eq!(stats.admin_memory, 250);
assert_eq!(stats.log_buffer_budget, 350); }
}