1 use core::sync::atomic::Ordering;
2 
3 use 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)]
11 pub struct PoolingAllocatorMetrics {
12     engine: Engine,
13 }
14 
15 impl 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     fn allocator(&self) -> &PoolingInstanceAllocator {
45         self.engine
46             .allocator()
47             .as_pooling()
48             .expect("engine should have pooling allocator")
49     }
50 }
51 
52 #[cfg(test)]
53 mod tests {
54     use crate::{
55         Config, InstanceAllocationStrategy, Store,
56         component::{Component, Linker},
57     };
58 
59     use super::*;
60 
61     // A component with 1 core instance, 1 memory, 1 table
62     const TEST_COMPONENT: &[u8] = b"
63         (component
64             (core module $m
65                 (memory 1)
66                 (table 1 funcref)
67             )
68             (core instance (instantiate (module $m)))
69         )
70     ";
71 
72     #[test]
73     #[cfg_attr(miri, ignore)]
74     fn smoke_test() {
75         // Start with nothing
76         let engine =
77             Engine::new(&Config::new().allocation_strategy(InstanceAllocationStrategy::pooling()))
78                 .unwrap();
79         let metrics = engine.pooling_allocator_metrics().unwrap();
80 
81         assert_eq!(metrics.core_instances(), 0);
82         assert_eq!(metrics.component_instances(), 0);
83         assert_eq!(metrics.memories(), 0);
84         assert_eq!(metrics.tables(), 0);
85 
86         // Instantiate one of each
87         let mut store = Store::new(&engine, ());
88         let component = Component::new(&engine, TEST_COMPONENT).unwrap();
89         let linker = Linker::new(&engine);
90         let instance = linker.instantiate(&mut store, &component).unwrap();
91 
92         assert_eq!(metrics.core_instances(), 1);
93         assert_eq!(metrics.component_instances(), 1);
94         assert_eq!(metrics.memories(), 1);
95         assert_eq!(metrics.tables(), 1);
96 
97         // Back to nothing
98         let _ = (instance, store);
99 
100         assert_eq!(metrics.core_instances(), 0);
101         assert_eq!(metrics.component_instances(), 0);
102         assert_eq!(metrics.memories(), 0);
103         assert_eq!(metrics.tables(), 0);
104     }
105 
106     #[test]
107     fn test_non_pooling_allocator() {
108         let engine =
109             Engine::new(&Config::new().allocation_strategy(InstanceAllocationStrategy::OnDemand))
110                 .unwrap();
111 
112         let maybe_metrics = engine.pooling_allocator_metrics();
113         assert!(maybe_metrics.is_none());
114     }
115 }
116