]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
os/bluestore: Unit tests for punch_hole_2
authorAdam Kupczyk <akupczyk@ibm.com>
Tue, 14 Nov 2023 16:46:32 +0000 (16:46 +0000)
committerAdam Kupczyk <akupczyk@ibm.com>
Wed, 7 Aug 2024 10:55:45 +0000 (10:55 +0000)
Comprehensive tests for punch_hole_2.
New formulation of punch_hole_2 makes it very easy to
create patterns and inspect results.

Signed-off-by: Adam Kupczyk <akupczyk@ibm.com>
src/os/bluestore/BlueStore.h
src/test/objectstore/test_bluestore_types.cc

index e96547da9903c7629fb2c9ba6be3712fe5ce159c..efedf90f6fa30ef12b04a338a149a2fc69a092da 100644 (file)
@@ -1836,6 +1836,14 @@ public:
       values[STATFS_COMPRESSED_ALLOCATED] = st.data_compressed_allocated;
       return *this;
     }
+    bool operator==(const volatile_statfs& rhs) const {
+      return
+      values[STATFS_ALLOCATED] == rhs.values[STATFS_ALLOCATED] &&
+      values[STATFS_STORED] == rhs.values[STATFS_STORED] &&
+      values[STATFS_COMPRESSED_ORIGINAL] == rhs.values[STATFS_COMPRESSED_ORIGINAL] &&
+      values[STATFS_COMPRESSED] == rhs.values[STATFS_COMPRESSED] &&
+      values[STATFS_COMPRESSED_ALLOCATED] == rhs.values[STATFS_COMPRESSED_ALLOCATED];
+    }
     bool is_empty() {
       return values[STATFS_ALLOCATED] == 0 &&
        values[STATFS_STORED] == 0 &&
@@ -3532,6 +3540,18 @@ public:
     CephContext* cct, const std::string &path,
     const bluestore_bdev_label_t& label, uint64_t disk_position = 0);
 
+  void debug_punch_hole_2(
+    CollectionRef& c,
+    OnodeRef& o,
+    uint32_t offset,
+    uint32_t length,
+    PExtentVector& released,
+    std::vector<BlobRef>& pruned_blobs,
+    std::set<SharedBlobRef>& shared_changed,
+    volatile_statfs& statfs_delta) {
+      _punch_hole_2(c.get(), o, offset, length, released,
+        pruned_blobs, shared_changed, statfs_delta);
+    }
   inline void log_latency(const char* name,
     int idx,
     const ceph::timespan& lat,
index b0a06fe80d4f9e0d5b138a5d8037425f526e4014..535b2fb7c32490259f533234bd44a87f5a14aec9 100644 (file)
@@ -1233,6 +1233,339 @@ TEST(ExtentMap, compress_extent_map) {
   ASSERT_EQ(6u, em.extent_map.size());
 }
 
+class BlueStoreFixture :
+  virtual public ::testing::Test,
+  virtual public ::testing::WithParamInterface<std::vector<int>>
+ {
+public:
+  BlueStore* store;
+  BlueStore::OnodeCacheShard *oc;
+  BlueStore::BufferCacheShard *bc;
+  BlueStore::CollectionRef coll;
+  uint32_t au_size = 0;
+  uint32_t csum_order = 12;
+  BlueStore::OnodeRef onode;
+
+  explicit BlueStoreFixture() {}
+  void Init(uint32_t _au_size) {
+    au_size = _au_size;
+    store = new BlueStore(g_ceph_context, "", au_size);
+    oc = BlueStore::OnodeCacheShard::create(g_ceph_context, "lru", NULL);
+    bc = BlueStore::BufferCacheShard::create(g_ceph_context, "lru", NULL);
+    coll = ceph::make_ref<BlueStore::Collection>(store, oc, bc, coll_t());
+  }
+  void SetUp() override {
+    std::vector param = GetParam();
+    Init(param[0]);
+    onode = new BlueStore::Onode(coll.get(), ghobject_t(), "");
+  }
+  void TearDown() override {
+    onode.reset(nullptr);
+    coll.reset(nullptr);
+    delete bc;
+    delete oc;
+    delete store;
+  }
+};
+
+class PunchHoleFixture : public BlueStoreFixture
+{
+  public:
+  struct logical_range_t {
+    uint32_t offset = 0;
+    uint32_t length = 0;
+    uint32_t compressed = 0;
+  };
+  struct punch_range_t {
+    uint32_t offset = 0;
+    uint32_t length = 0;
+  };
+
+  interval_set<uint64_t> disk_allocated;
+  std::set<BlueStore::BlobRef> blobs_created;
+  std::set<BlueStore::SharedBlobRef> blobs_shared_created;
+  BlueStore::volatile_statfs statfs;
+
+  interval_set<uint64_t> disk_to_free;
+  std::set<BlueStore::BlobRef> blobs_to_free;
+  std::set<BlueStore::SharedBlobRef> blobs_shared_to_free;
+  BlueStore::volatile_statfs statfs_to_free;
+
+  uint32_t allocate_block = 10; //let's not start from 0
+  uint32_t allocate_offset = 0;
+
+  uint32_t align_nom;
+  uint32_t align_denom;
+  uint32_t compr_nom = 0;
+  uint32_t compr_denom = 0;
+  uint32_t compr_low = 0;
+  uint32_t compr_high = 0;
+  uint32_t shared_nom = 0;
+  uint32_t shared_denom = 0;
+  // random maybe aligned
+  uint32_t rma(
+    uint32_t low,
+    uint32_t high = 0) {
+      if (high == 0) {
+        high = low;
+        low = 0;
+      }
+      if (low == high) {
+        return low;
+      }
+      if (rand() % align_denom < align_nom) {
+        if (a2align(high) > a2roundup(low)) {
+          uint32_t v = rand() % (a2align(high) - a2roundup(low));
+          return a2align(v) + a2roundup(low);
+        }
+      }
+      return rand() % (high - low) + low;
+    }
+
+  void SetUp() override {
+    std::vector param = GetParam();
+    BlueStoreFixture::SetUp(); //uses param[0]
+    align_nom = param[1];
+    align_denom = param[2];
+    if (param.size() > 7) {
+      ceph_assert(param.size() >= 11);
+      compr_nom = param[7];
+      compr_denom = param[8];
+      compr_low = param[9];
+      compr_high = param[10];
+    }
+    if (param.size() > 11) {
+      ceph_assert(param.size() >= 13);
+      shared_nom = param[11];
+      shared_denom = param[12];
+    }
+  }
+  void clear() {
+    disk_allocated.clear();
+    blobs_created.clear();
+    blobs_shared_created.clear();
+    statfs.reset();
+    disk_to_free.clear();
+    blobs_to_free.clear();
+    blobs_shared_to_free.clear();
+    statfs_to_free.reset();
+    allocate_block = 10;
+    allocate_offset = 0;
+  }
+
+  void append(interval_set<uint64_t>& d, const PExtentVector& vec) {
+    for (auto v: vec) {
+      if (v.length > 0)
+        d.insert(v.offset, v.length);
+    }
+  }
+  interval_set<uint64_t> to_iset(const PExtentVector& vec) {
+    interval_set<uint64_t> set;
+    for (auto& v : vec) {
+      set.insert(v.offset, v.length);
+    }
+    return set;
+  }
+  std::set<BlueStore::BlobRef> to_set(const std::vector<BlueStore::BlobRef>& vec) {
+    std::set<BlueStore::BlobRef> set;
+    for (auto& b : vec) {
+      ceph_assert(!set.contains(b));
+      set.insert(b);
+    }
+    return set;
+  }
+  PExtentVector allocate(uint32_t size) {
+    if (rand()% 6 < 5) {
+      return allocate_continue(size);
+    }
+    ++allocate_block;
+    allocate_offset = 0;
+    PExtentVector v;
+    if (size > 0)
+      v.emplace_back((uint64_t)allocate_block << 32 | allocate_offset, size);
+    allocate_offset += size;
+    return v;
+  }
+  PExtentVector allocate_continue(uint32_t size) {
+    PExtentVector v;
+    if (size > 0)
+      v.emplace_back((uint64_t)allocate_block << 32 | allocate_offset, size);
+    allocate_offset += size;
+    return v;
+  }
+  uint32_t a2roundup(uint32_t x) {
+    return p2roundup(x, au_size);
+  }
+  uint32_t a2align(uint32_t x) {
+    return p2align(x, au_size);
+  }
+  uint32_t a2phase(uint32_t x) {
+    return p2phase(x, au_size);
+  }
+
+// Fills onode data on specific range.
+// Simulates blob-like operation.
+// Specifies which part of the data will be punched in future.
+  void populate(logical_range_t blob_like, punch_range_t will_punch)
+  {
+    if (compr_denom > 0 && rand() % compr_denom < compr_nom) {
+      blob_like.compressed = blob_like.length *
+        (rand() % (compr_high - compr_low) + compr_low);
+      populate_compressed(blob_like, will_punch);
+      return;
+    }
+    uint32_t al_start = a2align(blob_like.offset);
+    uint32_t al_hole_begin = a2roundup(will_punch.offset);
+    uint32_t al_hole_end = a2align(will_punch.offset + will_punch.length);
+    uint32_t al_end = a2roundup(blob_like.offset + blob_like.length);
+    if (will_punch.length == 0) {
+      // no punch, no hole
+      al_hole_begin = al_end;
+      al_hole_end = al_end;
+    } else {
+      if (blob_like.offset == will_punch.offset) {
+        al_hole_begin = al_start;
+      }
+      if (will_punch.offset + will_punch.length == blob_like.offset + blob_like.length) {
+        al_hole_end = al_end;
+      }
+      if (al_hole_end < al_hole_begin) {
+        al_hole_begin = al_end;
+        al_hole_end = al_end;
+      }
+    }
+    PExtentVector disk;
+    PExtentVector d_a = allocate(al_hole_begin - al_start);
+    PExtentVector d_b = allocate_continue(al_hole_end - al_hole_begin);
+    PExtentVector d_c = allocate_continue(al_end - al_hole_end);
+
+    bool blob_remains = al_start != al_hole_begin || al_hole_end != al_end;
+    BlueStore::BlobRef b(new BlueStore::Blob);
+    coll->open_shared_blob(0, b);
+    blobs_created.insert(b);
+    if (!blob_remains) {
+      blobs_to_free.insert(b);
+    }
+    append(disk_allocated, d_a);
+    append(disk_allocated, d_b);
+    append(disk_allocated, d_c);
+
+    disk.insert(disk.end(), d_a.begin(), d_a.end());
+    disk.insert(disk.end(), d_b.begin(), d_b.end());
+    disk.insert(disk.end(), d_c.begin(), d_c.end());
+
+    uint32_t blob_length = al_end - al_start;
+    bluestore_blob_t &bb = b->dirty_blob();
+    bb.init_csum(Checksummer::CSUM_CRC32C, csum_order, blob_length);
+    ceph_assert(p2phase(blob_length, au_size) == 0);
+    uint32_t num_aus = blob_length / au_size;
+    for (size_t i = 0; i < num_aus; ++i) {
+      bb.set_csum_item(i, 0);
+    }
+    bb.allocated(0, blob_length, disk);
+    BlueStore::Extent *ext = new BlueStore::Extent(
+      blob_like.offset, a2phase(blob_like.offset), blob_like.length, b);
+    onode->extent_map.extent_map.insert(*ext);
+    b->get_ref(coll.get(), a2phase(blob_like.offset), blob_like.length);
+    bb.mark_used(a2phase(blob_like.offset), blob_like.length);
+
+    //when shared is triggered, select how much is will be shared
+    bool do_shared = shared_denom !=0 && rand() % shared_denom < shared_nom;
+    uint32_t hole_range = al_hole_end - al_hole_begin;
+    if (do_shared && hole_range > 0) {
+      uint32_t x = (rand() % shared_denom < shared_nom)
+        ? 0 : a2align(rand() % hole_range);
+      uint32_t y = (rand() % shared_denom < shared_nom)
+        ? hole_range : hole_range - a2roundup(rand() % (hole_range - x));
+      if (x == y) {
+        x = 0;
+        y = hole_range;
+      }
+      coll->make_blob_shared(rand(), b);
+      BlueStore::BlobRef cb = new BlueStore::Blob();
+      b->dup(*cb);
+      ceph_assert(d_b.size() == 1);
+      ceph_assert(d_b[0].length == hole_range);
+      PExtentVector d_b_non_shared;
+      if (x != 0) {
+        d_b_non_shared.emplace_back(d_b[0].offset, x);
+      }
+      if (y != hole_range) {
+        d_b_non_shared.emplace_back(d_b[0].offset + y, hole_range - y);
+      }
+      append(disk_to_free, d_b_non_shared);
+      if (x < y) {
+        cb->shared_blob->get_ref(d_b[0].offset + x, y - x);
+        statfs_to_free.allocated() += y - x;
+      }
+    } else {
+      append(disk_to_free, d_b);
+    }
+    statfs_to_free.stored() -= will_punch.length;
+    statfs_to_free.allocated() -= al_hole_end - al_hole_begin;
+  }
+
+  void populate_compressed(logical_range_t blob_like, punch_range_t will_punch)
+  {
+    //compressed are never aligning data
+    ceph_assert(blob_like.compressed > 0);
+    uint32_t al_size = a2roundup(blob_like.compressed);
+    bool blob_remains =
+      will_punch.offset > blob_like.offset ||
+      will_punch.offset + will_punch.length < blob_like.offset + blob_like.length;
+    PExtentVector disk = allocate(al_size);
+
+    BlueStore::BlobRef b(new BlueStore::Blob);
+    coll->open_shared_blob(0, b);
+    blobs_created.insert(b);
+    if (!blob_remains) {
+      blobs_to_free.insert(b);
+    }
+    append(disk_allocated, disk);
+
+    uint32_t blob_length = al_size;
+    bluestore_blob_t &bb = b->dirty_blob();
+    bb.set_compressed(blob_like.length, blob_like.compressed);
+    bb.init_csum(Checksummer::CSUM_CRC32C, csum_order, blob_length);
+    ceph_assert(p2phase(blob_length, au_size) == 0);
+    uint32_t num_aus = blob_length / au_size;
+    for (size_t i = 0; i < num_aus; ++i) {
+      bb.set_csum_item(i, 0);
+    }
+    bb.allocated(0, blob_length, disk);
+    BlueStore::Extent *ext = new BlueStore::Extent(
+      blob_like.offset, 0, blob_like.length, b);
+    onode->extent_map.extent_map.insert(*ext);
+    b->get_ref(coll.get(), 0, blob_like.length);
+
+    //when shared is triggered, it is all or nothing
+    bool do_shared = shared_denom !=0 && rand() % shared_denom < shared_nom;
+    bool create_additional_ref = false;
+    if (do_shared) {
+      create_additional_ref = rand() % 2 == 0;
+      coll->make_blob_shared(rand(), b);
+      BlueStore::BlobRef cb = new BlueStore::Blob();
+      b->dup(*cb);
+      ceph_assert(disk.size() == 1);
+      ceph_assert(disk[0].length == al_size);
+      if (create_additional_ref) {
+        cb->shared_blob->get_ref(disk[0].offset, al_size);
+      }
+    }
+    statfs_to_free.stored() -= will_punch.length;
+    statfs_to_free.compressed_original() -= will_punch.length;
+    if (!blob_remains) {
+      statfs_to_free.compressed() -= blob_like.compressed;
+      if (!create_additional_ref) {
+        statfs_to_free.allocated() -= al_size;
+        statfs_to_free.compressed_allocated() -= al_size;
+        append(disk_to_free, disk);
+      }
+    }
+  }
+};
+
+
 class ExtentMapFixture : virtual public ::testing::Test {
 
 public:
@@ -1598,7 +1931,171 @@ TEST_F(ExtentMapFixture, petri) {
   }
 }
 
-TEST(ExtentMap, dup_extent_map) {
+TEST_P(PunchHoleFixture, selftest)
+{
+  populate({1000, 32000}, {11000, 9000});
+  PExtentVector released;
+  std::vector<BlueStore::BlobRef> pruned_blobs;
+  std::set<BlueStore::SharedBlobRef> shared_changed;
+  BlueStore::volatile_statfs statfs_delta;
+  store->debug_punch_hole_2(coll, onode, 1000, 32000,
+    released, pruned_blobs, shared_changed, statfs_delta);
+  clear();
+}
+
+TEST_P(PunchHoleFixture, all)
+{
+  for (int i = 0; i < 1000; i++) {
+    onode = new BlueStore::Onode(coll.get(), ghobject_t(), "");
+
+    uint32_t start = (rand() % 30000) + 1;
+    uint32_t end = start + (rand() % 100000) + 1;
+    uint32_t blob_length;
+    uint32_t pos = start;
+    while (pos < end) {
+      blob_length = (rand() % 30000) + 1;
+      if (pos + blob_length > end) {
+        blob_length = end - pos;
+      }
+      populate({pos, blob_length}, {pos, blob_length});
+      pos = pos + blob_length;
+    }
+    PExtentVector released;
+    std::vector<BlueStore::BlobRef> pruned_blobs;
+    std::set<BlueStore::SharedBlobRef> shared_changed;
+    BlueStore::volatile_statfs statfs_delta;
+    store->debug_punch_hole_2(coll, onode, start, end - start,
+                         released, pruned_blobs, shared_changed, statfs_delta);
+    EXPECT_EQ(to_iset(released), disk_to_free);
+    EXPECT_EQ(to_set(pruned_blobs), blobs_to_free);
+    EXPECT_EQ(statfs_delta, statfs_to_free);
+    clear();
+  }
+}
+
+TEST_P(PunchHoleFixture, some)
+{
+  for (int i = 0; i < 1000; i++) {
+    onode = new BlueStore::Onode(coll.get(), ghobject_t(), "");
+
+    uint32_t start = (rand() % 30000) + 1;
+    uint32_t hole_start = start + (rand() % 30000);
+    uint32_t hole_end = hole_start + (rand() % 100000) + 1;
+    uint32_t end = hole_end  + (rand() % 30000);
+    uint32_t blob_length;
+    uint32_t pos = start;
+    while (pos < end) {
+      blob_length = (rand() % 30000) + 1;
+      if (pos + blob_length > end) {
+        blob_length = end - pos;
+      }
+      uint32_t a = hole_start;
+      uint32_t b = hole_end;
+      if (a < pos) a = pos;
+      if (b > pos + blob_length) b = pos + blob_length;
+      if (a < b)
+        populate({pos, blob_length}, {a, b - a});
+      else
+        populate({pos, blob_length}, {});
+      pos = pos + blob_length;
+    }
+    PExtentVector released;
+    std::vector<BlueStore::BlobRef> pruned_blobs;
+    std::set<BlueStore::SharedBlobRef> shared_changed;
+    BlueStore::volatile_statfs statfs_delta;
+    store->debug_punch_hole_2(coll, onode, hole_start, hole_end - hole_start,
+                         released, pruned_blobs, shared_changed, statfs_delta);
+    EXPECT_EQ(to_iset(released), disk_to_free);
+    EXPECT_EQ(to_set(pruned_blobs), blobs_to_free);
+    EXPECT_EQ(statfs_delta, statfs_to_free);
+    clear();
+  }
+}
+
+TEST_P(PunchHoleFixture, multipunch)
+{
+  std::vector param = GetParam();
+  ceph_assert(param.size() >= 7);
+  //param[0] for au_size
+  uint32_t object_size_low = param[3];
+  uint32_t object_size_high = param[4];
+  uint32_t blob_size_low = param[5];
+  uint32_t blob_size_high = param[6];
+
+  for (int i = 0; i < 1000; i++) {
+    onode = new BlueStore::Onode(coll.get(), ghobject_t(), "");
+    uint32_t step = object_size_high - object_size_low / 4;
+    uint32_t start =      rma(30000);
+    uint32_t hole_start = rma(start, start + step);
+    uint32_t hole_end;
+    do {
+      hole_end = rma(hole_start, hole_start + step * 2 + 1);
+    } while (hole_end == hole_start);
+    uint32_t end =        rma(hole_end, hole_end + step);
+    uint32_t blob_length;
+    uint32_t pos = start;
+    while (pos < end) {
+      blob_length = rma(blob_size_low, blob_size_high);
+      if (pos + blob_length > end) {
+        blob_length = end - pos;
+      }
+      uint32_t a = hole_start;
+      uint32_t b = hole_end;
+      if (a < pos) a = pos;
+      if (b > pos + blob_length) b = pos + blob_length;
+      if (a < b)
+        populate({pos, blob_length}, {a, b - a});
+      else
+        populate({pos, blob_length}, {});
+      pos = pos + blob_length;
+    }
+    PExtentVector released;
+    std::vector<BlueStore::BlobRef> pruned_blobs;
+    std::set<BlueStore::SharedBlobRef> shared_changed;
+    BlueStore::volatile_statfs statfs_delta;
+
+    for (int j = 0; j < 10; j++) {
+      uint32_t s = rand() % ((hole_end - hole_start) / 5) + 1;
+      uint32_t p = rand() % (hole_end - hole_start - s) + hole_start;
+      store->debug_punch_hole_2(
+        coll, onode, p, s,
+        released, pruned_blobs, shared_changed, statfs_delta);
+    }
+    // and mandatory full clear at the end
+    store->debug_punch_hole_2(
+      coll, onode, hole_start, hole_end - hole_start,
+      released, pruned_blobs, shared_changed, statfs_delta);
+    EXPECT_EQ(to_iset(released), disk_to_free);
+    EXPECT_EQ(to_set(pruned_blobs), blobs_to_free);
+    EXPECT_EQ(statfs_delta, statfs_to_free);
+    clear();
+  }
+}
+
+//0 = au_size, 1/2 = %is_aligned, 3-4 = min-max object
+//5-6 = min-max blob, 7/8 = %is_compressed, 9-10 = min-max %compressed
+INSTANTIATE_TEST_SUITE_P(
+  BlueStore,
+  PunchHoleFixture,
+  ::testing::Values(
+    std::vector<int>({4096, 2, 7, 10000, 100000, 20000, 40000}),
+    std::vector<int>({4096, 11, 13, 30000, 300000, 65536, 65536}),
+    std::vector<int>({8192, 3, 4, 20000, 150000, 10000, 25000}),
+    std::vector<int>({32768, 3, 4, 40000, 400000, 65536, 65536}),
+    std::vector<int>({4096, 2, 7, 10000, 100000, 20000, 40000, 1, 2, 10, 50}),
+    std::vector<int>({4096, 11, 13, 30000, 300000, 65536, 65536, 2, 3, 20, 70}),
+    std::vector<int>({8192, 3, 4, 20000, 150000, 10000, 25000, 2, 3 ,10, 50}),
+    std::vector<int>({32768, 3, 4, 40000, 400000, 65536, 65536, 1, 2, 20, 70}),
+    std::vector<int>({4096, 2, 7, 10000, 100000, 20000, 40000, 1, 2, 10, 50, 2, 3}),
+    std::vector<int>({4096, 11, 13, 30000, 300000, 65536, 65536, 2, 3, 20, 70, 1, 5}),
+    std::vector<int>({8192, 3, 4, 20000, 150000, 10000, 25000, 2, 3 ,10, 50, 5, 7}),
+    std::vector<int>({32768, 3, 4, 40000, 400000, 65536, 65536, 1, 2, 20, 70, 1, 3})
+    )
+);
+
+
+TEST(ExtentMap, dup_extent_map)
+{
   BlueStore store(g_ceph_context, "", 4096);
   BlueStore::OnodeCacheShard *oc =
       BlueStore::OnodeCacheShard::create(g_ceph_context, "lru", NULL);