From e6918187568dbd01842d8d1d2c808ce16a894239 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 21 Apr 2024 13:54:28 +0200 Subject: Adding upstream version 18.2.2. Signed-off-by: Daniel Baumann --- .../objectstore/Allocator_aging_fragmentation.cc | 463 +++++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100755 src/test/objectstore/Allocator_aging_fragmentation.cc (limited to 'src/test/objectstore/Allocator_aging_fragmentation.cc') diff --git a/src/test/objectstore/Allocator_aging_fragmentation.cc b/src/test/objectstore/Allocator_aging_fragmentation.cc new file mode 100755 index 000000000..220f8841b --- /dev/null +++ b/src/test/objectstore/Allocator_aging_fragmentation.cc @@ -0,0 +1,463 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Bitmap allocator fragmentation benchmarks. + * Author: Adam Kupczyk, akupczyk@redhat.com + */ +#include +#include +#include +#include +#include + +#include "common/ceph_mutex.h" +#include "common/Cond.h" +#include "common/errno.h" +#include "global/global_init.h" +#include "include/stringify.h" +#include "include/Context.h" +#include "os/bluestore/Allocator.h" + +#include + +typedef boost::mt11213b gen_type; + +#include "common/debug.h" +#define dout_context cct +#define dout_subsys ceph_subsys_ + +struct Scenario { + uint64_t capacity; + uint64_t alloc_unit; + double high_mark; + double low_mark; + double leakness; + uint32_t repeats; +}; + +std::vector scenarios{ + Scenario{512, 65536, 0.8, 0.6, 0.1, 3}, + Scenario{512, 65536, 0.9, 0.7, 0.0, 3}, + Scenario{512, 65536, 0.9, 0.7, 0.1, 3}, + Scenario{512, 65536, 0.8, 0.6, 0.5, 3}, + Scenario{512, 65536, 0.9, 0.7, 0.5, 3}, + Scenario{1024, 65536, 0.8, 0.6, 0.1, 3}, + Scenario{1024, 65536, 0.9, 0.7, 0.0, 3}, + Scenario{1024, 65536, 0.9, 0.7, 0.1, 3}, + Scenario{1024*2, 65536, 0.8, 0.6, 0.3, 3}, + Scenario{1024*2, 65536, 0.9, 0.7, 0.0, 3}, + Scenario{1024*2, 65536, 0.9, 0.7, 0.3, 3}, + Scenario{512, 65536/16, 0.8, 0.6, 0.1, 3}, + Scenario{512, 65536/16, 0.9, 0.7, 0.0, 3}, + Scenario{512, 65536/16, 0.9, 0.7, 0.1, 3}, + Scenario{512, 65536/16, 0.8, 0.6, 0.5, 3}, + Scenario{512, 65536/16, 0.9, 0.7, 0.5, 3}, + Scenario{1024, 65536/16, 0.8, 0.6, 0.1, 3}, + Scenario{1024, 65536/16, 0.9, 0.7, 0.0, 3}, + Scenario{1024, 65536/16, 0.9, 0.7, 0.1, 3}, + Scenario{1024*2, 65536/16, 0.8, 0.6, 0.3, 3}, + Scenario{1024*2, 65536/16, 0.9, 0.7, 0.0, 3}, + Scenario{1024*2, 65536/16, 0.9, 0.7, 0.3, 3} +}; + +void PrintTo(const Scenario& s, ::std::ostream* os) +{ + *os << "(capacity=" << s.capacity; + *os << "G, alloc_unit=" << s.alloc_unit; + *os << ", high_mark=" << s.high_mark; + *os << ", low_mark=" << s.low_mark; + *os << ", leakness=" << s.leakness; + *os << ", repeats=" << s.repeats << ")"; +} +bool verbose = getenv("VERBOSE") != nullptr; + +class AllocTracker; +class AllocTest : public ::testing::TestWithParam { +protected: + boost::scoped_ptr at; + gen_type rng; + static boost::intrusive_ptr cct; + +public: + boost::scoped_ptr alloc; + AllocTest(): alloc(nullptr) {} + void init_alloc(const std::string& alloc_name, int64_t size, uint64_t min_alloc_size); + void init_close(); + void doAgingTest(std::function size_generator, + const std::string& alloc_name, uint64_t capacity, uint32_t alloc_unit, + uint64_t high_mark, uint64_t low_mark, uint32_t iterations, double leak_factor = 0); + + uint64_t capacity; + uint32_t alloc_unit; + + uint64_t level = 0; + uint64_t allocs = 0; + uint64_t fragmented = 0; + uint64_t fragments = 0; + uint64_t total_fragments = 0; + + void do_fill(uint64_t high_mark, std::function size_generator, double leak_factor = 0); + void do_free(uint64_t low_mark); + uint32_t free_random(); + + void TearDown() final; + static void SetUpTestSuite(); + static void TearDownTestSuite(); +}; + +struct test_result { + uint64_t tests_cnt = 0; + double fragmented_percent = 0; + double fragments_count = 0; + double time = 0; + double frag_score = 0; +}; + +std::map results_per_allocator; + +const uint64_t _1m = 1024 * 1024; +const uint64_t _1G = 1024 * 1024 * 1024; + +const uint64_t _2m = 2 * 1024 * 1024; + +class AllocTracker +{ + std::vector allocations; + uint64_t size = 0; + +public: + bool push(uint64_t offs, uint32_t len) + { + assert(len != 0); + if (size + 1 > allocations.size()) + allocations.resize(size + 100); + allocations[size++] = bluestore_pextent_t(offs, len); + return true; + } + + bool pop_random(gen_type& rng, uint64_t* offs, uint32_t* len, + uint32_t max_len = 0) + { + if (size == 0) + return false; + uint64_t pos = rng() % size; + *len = allocations[pos].length; + *offs = allocations[pos].offset; + + if (max_len && *len > max_len) { + allocations[pos].length = *len - max_len; + allocations[pos].offset = *offs + max_len; + *len = max_len; + } else { + allocations[pos] = allocations[size-1]; + --size; + } + return true; + } +}; + +boost::intrusive_ptr AllocTest::cct; + +void AllocTest::init_alloc(const std::string& allocator_name, int64_t size, uint64_t min_alloc_size) { + this->capacity = size; + this->alloc_unit = min_alloc_size; + rng.seed(0); + alloc.reset(Allocator::create(cct.get(), allocator_name, size, + min_alloc_size)); + at.reset(new AllocTracker()); +} + +void AllocTest::init_close() { + alloc.reset(0); + at.reset(nullptr); +} + +uint32_t AllocTest::free_random() { + uint64_t o = 0; + uint32_t l = 0; + interval_set release_set; + if (!at->pop_random(rng, &o, &l)) { + //empty? + return 0; + } + release_set.insert(o, l); + alloc->release(release_set); + level -= l; + return l; +} + + +void AllocTest::do_fill(uint64_t high_mark, std::function size_generator, double leak_factor) { + assert (leak_factor >= 0); + assert (leak_factor < 1); + uint32_t leak_level = leak_factor * std::numeric_limits::max(); + PExtentVector tmp; + while (level < high_mark) + { + uint32_t want = size_generator(); + tmp.clear(); + auto r = alloc->allocate(want, alloc_unit, 0, 0, &tmp); + if (r < want) { + break; + } + level += r; + for(auto a : tmp) { + bool full = !at->push(a.offset, a.length); + EXPECT_EQ(full, false); + } + allocs++; + if (tmp.size() > 1) { + fragmented ++; + total_fragments += r; + fragments += tmp.size(); + } + if (leak_level > 0) { + for (size_t i=0; i low_mark) + { + if (free_random() == 0) + break; + } +} + +void AllocTest::doAgingTest( + std::function size_generator, + const std::string& allocator_name, + uint64_t capacity, uint32_t alloc_unit, + uint64_t high_mark, uint64_t low_mark, uint32_t iterations, double leak_factor) +{ + assert(std::has_single_bit(alloc_unit)); + cct->_conf->bdev_block_size = alloc_unit; + PExtentVector allocated, tmp; + init_alloc(allocator_name, capacity, alloc_unit); + alloc->init_add_free(0, capacity); + + utime_t start = ceph_clock_now(); + level = 0; + allocs = 0; + fragmented = 0; + fragments = 0; + total_fragments = 0; + if (verbose) std::cout << "INITIAL FILL" << std::endl; + do_fill(high_mark, size_generator, leak_factor); //initial fill with data + if (verbose) std::cout << " fragmented allocs=" << 100.0 * fragmented / allocs << "%" << + " #frags=" << ( fragmented != 0 ? double(fragments) / fragmented : 0 )<< + " time=" << (ceph_clock_now() - start) * 1000 << "ms" << std::endl; + + for (uint32_t i=0; i < iterations; i++) + { + allocs = 0; + fragmented = 0; + fragments = 0; + total_fragments = 0; + + uint64_t level_previous = level; + start = ceph_clock_now(); + if (verbose) std::cout << "ADDING CAPACITY " << i + 1 << std::endl; + do_free(low_mark); //simulates adding new capacity to cluster + if (verbose) std::cout << " level change: " << + double(level_previous) / capacity * 100 << "% -> " << + double(level) / capacity * 100 << "% time=" << + (ceph_clock_now() - start) * 1000 << "ms" << std::endl; + + start = ceph_clock_now(); + if (verbose) std::cout << "APPENDING " << i + 1 << std::endl; + do_fill(high_mark, size_generator, leak_factor); //only creating elements + if (verbose) std::cout << " fragmented allocs=" << 100.0 * fragmented / allocs << "%" << + " #frags=" << ( fragmented != 0 ? double(fragments) / fragmented : 0 ) << + " time=" << (ceph_clock_now() - start) * 1000 << "ms" << std::endl; + } + double frag_score = alloc->get_fragmentation_score(); + do_free(0); + double free_frag_score = alloc->get_fragmentation_score(); + ASSERT_EQ(alloc->get_free(), capacity); + + std::cout << " fragmented allocs=" << 100.0 * fragmented / allocs << "%" << + " #frags=" << ( fragmented != 0 ? double(fragments) / fragmented : 0 ) << + " time=" << (ceph_clock_now() - start) * 1000 << "ms" << + " frag.score=" << frag_score << " after free frag.score=" << free_frag_score << std::endl; + + uint64_t sum = 0; + uint64_t cnt = 0; + auto list_free = [&](size_t off, size_t len) { + cnt++; + sum+=len; + }; + alloc->dump(list_free); + ASSERT_EQ(sum, capacity); + if (verbose) + std::cout << "free chunks sum=" << sum << " free chunks count=" << cnt << std::endl; + + //adding to totals + test_result &r = results_per_allocator[allocator_name]; + r.tests_cnt ++; + r.fragmented_percent += 100.0 * fragmented / allocs; + r.fragments_count += ( fragmented != 0 ? double(fragments) / fragmented : 2 ); + r.time += ceph_clock_now() - start; + r.frag_score += frag_score; +} + +void AllocTest::SetUpTestSuite() +{ + vector args; + cct = global_init(NULL, args, + CEPH_ENTITY_TYPE_CLIENT, + CODE_ENVIRONMENT_UTILITY, + CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); + common_init_finish(cct.get()); +} + +void AllocTest::TearDown() +{ + at.reset(); + alloc.reset(); +} + +void AllocTest::TearDownTestSuite() +{ + cct.reset(); + + std::cout << "Summary: " << std::endl; + for (auto& r: results_per_allocator) { + std::cout << r.first << + " fragmented allocs=" << r.second.fragmented_percent / r.second.tests_cnt << "%" << + " #frags=" << r.second.fragments_count / r.second.tests_cnt << + " free_score=" << r.second.frag_score / r.second.tests_cnt << + " time=" << r.second.time * 1000 << "ms" << std::endl; + } +} + + +TEST_P(AllocTest, test_alloc_triangle_0_8M_16M) +{ + std::string allocator_name = GetParam(); + boost::triangle_distribution D(1, (8 * 1024 * 1024) , (16 * 1024 * 1024) ); + for (auto& s:scenarios) { + std::cout << "Allocator: " << allocator_name << ", "; + PrintTo(s, &std::cout); + std::cout << std::endl; + + auto size_generator = [&]() -> uint32_t { + return (uint32_t(D(rng)) + s.alloc_unit) & ~(s.alloc_unit - 1); + }; + + doAgingTest(size_generator, allocator_name, s.capacity * _1G, s.alloc_unit, + s.high_mark * s.capacity * _1G, + s.low_mark * s.capacity * _1G, + s.repeats, s.leakness); + } +} + +TEST_P(AllocTest, test_alloc_8M_and_64K) +{ + std::string allocator_name = GetParam(); + constexpr uint32_t max_chunk_size = 8*1024*1024; + constexpr uint32_t min_chunk_size = 64*1024; + for (auto& s:scenarios) { + std::cout << "Allocator: " << allocator_name << ", "; + PrintTo(s, &std::cout); + std::cout << std::endl; + boost::uniform_int<> D(0, 1); + + auto size_generator = [&]() -> uint32_t { + if (D(rng) == 0) + return max_chunk_size; + else + return min_chunk_size; + }; + + doAgingTest(size_generator, allocator_name, s.capacity * _1G, s.alloc_unit, + s.high_mark * s.capacity * _1G, + s.low_mark * s.capacity * _1G, + s.repeats, s.leakness); + } +} + +TEST_P(AllocTest, test_alloc_fragmentation_max_chunk_8M) +{ + std::string allocator_name = GetParam(); + constexpr uint32_t max_object_size = 150*1000*1000; + constexpr uint32_t max_chunk_size = 8*1024*1024; + for (auto& s:scenarios) { + std::cout << "Allocator: " << allocator_name << ", "; + PrintTo(s, &std::cout); + std::cout << std::endl; + boost::uniform_int<> D(1, max_object_size / s.alloc_unit); + + uint32_t object_size = 0; + + auto size_generator = [&]() -> uint32_t { + uint32_t c; + if (object_size == 0) + object_size = (uint32_t(D(rng))* s.alloc_unit); + if (object_size > max_chunk_size) + c = max_chunk_size; + else + c = object_size; + object_size -= c; + return c; + }; + + doAgingTest(size_generator, allocator_name, s.capacity * _1G, s.alloc_unit, + s.high_mark * s.capacity * _1G, + s.low_mark * s.capacity * _1G, + s.repeats, s.leakness); + } +} + +TEST_P(AllocTest, test_bonus_empty_fragmented) +{ + uint64_t capacity = uint64_t(512) * 1024 * 1024 * 1024; //512 G + uint64_t alloc_unit = 64 * 1024; + std::string allocator_name = GetParam(); + std::cout << "Allocator: " << allocator_name << std::endl; + init_alloc(allocator_name, capacity, alloc_unit); + alloc->init_add_free(0, capacity); + PExtentVector tmp; + for (size_t i = 0; i < capacity / (1024 * 1024); i++) { + tmp.clear(); + uint32_t want = 1024 * 1024; + int r = alloc->allocate(want, alloc_unit, 0, 0, &tmp); + ASSERT_EQ(r, want); + if (tmp.size() > 1) { + interval_set release_set; + for (auto& t: tmp) { + release_set.insert(t.offset, t.length); + } + alloc->release(release_set); + } else { + interval_set release_set; + uint64_t offset = tmp[0].offset; + uint64_t length = tmp[0].length; + + release_set.insert(offset + alloc_unit, length - 3 * alloc_unit); + alloc->release(release_set); + release_set.clear(); + + release_set.insert(offset , alloc_unit); + alloc->release(release_set); + release_set.clear(); + + release_set.insert(offset + length - 2 * alloc_unit, 2 * alloc_unit); + alloc->release(release_set); + release_set.clear(); + } + } + double frag_score = alloc->get_fragmentation_score(); + ASSERT_EQ(alloc->get_free(), capacity); + std::cout << " empty storage frag.score=" << frag_score << std::endl; +} + +INSTANTIATE_TEST_SUITE_P( + Allocator, + AllocTest, + ::testing::Values("stupid", "bitmap", "avl", "btree")); -- cgit v1.2.3