From b3fa1fc96a1c6edec42ea9a058c5435bdc32df8b Mon Sep 17 00:00:00 2001 From: Michal Mielewczyk Date: Mon, 14 Oct 2024 13:55:36 +0200 Subject: [PATCH 1/4] Reset repart flag during refreshing request status The flag isn't reset before retraversation so it might be true even if cache line was reparted in the meantime Signed-off-by: Michal Mielewczyk --- src/engine/engine_common.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/engine/engine_common.c b/src/engine/engine_common.c index e25eb4f7..9a24828b 100644 --- a/src/engine/engine_common.c +++ b/src/engine/engine_common.c @@ -228,6 +228,12 @@ void ocf_engine_lookup(struct ocf_request *req) ocf_engine_lookup_map_entry(cache, entry, core_id, core_line); + /* + * The flag could have been set on the previous lookup, but + * cache line might be remapped in the meantime. + */ + entry->re_part = false; + if (entry->status != LOOKUP_HIT) { /* There is miss then lookup for next map entry */ OCF_DEBUG_PARAM(cache, "Miss, core line = %llu", @@ -267,6 +273,11 @@ int ocf_engine_check(struct ocf_request *req) core_line <= req->core_line_last; core_line++, i++) { struct ocf_map_info *entry = &(req->map[i]); + /* + * The flag could have been set on the previous traverse, but + * cache line might be remapped in the meantime. + */ + entry->re_part = false; if (entry->status == LOOKUP_MISS) { continue; From 6bfd2122d2db3bdeb27dc0643cef8693b51a54a5 Mon Sep 17 00:00:00 2001 From: Michal Mielewczyk Date: Mon, 14 Oct 2024 14:27:41 +0200 Subject: [PATCH 2/4] Repart all misclassified cache lines Remapped cache lines my require repartitioning as well Signed-off-by: Michal Mielewczyk --- src/utils/utils_user_part.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/utils/utils_user_part.c b/src/utils/utils_user_part.c index 6cd5a402..4ac35513 100644 --- a/src/utils/utils_user_part.c +++ b/src/utils/utils_user_part.c @@ -1,5 +1,6 @@ /* * Copyright(c) 2012-2021 Intel Corporation + * Copyright(c) 2024 Huawei Technologies * SPDX-License-Identifier: BSD-3-Clause */ @@ -107,16 +108,6 @@ void ocf_user_part_move(struct ocf_request *req) continue; } - /* Moving cachelines to another partition is needed only - * for those already mapped before this request and remapped - * cachelines are assigned to target partition during eviction. - * So only hit cachelines are interesting. - */ - if (entry->status != LOOKUP_HIT) { - /* No HIT */ - continue; - } - line = entry->coll_idx; id_old = ocf_metadata_get_partition_id(cache, line); id_new = req->part_id; @@ -124,6 +115,13 @@ void ocf_user_part_move(struct ocf_request *req) ENV_BUG_ON(id_old >= OCF_USER_IO_CLASS_MAX || id_new >= OCF_USER_IO_CLASS_MAX); + if (unlikely(entry->status == LOOKUP_MISS)) { + ocf_cache_log(cache, log_err, "Attempt to remap " + "an unmapped cache line from ioclass " + "%hu to ioclass %hu\n", id_old, id_new); + ENV_BUG(); + } + if (id_old == id_new) { /* Partition of the request and cache line is the same, * no need to change partition From a18bee3d23dddefe6658ce9f1521b47f6908e28d Mon Sep 17 00:00:00 2001 From: Michal Mielewczyk Date: Mon, 21 Oct 2024 19:32:30 +0200 Subject: [PATCH 3/4] pyocf: ioclass statas api Signed-off-by: Michal Mielewczyk --- tests/functional/pyocf/types/cache.py | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/functional/pyocf/types/cache.py b/tests/functional/pyocf/types/cache.py index 4c6dd943..298bc975 100644 --- a/tests/functional/pyocf/types/cache.py +++ b/tests/functional/pyocf/types/cache.py @@ -931,6 +931,28 @@ def get_stats(self): "errors": struct_to_dict(errors), } + def get_ioclass_stats(self, io_class_id): + usage = UsageStats() + req = RequestsStats() + block = BlocksStats() + + self.read_lock() + + status = self.owner.lib.ocf_stats_collect_part_cache( + self.cache_handle, io_class_id, byref(usage), byref(req), byref(block) + ) + + self.read_unlock() + + if status: + raise OcfError(f"Failed to retrieve ioclass {io_class_id} stats", status) + + return { + "usage": struct_to_dict(usage), + "block": struct_to_dict(block), + "req": struct_to_dict(req), + } + def reset_stats(self): self.owner.lib.ocf_core_stats_initialize_all(self.cache_handle) @@ -1056,6 +1078,14 @@ def get_by_name(cache_name, owner=None): c_void_p, ] lib.ocf_stats_collect_cache.restype = c_int +lib.ocf_stats_collect_part_cache.argtypes = [ + c_void_p, + c_uint16, + c_void_p, + c_void_p, + c_void_p, +] +lib.ocf_stats_collect_part_cache.restype = c_int lib.ocf_cache_get_info.argtypes = [c_void_p, c_void_p] lib.ocf_cache_get_info.restype = c_int lib.ocf_mngt_cache_cleaning_set_param.argtypes = [ From 91ad6476e2fd5f944f8c6f4e5a793fc62742d9b6 Mon Sep 17 00:00:00 2001 From: Michal Mielewczyk Date: Mon, 14 Oct 2024 17:58:08 +0200 Subject: [PATCH 4/4] pyocf: Cache lines reclassification test Signed-off-by: Michal Mielewczyk --- .../tests/eviction/test_eviction.py | 213 +++++++++++++++++- 1 file changed, 210 insertions(+), 3 deletions(-) diff --git a/tests/functional/tests/eviction/test_eviction.py b/tests/functional/tests/eviction/test_eviction.py index 6b34d892..690dcd8f 100644 --- a/tests/functional/tests/eviction/test_eviction.py +++ b/tests/functional/tests/eviction/test_eviction.py @@ -108,6 +108,204 @@ def test_write_size_greater_than_cache(pyocf_ctx, mode: CacheMode, cls: CacheLin ) +@pytest.mark.parametrize("io_dir", IoDir) +@pytest.mark.parametrize( + "cls", [CacheLineSize.LINE_4KiB, CacheLineSize.LINE_16KiB, CacheLineSize.LINE_64KiB] +) +@pytest.mark.parametrize("cache_mode", [CacheMode.WT, CacheMode.WB]) +def test_eviction_priority_1(pyocf_ctx, cls: CacheLineSize, cache_mode: CacheMode, io_dir: IoDir): + """Verify if data of higher priority is not evicted by low priority data""" + cache_device = RamVolume(Size.from_MiB(50)) + core_device = RamVolume(Size.from_MiB(200)) + cache = Cache.start_on_device(cache_device, cache_mode=cache_mode, cache_line_size=cls) + core = Core.using_device(core_device) + cache.add_core(core) + vol = CoreVolume(core) + + high_prio_ioclass = 1 + low_prio_ioclass = 2 + + cache.configure_partition( + part_id=high_prio_ioclass, + name="high_prio", + max_size=100, + priority=1, + ) + cache.configure_partition( + part_id=low_prio_ioclass, + name="low_prio", + max_size=100, + priority=2, + ) + + def get_ioclass_occupancy(cache, ioclass_id): + return cache.get_ioclass_stats(ioclass_id)["usage"]["occupancy"]["value"] + + cache.set_seq_cut_off_policy(SeqCutOffPolicy.NEVER) + + cache_size_4k = cache.get_stats()["conf"]["size"].blocks_4k + cache_line_size_4k = Size(cls).blocks_4k + + data = Data(4096) + + # Populate cache with high priority data + for i in range(cache_size_4k): + send_io(vol, data, i * 4096, high_prio_ioclass, io_dir) + + high_prio_ioclass_occupancy = get_ioclass_occupancy(cache, high_prio_ioclass) + + assert isclose( + high_prio_ioclass_occupancy, cache_size_4k, abs_tol=cache_line_size_4k + ), "High priority data should occupy the whole cache" + + # Write data of lower priority + for i in range(cache_size_4k, 2 * cache_size_4k): + send_io(vol, data, i * 4096, low_prio_ioclass, io_dir) + + high_prio_ioclass_occupancy = get_ioclass_occupancy(cache, high_prio_ioclass) + low_prio_ioclass_occupancy = get_ioclass_occupancy(cache, low_prio_ioclass) + + assert isclose( + high_prio_ioclass_occupancy, cache_size_4k, abs_tol=cache_line_size_4k + ), "High priority data shouldn't be evicted" + + assert low_prio_ioclass_occupancy == 0 + + +@pytest.mark.parametrize( + ("cache_mode", "io_dir"), + [ + (CacheMode.WB, IoDir.READ), + (CacheMode.WT, IoDir.WRITE), + (CacheMode.WT, IoDir.READ), + ], +) +@pytest.mark.parametrize("cls", [CacheLineSize.LINE_16KiB, CacheLineSize.LINE_64KiB]) +def test_eviction_priority_2(pyocf_ctx, cls: CacheLineSize, cache_mode: CacheMode, io_dir: IoDir): + """Verify if data of low priority gets evicted by high priority data""" + cache_device = RamVolume(Size.from_MiB(50)) + core_device = RamVolume(Size.from_MiB(200)) + cache = Cache.start_on_device(cache_device, cache_mode=cache_mode, cache_line_size=cls) + core = Core.using_device(core_device) + cache.add_core(core) + vol = CoreVolume(core) + + high_prio_ioclass = 1 + low_prio_ioclass = 2 + + cache.configure_partition( + part_id=high_prio_ioclass, + name="high_prio", + max_size=100, + priority=1, + ) + cache.configure_partition( + part_id=low_prio_ioclass, + name="low_prio", + max_size=100, + priority=2, + ) + + def get_ioclass_occupancy(cache, ioclass_id): + return cache.get_ioclass_stats(ioclass_id)["usage"]["occupancy"]["value"] + + cache.set_seq_cut_off_policy(SeqCutOffPolicy.NEVER) + + cache_size_4k = cache.get_stats()["conf"]["size"].blocks_4k + cache_line_size_4k = Size(cls).blocks_4k + + data = Data(4096) + + # Populate cache with low priority data + for i in range(cache_size_4k): + send_io(vol, data, i * 4096, low_prio_ioclass, io_dir) + + low_prio_ioclass_occupancy = get_ioclass_occupancy(cache, low_prio_ioclass) + + assert isclose( + low_prio_ioclass_occupancy, cache_size_4k, abs_tol=cache_line_size_4k + ), "Low priority data should occupy the whole cache" + + # Write data of higher priority + for i in range(cache_size_4k, 2 * cache_size_4k): + send_io(vol, data, i * 4096, high_prio_ioclass, io_dir) + + high_prio_ioclass_occupancy = get_ioclass_occupancy(cache, high_prio_ioclass) + low_prio_ioclass_occupancy = get_ioclass_occupancy(cache, low_prio_ioclass) + + assert low_prio_ioclass_occupancy == 0, "Low priority data should be evicted from cache" + + assert isclose( + high_prio_ioclass_occupancy, cache_size_4k, abs_tol=cache_line_size_4k + ), "High priority data should occupy the whole cache" + + +@pytest.mark.parametrize("io_dir", IoDir) +@pytest.mark.parametrize("cls", [CacheLineSize.LINE_16KiB, CacheLineSize.LINE_64KiB]) +@pytest.mark.parametrize("cache_mode", [CacheMode.WT, CacheMode.WB]) +def test_eviction_freelist(pyocf_ctx, cls: CacheLineSize, cache_mode: CacheMode, io_dir: IoDir): + """Verify that no eviction from low priority ioclass occurs if free cachelines are avaliable""" + cache_device = RamVolume(Size.from_MiB(50)) + core_device = RamVolume(Size.from_MiB(200)) + cache = Cache.start_on_device(cache_device, cache_mode=cache_mode, cache_line_size=cls) + core = Core.using_device(core_device) + cache.add_core(core) + vol = CoreVolume(core) + + high_prio_ioclass = 1 + low_prio_ioclasses = list(range(2, 33)) + + cache.configure_partition( + part_id=high_prio_ioclass, + name="high_prio", + max_size=100, + priority=1, + ) + for low_prio_ioclass in low_prio_ioclasses: + cache.configure_partition( + part_id=low_prio_ioclass, + name=f"low_prio_{low_prio_ioclass}", + max_size=100, + priority=low_prio_ioclass * 5, + ) + + def get_ioclass_occupancy(cache, ioclass_id): + return cache.get_ioclass_stats(ioclass_id)["usage"]["occupancy"]["value"] + + cache.set_seq_cut_off_policy(SeqCutOffPolicy.NEVER) + + cache_size_4k = cache.get_stats()["conf"]["size"].blocks_4k + cache_line_size_4k = Size(cls).blocks_4k + + cache_lines_written = 5 + data = Data(4096 * cache_line_size_4k) + expected_occpancy_4k = (cache_lines_written * data.size) / 4096 + + for i, ioclass in enumerate([high_prio_ioclass] + low_prio_ioclasses): + for j in range(cache_lines_written): + addr = (cache_lines_written * i + j) * data.size + send_io(vol, data, addr, ioclass, io_dir) + assert ( + get_ioclass_occupancy(cache, ioclass) == expected_occpancy_4k + ), f"Doesn't match for ioclass {ioclass}" + + for ioclass in [high_prio_ioclass] + low_prio_ioclasses: + assert ( + get_ioclass_occupancy(cache, ioclass) == expected_occpancy_4k + ), f"Doesn't match for ioclass {ioclass}" + + while cache.get_stats()["usage"]["free"]["value"] > 0: + addr += data.size + send_io(vol, data, addr, high_prio_ioclass, io_dir) + + assert cache.get_stats()["usage"]["occupancy"]["value"] == cache_size_4k + + for ioclass in low_prio_ioclasses: + assert ( + get_ioclass_occupancy(cache, ioclass) == expected_occpancy_4k + ), f"Doesn't match for ioclass {ioclass}" + + @pytest.mark.parametrize("cls", CacheLineSize) def test_evict_overflown_pinned(pyocf_ctx, cls: CacheLineSize): """ Verify if overflown pinned ioclass is evicted """ @@ -169,14 +367,23 @@ def test_evict_overflown_pinned(pyocf_ctx, cls: CacheLineSize): cache.get_partition_info(part_id=pinned_ioclass_id)["_curr_size"], cls ) assert isclose( - part_current_size.blocks_4k, ceil(cache_size.blocks_4k * 0.1), abs_tol=Size(cls).blocks_4k, + part_current_size.blocks_4k, + ceil(cache_size.blocks_4k * 0.1), + abs_tol=Size(cls).blocks_4k, ), "Overflown part has not been evicted" -def send_io(vol: CoreVolume, data: Data, addr: int = 0, target_ioclass: int = 0): +def send_io( + vol: CoreVolume, data: Data, addr: int = 0, target_ioclass: int = 0, io_dir: IoDir = IoDir.WRITE +): vol.open() io = vol.new_io( - vol.parent.get_default_queue(), addr, data.size, IoDir.WRITE, target_ioclass, 0, + vol.parent.get_default_queue(), + addr, + data.size, + io_dir, + target_ioclass, + 0, ) io.set_data(data)