wasmtime/runtime/vm/instance/allocator/pooling/
metrics.rs

1use core::sync::atomic::Ordering;
2
3use crate::{Engine, vm::PoolingInstanceAllocator};
4
5/// `PoolingAllocatorMetrics` provides access to runtime metrics of a pooling
6/// allocator configured with [`crate::InstanceAllocationStrategy::Pooling`].
7///
8/// This is a cheap cloneable handle which can be obtained with
9/// [`Engine::pooling_allocator_metrics`].
10#[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    /// Returns the number of core (module) instances currently allocated.
23    pub fn core_instances(&self) -> u64 {
24        self.allocator().live_core_instances.load(Ordering::Relaxed)
25    }
26
27    /// Returns the number of component instances currently allocated.
28    pub fn component_instances(&self) -> u64 {
29        self.allocator()
30            .live_component_instances
31            .load(Ordering::Relaxed)
32    }
33
34    /// Returns the number of WebAssembly memories currently allocated.
35    pub fn memories(&self) -> usize {
36        self.allocator().live_memories.load(Ordering::Relaxed)
37    }
38
39    /// Returns the number of WebAssembly tables currently allocated.
40    pub fn tables(&self) -> usize {
41        self.allocator().live_tables.load(Ordering::Relaxed)
42    }
43
44    /// Returns the number of WebAssembly stacks currently allocated.
45    #[cfg(feature = "async")]
46    pub fn stacks(&self) -> usize {
47        self.allocator().live_stacks.load(Ordering::Relaxed)
48    }
49
50    /// Returns the number of WebAssembly GC heaps currently allocated.
51    #[cfg(feature = "gc")]
52    pub fn gc_heaps(&self) -> usize {
53        self.allocator().live_gc_heaps.load(Ordering::Relaxed)
54    }
55
56    /// Returns the number of slots for linear memories in this allocator which
57    /// are not currently in use but were previously used.
58    ///
59    /// A "warm" slot means that there was a previous instantiation of a memory
60    /// in that slot. Warm slots are favored in general for allocating new
61    /// memories over using a slot that has never been used before.
62    pub fn unused_warm_memories(&self) -> u32 {
63        self.allocator().memories.unused_warm_slots()
64    }
65
66    /// Returns the number of bytes in this pooling allocator which are not part
67    /// of any in-used linear memory slot but were previously used and are kept
68    /// resident via the `*_keep_resident` configuration options.
69    pub fn unused_memory_bytes_resident(&self) -> usize {
70        self.allocator().memories.unused_bytes_resident()
71    }
72
73    /// Returns the number of slots for tables in this allocator which are not
74    /// currently in use but were previously used.
75    ///
76    /// A "warm" slot means that there was a previous instantiation of a table
77    /// in that slot. Warm slots are favored in general for allocating new
78    /// tables over using a slot that has never been used before.
79    pub fn unused_warm_tables(&self) -> u32 {
80        self.allocator().tables.unused_warm_slots()
81    }
82
83    /// Returns the number of bytes in this pooling allocator which are not part
84    /// of any in-used linear table slot but were previously used and are kept
85    /// resident via the `*_keep_resident` configuration options.
86    pub fn unused_table_bytes_resident(&self) -> usize {
87        self.allocator().tables.unused_bytes_resident()
88    }
89
90    /// Returns the number of slots for stacks in this allocator which are not
91    /// currently in use but were previously used.
92    ///
93    /// A "warm" slot means that there was a previous use of a stack
94    /// in that slot. Warm slots are favored in general for allocating new
95    /// stacks over using a slot that has never been used before.
96    #[cfg(feature = "async")]
97    pub fn unused_warm_stacks(&self) -> u32 {
98        self.allocator().stacks.unused_warm_slots()
99    }
100
101    /// Returns the number of bytes in this pooling allocator which are not part
102    /// of any in-used linear stack slot but were previously used and are kept
103    /// resident via the `*_keep_resident` configuration options.
104    ///
105    /// This returns `None` if the `async_stack_zeroing` option is disabled or
106    /// if the platform doesn't manage stacks (e.g. Windows returns `None`).
107    #[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    // A component with 1 core instance, 1 memory, 1 table
133    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        // Start with nothing
159        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        // Instantiate one of each
168        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        // Back to nothing
179        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}