diff --git a/Makefile b/Makefile index 9f46e6889..87fa181cb 100644 --- a/Makefile +++ b/Makefile @@ -462,6 +462,10 @@ $(BINDIR)/$(UNITDIR)/platform_apis_test: $(UTIL_SYS) \ $(COMMON_UNIT_TESTOBJ) \ $(PLATFORM_SYS) +$(BINDIR)/$(UNITDIR)/mini_allocator_test: $(COMMON_TESTOBJ) \ + $(OBJDIR)/$(FUNCTIONAL_TESTSDIR)/test_async.o \ + $(LIBDIR)/libsplinterdb.so + ######################################## # Convenience mini unit-test targets unit/util_test: $(BINDIR)/$(UNITDIR)/util_test diff --git a/src/allocator.h b/src/allocator.h index 155d2db19..474341751 100644 --- a/src/allocator.h +++ b/src/allocator.h @@ -253,6 +253,23 @@ allocator_print_allocated(allocator *al) return al->ops->print_allocated(al); } +// Return the address of the extent holding page at address 'addr' +static inline uint64 +allocator_extent_base_addr(allocator *al, uint64 addr) +{ + allocator_config *allocator_cfg = allocator_get_config(al); + return allocator_config_extent_base_addr(allocator_cfg, addr); +} + +// Returns the address of the page next to input 'page_addr' +static inline uint64 +allocator_next_page_addr(allocator *al, uint64 page_addr) +{ + allocator_config *allocator_cfg = allocator_get_config(al); + return (page_addr + allocator_cfg->io_cfg->page_size); +} + + static inline bool allocator_page_valid(allocator *al, uint64 addr) { diff --git a/src/btree.c b/src/btree.c index e82f3bfde..6ddca4e5d 100644 --- a/src/btree.c +++ b/src/btree.c @@ -1016,8 +1016,11 @@ btree_truncate_index(const btree_config *cfg, // IN *----------------------------------------------------------------------------- * btree_alloc -- * - * Allocates a node from the preallocator. Will refill it if there are no - * more nodes available for the given height. + * Allocates a new page from the mini-allocator for a new BTree node. + * from the (previously setup) mini-allocator. Will refill the mini- + * allocator's cache of pre-allocated extents if there are no more nodes + * (pages) available from the already-allocated extent for the given + * height. *----------------------------------------------------------------------------- */ bool diff --git a/src/mini_allocator.c b/src/mini_allocator.c index b8edfac36..41df2c39c 100644 --- a/src/mini_allocator.c +++ b/src/mini_allocator.c @@ -245,11 +245,55 @@ base_addr(cache *cc, uint64 addr) allocator_get_config(cache_get_allocator(cc)), addr); } +/* + * Return page number for the page at 'addr', in terms of page-size. + * This routine assume that input 'addr' is a valid page address. + * RESOLVE: This will go-away and will directly call allocator_page_number() + * that is being added by another in-flight PR #537. + */ +static uint64 +allocator_page_number(allocator *al, uint64 page_addr) +{ + allocator_config *allocator_cfg = allocator_get_config(al); + debug_assert(allocator_valid_page_addr(al, page_addr)); + return ((page_addr / allocator_cfg->io_cfg->page_size)); +} + +static inline uint64 +page_number(cache *cc, uint64 addr) +{ + return allocator_page_number(cache_get_allocator(cc), addr); +} + +/* + * Return extent number of the extent holding the page at 'addr'. + * This routine assume that input 'addr' is a valid page address. + * RESOLVE: This will go-away and will directly call allocator_page_number() + * that is being added by another in-flight PR #537. + */ +static uint64 +allocator_extent_number(allocator *al, uint64 page_addr) +{ + allocator_config *allocator_cfg = allocator_get_config(al); + debug_assert(allocator_valid_page_addr(al, page_addr)); + return ((allocator_extent_base_addr(al, page_addr) + / allocator_cfg->io_cfg->extent_size)); +} + +static inline uint64 +extent_number(cache *cc, uint64 addr) +{ + return allocator_extent_number(cache_get_allocator(cc), addr); +} + /* *----------------------------------------------------------------------------- * mini_init -- * - * Initialize a new mini allocator. + * Initialize a new mini allocator for a specified number of batches. + * A "batch" is a set of logically related pages. E.g., all pages of a + * trunk node or BTree node at a specific level. Pages allocated from a + * batch share the same extents. * * There are two types of mini allocator: keyed and unkeyed. * @@ -260,7 +304,7 @@ base_addr(cache *cc, uint64 addr) * is overloaded onto the meta_head disk-allocator ref count. * * Results: - * The 0th batch next address to be allocated. + * The next address to be allocated from the 0th batch. * * Side effects: * None. @@ -346,6 +390,8 @@ mini_num_entries(page_handle *meta_page) * mini_keyed_set_last_end_key -- * mini_unkeyed_[get,set]_entry -- * + * mini_append_entry, mini_keyed_append_entry, mini_unkeyed_append_entry -- + * * Allocator functions for adding new extents to the meta_page or getting * the metadata of the pos-th extent in the given meta_page. * @@ -570,6 +616,12 @@ mini_append_entry(mini_allocator *mini, * If next_extent is not NULL, then the successor extent to the allocated * addr will be copied to it. * + * NOTE: Mini-allocator pre-stages allocation of extents for each batch. + * At init time, we pre-allocate an extent for each batch. When allocating + * the 1st page from this pre-allocated extent, we allocate a new extent + * for the requested batch. This way, we always have an allocated extent + * for each batch ready for use by the mini-allocator. + * * Results: * A newly allocated disk address. * @@ -588,8 +640,8 @@ mini_alloc(mini_allocator *mini, uint64 next_addr = mini_lock_batch_get_next_addr(mini, batch); + // Need to allocate a new next-extent if the next_addr is start of an extent. if (next_addr % cache_extent_size(mini->cc) == 0) { - // need to allocate the next extent uint64 extent_addr = mini->next_extent[batch]; platform_status rc = @@ -614,10 +666,12 @@ mini_alloc(mini_allocator *mini, *----------------------------------------------------------------------------- * mini_release -- * - * Called to finalize the mini_allocator. After calling, no more - * allocations can be made, but the mini_allocator linked list containing - * the extents allocated and their metadata can be accessed by functions - * using its meta_head. + * Called to finalize the mini_allocator. As the mini-allocator always + * pre-allocates an extent, 'release' here means to deallocate this + * pre-allocated extent for each active batch. After calling release, no + * more allocations can be made, but the mini_allocator's linked list + * containing the extents allocated and their metadata can be accessed by + * functions using its meta_head. * * Keyed allocators use this to set the final end keys of the batches. * @@ -633,8 +687,9 @@ mini_release(mini_allocator *mini, key end_key) { debug_assert(!mini->keyed || !key_is_null(end_key)); + // For all batches that this mini-allocator was setup for ... for (uint64 batch = 0; batch < mini->num_batches; batch++) { - // Dealloc the next extent + // Dealloc the next pre-allocated extent uint8 ref = allocator_dec_ref(mini->al, mini->next_extent[batch], mini->type); platform_assert(ref == AL_NO_REFS); @@ -651,7 +706,7 @@ mini_release(mini_allocator *mini, key end_key) /* *----------------------------------------------------------------------------- - * mini_deinit -- + * mini_deinit_metadata -- * * Cleanup function to deallocate the metadata extents of the mini * allocator. Does not deallocate or otherwise access the data extents. @@ -663,9 +718,8 @@ mini_release(mini_allocator *mini, key end_key) * Disk deallocation, standard cache side effects. *----------------------------------------------------------------------------- */ - -void -mini_deinit(cache *cc, uint64 meta_head, page_type type, bool pinned) +static void +mini_deinit_metadata(cache *cc, uint64 meta_head, page_type type, bool pinned) { allocator *al = cache_get_allocator(cc); uint64 meta_addr = meta_head; @@ -690,6 +744,35 @@ mini_deinit(cache *cc, uint64 meta_head, page_type type, bool pinned) } while (meta_addr != 0); } +/* + *----------------------------------------------------------------------------- + * mini_deinit -- De-Initialize a new mini allocator. + * + * This is the last thing to do to release all resources acquired by the + * mini-allocator. All pages & extents reserved or allocated by the mini- + * allocator will be released. The mini-allocator cannot be used after this + * call, and will need to go through mini_init(). + * + * Returns: Nothing. + *----------------------------------------------------------------------------- + */ +void +mini_deinit(mini_allocator *mini, key start_key, key end_key) +{ + mini_release(mini, end_key); + if (!mini->keyed) { + mini_unkeyed_dec_ref(mini->cc, mini->meta_head, mini->type, mini->pinned); + } else { + mini_keyed_dec_ref(mini->cc, + mini->data_cfg, + mini->type, + mini->meta_head, + start_key, + end_key); + } + ZERO_CONTENTS(mini); +} + /* *----------------------------------------------------------------------------- * mini_destroy_unused -- @@ -720,6 +803,10 @@ mini_destroy_unused(mini_allocator *mini) mini->num_extents, mini->num_batches); + /* RESOLVE: What's the difference between this block of code and the + * work done in mini_release(). Can we merge these two fns into one? + */ + // For all batches that this mini-allocator was setup for ... for (uint64 batch = 0; batch < mini->num_batches; batch++) { // Dealloc the next extent uint8 ref = @@ -729,7 +816,7 @@ mini_destroy_unused(mini_allocator *mini) platform_assert(ref == AL_FREE); } - mini_deinit(mini->cc, mini->meta_head, mini->type, FALSE); + mini_deinit_metadata(mini->cc, mini->meta_head, mini->type, FALSE); } @@ -1038,7 +1125,7 @@ mini_unkeyed_dec_ref(cache *cc, uint64 meta_head, page_type type, bool pinned) // need to deallocate and clean up the mini allocator mini_unkeyed_for_each(cc, meta_head, type, FALSE, mini_dealloc_extent, NULL); - mini_deinit(cc, meta_head, type, pinned); + mini_deinit_metadata(cc, meta_head, type, pinned); return 0; } @@ -1153,7 +1240,7 @@ mini_keyed_dec_ref(cache *cc, allocator *al = cache_get_allocator(cc); uint8 ref = allocator_get_refcount(al, base_addr(cc, meta_head)); platform_assert(ref == AL_ONE_REF); - mini_deinit(cc, meta_head, type, FALSE); + mini_deinit_metadata(cc, meta_head, type, FALSE); } return should_cleanup; } @@ -1281,29 +1368,56 @@ mini_unkeyed_print(cache *cc, uint64 meta_head, page_type type) uint64 next_meta_addr = meta_head; platform_default_log("---------------------------------------------\n"); - platform_default_log("| Mini Allocator -- meta_head: %12lu |\n", meta_head); - platform_default_log("|-------------------------------------------|\n"); - platform_default_log("| idx | %35s |\n", "extent_addr"); + platform_default_log("| Mini Allocator -- meta_head=%lu (pgnum=%lu) \n", + meta_head, + page_number(cc, meta_head)); platform_default_log("|-------------------------------------------|\n"); + uint64 num_meta_pages = 0; + uint64 num_extent_addrs = 0; + do { - page_handle *meta_page = cache_get(cc, next_meta_addr, TRUE, type); + page_handle *meta_page = cache_get(cc, next_meta_addr, TRUE, type); + mini_meta_hdr *meta_hdr = (mini_meta_hdr *)meta_page->data; - platform_default_log("| meta addr %31lu |\n", next_meta_addr); + uint64 num_entries = mini_num_entries(meta_page); + platform_default_log("{\n"); + platform_default_log("|-------------------------------------------|\n"); + platform_default_log("| meta addr=%-lu (pgnum=%lu), num_entries=%lu\n", + next_meta_addr, + page_number(cc, next_meta_addr), + num_entries); + platform_default_log("| next_meta_addr=%lu (pgnum=%lu), pos=%lu\n", + meta_hdr->next_meta_addr, + page_number(cc, meta_hdr->next_meta_addr), + meta_hdr->pos); + platform_default_log("|-------------------------------------------|\n"); + platform_default_log( + "| idx | %18s | %14s |\n", "extent_addr", "extent_num"); platform_default_log("|-------------------------------------------|\n"); - uint64 num_entries = mini_num_entries(meta_page); - unkeyed_meta_entry *entry = unkeyed_first_entry(meta_page); + unkeyed_meta_entry *entry = unkeyed_first_entry(meta_page); for (uint64 i = 0; i < num_entries; i++) { - platform_default_log("| %3lu | %35lu |\n", i, entry->extent_addr); + platform_default_log("| %3lu | %18lu | %14lu |\n", + i, + entry->extent_addr, + extent_number(cc, entry->extent_addr)); entry = unkeyed_next_entry(entry); } platform_default_log("|-------------------------------------------|\n"); + platform_default_log("}\n"); + + num_meta_pages++; + num_extent_addrs += num_entries; next_meta_addr = mini_get_next_meta_addr(meta_page); cache_unget(cc, meta_page); } while (next_meta_addr != 0); platform_default_log("\n"); + + platform_default_log("Found %lu meta-data pages tracking %lu extents.\n", + num_meta_pages, + num_extent_addrs); } void @@ -1315,59 +1429,70 @@ mini_keyed_print(cache *cc, allocator *al = cache_get_allocator(cc); uint64 next_meta_addr = meta_head; - platform_default_log("------------------------------------------------------" - "---------------\n"); - platform_default_log( - "| Mini Keyed Allocator -- meta_head: %12lu |\n", - meta_head); - platform_default_log("|-----------------------------------------------------" - "--------------|\n"); - platform_default_log("| idx | %5s | %14s | %18s | %3s |\n", - "batch", - "extent_addr", - "start_key", - "rc"); - platform_default_log("|-----------------------------------------------------" - "--------------|\n"); + // clang-format off + const char *dashes = "------------------------------------------------------------------------"; + // clang-format on + platform_default_log("%s\n", dashes); + + platform_default_log("| Mini Keyed Allocator -- meta_head=%lu (pgnum=%lu)\n", + meta_head, + page_number(cc, meta_head)); do { - page_handle *meta_page = cache_get(cc, next_meta_addr, TRUE, type); + page_handle *meta_page = cache_get(cc, next_meta_addr, TRUE, type); + mini_meta_hdr *meta_hdr = (mini_meta_hdr *)meta_page->data; + uint64 num_entries = mini_num_entries(meta_page); + platform_default_log("{\n"); + platform_default_log("%s\n", dashes); platform_default_log( - "| meta addr: %12lu (%u) |\n", + "| meta addr=%lu (pgnum=%lu, refcount=%u), num_entries=%lu\n", next_meta_addr, - allocator_get_refcount(al, base_addr(cc, next_meta_addr))); - platform_default_log("|--------------------------------------------------" - "-----------------|\n"); + page_number(cc, next_meta_addr), + allocator_get_refcount(al, base_addr(cc, next_meta_addr)), + num_entries); + platform_default_log("| next_meta_addr=%lu (pgnum=%lu), pos=%lu\n", + meta_hdr->next_meta_addr, + page_number(cc, meta_hdr->next_meta_addr), + meta_hdr->pos); + platform_default_log("%s\n", dashes); + platform_default_log("| idx | %5s | %14s | %10s | %18s | %3s |\n", + "batch", + "extent_addr", + "extent_num", + "start_key", + "rc"); + platform_default_log("%s\n", dashes); - uint64 num_entries = mini_num_entries(meta_page); - keyed_meta_entry *entry = keyed_first_entry(meta_page); + keyed_meta_entry *entry = keyed_first_entry(meta_page); for (uint64 i = 0; i < num_entries; i++) { - key start_key = keyed_meta_entry_start_key(entry); - char extent_str[32]; - if (entry->extent_addr == TERMINAL_EXTENT_ADDR) { - snprintf(extent_str, sizeof(extent_str), "TERMINAL_ENTRY"); - } else { - snprintf( - extent_str, sizeof(extent_str), "%14lu", entry->extent_addr); - } - char ref_str[4]; + key start_key = keyed_meta_entry_start_key(entry); + if (entry->extent_addr == TERMINAL_EXTENT_ADDR) { - snprintf(ref_str, 4, "n/a"); + platform_default_log( + "| %3lu | %5u | %14s | %10s | %18.18s | %3s |\n", + i, + entry->batch, + "TERMINAL_ENTRY", + "n/a", + key_string(data_cfg, start_key), + "n/a"); } else { uint8 ref = allocator_get_refcount(al, entry->extent_addr); - snprintf(ref_str, 4, "%3u", ref); + platform_default_log( + "| %3lu | %5u | %14lu | %10lu | %18.18s | %3u |\n", + i, + entry->batch, + entry->extent_addr, + extent_number(cc, entry->extent_addr), + key_string(data_cfg, start_key), + ref); } - platform_default_log("| %3lu | %5u | %14s | %18.18s | %3s |\n", - i, - entry->batch, - extent_str, - key_string(data_cfg, start_key), - ref_str); + entry = keyed_next_entry(entry); } - platform_default_log("|--------------------------------------------------" - "-----------------|\n"); + platform_default_log("%s\n", dashes); + platform_default_log("}\n"); next_meta_addr = mini_get_next_meta_addr(meta_page); cache_unget(cc, meta_page); diff --git a/src/mini_allocator.h b/src/mini_allocator.h index 7c9162aa5..d4c659a66 100644 --- a/src/mini_allocator.h +++ b/src/mini_allocator.h @@ -49,6 +49,10 @@ typedef struct mini_allocator { uint64 next_extent[MINI_MAX_BATCHES]; } mini_allocator; +/* + * Initialize a new mini allocator. + * Returns the next address to be allocated from the 0th batch. + */ uint64 mini_init(mini_allocator *mini, cache *cc, @@ -58,15 +62,30 @@ mini_init(mini_allocator *mini, uint64 num_batches, page_type type, bool keyed); + +void +mini_deinit(mini_allocator *mini, key start_key, key end_key); + +/* + * Called to finalize the mini_allocator. After calling, no more + * allocations can be made, but the mini_allocator's linked list containing + * the extents allocated and their metadata can be accessed by functions + * using its meta_head. + */ void mini_release(mini_allocator *mini, key end_key); /* - * NOTE: Can only be called on a mini_allocator which has made no allocations. + * Called to destroy a mini_allocator that was created but never used to + * allocate an extent. Can only be called on a keyed mini allocator. */ void mini_destroy_unused(mini_allocator *mini); +/* + * Allocate a next disk address from the mini_allocator. + * Returns a newly allocated disk address. + */ uint64 mini_alloc(mini_allocator *mini, uint64 batch, diff --git a/src/trunk.c b/src/trunk.c index 233e48045..8ce98e7bc 100644 --- a/src/trunk.c +++ b/src/trunk.c @@ -7289,9 +7289,6 @@ trunk_prepare_for_shutdown(trunk_handle *spl) platform_free(spl->heap_id, spl->log); } - // release the trunk mini allocator - mini_release(&spl->mini, NULL_KEY); - // flush all dirty pages in the cache cache_flush(spl->cc); } @@ -7346,7 +7343,8 @@ trunk_destroy(trunk_handle *spl) trunk_for_each_node(spl, trunk_node_destroy, NULL); - mini_unkeyed_dec_ref(spl->cc, spl->mini.meta_head, PAGE_TYPE_TRUNK, FALSE); + // Deinit the trunk's unkeyed mini allocator; release all its resources + mini_deinit(&spl->mini, NULL_KEY, NULL_KEY); // clear out this splinter table from the meta page. allocator_remove_super_addr(spl->al, spl->id); diff --git a/tests/unit/mini_allocator_test.c b/tests/unit/mini_allocator_test.c new file mode 100644 index 000000000..369253c66 --- /dev/null +++ b/tests/unit/mini_allocator_test.c @@ -0,0 +1,430 @@ +// Copyright 2023 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/* + * ----------------------------------------------------------------------------- + * mini_allocator_test.c -- + * + * Exercises some of the interfaces in mini_allocator.c to exercise this + * sub-system for both keyed and unkeyed page allocations. + * ----------------------------------------------------------------------------- + */ +#include "splinterdb/public_platform.h" +#include "unit_tests.h" +#include "ctest.h" // This is required for all test-case files. +#include "rc_allocator.h" +#include "config.h" +#include "splinterdb/default_data_config.h" +#include "btree.h" +#include "functional/random.h" + +#define TEST_MAX_KEY_SIZE 44 + +/* + * Global data declaration macro: + */ +CTEST_DATA(mini_allocator) +{ + // Declare head handles for io, allocator, cache memory allocation. + platform_heap_handle hh; + platform_heap_id hid; + platform_module_id mid; + + // Config structs for sub-systems that clockcache depends on + io_config io_cfg; + task_system_config task_cfg; + allocator_config al_cfg; + clockcache_config cache_cfg; + data_config default_data_cfg; + + // Handles to various sub-systems for which memory will be allocated + platform_io_handle *io; + task_system *tasks; + allocator *al; + rc_allocator *rc_al; + clockcache *clock_cache; +}; + +// Setup function for suite, called before every test in suite +CTEST_SETUP(mini_allocator) +{ + if (Ctest_verbose) { + platform_set_log_streams(stdout, stderr); + } + uint64 heap_capacity = 1024 * MiB; + + default_data_config_init(TEST_MAX_KEY_SIZE, &data->default_data_cfg); + + // Create a heap for io, allocator, cache and splinter + platform_status rc = platform_heap_create( + platform_get_module_id(), heap_capacity, &data->hh, &data->hid); + ASSERT_TRUE(SUCCESS(rc)); + + // Allocate memory for and set default for a master config struct + master_config *master_cfg = TYPED_ZALLOC(data->hid, master_cfg); + config_set_defaults(master_cfg); + + io_config_init(&data->io_cfg, + master_cfg->page_size, + master_cfg->extent_size, + master_cfg->io_flags, + master_cfg->io_perms, + master_cfg->io_async_queue_depth, + master_cfg->io_filename); + + // no background threads by default. + uint64 num_bg_threads[NUM_TASK_TYPES] = {0}; + rc = task_system_config_init(&data->task_cfg, + TRUE, // use stats + num_bg_threads, + trunk_get_scratch_size()); + + ASSERT_TRUE(SUCCESS(rc)); + allocator_config_init( + &data->al_cfg, &data->io_cfg, master_cfg->allocator_capacity); + + clockcache_config_init(&data->cache_cfg, + &data->io_cfg, + master_cfg->cache_capacity, + master_cfg->cache_logfile, + master_cfg->use_stats); + + // Allocate and initialize the task, IO, rc-allocator sub-systems. + data->io = TYPED_ZALLOC(data->hid, data->io); + ASSERT_TRUE((data->io != NULL)); + rc = io_handle_init(data->io, &data->io_cfg, data->hh, data->hid); + + rc = task_system_create(data->hid, data->io, &data->tasks, &data->task_cfg); + ASSERT_TRUE(SUCCESS(rc)); + + data->rc_al = TYPED_ZALLOC(data->hid, data->rc_al); + ASSERT_TRUE((data->rc_al != NULL)); + rc_allocator_init(data->rc_al, + &data->al_cfg, + (io_handle *)data->io, + data->hid, + platform_get_module_id()); + data->al = (allocator *)data->rc_al; + + data->clock_cache = TYPED_ZALLOC(data->hid, data->clock_cache); + ASSERT_TRUE((data->clock_cache != NULL)); + + rc = clockcache_init(data->clock_cache, + &data->cache_cfg, + (io_handle *)data->io, + data->al, + "test_mini_allocator", + data->hid, + data->mid); + ASSERT_TRUE(SUCCESS(rc)); + + platform_free(data->hid, master_cfg); +} + +// Teardown function for suite, called after every test in suite +CTEST_TEARDOWN(mini_allocator) +{ + clockcache_deinit(data->clock_cache); + platform_free(data->hid, data->clock_cache); + + rc_allocator_deinit(data->rc_al); + platform_free(data->hid, data->rc_al); + data->al = NULL; + + task_system_destroy(data->hid, &data->tasks); + + io_handle_deinit(data->io); + platform_free(data->hid, data->io); + + platform_heap_destroy(&data->hh); +} + +/* + * ---------------------------------------------------------------------------- + * Exercise the core APIs of the mini-allocator for allocating keyed pages. + * This will be typically used to allocated BTree pages, which have keys. + * Before we can do page-allocation, need to allocate a new extent, which will + * be used by the mini-allocator. + * ---------------------------------------------------------------------------- + */ +CTEST2(mini_allocator, test_mini_keyed_allocator_basic) +{ + platform_status rc = STATUS_TEST_FAILED; + uint64 extent_addr = 0; + + rc = allocator_alloc(data->al, &extent_addr, PAGE_TYPE_BRANCH); + ASSERT_TRUE(SUCCESS(rc)); + + mini_allocator mini_alloc_ctxt; + mini_allocator *mini = &mini_alloc_ctxt; + ZERO_CONTENTS(mini); + + page_type type = PAGE_TYPE_BRANCH; + uint64 first_ext_addr = extent_addr; + bool keyed_mini_alloc = TRUE; + + uint64 meta_head_addr = allocator_next_page_addr(data->al, first_ext_addr); + + first_ext_addr = mini_init(mini, + (cache *)data->clock_cache, + &data->default_data_cfg, + meta_head_addr, + 0, + MINI_MAX_BATCHES, + type, + keyed_mini_alloc); + ASSERT_TRUE(first_ext_addr != extent_addr); + + mini_destroy_unused(mini); +} + +/* + * Exercise the mini-allocator's interfaces for unkeyed page allocations. + */ +CTEST2(mini_allocator, test_mini_unkeyed_many_allocs_one_batch) +{ + platform_status rc = STATUS_TEST_FAILED; + uint64 extent_addr = 0; + page_type pgtype = PAGE_TYPE_BRANCH; + + uint64 extents_in_use_prev = allocator_in_use(data->al); + ASSERT_TRUE(extents_in_use_prev != 0); + + rc = allocator_alloc(data->al, &extent_addr, pgtype); + ASSERT_TRUE(SUCCESS(rc)); + + mini_allocator mini_alloc_ctxt; + mini_allocator *mini = &mini_alloc_ctxt; + ZERO_CONTENTS(mini); + + uint64 first_ext_addr = extent_addr; + uint64 num_batches = 1; + bool unkeyed_mini_alloc = FALSE; + + uint64 meta_head_addr = allocator_next_page_addr(data->al, first_ext_addr); + + first_ext_addr = mini_init(mini, + (cache *)data->clock_cache, + &data->default_data_cfg, + meta_head_addr, + 0, + num_batches, + pgtype, + unkeyed_mini_alloc); + ASSERT_TRUE(first_ext_addr != extent_addr); + + // 1 for the extent holding the metadata page and 1 for the newly allocated + // extent. + uint64 exp_num_extents = 2; + ASSERT_EQUAL(exp_num_extents, mini_num_extents(mini)); + + allocator_config *allocator_cfg = allocator_get_config(data->al); + + uint64 extent_size = allocator_cfg->io_cfg->extent_size; + uint64 page_size = allocator_cfg->io_cfg->page_size; + + // Test that mini-allocator's state of extent-to-use changes when all pages + // in currently pre-allocated extent are allocated. + uint64 next_ext_addr = 0; + uint64 exp_next_page = first_ext_addr; + uint64 next_page_addr = 0; + uint64 npages_in_extent = (extent_size / page_size); + uint64 batch_num = 0; + uint64 pgctr; + + for (pgctr = 0; pgctr < npages_in_extent; pgctr++) { + next_page_addr = mini_alloc(mini, batch_num, NULL_KEY, &next_ext_addr); + + // All pages allocated should be from previously allocated extent + ASSERT_EQUAL(first_ext_addr, + allocator_extent_base_addr(data->al, next_page_addr)); + + // And we should get a diff page for each allocation request + ASSERT_EQUAL(exp_next_page, + next_page_addr, + "pgctr=%lu, exp_next_page=%lu, next_page_addr=%lu\n", + pgctr, + exp_next_page, + next_page_addr); + + // We should have pre-allocated a new extent when we just started to + // allocate pages from currently allocated extent. + if (pgctr == 0) { + exp_num_extents++; + } + ASSERT_EQUAL(exp_num_extents, mini_num_extents(mini)); + + exp_next_page = allocator_next_page_addr(data->al, next_page_addr); + } + + uint64 new_next_ext_addr; + + // Allocating the next page in a new extent pre-allocates a new extent. + next_page_addr = mini_alloc(mini, batch_num, NULL_KEY, &new_next_ext_addr); + + ASSERT_NOT_EQUAL(first_ext_addr, next_ext_addr); + + // This most-recently allocated page should be from the recently + // pre-allocated extent, tracked by next_ext_addr. In fact it should be + // exactly that 1st page on that pre-allocated extent. + ASSERT_EQUAL(next_ext_addr, + next_page_addr, + "pgctr=%lu, next_ext_addr=%lu, next_page_addr=%lu\n", + pgctr, + next_ext_addr, + next_page_addr); + + // The alloc of this 1st page should have pre-allocated a new extent. + exp_num_extents++; + ASSERT_EQUAL(exp_num_extents, mini_num_extents(mini)); + + // Release extents reserved by mini-allocator, to verify extents in-use. + mini_deinit(mini, NULL_KEY, NULL_KEY); + + exp_num_extents = 0; + ASSERT_EQUAL(exp_num_extents, mini_num_extents(mini)); + + uint64 extents_in_use_now = allocator_in_use(data->al); + ASSERT_EQUAL(extents_in_use_prev, extents_in_use_now); +} + +/* + * ---------------------------------------------------------------------------- + * Exercise the mini-allocator's interfaces for unkeyed page allocations, + * pretending that we are allocating pages for all levels of the trunk's nodes. + * Then, exercise the method to print contents of chain of unkeyed allocator's + * meta-data pages to ensure that the print function works reasonably. + * ---------------------------------------------------------------------------- + */ +CTEST2(mini_allocator, test_trunk_mini_unkeyed_allocs_print_diags) +{ + platform_status rc = STATUS_TEST_FAILED; + uint64 extent_addr = 0; + page_type pgtype = PAGE_TYPE_TRUNK; + + rc = allocator_alloc(data->al, &extent_addr, pgtype); + ASSERT_TRUE(SUCCESS(rc)); + + mini_allocator mini_alloc_ctxt; + mini_allocator *mini = &mini_alloc_ctxt; + ZERO_CONTENTS(mini); + + uint64 first_ext_addr = extent_addr; + uint64 num_batches = TRUNK_MAX_HEIGHT; + bool unkeyed_mini_alloc = FALSE; + + uint64 meta_head_addr = allocator_next_page_addr(data->al, first_ext_addr); + + first_ext_addr = mini_init(mini, + (cache *)data->clock_cache, + &data->default_data_cfg, + meta_head_addr, + 0, + num_batches, + pgtype, + unkeyed_mini_alloc); + ASSERT_TRUE(first_ext_addr != extent_addr); + + uint64 npages_in_extent = data->clock_cache->cfg->pages_per_extent; + uint64 npages_allocated = 0; + + uint64 exp_num_extents = mini_num_extents(mini); + + // Allocate n-pages for each level (bctr) of the trunk tree. Pick some + // large'ish # of pages to allocate so we fill-up multiple metadata pages + // worth of unkeyed_meta_entry{} entries. + for (uint64 bctr = 0; bctr < num_batches; bctr++) { + uint64 num_extents_per_level = (64 * bctr); + for (uint64 pgctr = 0; pgctr < (num_extents_per_level * npages_in_extent); + pgctr++) + { + uint64 next_page_addr = mini_alloc(mini, bctr, NULL_KEY, NULL); + ASSERT_FALSE(next_page_addr == 0); + + npages_allocated++; + } + exp_num_extents += num_extents_per_level; + } + // Validate book-keeping of # of extents allocated by mini-allocator + ASSERT_EQUAL(exp_num_extents, mini_num_extents(mini)); + + // Exercise print method of mini-allocator's keyed meta-page + CTEST_LOG_INFO("\n** Unkeyed Mini-Allocator dump **\n"); + mini_unkeyed_print((cache *)data->clock_cache, meta_head_addr, pgtype); + + mini_deinit(mini, NULL_KEY, NULL_KEY); +} + +/* + * ---------------------------------------------------------------------------- + * Exercise the mini-allocator's interfaces for keyed page allocations, + * pretending that we are allocating pages for all levels of a BTree. + * Then, exercise the method to print contents of chain of keyed allocator's + * meta-data pages to ensure that the print function works reasonably. + * ---------------------------------------------------------------------------- + */ +CTEST2(mini_allocator, test_trunk_mini_keyed_allocs_print_diags) +{ + platform_status rc = STATUS_TEST_FAILED; + uint64 extent_addr = 0; + page_type pgtype = PAGE_TYPE_BRANCH; + + rc = allocator_alloc(data->al, &extent_addr, pgtype); + ASSERT_TRUE(SUCCESS(rc)); + + mini_allocator mini_alloc_ctxt; + mini_allocator *mini = &mini_alloc_ctxt; + ZERO_CONTENTS(mini); + + uint64 first_ext_addr = extent_addr; + uint64 num_batches = BTREE_MAX_HEIGHT; + bool keyed_mini_alloc = TRUE; + + uint64 meta_head_addr = allocator_next_page_addr(data->al, first_ext_addr); + + first_ext_addr = mini_init(mini, + (cache *)data->clock_cache, + &data->default_data_cfg, + meta_head_addr, + 0, + num_batches, + pgtype, + keyed_mini_alloc); + ASSERT_TRUE(first_ext_addr != extent_addr); + + uint64 npages_in_extent = data->clock_cache->cfg->pages_per_extent; + uint64 npages_allocated = 0; + + uint64 exp_num_extents = mini_num_extents(mini); + + // Allocate n-pages for each level (bctr) of the Btree. Pick some + // large'ish # of pages to allocate so we fill-up multiple metadata pages + // worth of unkeyed_meta_entry{} entries. + for (uint64 bctr = 0; bctr < num_batches; bctr++) { + uint64 num_extents_per_level = (64 * bctr); + for (uint64 pgctr = 0; pgctr < (num_extents_per_level * npages_in_extent); + pgctr++) + { + random_state rand_state; + random_init(&rand_state, pgctr + 42, 0); + char key_buffer[TEST_MAX_KEY_SIZE] = {0}; + random_bytes(&rand_state, key_buffer, TEST_MAX_KEY_SIZE); + + uint64 next_page_addr = mini_alloc( + mini, bctr, key_create(sizeof(key_buffer), key_buffer), NULL); + ASSERT_FALSE(next_page_addr == 0); + + npages_allocated++; + } + exp_num_extents += num_extents_per_level; + } + // Validate book-keeping of # of extents allocated by mini-allocator + ASSERT_EQUAL(exp_num_extents, mini_num_extents(mini)); + + // Exercise print method of mini-allocator's keyed meta-page + CTEST_LOG_INFO("\n** Keyed Mini-Allocator dump **\n"); + mini_keyed_print( + (cache *)data->clock_cache, mini->data_cfg, meta_head_addr, pgtype); + + mini_deinit(mini, NEGATIVE_INFINITY_KEY, POSITIVE_INFINITY_KEY); +}