From: Adam Kupczyk Date: Tue, 14 Nov 2023 16:46:32 +0000 (+0000) Subject: os/bluestore: Unit tests for punch_hole_2 X-Git-Tag: testing/wip-vshankar-testing-20240814.051758-debug~10^2~28 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=00b98788028c5e5f536267153e5dfa41a8a84595;p=ceph-ci.git os/bluestore: Unit tests for punch_hole_2 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 --- diff --git a/src/os/bluestore/BlueStore.h b/src/os/bluestore/BlueStore.h index e96547da990..efedf90f6fa 100644 --- a/src/os/bluestore/BlueStore.h +++ b/src/os/bluestore/BlueStore.h @@ -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& pruned_blobs, + std::set& 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, diff --git a/src/test/objectstore/test_bluestore_types.cc b/src/test/objectstore/test_bluestore_types.cc index b0a06fe80d4..535b2fb7c32 100644 --- a/src/test/objectstore/test_bluestore_types.cc +++ b/src/test/objectstore/test_bluestore_types.cc @@ -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> + { +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(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 disk_allocated; + std::set blobs_created; + std::set blobs_shared_created; + BlueStore::volatile_statfs statfs; + + interval_set disk_to_free; + std::set blobs_to_free; + std::set 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& d, const PExtentVector& vec) { + for (auto v: vec) { + if (v.length > 0) + d.insert(v.offset, v.length); + } + } + interval_set to_iset(const PExtentVector& vec) { + interval_set set; + for (auto& v : vec) { + set.insert(v.offset, v.length); + } + return set; + } + std::set to_set(const std::vector& vec) { + std::set 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 pruned_blobs; + std::set 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 pruned_blobs; + std::set 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 pruned_blobs; + std::set 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 pruned_blobs; + std::set 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({4096, 2, 7, 10000, 100000, 20000, 40000}), + std::vector({4096, 11, 13, 30000, 300000, 65536, 65536}), + std::vector({8192, 3, 4, 20000, 150000, 10000, 25000}), + std::vector({32768, 3, 4, 40000, 400000, 65536, 65536}), + std::vector({4096, 2, 7, 10000, 100000, 20000, 40000, 1, 2, 10, 50}), + std::vector({4096, 11, 13, 30000, 300000, 65536, 65536, 2, 3, 20, 70}), + std::vector({8192, 3, 4, 20000, 150000, 10000, 25000, 2, 3 ,10, 50}), + std::vector({32768, 3, 4, 40000, 400000, 65536, 65536, 1, 2, 20, 70}), + std::vector({4096, 2, 7, 10000, 100000, 20000, 40000, 1, 2, 10, 50, 2, 3}), + std::vector({4096, 11, 13, 30000, 300000, 65536, 65536, 2, 3, 20, 70, 1, 5}), + std::vector({8192, 3, 4, 20000, 150000, 10000, 25000, 2, 3 ,10, 50, 5, 7}), + std::vector({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);