...using the first usage situation as guideline, and verify proper operation through unit-test - the LocalBufferStage gets a set of thread-local BufferMetadata - furthermore, it accesses the central hub via dependency-injection - after each type registration ''from a worker thread'' data is published As an asside: ensure most metadata registration can ''move'' the TypeHandler
310 lines
7.4 KiB
C++
310 lines
7.4 KiB
C++
/*
|
||
DiagnosticBufferProvider - helper for testing against the BufferProvider interface
|
||
|
||
Copyright (C)
|
||
2011, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||
under the terms of the GNU General Public License as published by the
|
||
Free Software Foundation; either version 2 of the License, or (at your
|
||
option) any later version. See the file COPYING for further details.
|
||
|
||
* *****************************************************************/
|
||
|
||
|
||
/** @file diagnostic-buffer-provider.cpp
|
||
** Implementation details for an instrumented BufferProvider to support
|
||
** verifying render engine buffer allocation schemes through unit tests.
|
||
**
|
||
** @see buffer-provider-protocol-test.cpp
|
||
*/
|
||
|
||
|
||
#include "lib/error.hpp"
|
||
#include "vault/mem/diagnostic-buffer-provider.hpp"
|
||
#include "lib/format-string.hpp"
|
||
#include "lib/util-foreach.hpp"
|
||
#include "lib/util.hpp"
|
||
|
||
#include <algorithm>
|
||
#include <utility>
|
||
#include <memory>
|
||
|
||
|
||
namespace vault {
|
||
namespace mem {
|
||
namespace err = lumiera::error;
|
||
|
||
using util::_Fmt;
|
||
using diagn::Block;
|
||
using diagn::StateReg;
|
||
using std::unique_ptr;
|
||
using std::ranges::find_if;
|
||
using std::move;
|
||
|
||
using metadata::Key;
|
||
|
||
|
||
|
||
/* ====== Accounting API ====== */
|
||
|
||
Block::Block (Key key, Buff* mem)
|
||
: typeKey{mem? key.parentKey(): HashVal(key)}
|
||
, stateKey{mem? key : Key::INVALID}
|
||
, buffSize{key.storageSize()}
|
||
, storage{mem}
|
||
{ }
|
||
|
||
|
||
void
|
||
StateReg::record (Block block)
|
||
{
|
||
reg_.emplace_back (move (block));
|
||
}
|
||
|
||
size_t
|
||
StateReg::cnt() const
|
||
{
|
||
return reg_.size();
|
||
}
|
||
|
||
StateReg::Result
|
||
StateReg::operator[] (size_t seqNr) const
|
||
{
|
||
if (seqNr < cnt())
|
||
return reg_[seqNr];
|
||
else
|
||
return err::Invalid(_Fmt{"no Buffer with seq-nr %d (%d known entries)"}
|
||
% seqNr % cnt()
|
||
, LERR_(BOTTOM_VALUE));
|
||
}
|
||
|
||
|
||
namespace {
|
||
inline auto
|
||
findHash (auto& registry, HashVal handle)
|
||
{
|
||
return find_if (registry
|
||
,[handle](auto& entry) { return handle == HashVal(entry); });
|
||
}
|
||
}
|
||
|
||
StateReg::Result
|
||
StateReg::byHandle (HashVal handle) const
|
||
{
|
||
if (auto pos = findHash (reg_, handle)
|
||
;pos != reg_.end()
|
||
)
|
||
return *pos;
|
||
else
|
||
return err::Invalid("requested Buffer never encountered"
|
||
, LERR_(BOTTOM_VALUE));
|
||
}
|
||
|
||
bool
|
||
StateReg::contains (HashVal handle) const
|
||
{
|
||
auto pos = findHash (reg_, handle);
|
||
return pos != reg_.end();
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* Implementation of tracking and instrumentation
|
||
*/
|
||
struct DiagnosticBufferProvider::BlockTracker
|
||
: util::NonCopyable
|
||
{
|
||
StateReg created;
|
||
StateReg emitted;
|
||
StateReg released;
|
||
|
||
void
|
||
record_locked (Key stateKey, Buff* mem)
|
||
{
|
||
REQUIRE (not created.contains (stateKey));
|
||
created.record (Block{stateKey, mem});
|
||
}
|
||
|
||
void
|
||
record_emitted (HashVal id)
|
||
{
|
||
emitted.record (created.byHandle (id));
|
||
}
|
||
|
||
void
|
||
record_released (HashVal id)
|
||
{
|
||
released.record (created.byHandle (id));
|
||
}
|
||
};
|
||
|
||
|
||
class DiagnosticBufferProvider::InstrumentedStageProxy
|
||
: public BufferProviderSetup::Stage
|
||
{
|
||
using StageImp = unique_ptr<BufferProvider::BufferStage>;
|
||
using Tracker = DiagnosticBufferProvider::BlockTracker;
|
||
|
||
StageImp stage_;
|
||
Tracker& tracker_;
|
||
|
||
|
||
/* === BufferStage proxy implementation === */
|
||
|
||
ID
|
||
lookup (HashVal key) override
|
||
{
|
||
return stage_->lookup (key);
|
||
}
|
||
|
||
bool
|
||
isAllotted (HashVal stateKey) const override
|
||
{
|
||
return stage_->isAllotted (stateKey);
|
||
}
|
||
|
||
bool
|
||
isAccessible (HashVal stateKey) const override
|
||
{
|
||
return stage_->isAccessible (stateKey);
|
||
}
|
||
|
||
ID
|
||
defineBufferType (size_t buffSiz, TypeHandler handlerFunctions, LocalTag localTag)
|
||
{
|
||
return stage_->defineBufferType (buffSiz, move(handlerFunctions), localTag);
|
||
}
|
||
|
||
ID
|
||
mark_locked (ID typeKey, BuffAlloc storage) override
|
||
{
|
||
ID stateKey = stage_->mark_locked (typeKey, storage);
|
||
tracker_.record_locked (stateKey, getAddr(storage));
|
||
return stateKey;
|
||
}
|
||
|
||
ID
|
||
mark_emitted (HashVal id) override
|
||
{
|
||
ID stateKey = stage_->mark_emitted (id);
|
||
tracker_.record_emitted (stateKey);
|
||
return stateKey;
|
||
}
|
||
|
||
ID
|
||
mark_released (HashVal id) override
|
||
{
|
||
ID stateKey = stage_->mark_released (id);
|
||
tracker_.record_released (stateKey);
|
||
return stateKey;
|
||
}
|
||
|
||
ID
|
||
abandon (HashVal id, bool invokeDtor) override
|
||
{
|
||
ID stateKey = stage_->abandon (id, invokeDtor);
|
||
tracker_.record_released (stateKey);
|
||
return stateKey;
|
||
}
|
||
|
||
void
|
||
discard (HashVal stateKey) override
|
||
{
|
||
stage_->discard (stateKey);
|
||
}
|
||
|
||
public:
|
||
InstrumentedStageProxy (StageImp rawImpl, Tracker& tracker)
|
||
: stage_{move(rawImpl)}
|
||
, tracker_{tracker}
|
||
{ }
|
||
|
||
HashVal getFamilyID() const override { return stage_->getFamilyID(); }
|
||
};
|
||
|
||
//(End) internal diagnostics helper
|
||
|
||
|
||
|
||
|
||
|
||
DiagnosticBufferProvider::DiagnosticBufferProvider()
|
||
: NaiveBufferSetup{}
|
||
, tracker_{std::make_unique<BlockTracker>()}
|
||
{
|
||
decorate<InstrumentedStageProxy> (bufferStage_, *tracker_);
|
||
}
|
||
|
||
DiagnosticBufferProvider::~DiagnosticBufferProvider()
|
||
{
|
||
ENSURE (tracker_);
|
||
auto created = [this]{ return tracker_->created.cnt(); };
|
||
auto released = [this]{ return tracker_->released.cnt(); };
|
||
INFO (proc_mem, "discarding %zu diagnostic buffer entries", created() );
|
||
REQUIRE (released() <= created(), "Buffer handling broken: %zu excess released", released() - created());
|
||
WARN_IF (created() > released()
|
||
,proc_mem, "Allocation Logic mishap: %zu buffers not released", created() - released());
|
||
}
|
||
|
||
BufferDiagnostic::BufferDiagnostic (DiagnosticBufferProvider& thePro)
|
||
: dbp_{thePro}
|
||
, created{dbp_.tracker_->created}
|
||
, emitted{dbp_.tracker_->emitted}
|
||
, released{dbp_.tracker_->released}
|
||
{ }
|
||
|
||
|
||
|
||
/* === diagnostic API === */
|
||
|
||
Buff*
|
||
BufferDiagnostic::accessMemory (HashVal id)
|
||
{ // may throw when found
|
||
return created.byHandle(id).get().accessMemory();
|
||
}
|
||
|
||
bool
|
||
BufferDiagnostic::was_created (HashVal id)
|
||
{
|
||
return created.contains (id);
|
||
}
|
||
|
||
bool
|
||
BufferDiagnostic::was_emitted (HashVal id)
|
||
{
|
||
return emitted.contains (id);
|
||
}
|
||
|
||
bool
|
||
BufferDiagnostic::was_released (HashVal id)
|
||
{
|
||
return released.contains (id);
|
||
}
|
||
|
||
bool
|
||
BufferDiagnostic::is_in_use (HashVal id)
|
||
{
|
||
return was_created (id)
|
||
and not (was_released (id) or was_emitted (id));
|
||
}
|
||
|
||
bool
|
||
BufferDiagnostic::was_used (HashVal id)
|
||
{
|
||
return was_created (id)
|
||
and was_released (id);
|
||
}
|
||
|
||
bool
|
||
BufferDiagnostic::all_buffers_released()
|
||
{
|
||
return util::and_all (created.each()
|
||
,[this](Block const& entry){ return released.contains(entry); }
|
||
);
|
||
}
|
||
|
||
|
||
|
||
}} // namespace vault::mem
|