memory_accounting/
lib.rs

1//! Building blocks for declaring and enforcing memory bounds for components.
2//!
3//! ## Overview
4//!
5//! This crate provides a three-pronged approach to memory accounting:
6//!
7//! - memory bounds (components declare their _expected_ memory usage)
8//! - allocation tracking (tracking _actual_ memory usage)
9//! - memory limiting (enforcing _maximum_ memory usage)
10//!
11//! Through this approach, data planes can be vastly more resilient to memory exhaustion or
12//! exceeding externally-applied memory limits.
13//!
14//! ## Memory bounds
15//!
16//! One major problem with resource planning is predicting memory usage. For many applications,
17//! there are a number of factors that can influence memory usage, such as:
18//!
19//! - the workload itself (amount of data coming in)
20//! - application configuration (buffer sizes)
21//! - application changes (new features, bug fixes)
22//!
23//! This requires additional effort by operators, potentially on an ongoing basis, to empirically
24//! determine the right amount of memory to dedicate. What if instead, an application could
25//! determine a reasonable upper bound on its memory usage based on its configuration and report
26//! that to the operator? This is the goal of memory bounds.
27//!
28//! Memory bounds are a way for components to declare their expected memory usage, categorized into
29//! both a minimum required amount and a firm limit. The minimum required amount is the amount of
30//! memory that is required for the component to function correctly, which generally encompasses
31//! things like pre-allocated buffers. The firm limit is meant to indicate the maximum amount of
32//! memory that the component should use, regardless of the workload.
33//!
34//! Providing firm limits does require some additional thought and care, as a component needs to be
35//! able to actually limit itself in order to adhere to those limits. While determining the the
36//! bounds themselves is out of scope for this crate, our other two prongs are meant to pick up the
37//! slack where memory bounds fall off.
38//!
39//! ## Allocation tracking
40//!
41//! As memory bounds are inherently lossy, and not everything can be fully bounded, we need a way to
42//! track the actual memory used against the expected memory usage. This is where allocation
43//! tracking comes into play and offers a very precise view into per-component memory usage.
44//!
45//! A custom allocator is provided that tracks all memory allocations, and more specifically,
46//! attributes them to a set of registered components. Components register with the allocator and
47//! receive a "token" that can be used to scope allocations to that component.
48//!
49//! By tracking allocations in this way, we end up with the actual usage of each component, which
50//! can then be compared against the memory bounds to determine if a component is exceeding its
51//! bounds or not. In cases where a component is exceeding its bounds, or the application as a whole
52//! is exceeding its configured limit, we need a way to attempt to enforce those limits.
53//!
54//! ## Memory limiting
55//!
56//! Memory limiting is the final prong in our approach to memory accounting.
57//!
58//! When the application is approaching its configured memory limit, or is exceeding the limit, a
59//! mechanism is needed to slow down the rate of memory growth. The global memory limiter is a
60//! mechanism for cooperatively applying backpressure in order to limit the rate of work, and
61//! thereby limit the rate of allocations. Components participate by utilizing the global memory
62//! limiter, which conditionally applies small delays in order to artificially generate backpressure.
63#![deny(warnings)]
64#![deny(missing_docs)]
65
66use std::collections::HashMap;
67
68//mod partitioner;
69
70#[cfg(test)]
71pub mod test_util;
72
73pub mod allocator;
74mod api;
75pub use self::api::MemoryAPIHandler;
76
77mod registry;
78
79use serde::Serialize;
80
81pub use self::registry::{ComponentRegistry, MemoryBoundsBuilder};
82
83mod grant;
84pub use self::grant::MemoryGrant;
85
86mod limiter;
87pub use self::limiter::MemoryLimiter;
88
89mod verifier;
90pub use self::verifier::{BoundsVerifier, VerifiedBounds, VerifierError};
91
92/// Memory bounds for a component.
93///
94/// Components will naturally allocate memory in many phases, from initialization to normal operation. In some cases,
95/// these allocations can be unbounded, leading to potential memory exhaustion.
96///
97/// When a component has a way to bound its memory usage, it can implement this trait to provide that accounting. A
98/// bounds builder exposes a simple interface for tallying up the memory usage of individual pieces of a component, such
99/// as buffers and buffer pools, containers, and more.
100pub trait MemoryBounds {
101    /// Specifies the minimum and firm memory bounds for this component and its subcomponents.
102    fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder);
103}
104
105impl<T> MemoryBounds for &T
106where
107    T: MemoryBounds,
108{
109    fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder) {
110        T::specify_bounds(self, builder);
111    }
112}
113
114impl<T> MemoryBounds for Box<T>
115where
116    T: MemoryBounds + ?Sized,
117{
118    fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder) {
119        T::specify_bounds(self, builder);
120    }
121}
122
123/// Represents a memory usage expression for a component.
124#[derive(Clone, Debug, Serialize)]
125#[serde(tag = "type")]
126pub enum UsageExpr {
127    /// A config value
128    Config {
129        /// The name
130        name: String,
131        /// The value
132        value: usize,
133    },
134
135    /// A struct size
136    StructSize {
137        /// The value
138        name: String,
139        /// The value
140        value: usize,
141    },
142
143    /// A constant value
144    Constant {
145        /// The name
146        name: String,
147        /// The value
148        value: usize,
149    },
150
151    /// A product of subexpressions
152    Product {
153        /// Values to multiply
154        values: Vec<UsageExpr>,
155    },
156
157    /// A sum of subexpressions
158    Sum {
159        /// Values to add
160        values: Vec<UsageExpr>,
161    },
162}
163
164impl UsageExpr {
165    /// Creates a new usage expression that is a config value.
166    pub fn config(s: impl Into<String>, value: usize) -> Self {
167        Self::Config { name: s.into(), value }
168    }
169
170    /// Creates a new usage expression that is a constant value.
171    pub fn constant(s: impl Into<String>, value: usize) -> Self {
172        Self::Constant { name: s.into(), value }
173    }
174
175    /// Creates a new usage expression that is a struct size.
176    pub fn struct_size<T>(s: impl Into<String>) -> Self {
177        Self::StructSize {
178            name: s.into(),
179            value: std::mem::size_of::<T>(),
180        }
181    }
182
183    /// Creates a new usage expression that is the product of two subexpressions.
184    pub fn product(_s: impl Into<String>, lhs: UsageExpr, rhs: UsageExpr) -> Self {
185        Self::Product { values: vec![lhs, rhs] }
186    }
187
188    /// Creates a new usage expression that is the sum of two subexpressions.
189    pub fn sum(_s: impl Into<String>, lhs: UsageExpr, rhs: UsageExpr) -> Self {
190        Self::Sum { values: vec![lhs, rhs] }
191    }
192
193    fn evaluate(&self) -> usize {
194        match self {
195            Self::Config { value, .. } | Self::StructSize { value, .. } | Self::Constant { value, .. } => *value,
196            Self::Product { values } => values.iter().map(UsageExpr::evaluate).product(),
197            Self::Sum { values } => values.iter().map(UsageExpr::evaluate).sum(),
198        }
199    }
200}
201
202/// Memory bounds for a component.
203#[derive(Clone, Debug, Default)]
204pub struct ComponentBounds {
205    self_minimum_required_bytes: Vec<UsageExpr>,
206    self_firm_limit_bytes: Vec<UsageExpr>,
207    subcomponents: HashMap<String, ComponentBounds>,
208}
209
210impl ComponentBounds {
211    /// Gets the total minimum required bytes for this component and all subcomponents.
212    pub fn total_minimum_required_bytes(&self) -> usize {
213        self.self_minimum_required_bytes
214            .iter()
215            .map(UsageExpr::evaluate)
216            .sum::<usize>()
217            + self
218                .subcomponents
219                .values()
220                .map(|cb| cb.total_minimum_required_bytes())
221                .sum::<usize>()
222    }
223
224    /// Gets the total firm limit bytes for this component and all subcomponents.
225    ///
226    /// The firm limit includes the minimum required bytes.
227    pub fn total_firm_limit_bytes(&self) -> usize {
228        self.self_minimum_required_bytes
229            .iter()
230            .map(UsageExpr::evaluate)
231            .sum::<usize>()
232            + self
233                .self_firm_limit_bytes
234                .iter()
235                .map(UsageExpr::evaluate)
236                .sum::<usize>()
237            + self
238                .subcomponents
239                .values()
240                .map(|cb| cb.total_firm_limit_bytes())
241                .sum::<usize>()
242    }
243
244    /// Returns an iterator of all subcomponents within this component.
245    ///
246    /// Only iterates over direct subcomponents, not the subcomponents of those subcomponents, and so on.
247    pub fn subcomponents(&self) -> impl IntoIterator<Item = (&String, &ComponentBounds)> {
248        self.subcomponents.iter()
249    }
250
251    /// Returns a tree of all bound expressions for this component and its subcomponents as JSON.
252    pub fn to_exprs(&self) -> Vec<serde_json::Value> {
253        let path = vec!["root".to_string()];
254        let mut stack = vec![(path, self)];
255        let mut output = Vec::new();
256
257        while let Some((path, cb)) = stack.pop() {
258            for expr in &cb.self_minimum_required_bytes {
259                output.push(serde_json::json!({
260                    "name": format!("{}.min", path.join(".")),
261                    "expr": expr,
262                }));
263            }
264            for expr in &cb.self_firm_limit_bytes {
265                output.push(serde_json::json!({
266                    "name": format!("{}.firm", path.join(".")),
267                    "expr": expr,
268                }));
269            }
270
271            for (name, subcomponent) in cb.subcomponents() {
272                let mut path = path.clone();
273                path.push(name.clone());
274                stack.push((path, subcomponent));
275            }
276        }
277
278        output
279    }
280}