#![allow(deprecated)]
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::api::tag::AllocationTag;
use crate::sync::mutex::Mutex;
pub struct BudgetManager {
global_limit: usize,
current_usage: AtomicUsize,
tag_data: Mutex<HashMap<&'static str, TagBudget>>,
event_callback: Mutex<Option<Box<dyn Fn(BudgetEvent) + Send + Sync>>>,
}
#[derive(Debug, Clone)]
pub struct TagBudget {
pub name: &'static str,
pub soft_limit: usize,
pub hard_limit: usize,
pub current_usage: usize,
pub peak_usage: usize,
pub allocation_count: u64,
pub deallocation_count: u64,
}
impl TagBudget {
pub fn new(name: &'static str, soft_limit: usize, hard_limit: usize) -> Self {
Self {
name,
soft_limit,
hard_limit,
current_usage: 0,
peak_usage: 0,
allocation_count: 0,
deallocation_count: 0,
}
}
pub fn check_status(&self, additional_size: usize) -> BudgetStatus {
let projected = self.current_usage + additional_size;
if self.hard_limit > 0 && projected > self.hard_limit {
BudgetStatus::Exceeded
} else if self.soft_limit > 0 && projected > self.soft_limit {
BudgetStatus::Warning
} else {
BudgetStatus::Ok
}
}
pub fn usage_percent(&self) -> f64 {
if self.hard_limit == 0 {
0.0
} else {
(self.current_usage as f64 / self.hard_limit as f64) * 100.0
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BudgetStatus {
Ok,
Warning,
Exceeded,
}
#[derive(Debug, Clone)]
pub enum BudgetEvent {
SoftLimitExceeded {
tag: &'static str,
current: usize,
limit: usize,
},
HardLimitExceeded {
tag: &'static str,
current: usize,
limit: usize,
},
GlobalLimitExceeded {
current: usize,
limit: usize,
},
NewPeak {
tag: &'static str,
peak: usize,
},
}
impl BudgetManager {
pub fn new(global_limit: usize) -> Self {
Self {
global_limit,
current_usage: AtomicUsize::new(0),
tag_data: Mutex::new(HashMap::new()),
event_callback: Mutex::new(None),
}
}
pub fn set_event_callback<F>(&self, callback: F)
where
F: Fn(BudgetEvent) + Send + Sync + 'static,
{
let mut cb = self.event_callback.lock();
*cb = Some(Box::new(callback));
}
pub fn register_tag(&self, tag: &AllocationTag, soft_limit: usize, hard_limit: usize) {
let mut data = self.tag_data.lock();
data.insert(tag.name(), TagBudget::new(tag.name(), soft_limit, hard_limit));
}
pub fn register_tag_budget(&self, name: &'static str, soft_limit: usize, hard_limit: usize) {
let mut data = self.tag_data.lock();
data.insert(name, TagBudget::new(name, soft_limit, hard_limit));
}
pub fn check_allocation(&self, size: usize, new_total: usize) -> BudgetStatus {
if self.global_limit > 0 && new_total > self.global_limit {
self.emit_event(BudgetEvent::GlobalLimitExceeded {
current: new_total,
limit: self.global_limit,
});
return BudgetStatus::Exceeded;
}
self.current_usage.store(new_total, Ordering::Relaxed);
if self.global_limit > 0 {
let soft_limit = self.global_limit * 9 / 10;
if new_total > soft_limit {
return BudgetStatus::Warning;
}
}
let _ = size; BudgetStatus::Ok
}
pub fn check_tagged_allocation(&self, tag: &AllocationTag, size: usize) -> BudgetStatus {
let mut data = self.tag_data.lock();
let budget = data.entry(tag.name()).or_insert_with(|| {
TagBudget::new(tag.name(), 0, 0) });
let status = budget.check_status(size);
budget.current_usage += size;
budget.allocation_count += 1;
if budget.current_usage > budget.peak_usage {
budget.peak_usage = budget.current_usage;
self.emit_event(BudgetEvent::NewPeak {
tag: tag.name(),
peak: budget.peak_usage,
});
}
match status {
BudgetStatus::Warning => {
self.emit_event(BudgetEvent::SoftLimitExceeded {
tag: tag.name(),
current: budget.current_usage,
limit: budget.soft_limit,
});
}
BudgetStatus::Exceeded => {
self.emit_event(BudgetEvent::HardLimitExceeded {
tag: tag.name(),
current: budget.current_usage,
limit: budget.hard_limit,
});
}
BudgetStatus::Ok => {}
}
status
}
pub fn record_tagged_deallocation(&self, tag: &AllocationTag, size: usize) {
let mut data = self.tag_data.lock();
if let Some(budget) = data.get_mut(tag.name()) {
budget.current_usage = budget.current_usage.saturating_sub(size);
budget.deallocation_count += 1;
}
}
pub fn current_usage(&self) -> usize {
self.current_usage.load(Ordering::Relaxed)
}
pub fn global_limit(&self) -> usize {
self.global_limit
}
pub fn get_all_tag_budgets(&self) -> Vec<TagBudget> {
let data = self.tag_data.lock();
data.values().cloned().collect()
}
pub fn get_tag_budget(&self, tag: &AllocationTag) -> Option<TagBudget> {
let data = self.tag_data.lock();
data.get(tag.name()).cloned()
}
pub fn reset_stats(&self) {
let mut data = self.tag_data.lock();
for budget in data.values_mut() {
budget.current_usage = 0;
budget.peak_usage = 0;
budget.allocation_count = 0;
budget.deallocation_count = 0;
}
self.current_usage.store(0, Ordering::Relaxed);
}
fn emit_event(&self, event: BudgetEvent) {
if let Some(ref callback) = *self.event_callback.lock() {
callback(event);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tag_budget_tracking() {
let manager = BudgetManager::new(0);
let tag = AllocationTag::new("test");
manager.register_tag(&tag, 1000, 2000);
let status = manager.check_tagged_allocation(&tag, 500);
assert_eq!(status, BudgetStatus::Ok);
let status = manager.check_tagged_allocation(&tag, 600);
assert_eq!(status, BudgetStatus::Warning);
let budget = manager.get_tag_budget(&tag).unwrap();
assert_eq!(budget.current_usage, 1100);
assert_eq!(budget.allocation_count, 2);
}
#[test]
fn test_hard_limit() {
let manager = BudgetManager::new(0);
let tag = AllocationTag::new("limited");
manager.register_tag(&tag, 500, 1000);
manager.check_tagged_allocation(&tag, 800);
let status = manager.check_tagged_allocation(&tag, 300);
assert_eq!(status, BudgetStatus::Exceeded);
}
#[test]
fn test_deallocation() {
let manager = BudgetManager::new(0);
let tag = AllocationTag::new("dealloc_test");
manager.check_tagged_allocation(&tag, 1000);
manager.record_tagged_deallocation(&tag, 400);
let budget = manager.get_tag_budget(&tag).unwrap();
assert_eq!(budget.current_usage, 600);
assert_eq!(budget.deallocation_count, 1);
}
}