wasmtime/runtime/vm/instance/allocator/pooling/
metrics.rs1use core::sync::atomic::Ordering;
2
3use crate::{Engine, vm::PoolingInstanceAllocator};
4
5#[derive(Clone)]
11pub struct PoolingAllocatorMetrics {
12 engine: Engine,
13}
14
15impl PoolingAllocatorMetrics {
16 pub(crate) fn new(engine: &Engine) -> Option<Self> {
17 engine.allocator().as_pooling().map(|_| Self {
18 engine: engine.clone(),
19 })
20 }
21
22 pub fn core_instances(&self) -> u64 {
24 self.allocator().live_core_instances.load(Ordering::Relaxed)
25 }
26
27 pub fn component_instances(&self) -> u64 {
29 self.allocator()
30 .live_component_instances
31 .load(Ordering::Relaxed)
32 }
33
34 pub fn memories(&self) -> usize {
36 self.allocator().live_memories.load(Ordering::Relaxed)
37 }
38
39 pub fn tables(&self) -> usize {
41 self.allocator().live_tables.load(Ordering::Relaxed)
42 }
43
44 #[cfg(feature = "async")]
46 pub fn stacks(&self) -> usize {
47 self.allocator().live_stacks.load(Ordering::Relaxed)
48 }
49
50 #[cfg(feature = "gc")]
52 pub fn gc_heaps(&self) -> usize {
53 self.allocator().live_gc_heaps.load(Ordering::Relaxed)
54 }
55
56 pub fn unused_warm_memories(&self) -> u32 {
63 self.allocator().memories.unused_warm_slots()
64 }
65
66 pub fn unused_memory_bytes_resident(&self) -> usize {
70 self.allocator().memories.unused_bytes_resident()
71 }
72
73 pub fn unused_warm_tables(&self) -> u32 {
80 self.allocator().tables.unused_warm_slots()
81 }
82
83 pub fn unused_table_bytes_resident(&self) -> usize {
87 self.allocator().tables.unused_bytes_resident()
88 }
89
90 #[cfg(feature = "async")]
97 pub fn unused_warm_stacks(&self) -> u32 {
98 self.allocator().stacks.unused_warm_slots()
99 }
100
101 #[cfg(feature = "async")]
108 pub fn unused_stack_bytes_resident(&self) -> Option<usize> {
109 self.allocator().stacks.unused_bytes_resident()
110 }
111
112 fn allocator(&self) -> &PoolingInstanceAllocator {
113 self.engine
114 .allocator()
115 .as_pooling()
116 .expect("engine should have pooling allocator")
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use crate::vm::instance::allocator::pooling::StackPool;
123 use crate::{
124 Config, Enabled, InstanceAllocationStrategy, Module, PoolingAllocationConfig, Result,
125 Store,
126 component::{Component, Linker},
127 };
128 use std::vec::Vec;
129
130 use super::*;
131
132 const TEST_COMPONENT: &[u8] = b"
134 (component
135 (core module $m
136 (memory 1)
137 (table 1 funcref)
138 )
139 (core instance (instantiate (module $m)))
140 )
141 ";
142
143 pub(crate) fn small_pool_config() -> PoolingAllocationConfig {
144 let mut config = PoolingAllocationConfig::new();
145
146 config.total_memories(10);
147 config.max_memory_size(2 << 16);
148 config.total_tables(10);
149 config.table_elements(10);
150 config.total_stacks(1);
151
152 config
153 }
154
155 const TEST_MEMORY_MODULE: &str = r#"
158 (module
159 (memory (export "memory") 1)
160 (data (i32.const 0) "init")
161 )
162 "#;
163
164 #[test]
167 #[cfg_attr(miri, ignore)]
168 fn release_idle_pool_memory_frees_unused_slots() -> Result<()> {
169 let mut pool = small_pool_config();
170 pool.linear_memory_keep_resident(1 << 16);
171 let engine = Engine::new(&Config::new().allocation_strategy(pool))?;
172 let metrics = engine.pooling_allocator_metrics().unwrap();
173 let module = Module::new(&engine, TEST_MEMORY_MODULE)?;
174
175 {
177 let mut store = Store::new(&engine, ());
178 let instance = crate::Instance::new(&mut store, &module, &[])?;
179 let memory = instance.get_memory(&mut store, "memory").unwrap();
180 memory.data_mut(&mut store)[4..8].copy_from_slice(b"dirt");
181 }
182
183 let resident = metrics.unused_memory_bytes_resident();
187 if resident > 0 {
188 let released = engine.release_idle_pool_memory();
189 assert!(
190 released > 0,
191 "{resident} bytes were resident and none were released"
192 );
193 assert_eq!(
194 metrics.unused_memory_bytes_resident(),
195 0,
196 "a released slot still reports resident bytes"
197 );
198 assert_eq!(metrics.unused_warm_memories(), 1);
200 }
201
202 let mut store = Store::new(&engine, ());
206 let instance = crate::Instance::new(&mut store, &module, &[])?;
207 let memory = instance.get_memory(&mut store, "memory").unwrap();
208 assert_eq!(&memory.data(&store)[..8], b"init\0\0\0\0");
209
210 assert_eq!(engine.release_idle_pool_memory(), 0);
212 Ok(())
213 }
214
215 #[test]
216 #[cfg_attr(miri, ignore)]
217 fn smoke_test() {
218 let engine = Engine::new(&Config::new().allocation_strategy(small_pool_config())).unwrap();
220 let metrics = engine.pooling_allocator_metrics().unwrap();
221
222 assert_eq!(metrics.core_instances(), 0);
223 assert_eq!(metrics.component_instances(), 0);
224 assert_eq!(metrics.memories(), 0);
225 assert_eq!(metrics.tables(), 0);
226
227 let mut store = Store::new(&engine, ());
229 let component = Component::new(&engine, TEST_COMPONENT).unwrap();
230 let linker = Linker::new(&engine);
231 let instance = linker.instantiate(&mut store, &component).unwrap();
232
233 assert_eq!(metrics.core_instances(), 1);
234 assert_eq!(metrics.component_instances(), 1);
235 assert_eq!(metrics.memories(), 1);
236 assert_eq!(metrics.tables(), 1);
237
238 let _ = (instance, store);
240
241 assert_eq!(metrics.core_instances(), 0);
242 assert_eq!(metrics.component_instances(), 0);
243 assert_eq!(metrics.memories(), 0);
244 assert_eq!(metrics.tables(), 0);
245 }
246
247 #[test]
248 fn test_non_pooling_allocator() {
249 let engine =
250 Engine::new(&Config::new().allocation_strategy(InstanceAllocationStrategy::OnDemand))
251 .unwrap();
252
253 let maybe_metrics = engine.pooling_allocator_metrics();
254 assert!(maybe_metrics.is_none());
255 }
256
257 #[test]
258 #[cfg_attr(any(miri, not(target_os = "linux")), ignore)]
259 fn unused_memories_tables_and_more() -> Result<()> {
260 let mut pool = small_pool_config();
261 pool.linear_memory_keep_resident(65536);
262 pool.table_keep_resident(65536);
263 pool.pagemap_scan(Enabled::Auto);
264 let mut config = Config::new();
265 config.allocation_strategy(pool);
266 let engine = Engine::new(&config)?;
267
268 let metrics = engine.pooling_allocator_metrics().unwrap();
269 let host_page_size = crate::vm::host_page_size();
270
271 assert_eq!(metrics.memories(), 0);
272 assert_eq!(metrics.core_instances(), 0);
273 assert_eq!(metrics.component_instances(), 0);
274 assert_eq!(metrics.memories(), 0);
275 assert_eq!(metrics.tables(), 0);
276 assert_eq!(metrics.unused_warm_memories(), 0);
277 assert_eq!(metrics.unused_memory_bytes_resident(), 0);
278 assert_eq!(metrics.unused_warm_tables(), 0);
279 assert_eq!(metrics.unused_table_bytes_resident(), 0);
280
281 let m1 = Module::new(
282 &engine,
283 r#"
284 (module (memory (export "m") 1) (table 1 funcref))
285 "#,
286 )?;
287
288 let mut store = Store::new(&engine, ());
289 crate::Instance::new(&mut store, &m1, &[])?;
290 assert_eq!(metrics.memories(), 1);
291 assert_eq!(metrics.tables(), 1);
292 assert_eq!(metrics.core_instances(), 1);
293 assert_eq!(metrics.component_instances(), 0);
294 drop(store);
295
296 assert_eq!(metrics.memories(), 0);
297 assert_eq!(metrics.tables(), 0);
298 assert_eq!(metrics.core_instances(), 0);
299 assert_eq!(metrics.unused_warm_memories(), 1);
300 assert_eq!(metrics.unused_warm_tables(), 1);
301 if PoolingAllocationConfig::is_pagemap_scan_available() {
302 assert_eq!(metrics.unused_memory_bytes_resident(), 0);
303 assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
304 } else {
305 assert_eq!(metrics.unused_memory_bytes_resident(), 65536);
306 assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
307 }
308
309 let mut store = Store::new(&engine, ());
310 let i = crate::Instance::new(&mut store, &m1, &[])?;
311 assert_eq!(metrics.memories(), 1);
312 assert_eq!(metrics.tables(), 1);
313 assert_eq!(metrics.core_instances(), 1);
314 assert_eq!(metrics.component_instances(), 0);
315 assert_eq!(metrics.unused_warm_memories(), 0);
316 assert_eq!(metrics.unused_warm_tables(), 0);
317 assert_eq!(metrics.unused_memory_bytes_resident(), 0);
318 assert_eq!(metrics.unused_table_bytes_resident(), 0);
319 let m = i.get_memory(&mut store, "m").unwrap();
320 m.data_mut(&mut store)[0] = 1;
321 m.grow(&mut store, 1)?;
322 drop(store);
323
324 assert_eq!(metrics.memories(), 0);
325 assert_eq!(metrics.tables(), 0);
326 assert_eq!(metrics.core_instances(), 0);
327 assert_eq!(metrics.unused_warm_memories(), 1);
328 assert_eq!(metrics.unused_warm_tables(), 1);
329 if PoolingAllocationConfig::is_pagemap_scan_available() {
330 assert_eq!(metrics.unused_memory_bytes_resident(), host_page_size);
331 assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
332 } else {
333 assert_eq!(metrics.unused_memory_bytes_resident(), 65536);
334 assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
335 }
336
337 let stores = (0..10)
338 .map(|_| {
339 let mut store = Store::new(&engine, ());
340 crate::Instance::new(&mut store, &m1, &[]).unwrap();
341 store
342 })
343 .collect::<Vec<_>>();
344
345 assert_eq!(metrics.memories(), 10);
346 assert_eq!(metrics.tables(), 10);
347 assert_eq!(metrics.core_instances(), 10);
348 assert_eq!(metrics.unused_warm_memories(), 0);
349 assert_eq!(metrics.unused_warm_tables(), 0);
350 assert_eq!(metrics.unused_memory_bytes_resident(), 0);
351 assert_eq!(metrics.unused_table_bytes_resident(), 0);
352
353 drop(stores);
354
355 assert_eq!(metrics.memories(), 00);
356 assert_eq!(metrics.tables(), 00);
357 assert_eq!(metrics.core_instances(), 00);
358 assert_eq!(metrics.unused_warm_memories(), 10);
359 assert_eq!(metrics.unused_warm_tables(), 10);
360 if PoolingAllocationConfig::is_pagemap_scan_available() {
361 assert_eq!(metrics.unused_memory_bytes_resident(), host_page_size);
362 assert_eq!(metrics.unused_table_bytes_resident(), 10 * host_page_size);
363 } else {
364 assert_eq!(metrics.unused_memory_bytes_resident(), 10 * 65536);
365 assert_eq!(metrics.unused_table_bytes_resident(), 10 * host_page_size);
366 }
367
368 Ok(())
369 }
370
371 #[test]
372 #[cfg_attr(miri, ignore)]
373 fn gc_heaps() -> Result<()> {
374 let pool = small_pool_config();
375 let mut config = Config::new();
376 config.allocation_strategy(pool);
377 let engine = Engine::new(&config)?;
378
379 let metrics = engine.pooling_allocator_metrics().unwrap();
380
381 assert_eq!(metrics.gc_heaps(), 0);
382 let mut store = Store::new(&engine, ());
383 crate::ExternRef::new(&mut store, ())?;
384 assert_eq!(metrics.gc_heaps(), 1);
385 drop(store);
386 assert_eq!(metrics.gc_heaps(), 0);
387
388 Ok(())
389 }
390
391 #[tokio::test]
392 #[cfg_attr(miri, ignore)]
393 async fn stacks() -> Result<()> {
394 let pool = small_pool_config();
395 let mut config = Config::new();
396 config.allocation_strategy(pool);
397 let engine = Engine::new(&config)?;
398
399 let metrics = engine.pooling_allocator_metrics().unwrap();
400
401 assert_eq!(metrics.stacks(), 0);
402 assert_eq!(metrics.unused_warm_stacks(), 0);
403 let mut store = Store::new(&engine, ());
404
405 crate::Func::wrap(&mut store, || {})
406 .call_async(&mut store, &[], &mut [])
407 .await?;
408 assert_eq!(metrics.stacks(), 1);
409 drop(store);
410 assert_eq!(metrics.stacks(), 0);
411 assert_eq!(metrics.unused_stack_bytes_resident(), None);
412 if StackPool::enabled() {
413 assert_eq!(metrics.unused_warm_stacks(), 1);
414 } else {
415 assert_eq!(metrics.unused_warm_stacks(), 0);
416 }
417
418 Ok(())
419 }
420}