1use std::collections::HashMap;
22
23use serde::Serialize;
24
25mod api;
26pub use self::api::ResourceAPIHandler;
27
28mod grant;
29pub use self::grant::MemoryGrant;
30
31mod limiter;
32pub use self::limiter::MemoryLimiter;
33
34mod registry;
35pub use self::registry::{ComponentRegistry, ComponentRegistryHandle, MemoryBoundsBuilder};
36
37mod verifier;
38pub use self::verifier::{BoundsVerifier, VerifiedBounds, VerifierError};
39
40#[cfg(test)]
41pub(crate) mod test_util;
42
43pub trait MemoryBounds {
52 fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder);
54}
55
56impl<T> MemoryBounds for &T
57where
58 T: MemoryBounds,
59{
60 fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder) {
61 T::specify_bounds(self, builder);
62 }
63}
64
65impl<T> MemoryBounds for Box<T>
66where
67 T: MemoryBounds + ?Sized,
68{
69 fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder) {
70 T::specify_bounds(self, builder);
71 }
72}
73
74#[derive(Clone, Debug, Serialize)]
76#[serde(tag = "type")]
77pub enum UsageExpr {
78 Config {
80 name: String,
82 value: usize,
84 },
85
86 StructSize {
88 name: String,
90 value: usize,
92 },
93
94 Constant {
96 name: String,
98 value: usize,
100 },
101
102 Product {
104 values: Vec<UsageExpr>,
106 },
107
108 Sum {
110 values: Vec<UsageExpr>,
112 },
113}
114
115impl UsageExpr {
116 pub fn config(s: impl Into<String>, value: usize) -> Self {
118 Self::Config { name: s.into(), value }
119 }
120
121 pub fn constant(s: impl Into<String>, value: usize) -> Self {
123 Self::Constant { name: s.into(), value }
124 }
125
126 pub fn struct_size<T>(s: impl Into<String>) -> Self {
128 Self::StructSize {
129 name: s.into(),
130 value: std::mem::size_of::<T>(),
131 }
132 }
133
134 pub fn product(_s: impl Into<String>, lhs: UsageExpr, rhs: UsageExpr) -> Self {
136 Self::Product { values: vec![lhs, rhs] }
137 }
138
139 pub fn sum(_s: impl Into<String>, lhs: UsageExpr, rhs: UsageExpr) -> Self {
141 Self::Sum { values: vec![lhs, rhs] }
142 }
143
144 fn evaluate(&self) -> usize {
152 match self {
153 Self::Config { value, .. } | Self::StructSize { value, .. } | Self::Constant { value, .. } => *value,
154 Self::Product { values } => values.iter().map(UsageExpr::evaluate).fold(1, usize::saturating_mul),
155 Self::Sum { values } => values.iter().map(UsageExpr::evaluate).fold(0, usize::saturating_add),
156 }
157 }
158}
159
160#[derive(Clone, Debug, Default)]
162pub struct ComponentBounds {
163 self_minimum_required_bytes: Vec<UsageExpr>,
164 self_firm_limit_bytes: Vec<UsageExpr>,
165 subcomponents: HashMap<String, ComponentBounds>,
166}
167
168impl ComponentBounds {
169 pub fn total_minimum_required_bytes(&self) -> usize {
171 self.self_minimum_required_bytes
172 .iter()
173 .map(UsageExpr::evaluate)
174 .chain(self.subcomponents.values().map(|cb| cb.total_minimum_required_bytes()))
175 .fold(0, usize::saturating_add)
176 }
177
178 pub fn total_firm_limit_bytes(&self) -> usize {
182 self.self_minimum_required_bytes
183 .iter()
184 .chain(self.self_firm_limit_bytes.iter())
185 .map(UsageExpr::evaluate)
186 .chain(self.subcomponents.values().map(|cb| cb.total_firm_limit_bytes()))
187 .fold(0, usize::saturating_add)
188 }
189
190 pub fn subcomponents(&self) -> impl IntoIterator<Item = (&String, &ComponentBounds)> {
194 self.subcomponents.iter()
195 }
196
197 pub fn to_exprs(&self) -> Vec<serde_json::Value> {
199 let path = vec!["root".to_string()];
200 let mut stack = vec![(path, self)];
201 let mut output = Vec::new();
202
203 while let Some((path, cb)) = stack.pop() {
204 for expr in &cb.self_minimum_required_bytes {
205 output.push(serde_json::json!({
206 "name": format!("{}.min", path.join(".")),
207 "expr": expr,
208 }));
209 }
210 for expr in &cb.self_firm_limit_bytes {
211 output.push(serde_json::json!({
212 "name": format!("{}.firm", path.join(".")),
213 "expr": expr,
214 }));
215 }
216
217 for (name, subcomponent) in cb.subcomponents() {
218 let mut path = path.clone();
219 path.push(name.clone());
220 stack.push((path, subcomponent));
221 }
222 }
223
224 output
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use std::collections::HashMap;
231
232 use super::{ComponentBounds, UsageExpr};
233
234 #[test]
235 fn leaf_expressions_evaluate_to_their_value() {
236 assert_eq!(UsageExpr::config("cfg", 7).evaluate(), 7);
237 assert_eq!(UsageExpr::constant("const", 11).evaluate(), 11);
238 assert_eq!(
239 UsageExpr::struct_size::<u64>("u64").evaluate(),
240 std::mem::size_of::<u64>()
241 );
242 }
243
244 #[test]
245 fn product_evaluates_to_the_product_of_its_subexpressions() {
246 let expr = UsageExpr::product(
247 "area",
248 UsageExpr::constant("width", 4),
249 UsageExpr::constant("height", 8),
250 );
251 assert_eq!(expr.evaluate(), 32);
252 }
253
254 #[test]
255 fn sum_evaluates_to_the_sum_of_its_subexpressions() {
256 let expr = UsageExpr::sum("total", UsageExpr::constant("a", 4), UsageExpr::constant("b", 8));
257 assert_eq!(expr.evaluate(), 12);
258 }
259
260 #[test]
261 fn products_and_sums_compose_recursively() {
262 let expr = UsageExpr::product(
264 "scaled",
265 UsageExpr::sum("base", UsageExpr::constant("a", 2), UsageExpr::constant("b", 3)),
266 UsageExpr::constant("factor", 4),
267 );
268 assert_eq!(expr.evaluate(), 20);
269 }
270
271 #[test]
272 fn empty_products_and_sums_keep_their_identities() {
273 assert_eq!(UsageExpr::Product { values: Vec::new() }.evaluate(), 1);
276 assert_eq!(UsageExpr::Sum { values: Vec::new() }.evaluate(), 0);
277 }
278
279 #[test]
280 fn a_sum_too_large_to_represent_saturates() {
281 let expr = UsageExpr::sum(
284 "total",
285 UsageExpr::config("queue budget", usize::MAX),
286 UsageExpr::constant("overhead", 4096),
287 );
288
289 assert_eq!(expr.evaluate(), usize::MAX);
290 }
291
292 #[test]
293 fn a_product_too_large_to_represent_saturates() {
294 let expr = UsageExpr::product(
295 "scaled",
296 UsageExpr::config("count", usize::MAX),
297 UsageExpr::constant("size", 2),
298 );
299
300 assert_eq!(expr.evaluate(), usize::MAX);
301 }
302
303 #[test]
304 fn saturation_survives_aggregation_across_subcomponents() {
305 let saturated = ComponentBounds {
308 self_minimum_required_bytes: vec![UsageExpr::config("queue budget", usize::MAX)],
309 self_firm_limit_bytes: vec![UsageExpr::constant("overhead", 4096)],
310 subcomponents: HashMap::new(),
311 };
312 let mut subcomponents = HashMap::new();
313 subcomponents.insert("forwarder".to_string(), saturated);
314
315 let bounds = ComponentBounds {
316 self_minimum_required_bytes: vec![UsageExpr::constant("root min", 1024)],
317 self_firm_limit_bytes: vec![UsageExpr::constant("root firm", 2048)],
318 subcomponents,
319 };
320
321 assert_eq!(bounds.total_minimum_required_bytes(), usize::MAX);
322 assert_eq!(bounds.total_firm_limit_bytes(), usize::MAX);
323 }
324}