Skip to content

Commit 30f7954

Browse files
committed
Add PoolingAllocatorMetrics
This exposes some basic runtime metrics derived from the internal state of a `PoolingInstanceAllocator`. Two new atomics were added to PoolingInstanceAllocator: `live_memories` and `live_tables`. While these counts could be derived from existing state it would require acquiring mutexes on some inner state.
1 parent 1292237 commit 30f7954

File tree

6 files changed

+158
-3
lines changed

6 files changed

+158
-3
lines changed

crates/wasmtime/src/engine.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,13 @@ impl Engine {
720720
crate::runtime::vm::tls_eager_initialize();
721721
}
722722

723+
/// Returns a [`PoolingAllocatorMetrics`] if this engine was configured with
724+
/// [`InstanceAllocationStrategy::Pooling`].
725+
#[cfg(feature = "pooling-allocator")]
726+
pub fn pooling_allocator_metrics(&self) -> Option<crate::vm::PoolingAllocatorMetrics> {
727+
crate::runtime::vm::PoolingAllocatorMetrics::new(self)
728+
}
729+
723730
pub(crate) fn allocator(&self) -> &dyn crate::runtime::vm::InstanceAllocator {
724731
self.inner.allocator.as_ref()
725732
}

crates/wasmtime/src/runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ pub use values::*;
9595
pub(crate) use uninhabited::*;
9696

9797
#[cfg(feature = "pooling-allocator")]
98-
pub use vm::PoolConcurrencyLimitError;
98+
pub use vm::{PoolConcurrencyLimitError, PoolingAllocatorMetrics};
9999

100100
#[cfg(feature = "profiling")]
101101
mod profiling;

crates/wasmtime/src/runtime/vm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ pub use crate::runtime::vm::instance::{
102102
};
103103
#[cfg(feature = "pooling-allocator")]
104104
pub use crate::runtime::vm::instance::{
105-
InstanceLimits, PoolConcurrencyLimitError, PoolingInstanceAllocator,
105+
InstanceLimits, PoolConcurrencyLimitError, PoolingAllocatorMetrics, PoolingInstanceAllocator,
106106
PoolingInstanceAllocatorConfig,
107107
};
108108
pub use crate::runtime::vm::interpreter::*;

crates/wasmtime/src/runtime/vm/instance/allocator.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub use self::on_demand::OnDemandInstanceAllocator;
3232
mod pooling;
3333
#[cfg(feature = "pooling-allocator")]
3434
pub use self::pooling::{
35-
InstanceLimits, PoolConcurrencyLimitError, PoolingInstanceAllocator,
35+
InstanceLimits, PoolConcurrencyLimitError, PoolingAllocatorMetrics, PoolingInstanceAllocator,
3636
PoolingInstanceAllocatorConfig,
3737
};
3838

@@ -357,6 +357,12 @@ pub unsafe trait InstanceAllocatorImpl {
357357

358358
/// Allow access to memory regions protected by any protection key.
359359
fn allow_all_pkeys(&self);
360+
361+
/// Returns `Some(&PoolingInstanceAllocator)` if this is one.
362+
#[cfg(feature = "pooling-allocator")]
363+
fn as_pooling(&self) -> Option<&PoolingInstanceAllocator> {
364+
None
365+
}
360366
}
361367

362368
/// A thing that can allocate instances.

crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
mod decommit_queue;
2222
mod index_allocator;
2323
mod memory_pool;
24+
mod metrics;
2425
mod table_pool;
2526

2627
#[cfg(feature = "gc")]
@@ -54,6 +55,7 @@ use crate::runtime::vm::{
5455
mpk::{self, ProtectionKey, ProtectionMask},
5556
sys::vm::PageMap,
5657
};
58+
use core::sync::atomic::AtomicUsize;
5759
use std::borrow::Cow;
5860
use std::fmt::Display;
5961
use std::sync::{Mutex, MutexGuard};
@@ -65,6 +67,8 @@ use wasmtime_environ::{
6567
DefinedMemoryIndex, DefinedTableIndex, HostPtr, Module, Tunables, VMOffsets,
6668
};
6769

70+
pub use self::metrics::PoolingAllocatorMetrics;
71+
6872
#[cfg(feature = "gc")]
6973
use super::GcHeapAllocationIndex;
7074
#[cfg(feature = "gc")]
@@ -305,8 +309,12 @@ pub struct PoolingInstanceAllocator {
305309
live_component_instances: AtomicU64,
306310

307311
decommit_queue: Mutex<DecommitQueue>,
312+
308313
memories: MemoryPool,
314+
live_memories: AtomicUsize,
315+
309316
tables: TablePool,
317+
live_tables: AtomicUsize,
310318

311319
#[cfg(feature = "gc")]
312320
gc_heaps: GcHeapPool,
@@ -335,6 +343,8 @@ impl Drop for PoolingInstanceAllocator {
335343

336344
debug_assert_eq!(self.live_component_instances.load(Ordering::Acquire), 0);
337345
debug_assert_eq!(self.live_core_instances.load(Ordering::Acquire), 0);
346+
debug_assert_eq!(self.live_memories.load(Ordering::Acquire), 0);
347+
debug_assert_eq!(self.live_tables.load(Ordering::Acquire), 0);
338348

339349
debug_assert!(self.memories.is_empty());
340350
debug_assert!(self.tables.is_empty());
@@ -357,7 +367,9 @@ impl PoolingInstanceAllocator {
357367
live_core_instances: AtomicU64::new(0),
358368
decommit_queue: Mutex::new(DecommitQueue::default()),
359369
memories: MemoryPool::new(config, tunables)?,
370+
live_memories: AtomicUsize::new(0),
360371
tables: TablePool::new(config)?,
372+
live_tables: AtomicUsize::new(0),
361373
#[cfg(feature = "gc")]
362374
gc_heaps: GcHeapPool::new(config)?,
363375
#[cfg(feature = "async")]
@@ -651,7 +663,12 @@ unsafe impl InstanceAllocatorImpl for PoolingInstanceAllocator {
651663
tunables: &Tunables,
652664
memory_index: Option<DefinedMemoryIndex>,
653665
) -> Result<(MemoryAllocationIndex, Memory)> {
666+
self.live_memories.fetch_add(1, Ordering::AcqRel);
654667
self.with_flush_and_retry(|| self.memories.allocate(request, ty, tunables, memory_index))
668+
.map_err(|err| {
669+
self.live_memories.fetch_sub(1, Ordering::AcqRel);
670+
err
671+
})
655672
}
656673

657674
unsafe fn deallocate_memory(
@@ -660,6 +677,7 @@ unsafe impl InstanceAllocatorImpl for PoolingInstanceAllocator {
660677
allocation_index: MemoryAllocationIndex,
661678
memory: Memory,
662679
) {
680+
self.live_memories.fetch_sub(1, Ordering::AcqRel);
663681
// Reset the image slot. If there is any error clearing the
664682
// image, just drop it here, and let the drop handler for the
665683
// slot unmap in a way that retains the address space
@@ -696,7 +714,12 @@ unsafe impl InstanceAllocatorImpl for PoolingInstanceAllocator {
696714
tunables: &Tunables,
697715
_table_index: DefinedTableIndex,
698716
) -> Result<(super::TableAllocationIndex, Table)> {
717+
self.live_tables.fetch_add(1, Ordering::AcqRel);
699718
self.with_flush_and_retry(|| self.tables.allocate(request, ty, tunables))
719+
.map_err(|err| {
720+
self.live_tables.fetch_sub(1, Ordering::AcqRel);
721+
err
722+
})
700723
}
701724

702725
unsafe fn deallocate_table(
@@ -705,6 +728,7 @@ unsafe impl InstanceAllocatorImpl for PoolingInstanceAllocator {
705728
allocation_index: TableAllocationIndex,
706729
mut table: Table,
707730
) {
731+
self.live_tables.fetch_sub(1, Ordering::AcqRel);
708732
let mut queue = DecommitQueue::default();
709733
// SAFETY: This table is no longer in use by the allocator when this
710734
// method is called and additionally all image ranges are pushed with
@@ -786,6 +810,10 @@ unsafe impl InstanceAllocatorImpl for PoolingInstanceAllocator {
786810
) -> (MemoryAllocationIndex, Memory) {
787811
self.gc_heaps.deallocate(allocation_index, gc_heap)
788812
}
813+
814+
fn as_pooling(&self) -> Option<&PoolingInstanceAllocator> {
815+
Some(self)
816+
}
789817
}
790818

791819
#[cfg(test)]
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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::Acquire)
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::Acquire)
32+
}
33+
34+
/// Returns the number of WebAssembly memories currently allocated.
35+
pub fn memories(&self) -> usize {
36+
self.allocator().live_memories.load(Ordering::Acquire)
37+
}
38+
39+
/// Returns the number of WebAssembly tables currently allocated.
40+
pub fn tables(&self) -> usize {
41+
self.allocator().live_tables.load(Ordering::Acquire)
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+
fn smoke_test() {
74+
// Start with nothing
75+
let engine =
76+
Engine::new(&Config::new().allocation_strategy(InstanceAllocationStrategy::pooling()))
77+
.unwrap();
78+
let metrics = engine.pooling_allocator_metrics().unwrap();
79+
80+
assert_eq!(metrics.core_instances(), 0);
81+
assert_eq!(metrics.component_instances(), 0);
82+
assert_eq!(metrics.memories(), 0);
83+
assert_eq!(metrics.tables(), 0);
84+
85+
// Instantiate one of each
86+
let mut store = Store::new(&engine, ());
87+
let component = Component::new(&engine, TEST_COMPONENT).unwrap();
88+
let linker = Linker::new(&engine);
89+
let instance = linker.instantiate(&mut store, &component).unwrap();
90+
91+
assert_eq!(metrics.core_instances(), 1);
92+
assert_eq!(metrics.component_instances(), 1);
93+
assert_eq!(metrics.memories(), 1);
94+
assert_eq!(metrics.tables(), 1);
95+
96+
// Back to nothing
97+
let _ = (instance, store);
98+
99+
assert_eq!(metrics.core_instances(), 0);
100+
assert_eq!(metrics.component_instances(), 0);
101+
assert_eq!(metrics.memories(), 0);
102+
assert_eq!(metrics.tables(), 0);
103+
}
104+
105+
#[test]
106+
fn test_non_pooling_allocator() {
107+
let engine =
108+
Engine::new(&Config::new().allocation_strategy(InstanceAllocationStrategy::OnDemand))
109+
.unwrap();
110+
111+
let maybe_metrics = engine.pooling_allocator_metrics();
112+
assert!(maybe_metrics.is_none());
113+
}
114+
}

0 commit comments

Comments
 (0)