Skip to content

Commit f1a7a14

Browse files
committed
Expand HOST ALL memspace tests
Those tests confirm that the HOST ALL memspace is composed of memory targets that correspond to all available NUMA node ids. They also affirm that the memory obtained from HOST ALL memspace follows UMF_NUMA_MODE_BIND policy and is made available to all discovered NUMA nodes.
1 parent 9e88229 commit f1a7a14

File tree

3 files changed

+263
-118
lines changed

3 files changed

+263
-118
lines changed

test/memspaces/memspace_helpers.hpp

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright (C) 2024 Intel Corporation
2+
// Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
5+
#ifndef UMF_MEMSPACE_HELPERS_HPP
6+
#define UMF_MEMSPACE_HELPERS_HPP
7+
8+
#include "base.hpp"
9+
#include "memspace_internal.h"
10+
#include "memspaces/memspace_numa.h"
11+
12+
#include <numa.h>
13+
#include <umf/providers/provider_os_memory.h>
14+
15+
#define SIZE_4K (4096UL)
16+
#define SIZE_4M (SIZE_4K * 1024UL)
17+
18+
struct numaNodesTest : ::umf_test::test {
19+
void SetUp() override {
20+
::umf_test::test::SetUp();
21+
22+
if (numa_available() == -1 || numa_all_nodes_ptr == nullptr) {
23+
GTEST_FAIL() << "Failed to initialize libnuma";
24+
}
25+
26+
int maxNode = numa_max_node();
27+
if (maxNode < 0) {
28+
GTEST_FAIL() << "No available numa nodes";
29+
}
30+
31+
for (int i = 0; i <= maxNode; i++) {
32+
if (numa_bitmask_isbitset(numa_all_nodes_ptr, i)) {
33+
nodeIds.emplace_back(i);
34+
maxNodeId = i;
35+
}
36+
}
37+
}
38+
39+
std::vector<size_t> nodeIds;
40+
unsigned long maxNodeId = 0;
41+
};
42+
43+
struct memspaceNumaTest : ::numaNodesTest {
44+
void SetUp() override {
45+
::numaNodesTest::SetUp();
46+
47+
enum umf_result_t ret = umfMemspaceCreateFromNumaArray(
48+
nodeIds.data(), nodeIds.size(), &hMemspace);
49+
ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
50+
ASSERT_NE(hMemspace, nullptr);
51+
}
52+
53+
void TearDown() override {
54+
::numaNodesTest::TearDown();
55+
if (hMemspace) {
56+
umfMemspaceDestroy(hMemspace);
57+
}
58+
}
59+
60+
umf_memspace_handle_t hMemspace = nullptr;
61+
};
62+
63+
struct memspaceNumaProviderTest : ::memspaceNumaTest {
64+
void SetUp() override {
65+
::memspaceNumaTest::SetUp();
66+
67+
umf_result_t ret =
68+
umfMemoryProviderCreateFromMemspace(hMemspace, nullptr, &hProvider);
69+
ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
70+
ASSERT_NE(hProvider, nullptr);
71+
}
72+
73+
void TearDown() override {
74+
::memspaceNumaTest::TearDown();
75+
76+
umfMemoryProviderDestroy(hProvider);
77+
}
78+
79+
umf_memory_provider_handle_t hProvider = nullptr;
80+
};
81+
82+
struct memspaceHostAllTest : ::numaNodesTest {
83+
void SetUp() override {
84+
::numaNodesTest::SetUp();
85+
86+
hMemspace = umfMemspaceHostAllGet();
87+
ASSERT_NE(hMemspace, nullptr);
88+
}
89+
90+
umf_memspace_handle_t hMemspace = nullptr;
91+
};
92+
93+
struct memspaceHostAllProviderTest : ::memspaceHostAllTest {
94+
void SetUp() override {
95+
::memspaceHostAllTest::SetUp();
96+
97+
umf_result_t ret =
98+
umfMemoryProviderCreateFromMemspace(hMemspace, nullptr, &hProvider);
99+
ASSERT_EQ(ret, UMF_RESULT_SUCCESS);
100+
ASSERT_NE(hProvider, nullptr);
101+
}
102+
103+
void TearDown() override {
104+
::memspaceHostAllTest::TearDown();
105+
106+
umfMemoryProviderDestroy(hProvider);
107+
}
108+
109+
umf_memory_provider_handle_t hProvider = nullptr;
110+
};
111+
112+
#endif /* UMF_MEMSPACE_HELPERS_HPP */

test/memspaces/memspace_host_all.cpp

Lines changed: 137 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,164 @@
22
// Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT.
33
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
44

5-
#include "base.hpp"
5+
#include "memory_target_numa.h"
6+
#include "memspace_helpers.hpp"
7+
#include "memspace_internal.h"
68
#include "test_helpers.h"
79

10+
#include <numa.h>
11+
#include <numaif.h>
812
#include <umf/memspace.h>
13+
#include <unordered_set>
914

1015
using umf_test::test;
1116

12-
#define SIZE_4K (4096)
13-
14-
struct predefinedMemspaceTest : test {
15-
16-
void SetUp() override {
17-
::test::SetUp();
18-
19-
hMemspace = umfMemspaceHostAllGet();
20-
UT_ASSERTne(hMemspace, nullptr);
21-
}
22-
23-
umf_memspace_handle_t hMemspace;
24-
};
25-
26-
struct predefinedMemspaceProviderTest : predefinedMemspaceTest {
27-
28-
void SetUp() override {
29-
::predefinedMemspaceTest::SetUp();
30-
31-
enum umf_result_t ret =
32-
umfMemoryProviderCreateFromMemspace(hMemspace, nullptr, &provider);
33-
UT_ASSERTeq(ret, UMF_RESULT_SUCCESS);
34-
UT_ASSERTne(provider, nullptr);
35-
}
36-
37-
void TearDown() override {
38-
::predefinedMemspaceTest::TearDown();
17+
TEST_F(numaNodesTest, memspaceGet) {
18+
umf_memspace_handle_t hMemspace = umfMemspaceHostAllGet();
19+
UT_ASSERTne(hMemspace, nullptr);
3920

40-
umfMemoryProviderDestroy(provider);
21+
// Confirm that the HOST ALL memspace is composed of all available NUMA nodes.
22+
UT_ASSERTeq(hMemspace->size, nodeIds.size());
23+
for (size_t i = 0; i < hMemspace->size; i++) {
24+
// NUMA memory target internally casts the config directly into priv.
25+
// TODO: Use the memory target API when it becomes available.
26+
struct umf_numa_memory_target_config_t *numaTargetCfg =
27+
(struct umf_numa_memory_target_config_t *)hMemspace->nodes[i]->priv;
28+
UT_ASSERT(std::find(nodeIds.begin(), nodeIds.end(),
29+
numaTargetCfg->id) != nodeIds.end());
4130
}
31+
}
4232

43-
umf_memory_provider_handle_t provider = nullptr;
44-
};
33+
TEST_F(memspaceHostAllTest, providerFromHostAllMemspace) {
34+
umf_memory_provider_handle_t hProvider = nullptr;
35+
enum umf_result_t ret =
36+
umfMemoryProviderCreateFromMemspace(hMemspace, nullptr, &hProvider);
37+
UT_ASSERTeq(ret, UMF_RESULT_SUCCESS);
38+
UT_ASSERTne(hProvider, nullptr);
4539

46-
TEST_F(test, memspaceGet) {
47-
umf_memspace_handle_t hMemspace = umfMemspaceHostAllGet();
48-
UT_ASSERTne(hMemspace, nullptr);
40+
umfMemoryProviderDestroy(hProvider);
4941
}
5042

51-
TEST_F(predefinedMemspaceProviderTest, allocFree) {
43+
TEST_F(memspaceHostAllProviderTest, allocFree) {
5244
void *ptr = nullptr;
5345
size_t size = SIZE_4K;
5446
size_t alignment = 0;
5547

5648
enum umf_result_t ret =
57-
umfMemoryProviderAlloc(provider, size, alignment, &ptr);
49+
umfMemoryProviderAlloc(hProvider, size, alignment, &ptr);
5850
UT_ASSERTeq(ret, UMF_RESULT_SUCCESS);
5951
UT_ASSERTne(ptr, nullptr);
6052

6153
memset(ptr, 0xFF, size);
6254

63-
ret = umfMemoryProviderFree(provider, ptr, size);
55+
ret = umfMemoryProviderFree(hProvider, ptr, size);
6456
UT_ASSERTeq(ret, UMF_RESULT_SUCCESS);
6557
}
58+
59+
///
60+
/// @brief Retrieves the memory policy information for \p ptr.
61+
/// @param ptr allocation pointer.
62+
/// @param maxNodeId maximum node id.
63+
/// @param mode [out] memory policy.
64+
/// @param boundNodeIds [out] node ids associated with the policy.
65+
/// @param allocNodeId [out] id of the node that allocated the memory.
66+
///
67+
static void getAllocationPolicy(void *ptr, unsigned long maxNodeId, int &mode,
68+
std::vector<size_t> &boundNodeIds,
69+
size_t &allocNodeId) {
70+
const static unsigned bitsPerUlong = sizeof(unsigned long) * 8;
71+
72+
const unsigned nrUlongs = (maxNodeId + bitsPerUlong) / bitsPerUlong;
73+
std::vector<unsigned long> memNodeMasks(nrUlongs, 0);
74+
75+
int memMode = -1;
76+
// Get policy and the nodes associated with this policy.
77+
int ret = get_mempolicy(&memMode, memNodeMasks.data(),
78+
nrUlongs * bitsPerUlong, ptr, MPOL_F_ADDR);
79+
UT_ASSERTeq(ret, 0);
80+
mode = memMode;
81+
82+
UT_ASSERTeq(boundNodeIds.size(), 0);
83+
for (size_t i = 0; i <= maxNodeId; i++) {
84+
const size_t memNodeMaskIdx = ((i + bitsPerUlong) / bitsPerUlong) - 1;
85+
const auto &memNodeMask = memNodeMasks.at(memNodeMaskIdx);
86+
87+
if (memNodeMask && (1UL << (i % bitsPerUlong))) {
88+
boundNodeIds.emplace_back(i);
89+
}
90+
}
91+
92+
// Get the node that allocated the memory at 'ptr'.
93+
int nodeId = -1;
94+
ret = get_mempolicy(&nodeId, nullptr, 0, ptr, MPOL_F_ADDR | MPOL_F_NODE);
95+
UT_ASSERTeq(ret, 0);
96+
allocNodeId = static_cast<size_t>(nodeId);
97+
}
98+
99+
TEST_F(memspaceHostAllProviderTest, memoryPolicyOOM) {
100+
// Arbitrary allocation size, should be big enough to avoid unnecessarily
101+
// prolonging the test execution.
102+
size_t size = SIZE_4M * 128;
103+
size_t alignment = 0;
104+
std::vector<void *> allocs;
105+
106+
enum umf_result_t umf_ret = UMF_RESULT_SUCCESS;
107+
// Create allocations until OOM.
108+
while (true) {
109+
void *ptr = nullptr;
110+
umf_ret = umfMemoryProviderAlloc(hProvider, size, alignment, &ptr);
111+
if (umf_ret != UMF_RESULT_SUCCESS) {
112+
break;
113+
}
114+
115+
UT_ASSERTne(ptr, nullptr);
116+
allocs.push_back(ptr);
117+
}
118+
119+
UT_ASSERTeq(umf_ret, UMF_RESULT_ERROR_MEMORY_PROVIDER_SPECIFIC);
120+
const char *msg = nullptr;
121+
int32_t err = 0;
122+
umfMemoryProviderGetLastNativeError(hProvider, &msg, &err);
123+
// In this scenario, 'UMF_OS_RESULT_ERROR_ALLOC_FAILED' indicates OOM.
124+
UT_ASSERTeq(err, UMF_OS_RESULT_ERROR_ALLOC_FAILED);
125+
126+
// When allocating until OOM, the allocations should be distributed across
127+
// all the NUMA nodes bound to 'HOST ALL' memspace, until each node runs
128+
// out of memory.
129+
UT_ASSERT(allocs.size() >= nodeIds.size());
130+
std::unordered_set<size_t> allocNodeIds;
131+
for (auto &ptr : allocs) {
132+
int mode = -1;
133+
std::vector<size_t> boundNodeIds;
134+
size_t allocNodeId = SIZE_MAX;
135+
getAllocationPolicy(ptr, maxNodeId, mode, boundNodeIds, allocNodeId);
136+
137+
// 'BIND' mode specifies that the memory is bound to a set of NUMA nodes.
138+
// In case of 'HOST ALL' memspace, those set of nodes should be all
139+
// available nodes.
140+
UT_ASSERTeq(mode, MPOL_BIND);
141+
142+
// Confirm that the memory is bound to all the nodes from 'HOST ALL'
143+
// memspace.
144+
for (auto &id : nodeIds) {
145+
auto it = std::find(boundNodeIds.begin(), boundNodeIds.end(), id);
146+
UT_ASSERT(it != boundNodeIds.end());
147+
}
148+
149+
// Confirm that the memory is allocated on one of the nodes in
150+
// 'HOST ALL' memspace.
151+
auto it = std::find(nodeIds.begin(), nodeIds.end(), allocNodeId);
152+
UT_ASSERT(it != nodeIds.end());
153+
154+
allocNodeIds.insert(allocNodeId);
155+
156+
umf_ret = umfMemoryProviderFree(hProvider, ptr, size);
157+
UT_ASSERTeq(umf_ret, UMF_RESULT_SUCCESS);
158+
}
159+
160+
// Confirm that all the NUMA nodes bound to 'HOST ALL' memspace were exhausted.
161+
for (auto &id : nodeIds) {
162+
auto it = std::find(allocNodeIds.begin(), allocNodeIds.end(), id);
163+
UT_ASSERT(it != allocNodeIds.end());
164+
}
165+
}

0 commit comments

Comments
 (0)