]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
bluestore/allocator: Aging test for bluestore allocators.
authorAdam Kupczyk <akupczyk@redhat.com>
Fri, 15 Jun 2018 09:33:59 +0000 (11:33 +0200)
committerAdam Kupczyk <akupczyk@redhat.com>
Wed, 4 Sep 2019 08:49:26 +0000 (10:49 +0200)
Tests performance and fragmentation for data accumulation
and storage capacity extension scenario.

Signed-off-by: Adam Kupczyk <akupczyk@redhat.com>
src/test/objectstore/Allocator_aging_fragmentation.cc [new file with mode: 0755]
src/test/objectstore/CMakeLists.txt

diff --git a/src/test/objectstore/Allocator_aging_fragmentation.cc b/src/test/objectstore/Allocator_aging_fragmentation.cc
new file mode 100755 (executable)
index 0000000..ee20c31
--- /dev/null
@@ -0,0 +1,423 @@
+// -*- 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 <iostream>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <boost/random/triangle_distribution.hpp>
+
+#include "common/ceph_mutex.h"
+#include "common/Cond.h"
+#include "common/errno.h"
+#include "include/stringify.h"
+#include "include/Context.h"
+#include "os/bluestore/Allocator.h"
+
+#include <boost/random/uniform_int.hpp>
+
+typedef boost::mt11213b gen_type;
+
+#include "common/debug.h"
+#define dout_context g_ceph_context
+#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<Scenario> 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}
+};
+
+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<std::string> {
+protected:
+  boost::scoped_ptr<AllocTracker> at;
+  gen_type rng;
+public:
+  boost::scoped_ptr<Allocator> 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<uint32_t()> 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<uint32_t()> size_generator, double leak_factor = 0);
+  void do_free(uint64_t low_mark);
+  uint32_t free_random();
+
+  static void TearDownTestCase();
+};
+
+struct test_result {
+  uint64_t tests_cnt = 0;
+  double fragmented_percent = 0;
+  double fragments_count = 0;
+  double time = 0;
+};
+
+std::map<std::string, test_result> 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<bluestore_pextent_t> 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;
+  }
+};
+
+
+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(g_ceph_context, 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<uint64_t> 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<uint32_t()> size_generator, double leak_factor) {
+  assert (leak_factor >= 0);
+  assert (leak_factor < 1);
+  uint32_t leak_level = leak_factor * std::numeric_limits<uint32_t>::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<tmp.size(); i++) {
+       if (uint32_t(rng()) < leak_level) {
+         free_random();
+       }
+      }
+    }
+  }
+}
+
+void AllocTest::do_free(uint64_t low_mark) {
+  while (level > low_mark)
+  {
+    if (free_random() == 0)
+      break;
+  }
+}
+
+void AllocTest::doAgingTest(
+    std::function<uint32_t()> 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(isp2(alloc_unit));
+  g_ceph_context->_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;
+}
+
+void AllocTest::TearDownTestCase() {
+  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 <<
+        " 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<double> 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);
+
+    auto size_generator = [&]() -> uint32_t {
+      static uint32_t object_size = 0;
+      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<uint64_t> release_set;
+      for (auto& t: tmp) {
+       release_set.insert(t.offset, t.length);
+      }
+      alloc->release(release_set);
+    } else {
+      interval_set<uint64_t> 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_CASE_P(
+  Allocator,
+  AllocTest,
+  ::testing::Values("stupid", "bitmap"));
+
index 747211f800c18caff224a8304da0f8f947e09b09..273bb9cf4fb52fc2468bacf5d0d70b1f103faee1 100644 (file)
@@ -92,7 +92,6 @@ if(WITH_BLUESTORE)
   add_ceph_unittest(unittest_alloc)
   target_link_libraries(unittest_alloc os global)
 
-
   add_executable(unittest_alloc_bench
     Allocator_bench.cc
     $<TARGET_OBJECTS:unit-main>
@@ -106,6 +105,17 @@ if(WITH_BLUESTORE)
   add_ceph_unittest(unittest_fastbmap_allocator)
   target_link_libraries(unittest_fastbmap_allocator os global)
 
+  set_target_properties(unittest_fastbmap_allocator PROPERTIES COMPILE_FLAGS
+  "${UNITTEST_CXX_FLAGS}")
+
+  add_executable(unittest_alloc_aging
+    Allocator_aging_fragmentation.cc
+    $<TARGET_OBJECTS:unit-main>
+    )
+  add_ceph_unittest(unittest_alloc_aging)
+  target_link_libraries(unittest_alloc_aging os global)
+
+  # unittest_bluefs
   add_executable(unittest_bluefs
     test_bluefs.cc
     )