4#ifndef IT_D4NP_MEMORYPOOL_INSTRUMENTED_POOL_HPP_
5#define IT_D4NP_MEMORYPOOL_INSTRUMENTED_POOL_HPP_
41namespace it::d4np::memorypool {
104 [[nodiscard]]
static std::optional<InstrumentedPool>
make(std::size_t
block_size, std::size_t block_count) {
106 if (!pool.has_value()) {
115 std::size_t growth_factor) {
117 if (!pool.has_value()) {
129 : pool_(std::move(other.pool_)), allocations_(other.allocations_.load(std::memory_order_relaxed)),
130 deallocations_(other.deallocations_.load(std::memory_order_relaxed)),
131 allocation_failures_(other.allocation_failures_.load(std::memory_order_relaxed)),
132 live_(other.live_.load(std::memory_order_relaxed)),
133 peak_live_(other.peak_live_.load(std::memory_order_relaxed)), observers_(std::move(other.observers_)),
134 last_growths_(other.last_growths_.load(std::memory_order_relaxed)) {}
140 if (
this != &other) {
141 notify(PoolEvent::destroyed);
142 pool_ = std::move(other.pool_);
143 allocations_.store(other.allocations_.load(std::memory_order_relaxed), std::memory_order_relaxed);
144 deallocations_.store(other.deallocations_.load(std::memory_order_relaxed), std::memory_order_relaxed);
145 allocation_failures_.store(other.allocation_failures_.load(std::memory_order_relaxed),
146 std::memory_order_relaxed);
147 live_.store(other.live_.load(std::memory_order_relaxed), std::memory_order_relaxed);
148 peak_live_.store(other.peak_live_.load(std::memory_order_relaxed), std::memory_order_relaxed);
149 observers_ = std::move(other.observers_);
150 last_growths_.store(other.last_growths_.load(std::memory_order_relaxed), std::memory_order_relaxed);
157 notify(PoolEvent::destroyed);
162 observers_.push_back(&observer);
167 void* block =
nullptr;
170 }
catch (
const std::bad_alloc&) {
171 allocation_failures_.fetch_add(1U, std::memory_order_relaxed);
172 notify(PoolEvent::exhausted);
183 if (block ==
nullptr) {
184 allocation_failures_.fetch_add(1U, std::memory_order_relaxed);
185 notify(PoolEvent::exhausted);
195 if (block !=
nullptr) {
196 deallocations_.fetch_add(1U, std::memory_order_relaxed);
200 std::size_t live = live_.load(std::memory_order_relaxed);
201 while (live > 0U && !live_.compare_exchange_weak(live, live - 1U, std::memory_order_relaxed)) {
210 return PoolStats{allocations_.load(std::memory_order_relaxed), deallocations_.load(std::memory_order_relaxed),
211 allocation_failures_.load(std::memory_order_relaxed), live_.load(std::memory_order_relaxed),
212 peak_live_.load(std::memory_order_relaxed)};
220 <<
" peak_live=" << snapshot.
peak_live_ <<
"\n";
240 void record_allocation() noexcept {
241 allocations_.fetch_add(1U, std::memory_order_relaxed);
242 const std::size_t live = live_.fetch_add(1U, std::memory_order_relaxed) + 1U;
244 std::size_t peak = peak_live_.load(std::memory_order_relaxed);
245 while (peak < live && !peak_live_.compare_exchange_weak(peak, live, std::memory_order_relaxed)) {
251 void notify(PoolEvent event)
noexcept {
252 const PoolStats snapshot =
stats();
253 for (PoolObserver*
const observer : observers_) {
254 observer->on_pool_event(event, snapshot);
260 void notify_if_grew() noexcept {
265 std::size_t last = last_growths_.load(std::memory_order_relaxed);
266 while (growths > last) {
267 if (last_growths_.compare_exchange_weak(last, growths, std::memory_order_relaxed)) {
268 notify(PoolEvent::grew);
276 std::atomic<std::size_t> allocations_{0U};
277 std::atomic<std::size_t> deallocations_{0U};
278 std::atomic<std::size_t> allocation_failures_{0U};
279 std::atomic<std::size_t> live_{0U};
280 std::atomic<std::size_t> peak_live_{0U};
281 std::vector<PoolObserver*> observers_;
284 std::atomic<std::size_t> last_growths_{0U};
Move-only Decorator over Pool that instruments allocation activity.
static std::optional< InstrumentedPool > make_dynamic(std::size_t block_size, std::size_t block_count, std::size_t growth_factor)
Factory mirroring Pool::make_dynamic (ADR-0024) — std::nullopt on failure.
InstrumentedPool & operator=(InstrumentedPool &&other) noexcept
Move-assign; releases the current pool and re-seeds the counters + observers.
void * allocate()
Throwing allocation verb (ADR-0016 §2).
PoolStats stats() const noexcept
InstrumentedPool(InstrumentedPool &&other) noexcept
Move-construct; the atomic counters are loaded and re-seeded (ADR-0025 §2).
void deallocate(void *block) noexcept
Return a block; counts the deallocation for any non-null pointer.
~InstrumentedPool()
Notify observers of destruction (ADR-0026); a moved-from instance has no observers.
std::size_t metadata_bytes() const noexcept
void write_summary(std::ostream &os) const
Write a one-line human-readable summary of the counters to os.
memory_pool_t * native_handle() noexcept
void add_observer(PoolObserver &observer)
Register an observer of lifecycle events (ADR-0026).
static std::optional< InstrumentedPool > make(std::size_t block_size, std::size_t block_count)
Factory mirroring Pool::make — std::nullopt on construction failure.
std::size_t block_size() const noexcept
InstrumentedPool(Pool &&pool) noexcept
Adopt pool by move and start instrumenting it.
void * try_allocate() noexcept
Non-throwing allocation verb (ADR-0016 §2).
Owning, non-copyable, move-only wrapper around a memory_pool_t*.
void deallocate(void *block) noexcept
Return a previously allocated block to the pool in O(1).
void * allocate()
Allocate one block in O(1) — throwing verb (ADR-0016 §2).
std::size_t block_size() const noexcept
Report the configured per-block size in bytes (ADR-0018 §3).
memory_pool_t * native_handle() noexcept
void * try_allocate() noexcept
Allocate one block in O(1) — non-throwing verb (ADR-0016 §2).
static std::optional< Pool > make_dynamic(std::size_t block_size, std::size_t block_count, std::size_t growth_factor)
Factory function for a dynamic-growth pool (spec §2.2, ADR-0022 / ADR-0024): on exhaustion it acquire...
std::size_t metadata_bytes() const noexcept
Report the per-pool metadata overhead in bytes (spec §3.2 / ADR-0015).
static std::optional< Pool > make(std::size_t block_size, std::size_t block_count)
Factory function returning an engaged std::optional<Pool> on successful construction or std::nullopt ...
PoolEvent
Pool lifecycle events delivered to observers (ADR-0026).
@ exhausted
an allocation found the pool exhausted (returned NULL / threw)
@ grew
the (dynamic) pool acquired an overflow chunk
@ destroyed
the instrumented pool is being destroyed
struct memory_pool memory_pool_t
Opaque handle to a memory pool instance.
size_t memory_pool_growths(const memory_pool_t *pool)
Report how many times pool has grown — i.e.
C++17 RAII wrapper around the C memory pool.
Observer of pool-lifecycle events (the GoF Observer — ADR-0026).
virtual void on_pool_event(PoolEvent event, const PoolStats &stats) noexcept=0
Called once per event, with a snapshot of the pool's counters.
Copyable snapshot of an InstrumentedPool's counters (ADR-0025 §2).
std::size_t peak_live_
high-water mark of live_
std::size_t allocations_
successful allocations
std::size_t deallocations_
deallocate calls with a non-null block
std::size_t live_
currently outstanding blocks
std::size_t allocation_failures_
allocate/try_allocate that found the pool exhausted