use core::sync::atomic::Ordering;
use crate::{Engine, vm::PoolingInstanceAllocator};
#[derive(Clone)]
pub struct PoolingAllocatorMetrics {
engine: Engine,
}
impl PoolingAllocatorMetrics {
pub(crate) fn new(engine: &Engine) -> Option<Self> {
engine.allocator().as_pooling().map(|_| Self {
engine: engine.clone(),
})
}
pub fn core_instances(&self) -> u64 {
self.allocator().live_core_instances.load(Ordering::Relaxed)
}
pub fn component_instances(&self) -> u64 {
self.allocator()
.live_component_instances
.load(Ordering::Relaxed)
}
pub fn memories(&self) -> usize {
self.allocator().live_memories.load(Ordering::Relaxed)
}
pub fn tables(&self) -> usize {
self.allocator().live_tables.load(Ordering::Relaxed)
}
#[cfg(feature = "async")]
pub fn stacks(&self) -> usize {
self.allocator().live_stacks.load(Ordering::Relaxed)
}
#[cfg(feature = "gc")]
pub fn gc_heaps(&self) -> usize {
self.allocator().live_gc_heaps.load(Ordering::Relaxed)
}
pub fn unused_warm_memories(&self) -> u32 {
self.allocator().memories.unused_warm_slots()
}
pub fn unused_memory_bytes_resident(&self) -> usize {
self.allocator().memories.unused_bytes_resident()
}
pub fn unused_warm_tables(&self) -> u32 {
self.allocator().tables.unused_warm_slots()
}
pub fn unused_table_bytes_resident(&self) -> usize {
self.allocator().tables.unused_bytes_resident()
}
#[cfg(feature = "async")]
pub fn unused_warm_stacks(&self) -> u32 {
self.allocator().stacks.unused_warm_slots()
}
#[cfg(feature = "async")]
pub fn unused_stack_bytes_resident(&self) -> Option<usize> {
self.allocator().stacks.unused_bytes_resident()
}
fn allocator(&self) -> &PoolingInstanceAllocator {
self.engine
.allocator()
.as_pooling()
.expect("engine should have pooling allocator")
}
}
#[cfg(test)]
mod tests {
use crate::vm::instance::allocator::pooling::StackPool;
use crate::{
Config, Enabled, InstanceAllocationStrategy, Module, PoolingAllocationConfig, Result,
Store,
component::{Component, Linker},
};
use std::vec::Vec;
use super::*;
const TEST_COMPONENT: &[u8] = b"
(component
(core module $m
(memory 1)
(table 1 funcref)
)
(core instance (instantiate (module $m)))
)
";
pub(crate) fn small_pool_config() -> PoolingAllocationConfig {
let mut config = PoolingAllocationConfig::new();
config.total_memories(10);
config.max_memory_size(2 << 16);
config.total_tables(10);
config.table_elements(10);
config.total_stacks(1);
config
}
#[test]
#[cfg_attr(miri, ignore)]
fn smoke_test() {
let engine = Engine::new(&Config::new().allocation_strategy(small_pool_config())).unwrap();
let metrics = engine.pooling_allocator_metrics().unwrap();
assert_eq!(metrics.core_instances(), 0);
assert_eq!(metrics.component_instances(), 0);
assert_eq!(metrics.memories(), 0);
assert_eq!(metrics.tables(), 0);
let mut store = Store::new(&engine, ());
let component = Component::new(&engine, TEST_COMPONENT).unwrap();
let linker = Linker::new(&engine);
let instance = linker.instantiate(&mut store, &component).unwrap();
assert_eq!(metrics.core_instances(), 1);
assert_eq!(metrics.component_instances(), 1);
assert_eq!(metrics.memories(), 1);
assert_eq!(metrics.tables(), 1);
let _ = (instance, store);
assert_eq!(metrics.core_instances(), 0);
assert_eq!(metrics.component_instances(), 0);
assert_eq!(metrics.memories(), 0);
assert_eq!(metrics.tables(), 0);
}
#[test]
fn test_non_pooling_allocator() {
let engine =
Engine::new(&Config::new().allocation_strategy(InstanceAllocationStrategy::OnDemand))
.unwrap();
let maybe_metrics = engine.pooling_allocator_metrics();
assert!(maybe_metrics.is_none());
}
#[test]
#[cfg_attr(any(miri, not(target_os = "linux")), ignore)]
fn unused_memories_tables_and_more() -> Result<()> {
let mut pool = small_pool_config();
pool.linear_memory_keep_resident(65536);
pool.table_keep_resident(65536);
pool.pagemap_scan(Enabled::Auto);
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let metrics = engine.pooling_allocator_metrics().unwrap();
let host_page_size = crate::vm::host_page_size();
assert_eq!(metrics.memories(), 0);
assert_eq!(metrics.core_instances(), 0);
assert_eq!(metrics.component_instances(), 0);
assert_eq!(metrics.memories(), 0);
assert_eq!(metrics.tables(), 0);
assert_eq!(metrics.unused_warm_memories(), 0);
assert_eq!(metrics.unused_memory_bytes_resident(), 0);
assert_eq!(metrics.unused_warm_tables(), 0);
assert_eq!(metrics.unused_table_bytes_resident(), 0);
let m1 = Module::new(
&engine,
r#"
(module (memory (export "m") 1) (table 1 funcref))
"#,
)?;
let mut store = Store::new(&engine, ());
crate::Instance::new(&mut store, &m1, &[])?;
assert_eq!(metrics.memories(), 1);
assert_eq!(metrics.tables(), 1);
assert_eq!(metrics.core_instances(), 1);
assert_eq!(metrics.component_instances(), 0);
drop(store);
assert_eq!(metrics.memories(), 0);
assert_eq!(metrics.tables(), 0);
assert_eq!(metrics.core_instances(), 0);
assert_eq!(metrics.unused_warm_memories(), 1);
assert_eq!(metrics.unused_warm_tables(), 1);
if PoolingAllocationConfig::is_pagemap_scan_available() {
assert_eq!(metrics.unused_memory_bytes_resident(), 0);
assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
} else {
assert_eq!(metrics.unused_memory_bytes_resident(), 65536);
assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
}
let mut store = Store::new(&engine, ());
let i = crate::Instance::new(&mut store, &m1, &[])?;
assert_eq!(metrics.memories(), 1);
assert_eq!(metrics.tables(), 1);
assert_eq!(metrics.core_instances(), 1);
assert_eq!(metrics.component_instances(), 0);
assert_eq!(metrics.unused_warm_memories(), 0);
assert_eq!(metrics.unused_warm_tables(), 0);
assert_eq!(metrics.unused_memory_bytes_resident(), 0);
assert_eq!(metrics.unused_table_bytes_resident(), 0);
let m = i.get_memory(&mut store, "m").unwrap();
m.data_mut(&mut store)[0] = 1;
m.grow(&mut store, 1)?;
drop(store);
assert_eq!(metrics.memories(), 0);
assert_eq!(metrics.tables(), 0);
assert_eq!(metrics.core_instances(), 0);
assert_eq!(metrics.unused_warm_memories(), 1);
assert_eq!(metrics.unused_warm_tables(), 1);
if PoolingAllocationConfig::is_pagemap_scan_available() {
assert_eq!(metrics.unused_memory_bytes_resident(), host_page_size);
assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
} else {
assert_eq!(metrics.unused_memory_bytes_resident(), 65536);
assert_eq!(metrics.unused_table_bytes_resident(), host_page_size);
}
let stores = (0..10)
.map(|_| {
let mut store = Store::new(&engine, ());
crate::Instance::new(&mut store, &m1, &[]).unwrap();
store
})
.collect::<Vec<_>>();
assert_eq!(metrics.memories(), 10);
assert_eq!(metrics.tables(), 10);
assert_eq!(metrics.core_instances(), 10);
assert_eq!(metrics.unused_warm_memories(), 0);
assert_eq!(metrics.unused_warm_tables(), 0);
assert_eq!(metrics.unused_memory_bytes_resident(), 0);
assert_eq!(metrics.unused_table_bytes_resident(), 0);
drop(stores);
assert_eq!(metrics.memories(), 00);
assert_eq!(metrics.tables(), 00);
assert_eq!(metrics.core_instances(), 00);
assert_eq!(metrics.unused_warm_memories(), 10);
assert_eq!(metrics.unused_warm_tables(), 10);
if PoolingAllocationConfig::is_pagemap_scan_available() {
assert_eq!(metrics.unused_memory_bytes_resident(), host_page_size);
assert_eq!(metrics.unused_table_bytes_resident(), 10 * host_page_size);
} else {
assert_eq!(metrics.unused_memory_bytes_resident(), 10 * 65536);
assert_eq!(metrics.unused_table_bytes_resident(), 10 * host_page_size);
}
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
fn gc_heaps() -> Result<()> {
let pool = small_pool_config();
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let metrics = engine.pooling_allocator_metrics().unwrap();
assert_eq!(metrics.gc_heaps(), 0);
let mut store = Store::new(&engine, ());
crate::ExternRef::new(&mut store, ())?;
assert_eq!(metrics.gc_heaps(), 1);
drop(store);
assert_eq!(metrics.gc_heaps(), 0);
Ok(())
}
#[tokio::test]
#[cfg_attr(miri, ignore)]
async fn stacks() -> Result<()> {
let pool = small_pool_config();
let mut config = Config::new();
config.allocation_strategy(pool);
let engine = Engine::new(&config)?;
let metrics = engine.pooling_allocator_metrics().unwrap();
assert_eq!(metrics.stacks(), 0);
assert_eq!(metrics.unused_warm_stacks(), 0);
let mut store = Store::new(&engine, ());
crate::Func::wrap(&mut store, || {})
.call_async(&mut store, &[], &mut [])
.await?;
assert_eq!(metrics.stacks(), 1);
drop(store);
assert_eq!(metrics.stacks(), 0);
assert_eq!(metrics.unused_stack_bytes_resident(), None);
if StackPool::enabled() {
assert_eq!(metrics.unused_warm_stacks(), 1);
} else {
assert_eq!(metrics.unused_warm_stacks(), 0);
}
Ok(())
}
}