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 #[test]
156 #[cfg_attr(miri, ignore)]
157 fn smoke_test() {
158 let engine = Engine::new(&Config::new().allocation_strategy(small_pool_config())).unwrap();
160 let metrics = engine.pooling_allocator_metrics().unwrap();
161
162 assert_eq!(metrics.core_instances(), 0);
163 assert_eq!(metrics.component_instances(), 0);
164 assert_eq!(metrics.memories(), 0);
165 assert_eq!(metrics.tables(), 0);
166
167 let mut store = Store::new(&engine, ());
169 let component = Component::new(&engine, TEST_COMPONENT).unwrap();
170 let linker = Linker::new(&engine);
171 let instance = linker.instantiate(&mut store, &component).unwrap();
172
173 assert_eq!(metrics.core_instances(), 1);
174 assert_eq!(metrics.component_instances(), 1);
175 assert_eq!(metrics.memories(), 1);
176 assert_eq!(metrics.tables(), 1);
177
178 let _ = (instance, store);
180
181 assert_eq!(metrics.core_instances(), 0);
182 assert_eq!(metrics.component_instances(), 0);
183 assert_eq!(metrics.memories(), 0);
184 assert_eq!(metrics.tables(), 0);
185 }
186
187 #[test]
188 fn test_non_pooling_allocator() {
189 let engine =
190 Engine::new(&Config::new().allocation_strategy(InstanceAllocationStrategy::OnDemand))
191 .unwrap();
192
193 let maybe_metrics = engine.pooling_allocator_metrics();
194 assert!(maybe_metrics.is_none());
195 }
196
197 #[test]
198 #[cfg_attr(any(miri, not(target_os = "linux")), ignore)]
199 fn unused_memories_tables_and_more() -> Result<()> {
200 let mut pool = small_pool_config();
201 pool.linear_memory_keep_resident(65536);
202 pool.table_keep_resident(65536);
203 pool.pagemap_scan(Enabled::Auto);
204 let mut config = Config::new();
205 config.allocation_strategy(pool);
206 let engine = Engine::new(&config)?;
207
208 let metrics = engine.pooling_allocator_metrics().unwrap();
209 let host_page_size = crate::vm::host_page_size();
210
211 assert_eq!(metrics.memories(), 0);
212 assert_eq!(metrics.core_instances(), 0);
213 assert_eq!(metrics.component_instances(), 0);
214 assert_eq!(metrics.memories(), 0);
215 assert_eq!(metrics.tables(), 0);
216 assert_eq!(metrics.unused_warm_memories(), 0);
217 assert_eq!(metrics.unused_memory_bytes_resident(), 0);
218 assert_eq!(metrics.unused_warm_tables(), 0);
219 assert_eq!(metrics.unused_table_bytes_resident(), 0);
220
221 let m1 = Module::new(
222 &engine,
223 r#"
224 (module (memory (export "m") 1) (table 1 funcref))
225 "#,
226 )?;
227
228 let mut store = Store::new(&engine, ());
229 crate::Instance::new(&mut store, &m1, &[])?;
230 assert_eq!(metrics.memories(), 1);
231 assert_eq!(metrics.tables(), 1);
232 assert_eq!(metrics.core_instances(), 1);
233 assert_eq!(metrics.component_instances(), 0);
234 drop(store);
235
236 assert_eq!(metrics.memories(), 0);
237 assert_eq!(metrics.tables(), 0);
238 assert_eq!(metrics.core_instances(), 0);
239 assert_eq!(metrics.unused_warm_memories(), 1);
240 assert_eq!(metrics.unused_warm_tables(), 1);
241 if PoolingAllocationConfig::is_pagemap_scan_available() {
242 assert_eq!(metrics.unused_memory_bytes_resident(), 0);
243 assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
244 } else {
245 assert_eq!(metrics.unused_memory_bytes_resident(), 65536);
246 assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
247 }
248
249 let mut store = Store::new(&engine, ());
250 let i = crate::Instance::new(&mut store, &m1, &[])?;
251 assert_eq!(metrics.memories(), 1);
252 assert_eq!(metrics.tables(), 1);
253 assert_eq!(metrics.core_instances(), 1);
254 assert_eq!(metrics.component_instances(), 0);
255 assert_eq!(metrics.unused_warm_memories(), 0);
256 assert_eq!(metrics.unused_warm_tables(), 0);
257 assert_eq!(metrics.unused_memory_bytes_resident(), 0);
258 assert_eq!(metrics.unused_table_bytes_resident(), 0);
259 let m = i.get_memory(&mut store, "m").unwrap();
260 m.data_mut(&mut store)[0] = 1;
261 m.grow(&mut store, 1)?;
262 drop(store);
263
264 assert_eq!(metrics.memories(), 0);
265 assert_eq!(metrics.tables(), 0);
266 assert_eq!(metrics.core_instances(), 0);
267 assert_eq!(metrics.unused_warm_memories(), 1);
268 assert_eq!(metrics.unused_warm_tables(), 1);
269 if PoolingAllocationConfig::is_pagemap_scan_available() {
270 assert_eq!(metrics.unused_memory_bytes_resident(), host_page_size);
271 assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
272 } else {
273 assert_eq!(metrics.unused_memory_bytes_resident(), 65536);
274 assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
275 }
276
277 let stores = (0..10)
278 .map(|_| {
279 let mut store = Store::new(&engine, ());
280 crate::Instance::new(&mut store, &m1, &[]).unwrap();
281 store
282 })
283 .collect::<Vec<_>>();
284
285 assert_eq!(metrics.memories(), 10);
286 assert_eq!(metrics.tables(), 10);
287 assert_eq!(metrics.core_instances(), 10);
288 assert_eq!(metrics.unused_warm_memories(), 0);
289 assert_eq!(metrics.unused_warm_tables(), 0);
290 assert_eq!(metrics.unused_memory_bytes_resident(), 0);
291 assert_eq!(metrics.unused_table_bytes_resident(), 0);
292
293 drop(stores);
294
295 assert_eq!(metrics.memories(), 00);
296 assert_eq!(metrics.tables(), 00);
297 assert_eq!(metrics.core_instances(), 00);
298 assert_eq!(metrics.unused_warm_memories(), 10);
299 assert_eq!(metrics.unused_warm_tables(), 10);
300 if PoolingAllocationConfig::is_pagemap_scan_available() {
301 assert_eq!(metrics.unused_memory_bytes_resident(), host_page_size);
302 assert_eq!(metrics.unused_table_bytes_resident(), 10 * host_page_size);
303 } else {
304 assert_eq!(metrics.unused_memory_bytes_resident(), 10 * 65536);
305 assert_eq!(metrics.unused_table_bytes_resident(), 10 * host_page_size);
306 }
307
308 Ok(())
309 }
310
311 #[test]
312 #[cfg_attr(miri, ignore)]
313 fn gc_heaps() -> Result<()> {
314 let pool = small_pool_config();
315 let mut config = Config::new();
316 config.allocation_strategy(pool);
317 let engine = Engine::new(&config)?;
318
319 let metrics = engine.pooling_allocator_metrics().unwrap();
320
321 assert_eq!(metrics.gc_heaps(), 0);
322 let mut store = Store::new(&engine, ());
323 crate::ExternRef::new(&mut store, ())?;
324 assert_eq!(metrics.gc_heaps(), 1);
325 drop(store);
326 assert_eq!(metrics.gc_heaps(), 0);
327
328 Ok(())
329 }
330
331 #[tokio::test]
332 #[cfg_attr(miri, ignore)]
333 async fn stacks() -> Result<()> {
334 let pool = small_pool_config();
335 let mut config = Config::new();
336 config.allocation_strategy(pool);
337 config.async_support(true);
338 let engine = Engine::new(&config)?;
339
340 let metrics = engine.pooling_allocator_metrics().unwrap();
341
342 assert_eq!(metrics.stacks(), 0);
343 assert_eq!(metrics.unused_warm_stacks(), 0);
344 let mut store = Store::new(&engine, ());
345
346 crate::Func::wrap(&mut store, || {})
347 .call_async(&mut store, &[], &mut [])
348 .await?;
349 assert_eq!(metrics.stacks(), 1);
350 drop(store);
351 assert_eq!(metrics.stacks(), 0);
352 assert_eq!(metrics.unused_stack_bytes_resident(), None);
353 if StackPool::enabled() {
354 assert_eq!(metrics.unused_warm_stacks(), 1);
355 } else {
356 assert_eq!(metrics.unused_warm_stacks(), 0);
357 }
358
359 Ok(())
360 }
361}