Skip to main content

ringkernel_core/resource/
estimate.rs

1//! Memory estimation for resource planning.
2
3/// Memory estimate for a workload.
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct MemoryEstimate {
6    /// Primary memory requirement (main data structures).
7    pub primary_bytes: u64,
8    /// Auxiliary memory (temporary buffers, working space).
9    pub auxiliary_bytes: u64,
10    /// Peak memory usage (during execution).
11    pub peak_bytes: u64,
12    /// Confidence level (0.0-1.0) of this estimate.
13    pub confidence: f32,
14}
15
16impl Default for MemoryEstimate {
17    fn default() -> Self {
18        Self::new()
19    }
20}
21
22impl MemoryEstimate {
23    /// Creates a new empty memory estimate.
24    #[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    /// Creates an estimate with only primary memory.
35    #[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    /// Builder method to set primary memory.
46    #[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    /// Builder method to set auxiliary memory.
54    #[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    /// Builder method to set peak memory.
62    #[must_use]
63    pub fn with_peak(mut self, bytes: u64) -> Self {
64        self.peak_bytes = bytes;
65        self
66    }
67
68    /// Builder method to set confidence.
69    #[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    /// Total estimated memory in bytes.
76    #[must_use]
77    pub fn total_bytes(&self) -> u64 {
78        self.primary_bytes.saturating_add(self.auxiliary_bytes)
79    }
80
81    /// Updates peak to be at least total.
82    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    /// Returns a human-readable summary.
90    #[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    /// Combines two estimates (for composite workloads).
102    #[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    /// Scales the estimate by a factor.
113    #[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
124/// Trait for types that can estimate their memory requirements.
125pub trait MemoryEstimator: Send + Sync {
126    /// Returns an estimate of memory required.
127    fn estimate(&self) -> MemoryEstimate;
128
129    /// Returns the name of this estimator (for logging).
130    fn name(&self) -> &str;
131
132    /// Returns an estimate scaled for a specific element count.
133    fn estimate_for(&self, element_count: usize) -> MemoryEstimate {
134        let _ = element_count;
135        self.estimate()
136    }
137}
138
139/// A linear memory estimator (bytes_per_element * count + overhead).
140#[derive(Debug, Clone)]
141pub struct LinearEstimator {
142    /// Name of this estimator.
143    pub name: String,
144    /// Bytes per element.
145    pub bytes_per_element: usize,
146    /// Fixed overhead bytes.
147    pub fixed_overhead: usize,
148    /// Auxiliary bytes per element.
149    pub auxiliary_per_element: usize,
150}
151
152impl LinearEstimator {
153    /// Creates a new linear estimator.
154    #[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    /// Builder method to set fixed overhead.
165    #[must_use]
166    pub fn with_overhead(mut self, bytes: usize) -> Self {
167        self.fixed_overhead = bytes;
168        self
169    }
170
171    /// Builder method to set auxiliary bytes per element.
172    #[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
201/// Formats bytes as human-readable string.
202fn 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}