diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e756158..cd6b8bc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,3 +9,13 @@ target_compile_features(mesytec-mnode PRIVATE cxx_std_17) target_compile_options(mesytec-mnode PRIVATE ${MVLC_NNG_MNODE_WARN_FLAGS}) add_subdirectory(tools) + +if (MNODE_BUILD_TESTS) + function (add_mnode_gtest name) + add_executable(test_${name} ${name}.test.cc) + target_link_libraries(test_${name} PRIVATE mesytec-mnode GTest::gtest_main) + add_test(NAME name COMMAND $) + endfunction() + + add_mnode_gtest(mana) +endif() diff --git a/src/internal/mana_arena.h b/src/internal/mana_arena.h new file mode 100644 index 0000000..7de87ee --- /dev/null +++ b/src/internal/mana_arena.h @@ -0,0 +1,116 @@ +#ifndef F20CF38F_7327_4608_8307_6AE058041CD5 +#define F20CF38F_7327_4608_8307_6AE058041CD5 + +#include +#include +#include +#include + +namespace mesytec::mnode::mana +{ + +class Arena +{ + public: + static constexpr size_t initial_segment_size = 1u << 20; + static constexpr size_t default_pad = 8; + + Arena() = default; + ~Arena() = default; + + Arena(Arena &&other); + Arena &operator=(Arena &&other); + + // pushes at least the required amount of memory onto the arena. + // 'required' is rounded up to the nearest multiple of 'default_pad' to make + // subsequent allocations aligned. + u8 *push_size(size_t required) + { + size_t padded = round_up(required, default_pad); + size_t pad_waste = padded - required; + required = padded; + + if (!segment_ || segment_->free() < required) + { + // could waste complete segments if required > default size of 1u << 20 but that's fine + // for now + segment_ = + &segments_.emplace_back(Arena::Segment(std::max(required, initial_segment_size))); + } + + assert(segment_ && segment_->free() >= required); + + std::memset(segment_->cur(), 0, required); + segment_->used += required; + ++allocations_; + pad_waste_ += pad_waste; + return segment_->cur(); + } + + template T *push_t(size_t count = 1) + { + static_assert(std::is_trivial::value, "T must be a trivial type"); + return reinterpret_cast(push_size(sizeof(T) * count)); + } + + const char *push_cstr(const std::string &str) + { + auto mem = push_size(str.size() + 1); + std::memcpy(mem, str.c_str(), str.size()); + return reinterpret_cast(mem); + } + + void reset() + { + std::for_each(std::begin(segments_), std::end(segments_), + [](Segment &seg) { seg.reset(); }); + segment_ = segments_.empty() ? nullptr : &segments_.front(); + allocations_ = 0; + pad_waste_ = 0; + } + + size_t allocations() const { return allocations_; } + size_t pad_waste() const { return pad_waste_; } + size_t segment_count() const { return segments_.size(); } + size_t capacity() const + { + auto accu = [](size_t sum, const Segment &seg) { return sum + seg.data.size(); }; + return std::accumulate(std::begin(segments_), std::end(segments_), static_cast(0u), + accu); + } + + size_t used() const + { + auto accu = [](size_t sum, const Segment &seg) { return sum + seg.used; }; + return std::accumulate(std::begin(segments_), std::end(segments_), static_cast(0u), + accu) - pad_waste_; + } + + private: + Arena(const Arena &) = delete; + Arena &operator=(const Arena &) = delete; + + struct Segment + { + std::vector data; + size_t used; + + explicit Segment(size_t size) + : data(size) + , used(0) + { + } + size_t free() const { return data.size() - used; } + u8 *cur() { return data.data() + used; } + void reset() { used = 0; } + }; + + std::list segments_; + Segment *segment_ = nullptr; + size_t allocations_ = 0; + size_t pad_waste_ = 0; +}; + +} // namespace mesytec::mnode::mana + +#endif /* F20CF38F_7327_4608_8307_6AE058041CD5 */ diff --git a/src/mana.test.cc b/src/mana.test.cc new file mode 100644 index 0000000..477ef0f --- /dev/null +++ b/src/mana.test.cc @@ -0,0 +1,36 @@ +#include +#include +#include "internal/mana_arena.h" + +using namespace mesytec::mnode; + +TEST(mnode_mana, Arena) +{ + mana::Arena arena; + + ASSERT_EQ(arena.allocations(), 0); + ASSERT_EQ(arena.pad_waste(), 0); + ASSERT_EQ(arena.segment_count(), 0); + ASSERT_EQ(arena.capacity(), 0); + ASSERT_EQ(arena.used(), 0); + + for (size_t i=0; i<10; ++i) + { + auto mem = arena.push_size(mana::Arena::default_pad + 2); + ASSERT_NE(mem, nullptr); + } + + ASSERT_EQ(arena.allocations(), 10); + ASSERT_GE(arena.pad_waste(), 0); + ASSERT_GE(arena.segment_count(), 1); + ASSERT_GE(arena.capacity(), 10 * (mana::Arena::default_pad + 2)); + ASSERT_EQ(arena.used(), 10 * (mana::Arena::default_pad + 2)); + + arena.reset(); + + ASSERT_EQ(arena.allocations(), 0); + ASSERT_EQ(arena.pad_waste(), 0); + ASSERT_GE(arena.segment_count(), 1); + ASSERT_GE(arena.capacity(), 10 * (mana::Arena::default_pad + 2)); + ASSERT_EQ(arena.used(), 0); +} diff --git a/src/tools/mana_auto_replay.cc b/src/tools/mana_auto_replay.cc index 5c06c3e..af8d516 100644 --- a/src/tools/mana_auto_replay.cc +++ b/src/tools/mana_auto_replay.cc @@ -34,92 +34,17 @@ #include // mnode::resources #include #include +#include #include #include #include "mana_replay_api.h" +#include "internal/mana_arena.h" CMRC_DECLARE(mnode::resources); using namespace mesytec; using namespace mesytec::mnode; -namespace arena -{ - -// source: https://blog.xoria.org/rounding-up/ -inline int64_t round_up(int64_t n, int64_t p) -{ - int64_t mask = p - 1; - return (n + mask) & ~mask; -} - -struct Arena -{ - Arena() = default; - ~Arena() = default; - Arena(const Arena &) = delete; - Arena &operator=(const Arena &) = delete; - Arena(Arena &&) = delete; - Arena &operator=(Arena &&) = delete; - struct Segment - { - std::vector data; - size_t used; - - explicit Segment(size_t size) - : data(size) - , used(0) - { - } - inline size_t free() const { return data.size() - used; } - inline u8 *cur() { return data.data() + used; } - }; - - std::list segments; - size_t current = 0; - - inline Segment *current_segment() - { - if (current < segments.size()) - { - auto it = segments.begin(); - std::advance(it, current); - return &(*it); - } - - return nullptr; - } - - inline u8 *push_size(size_t required) - { - required = round_up(required, 16); - - if (auto segment = current_segment(); !segment || segment->free() < required) - { - // could waste segments if required > default size of 1u << 20 but that's fine for now - segments.emplace_back( - Arena::Segment(std::max(required, static_cast(1u << 20)))); - current = segments.size() - 1; - } - - auto segment = current_segment(); - assert(segment); - assert(segment->free() >= required); - u8 *result = segment->cur(); - std::memset(result, 0, required); - segment->used += required; - return result; - } - - template T *push_t(size_t count = 1) - { - static_assert(std::is_trivial::value, "T must be a trivial type"); - return reinterpret_cast(push_size(sizeof(T) * count)); - } -}; - -}; // namespace arena - struct ListfileContext { std::unique_ptr zipReader; @@ -167,7 +92,7 @@ std::optional make_listfile_context(const std::string &filename } } - return std::move(ctx); + return ctx; } catch (const std::exception &e) { @@ -208,23 +133,46 @@ std::optional make_parser_context(const mvlc::CrateConfig &crateC struct AnalysisContext { + std::unique_ptr arena; + run_descriptor_t *runDescriptor = nullptr; }; +std::optional make_mana(const mvlc::CrateConfig &crateConfig, + const nlohmann::json &jModuleDataSources) +{ + try + { + std::map moduleInfoByType; + for (const auto &mod_: jModuleDataSources["modules"]) + moduleInfoByType[mod_["type_name"]] = mod_; + + AnalysisContext ctx; + ctx.arena = std::make_unique(); + auto arena = ctx.arena.get(); + + return ctx; + } + catch (const std::exception &e) + { + std::cerr << fmt::format("Error: {}\n", e.what()); + return {}; + } +} + void event_data_callback(AnalysisContext *ctx, int crateIndex, int eventIndex, const mvlc::readout_parser::ModuleData *moduleDataList, unsigned moduleCount) { assert(moduleDataList); - // std::cout << fmt::format("ReadoutEvent: ctx={}, crateId={}, eventIndex={}, moduleCount={}\n", - // fmt::ptr(ctx), crateIndex, eventIndex, moduleCount); + std::cout << fmt::format("ReadoutEvent: ctx={}, crateId={}, eventIndex={}, moduleCount={}\n", + fmt::ptr(ctx), crateIndex, eventIndex, moduleCount); } void system_event_callback(AnalysisContext *ctx, int crateIndex, const u32 *header, u32 size) { assert(header); - // std::cout << fmt::format("SystemEvent: ctx={}, crateId={}, size={}\n", fmt::ptr(ctx), - // crateIndex, - // size); + std::cout << fmt::format("SystemEvent: ctx={}, crateId={}, size={}\n", fmt::ptr(ctx), + crateIndex, size); } size_t process_one_buffer(size_t bufferNumber, ListfileContext &listfileContext, @@ -263,11 +211,13 @@ int main(int argc, char *argv[]) if (!listfileContext) return 1; - std::optional analysisContext = AnalysisContext(); + auto analysisContext = make_mana(listfileContext->crateConfig, jModuleDataSources); if (!analysisContext) return 1; + return 42; + auto event_data = [](void *ctx, int crateIndex, int eventIndex, const mvlc::readout_parser::ModuleData *moduleDataList, unsigned moduleCount)