From 6ce479f67fcb12e2e77c5db6f7c342459ea3e331 Mon Sep 17 00:00:00 2001 From: shosseinimotlagh Date: Fri, 19 Jan 2024 01:26:17 -0800 Subject: [PATCH] FIX btree tests --- src/tests/CMakeLists.txt | 4 +- src/tests/btree_helpers/btree_test_helper.hpp | 122 ++++++++++++------ src/tests/btree_helpers/shadow_map.hpp | 44 +++++-- .../test_common/homestore_test_common.hpp | 2 +- src/tests/test_common/range_scheduler.hpp | 26 +--- src/tests/test_index_btree.cpp | 5 +- src/tests/test_scripts/btree_test.py | 12 +- 7 files changed, 127 insertions(+), 88 deletions(-) diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index b40528358..08ebab778 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -36,8 +36,8 @@ if (${build_nonio_tests}) set(TEST_MEMBTREE_SOURCE_FILES test_mem_btree.cpp) add_executable(test_mem_btree ${TEST_MEMBTREE_SOURCE_FILES}) target_link_libraries(test_mem_btree ${COMMON_TEST_DEPS} GTest::gtest) - # add_test(NAME MemBtree COMMAND test_mem_btree) - # set_tests_properties(MemBtree PROPERTIES TIMEOUT 180) + add_test(NAME MemBtree COMMAND test_mem_btree) + set_tests_properties(MemBtree PROPERTIES TIMEOUT 600) add_executable(test_blk_read_tracker) target_sources(test_blk_read_tracker PRIVATE test_blk_read_tracker.cpp ../lib/blkdata_svc/blk_read_tracker.cpp ../lib/blkalloc/blk.cpp) diff --git a/src/tests/btree_helpers/btree_test_helper.hpp b/src/tests/btree_helpers/btree_test_helper.hpp index be5cc4e14..2cb03bca1 100644 --- a/src/tests/btree_helpers/btree_test_helper.hpp +++ b/src/tests/btree_helpers/btree_test_helper.hpp @@ -50,7 +50,7 @@ struct BtreeTestHelper { if (m_is_multi_threaded) { std::mutex mtx; m_run_time = SISL_OPTIONS["run_time"].as< uint32_t >(); - iomanager.run_on_wait(iomgr::reactor_regex::all_io, [this, &mtx]() { + iomanager.run_on_wait(iomgr::reactor_regex::all_worker, [this, &mtx]() { auto fv = iomanager.sync_io_capable_fibers(); std::unique_lock lg(mtx); m_fibers.insert(m_fibers.end(), fv.begin(), fv.end()); @@ -84,27 +84,30 @@ struct BtreeTestHelper { public: void preload(uint32_t preload_size) { - const auto chunk_size = preload_size / m_fibers.size(); - const auto last_chunk_size = preload_size % chunk_size ?: chunk_size; - auto test_count = m_fibers.size(); - - for (std::size_t i = 0; i < m_fibers.size(); ++i) { - const auto start_range = i * chunk_size; - const auto end_range = start_range + ((i == m_fibers.size() - 1) ? last_chunk_size : chunk_size); - iomanager.run_on_forget(m_fibers[i], [this, start_range, end_range, &test_count]() { - for (uint32_t i = start_range; i < end_range; i++) { - put(i, btree_put_type::INSERT); - } - { - std::unique_lock lg(m_test_done_mtx); - if (--test_count == 0) { m_test_done_cv.notify_one(); } - } - }); - } + if (preload_size) { + const auto n_fibers = std::min(preload_size, (uint32_t)m_fibers.size()); + const auto chunk_size = preload_size / n_fibers; + const auto last_chunk_size = preload_size % chunk_size ?: chunk_size; + auto test_count = n_fibers; + + for (std::size_t i = 0; i < n_fibers; ++i) { + const auto start_range = i * chunk_size; + const auto end_range = start_range + ((i == n_fibers - 1) ? last_chunk_size : chunk_size); + iomanager.run_on_forget(m_fibers[i], [this, start_range, end_range, &test_count]() { + for (uint32_t i = start_range; i < end_range; i++) { + put(i, btree_put_type::INSERT); + } + { + std::unique_lock lg(m_test_done_mtx); + if (--test_count == 0) { m_test_done_cv.notify_one(); } + } + }); + } - { - std::unique_lock< std::mutex > lk(m_test_done_mtx); - m_test_done_cv.wait(lk, [&]() { return test_count == 0; }); + { + std::unique_lock< std::mutex > lk(m_test_done_mtx); + m_test_done_cv.wait(lk, [&]() { return test_count == 0; }); + } } LOGINFO("Preload Done"); } @@ -157,15 +160,13 @@ struct BtreeTestHelper { auto pk = std::make_unique< K >(k); auto rreq = BtreeSingleRemoveRequest{pk.get(), existing_v.get()}; + rreq.enable_route_tracing(); bool removed = (m_bt->remove(rreq) == btree_status_t::success); ASSERT_EQ(removed, m_shadow_map.exists(*pk)) << "Removal of key " << pk->key() << " status doesn't match with shadow"; - if (removed) { - m_shadow_map.validate_data(rreq.key(), (const V&)rreq.value()); - m_shadow_map.erase(rreq.key()); - } + if (removed) { m_shadow_map.remove_and_check(*pk, *existing_v); } } void remove_random() { @@ -213,14 +214,17 @@ struct BtreeTestHelper { auto const expected_count = std::min(remaining, batch_size); ASSERT_EQ(out_vector.size(), expected_count) << "Received incorrect value on query pagination"; - remaining -= expected_count; - if (remaining == 0) { + if (remaining < batch_size) { ASSERT_EQ(ret, btree_status_t::success) << "Expected success on query"; - } else { + } else if (remaining > batch_size) { ASSERT_EQ(ret, btree_status_t::has_more) << "Expected query to return has_more"; + } else if (remaining == batch_size) { + // we don't know, go to the next round } + remaining -= expected_count; + for (size_t idx{0}; idx < out_vector.size(); ++idx) { ASSERT_EQ(out_vector[idx].second, it->second) << "Range get doesn't return correct data for key=" << it->first << " idx=" << idx; @@ -253,7 +257,7 @@ struct BtreeTestHelper { *copy_key = key; auto out_v = std::make_unique< V >(); auto req = BtreeSingleGetRequest{copy_key.get(), out_v.get()}; - + req.enable_route_tracing(); const auto ret = m_bt->get(req); ASSERT_EQ(ret, btree_status_t::success) << "Missing key " << key << " in btree but present in shadow map"; ASSERT_EQ((const V&)req.value(), value) @@ -265,7 +269,7 @@ struct BtreeTestHelper { auto pk = std::make_unique< K >(k); auto out_v = std::make_unique< V >(); auto req = BtreeSingleGetRequest{pk.get(), out_v.get()}; - + req.enable_route_tracing(); const auto status = m_bt->get(req); if (status == btree_status_t::success) { m_shadow_map.validate_data(req.key(), (const V&)req.value()); @@ -279,6 +283,7 @@ struct BtreeTestHelper { auto out_v = std::make_unique< V >(); auto req = BtreeGetAnyRequest< K >{BtreeKeyRange< K >{K{start_k}, true, K{end_k}, true}, out_k.get(), out_v.get()}; + req.enable_route_tracing(); const auto status = m_bt->get(req); if (status == btree_status_t::success) { @@ -335,6 +340,7 @@ struct BtreeTestHelper { auto existing_v = std::make_unique< V >(); K key = K{k}; auto sreq = BtreeSinglePutRequest{&key, &value, put_type, existing_v.get()}; + sreq.enable_route_tracing(); bool done = (m_bt->put(sreq) == btree_status_t::success); if (put_type == btree_put_type::INSERT) { @@ -351,43 +357,73 @@ struct BtreeTestHelper { K end_key = K{end_k}; auto rreq = BtreeRangeRemoveRequest< K >{BtreeKeyRange< K >{start_key, true, end_key, true}}; + rreq.enable_route_tracing(); auto const ret = m_bt->remove(rreq); - m_shadow_map.range_erase(start_key, end_key); - if (all_existing) { + m_shadow_map.range_erase(start_key, end_key); ASSERT_EQ((ret == btree_status_t::success), true) << "not a successful remove op for range " << start_k << "-" << end_k; - } - - if (start_k < m_max_range_input) { - m_shadow_map.remove_keys(start_k, std::min(end_k, uint64_cast(m_max_range_input - 1))); + } else if (start_k < m_max_range_input) { + K end_range{std::min(end_k, uint64_cast(m_max_range_input - 1))}; + m_shadow_map.range_erase(start_key, end_range); } } protected: void run_in_parallel(const std::vector< std::pair< std::string, int > >& op_list) { auto test_count = m_fibers.size(); - for (auto it = m_fibers.begin(); it < m_fibers.end(); ++it) { - iomanager.run_on_forget(*it, [this, &test_count, op_list]() { + const auto total_iters = SISL_OPTIONS["num_iters"].as< uint32_t >(); + const auto num_iters_per_thread = total_iters / m_fibers.size(); + const auto extra_iters = total_iters % num_iters_per_thread; + LOGINFO("number of fibers {} num_iters_per_thread {} extra_iters {} ", m_fibers.size(), num_iters_per_thread, + extra_iters); + + for (uint32_t fiber_id = 0; fiber_id < m_fibers.size(); ++fiber_id) { + auto num_iters_this_fiber = num_iters_per_thread + (fiber_id < extra_iters ? 1 : 0); + iomanager.run_on_forget(m_fibers[fiber_id], [this, fiber_id, &test_count, op_list, num_iters_this_fiber]() { std::random_device g_rd{}; std::default_random_engine re{g_rd()}; - const auto num_iters_per_thread = - sisl::round_up(SISL_OPTIONS["num_iters"].as< uint32_t >() / m_fibers.size(), m_fibers.size()); std::vector< uint32_t > weights; std::transform(op_list.begin(), op_list.end(), std::back_inserter(weights), [](const auto& pair) { return pair.second; }); + double progress_interval = (double)num_iters_this_fiber / 20; // 5% of the total number of iterations + double progress_thresh = progress_interval; // threshold for progress interval + double elapsed_time, progress_percent, last_progress_time = 0; + // Construct a weighted distribution based on the input frequencies std::discrete_distribution< uint32_t > s_rand_op_generator(weights.begin(), weights.end()); auto m_start_time = Clock::now(); + auto time_to_stop = [this, m_start_time]() { + return (get_elapsed_time_sec(m_start_time) > m_run_time); + }; - auto time_to_stop = [this, m_start_time]() {return (get_elapsed_time_sec(m_start_time) > m_run_time);}; - - for (uint32_t i = 0; i < num_iters_per_thread && !time_to_stop(); i++) { + for (uint32_t i = 0; i < num_iters_this_fiber && !time_to_stop(); i++) { uint32_t op_idx = s_rand_op_generator(re); (this->m_operations[op_list[op_idx].first])(); m_num_ops.fetch_add(1); + + if (fiber_id == 0) { + elapsed_time = get_elapsed_time_sec(m_start_time); + progress_percent = (double)i / num_iters_this_fiber * 100; + + // check progress every 5% of the total number of iterations or every 30 seconds + bool print_time = false; + if (i >= progress_thresh) { + progress_thresh += progress_interval; + print_time = true; + } + if (elapsed_time - last_progress_time > 30) { + last_progress_time = elapsed_time; + print_time = true; + } + if (print_time) { + LOGINFO("Progress: iterations completed ({:.2f}%)- Elapsed time: {:.0f} seconds of total " + "{} - total entries: {}", + progress_percent, elapsed_time, m_run_time, m_shadow_map.size()); + } + } } { std::unique_lock lg(m_test_done_mtx); diff --git a/src/tests/btree_helpers/shadow_map.hpp b/src/tests/btree_helpers/shadow_map.hpp index f8c40e140..edd6e567f 100644 --- a/src/tests/btree_helpers/shadow_map.hpp +++ b/src/tests/btree_helpers/shadow_map.hpp @@ -48,7 +48,7 @@ class ShadowMap { } std::pair< K, K > pick_existing_range(const K& start_key, uint32_t max_count) const { - std::shared_lock lock{m_mutex}; + std::lock_guard lock{m_mutex}; auto const start_it = m_map.lower_bound(start_key); auto it = start_it; uint32_t count = 0; @@ -59,12 +59,12 @@ class ShadowMap { } bool exists(const K& key) const { - std::shared_lock lock{m_mutex}; + std::lock_guard lock{m_mutex}; return m_map.find(key) != m_map.end(); } bool exists_in_range(const K& key, uint64_t start_k, uint64_t end_k) const { - std::shared_lock lock{m_mutex}; + std::lock_guard lock{m_mutex}; const auto itlower = m_map.lower_bound(K{start_k}); const auto itupper = m_map.upper_bound(K{end_k}); auto it = itlower; @@ -76,7 +76,7 @@ class ShadowMap { } uint64_t size() const { - std::shared_lock lock{m_mutex}; + std::lock_guard lock{m_mutex}; return m_map.size(); } @@ -87,12 +87,21 @@ class ShadowMap { } void validate_data(const K& key, const V& btree_val) const { - std::shared_lock lock{m_mutex}; + std::lock_guard lock{m_mutex}; const auto r = m_map.find(key); ASSERT_NE(r, m_map.end()) << "Key " << key.to_string() << " is not present in shadow map"; ASSERT_EQ(btree_val, r->second) << "Found value in btree doesn't return correct data for key=" << r->first; } + void remove_and_check(const K& key, const V& btree_val) { + std::lock_guard lock{m_mutex}; + const auto r = m_map.find(key); + ASSERT_NE(r, m_map.end()) << "Key " << key.to_string() << " is not present in shadow map"; + ASSERT_EQ(btree_val, r->second) << "Found value in btree doesn't return correct data for key=" << r->first; + m_map.erase(key); + m_range_scheduler.remove_key(key.key()); + } + void erase(const K& key) { std::lock_guard lock{m_mutex}; m_map.erase(key); @@ -101,7 +110,7 @@ class ShadowMap { void range_erase(const K& start_key, uint32_t count) { std::lock_guard lock{m_mutex}; - auto const it = m_map.lower_bound(start_key); + auto it = m_map.lower_bound(start_key); uint32_t i{0}; while ((it != m_map.cend()) && (i++ < count)) { it = m_map.erase(it); @@ -124,25 +133,34 @@ class ShadowMap { const std::map< K, V >& map_const() const { return m_map; } void foreach (std::function< void(K, V) > func) const { - std::shared_lock lock{m_mutex}; + std::lock_guard lock{m_mutex}; for (const auto& [key, value] : m_map) { func(key, value); } } std::pair< uint32_t, uint32_t > pick_random_non_existing_keys(uint32_t max_keys) { - std::shared_lock lock{m_mutex}; - return m_range_scheduler.pick_random_non_existing_keys(max_keys); + do { + std::lock_guard lock{m_mutex}; + auto ret = m_range_scheduler.pick_random_non_existing_keys(max_keys); + if (ret.first != UINT32_MAX) { return ret; } + } while (true); } std::pair< uint32_t, uint32_t > pick_random_existing_keys(uint32_t max_keys) { - std::shared_lock lock{m_mutex}; - return m_range_scheduler.pick_random_existing_keys(max_keys); + do { + std::lock_guard lock{m_mutex}; + auto ret = m_range_scheduler.pick_random_existing_keys(max_keys); + if (ret.first != UINT32_MAX) { return ret; } + } while (true); } std::pair< uint32_t, uint32_t > pick_random_non_working_keys(uint32_t max_keys) { - std::shared_lock lock{m_mutex}; - return m_range_scheduler.pick_random_non_working_keys(max_keys); + do { + std::lock_guard lock{m_mutex}; + auto ret = m_range_scheduler.pick_random_non_working_keys(max_keys); + if (ret.first != UINT32_MAX) { return ret; } + } while (true); } void remove_keys_from_working(uint32_t s, uint32_t e) { diff --git a/src/tests/test_common/homestore_test_common.hpp b/src/tests/test_common/homestore_test_common.hpp index 277c54a24..3589fca43 100644 --- a/src/tests/test_common/homestore_test_common.hpp +++ b/src/tests/test_common/homestore_test_common.hpp @@ -221,7 +221,7 @@ class HSTestHelper { LOGINFO("Starting iomgr with {} threads, spdk: {}", num_threads, is_spdk); ioenvironment.with_iomgr( - iomgr::iomgr_params{.num_threads = num_threads, .is_spdk = is_spdk, .num_fibers = num_fibers}); + iomgr::iomgr_params{.num_threads = num_threads, .is_spdk = is_spdk, .num_fibers = 1 + num_fibers}); auto const http_port = SISL_OPTIONS["http_port"].as< int >(); if (http_port != 0) { diff --git a/src/tests/test_common/range_scheduler.hpp b/src/tests/test_common/range_scheduler.hpp index 5dc2e4d1b..9c6bd937a 100644 --- a/src/tests/test_common/range_scheduler.hpp +++ b/src/tests/test_common/range_scheduler.hpp @@ -24,8 +24,6 @@ #include namespace homestore { -using mutex = iomgr::FiberManagerLib::shared_mutex; - static std::pair< uint64_t, uint64_t > get_next_contiguous_set_bits(const sisl::Bitset& bm, uint64_t search_start_bit, uint64_t max_count) { uint64_t first_set_bit{sisl::Bitset::npos}; @@ -48,9 +46,7 @@ class RangeScheduler { private: sisl::Bitset m_existing_keys; sisl::Bitset m_working_keys; - mutex m_set_lock; std::uniform_int_distribution< uint32_t > m_rand_start_key_generator; - std::random_device m_rd; public: @@ -58,68 +54,63 @@ class RangeScheduler { m_rand_start_key_generator = std::uniform_int_distribution< uint32_t >(0, num_keys - 1); } - void remove_keys_from_working(uint32_t s, uint32_t e) { - std::unique_lock< mutex > lk(m_set_lock); - remove_from_working(s, e); - } + void remove_keys_from_working(uint32_t s, uint32_t e) { remove_from_working(s, e); } void put_key(uint32_t key) { - std::unique_lock< mutex > lk(m_set_lock); add_to_existing(key); remove_from_working(key); } void put_keys(uint32_t start_key, uint32_t end_key) { - std::unique_lock< mutex > lk(m_set_lock); add_to_existing(start_key, end_key); remove_from_working(start_key, end_key); } void remove_key(uint32_t key) { - std::unique_lock< mutex > lk(m_set_lock); remove_from_existing(key); remove_from_working(key); } void remove_keys(uint32_t start_key, uint32_t end_key) { - std::unique_lock< mutex > lk(m_set_lock); remove_from_existing(start_key, end_key); remove_from_working(start_key, end_key); } std::pair< uint32_t, uint32_t > pick_random_non_existing_keys(uint32_t max_keys) { std::pair< uint32_t, uint32_t > ret; + auto max_tries = 10; do { ret = try_pick_random_non_existing_keys(max_keys); if (ret.first != UINT32_MAX) { break; } - } while (true); + } while (--max_tries); return ret; } std::pair< uint32_t, uint32_t > pick_random_existing_keys(uint32_t max_keys) { std::pair< uint32_t, uint32_t > ret; + auto max_tries = 10; do { ret = try_pick_random_existing_keys(max_keys); if (ret.first != UINT32_MAX) { break; } - } while (true); + } while (--max_tries); return ret; } std::pair< uint32_t, uint32_t > pick_random_non_working_keys(uint32_t max_keys) { std::pair< uint32_t, uint32_t > ret; + auto max_tries = 10; do { ret = try_pick_random_non_working_keys(max_keys); if (ret.first != UINT32_MAX) { break; } - } while (true); + } while (--max_tries); return ret; } private: std::pair< uint32_t, uint32_t > try_pick_random_non_existing_keys(uint32_t max_keys) { - std::unique_lock< mutex > lk(m_set_lock); if ((m_existing_keys.size() - m_existing_keys.get_set_count()) == 0) { throw std::out_of_range("All keys are being worked on right now"); } @@ -137,7 +128,6 @@ class RangeScheduler { } std::pair< uint32_t, uint32_t > try_pick_random_existing_keys(uint32_t max_keys) { - std::unique_lock< mutex > lk(m_set_lock); if (m_existing_keys.get_set_count() == 0) { DEBUG_ASSERT(false, "Couldn't find one existing keys"); throw std::out_of_range("Couldn't find one existing keys"); @@ -157,8 +147,6 @@ class RangeScheduler { } std::pair< uint32_t, uint32_t > try_pick_random_non_working_keys(uint32_t max_keys) { - std::unique_lock< mutex > lk(m_set_lock); - uint32_t const search_start = m_rand_start_key_generator(m_rd); auto bb = m_working_keys.get_next_contiguous_n_reset_bits(search_start, max_keys); diff --git a/src/tests/test_index_btree.cpp b/src/tests/test_index_btree.cpp index 1e833b059..fcda6fd9c 100644 --- a/src/tests/test_index_btree.cpp +++ b/src/tests/test_index_btree.cpp @@ -33,7 +33,6 @@ SISL_LOGGING_DECL(test_index_btree) std::vector< std::string > test_common::HSTestHelper::s_dev_names; - // TODO Add tests to do write,remove after recovery. // TODO Test with var len key with io mgr page size is 512. @@ -367,7 +366,6 @@ TYPED_TEST(BtreeTest, ThreadedCpFlush) { // Remove a random entry. std::uniform_int_distribution< uint32_t > rand{0, last_index.load()}; auto rm_idx = rand(g_re); - LOGINFO("Removing entry {}", rm_idx); this->remove_one(rm_idx); } }); @@ -431,7 +429,6 @@ struct BtreeConcurrentTest : public BtreeTestHelper< TestType >, public ::testin BtreeConcurrentTest* m_test; }; - BtreeConcurrentTest() : testing::Test() { this->m_is_multi_threaded = true; } void SetUp() override { @@ -469,7 +466,7 @@ struct BtreeConcurrentTest : public BtreeTestHelper< TestType >, public ::testin TYPED_TEST_SUITE(BtreeConcurrentTest, BtreeTypes); TYPED_TEST(BtreeConcurrentTest, ConcurrentAllOps) { // range put is not supported for non-extent keys - std::vector< std::string > input_ops = {"put:20", "remove:20", "range_put:20", "range_remove:20", "query:20"}; + std::vector< std::string > input_ops = {"put:19", "remove:14", "range_put:20", "range_remove:2", "query:10"}; if (SISL_OPTIONS.count("operation_list")) { input_ops = SISL_OPTIONS["operation_list"].as< std::vector< std::string > >(); } diff --git a/src/tests/test_scripts/btree_test.py b/src/tests/test_scripts/btree_test.py index dc6390fb6..55d257955 100755 --- a/src/tests/test_scripts/btree_test.py +++ b/src/tests/test_scripts/btree_test.py @@ -17,11 +17,11 @@ dirpath = "./" op_list = "" log_mods = "" -threads = " --num_threads=10" -fibers = " --num_fibers=10" -preload_size = " --preload_size=16384" -num_entries = " --num_entries=65536" -num_iters = " --num_iters=10000000" +threads = " --num_threads=5" +fibers = " --num_fibers=5" +preload_size = " --preload_size=262144" +num_entries = " --num_entries=1048576" +num_iters = " --num_iters=100000000" run_time = " --run_time=36000" dev_list = "" @@ -76,7 +76,7 @@ def normal(): print("normal test started with (%s)" % btree_options) # " --operation_list=query:20 --operation_list=put:20 --operation_list=remove:20" - cmd_opts = " --gtest_filter=BtreeConcurrentTest/*.ConcurrentAllOps --gtest_break_on_failure " + btree_options + " "+log_mods + cmd_opts = " --gtest_filter=BtreeConcurrentTest/0.ConcurrentAllOps --gtest_break_on_failure " + btree_options + " "+log_mods subprocess.check_call(dirpath + "test_index_btree " + cmd_opts, stderr=subprocess.STDOUT, shell=True) print("normal test completed")