From ec1092a4bee4c10e524f53e7ddd1a057a80f670e Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Fri, 19 Jun 2026 12:01:24 +0200 Subject: [PATCH 1/5] feat(cpp): add compile flag NDTBL_MMAP_POPULATE which forces mmap to map the entire file immediately --- CMakeLists.txt | 13 +++++++++++++ README.md | 13 ++++++++++++- include/ndtbl/detail/mapped_payload.hpp | 23 ++++++++++++++++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d542f12..5182e5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,11 +16,23 @@ option(ndtbl_BUILD_TESTING "Enable building of tests" OFF) # Option for enabling POSIX mmap support for payload reads option(ndtbl_ENABLE_MMAP "Enable POSIX mmap-backed ndtbl payload reads" OFF) +option(ndtbl_MMAP_POPULATE + "Enable Linux MAP_POPULATE for mmap-backed ndtbl payload reads" + OFF +) if(ndtbl_ENABLE_MMAP AND NOT UNIX) message(FATAL_ERROR "ndtbl_ENABLE_MMAP requires a POSIX platform") endif() +if(ndtbl_MMAP_POPULATE AND NOT ndtbl_ENABLE_MMAP) + message(FATAL_ERROR "ndtbl_MMAP_POPULATE requires ndtbl_ENABLE_MMAP") +endif() + +if(ndtbl_MMAP_POPULATE AND NOT LINUX) + message(FATAL_ERROR "ndtbl_MMAP_POPULATE requires Linux") +endif() + # Add an interface target for the header-only library add_library(ndtbl INTERFACE) @@ -48,6 +60,7 @@ target_sources(ndtbl target_compile_definitions(ndtbl INTERFACE NDTBL_ENABLE_MMAP=$ + NDTBL_MMAP_POPULATE=$ ) # Request a minimum C++ standard for this target diff --git a/README.md b/README.md index d1bd218..35235da 100644 --- a/README.md +++ b/README.md @@ -66,12 +66,16 @@ Relevant CMake options: - `ndtbl_BUILD_BENCHMARKS`: build developer lookup benchmarks, default `OFF` - `ndtbl_BUILD_DOCS`: build the documentation, default `ON` for top-level builds - `ndtbl_ENABLE_MMAP`: enable POSIX-only `mmap`-backed payload reads, default `OFF` +- `ndtbl_MMAP_POPULATE`: add Linux-only `MAP_POPULATE` to `mmap`-backed + payload reads, default `OFF`; requires `ndtbl_ENABLE_MMAP=ON` When `ndtbl_ENABLE_MMAP=OFF` (the default), `read_field_group()` and `read_runtime_field_group()` read payload data into owned heap storage. When `ndtbl_ENABLE_MMAP=ON`, supported POSIX builds use read-only memory mapping instead, which can reduce heap usage for large tables and enables shared memory -access in multi-process environments. +access in multi-process environments. On Linux, `ndtbl_MMAP_POPULATE=ON` +adds `MAP_POPULATE` to the mapping flags so the kernel faults the mapped payload +in during `mmap()` instead of on first access. If you want to install the C++ headers and CMake package metadata: @@ -86,6 +90,13 @@ cmake -B build -Dndtbl_ENABLE_MMAP=ON cmake --build build ``` +To additionally pre-populate mapped payload pages on Linux: + +```bash +cmake -B build -Dndtbl_ENABLE_MMAP=ON -Dndtbl_MMAP_POPULATE=ON +cmake --build build +``` + ## ⚙️ C++ Tool Workflow Inspect existing `.ndtbl` files: diff --git a/include/ndtbl/detail/mapped_payload.hpp b/include/ndtbl/detail/mapped_payload.hpp index af260a3..794fe9b 100644 --- a/include/ndtbl/detail/mapped_payload.hpp +++ b/include/ndtbl/detail/mapped_payload.hpp @@ -4,6 +4,10 @@ #define NDTBL_ENABLE_MMAP 0 #endif +#ifndef NDTBL_MMAP_POPULATE +#define NDTBL_MMAP_POPULATE 0 +#endif + #include #include #include @@ -20,6 +24,18 @@ #include #endif +#if NDTBL_MMAP_POPULATE +#if !NDTBL_ENABLE_MMAP +#error "NDTBL_MMAP_POPULATE requires NDTBL_ENABLE_MMAP" +#endif +#if !defined(__linux__) +#error "NDTBL_MMAP_POPULATE requires Linux" +#endif +#if !defined(MAP_POPULATE) +#error "NDTBL_MMAP_POPULATE requires MAP_POPULATE" +#endif +#endif + namespace ndtbl { namespace detail { @@ -99,8 +115,13 @@ map_payload_bytes(const std::string& path, const std::size_t delta = payload_offset - aligned_offset; const std::size_t mapping_length = delta + payload_size; + int mapping_flags = MAP_PRIVATE; +#if NDTBL_MMAP_POPULATE + mapping_flags |= MAP_POPULATE; +#endif + void* const mapping = - mmap(nullptr, mapping_length, PROT_READ, MAP_PRIVATE, fd, aligned_offset); + mmap(nullptr, mapping_length, PROT_READ, mapping_flags, fd, aligned_offset); const int saved_errno = errno; close(fd); if (mapping == MAP_FAILED) { From 5b447617b089616cbc2b215f9c6b08d21a81b40f Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Fri, 19 Jun 2026 13:52:21 +0200 Subject: [PATCH 2/5] chore: bump Catch2 to v3.15.1 --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 506ee1d..a074bfa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,7 +8,7 @@ if(NOT TARGET Catch2::Catch2WithMain) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.14.0) + GIT_TAG v3.15.1) FetchContent_MakeAvailable(Catch2) endif() From 809a5084b902e9aba3ee76c2af1466fd18255bc1 Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:01:41 +0200 Subject: [PATCH 3/5] feat(cpp): ndtbl_ENABLE_MMAP_DIAGNOSTICS allows reporting OS page residency via calls to mincore --- CMakeLists.txt | 26 ++++-- README.md | 28 +++++- include/ndtbl/detail/mapped_payload.hpp | 116 +++++++++++++++++++++--- include/ndtbl/diagnostics.hpp | 22 +++++ include/ndtbl/field_group.hpp | 24 ++++- include/ndtbl/io.hpp | 9 +- include/ndtbl/ndtbl.hpp | 1 + include/ndtbl/runtime_field_group.hpp | 19 ++++ tests/ndtbl_io_t.cpp | 47 ++++++++++ 9 files changed, 265 insertions(+), 27 deletions(-) create mode 100644 include/ndtbl/diagnostics.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5182e5f..37cfc53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,21 +16,33 @@ option(ndtbl_BUILD_TESTING "Enable building of tests" OFF) # Option for enabling POSIX mmap support for payload reads option(ndtbl_ENABLE_MMAP "Enable POSIX mmap-backed ndtbl payload reads" OFF) -option(ndtbl_MMAP_POPULATE +option(ndtbl_ENABLE_MMAP_POPULATE "Enable Linux MAP_POPULATE for mmap-backed ndtbl payload reads" OFF ) +option(ndtbl_ENABLE_MMAP_DIAGNOSTICS + "Enable mmap-backed payload diagnostics" + OFF +) if(ndtbl_ENABLE_MMAP AND NOT UNIX) message(FATAL_ERROR "ndtbl_ENABLE_MMAP requires a POSIX platform") endif() -if(ndtbl_MMAP_POPULATE AND NOT ndtbl_ENABLE_MMAP) - message(FATAL_ERROR "ndtbl_MMAP_POPULATE requires ndtbl_ENABLE_MMAP") +if(ndtbl_ENABLE_MMAP_POPULATE AND NOT ndtbl_ENABLE_MMAP) + message(FATAL_ERROR "ndtbl_ENABLE_MMAP_POPULATE requires ndtbl_ENABLE_MMAP") +endif() + +if(ndtbl_ENABLE_MMAP_POPULATE AND NOT LINUX) + message(FATAL_ERROR "ndtbl_ENABLE_MMAP_POPULATE requires Linux") +endif() + +if(ndtbl_ENABLE_MMAP_DIAGNOSTICS AND NOT ndtbl_ENABLE_MMAP) + message(FATAL_ERROR "ndtbl_ENABLE_MMAP_DIAGNOSTICS requires ndtbl_ENABLE_MMAP") endif() -if(ndtbl_MMAP_POPULATE AND NOT LINUX) - message(FATAL_ERROR "ndtbl_MMAP_POPULATE requires Linux") +if(ndtbl_ENABLE_MMAP_DIAGNOSTICS AND NOT LINUX) + message(FATAL_ERROR "ndtbl_ENABLE_MMAP_DIAGNOSTICS requires a LINUX platform") endif() # Add an interface target for the header-only library @@ -47,6 +59,7 @@ target_sources(ndtbl include/ndtbl/grid.hpp include/ndtbl/field_group.hpp include/ndtbl/runtime_field_group.hpp + include/ndtbl/diagnostics.hpp include/ndtbl/metadata.hpp include/ndtbl/payload.hpp include/ndtbl/io.hpp @@ -60,7 +73,8 @@ target_sources(ndtbl target_compile_definitions(ndtbl INTERFACE NDTBL_ENABLE_MMAP=$ - NDTBL_MMAP_POPULATE=$ + NDTBL_ENABLE_MMAP_POPULATE=$ + NDTBL_ENABLE_MMAP_DIAGNOSTICS=$ ) # Request a minimum C++ standard for this target diff --git a/README.md b/README.md index 35235da..82d193d 100644 --- a/README.md +++ b/README.md @@ -66,17 +66,32 @@ Relevant CMake options: - `ndtbl_BUILD_BENCHMARKS`: build developer lookup benchmarks, default `OFF` - `ndtbl_BUILD_DOCS`: build the documentation, default `ON` for top-level builds - `ndtbl_ENABLE_MMAP`: enable POSIX-only `mmap`-backed payload reads, default `OFF` -- `ndtbl_MMAP_POPULATE`: add Linux-only `MAP_POPULATE` to `mmap`-backed +- `ndtbl_ENABLE_MMAP_POPULATE`: add Linux-only `MAP_POPULATE` to `mmap`-backed payload reads, default `OFF`; requires `ndtbl_ENABLE_MMAP=ON` +- `ndtbl_ENABLE_MMAP_DIAGNOSTICS`: enable mmap payload residency diagnostics + through `mincore`, default `OFF`; requires `ndtbl_ENABLE_MMAP=ON` When `ndtbl_ENABLE_MMAP=OFF` (the default), `read_field_group()` and `read_runtime_field_group()` read payload data into owned heap storage. When `ndtbl_ENABLE_MMAP=ON`, supported POSIX builds use read-only memory mapping instead, which can reduce heap usage for large tables and enables shared memory -access in multi-process environments. On Linux, `ndtbl_MMAP_POPULATE=ON` +access in multi-process environments. On Linux, `ndtbl_ENABLE_MMAP_POPULATE=ON` adds `MAP_POPULATE` to the mapping flags so the kernel faults the mapped payload in during `mmap()` instead of on first access. +When `ndtbl_ENABLE_MMAP_DIAGNOSTICS=ON`, mmap-loaded field groups can report OS +page residency for their payload: + +```cpp +const auto info = group.payload_residency(); +if (info.available) { + // inspect info.resident_pages, info.total_pages, info.resident_fraction +} +``` + +The diagnostic is page-granular: it reports what `mincore` exposes for the +mapped payload pages, not exact application RSS or exact bytes touched. + If you want to install the C++ headers and CMake package metadata: ```bash @@ -93,7 +108,14 @@ cmake --build build To additionally pre-populate mapped payload pages on Linux: ```bash -cmake -B build -Dndtbl_ENABLE_MMAP=ON -Dndtbl_MMAP_POPULATE=ON +cmake -B build -Dndtbl_ENABLE_MMAP=ON -Dndtbl_ENABLE_MMAP_POPULATE=ON +cmake --build build +``` + +To enable mmap payload residency diagnostics on Linux: + +```bash +cmake -B build -Dndtbl_ENABLE_MMAP=ON -Dndtbl_ENABLE_MMAP_DIAGNOSTICS=ON cmake --build build ``` diff --git a/include/ndtbl/detail/mapped_payload.hpp b/include/ndtbl/detail/mapped_payload.hpp index 794fe9b..82d5efb 100644 --- a/include/ndtbl/detail/mapped_payload.hpp +++ b/include/ndtbl/detail/mapped_payload.hpp @@ -4,17 +4,25 @@ #define NDTBL_ENABLE_MMAP 0 #endif -#ifndef NDTBL_MMAP_POPULATE -#define NDTBL_MMAP_POPULATE 0 +#ifndef NDTBL_ENABLE_MMAP_POPULATE +#define NDTBL_ENABLE_MMAP_POPULATE 0 #endif +#ifndef NDTBL_ENABLE_MMAP_DIAGNOSTICS +#define NDTBL_ENABLE_MMAP_DIAGNOSTICS 0 +#endif + +#include "ndtbl/diagnostics.hpp" + #include #include +#include #include #include #include +#include -#if NDTBL_ENABLE_MMAP +#if NDTBL_ENABLE_MMAP || NDTBL_ENABLE_MMAP_DIAGNOSTICS #include #include @@ -24,21 +32,107 @@ #include #endif -#if NDTBL_MMAP_POPULATE +#if NDTBL_ENABLE_MMAP_DIAGNOSTICS #if !NDTBL_ENABLE_MMAP -#error "NDTBL_MMAP_POPULATE requires NDTBL_ENABLE_MMAP" +#error "NDTBL_ENABLE_MMAP_DIAGNOSTICS requires NDTBL_ENABLE_MMAP" +#endif +#endif + +#if NDTBL_ENABLE_MMAP_POPULATE +#if !NDTBL_ENABLE_MMAP +#error "NDTBL_ENABLE_MMAP_POPULATE requires NDTBL_ENABLE_MMAP" #endif #if !defined(__linux__) -#error "NDTBL_MMAP_POPULATE requires Linux" +#error "NDTBL_ENABLE_MMAP_POPULATE requires Linux" #endif #if !defined(MAP_POPULATE) -#error "NDTBL_MMAP_POPULATE requires MAP_POPULATE" +#error "NDTBL_ENABLE_MMAP_POPULATE requires MAP_POPULATE" #endif #endif namespace ndtbl { namespace detail { +inline residency_info +unavailable_residency() +{ + return residency_info{ false, 0, 0, 0, 0, 0.0 }; +} + +#if NDTBL_ENABLE_MMAP || NDTBL_ENABLE_MMAP_DIAGNOSTICS + +inline std::string +system_error_message(const std::string& prefix) +{ + return prefix + ": " + std::strerror(errno); +} + +#endif + +#if NDTBL_ENABLE_MMAP_DIAGNOSTICS + +inline residency_info +query_residency(const void* address, std::size_t length) +{ + if (length == 0) { + return residency_info{ true, 0, 0, 0, 0, 0.0 }; + } + if (address == nullptr) { + throw std::invalid_argument("cannot query residency for a null payload"); + } + + const long page_size_long = sysconf(_SC_PAGESIZE); + if (page_size_long <= 0) { + throw std::runtime_error("failed to query system page size for mincore"); + } + + const std::size_t page_size = static_cast(page_size_long); + const std::uintptr_t addr = reinterpret_cast(address); + const std::uintptr_t aligned_addr = addr - (addr % page_size); + const std::size_t delta = static_cast(addr - aligned_addr); + if (length > std::numeric_limits::max() - delta) { + throw std::overflow_error("payload residency range is too large"); + } + const std::size_t aligned_length = delta + length; + const std::size_t total_pages = + aligned_length / page_size + (aligned_length % page_size == 0 ? 0 : 1); + if (total_pages > std::numeric_limits::max() / page_size) { + throw std::overflow_error("payload residency page range is too large"); + } + + std::vector vec(total_pages); + if (mincore(reinterpret_cast(aligned_addr), + total_pages * page_size, + vec.data()) != 0) { + throw std::runtime_error(system_error_message("mincore failed")); + } + + std::size_t resident_pages = 0; + for (std::size_t index = 0; index < vec.size(); ++index) { + if ((vec[index] & 1U) != 0U) { + ++resident_pages; + } + } + + return residency_info{ true, + page_size, + total_pages, + resident_pages, + resident_pages * page_size, + static_cast(resident_pages) / + static_cast(total_pages) }; +} + +#else + +inline residency_info +query_residency(const void*, std::size_t) +{ + return unavailable_residency(); +} + +#endif + #if NDTBL_ENABLE_MMAP class mapped_payload_owner @@ -65,12 +159,6 @@ class mapped_payload_owner std::size_t mapping_length_; }; -inline std::string -system_error_message(const std::string& prefix) -{ - return prefix + ": " + std::strerror(errno); -} - inline std::shared_ptr map_payload_bytes(const std::string& path, std::size_t payload_offset, @@ -116,7 +204,7 @@ map_payload_bytes(const std::string& path, const std::size_t mapping_length = delta + payload_size; int mapping_flags = MAP_PRIVATE; -#if NDTBL_MMAP_POPULATE +#if NDTBL_ENABLE_MMAP_POPULATE mapping_flags |= MAP_POPULATE; #endif diff --git a/include/ndtbl/diagnostics.hpp b/include/ndtbl/diagnostics.hpp new file mode 100644 index 0000000..7f5be09 --- /dev/null +++ b/include/ndtbl/diagnostics.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace ndtbl { + +/** + * @brief Page residency information for an ndtbl payload. + * + * The byte count is page-granular because OS residency is reported per page. + */ +struct residency_info +{ + bool available; + std::size_t page_size; + std::size_t total_pages; + std::size_t resident_pages; + std::size_t resident_bytes; + double resident_fraction; +}; + +} // namespace ndtbl diff --git a/include/ndtbl/field_group.hpp b/include/ndtbl/field_group.hpp index 76ea6de..1883144 100644 --- a/include/ndtbl/field_group.hpp +++ b/include/ndtbl/field_group.hpp @@ -1,5 +1,6 @@ #pragma once +#include "ndtbl/detail/mapped_payload.hpp" #include "ndtbl/detail/size_math.hpp" #include "ndtbl/grid.hpp" #include "ndtbl/payload.hpp" @@ -77,11 +78,13 @@ class FieldGroup FieldGroup(const Grid& grid, const std::vector& field_names, const PayloadView& interleaved_values, - std::shared_ptr payload_owner) + std::shared_ptr payload_owner, + bool payload_is_mmap = false) : grid_(grid) , field_names_(field_names) , interleaved_values_(interleaved_values) , payload_owner_(std::move(payload_owner)) + , payload_is_mmap_(payload_is_mmap) { if (interleaved_values_.size() != 0 && !payload_owner_) { throw std::invalid_argument("field group payload owner is empty"); @@ -124,6 +127,24 @@ class FieldGroup */ PayloadView interleaved_values() const { return interleaved_values_; } + /** + * @brief Return OS page residency information for mmap-backed payloads. + * + * The returned value has `available=false` unless this group was loaded + * through ndtbl's mmap path and mmap diagnostics were enabled at compile + * time. + * + * @return Page residency diagnostic information. + */ + residency_info payload_residency() const + { + if (!payload_is_mmap_) { + return detail::unavailable_residency(); + } + return detail::query_residency(interleaved_values_.byte_data(), + interleaved_values_.byte_size()); + } + /** * @brief Resolve a field name to its local field index. * @@ -432,6 +453,7 @@ class FieldGroup std::vector field_names_; PayloadView interleaved_values_; std::shared_ptr payload_owner_; + bool payload_is_mmap_ = false; }; } // namespace ndtbl diff --git a/include/ndtbl/io.hpp b/include/ndtbl/io.hpp index 3cd2a88..67d7620 100644 --- a/include/ndtbl/io.hpp +++ b/include/ndtbl/io.hpp @@ -185,7 +185,8 @@ read_field_group(const std::string& path) grid, metadata.field_names, PayloadView(payload_owner.get(), layout.value_count), - payload_owner); + payload_owner, + true); #else // Keep this non-const so the payload buffer can be moved into the read-only // FieldGroup storage instead of copied during load. @@ -237,7 +238,8 @@ read_runtime_field_group(const std::string& path) grid, metadata.field_names, PayloadView(payload_owner.get(), layout.value_count), - payload_owner)); + payload_owner, + true)); #else // Keep this non-const so the payload buffer can be moved into the // read-only FieldGroup storage instead of copied during load. @@ -257,7 +259,8 @@ read_runtime_field_group(const std::string& path) grid, metadata.field_names, PayloadView(payload_owner.get(), layout.value_count), - payload_owner)); + payload_owner, + true)); #else // Keep this non-const so the payload buffer can be moved into the // read-only FieldGroup storage instead of copied during load. diff --git a/include/ndtbl/ndtbl.hpp b/include/ndtbl/ndtbl.hpp index 94429f4..e2ef7c7 100644 --- a/include/ndtbl/ndtbl.hpp +++ b/include/ndtbl/ndtbl.hpp @@ -1,6 +1,7 @@ #pragma once #include "ndtbl/axis.hpp" +#include "ndtbl/diagnostics.hpp" #include "ndtbl/field_group.hpp" #include "ndtbl/grid.hpp" #include "ndtbl/io.hpp" diff --git a/include/ndtbl/runtime_field_group.hpp b/include/ndtbl/runtime_field_group.hpp index 02e167a..1301bab 100644 --- a/include/ndtbl/runtime_field_group.hpp +++ b/include/ndtbl/runtime_field_group.hpp @@ -204,6 +204,19 @@ class RuntimeFieldGroup impl_->evaluate_all_cubic_into(coordinates, values, policy); } + /** + * @brief Return OS page residency information for mmap-backed payloads. + * + * @return Page residency diagnostic information. + */ + residency_info payload_residency() const + { + if (!impl_) { + throw std::runtime_error("ndtbl field group is empty"); + } + return impl_->payload_residency(); + } + /** * @brief Write the wrapped group to an already opened binary stream. * @@ -235,6 +248,7 @@ class RuntimeFieldGroup const std::array& coordinates, Output* values, bounds_policy policy) const = 0; + virtual residency_info payload_residency() const = 0; virtual void write(std::ostream& os) const = 0; }; @@ -278,6 +292,11 @@ class RuntimeFieldGroup coordinates, values, policy); } + residency_info payload_residency() const override + { + return group_.payload_residency(); + } + void write(std::ostream& os) const override { write_group_stream(os, group_); diff --git a/tests/ndtbl_io_t.cpp b/tests/ndtbl_io_t.cpp index b623346..3bd4af3 100644 --- a/tests/ndtbl_io_t.cpp +++ b/tests/ndtbl_io_t.cpp @@ -88,6 +88,53 @@ TEST_CASE("typed field group loader reads known float payloads", "[io]") std::remove(path.c_str()); } +TEST_CASE("payload residency reports availability for supported storage", + "[io]") +{ + const std::array axes = { + ndtbl::Axis::uniform(0.0, 1.0, 2), + }; + const ndtbl::FieldGroup<1, double> group( + ndtbl::Grid<1>(axes), { "A" }, { 0.0, 1.0 }); + const ndtbl::RuntimeFieldGroup<1> runtime(group); + + REQUIRE_FALSE(group.payload_residency().available); + REQUIRE_FALSE(runtime.payload_residency().available); + + const std::string path = ndtbl_test::temporary_path(); + ndtbl::write_group(path, group); + + const ndtbl::FieldGroup<1, double> loaded = + ndtbl::read_field_group<1, double>(path); + const ndtbl::RuntimeFieldGroup<1> loaded_runtime = + ndtbl::read_runtime_field_group<1>(path); + + const ndtbl::residency_info info = loaded.payload_residency(); + const ndtbl::residency_info runtime_info = loaded_runtime.payload_residency(); + +#if NDTBL_ENABLE_MMAP && NDTBL_ENABLE_MMAP_DIAGNOSTICS + REQUIRE(info.available); + REQUIRE(info.page_size > 0); + REQUIRE(info.total_pages > 0); + REQUIRE(info.resident_pages <= info.total_pages); + REQUIRE(info.resident_bytes == info.resident_pages * info.page_size); + REQUIRE(info.resident_fraction >= 0.0); + REQUIRE(info.resident_fraction <= 1.0); + + REQUIRE(runtime_info.available); + REQUIRE(runtime_info.page_size > 0); + REQUIRE(runtime_info.total_pages > 0); + REQUIRE(runtime_info.resident_pages <= runtime_info.total_pages); + REQUIRE(runtime_info.resident_fraction >= 0.0); + REQUIRE(runtime_info.resident_fraction <= 1.0); +#else + REQUIRE_FALSE(info.available); + REQUIRE_FALSE(runtime_info.available); +#endif + + std::remove(path.c_str()); +} + TEST_CASE("typed field group loader rejects wrong scalar type", "[io]") { const std::array axes = { From 2c591c4b7572bef59b638b2cc5aea7d3a803a9bc Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:12:22 +0200 Subject: [PATCH 4/5] fix(cpp): use atomic gcov counters in coverage CI --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7403914..26b176d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,7 +100,13 @@ jobs: - name: Configure cmake shell: bash - run: cmake -B build -DCMAKE_CXX_FLAGS="--coverage" -Dndtbl_BUILD_TESTING=ON -Dndtbl_BUILD_DOCS=OFF + run: | + cmake -B build \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_CXX_FLAGS="--coverage -fprofile-update=atomic -O0 -g" \ + -DCMAKE_EXE_LINKER_FLAGS="--coverage" \ + -Dndtbl_BUILD_TESTING=ON \ + -Dndtbl_BUILD_DOCS=OFF - name: Build shell: bash From 090b5043689189140033e72acd96f8c3fbd9e78d Mon Sep 17 00:00:00 2001 From: Thomas Isensee <26852629+thomasisensee@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:27:53 +0200 Subject: [PATCH 5/5] chore: bump actions versions --- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/release-pypi.yml | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26b176d..f664e0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Filter changes uses: dorny/paths-filter@v4 @@ -63,7 +63,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Doxygen uses: ssciwr/doxygen-install@v2 @@ -89,7 +89,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install LCov run: | @@ -124,7 +124,7 @@ jobs: lcov --directory . --capture --output-file coverage.info --ignore-errors mismatch,unused --exclude '*/catch2/*' - name: Upload C++ coverage to Codecov - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 with: disable_search: true fail_ci_if_error: true @@ -147,7 +147,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 @@ -172,7 +172,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up Python uses: actions/setup-python@v6 @@ -195,7 +195,7 @@ jobs: grep -o 'filename="[^"]*"' coverage.xml | head -n 1000 - name: Upload Python coverage to Codecov - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 with: disable_search: true fail_ci_if_error: true diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index f346d54..3a745d9 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up Python uses: actions/setup-python@v6 @@ -81,7 +81,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Download built distributions uses: actions/download-artifact@v8 @@ -117,7 +117,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Download built distributions uses: actions/download-artifact@v8