From 9a77c89cfcfc08e025e04381969a17c37874ec9b Mon Sep 17 00:00:00 2001 From: gbornste Date: Wed, 19 Aug 2026 13:45:09 -0400 Subject: [PATCH 1/3] Delete unused code --- src/turtle_kv/kv_store.cpp | 17 ++- src/turtle_kv/on_page_cache_overcommit.cpp | 3 +- src/turtle_kv/tree/config.cpp | 17 --- src/turtle_kv/tree/config.hpp | 25 ----- src/turtle_kv/tree/memory_storage.cpp | 5 +- src/turtle_kv/tree/tree_options.cpp | 16 --- src/turtle_kv/util/memory_stats.hpp | 122 --------------------- 7 files changed, 12 insertions(+), 193 deletions(-) delete mode 100644 src/turtle_kv/tree/config.cpp delete mode 100644 src/turtle_kv/tree/config.hpp delete mode 100644 src/turtle_kv/util/memory_stats.hpp diff --git a/src/turtle_kv/kv_store.cpp b/src/turtle_kv/kv_store.cpp index 6e07922..ebc0263 100644 --- a/src/turtle_kv/kv_store.cpp +++ b/src/turtle_kv/kv_store.cpp @@ -28,8 +28,6 @@ #include #include -#include - #include #include @@ -423,7 +421,7 @@ u64 query_page_loader_reset_every_n() this->tree_options_, this->page_cache(), batt::make_copy(this->filter_page_write_state_), - batt::Toggle::Reader{this->state_}->base_checkpoint_->clone(), + batt::Toggle::Reader { this->state_ } -> base_checkpoint_->clone(), *this->checkpoint_volume_); this->tree_options_.set_trie_index_reserve_size(this->tree_options_.trie_index_reserve_size()); @@ -728,7 +726,6 @@ StatusOr KVStore::put(const KeyView& key, return Status{batt::StatusCode::kUnavailable}; } - //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // Status KVStore::put(const KeyView& key, const ValueView& value) noexcept /*override*/ @@ -1351,10 +1348,10 @@ using CheckpointEvent = llfs::PackedVariant; //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // -Status KVStore::sync(Optional upper_bound, Optional write_options) noexcept +Status KVStore::sync(Optional upper_bound, + Optional write_options) noexcept { - EditOffset target = - upper_bound ? *upper_bound : this->change_log_writer_->next_edit_offset(); + EditOffset target = upper_bound ? *upper_bound : this->change_log_writer_->next_edit_offset(); bool urgent = write_options && write_options->urgent_sync ? true : false; @@ -1400,9 +1397,9 @@ void KVStore::mem_table_batch_scanner_thread_main() //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // template - requires std::invocable> -Status KVStore::scan_mem_table_to_build_batches(boost::intrusive_ptr&& mem_table, - Fn&& consume_fn) +requires std::invocable> Status +KVStore::scan_mem_table_to_build_batches(boost::intrusive_ptr&& mem_table, + Fn&& consume_fn) { MemTable::BatchCompactor batch_compactor{*mem_table, /*byte_size_limit=*/this->tree_options_.flush_size()}; diff --git a/src/turtle_kv/on_page_cache_overcommit.cpp b/src/turtle_kv/on_page_cache_overcommit.cpp index f3d2856..0946647 100644 --- a/src/turtle_kv/on_page_cache_overcommit.cpp +++ b/src/turtle_kv/on_page_cache_overcommit.cpp @@ -1,6 +1,5 @@ #include // -#include #include @@ -35,7 +34,7 @@ void on_page_cache_overcommit(const std::function& cont << batt::dump_size(cache_slots.estimate_cache_bytes()) << ")" << std::endl << BATT_INSPECT(cache_slots.estimate_pinned_bytes()) << "(" << batt::dump_size(cache_slots.estimate_pinned_bytes()) << ")" << std::endl - << print_page_alloc_info << dump_memory_stats(); + << print_page_alloc_info; } } // namespace turtle_kv diff --git a/src/turtle_kv/tree/config.cpp b/src/turtle_kv/tree/config.cpp deleted file mode 100644 index d9b407a..0000000 --- a/src/turtle_kv/tree/config.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include -// - -#include - -namespace turtle_kv { - -//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - -// -unsigned tree_prefetch_level() -{ - static const unsigned level_ = - batt::getenv_as("TURTLE_TREE_PREFETCH").value_or(kDefaultPrefetchLevel); - return level_; -} - -} // namespace turtle_kv diff --git a/src/turtle_kv/tree/config.hpp b/src/turtle_kv/tree/config.hpp deleted file mode 100644 index 8fa4a9a..0000000 --- a/src/turtle_kv/tree/config.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -namespace turtle_kv { - -// The utilization ratio between the smallest allowed (non-root) node and the largest. -// -constexpr unsigned kMaxNodeUtilizationRatio = 3; - -// The utilization ratio between the smallest allowed leaf and the largest. -// -constexpr unsigned kMaxLeafUtilizationRatio = 3; - -// Controls whether pages are prefetched during batch updates to checkpoints. -// -// 0 == no prefetching -// 1 == (default) prefetch only pages we absolutely *know* we will need immediately -// 2 == prefetch aggressively/speculatively -// -// Default can be overridden at runtime with environment variable TURTLE_TREE_PREFETCH= -// -unsigned tree_prefetch_level(); - -constexpr unsigned kDefaultPrefetchLevel = 1; - -} // namespace turtle_kv diff --git a/src/turtle_kv/tree/memory_storage.cpp b/src/turtle_kv/tree/memory_storage.cpp index 02b530c..cc752ef 100644 --- a/src/turtle_kv/tree/memory_storage.cpp +++ b/src/turtle_kv/tree/memory_storage.cpp @@ -1,7 +1,6 @@ #include // -#include #include #include @@ -18,6 +17,10 @@ std::shared_ptr make_memory_page_cache(batt::TaskScheduler& sch const TreeOptions& opts, usize byte_capacity) { + // The utilization ratio between the smallest allowed (non-root) node and the largest. + // + constexpr unsigned kMaxNodeUtilizationRatio = 3; + const auto n_leaf_pages = llfs::PageCount{(byte_capacity + opts.leaf_size() - 1) / opts.leaf_size()}; diff --git a/src/turtle_kv/tree/tree_options.cpp b/src/turtle_kv/tree/tree_options.cpp index 99bf2d8..50f206c 100644 --- a/src/turtle_kv/tree/tree_options.cpp +++ b/src/turtle_kv/tree/tree_options.cpp @@ -24,22 +24,6 @@ namespace turtle_kv { return instance_; } -//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - -// -/*static*/ TreeOptions::GlobalOptions& TreeOptions::global_options() -{ - static constexpr const char* const kVarName = "TURTLE_KV_PAGE_CACHE_OBSOLETE_HINTS"; - - static GlobalOptions* const p_global_options_ = []() { - static GlobalOptions global_options_; - global_options_.page_cache_obsolete_hints = batt::getenv_as(kVarName).value_or(false); - LOG(INFO) << kVarName << "=" << global_options_.page_cache_obsolete_hints; - return &global_options_; - }(); - - return *p_global_options_; -} - //==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - // std::ostream& operator<<(std::ostream& out, const TreeOptions& t) diff --git a/src/turtle_kv/util/memory_stats.hpp b/src/turtle_kv/util/memory_stats.hpp deleted file mode 100644 index fc8a4de..0000000 --- a/src/turtle_kv/util/memory_stats.hpp +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once -#define TURTLE_KV_UTIL_MEMORY_STATS_HPP - -#include -// -#include -#include -#include - -#if TURTLE_KV_ENABLE_TCMALLOC -#include -#endif // TURTLE_KV_ENABLE_TCMALLOC - -#include - -#include - -namespace turtle_kv { - -inline auto print_size(usize n) -{ - return [n](std::ostream& out) { - out << n << "(" << batt::dump_size(n) << ")"; - }; -} - -inline auto dump_memory_stats() -{ - return [](std::ostream& out) { -#if TURTLE_KV_ENABLE_TCMALLOC && TURTLE_KV_ENABLE_TCMALLOC_HEAP_PROFILING - int n_blocks = 0; - usize total = 0; - int hist[kMallocHistogramSize]; - - auto* malloc_ext = MallocExtension::instance(); - { - const bool ok = malloc_ext->MallocMemoryStats(&n_blocks, &total, hist); - if (!ok) { - out << "(MallocMemoryStats failed)"; - } else { - if (n_blocks == 0 && total == 0 && - std::all_of(hist, hist + kMallocHistogramSize, [](int n) { - return n == 0; - })) { - out << "\n memory_stats == {0}"; - } else { - out << " memory_stats.n_blocks == " << n_blocks // - << "\n memory_stats.total == " << total // - << "\n memory_stats.histogram[ 0..1 ] == " << hist[0] // - << "\n memory_stats.histogram[ 2..4 ] == " << hist[1] // - << "\n memory_stats.histogram[ 4..8 ] == " << hist[2] // - << "\n memory_stats.histogram[ 8..16 ] == " << hist[3] // - << "\n memory_stats.histogram[ 16..32 ] == " << hist[4] // - << "\n memory_stats.histogram[ 32..64 ] == " << hist[5] // - << "\n memory_stats.histogram[ 64..128 ] == " << hist[6] // - << "\n memory_stats.histogram[ 128..256 ] == " << hist[7] // - << "\n memory_stats.histogram[ 256..512 ] == " << hist[8] // - << "\n memory_stats.histogram[ 512..1K ] == " << hist[9] // - << "\n memory_stats.histogram[ 1K..2K ] == " << hist[10] // - << "\n memory_stats.histogram[ 2K..4K ] == " << hist[11] // - << "\n memory_stats.histogram[ 4K..8K ] == " << hist[12] // - << "\n memory_stats.histogram[ 8K..16K ] == " << hist[13] // - << "\n memory_stats.histogram[ 16K..32K ] == " << hist[14] // - << "\n memory_stats.histogram[ 32K..64K ] == " << hist[15] // - << "\n memory_stats.histogram[ 64K..128K] == " << hist[16] // - << "\n memory_stats.histogram[128K..256K] == " << hist[17] // - << "\n memory_stats.histogram[256K..512K] == " << hist[18] // - << "\n memory_stats.histogram[512K..1M ] == " << hist[19] // - << "\n memory_stats.histogram[ 1M..2M ] == " << hist[20] // - << "\n memory_stats.histogram[ 2M..4M ] == " << hist[21] // - << "\n memory_stats.histogram[ 4M..8M ] == " << hist[22] // - << "\n memory_stats.histogram[ 8M..16M ] == " << hist[23] // - << "\n memory_stats.histogram[ 16M..32M ] == " << hist[24] // - << "\n memory_stats.histogram[ 32M..64M ] == " << hist[25] // - << "\n memory_stats.histogram[ 64M..128M] == " << hist[26] // - << "\n memory_stats.histogram[128M..256M] == " << hist[27] // - << "\n memory_stats.histogram[256M..512M] == " << hist[28] // - << "\n memory_stats.histogram[512M..1G ] == " << hist[29] // - << "\n memory_stats.histogram[ 1G..2G ] == " << hist[30] // - << "\n memory_stats.histogram[ 2G..4G ] == " << hist[31] // - << "\n memory_stats.histogram[ 4G..8G ] == " << hist[32] // - << "\n memory_stats.histogram[ 8G..16G ] == " << hist[33] // - << "\n memory_stats.histogram[ 16G..32G ] == " << hist[34] // - << "\n memory_stats.histogram[ 32G..64G ] == " << hist[35] // - << "\n memory_stats.histogram[ 64G..128G] == " << hist[36] // - << "\n memory_stats.histogram[128G..256G] == " << hist[37] // - << "\n memory_stats.histogram[256G..512G] == " << hist[38] // - << "\n memory_stats.histogram[512G..1T ] == " << hist[39] // - ; - } - } - } - - const auto get_malloc_stat = [&](const char* name) -> Optional { - usize value = 0; - if (malloc_ext->GetNumericProperty(name, &value)) { - return value; - } - return None; - }; - - out << "\n" << BATT_INSPECT(get_malloc_stat("generic.current_allocated_bytes")); - out << "\n" << BATT_INSPECT(get_malloc_stat("generic.heap_size")); - out << "\n" << BATT_INSPECT(get_malloc_stat("generic.total_physical_bytes")); - -#endif // TURTLE_KV_ENABLE_TCMALLOC && TURTLE_KV_ENABLE_TCMALLOC_HEAP_PROFILING - - struct mallinfo2 mi2 = mallinfo2(); - out << "\n non-mapped allocated bytes: " << print_size(mi2.arena) // - << "\n free chunks: " << print_size(mi2.ordblks) // - << "\n free fastbin blocks: " << print_size(mi2.smblks) // - << "\n mapped regions: " << print_size(mi2.hblks) // - << "\n mapped bytes: " << print_size(mi2.hblkhd) // - << "\n free fastbin bytes: " << print_size(mi2.fsmblks) // - << "\n total allocated bytes: " << print_size(mi2.uordblks) // - << "\n total free bytes: " << print_size(mi2.fordblks) // - << "\n releasable bytes: " << print_size(mi2.keepcost) // - ; - }; -} - -} // namespace turtle_kv From 605394ae40039cbc5594d39f1e3dca65f6011ef0 Mon Sep 17 00:00:00 2001 From: gbornste Date: Wed, 19 Aug 2026 15:12:40 -0400 Subject: [PATCH 2/3] Add simple unit tests --- src/turtle_kv/tree/in_memory_node.test.cpp | 2 +- src/turtle_kv/tree/segmented_level_scanner.test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/turtle_kv/tree/in_memory_node.test.cpp b/src/turtle_kv/tree/in_memory_node.test.cpp index 22b3d56..c761f37 100644 --- a/src/turtle_kv/tree/in_memory_node.test.cpp +++ b/src/turtle_kv/tree/in_memory_node.test.cpp @@ -294,7 +294,7 @@ TEST(InMemoryNodeTest, Subtree) { llfs::testing::ScenarioRunner runner; - u32 n_seeds = getenv_as("TURTLE_TREE_TEST_N_SEEDS").value_or(64); + u32 n_seeds = getenv_as("TURTLE_TREE_TEST_N_SEEDS").value_or(1); usize n_threads = getenv_as("TURTLE_TREE_TEST_N_THREADS").value_or(0); if (n_threads != 0) { diff --git a/src/turtle_kv/tree/segmented_level_scanner.test.cpp b/src/turtle_kv/tree/segmented_level_scanner.test.cpp index fe182c0..7df0b8d 100644 --- a/src/turtle_kv/tree/segmented_level_scanner.test.cpp +++ b/src/turtle_kv/tree/segmented_level_scanner.test.cpp @@ -283,7 +283,7 @@ TEST_F(SegmentedLevelScannerTest, Test) { llfs::testing::ScenarioRunner runner; - runner.n_seeds(64); + runner.n_seeds(1); runner.n_updates(0); runner.run(batt::StaticType{}); } From de8192da2c63138d30acb4f95996533f5f07119d Mon Sep 17 00:00:00 2001 From: gbornste Date: Wed, 19 Aug 2026 15:42:02 -0400 Subject: [PATCH 3/3] Add simple unit tests --- src/turtle_kv/tree/in_memory_node.test.cpp | 2 +- .../tree/in_memory_node_hybrid_level.test.cpp | 286 ++++++++++++ .../tree/segmented_level_scanner.test.cpp | 2 +- src/turtle_kv/tree/subtree_viability.test.cpp | 427 ++++++++++++++++++ 4 files changed, 715 insertions(+), 2 deletions(-) create mode 100644 src/turtle_kv/tree/in_memory_node_hybrid_level.test.cpp create mode 100644 src/turtle_kv/tree/subtree_viability.test.cpp diff --git a/src/turtle_kv/tree/in_memory_node.test.cpp b/src/turtle_kv/tree/in_memory_node.test.cpp index c761f37..22b3d56 100644 --- a/src/turtle_kv/tree/in_memory_node.test.cpp +++ b/src/turtle_kv/tree/in_memory_node.test.cpp @@ -294,7 +294,7 @@ TEST(InMemoryNodeTest, Subtree) { llfs::testing::ScenarioRunner runner; - u32 n_seeds = getenv_as("TURTLE_TREE_TEST_N_SEEDS").value_or(1); + u32 n_seeds = getenv_as("TURTLE_TREE_TEST_N_SEEDS").value_or(64); usize n_threads = getenv_as("TURTLE_TREE_TEST_N_THREADS").value_or(0); if (n_threads != 0) { diff --git a/src/turtle_kv/tree/in_memory_node_hybrid_level.test.cpp b/src/turtle_kv/tree/in_memory_node_hybrid_level.test.cpp new file mode 100644 index 0000000..fcd10d1 --- /dev/null +++ b/src/turtle_kv/tree/in_memory_node_hybrid_level.test.cpp @@ -0,0 +1,286 @@ +#include +// +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +namespace { + +using namespace turtle_kv::int_types; +using namespace turtle_kv::constants; + +using turtle_kv::InMemoryNodeHybridLevel; +using turtle_kv::InMemoryNodeMergedLevel; +using turtle_kv::InMemoryNodeSegment; +using turtle_kv::InMemoryNodeSegmentedLevel; +using turtle_kv::TreeOptions; + +using HybridLevel = InMemoryNodeHybridLevel; +using MergedLevel = InMemoryNodeMergedLevel; +using SegmentedLevel = InMemoryNodeSegmentedLevel; +using Segment = InMemoryNodeSegment; + +//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - +// +class InMemoryNodeHybridLevelTest : public ::testing::Test +{ + public: + Segment make_segment(u64 page_id, const std::vector& active_pivot_indices) + { + Segment segment; + segment.page_id_slot = llfs::PageIdSlot::from_page_id(llfs::PageId{page_id}); + for (i32 pivot_i : active_pivot_indices) { + segment.active_pivots.set(pivot_i, true); + } + return segment; + } + + SegmentedLevel make_segmented_level(std::vector segments) + { + SegmentedLevel level; + for (auto& seg : segments) { + level.segments.emplace_back(std::move(seg)); + } + return level; + } +}; + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// empty / front / back / get_levels +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST_F(InMemoryNodeHybridLevelTest, EmptyByDefault) +{ + HybridLevel level; + EXPECT_TRUE(level.empty()); + EXPECT_EQ(level.front(), nullptr); + EXPECT_EQ(level.back(), nullptr); + EXPECT_TRUE(level.get_levels().empty()); +} + +TEST_F(InMemoryNodeHybridLevelTest, NonEmptyAfterAddSubLevel) +{ + HybridLevel level; + SegmentedLevel seg = this->make_segmented_level({this->make_segment(1, {0})}); + level.add_new_sub_level(HybridLevel::SubLevel{std::move(seg)}); + + EXPECT_FALSE(level.empty()); + EXPECT_NE(level.front(), nullptr); + EXPECT_NE(level.back(), nullptr); + EXPECT_EQ(level.get_levels().size(), 1u); +} + +TEST_F(InMemoryNodeHybridLevelTest, FrontAndBackDistinct) +{ + HybridLevel level; + SegmentedLevel seg1 = this->make_segmented_level({this->make_segment(1, {0})}); + MergedLevel merged; + + level.add_new_sub_level(HybridLevel::SubLevel{std::move(seg1)}); + level.add_new_sub_level(HybridLevel::SubLevel{std::move(merged)}); + + EXPECT_NE(level.front(), level.back()); + EXPECT_TRUE(batt::is_case(*level.front())); + EXPECT_TRUE(batt::is_case(*level.back())); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// add_new_sub_level (SubLevel&&) +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST_F(InMemoryNodeHybridLevelTest, AddSubLevelWithPushPivots) +{ + HybridLevel level; + SegmentedLevel seg = this->make_segmented_level({this->make_segment(10, {0, 1})}); + + level.add_new_sub_level(HybridLevel::SubLevel{std::move(seg)}, /*push_pivot_count=*/3); + + ASSERT_EQ(level.get_levels().size(), 1u); + auto& segmented = std::get(level.sub_levels[0]); + EXPECT_TRUE(segmented.get_segment(0).is_pivot_active(3)); + EXPECT_TRUE(segmented.get_segment(0).is_pivot_active(4)); + EXPECT_FALSE(segmented.get_segment(0).is_pivot_active(0)); + EXPECT_FALSE(segmented.get_segment(0).is_pivot_active(1)); +} + +TEST_F(InMemoryNodeHybridLevelTest, AddSubLevelNoPush) +{ + HybridLevel level; + SegmentedLevel seg = this->make_segmented_level({this->make_segment(20, {2, 5})}); + + level.add_new_sub_level(HybridLevel::SubLevel{std::move(seg)}, /*push_pivot_count=*/0); + + auto& segmented = std::get(level.sub_levels[0]); + EXPECT_TRUE(segmented.get_segment(0).is_pivot_active(2)); + EXPECT_TRUE(segmented.get_segment(0).is_pivot_active(5)); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// add_new_sub_level (HybridLevel&&) +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST_F(InMemoryNodeHybridLevelTest, AddHybridLevelMerges) +{ + HybridLevel level; + level.add_new_sub_level(HybridLevel::SubLevel{MergedLevel{}}); + + HybridLevel other; + other.add_new_sub_level( + HybridLevel::SubLevel{this->make_segmented_level({this->make_segment(30, {1})})}); + other.add_new_sub_level( + HybridLevel::SubLevel{this->make_segmented_level({this->make_segment(31, {2})})}); + + level.add_new_sub_level(std::move(other), /*push_pivot_count=*/0); + + EXPECT_EQ(level.get_levels().size(), 3u); +} + +TEST_F(InMemoryNodeHybridLevelTest, AddHybridLevelWithPushPivots) +{ + HybridLevel level; + + HybridLevel other; + other.add_new_sub_level( + HybridLevel::SubLevel{this->make_segmented_level({this->make_segment(40, {0})})}); + + level.add_new_sub_level(std::move(other), /*push_pivot_count=*/5); + + ASSERT_EQ(level.get_levels().size(), 1u); + auto& segmented = std::get(level.sub_levels[0]); + EXPECT_TRUE(segmented.get_segment(0).is_pivot_active(5)); + EXPECT_FALSE(segmented.get_segment(0).is_pivot_active(0)); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// push_front_pivots +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST_F(InMemoryNodeHybridLevelTest, PushFrontPivotsShiftsSegmented) +{ + HybridLevel level; + level.add_new_sub_level( + HybridLevel::SubLevel{this->make_segmented_level({this->make_segment(50, {0, 3})})}); + level.add_new_sub_level(HybridLevel::SubLevel{MergedLevel{}}); + + level.push_front_pivots(/*node_pivot_count=*/2); + + auto& segmented = std::get(level.sub_levels[0]); + EXPECT_TRUE(segmented.get_segment(0).is_pivot_active(2)); + EXPECT_TRUE(segmented.get_segment(0).is_pivot_active(5)); + EXPECT_FALSE(segmented.get_segment(0).is_pivot_active(0)); + EXPECT_FALSE(segmented.get_segment(0).is_pivot_active(3)); +} + +TEST_F(InMemoryNodeHybridLevelTest, PushFrontPivotsSkipsMerged) +{ + HybridLevel level; + MergedLevel merged; + level.add_new_sub_level(HybridLevel::SubLevel{std::move(merged)}); + + level.push_front_pivots(/*node_pivot_count=*/10); + + EXPECT_TRUE(batt::is_case(level.sub_levels[0])); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// segment_count +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST_F(InMemoryNodeHybridLevelTest, SegmentCountEmpty) +{ + HybridLevel level; + TreeOptions tree_options = TreeOptions::with_default_values() + .set_leaf_size(512 * kKiB) + .set_node_size(4 * kKiB) + .set_key_size_hint(24) + .set_value_size_hint(100); + + EXPECT_EQ(level.segment_count(tree_options), 0u); +} + +TEST_F(InMemoryNodeHybridLevelTest, SegmentCountWithSegmentedSubLevel) +{ + HybridLevel level; + TreeOptions tree_options = TreeOptions::with_default_values() + .set_leaf_size(512 * kKiB) + .set_node_size(4 * kKiB) + .set_key_size_hint(24) + .set_value_size_hint(100); + + SegmentedLevel seg = this->make_segmented_level({ + this->make_segment(100, {0}), + this->make_segment(101, {1}), + this->make_segment(102, {2}), + }); + + level.add_new_sub_level(HybridLevel::SubLevel{std::move(seg)}); + + EXPECT_EQ(level.segment_count(tree_options), 3u); +} + +TEST_F(InMemoryNodeHybridLevelTest, SegmentCountMultipleSubLevels) +{ + HybridLevel level; + TreeOptions tree_options = TreeOptions::with_default_values() + .set_leaf_size(512 * kKiB) + .set_node_size(4 * kKiB) + .set_key_size_hint(24) + .set_value_size_hint(100); + + SegmentedLevel seg1 = this->make_segmented_level({ + this->make_segment(200, {0}), + this->make_segment(201, {1}), + }); + SegmentedLevel seg2 = this->make_segmented_level({ + this->make_segment(300, {0}), + }); + + level.add_new_sub_level(HybridLevel::SubLevel{std::move(seg1)}); + level.add_new_sub_level(HybridLevel::SubLevel{MergedLevel{}}); + level.add_new_sub_level(HybridLevel::SubLevel{std::move(seg2)}); + + EXPECT_GE(level.segment_count(tree_options), 3u); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// dump +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST_F(InMemoryNodeHybridLevelTest, DumpProducesOutput) +{ + HybridLevel level; + level.add_new_sub_level( + HybridLevel::SubLevel{this->make_segmented_level({this->make_segment(1, {0})})}); + + std::ostringstream oss; + auto dump_fn = level.dump(); + dump_fn(oss); + + std::string output = oss.str(); + EXPECT_FALSE(output.empty()); + EXPECT_NE(output.find("HybridLevel"), std::string::npos); +} + +TEST_F(InMemoryNodeHybridLevelTest, DumpEmptyLevel) +{ + HybridLevel level; + + std::ostringstream oss; + auto dump_fn = level.dump(); + dump_fn(oss); + + std::string output = oss.str(); + EXPECT_NE(output.find("HybridLevel"), std::string::npos); +} + +} // namespace diff --git a/src/turtle_kv/tree/segmented_level_scanner.test.cpp b/src/turtle_kv/tree/segmented_level_scanner.test.cpp index 7df0b8d..fe182c0 100644 --- a/src/turtle_kv/tree/segmented_level_scanner.test.cpp +++ b/src/turtle_kv/tree/segmented_level_scanner.test.cpp @@ -283,7 +283,7 @@ TEST_F(SegmentedLevelScannerTest, Test) { llfs::testing::ScenarioRunner runner; - runner.n_seeds(1); + runner.n_seeds(64); runner.n_updates(0); runner.run(batt::StaticType{}); } diff --git a/src/turtle_kv/tree/subtree_viability.test.cpp b/src/turtle_kv/tree/subtree_viability.test.cpp new file mode 100644 index 0000000..ee70c37 --- /dev/null +++ b/src/turtle_kv/tree/subtree_viability.test.cpp @@ -0,0 +1,427 @@ +#include +// +#include + +#include +#include + +#include + +#include + +namespace { + +using namespace turtle_kv::int_types; + +using turtle_kv::compacting_levels_might_fix; +using turtle_kv::is_root_viable; +using turtle_kv::NeedsMerge; +using turtle_kv::NeedsSplit; +using turtle_kv::normal_flush_might_fix; +using turtle_kv::normal_flush_might_fix_root; +using turtle_kv::SubtreeViability; +using turtle_kv::Viable; + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// NeedsMerge::operator bool +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST(NeedsMergeTest, DefaultIsFalse) +{ + NeedsMerge nm; + EXPECT_FALSE(bool(nm)); +} + +TEST(NeedsMergeTest, TooFewPivotsIsTrue) +{ + NeedsMerge nm; + nm.too_few_pivots = true; + EXPECT_TRUE(bool(nm)); +} + +TEST(NeedsMergeTest, TooFewItemsIsTrue) +{ + NeedsMerge nm; + nm.too_few_items = true; + EXPECT_TRUE(bool(nm)); +} + +TEST(NeedsMergeTest, SinglePivotAloneIsFalse) +{ + NeedsMerge nm; + nm.single_pivot = true; + EXPECT_FALSE(bool(nm)); +} + +TEST(NeedsMergeTest, ZeroItemsAloneIsFalse) +{ + NeedsMerge nm; + nm.zero_items = true; + EXPECT_FALSE(bool(nm)); +} + +TEST(NeedsMergeTest, StreamOutput) +{ + NeedsMerge nm; + nm.too_few_pivots = true; + nm.zero_items = true; + + std::ostringstream oss; + oss << nm; + std::string output = oss.str(); + EXPECT_NE(output.find("NeedsMerge"), std::string::npos); + EXPECT_NE(output.find("too_few_pivots"), std::string::npos); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// NeedsSplit::operator bool +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST(NeedsSplitTest, DefaultIsFalse) +{ + NeedsSplit ns{}; + EXPECT_FALSE(bool(ns)); +} + +TEST(NeedsSplitTest, ItemsTooLargeIsTrue) +{ + NeedsSplit ns{}; + ns.items_too_large = true; + EXPECT_TRUE(bool(ns)); +} + +TEST(NeedsSplitTest, KeysTooLargeIsTrue) +{ + NeedsSplit ns{}; + ns.keys_too_large = true; + EXPECT_TRUE(bool(ns)); +} + +TEST(NeedsSplitTest, TooManyPivotsIsTrue) +{ + NeedsSplit ns{}; + ns.too_many_pivots = true; + EXPECT_TRUE(bool(ns)); +} + +TEST(NeedsSplitTest, TooManySegmentsIsTrue) +{ + NeedsSplit ns{}; + ns.too_many_segments = true; + EXPECT_TRUE(bool(ns)); +} + +TEST(NeedsSplitTest, SegmentFiltersTooLargeIsTrue) +{ + NeedsSplit ns{}; + ns.segment_filters_too_large = true; + EXPECT_TRUE(bool(ns)); +} + +TEST(NeedsSplitTest, StreamOutput) +{ + NeedsSplit ns{}; + ns.too_many_pivots = true; + ns.pivot_count = 42; + + std::ostringstream oss; + oss << ns; + std::string output = oss.str(); + EXPECT_NE(output.find("NeedsSplit"), std::string::npos); + EXPECT_NE(output.find("too_many_pivots"), std::string::npos); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// Viable +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST(ViableTest, StreamOutput) +{ + Viable v; + std::ostringstream oss; + oss << v; + EXPECT_EQ(oss.str(), "Viable"); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// SubtreeViability stream output +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST(SubtreeViabilityTest, StreamOutputViable) +{ + SubtreeViability sv = Viable{}; + std::ostringstream oss; + oss << sv; + EXPECT_EQ(oss.str(), "Viable"); +} + +TEST(SubtreeViabilityTest, StreamOutputNeedsMerge) +{ + NeedsMerge nm; + nm.too_few_items = true; + SubtreeViability sv = nm; + + std::ostringstream oss; + oss << sv; + EXPECT_NE(oss.str().find("NeedsMerge"), std::string::npos); +} + +TEST(SubtreeViabilityTest, StreamOutputNeedsSplit) +{ + NeedsSplit ns{}; + ns.items_too_large = true; + SubtreeViability sv = ns; + + std::ostringstream oss; + oss << sv; + EXPECT_NE(oss.str().find("NeedsSplit"), std::string::npos); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// compacting_levels_might_fix +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST(CompactingLevelsMightFixTest, ViableReturnsFalse) +{ + SubtreeViability sv = Viable{}; + EXPECT_FALSE(compacting_levels_might_fix(sv)); +} + +TEST(CompactingLevelsMightFixTest, NeedsMergeReturnsFalse) +{ + NeedsMerge nm; + nm.too_few_items = true; + SubtreeViability sv = nm; + EXPECT_FALSE(compacting_levels_might_fix(sv)); +} + +TEST(CompactingLevelsMightFixTest, SegmentFiltersTooLargeOnly) +{ + NeedsSplit ns{}; + ns.segment_filters_too_large = true; + SubtreeViability sv = ns; + EXPECT_TRUE(compacting_levels_might_fix(sv)); +} + +TEST(CompactingLevelsMightFixTest, TooManySegmentsOnly) +{ + NeedsSplit ns{}; + ns.too_many_segments = true; + SubtreeViability sv = ns; + EXPECT_TRUE(compacting_levels_might_fix(sv)); +} + +TEST(CompactingLevelsMightFixTest, SegmentFiltersPlusItemsTooLarge) +{ + NeedsSplit ns{}; + ns.segment_filters_too_large = true; + ns.items_too_large = true; + SubtreeViability sv = ns; + EXPECT_FALSE(compacting_levels_might_fix(sv)); +} + +TEST(CompactingLevelsMightFixTest, TooManySegmentsPlusKeysTooLarge) +{ + NeedsSplit ns{}; + ns.too_many_segments = true; + ns.keys_too_large = true; + SubtreeViability sv = ns; + EXPECT_FALSE(compacting_levels_might_fix(sv)); +} + +TEST(CompactingLevelsMightFixTest, TooManySegmentsPlusTooManyPivots) +{ + NeedsSplit ns{}; + ns.too_many_segments = true; + ns.too_many_pivots = true; + SubtreeViability sv = ns; + EXPECT_FALSE(compacting_levels_might_fix(sv)); +} + +TEST(CompactingLevelsMightFixTest, NeitherSegmentIssueReturnsFalse) +{ + NeedsSplit ns{}; + ns.items_too_large = true; + SubtreeViability sv = ns; + EXPECT_FALSE(compacting_levels_might_fix(sv)); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// normal_flush_might_fix +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST(NormalFlushMightFixTest, ViableReturnsFalse) +{ + SubtreeViability sv = Viable{}; + EXPECT_FALSE(normal_flush_might_fix(sv)); +} + +TEST(NormalFlushMightFixTest, NeedsMergeReturnsFalse) +{ + NeedsMerge nm; + nm.too_few_pivots = true; + SubtreeViability sv = nm; + EXPECT_FALSE(normal_flush_might_fix(sv)); +} + +TEST(NormalFlushMightFixTest, HeightTwoSegmentFiltersTooLarge) +{ + NeedsSplit ns{}; + ns.height = 2; + ns.segment_filters_too_large = true; + SubtreeViability sv = ns; + EXPECT_TRUE(normal_flush_might_fix(sv)); +} + +TEST(NormalFlushMightFixTest, HeightTwoTooManySegments) +{ + NeedsSplit ns{}; + ns.height = 2; + ns.too_many_segments = true; + SubtreeViability sv = ns; + EXPECT_TRUE(normal_flush_might_fix(sv)); +} + +TEST(NormalFlushMightFixTest, HeightThreeReturnsFalse) +{ + NeedsSplit ns{}; + ns.height = 3; + ns.segment_filters_too_large = true; + SubtreeViability sv = ns; + EXPECT_FALSE(normal_flush_might_fix(sv)); +} + +TEST(NormalFlushMightFixTest, HeightTwoButItemsTooLarge) +{ + NeedsSplit ns{}; + ns.height = 2; + ns.too_many_segments = true; + ns.items_too_large = true; + SubtreeViability sv = ns; + EXPECT_FALSE(normal_flush_might_fix(sv)); +} + +TEST(NormalFlushMightFixTest, HeightTwoButKeysTooLarge) +{ + NeedsSplit ns{}; + ns.height = 2; + ns.segment_filters_too_large = true; + ns.keys_too_large = true; + SubtreeViability sv = ns; + EXPECT_FALSE(normal_flush_might_fix(sv)); +} + +TEST(NormalFlushMightFixTest, HeightTwoButTooManyPivots) +{ + NeedsSplit ns{}; + ns.height = 2; + ns.too_many_segments = true; + ns.too_many_pivots = true; + SubtreeViability sv = ns; + EXPECT_FALSE(normal_flush_might_fix(sv)); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// normal_flush_might_fix_root +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST(NormalFlushMightFixRootTest, ViableReturnsFalse) +{ + SubtreeViability sv = Viable{}; + EXPECT_FALSE(normal_flush_might_fix_root(sv)); +} + +TEST(NormalFlushMightFixRootTest, NeedsMergeReturnsFalse) +{ + NeedsMerge nm; + nm.too_few_items = true; + SubtreeViability sv = nm; + EXPECT_FALSE(normal_flush_might_fix_root(sv)); +} + +TEST(NormalFlushMightFixRootTest, TooManySegmentsOnly) +{ + NeedsSplit ns{}; + ns.too_many_segments = true; + SubtreeViability sv = ns; + EXPECT_TRUE(normal_flush_might_fix_root(sv)); +} + +TEST(NormalFlushMightFixRootTest, TooManySegmentsPlusTooManyPivots) +{ + NeedsSplit ns{}; + ns.too_many_segments = true; + ns.too_many_pivots = true; + SubtreeViability sv = ns; + EXPECT_FALSE(normal_flush_might_fix_root(sv)); +} + +TEST(NormalFlushMightFixRootTest, TooManySegmentsPlusKeysTooLarge) +{ + NeedsSplit ns{}; + ns.too_many_segments = true; + ns.keys_too_large = true; + SubtreeViability sv = ns; + EXPECT_FALSE(normal_flush_might_fix_root(sv)); +} + +TEST(NormalFlushMightFixRootTest, SegmentFiltersAloneReturnsFalse) +{ + NeedsSplit ns{}; + ns.segment_filters_too_large = true; + SubtreeViability sv = ns; + EXPECT_FALSE(normal_flush_might_fix_root(sv)); +} + +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- +// is_root_viable +//=#=#==#==#===============+=+=+=+=++=++++++++++++++-++-+--+-+----+--------------- + +TEST(IsRootViableTest, ViableReturnsTrue) +{ + SubtreeViability sv = Viable{}; + EXPECT_TRUE(is_root_viable(sv)); +} + +TEST(IsRootViableTest, NeedsSplitReturnsFalse) +{ + NeedsSplit ns{}; + ns.items_too_large = true; + SubtreeViability sv = ns; + EXPECT_FALSE(is_root_viable(sv)); +} + +TEST(IsRootViableTest, NeedsMergeNoSpecialFlagsReturnsTrue) +{ + NeedsMerge nm; + nm.too_few_items = true; + SubtreeViability sv = nm; + EXPECT_TRUE(is_root_viable(sv)); +} + +TEST(IsRootViableTest, NeedsMergeSinglePivotReturnsFalse) +{ + NeedsMerge nm; + nm.single_pivot = true; + SubtreeViability sv = nm; + EXPECT_FALSE(is_root_viable(sv)); +} + +TEST(IsRootViableTest, NeedsMergeZeroItemsReturnsFalse) +{ + NeedsMerge nm; + nm.zero_items = true; + SubtreeViability sv = nm; + EXPECT_FALSE(is_root_viable(sv)); +} + +TEST(IsRootViableTest, NeedsMergeBothSpecialFlagsReturnsFalse) +{ + NeedsMerge nm; + nm.single_pivot = true; + nm.zero_items = true; + SubtreeViability sv = nm; + EXPECT_FALSE(is_root_viable(sv)); +} + +} // namespace