From 5538a5a95db3ed57ef8b9450a01635294916fa5e Mon Sep 17 00:00:00 2001 From: Adam Rutkowski Date: Tue, 2 Feb 2021 15:24:20 +0100 Subject: [PATCH 1/3] Only request evict size equal to request unmapped count Removing the logic for oportunistic partition overflow reduction by evicting more cachelines than actually required by the request being serviced. Signed-off-by: Adam Rutkowski --- src/engine/engine_common.c | 53 +- src/engine/engine_fast.c | 8 +- src/utils/utils_part.c | 79 --- src/utils/utils_part.h | 37 +- .../engine_common.c/prepare_clines_miss.c | 38 +- .../utils/utils_part.c/ocf_part_evict_size.c | 516 ------------------ 6 files changed, 67 insertions(+), 664 deletions(-) delete mode 100644 tests/unit/tests/utils/utils_part.c/ocf_part_evict_size.c diff --git a/src/engine/engine_common.c b/src/engine/engine_common.c index 0af7597..2632140 100644 --- a/src/engine/engine_common.c +++ b/src/engine/engine_common.c @@ -430,55 +430,50 @@ static inline int ocf_prepare_clines_miss(struct ocf_request *req, { int lock_status = -OCF_ERR_NO_LOCK; struct ocf_metadata_lock *metadata_lock = &req->cache->metadata.lock; - uint32_t clines_to_evict = 0; - int res; + + /* requests to disabled partitions go in pass-through */ + if (!ocf_part_is_enabled(&req->cache->user_parts[req->part_id])) { + ocf_req_set_mapping_error(req); + ocf_req_hash_unlock_rd(req); + return lock_status; + } + + if (!ocf_part_has_space(req)) { + ocf_req_hash_unlock_rd(req); + goto eviction; + } /* Mapping must be performed holding (at least) hash-bucket write lock */ ocf_req_hash_lock_upgrade(req); - /* Verify whether partition occupancy threshold is not reached yet or cache - * is not out of free cachelines */ - res = ocf_part_check_space(req, &clines_to_evict); - if (res == OCF_PART_IS_DISABLED) { - ocf_req_set_mapping_error(req); - ocf_req_hash_unlock_wr(req); - return lock_status; - } - - if (res == OCF_PART_HAS_SPACE) { - ocf_engine_map(req); - if (ocf_req_test_mapping_error(req)) { - goto eviction; - } + ocf_engine_map(req); + if (!ocf_req_test_mapping_error(req)) { lock_status = lock_clines(req, engine_cbs); if (lock_status < 0) { /* Mapping succeeded, but we failed to acquire cacheline lock. * Don't try to evict, just return error to caller */ ocf_req_set_mapping_error(req); } - ocf_req_hash_unlock_wr(req); return lock_status; } -eviction: ocf_req_hash_unlock_wr(req); + +eviction: ocf_metadata_start_exclusive_access(metadata_lock); - ocf_part_check_space(req, &clines_to_evict); + /* repeat traversation to pick up latest metadata status */ + ocf_engine_traverse(req); - if (space_managment_evict_do(req->cache, req, clines_to_evict) == - LOOKUP_MISS) { - ocf_req_set_mapping_error(req); - goto unlock; - } + if (!ocf_part_has_space(req)) + ocf_req_set_part_evict(req); + else + ocf_req_clear_part_evict(req); - if (!ocf_part_is_enabled(&req->cache->user_parts[req->part_id])) { - /* Partition is disabled but it had cachelines assigned. Now, that they - * are evicted, don't try to map cachelines - we don't want to insert - * new cachelines - the request should be submited in pass through mode - * instead */ + if (space_managment_evict_do(req->cache, req, + ocf_engine_unmapped_count(req)) == LOOKUP_MISS) { ocf_req_set_mapping_error(req); goto unlock; } diff --git a/src/engine/engine_fast.c b/src/engine/engine_fast.c index 23a828b..5a89e8c 100644 --- a/src/engine/engine_fast.c +++ b/src/engine/engine_fast.c @@ -108,7 +108,7 @@ int ocf_read_fast(struct ocf_request *req) { bool hit; int lock = OCF_LOCK_NOT_ACQUIRED; - bool part_has_space = false; + bool part_has_space; /* Get OCF request - increase reference counter */ ocf_req_get(req); @@ -126,8 +126,7 @@ int ocf_read_fast(struct ocf_request *req) hit = ocf_engine_is_hit(req); - if (ocf_part_check_space(req, NULL) == OCF_PART_HAS_SPACE) - part_has_space = true; + part_has_space = ocf_part_has_space(req); if (hit && part_has_space) { ocf_io_start(&req->ioi.io); @@ -197,8 +196,7 @@ int ocf_write_fast(struct ocf_request *req) mapped = ocf_engine_is_mapped(req); - if (ocf_part_check_space(req, NULL) == OCF_PART_HAS_SPACE) - part_has_space = true; + part_has_space = ocf_part_has_space(req); if (mapped && part_has_space) { ocf_io_start(&req->ioi.io); diff --git a/src/utils/utils_part.c b/src/utils/utils_part.c index 7ee28d8..3da853b 100644 --- a/src/utils/utils_part.c +++ b/src/utils/utils_part.c @@ -190,82 +190,3 @@ void ocf_part_set_valid(struct ocf_cache *cache, ocf_part_id_t id, } } } - -static inline uint32_t ocf_part_evict_size(struct ocf_request *req) -{ - uint32_t needed_cache_lines, part_available, cache_lines_to_evict; - uint32_t part_occupancy, part_occupancy_debt; - struct ocf_user_part *target_part = &req->cache->user_parts[req->part_id]; - uint32_t part_occupancy_limit = - ocf_part_get_max_size(req->cache, target_part); - - needed_cache_lines = ocf_engine_repart_count(req) + - ocf_engine_unmapped_count(req); - - part_occupancy = ocf_part_get_occupancy(target_part); - - if (part_occupancy_limit >= part_occupancy) { - part_available = part_occupancy_limit - part_occupancy; - part_occupancy_debt = 0; - } else { - /* Occupancy is greater than occupancy limit. Evict missing number of - * cachelines, but no more than single eviction limit */ - part_occupancy_debt = min((uint32_t)OCF_PENDING_EVICTION_LIMIT, - part_occupancy - part_occupancy_limit); - part_available = 0; - } - - if (ocf_freelist_num_free(req->cache->freelist) < - ocf_engine_unmapped_count(req)) { - /* Number of cachelines to insert greater than number of free - * cachelines */ - if (part_available >= needed_cache_lines) { - /* Cache is full, but target's part occupancy limit is not reached - */ - ocf_req_clear_part_evict(req); - cache_lines_to_evict = needed_cache_lines; - } else { - /* Cache is full and target part reached it's occupancy limit */ - ocf_req_set_part_evict(req); - cache_lines_to_evict = needed_cache_lines - part_available; - } - - } else if (part_available < needed_cache_lines) { - /* Enough of free cache lines, but partition reached it's occupancy - * limit */ - cache_lines_to_evict = needed_cache_lines - part_available; - ocf_req_set_part_evict(req); - - } else if (part_available >= needed_cache_lines) { - /* Enough free cachelines available and they can be assigned to target - * partition */ - cache_lines_to_evict = 0; - - } - - return cache_lines_to_evict + part_occupancy_debt; -} - -uint32_t ocf_part_check_space(struct ocf_request *req, uint32_t *to_evict) -{ - uint32_t ret = OCF_PART_IS_FULL; - uint32_t _to_evict; - struct ocf_user_part *target_part = &req->cache->user_parts[req->part_id]; - - if (!ocf_part_is_enabled(target_part) && - ocf_part_get_occupancy(target_part) == 0) { - /* If partition is disabled, but has assigned cachelines, eviction has - * to be triggered */ - return OCF_PART_IS_DISABLED; - } - - _to_evict = ocf_part_evict_size(req); - - if (_to_evict == 0) - ret = OCF_PART_HAS_SPACE; - - if (to_evict) - *to_evict = _to_evict; - - return ret; -} diff --git a/src/utils/utils_part.h b/src/utils/utils_part.h index c37a43d..bf40602 100644 --- a/src/utils/utils_part.h +++ b/src/utils/utils_part.h @@ -100,20 +100,29 @@ static inline bool ocf_part_is_enabled(struct ocf_user_part *part) return part->config->max_size != 0; } -#define OCF_PART_HAS_SPACE 0 -#define OCF_PART_IS_FULL 1 -#define OCF_PART_IS_DISABLED 2 -/** - * Check whether there is enough free cachelines to serve request. If partition - * occupancy limit is reached, `req->part_evict` is set to true. Otherwise - * flag is set to false and eviction from any partition should be triggered. - * - * @return - * OCF_PART_HAS_SPACE when cachelines alloted successfully - * OCF_PART_IS_FULL when need to evict some cachelines to serve request - * OCF_PART_IS_DISABLED when caching for particular partition is disabled - */ -uint32_t ocf_part_check_space(struct ocf_request *req, uint32_t *to_evict); +static inline uint32_t ocf_part_overflow_size(struct ocf_cache *cache, + struct ocf_user_part *part) +{ + uint32_t part_occupancy = ocf_part_get_occupancy(part); + uint32_t part_occupancy_limit = ocf_part_get_max_size(cache, part); + + if (part_occupancy > part_occupancy_limit) + return part_occupancy - part_occupancy_limit; + + return 0; +} + +static inline bool ocf_part_has_space(struct ocf_request *req) +{ + struct ocf_user_part *target_part = &req->cache->user_parts[req->part_id]; + uint64_t part_occupancy_limit = + ocf_part_get_max_size(req->cache, target_part); + uint64_t needed_cache_lines = ocf_engine_repart_count(req) + + ocf_engine_unmapped_count(req); + uint64_t part_occupancy = ocf_part_get_occupancy(target_part); + + return (part_occupancy + needed_cache_lines <= part_occupancy_limit); +} static inline ocf_cache_mode_t ocf_part_get_cache_mode(ocf_cache_t cache, ocf_part_id_t part_id) diff --git a/tests/unit/tests/engine/engine_common.c/prepare_clines_miss.c b/tests/unit/tests/engine/engine_common.c/prepare_clines_miss.c index 81fec82..5534f23 100644 --- a/tests/unit/tests/engine/engine_common.c/prepare_clines_miss.c +++ b/tests/unit/tests/engine/engine_common.c/prepare_clines_miss.c @@ -44,8 +44,7 @@ void __wrap_ocf_req_hash_unlock_wr(struct ocf_request *req) { } -uint32_t __wrap_ocf_part_check_space(struct ocf_request *req, - uint32_t *to_evict) +uint32_t __wrap_ocf_part_has_space(struct ocf_request *req) { return mock(); } @@ -98,7 +97,7 @@ static void ocf_prepare_clines_miss_test01(void **state) { struct ocf_request req = {}; print_test_description("Target part is disabled and empty\n"); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_DISABLED); + will_return(__wrap_ocf_part_is_enabled, false); expect_function_call(__wrap_ocf_req_set_mapping_error); assert_int_equal(ocf_prepare_clines_miss(&req, NULL), -OCF_ERR_NO_LOCK); } @@ -107,13 +106,8 @@ static void ocf_prepare_clines_miss_test02(void **state) { struct ocf_request req = {}; print_test_description("Target part is disabled but has cachelines assigned.\n"); - print_test_description("\tTrigger eviction and mark mapping error\n"); + print_test_description("\tMark mapping error\n"); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); - - expect_function_call(__wrap_space_managment_evict_do); - will_return(__wrap_space_managment_evict_do, LOOKUP_MAPPED); will_return(__wrap_ocf_part_is_enabled, false); expect_function_call(__wrap_ocf_req_set_mapping_error); @@ -126,12 +120,12 @@ static void ocf_prepare_clines_miss_test03(void **state) print_test_description("Target part is enabled but doesn't have enough space.\n"); print_test_description("\tEviction is ok and cachelines lock is acquired.\n"); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); + will_return(__wrap_ocf_part_is_enabled, true); + will_return(__wrap_ocf_part_has_space, false); + will_return(__wrap_ocf_part_has_space, false); expect_function_call(__wrap_space_managment_evict_do); will_return(__wrap_space_managment_evict_do, LOOKUP_MAPPED); - will_return(__wrap_ocf_part_is_enabled, true); expect_function_call(__wrap_ocf_engine_map); @@ -149,9 +143,10 @@ static void ocf_prepare_clines_miss_test04(void **state) print_test_description("Target part is enabled but doesn't have enough space.\n"); print_test_description("\tEviction failed\n"); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); + will_return(__wrap_ocf_part_is_enabled, true); + will_return(__wrap_ocf_part_has_space, false); + will_return(__wrap_ocf_part_has_space, false); expect_function_call(__wrap_space_managment_evict_do); will_return(__wrap_space_managment_evict_do, LOOKUP_MISS); expect_function_call(__wrap_ocf_req_set_mapping_error); @@ -165,8 +160,8 @@ static void ocf_prepare_clines_miss_test05(void **state) print_test_description("Target part is enabled but doesn't have enough space.\n"); print_test_description("Eviction is ok, but mapping failed.\n"); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); + will_return(__wrap_ocf_part_has_space, false); + will_return(__wrap_ocf_part_has_space, false); expect_function_call(__wrap_space_managment_evict_do); will_return(__wrap_space_managment_evict_do, LOOKUP_HIT); @@ -185,8 +180,8 @@ static void ocf_prepare_clines_miss_test06(void **state) print_test_description("Target part is enabled but doesn't have enough space.\n"); print_test_description("Eviction and mapping were ok, but failed to lock cachelines.\n"); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); + will_return(__wrap_ocf_part_has_space, false); + will_return(__wrap_ocf_part_has_space, false); expect_function_call(__wrap_space_managment_evict_do); will_return(__wrap_space_managment_evict_do, LOOKUP_HIT); @@ -210,8 +205,8 @@ static void ocf_prepare_clines_miss_test07(void **state) print_test_description("Target part is enabled but doesn't have enough space.\n"); print_test_description("Eviction and mapping were ok, lock not acquired.\n"); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); - will_return(__wrap_ocf_part_check_space, OCF_PART_IS_FULL); + will_return(__wrap_ocf_part_has_space, false); + will_return(__wrap_ocf_part_has_space, false); expect_function_call(__wrap_space_managment_evict_do); will_return(__wrap_space_managment_evict_do, LOOKUP_HIT); @@ -233,7 +228,8 @@ static void ocf_prepare_clines_miss_test08(void **state) print_test_description("Target part is enabled has enough space.\n"); print_test_description("\tMapping and cacheline lock are both ok\n"); - will_return(__wrap_ocf_part_check_space, OCF_PART_HAS_SPACE); + will_return(__wrap_ocf_part_is_enabled, true); + will_return(__wrap_ocf_part_has_space, true); expect_function_call(__wrap_ocf_engine_map); will_return(__wrap_ocf_req_test_mapping_error, false); diff --git a/tests/unit/tests/utils/utils_part.c/ocf_part_evict_size.c b/tests/unit/tests/utils/utils_part.c/ocf_part_evict_size.c deleted file mode 100644 index 2980372..0000000 --- a/tests/unit/tests/utils/utils_part.c/ocf_part_evict_size.c +++ /dev/null @@ -1,516 +0,0 @@ -/* - * src/utils/utils_part.c - * ocf_part_evict_size - * - * INSERT HERE LIST OF FUNCTIONS YOU WANT TO LEAVE - * ONE FUNCTION PER LINE - * - */ - -#undef static - -#undef inline - - -#include -#include -#include -#include -#include "print_desc.h" - -#include "ocf/ocf.h" -#include "../ocf_cache_priv.h" -#include "../ocf_request.h" -#include "../metadata/metadata.h" -#include "../engine/cache_engine.h" -#include "../eviction/ops.h" -#include "utils_part.h" - -#include "utils/utils_part.c/ocf_part_evict_size_generated_wraps.c" - -uint32_t __wrap_ocf_part_get_max_size(ocf_cache_t cache, - struct ocf_user_part *target_part) -{ - return mock(); -} - -uint32_t __wrap_ocf_engine_repart_count(struct ocf_request *req) -{ - return mock(); -} - -uint32_t __wrap_ocf_engine_unmapped_count(struct ocf_request *req) -{ - return mock(); -} - -uint32_t __wrap_ocf_part_get_occupancy(struct ocf_user_part *target_part) -{ - return mock(); -} - -ocf_cache_line_t __wrap_ocf_freelist_num_free(ocf_freelist_t freelist) -{ - return mock(); -} - -void __wrap_ocf_req_set_part_evict(struct ocf_request *req) -{ - function_called(); -} - -void __wrap_ocf_req_clear_part_evict(struct ocf_request *req) -{ - function_called(); -} - -static void ocf_part_evict_size_test01(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 0; - uint32_t cachelines_to_map = 320; - uint32_t part_occupied_cachelines = 512; - uint32_t freelist_size = 500; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - print_test_description("Enough free space available"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - // Enough free cachelines to map a whole request - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - assert_int_equal(ocf_part_evict_size(&req), 0); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test02(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 0; - uint32_t cachelines_to_map = 320; - uint32_t part_occupied_cachelines = 960; - uint32_t freelist_size = 500; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t available_cachelines = max_part_size - part_occupied_cachelines; - uint32_t cachelines_to_evict = cachelines_to_map - available_cachelines; - - print_test_description("Cache has enough free cachelines," - " but target partition must be evicted"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - expect_function_call(__wrap_ocf_req_set_part_evict); - - assert_int_equal(ocf_part_evict_size(&req), cachelines_to_evict); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test03(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 320; - uint32_t cachelines_to_map = 0; - uint32_t part_occupied_cachelines = 512; - uint32_t freelist_size = 0; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t cachelines_to_evict = 0; - - print_test_description("Only repart (no mapping). Freelist is empty but " - "space in a target part is availabe,"); - print_test_description("\tso no cachelines should be " - " evcited from cache"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - assert_int_equal(ocf_part_evict_size(&req), cachelines_to_evict); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test04(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 320; - uint32_t cachelines_to_map = 0; - uint32_t part_occupied_cachelines = 1100; - uint32_t freelist_size = 0; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t cachelines_debt = part_occupied_cachelines - max_part_size; - uint32_t cachelines_to_evict = cachelines_to_repart + cachelines_debt; - - print_test_description("Only repart (no mapping). Freelist is empty and no" - " space in target part is availabe."); - print_test_description("\tEvict only from target partition"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - expect_function_call(__wrap_ocf_req_set_part_evict); - - assert_int_equal(ocf_part_evict_size(&req), cachelines_to_evict); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test05(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 0; - uint32_t cachelines_to_map = 320; - uint32_t part_occupied_cachelines = 960; - uint32_t freelist_size = 0; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t available_cachelines = max_part_size - part_occupied_cachelines; - uint32_t cachelines_to_evict = cachelines_to_map - available_cachelines; - - print_test_description("Freelist is empty and no space in the target part " - "is available"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - expect_function_call(__wrap_ocf_req_set_part_evict); - - assert_int_equal(ocf_part_evict_size(&req), cachelines_to_evict); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test06(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 0; - uint32_t cachelines_to_map = 320; - uint32_t part_occupied_cachelines = 320; - uint32_t freelist_size = 0; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t available_cachelines = max_part_size - part_occupied_cachelines; - uint32_t cachelines_to_evict = cachelines_to_map; - - print_test_description("Freelist is empty but target part has enough space"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - expect_function_call(__wrap_ocf_req_clear_part_evict); - - assert_int_equal(ocf_part_evict_size(&req), cachelines_to_evict); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test07(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 0; - uint32_t cachelines_to_map = 320; - uint32_t part_occupied_cachelines = 1280; - uint32_t freelist_size = 0; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t debt_cachelines = part_occupied_cachelines - max_part_size; - uint32_t cachelines_to_evict = cachelines_to_map + debt_cachelines; - - print_test_description("Freelist is empty and part occupancy exceeded"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - expect_function_call(__wrap_ocf_req_set_part_evict); - - assert_int_equal(ocf_part_evict_size(&req), - (part_occupied_cachelines - max_part_size) + cachelines_to_map); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test08(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 320; - uint32_t cachelines_to_map = 0; - uint32_t part_occupied_cachelines = 1280; - uint32_t freelist_size = 0; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t debt_cachelines = part_occupied_cachelines - max_part_size; - uint32_t cachelines_to_evict = debt_cachelines + cachelines_to_repart; - - print_test_description("Target part occupancy limit is exceeded during " - "repart"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - expect_function_call(__wrap_ocf_req_set_part_evict); - - assert_int_equal(ocf_part_evict_size(&req), - (part_occupied_cachelines - max_part_size) + cachelines_to_repart); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test09(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 320; - uint32_t cachelines_to_map = 0; - uint32_t part_occupied_cachelines = 320; - uint32_t freelist_size = 0; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t cachelines_to_evict = 0; - - print_test_description("Repart while target part has enough of available " - "space"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - assert_int_equal(ocf_part_evict_size(&req), cachelines_to_evict); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test10(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 0; - uint32_t cachelines_to_map = 320; - uint32_t part_occupied_cachelines = 320; - uint32_t freelist_size = 320; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t cachelines_to_evict = 0; - - print_test_description("Enough of available cachelines in target part, " - "freelist has exactly required number of free cachelines"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - assert_int_equal(ocf_part_evict_size(&req), cachelines_to_evict); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test11(void **state) -{ - uint32_t max_part_size = 1024; - uint32_t cachelines_to_repart = 320; - uint32_t cachelines_to_map = 0; - uint32_t part_occupied_cachelines = 384; - uint32_t freelist_size = 0; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t cachelines_to_evict = 0; - - print_test_description("Number of cachelines to repart is equal to number " - "of cachelines available in the target partition"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - assert_int_equal(ocf_part_evict_size(&req), cachelines_to_evict); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test12(void **state) -{ - uint32_t max_part_size = 0; - uint32_t cachelines_to_repart = 0; - uint32_t cachelines_to_map = 320; - uint32_t part_occupied_cachelines = 384; - uint32_t freelist_size = 0; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t cachelines_to_evict = - part_occupied_cachelines + cachelines_to_map; - - print_test_description("Freelist IS empty. Max occupancy set to 0, but " - "some cachelines are still assigned to traget part - evict them"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - expect_function_call(__wrap_ocf_req_set_part_evict); - - assert_true(ocf_part_evict_size(&req) >= part_occupied_cachelines); - - test_free(req.cache); -} - -static void ocf_part_evict_size_test13(void **state) -{ - uint32_t max_part_size = 0; - uint32_t cachelines_to_repart = 0; - uint32_t cachelines_to_map = 320; - uint32_t part_occupied_cachelines = 384; - uint32_t freelist_size = 1024; - - struct ocf_request req; - req.cache = test_malloc(sizeof(struct ocf_cache)); - - uint32_t cachelines_to_evict = - part_occupied_cachelines + cachelines_to_map; - - print_test_description("Freelist IS NOT empty. Max occupancy set to 0, but" - " some cachelines are still assigned to traget part - evict them"); - - will_return(__wrap_ocf_part_get_max_size, max_part_size); - - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - will_return(__wrap_ocf_engine_repart_count, cachelines_to_repart); - - will_return(__wrap_ocf_part_get_occupancy, part_occupied_cachelines); - - will_return(__wrap_ocf_freelist_num_free, freelist_size); - will_return(__wrap_ocf_engine_unmapped_count, cachelines_to_map); - - expect_function_call(__wrap_ocf_req_set_part_evict); - - assert_true(ocf_part_evict_size(&req) >= part_occupied_cachelines); - - test_free(req.cache); -} - -int main(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test(ocf_part_evict_size_test01), - cmocka_unit_test(ocf_part_evict_size_test02), - cmocka_unit_test(ocf_part_evict_size_test03), - cmocka_unit_test(ocf_part_evict_size_test04), - cmocka_unit_test(ocf_part_evict_size_test05), - cmocka_unit_test(ocf_part_evict_size_test06), - cmocka_unit_test(ocf_part_evict_size_test07), - cmocka_unit_test(ocf_part_evict_size_test08), - cmocka_unit_test(ocf_part_evict_size_test09), - cmocka_unit_test(ocf_part_evict_size_test10), - cmocka_unit_test(ocf_part_evict_size_test11), - cmocka_unit_test(ocf_part_evict_size_test12), - cmocka_unit_test(ocf_part_evict_size_test13) - }; - - print_message("Unit test for ocf_part_evict_size\n"); - - return cmocka_run_group_tests(tests, NULL, NULL); -} From 746b32c47df5a1eec9026b9e7254f372ff0a7a79 Mon Sep 17 00:00:00 2001 From: Adam Rutkowski Date: Tue, 2 Feb 2021 15:57:37 +0100 Subject: [PATCH 2/3] Evict from overflown partitions first Overflown partitions now have precedence over others during eviction, regardless of IO class priorities. Signed-off-by: Adam Rutkowski --- src/eviction/eviction.c | 63 +++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/src/eviction/eviction.c b/src/eviction/eviction.c index a61e9df..f1da57a 100644 --- a/src/eviction/eviction.c +++ b/src/eviction/eviction.c @@ -60,53 +60,92 @@ static inline uint32_t ocf_evict_part_do(ocf_cache_t cache, target_part, to_evict); } -static inline uint32_t ocf_evict_do(ocf_cache_t cache, - ocf_queue_t io_queue, const uint32_t evict_cline_no, - struct ocf_user_part *target_part) +static inline uint32_t ocf_evict_partitions(ocf_cache_t cache, + ocf_queue_t io_queue, uint32_t evict_cline_no, + bool overflown_only, uint32_t max_priority) { uint32_t to_evict = 0, evicted = 0; struct ocf_user_part *part; ocf_part_id_t part_id; + unsigned overflow_size; /* For each partition from the lowest priority to highest one */ for_each_part(cache, part, part_id) { - if (!ocf_eviction_can_evict(cache)) goto out; /* * Check stop and continue conditions */ - if (target_part->config->priority > part->config->priority) { + if (max_priority > part->config->priority) { /* - * iterate partition have higher priority, do not evict + * iterate partition have higher priority, + * do not evict */ break; } if (!part->config->flags.eviction) { - /* It seams that no more partition for eviction */ + /* no more partitions available for viction + */ break; } - if (evicted >= evict_cline_no) { - /* Evicted requested number of cache line, stop */ - goto out; + + if (overflown_only) { + overflow_size = ocf_part_overflow_size(cache, part); + if (overflow_size == 0) + continue; } - to_evict = ocf_evict_calculate(cache, part, evict_cline_no, - true); + to_evict = ocf_evict_calculate(cache, part, + evict_cline_no - evicted, true); if (to_evict == 0) { /* No cache lines to evict for this partition */ continue; } + if (overflown_only) + to_evict = OCF_MIN(to_evict, overflow_size); + evicted += ocf_eviction_need_space(cache, io_queue, part, to_evict); + + if (evicted >= evict_cline_no) { + /* Evicted requested number of cache line, stop + */ + goto out; + } + } out: return evicted; } +static inline uint32_t ocf_evict_do(ocf_cache_t cache, + ocf_queue_t io_queue, uint32_t evict_cline_no, + struct ocf_user_part *target_part) +{ + uint32_t evicted; + + /* First attempt to evict overflown partitions in order to + * achieve configured maximum size. Ignoring partitions + * priority in this case, as overflown partitions should + * free its cachelines regardless of destination partition + * priority. */ + evicted = ocf_evict_partitions(cache, io_queue, evict_cline_no, + true, OCF_IO_CLASS_PRIO_HIGHEST); + if (evicted >= evict_cline_no) + return evicted; + /* Not enough cachelines in overflown partitions. Go through + * partitions with priority <= target partition and attempt + * to evict from those. */ + evict_cline_no -= evicted; + evicted += ocf_evict_partitions(cache, io_queue, evict_cline_no, + false, target_part->config->priority); + + return evicted; +} + int space_managment_evict_do(struct ocf_cache *cache, struct ocf_request *req, uint32_t evict_cline_no) { From 4276d65e5a890cf5b023fa51ce6fba21b0500ac1 Mon Sep 17 00:00:00 2001 From: Adam Rutkowski Date: Tue, 2 Feb 2021 17:15:09 +0100 Subject: [PATCH 3/3] unit tests for new eviction order Signed-off-by: Adam Rutkowski --- .../unit/tests/eviction/eviction.c/eviction.c | 291 ++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 tests/unit/tests/eviction/eviction.c/eviction.c diff --git a/tests/unit/tests/eviction/eviction.c/eviction.c b/tests/unit/tests/eviction/eviction.c/eviction.c new file mode 100644 index 0000000..fb3e51b --- /dev/null +++ b/tests/unit/tests/eviction/eviction.c/eviction.c @@ -0,0 +1,291 @@ +/* + * src/eviction/eviction.c + * ocf_evict_do + * + ocf_evict_partitions + * + */ + +#undef static + +#undef inline + + +#include +#include +#include +#include +#include "print_desc.h" + +#include "eviction.h" +#include "ops.h" +#include "../utils/utils_part.h" + +#include "eviction/eviction.c/eviction_generated_wraps.c" + +struct test_cache +{ + struct ocf_cache cache; + struct ocf_user_part_config part[OCF_IO_CLASS_MAX]; + struct ocf_user_part upart[OCF_IO_CLASS_MAX]; + uint32_t overflow[OCF_IO_CLASS_MAX]; + uint32_t evictable[OCF_IO_CLASS_MAX]; +}; + +bool __wrap_ocf_eviction_can_evict(ocf_cache_t cache) +{ + return true; +} + +uint32_t __wrap_ocf_part_overflow_size(struct ocf_cache *cache, + struct ocf_user_part *part) +{ + struct test_cache* tcache = cache; + + return tcache->overflow[part->id]; +} + +uint32_t __wrap_ocf_evict_calculate(ocf_cache_t cache, + struct ocf_user_part *part, uint32_t to_evict, bool roundup) +{ + struct test_cache* tcache = cache; + + return min(tcache->evictable[part->id], to_evict); +} + +uint32_t __wrap_ocf_eviction_need_space(struct ocf_cache *cache, + ocf_queue_t io_queue, struct ocf_user_part *part, + uint32_t clines) +{ + struct test_cache *tcache = (struct test_cache *)cache; + unsigned overflown_consumed = min(clines, tcache->overflow[part->id]); + + tcache->overflow[part->id] -= overflown_consumed; + tcache->evictable[part->id] -= clines; + + check_expected(part); + check_expected(clines); + function_called(); + return mock(); +} + +int __wrap_ocf_log_raw(ocf_logger_t logger, ocf_logger_lvl_t lvl, + const char *fmt, ...) +{ +} + +int __wrap_ocf_log_stack_trace_raw(ocf_logger_t logger) +{ + return 0; +} + +ocf_ctx_t __wrap_ocf_cache_get_ctx(ocf_cache_t cache) +{ + return NULL; +} + + +bool ocf_cache_is_device_attached(ocf_cache_t cache) +{ + return true; +} + + +/* FIXME: copy-pasted from OCF */ +int ocf_part_lst_cmp_valid(struct ocf_cache *cache, + struct ocf_lst_entry *e1, struct ocf_lst_entry *e2) +{ + struct ocf_user_part *p1 = container_of(e1, struct ocf_user_part, + lst_valid); + struct ocf_user_part *p2 = container_of(e2, struct ocf_user_part, + lst_valid); + size_t p1_size = ocf_cache_is_device_attached(cache) ? + p1->runtime->curr_size : 0; + size_t p2_size = ocf_cache_is_device_attached(cache) ? + p2->runtime->curr_size : 0; + + int v1 = p1->config->priority; + int v2 = p2->config->priority; + + /* + * If partition is invalid the priority depends on current size: + * 1. Partition is empty - move to the end of list + * 2. Partition is not empty - move to the beginning of the list. This + * partition will be evicted first + */ + + if (p1->config->priority == OCF_IO_CLASS_PRIO_PINNED) + p1->config->flags.eviction = false; + else + p1->config->flags.eviction = true; + + if (p2->config->priority == OCF_IO_CLASS_PRIO_PINNED) + p2->config->flags.eviction = false; + else + p2->config->flags.eviction = true; + + if (!p1->config->flags.valid) { + if (p1_size) { + v1 = SHRT_MAX; + p1->config->flags.eviction = true; + } else { + v1 = SHRT_MIN; + p1->config->flags.eviction = false; + } + } + + if (!p2->config->flags.valid) { + if (p2_size) { + v2 = SHRT_MAX; + p2->config->flags.eviction = true; + } else { + v2 = SHRT_MIN; + p2->config->flags.eviction = false; + } + } + + if (v1 == v2) { + v1 = p1 - cache->user_parts; + v2 = p2 - cache->user_parts; + } + + return v2 - v1; +} + +static struct ocf_lst_entry *_list_getter( + struct ocf_cache *cache, ocf_cache_line_t idx) +{ + struct test_cache* tcache = cache; + + return &tcache->upart[idx].lst_valid; +} + +static void init_part_list(struct test_cache *tcache) +{ + unsigned i; + + for (i = 0; i < OCF_IO_CLASS_MAX; i++) { + tcache->upart[i].id = i; + tcache->upart[i].config = &tcache->part[i]; + tcache->upart[i].config->priority = i+1; + tcache->upart[i].config->flags.eviction = 1; + } + + ocf_lst_init((ocf_cache_t)tcache, &tcache->cache.lst_part, OCF_IO_CLASS_MAX, + _list_getter, ocf_part_lst_cmp_valid); + for (i = 0; i < OCF_IO_CLASS_MAX; i++) { + ocf_lst_init_entry(&tcache->cache.lst_part, &tcache->upart[i].lst_valid); + ocf_lst_add_tail(&tcache->cache.lst_part, i); + } +} + +#define _expect_evict_call(tcache, part_id, req_count, ret_count) \ + do { \ + expect_value(__wrap_ocf_eviction_need_space, part, &tcache.upart[part_id]); \ + expect_value(__wrap_ocf_eviction_need_space, clines, req_count); \ + expect_function_call(__wrap_ocf_eviction_need_space); \ + will_return(__wrap_ocf_eviction_need_space, ret_count); \ + } while (false); + +static void ocf_evict_do_test01(void **state) +{ + struct test_cache tcache = {}; + unsigned evicted; + + print_test_description("one IO class, no overflow\n"); + + init_part_list(&tcache); + + tcache.evictable[10] = 100; + + _expect_evict_call(tcache, 10, 50, 50); + + evicted = ocf_evict_do((ocf_cache_t *)&tcache, NULL, 50, &tcache.upart[0]); + assert_int_equal(evicted, 50); +} + +static void ocf_evict_do_test02(void **state) +{ + struct test_cache tcache = {}; + unsigned i; + unsigned evicted; + + print_test_description("one overflown IO class\n"); + + init_part_list(&tcache); + + tcache.evictable[10] = 100; + tcache.overflow[10] = 100; + + _expect_evict_call(tcache, 10, 50, 50); + + evicted = ocf_evict_do((ocf_cache_t *)&tcache, NULL, 50, &tcache.upart[0]); + assert_int_equal(evicted, 50); +} + +static void ocf_evict_do_test03(void **state) +{ + struct test_cache tcache = {}; + unsigned i; + unsigned evicted; + + print_test_description("multiple non-overflown IO class\n"); + + init_part_list(&tcache); + + tcache.evictable[10] = 100; + tcache.evictable[12] = 100; + tcache.evictable[16] = 100; + tcache.evictable[17] = 100; + + _expect_evict_call(tcache, 10, 100, 100); + _expect_evict_call(tcache, 12, 100, 100); + _expect_evict_call(tcache, 16, 100, 100); + _expect_evict_call(tcache, 17, 50, 50); + + evicted = ocf_evict_do((ocf_cache_t *)&tcache, NULL, 350, &tcache.upart[0]); + assert_int_equal(evicted, 350); +} + +static void ocf_evict_do_test04(void **state) +{ + struct test_cache tcache = {}; + unsigned i; + unsigned evicted; + + print_test_description("multiple IO class with and without overflow\n"); + + init_part_list(&tcache); + + tcache.evictable[10] = 100; + tcache.evictable[12] = 100; + tcache.overflow[12] = 40; + tcache.evictable[14] = 100; + tcache.overflow[14] = 100; + tcache.evictable[16] = 100; + tcache.evictable[17] = 100; + tcache.evictable[18] = 100; + tcache.overflow[18] = 100; + + _expect_evict_call(tcache, 12, 40, 40); + _expect_evict_call(tcache, 14, 100, 100); + _expect_evict_call(tcache, 18, 100, 100); + _expect_evict_call(tcache, 10, 100, 100); + _expect_evict_call(tcache, 12, 60, 60); + _expect_evict_call(tcache, 16, 100, 100); + _expect_evict_call(tcache, 17, 80, 80); + + evicted = ocf_evict_do((ocf_cache_t *)&tcache, NULL, 580, &tcache.upart[0]); + assert_int_equal(evicted, 580); +} +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(ocf_evict_do_test01), + cmocka_unit_test(ocf_evict_do_test02), + cmocka_unit_test(ocf_evict_do_test03), + cmocka_unit_test(ocf_evict_do_test04) + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +}