ringkernel_core/resource/
estimate.rs1#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct MemoryEstimate {
6 pub primary_bytes: u64,
8 pub auxiliary_bytes: u64,
10 pub peak_bytes: u64,
12 pub confidence: f32,
14}
15
16impl Default for MemoryEstimate {
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22impl MemoryEstimate {
23 #[must_use]
25 pub fn new() -> Self {
26 Self {
27 primary_bytes: 0,
28 auxiliary_bytes: 0,
29 peak_bytes: 0,
30 confidence: 1.0,
31 }
32 }
33
34 #[must_use]
36 pub fn primary(bytes: u64) -> Self {
37 Self {
38 primary_bytes: bytes,
39 auxiliary_bytes: 0,
40 peak_bytes: bytes,
41 confidence: 1.0,
42 }
43 }
44
45 #[must_use]
47 pub fn with_primary(mut self, bytes: u64) -> Self {
48 self.primary_bytes = bytes;
49 self.update_peak();
50 self
51 }
52
53 #[must_use]
55 pub fn with_auxiliary(mut self, bytes: u64) -> Self {
56 self.auxiliary_bytes = bytes;
57 self.update_peak();
58 self
59 }
60
61 #[must_use]
63 pub fn with_peak(mut self, bytes: u64) -> Self {
64 self.peak_bytes = bytes;
65 self
66 }
67
68 #[must_use]
70 pub fn with_confidence(mut self, confidence: f32) -> Self {
71 self.confidence = confidence.clamp(0.0, 1.0);
72 self
73 }
74
75 #[must_use]
77 pub fn total_bytes(&self) -> u64 {
78 self.primary_bytes.saturating_add(self.auxiliary_bytes)
79 }
80
81 fn update_peak(&mut self) {
83 let total = self.total_bytes();
84 if self.peak_bytes < total {
85 self.peak_bytes = total;
86 }
87 }
88
89 #[must_use]
91 pub fn summary(&self) -> String {
92 format!(
93 "primary={}, auxiliary={}, peak={}, confidence={:.0}%",
94 format_bytes(self.primary_bytes),
95 format_bytes(self.auxiliary_bytes),
96 format_bytes(self.peak_bytes),
97 self.confidence * 100.0
98 )
99 }
100
101 #[must_use]
103 pub fn combine(&self, other: &MemoryEstimate) -> Self {
104 Self {
105 primary_bytes: self.primary_bytes.saturating_add(other.primary_bytes),
106 auxiliary_bytes: self.auxiliary_bytes.saturating_add(other.auxiliary_bytes),
107 peak_bytes: self.peak_bytes.saturating_add(other.peak_bytes),
108 confidence: (self.confidence + other.confidence) / 2.0,
109 }
110 }
111
112 #[must_use]
114 pub fn scale(&self, factor: f64) -> Self {
115 Self {
116 primary_bytes: (self.primary_bytes as f64 * factor) as u64,
117 auxiliary_bytes: (self.auxiliary_bytes as f64 * factor) as u64,
118 peak_bytes: (self.peak_bytes as f64 * factor) as u64,
119 confidence: self.confidence,
120 }
121 }
122}
123
124pub trait MemoryEstimator: Send + Sync {
126 fn estimate(&self) -> MemoryEstimate;
128
129 fn name(&self) -> &str;
131
132 fn estimate_for(&self, element_count: usize) -> MemoryEstimate {
134 let _ = element_count;
135 self.estimate()
136 }
137}
138
139#[derive(Debug, Clone)]
141pub struct LinearEstimator {
142 pub name: String,
144 pub bytes_per_element: usize,
146 pub fixed_overhead: usize,
148 pub auxiliary_per_element: usize,
150}
151
152impl LinearEstimator {
153 #[must_use]
155 pub fn new(name: impl Into<String>, bytes_per_element: usize) -> Self {
156 Self {
157 name: name.into(),
158 bytes_per_element,
159 fixed_overhead: 0,
160 auxiliary_per_element: 0,
161 }
162 }
163
164 #[must_use]
166 pub fn with_overhead(mut self, bytes: usize) -> Self {
167 self.fixed_overhead = bytes;
168 self
169 }
170
171 #[must_use]
173 pub fn with_auxiliary(mut self, bytes_per_element: usize) -> Self {
174 self.auxiliary_per_element = bytes_per_element;
175 self
176 }
177}
178
179impl MemoryEstimator for LinearEstimator {
180 fn estimate(&self) -> MemoryEstimate {
181 MemoryEstimate::new()
182 .with_primary(self.fixed_overhead as u64)
183 .with_confidence(0.9)
184 }
185
186 fn name(&self) -> &str {
187 &self.name
188 }
189
190 fn estimate_for(&self, element_count: usize) -> MemoryEstimate {
191 let primary = self.fixed_overhead + (self.bytes_per_element * element_count);
192 let auxiliary = self.auxiliary_per_element * element_count;
193
194 MemoryEstimate::new()
195 .with_primary(primary as u64)
196 .with_auxiliary(auxiliary as u64)
197 .with_confidence(0.9)
198 }
199}
200
201fn format_bytes(bytes: u64) -> String {
203 const KB: u64 = 1024;
204 const MB: u64 = KB * 1024;
205 const GB: u64 = MB * 1024;
206
207 if bytes >= GB {
208 format!("{:.2} GB", bytes as f64 / GB as f64)
209 } else if bytes >= MB {
210 format!("{:.2} MB", bytes as f64 / MB as f64)
211 } else if bytes >= KB {
212 format!("{:.2} KB", bytes as f64 / KB as f64)
213 } else {
214 format!("{} B", bytes)
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn test_memory_estimate_new() {
224 let estimate = MemoryEstimate::new();
225 assert_eq!(estimate.total_bytes(), 0);
226 assert!((estimate.confidence - 1.0).abs() < f32::EPSILON);
227 }
228
229 #[test]
230 fn test_memory_estimate_primary() {
231 let estimate = MemoryEstimate::primary(1024);
232 assert_eq!(estimate.primary_bytes, 1024);
233 assert_eq!(estimate.total_bytes(), 1024);
234 assert_eq!(estimate.peak_bytes, 1024);
235 }
236
237 #[test]
238 fn test_memory_estimate_builder() {
239 let estimate = MemoryEstimate::new()
240 .with_primary(1024)
241 .with_auxiliary(512)
242 .with_confidence(0.8);
243
244 assert_eq!(estimate.primary_bytes, 1024);
245 assert_eq!(estimate.auxiliary_bytes, 512);
246 assert_eq!(estimate.total_bytes(), 1536);
247 assert!((estimate.confidence - 0.8).abs() < f32::EPSILON);
248 }
249
250 #[test]
251 fn test_memory_estimate_combine() {
252 let a = MemoryEstimate::new().with_primary(1000).with_auxiliary(500);
253 let b = MemoryEstimate::new()
254 .with_primary(2000)
255 .with_auxiliary(1000);
256
257 let combined = a.combine(&b);
258 assert_eq!(combined.primary_bytes, 3000);
259 assert_eq!(combined.auxiliary_bytes, 1500);
260 }
261
262 #[test]
263 fn test_memory_estimate_scale() {
264 let estimate = MemoryEstimate::new().with_primary(1000);
265 let scaled = estimate.scale(2.0);
266 assert_eq!(scaled.primary_bytes, 2000);
267 }
268
269 #[test]
270 fn test_linear_estimator() {
271 let estimator = LinearEstimator::new("test", 64)
272 .with_overhead(1024)
273 .with_auxiliary(16);
274
275 let estimate = estimator.estimate_for(100);
276 assert_eq!(estimate.primary_bytes, 1024 + 64 * 100);
277 assert_eq!(estimate.auxiliary_bytes, 16 * 100);
278 }
279
280 #[test]
281 fn test_format_bytes() {
282 assert_eq!(format_bytes(512), "512 B");
283 assert_eq!(format_bytes(1024), "1.00 KB");
284 assert_eq!(format_bytes(1_500_000), "1.43 MB");
285 }
286}