diff --git a/runtime/src/bpf_map/userspace/per_cpu_hash_map.cpp b/runtime/src/bpf_map/userspace/per_cpu_hash_map.cpp index 8b19bc1f..ade89d92 100644 --- a/runtime/src/bpf_map/userspace/per_cpu_hash_map.cpp +++ b/runtime/src/bpf_map/userspace/per_cpu_hash_map.cpp @@ -4,28 +4,40 @@ * All rights reserved. */ #include "bpf_map/map_common_def.hpp" +#include "linux/bpf.h" #include "spdlog/fmt/bin_to_hex.h" #include "spdlog/spdlog.h" #include #include +#include +#include #include #include "platform_utils.hpp" +static inline bool check_update_flags(uint64_t flags) +{ + if (flags != BPF_ANY && flags != BPF_NOEXIST && flags != BPF_EXIST) { + errno = EINVAL; + return false; + } + return true; +} + namespace bpftime { per_cpu_hash_map_impl::per_cpu_hash_map_impl( boost::interprocess::managed_shared_memory &memory, uint32_t key_size, - uint32_t value_size) - : per_cpu_hash_map_impl(memory, key_size, value_size, + uint32_t value_size, uint32_t max_entries) + : per_cpu_hash_map_impl(memory, key_size, value_size, max_entries, sysconf(_SC_NPROCESSORS_ONLN)) { } per_cpu_hash_map_impl::per_cpu_hash_map_impl( boost::interprocess::managed_shared_memory &memory, uint32_t key_size, - uint32_t value_size, int ncpu) + uint32_t value_size, uint32_t max_entries, int ncpu) : impl(memory.get_segment_manager()), key_size(key_size), - value_size(value_size), ncpu(ncpu), + value_size(value_size), ncpu(ncpu), _max_entries(max_entries), value_template(value_size * ncpu, memory.get_segment_manager()), key_templates(memory.get_segment_manager()), single_value_templates(memory.get_segment_manager()) @@ -64,6 +76,8 @@ void *per_cpu_hash_map_impl::elem_lookup(const void *key) long per_cpu_hash_map_impl::elem_update(const void *key, const void *value, uint64_t flags) { + if (!check_update_flags(flags)) + return -1; int cpu = my_sched_getcpu(); SPDLOG_DEBUG("Per cpu update, key {}, value {}", (const char *)key, *(long *)value); @@ -115,7 +129,7 @@ int per_cpu_hash_map_impl::map_get_next_key(const void *key, void *next_key) } // No need to be allocated at shm. Allocate as a local variable to make // it thread safe, since we use sharable lock - bytes_vec key_vec = this->key_templates[0]; + bytes_vec &key_vec = this->key_templates[0]; key_vec.assign((uint8_t *)key, (uint8_t *)key + key_size); auto itr = impl.find(key_vec); @@ -139,7 +153,7 @@ void *per_cpu_hash_map_impl::elem_lookup_userspace(const void *key) errno = ENOENT; return nullptr; } - bytes_vec key_vec = this->key_templates[0]; + bytes_vec &key_vec = this->key_templates[0]; key_vec.assign((uint8_t *)key, (uint8_t *)key + key_size); if (auto itr = impl.find(key_vec); itr != impl.end()) { SPDLOG_TRACE("Exit elem lookup of hash map: {}", @@ -157,24 +171,54 @@ long per_cpu_hash_map_impl::elem_update_userspace(const void *key, const void *value, uint64_t flags) { - bytes_vec key_vec = this->key_templates[0]; + if (!check_update_flags(flags)) + return -1; + bytes_vec &key_vec = this->key_templates[0]; bytes_vec value_vec = this->value_template; key_vec.assign((uint8_t *)key, (uint8_t *)key + key_size); value_vec.assign((uint8_t *)value, (uint8_t *)value + value_size * ncpu); - - if (auto itr = impl.find(key_vec); itr != impl.end()) { - itr->second = value_vec; - } else { - impl.insert(bi_map_value_ty(key_vec, value_vec)); + bool elem_exists = impl.find(key_vec) != impl.end(); + if (flags == BPF_NOEXIST && elem_exists) { + errno = EEXIST; + return -1; + } + if (flags == BPF_EXIST && !elem_exists) { + errno = ENOENT; + return -1; + } + if (elem_exists == false && impl.size() == _max_entries) { + errno = E2BIG; + return -1; } + impl.insert_or_assign(key_vec, value_vec); return 0; } long per_cpu_hash_map_impl::elem_delete_userspace(const void *key) { - bytes_vec key_vec = this->key_templates[0]; + bytes_vec &key_vec = this->key_templates[0]; key_vec.assign((uint8_t *)key, (uint8_t *)key + key_size); - impl.erase(key_vec); + auto itr = impl.find(key_vec); + if (itr == impl.end()) { + errno = ENOENT; + return -1; + } + impl.erase(itr); + return 0; +} + +long per_cpu_hash_map_impl::lookup_and_delete_userspace(const void *key, + void *value) +{ + bytes_vec &key_vec = this->key_templates[0]; + key_vec.assign((uint8_t *)key, (uint8_t *)key + key_size); + auto itr = this->impl.find(key_vec); + if (itr == impl.end()) { + errno = ENOENT; + return -1; + } + memcpy(value, itr->second.data(), ncpu * value_size); + impl.erase(itr); return 0; } } // namespace bpftime diff --git a/runtime/src/bpf_map/userspace/per_cpu_hash_map.hpp b/runtime/src/bpf_map/userspace/per_cpu_hash_map.hpp index 8cee8ae7..420ac515 100644 --- a/runtime/src/bpf_map/userspace/per_cpu_hash_map.hpp +++ b/runtime/src/bpf_map/userspace/per_cpu_hash_map.hpp @@ -27,27 +27,36 @@ class per_cpu_hash_map_impl { std::equal_to, bi_map_allocator>; using shm_hash_map_vec_allocator = boost::interprocess::allocator< - shm_hash_map, boost::interprocess::managed_shared_memory::segment_manager>; - using shm_hash_map_vec = boost::interprocess::vector; - + shm_hash_map, + boost::interprocess::managed_shared_memory::segment_manager>; + using shm_hash_map_vec = + boost::interprocess::vector; + using bytes_vec_vec_allocator = boost::interprocess::allocator< - bytes_vec, boost::interprocess::managed_shared_memory::segment_manager>; - using bytes_vec_vec = boost::interprocess::vector; + bytes_vec, + boost::interprocess::managed_shared_memory::segment_manager>; + using bytes_vec_vec = + boost::interprocess::vector; shm_hash_map impl; uint32_t key_size; uint32_t value_size; int ncpu; + uint32_t _max_entries; bytes_vec value_template; bytes_vec_vec key_templates, single_value_templates; + public: const static bool should_lock = false; per_cpu_hash_map_impl(boost::interprocess::managed_shared_memory &memory, - uint32_t key_size, uint32_t value_size); + uint32_t key_size, uint32_t value_size, + uint32_t max_entries); per_cpu_hash_map_impl(boost::interprocess::managed_shared_memory &memory, - uint32_t key_size, uint32_t value_size, int ncpu); + uint32_t key_size, uint32_t value_size, + uint32_t max_entries, int ncpu); void *elem_lookup(const void *key); long elem_update(const void *key, const void *value, uint64_t flags); @@ -62,6 +71,15 @@ class per_cpu_hash_map_impl { uint64_t flags); long elem_delete_userspace(const void *key); + long lookup_and_delete_userspace(const void *key, void *value); + uint32_t get_value_size() const + { + return value_size; + } + int getncpu() const + { + return ncpu; + } }; } // namespace bpftime diff --git a/runtime/src/handler/map_handler.cpp b/runtime/src/handler/map_handler.cpp index 8224d2fe..f542e3c3 100644 --- a/runtime/src/handler/map_handler.cpp +++ b/runtime/src/handler/map_handler.cpp @@ -119,7 +119,7 @@ const void *bpf_map_handler::map_lookup_elem(const void *key, return from_syscall ? do_lookup_userspace(impl) : do_lookup(impl); } - #ifdef BPFTIME_BUILD_WITH_LIBBPF +#ifdef BPFTIME_BUILD_WITH_LIBBPF case bpf_map_type::BPF_MAP_TYPE_KERNEL_USER_ARRAY: { auto impl = static_cast( map_impl_ptr.get()); @@ -145,7 +145,7 @@ const void *bpf_map_handler::map_lookup_elem(const void *key, static_cast(map_impl_ptr.get()); return do_lookup(impl); } - #endif +#endif case bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS: { auto impl = static_cast( map_impl_ptr.get()); @@ -213,7 +213,7 @@ long bpf_map_handler::map_update_elem(const void *key, const void *value, return from_syscall ? do_update_userspace(impl) : do_update(impl); } - #ifdef BPFTIME_BUILD_WITH_LIBBPF +#ifdef BPFTIME_BUILD_WITH_LIBBPF case bpf_map_type::BPF_MAP_TYPE_KERNEL_USER_ARRAY: { auto impl = static_cast( map_impl_ptr.get()); @@ -239,7 +239,7 @@ long bpf_map_handler::map_update_elem(const void *key, const void *value, static_cast(map_impl_ptr.get()); return do_update(impl); } - #endif +#endif case bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS: { if (!from_syscall) { // Map in maps only support update from syscall @@ -300,7 +300,7 @@ int bpf_map_handler::bpf_map_get_next_key(const void *key, void *next_key, map_impl_ptr.get()); return do_get_next_key(impl); } - #if __linux__ && defined(BPFTIME_BUILD_WITH_LIBBPF) +#if __linux__ && defined(BPFTIME_BUILD_WITH_LIBBPF) case bpf_map_type::BPF_MAP_TYPE_KERNEL_USER_ARRAY: { auto impl = static_cast( map_impl_ptr.get()); @@ -326,7 +326,7 @@ int bpf_map_handler::bpf_map_get_next_key(const void *key, void *next_key, static_cast(map_impl_ptr.get()); return do_get_next_key(impl); } - #endif +#endif case bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS: { auto impl = static_cast( map_impl_ptr.get()); @@ -394,7 +394,7 @@ long bpf_map_handler::map_delete_elem(const void *key, bool from_syscall) const return from_syscall ? do_delete_userspace(impl) : do_delete(impl); } - #ifdef BPFTIME_BUILD_WITH_LIBBPF +#ifdef BPFTIME_BUILD_WITH_LIBBPF case bpf_map_type::BPF_MAP_TYPE_KERNEL_USER_ARRAY: { auto impl = static_cast( map_impl_ptr.get()); @@ -420,7 +420,7 @@ long bpf_map_handler::map_delete_elem(const void *key, bool from_syscall) const static_cast(map_impl_ptr.get()); return do_delete(impl); } - #endif +#endif case bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS: { if (!from_syscall) { // Map in maps only support update from syscall @@ -447,8 +447,9 @@ int bpf_map_handler::map_init(managed_shared_memory &memory) auto container_name = get_container_name(); switch (type) { case bpf_map_type::BPF_MAP_TYPE_HASH: { - map_impl_ptr = memory.construct( - container_name.c_str())(memory, max_entries, key_size, value_size); + map_impl_ptr = + memory.construct(container_name.c_str())( + memory, max_entries, key_size, value_size); return 0; } case bpf_map_type::BPF_MAP_TYPE_ARRAY: { @@ -488,10 +489,11 @@ int bpf_map_handler::map_init(managed_shared_memory &memory) } case bpf_map_type::BPF_MAP_TYPE_PERCPU_HASH: { map_impl_ptr = memory.construct( - container_name.c_str())(memory, key_size, value_size); + container_name.c_str())(memory, key_size, value_size, + max_entries); return 0; } - #ifdef BPFTIME_BUILD_WITH_LIBBPF +#ifdef BPFTIME_BUILD_WITH_LIBBPF case bpf_map_type::BPF_MAP_TYPE_KERNEL_USER_ARRAY: { map_impl_ptr = memory.construct( container_name.c_str())(memory, attr.kernel_bpf_map_id); @@ -523,7 +525,7 @@ int bpf_map_handler::map_init(managed_shared_memory &memory) max_entries); return 0; } - #endif +#endif case bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS: { map_impl_ptr = memory.construct( container_name.c_str())(memory, max_entries); @@ -570,7 +572,7 @@ void bpf_map_handler::map_free(managed_shared_memory &memory) case bpf_map_type::BPF_MAP_TYPE_PERCPU_HASH: memory.destroy(container_name.c_str()); break; - #ifdef BPFTIME_BUILD_WITH_LIBBPF +#ifdef BPFTIME_BUILD_WITH_LIBBPF case bpf_map_type::BPF_MAP_TYPE_KERNEL_USER_ARRAY: memory.destroy( container_name.c_str()); @@ -590,7 +592,7 @@ void bpf_map_handler::map_free(managed_shared_memory &memory) case bpf_map_type::BPF_MAP_TYPE_PROG_ARRAY: memory.destroy(container_name.c_str()); break; - #endif +#endif default: auto func_ptr = global_map_ops_table[(int)type].map_free; if (func_ptr) { diff --git a/runtime/unit-test/maps/kernel_unit_tests.cpp b/runtime/unit-test/maps/kernel_unit_tests.cpp index c181cb80..36af67ce 100644 --- a/runtime/unit-test/maps/kernel_unit_tests.cpp +++ b/runtime/unit-test/maps/kernel_unit_tests.cpp @@ -1,3 +1,4 @@ +#include "bpf_map/userspace/per_cpu_hash_map.hpp" #include "catch2/catch_test_macros.hpp" #include "linux/bpf.h" #include "unit-test/common_def.hpp" @@ -5,6 +6,8 @@ #include #include #include "bpf_map/userspace/var_hash_map.hpp" +#include +#include static const char *SHM_NAME = "_HASH_MAP_TEST"; using namespace boost::interprocess; @@ -106,3 +109,144 @@ TEST_CASE("Test hash map (kernel)") REQUIRE(map.map_get_next_key(&key, &next_key) < 0); REQUIRE(errno == ENOENT); } + +TEST_CASE("test_hashmap_sizes (kernel)") +{ + shm_remove remover(SHM_NAME); + managed_shared_memory mem(boost::interprocess::create_only, SHM_NAME, + 20 << 20); + int i, j; + + for (i = 1; i <= 512; i <<= 1) + for (j = 1; j <= 1 << 18; j <<= 1) { + var_size_hash_map_impl map(mem, i, j, 2); + usleep(10); + } +} + +/** Macros from kernel source */ +#define __bpf_percpu_val_align __attribute__((__aligned__(8))) + +#define BPF_DECLARE_PERCPU(type, name) \ + struct { \ + type v; /* padding */ \ + } __bpf_percpu_val_align name[sysconf(_SC_NPROCESSORS_ONLN)] +#define bpf_percpu(name, cpu) name[(cpu)].v + +TEST_CASE("test_hashmap_percpu (kernel)") +{ + shm_remove remover(SHM_NAME); + managed_shared_memory mem(boost::interprocess::create_only, SHM_NAME, + 20 << 20); + + unsigned int nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); + BPF_DECLARE_PERCPU(long, value); + long long key, next_key, first_key; + int expected_key_mask = 0; + int i; + per_cpu_hash_map_impl map(mem, sizeof(key), + sizeof(bpf_percpu(value, 0)), 2); + + for (i = 0; i < (int)nr_cpus; i++) + bpf_percpu(value, i) = i + 100; + + key = 1; + /* Insert key=1 element. */ + REQUIRE(!(expected_key_mask & key)); + REQUIRE(map.elem_update_userspace(&key, value, BPF_ANY) == 0); + + /* Lookup and delete elem key=1 and check value. */ + REQUIRE((map.lookup_and_delete_userspace(&key, value) == 0 && + bpf_percpu(value, 0) == 100)); + + for (i = 0; i < (int)nr_cpus; i++) + bpf_percpu(value, i) = i + 100; + + /* Insert key=1 element which should not exist. */ + REQUIRE(map.elem_update_userspace(&key, value, BPF_NOEXIST) == 0); + expected_key_mask |= key; + + /* BPF_NOEXIST means add new element if it doesn't exist. */ + REQUIRE((map.elem_update_userspace(&key, value, BPF_NOEXIST) < 0 && + /* key=1 already exists. */ + errno == EEXIST)); + + /* -1 is an invalid flag. */ + REQUIRE((map.elem_update(&key, value, -1) < 0 && errno == EINVAL)); + + const auto lookup_helper = [&](const void *key, void *value) -> long { + auto returned_value = map.elem_lookup_userspace(key); + if (!returned_value) + return -1; + memcpy(value, returned_value, + map.get_value_size() * map.getncpu()); + return 0; + }; + + /* Check that key=1 can be found. Value could be 0 if the lookup + * was run from a different CPU. + */ + bpf_percpu(value, 0) = 1; + REQUIRE((lookup_helper(&key, value) == 0 && + bpf_percpu(value, 0) == 100)); + + key = 2; + /* Check that key=2 is not found. */ + REQUIRE((lookup_helper(&key, value) < 0 && errno == ENOENT)); + + /* BPF_EXIST means update existing element. */ + REQUIRE((map.elem_update_userspace(&key, value, BPF_EXIST) < 0 && + /* key=2 is not there. */ + errno == ENOENT)); + + /* Insert key=2 element. */ + REQUIRE(!(expected_key_mask & key)); + REQUIRE(map.elem_update_userspace(&key, value, BPF_NOEXIST) == 0); + expected_key_mask |= key; + + /* key=1 and key=2 were inserted, check that key=0 cannot be + * inserted due to max_entries limit. + */ + key = 0; + REQUIRE((map.elem_update_userspace(&key, value, BPF_NOEXIST) < 0 && + errno == E2BIG)); + + /* Check that key = 0 doesn't exist. */ + REQUIRE((map.elem_delete_userspace(&key) < 0 && errno == ENOENT)); + + /* Iterate over two elements. */ + REQUIRE((map.map_get_next_key(NULL, &first_key) == 0 && + ((expected_key_mask & first_key) == first_key))); + while (!map.map_get_next_key(&key, &next_key)) { + if (first_key) { + REQUIRE(next_key == first_key); + first_key = 0; + } + REQUIRE((expected_key_mask & next_key) == next_key); + expected_key_mask &= ~next_key; + + REQUIRE(lookup_helper(&next_key, value) == 0); + + for (i = 0; i < (int)nr_cpus; i++) + REQUIRE(bpf_percpu(value, i) == i + 100); + + key = next_key; + } + REQUIRE(errno == ENOENT); + + /* Update with BPF_EXIST. */ + key = 1; + REQUIRE(map.elem_update_userspace(&key, value, BPF_EXIST) == 0); + + /* Delete both elements. */ + key = 1; + REQUIRE(map.elem_delete_userspace(&key) == 0); + key = 2; + REQUIRE(map.elem_delete_userspace(&key) == 0); + REQUIRE((map.elem_delete_userspace(&key) < 0 && errno == ENOENT)); + + key = 0; + /* Check that map is empty. */ + REQUIRE((map.map_get_next_key(NULL, &next_key) < 0 && errno == ENOENT)); + REQUIRE((map.map_get_next_key(&key, &next_key) < 0 && errno == ENOENT)); +} diff --git a/runtime/unit-test/maps/test_bpftime_hash_map.cpp b/runtime/unit-test/maps/test_bpftime_hash_map.cpp index 6cdf8520..7f19a834 100644 --- a/runtime/unit-test/maps/test_bpftime_hash_map.cpp +++ b/runtime/unit-test/maps/test_bpftime_hash_map.cpp @@ -22,127 +22,138 @@ using namespace boost::interprocess; using namespace bpftime; -TEST_CASE("bpftime_hash_map basic operations", "[bpftime_hash_map]") { - managed_shared_memory segment(open_or_create, "bpftime_hash_map_test", 65536); - - size_t num_buckets = 10; - size_t key_size = 4; // Example key size - size_t value_size = 8; // Example value size - - bpftime_hash_map map(segment, num_buckets, key_size, value_size); - - SECTION("Insert and Lookup") { - int key1 = 1234; - int64_t value1 = 5678; - int key2 = 4321; - int64_t value2 = 8765; - - REQUIRE(map.elem_update(&key1, &value1) == true); - REQUIRE(map.elem_update(&key2, &value2) == true); - - REQUIRE(map.elem_lookup(&key1) != nullptr); - REQUIRE(*(int64_t*)map.elem_lookup(&key1) == value1); - - REQUIRE(map.elem_lookup(&key2) != nullptr); - REQUIRE(*(int64_t*)map.elem_lookup(&key2) == value2); - - int key3 = 9999; - REQUIRE(map.elem_lookup(&key3) == nullptr); - } - - SECTION("Update existing element") { - int key = 1234; - int64_t value1 = 5678; - int64_t value2 = 8765; - - REQUIRE(map.elem_update(&key, &value1) == true); - REQUIRE(map.elem_lookup(&key) != nullptr); - REQUIRE(*(int64_t*)map.elem_lookup(&key) == value1); - - REQUIRE(map.elem_update(&key, &value2) == true); - REQUIRE(map.elem_lookup(&key) != nullptr); - REQUIRE(*(int64_t*)map.elem_lookup(&key) == value2); - } - - SECTION("Delete element") { - int key1 = 1234; - int64_t value1 = 5678; - int key2 = 4321; - int64_t value2 = 8765; - - REQUIRE(map.elem_update(&key1, &value1) == true); - REQUIRE(map.elem_update(&key2, &value2) == true); - - REQUIRE(map.elem_delete(&key1) == true); - REQUIRE(map.elem_lookup(&key1) == nullptr); - REQUIRE(map.elem_lookup(&key2) != nullptr); - REQUIRE(*(int64_t*)map.elem_lookup(&key2) == value2); - - REQUIRE(map.elem_delete(&key2) == true); - REQUIRE(map.elem_lookup(&key2) == nullptr); - } - - SECTION("Get element count") { - int key1 = 1234; - int64_t value1 = 5678; - int key2 = 4321; - int64_t value2 = 8765; - - REQUIRE(map.get_elem_count() == 0); - - REQUIRE(map.elem_update(&key1, &value1) == true); - REQUIRE(map.get_elem_count() == 1); - - REQUIRE(map.elem_update(&key2, &value2) == true); - REQUIRE(map.get_elem_count() == 2); - - REQUIRE(map.elem_delete(&key1) == true); - REQUIRE(map.get_elem_count() == 1); - - REQUIRE(map.elem_delete(&key2) == true); - REQUIRE(map.get_elem_count() == 0); - } - - SECTION("Insert more elements than num_buckets") { - int key; - int64_t value; - - for (size_t i = 0; i < num_buckets; ++i) { - key = i; - value = i * 100; - REQUIRE(map.elem_update(&key, &value) == true); - } - - key = num_buckets; - value = num_buckets * 100; - REQUIRE(map.elem_update(&key, &value) == false); // Should fail as the map is full - - for (size_t i = 0; i < num_buckets; ++i) { - key = i; - REQUIRE(map.elem_lookup(&key) != nullptr); - REQUIRE(*(int64_t*)map.elem_lookup(&key) == i * 100); - } - } - - SECTION("Insert, delete, and re-insert elements") { - int key1 = 1234; - int64_t value1 = 5678; - int key2 = 4321; - int64_t value2 = 8765; - int key3 = 5678; - int64_t value3 = 4321; - - REQUIRE(map.elem_update(&key1, &value1) == true); - REQUIRE(map.elem_update(&key2, &value2) == true); - - REQUIRE(map.elem_delete(&key1) == true); - REQUIRE(map.elem_lookup(&key1) == nullptr); - - REQUIRE(map.elem_update(&key3, &value3) == true); - REQUIRE(map.elem_lookup(&key3) != nullptr); - REQUIRE(*(int64_t*)map.elem_lookup(&key3) == value3); - - REQUIRE(map.elem_lookup(&key2) != nullptr); - REQUIRE(*(int64_t*)map.elem_lookup(&key2) == value2); - } +TEST_CASE("bpftime_hash_map basic operations", "[bpftime_hash_map]") +{ + managed_shared_memory segment(open_or_create, "bpftime_hash_map_test", + 65536); + + size_t num_buckets = 10; + size_t key_size = 4; // Example key size + size_t value_size = 8; // Example value size + + bpftime_hash_map map(segment, num_buckets, key_size, value_size); + + SECTION("Insert and Lookup") + { + int key1 = 1234; + int64_t value1 = 5678; + int key2 = 4321; + int64_t value2 = 8765; + + REQUIRE(map.elem_update(&key1, &value1) == true); + REQUIRE(map.elem_update(&key2, &value2) == true); + + REQUIRE(map.elem_lookup(&key1) != nullptr); + REQUIRE(*(int64_t *)map.elem_lookup(&key1) == value1); + + REQUIRE(map.elem_lookup(&key2) != nullptr); + REQUIRE(*(int64_t *)map.elem_lookup(&key2) == value2); + + int key3 = 9999; + REQUIRE(map.elem_lookup(&key3) == nullptr); + } + + SECTION("Update existing element") + { + int key = 1234; + int64_t value1 = 5678; + int64_t value2 = 8765; + + REQUIRE(map.elem_update(&key, &value1) == true); + REQUIRE(map.elem_lookup(&key) != nullptr); + REQUIRE(*(int64_t *)map.elem_lookup(&key) == value1); + + REQUIRE(map.elem_update(&key, &value2) == true); + REQUIRE(map.elem_lookup(&key) != nullptr); + REQUIRE(*(int64_t *)map.elem_lookup(&key) == value2); + } + + SECTION("Delete element") + { + int key1 = 1234; + int64_t value1 = 5678; + int key2 = 4321; + int64_t value2 = 8765; + + REQUIRE(map.elem_update(&key1, &value1) == true); + REQUIRE(map.elem_update(&key2, &value2) == true); + + REQUIRE(map.elem_delete(&key1) == true); + REQUIRE(map.elem_lookup(&key1) == nullptr); + REQUIRE(map.elem_lookup(&key2) != nullptr); + REQUIRE(*(int64_t *)map.elem_lookup(&key2) == value2); + + REQUIRE(map.elem_delete(&key2) == true); + REQUIRE(map.elem_lookup(&key2) == nullptr); + } + + SECTION("Get element count") + { + int key1 = 1234; + int64_t value1 = 5678; + int key2 = 4321; + int64_t value2 = 8765; + + REQUIRE(map.get_elem_count() == 0); + + REQUIRE(map.elem_update(&key1, &value1) == true); + REQUIRE(map.get_elem_count() == 1); + + REQUIRE(map.elem_update(&key2, &value2) == true); + REQUIRE(map.get_elem_count() == 2); + + REQUIRE(map.elem_delete(&key1) == true); + REQUIRE(map.get_elem_count() == 1); + + REQUIRE(map.elem_delete(&key2) == true); + REQUIRE(map.get_elem_count() == 0); + } + + SECTION("Insert more elements than num_buckets") + { + int key; + int64_t value; + + for (size_t i = 0; i < num_buckets; ++i) { + key = i; + value = i * 100; + REQUIRE(map.elem_update(&key, &value) == true); + } + + key = num_buckets; + value = num_buckets * 100; + REQUIRE(map.elem_update(&key, &value) == false); // Should fail + // as the map + // is full + + for (size_t i = 0; i < num_buckets; ++i) { + key = i; + REQUIRE(map.elem_lookup(&key) != nullptr); + REQUIRE(*(int64_t *)map.elem_lookup(&key) == + (int64_t)i * 100); + } + } + + SECTION("Insert, delete, and re-insert elements") + { + int key1 = 1234; + int64_t value1 = 5678; + int key2 = 4321; + int64_t value2 = 8765; + int key3 = 5678; + int64_t value3 = 4321; + + REQUIRE(map.elem_update(&key1, &value1) == true); + REQUIRE(map.elem_update(&key2, &value2) == true); + + REQUIRE(map.elem_delete(&key1) == true); + REQUIRE(map.elem_lookup(&key1) == nullptr); + + REQUIRE(map.elem_update(&key3, &value3) == true); + REQUIRE(map.elem_lookup(&key3) != nullptr); + REQUIRE(*(int64_t *)map.elem_lookup(&key3) == value3); + + REQUIRE(map.elem_lookup(&key2) != nullptr); + REQUIRE(*(int64_t *)map.elem_lookup(&key2) == value2); + } } diff --git a/runtime/unit-test/maps/test_per_cpu_hash.cpp b/runtime/unit-test/maps/test_per_cpu_hash.cpp index e709a042..9c953a2a 100644 --- a/runtime/unit-test/maps/test_per_cpu_hash.cpp +++ b/runtime/unit-test/maps/test_per_cpu_hash.cpp @@ -38,7 +38,7 @@ TEST_CASE("Test basic operations of hash map") SECTION("Test writing from helpers, and read from userspace") { - per_cpu_hash_map_impl map(mem, 4, 8); + per_cpu_hash_map_impl map(mem, 4, 8, 1 << 20); for (uint32_t j = 0; j < ncpu; j++) { ensure_on_certain_cpu(j, [&]() { for (uint32_t i = 0; i < 100; i++) { @@ -64,7 +64,7 @@ TEST_CASE("Test basic operations of hash map") SECTION("Test writing from userspace, and reading & updating from helpers") { - per_cpu_hash_map_impl map(mem, 4, 8); + per_cpu_hash_map_impl map(mem, 4, 8, 1 << 20); std::vector buf(ncpu); for (uint32_t j = 0; j < ncpu; j++) { buf[j] = j;