graphos_common/memory/buffer/
stats.rs1use super::region::MemoryRegion;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub enum PressureLevel {
11 Normal,
13 Moderate,
15 High,
17 Critical,
19}
20
21impl PressureLevel {
22 #[must_use]
24 pub const fn description(&self) -> &'static str {
25 match self {
26 Self::Normal => "Normal operation",
27 Self::Moderate => "Proactive eviction",
28 Self::High => "Aggressive eviction/spilling",
29 Self::Critical => "Blocking allocations",
30 }
31 }
32
33 #[must_use]
35 pub const fn requires_eviction(&self) -> bool {
36 matches!(self, Self::Moderate | Self::High | Self::Critical)
37 }
38
39 #[must_use]
41 pub const fn should_spill(&self) -> bool {
42 matches!(self, Self::High | Self::Critical)
43 }
44
45 #[must_use]
47 pub const fn blocks_allocations(&self) -> bool {
48 matches!(self, Self::Critical)
49 }
50}
51
52impl std::fmt::Display for PressureLevel {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 Self::Normal => write!(f, "Normal"),
56 Self::Moderate => write!(f, "Moderate"),
57 Self::High => write!(f, "High"),
58 Self::Critical => write!(f, "Critical"),
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct BufferStats {
66 pub budget: usize,
68 pub total_allocated: usize,
70 pub region_allocated: [usize; 4],
72 pub pressure_level: PressureLevel,
74 pub consumer_count: usize,
76}
77
78impl BufferStats {
79 #[must_use]
81 pub fn utilization(&self) -> f64 {
82 if self.budget == 0 {
83 return 0.0;
84 }
85 self.total_allocated as f64 / self.budget as f64
86 }
87
88 #[must_use]
90 pub fn utilization_percent(&self) -> f64 {
91 self.utilization() * 100.0
92 }
93
94 #[must_use]
96 pub fn region_usage(&self, region: MemoryRegion) -> usize {
97 self.region_allocated[region.index()]
98 }
99
100 #[must_use]
102 pub fn available(&self) -> usize {
103 self.budget.saturating_sub(self.total_allocated)
104 }
105}
106
107impl Default for BufferStats {
108 fn default() -> Self {
109 Self {
110 budget: 0,
111 total_allocated: 0,
112 region_allocated: [0; 4],
113 pressure_level: PressureLevel::Normal,
114 consumer_count: 0,
115 }
116 }
117}
118
119impl std::fmt::Display for BufferStats {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 writeln!(f, "Buffer Manager Stats:")?;
122 writeln!(
123 f,
124 " Budget: {} ({:.2}% used)",
125 format_bytes(self.budget),
126 self.utilization_percent()
127 )?;
128 writeln!(
129 f,
130 " Allocated: {} / {}",
131 format_bytes(self.total_allocated),
132 format_bytes(self.budget)
133 )?;
134 writeln!(f, " Pressure: {}", self.pressure_level)?;
135 writeln!(f, " Consumers: {}", self.consumer_count)?;
136 writeln!(f, " Per-region:")?;
137 for region in MemoryRegion::all() {
138 writeln!(
139 f,
140 " {}: {}",
141 region.name(),
142 format_bytes(self.region_usage(region))
143 )?;
144 }
145 Ok(())
146 }
147}
148
149fn format_bytes(bytes: usize) -> String {
151 const KB: usize = 1024;
152 const MB: usize = KB * 1024;
153 const GB: usize = MB * 1024;
154
155 if bytes >= GB {
156 format!("{:.2} GB", bytes as f64 / GB as f64)
157 } else if bytes >= MB {
158 format!("{:.2} MB", bytes as f64 / MB as f64)
159 } else if bytes >= KB {
160 format!("{:.2} KB", bytes as f64 / KB as f64)
161 } else {
162 format!("{bytes} B")
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_pressure_level_ordering() {
172 assert!(PressureLevel::Normal < PressureLevel::Moderate);
173 assert!(PressureLevel::Moderate < PressureLevel::High);
174 assert!(PressureLevel::High < PressureLevel::Critical);
175 }
176
177 #[test]
178 fn test_pressure_level_properties() {
179 assert!(!PressureLevel::Normal.requires_eviction());
180 assert!(PressureLevel::Moderate.requires_eviction());
181 assert!(PressureLevel::High.requires_eviction());
182 assert!(PressureLevel::Critical.requires_eviction());
183
184 assert!(!PressureLevel::Normal.should_spill());
185 assert!(!PressureLevel::Moderate.should_spill());
186 assert!(PressureLevel::High.should_spill());
187 assert!(PressureLevel::Critical.should_spill());
188
189 assert!(!PressureLevel::Normal.blocks_allocations());
190 assert!(!PressureLevel::High.blocks_allocations());
191 assert!(PressureLevel::Critical.blocks_allocations());
192 }
193
194 #[test]
195 fn test_buffer_stats_utilization() {
196 let stats = BufferStats {
197 budget: 1000,
198 total_allocated: 750,
199 region_allocated: [250, 250, 200, 50],
200 pressure_level: PressureLevel::Moderate,
201 consumer_count: 3,
202 };
203
204 assert!((stats.utilization() - 0.75).abs() < 0.001);
205 assert!((stats.utilization_percent() - 75.0).abs() < 0.1);
206 assert_eq!(stats.available(), 250);
207 }
208
209 #[test]
210 fn test_buffer_stats_region_usage() {
211 let stats = BufferStats {
212 budget: 1000,
213 total_allocated: 600,
214 region_allocated: [100, 200, 250, 50],
215 pressure_level: PressureLevel::Normal,
216 consumer_count: 2,
217 };
218
219 assert_eq!(stats.region_usage(MemoryRegion::GraphStorage), 100);
220 assert_eq!(stats.region_usage(MemoryRegion::IndexBuffers), 200);
221 assert_eq!(stats.region_usage(MemoryRegion::ExecutionBuffers), 250);
222 assert_eq!(stats.region_usage(MemoryRegion::SpillStaging), 50);
223 }
224
225 #[test]
226 fn test_format_bytes() {
227 assert_eq!(format_bytes(512), "512 B");
228 assert_eq!(format_bytes(1024), "1.00 KB");
229 assert_eq!(format_bytes(1536), "1.50 KB");
230 assert_eq!(format_bytes(1024 * 1024), "1.00 MB");
231 assert_eq!(format_bytes(1024 * 1024 * 1024), "1.00 GB");
232 }
233}