mockforge_plugin_loader/
memory_tracking.rs1use wasmtime::ResourceLimiter;
27
28pub struct MemoryTracker {
34 max_memory_bytes: usize,
36 current_memory_bytes: usize,
39 peak_memory_bytes: usize,
43 max_tables: usize,
45 current_tables: usize,
47}
48
49impl MemoryTracker {
50 pub fn new(max_memory_mb: usize) -> Self {
52 Self::with_byte_limit(max_memory_mb * 1024 * 1024)
53 }
54
55 pub fn with_byte_limit(max_memory_bytes: usize) -> Self {
60 Self {
61 max_memory_bytes,
62 current_memory_bytes: 0,
63 peak_memory_bytes: 0,
64 max_tables: 10,
65 current_tables: 0,
66 }
67 }
68
69 pub fn current_memory(&self) -> usize {
71 self.current_memory_bytes
72 }
73
74 pub fn peak_memory(&self) -> usize {
76 self.peak_memory_bytes
77 }
78
79 pub fn max_memory(&self) -> usize {
81 self.max_memory_bytes
82 }
83
84 pub fn memory_usage_percent(&self) -> f64 {
86 if self.max_memory_bytes == 0 {
87 0.0
88 } else {
89 (self.current_memory_bytes as f64 / self.max_memory_bytes as f64) * 100.0
90 }
91 }
92
93 pub fn is_memory_exceeded(&self) -> bool {
95 self.current_memory_bytes > self.max_memory_bytes
96 }
97
98 fn update_peak(&mut self) {
99 if self.current_memory_bytes > self.peak_memory_bytes {
100 self.peak_memory_bytes = self.current_memory_bytes;
101 }
102 }
103}
104
105impl ResourceLimiter for MemoryTracker {
106 fn memory_growing(
107 &mut self,
108 current: usize,
109 desired: usize,
110 _maximum: Option<usize>,
111 ) -> anyhow::Result<bool> {
112 if desired > self.max_memory_bytes {
113 tracing::warn!(
114 "Memory growth denied: {} bytes requested, {} bytes allowed",
115 desired,
116 self.max_memory_bytes
117 );
118 return Ok(false);
119 }
120
121 self.current_memory_bytes = desired;
122 self.update_peak();
123
124 tracing::debug!(
125 "Memory growth allowed: {} -> {} bytes ({:.1}% of limit)",
126 current,
127 desired,
128 self.memory_usage_percent()
129 );
130
131 Ok(true)
132 }
133
134 fn table_growing(
135 &mut self,
136 _current: usize,
137 _desired: usize,
138 _maximum: Option<usize>,
139 ) -> anyhow::Result<bool> {
140 if self.current_tables >= self.max_tables {
141 tracing::warn!(
142 "Table creation denied: {} tables exist, {} allowed",
143 self.current_tables,
144 self.max_tables
145 );
146 return Ok(false);
147 }
148
149 self.current_tables += 1;
150 Ok(true)
151 }
152
153 fn tables(&self) -> usize {
154 self.current_tables
155 }
156
157 fn memories(&self) -> usize {
158 1
160 }
161}
162
163#[derive(Debug, Clone)]
166pub struct MemoryStats {
167 pub current_bytes: usize,
169 pub peak_bytes: usize,
171 pub limit_bytes: usize,
173 pub usage_percent: f64,
175}
176
177impl MemoryStats {
178 pub fn from_tracker(tracker: &MemoryTracker) -> Self {
180 Self {
181 current_bytes: tracker.current_memory(),
182 peak_bytes: tracker.peak_memory(),
183 limit_bytes: tracker.max_memory(),
184 usage_percent: tracker.memory_usage_percent(),
185 }
186 }
187
188 pub fn is_critical(&self) -> bool {
190 self.usage_percent > 90.0
191 }
192
193 pub fn is_high(&self) -> bool {
195 self.usage_percent > 75.0
196 }
197
198 pub fn summary(&self) -> String {
200 format!(
201 "{} / {} MB ({:.1}%), peak: {} MB",
202 self.current_bytes / (1024 * 1024),
203 self.limit_bytes / (1024 * 1024),
204 self.usage_percent,
205 self.peak_bytes / (1024 * 1024)
206 )
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_memory_tracker_creation() {
216 let tracker = MemoryTracker::new(10);
217 assert_eq!(tracker.current_memory(), 0);
218 assert_eq!(tracker.peak_memory(), 0);
219 assert_eq!(tracker.max_memory(), 10 * 1024 * 1024);
220 assert_eq!(tracker.memory_usage_percent(), 0.0);
221 }
222
223 #[test]
224 fn test_with_byte_limit_is_exact() {
225 let tracker = MemoryTracker::with_byte_limit(5_242_880); assert_eq!(tracker.max_memory(), 5_242_880);
227 }
228
229 #[test]
230 fn test_memory_tracker_growing() {
231 let mut tracker = MemoryTracker::new(10);
232
233 let ok = tracker.memory_growing(0, 5 * 1024 * 1024, None).unwrap();
234 assert!(ok);
235 assert_eq!(tracker.current_memory(), 5 * 1024 * 1024);
236 assert_eq!(tracker.peak_memory(), 5 * 1024 * 1024);
237
238 let denied = tracker.memory_growing(5 * 1024 * 1024, 15 * 1024 * 1024, None).unwrap();
239 assert!(!denied);
240 }
241
242 #[test]
243 fn test_memory_stats() {
244 let mut tracker = MemoryTracker::new(10);
245 tracker.memory_growing(0, 8 * 1024 * 1024, None).unwrap();
246
247 let stats = MemoryStats::from_tracker(&tracker);
248 assert_eq!(stats.current_bytes, 8 * 1024 * 1024);
249 assert_eq!(stats.limit_bytes, 10 * 1024 * 1024);
250 assert!(stats.is_high());
251 assert!(!stats.is_critical());
252 }
253
254 #[test]
255 fn test_memory_tracker_peak() {
256 let mut tracker = MemoryTracker::new(10);
257
258 tracker.memory_growing(0, 5 * 1024 * 1024, None).unwrap();
259 assert_eq!(tracker.peak_memory(), 5 * 1024 * 1024);
260
261 tracker.memory_growing(5 * 1024 * 1024, 8 * 1024 * 1024, None).unwrap();
262 assert_eq!(tracker.peak_memory(), 8 * 1024 * 1024);
263
264 tracker.current_memory_bytes = 6 * 1024 * 1024;
268 assert_eq!(tracker.peak_memory(), 8 * 1024 * 1024);
269 }
270
271 #[test]
272 fn test_table_limits() {
273 let mut tracker = MemoryTracker::new(10);
274 tracker.max_tables = 2;
275
276 assert!(tracker.table_growing(0, 1, None).unwrap());
277 assert_eq!(tracker.current_tables, 1);
278
279 assert!(tracker.table_growing(1, 2, None).unwrap());
280 assert_eq!(tracker.current_tables, 2);
281
282 assert!(!tracker.table_growing(2, 3, None).unwrap());
283 }
284}