use std::time::Instant;
#[derive(Debug, Clone)]
pub struct AllocatorSnapshot {
pub timestamp: Instant,
pub frame_number: u64,
pub global: GlobalSnapshot,
pub frame_arenas: Vec<FrameSnapshot>,
pub pools: Vec<PoolSnapshot>,
pub tags: Vec<TagSnapshot>,
pub streaming: Option<StreamingSnapshot>,
}
impl AllocatorSnapshot {
pub fn new(frame_number: u64) -> Self {
Self {
timestamp: Instant::now(),
frame_number,
global: GlobalSnapshot::default(),
frame_arenas: Vec::new(),
pools: Vec::new(),
tags: Vec::new(),
streaming: None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GlobalSnapshot {
pub total_allocated: usize,
pub peak_allocated: usize,
pub allocation_count: u64,
pub deallocation_count: u64,
pub frame_bytes: usize,
pub pool_bytes: usize,
pub heap_bytes: usize,
}
#[derive(Debug, Clone)]
pub struct FrameSnapshot {
pub thread_id: Option<u64>,
pub thread_name: Option<String>,
pub capacity: usize,
pub used: usize,
pub peak: usize,
pub allocations_this_frame: u64,
}
impl FrameSnapshot {
pub fn usage_percent(&self) -> f64 {
if self.capacity == 0 {
0.0
} else {
(self.used as f64 / self.capacity as f64) * 100.0
}
}
}
#[derive(Debug, Clone)]
pub struct PoolSnapshot {
pub size_class: usize,
pub in_use: usize,
pub available: usize,
pub total_objects: usize,
pub refill_count: u64,
}
impl PoolSnapshot {
pub fn efficiency_percent(&self) -> f64 {
if self.total_objects == 0 {
100.0
} else {
(self.in_use as f64 / self.total_objects as f64) * 100.0
}
}
pub fn total_bytes(&self) -> usize {
self.total_objects * self.size_class
}
}
#[derive(Debug, Clone)]
pub struct TagSnapshot {
pub name: String,
pub current_usage: usize,
pub peak_usage: usize,
pub soft_limit: usize,
pub hard_limit: usize,
pub allocation_count: u64,
pub deallocation_count: u64,
}
impl TagSnapshot {
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
}
}
pub fn is_warning(&self) -> bool {
self.soft_limit > 0 && self.current_usage > self.soft_limit
}
pub fn is_exceeded(&self) -> bool {
self.hard_limit > 0 && self.current_usage > self.hard_limit
}
}
#[derive(Debug, Clone)]
pub struct StreamingSnapshot {
pub budget: usize,
pub reserved: usize,
pub loaded: usize,
pub allocation_count: usize,
pub by_state: StreamingStateCount,
}
#[derive(Debug, Clone, Default)]
pub struct StreamingStateCount {
pub reserved: usize,
pub loading: usize,
pub ready: usize,
pub evicting: usize,
}
#[derive(Debug, Clone)]
pub struct SnapshotHistory {
max_snapshots: usize,
snapshots: Vec<AllocatorSnapshot>,
}
impl SnapshotHistory {
pub fn new(max_snapshots: usize) -> Self {
Self {
max_snapshots,
snapshots: Vec::with_capacity(max_snapshots),
}
}
pub fn push(&mut self, snapshot: AllocatorSnapshot) {
if self.snapshots.len() >= self.max_snapshots {
self.snapshots.remove(0);
}
self.snapshots.push(snapshot);
}
pub fn snapshots(&self) -> &[AllocatorSnapshot] {
&self.snapshots
}
pub fn latest(&self) -> Option<&AllocatorSnapshot> {
self.snapshots.last()
}
pub fn memory_timeline(&self) -> Vec<(u64, usize)> {
self.snapshots
.iter()
.map(|s| (s.frame_number, s.global.total_allocated))
.collect()
}
pub fn peak_timeline(&self) -> Vec<(u64, usize)> {
self.snapshots
.iter()
.map(|s| (s.frame_number, s.global.peak_allocated))
.collect()
}
pub fn clear(&mut self) {
self.snapshots.clear();
}
}
impl Default for SnapshotHistory {
fn default() -> Self {
Self::new(300) }
}