Skip to content

Commit 8fe24d4

Browse files
committed
[disjoint] Add CTL memory used/reserved metrics
1 parent ac85b70 commit 8fe24d4

File tree

2 files changed

+319
-0
lines changed

2 files changed

+319
-0
lines changed

src/pool/pool_disjoint.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,86 @@ static umf_result_t CTL_WRITE_HANDLER(name)(void *ctx,
7272
return UMF_RESULT_SUCCESS;
7373
}
7474

75+
static umf_result_t CTL_READ_HANDLER(used_memory)(void *ctx, umf_ctl_query_source_t source,
76+
void *arg, size_t size,
77+
umf_ctl_index_utlist_t *indexes,
78+
const char *extra_name,
79+
umf_ctl_query_type_t queryType) {
80+
(void)source, (void)indexes, (void)queryType, (void)extra_name;
81+
disjoint_pool_t *pool = (disjoint_pool_t *)ctx;
82+
83+
if (arg == NULL || size < sizeof(size_t)) {
84+
return UMF_RESULT_ERROR_INVALID_ARGUMENT;
85+
}
86+
87+
size_t used_memory = 0;
88+
89+
// Calculate used memory across all buckets
90+
for (size_t i = 0; i < pool->buckets_num; i++) {
91+
bucket_t *bucket = pool->buckets[i];
92+
utils_mutex_lock(&bucket->bucket_lock);
93+
94+
// Count allocated chunks in available slabs
95+
slab_list_item_t *it;
96+
for (it = bucket->available_slabs; it != NULL; it = it->next) {
97+
slab_t *slab = it->val;
98+
used_memory += slab->num_chunks_allocated * bucket->size;
99+
}
100+
101+
// Count allocated chunks in unavailable slabs (all chunks allocated)
102+
for (it = bucket->unavailable_slabs; it != NULL; it = it->next) {
103+
slab_t *slab = it->val;
104+
used_memory += slab->num_chunks_allocated * bucket->size;
105+
}
106+
107+
utils_mutex_unlock(&bucket->bucket_lock);
108+
}
109+
110+
*(size_t *)arg = used_memory;
111+
return UMF_RESULT_SUCCESS;
112+
}
113+
114+
static umf_result_t CTL_READ_HANDLER(reserved_memory)(void *ctx, umf_ctl_query_source_t source,
115+
void *arg, size_t size,
116+
umf_ctl_index_utlist_t *indexes,
117+
const char *extra_name,
118+
umf_ctl_query_type_t queryType) {
119+
(void)source, (void)indexes, (void)queryType, (void)extra_name;
120+
disjoint_pool_t *pool = (disjoint_pool_t *)ctx;
121+
122+
if (arg == NULL || size < sizeof(size_t)) {
123+
return UMF_RESULT_ERROR_INVALID_ARGUMENT;
124+
}
125+
126+
size_t reserved_memory = 0;
127+
128+
// Calculate reserved memory across all buckets
129+
for (size_t i = 0; i < pool->buckets_num; i++) {
130+
bucket_t *bucket = pool->buckets[i];
131+
utils_mutex_lock(&bucket->bucket_lock);
132+
133+
// Count all slabs (both available and unavailable)
134+
slab_list_item_t *it;
135+
for (it = bucket->available_slabs; it != NULL; it = it->next) {
136+
slab_t *slab = it->val;
137+
reserved_memory += slab->slab_size;
138+
}
139+
140+
for (it = bucket->unavailable_slabs; it != NULL; it = it->next) {
141+
slab_t *slab = it->val;
142+
reserved_memory += slab->slab_size;
143+
}
144+
145+
utils_mutex_unlock(&bucket->bucket_lock);
146+
}
147+
148+
*(size_t *)arg = reserved_memory;
149+
return UMF_RESULT_SUCCESS;
150+
}
151+
75152
static const umf_ctl_node_t CTL_NODE(disjoint)[] = {CTL_LEAF_RW(name),
153+
CTL_LEAF_RO(used_memory),
154+
CTL_LEAF_RO(reserved_memory),
76155
CTL_NODE_END};
77156

78157
static void initialize_disjoint_ctl(void) {

test/pools/disjoint_pool_ctl.cpp

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
#include <umf/pools/pool_disjoint.h>
1111
#include <umf/providers/provider_os_memory.h>
1212

13+
#include <vector>
14+
1315
#include "base.hpp"
16+
#include "utils_assert.h"
1417
#include "utils_log.h"
1518

1619
using umf_test::test;
@@ -152,3 +155,240 @@ TEST_F(test, disjointCtlChangeNameTwice) {
152155
ASSERT_SUCCESS(umfDisjointPoolParamsDestroy(params));
153156
ASSERT_SUCCESS(umfOsMemoryProviderParamsDestroy(os_memory_provider_params));
154157
}
158+
159+
TEST_F(test, disjointCtlUsedMemory) {
160+
umf_os_memory_provider_params_handle_t os_memory_provider_params = nullptr;
161+
if (UMF_RESULT_ERROR_NOT_SUPPORTED ==
162+
umfOsMemoryProviderParamsCreate(&os_memory_provider_params)) {
163+
GTEST_SKIP() << "OS memory provider is not supported!";
164+
}
165+
166+
ProviderWrapper providerWrapper(umfOsMemoryProviderOps(),
167+
os_memory_provider_params);
168+
if (providerWrapper.get() == NULL) {
169+
GTEST_SKIP() << "OS memory provider is not supported!";
170+
}
171+
172+
umf_disjoint_pool_params_handle_t params = nullptr;
173+
ASSERT_SUCCESS(umfDisjointPoolParamsCreate(&params));
174+
PoolWrapper poolWrapper(providerWrapper.get(), umfDisjointPoolOps(),
175+
params);
176+
177+
// Initially, used memory should be 0
178+
size_t used_memory = 0;
179+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.used_memory",
180+
poolWrapper.get(), &used_memory, sizeof(used_memory)));
181+
ASSERT_EQ(used_memory, 0);
182+
183+
// Allocate some memory
184+
void *ptr1 = umfPoolMalloc(poolWrapper.get(), 1024);
185+
ASSERT_NE(ptr1, nullptr);
186+
187+
// Check that used memory increased
188+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.used_memory",
189+
poolWrapper.get(), &used_memory, sizeof(used_memory)));
190+
ASSERT_GE(used_memory, 1024);
191+
192+
// Allocate more memory
193+
void *ptr2 = umfPoolMalloc(poolWrapper.get(), 2048);
194+
ASSERT_NE(ptr2, nullptr);
195+
196+
size_t used_memory2 = 0;
197+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.used_memory",
198+
poolWrapper.get(), &used_memory2, sizeof(used_memory2)));
199+
ASSERT_GE(used_memory2, used_memory + 2048);
200+
201+
// Free memory
202+
ASSERT_SUCCESS(umfPoolFree(poolWrapper.get(), ptr1));
203+
ASSERT_SUCCESS(umfPoolFree(poolWrapper.get(), ptr2));
204+
205+
// Check that used memory is still greater than 0
206+
size_t used_memory3 = 0;
207+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.used_memory",
208+
poolWrapper.get(), &used_memory3, sizeof(used_memory3)));
209+
ASSERT_EQ(used_memory3, 0);
210+
211+
// Allocate again at least slab_min_size
212+
void *ptr3 = umfPoolMalloc(poolWrapper.get(), 65536); // Default slab_min_size is 64KB
213+
ASSERT_NE(ptr3, nullptr);
214+
215+
// Check that used memory increased
216+
size_t used_memory4 = 0;
217+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.used_memory",
218+
poolWrapper.get(), &used_memory4, sizeof(used_memory4)));
219+
ASSERT_GT(used_memory4, used_memory3);
220+
221+
// Clean up
222+
ASSERT_SUCCESS(umfDisjointPoolParamsDestroy(params));
223+
ASSERT_SUCCESS(umfOsMemoryProviderParamsDestroy(os_memory_provider_params));
224+
}
225+
226+
TEST_F(test, disjointCtlReservedMemory) {
227+
umf_os_memory_provider_params_handle_t os_memory_provider_params = nullptr;
228+
const size_t slab_min_size = 64 * 1024; // Default slab_min_size is 64KB
229+
230+
if (UMF_RESULT_ERROR_NOT_SUPPORTED ==
231+
umfOsMemoryProviderParamsCreate(&os_memory_provider_params)) {
232+
GTEST_SKIP() << "OS memory provider is not supported!";
233+
}
234+
235+
ProviderWrapper providerWrapper(umfOsMemoryProviderOps(),
236+
os_memory_provider_params);
237+
if (providerWrapper.get() == NULL) {
238+
GTEST_SKIP() << "OS memory provider is not supported!";
239+
}
240+
241+
umf_disjoint_pool_params_handle_t params = nullptr;
242+
ASSERT_SUCCESS(umfDisjointPoolParamsCreate(&params));
243+
PoolWrapper poolWrapper(providerWrapper.get(), umfDisjointPoolOps(),
244+
params);
245+
246+
// Initially, reserved memory should be 0
247+
size_t reserved_memory = 0;
248+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.reserved_memory",
249+
poolWrapper.get(), &reserved_memory, sizeof(reserved_memory)));
250+
ASSERT_EQ(reserved_memory, 0);
251+
252+
// Allocate some memory
253+
void *ptr1 = umfPoolMalloc(poolWrapper.get(), 1024);
254+
ASSERT_NE(ptr1, nullptr);
255+
256+
// Check that reserved memory increased (should be at least slab_min_size)
257+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.reserved_memory",
258+
poolWrapper.get(), &reserved_memory, sizeof(reserved_memory)));
259+
ASSERT_GE(reserved_memory, slab_min_size); // Default slab_min_size is 64KB
260+
261+
// Allocate more memory in the same bucket
262+
void *ptr2 = umfPoolMalloc(poolWrapper.get(), 1024);
263+
ASSERT_NE(ptr2, nullptr);
264+
265+
size_t reserved_memory2 = 0;
266+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.reserved_memory",
267+
poolWrapper.get(), &reserved_memory2, sizeof(reserved_memory2)));
268+
// Reserved memory should be the same since we're using the same slab
269+
ASSERT_EQ(reserved_memory2, slab_min_size);
270+
271+
// Free memory - reserved memory should stay the same
272+
ASSERT_SUCCESS(umfPoolFree(poolWrapper.get(), ptr1));
273+
ASSERT_SUCCESS(umfPoolFree(poolWrapper.get(), ptr2));
274+
275+
size_t reserved_memory3 = 0;
276+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.reserved_memory",
277+
poolWrapper.get(), &reserved_memory3, sizeof(reserved_memory3)));
278+
ASSERT_EQ(reserved_memory3, slab_min_size);
279+
280+
// Clean up
281+
ASSERT_SUCCESS(umfDisjointPoolParamsDestroy(params));
282+
ASSERT_SUCCESS(umfOsMemoryProviderParamsDestroy(os_memory_provider_params));
283+
}
284+
285+
TEST_F(test, disjointCtlMemoryMetricsConsistency) {
286+
umf_os_memory_provider_params_handle_t os_memory_provider_params = nullptr;
287+
if (UMF_RESULT_ERROR_NOT_SUPPORTED ==
288+
umfOsMemoryProviderParamsCreate(&os_memory_provider_params)) {
289+
GTEST_SKIP() << "OS memory provider is not supported!";
290+
}
291+
292+
ProviderWrapper providerWrapper(umfOsMemoryProviderOps(),
293+
os_memory_provider_params);
294+
if (providerWrapper.get() == NULL) {
295+
GTEST_SKIP() << "OS memory provider is not supported!";
296+
}
297+
298+
umf_disjoint_pool_params_handle_t params = nullptr;
299+
ASSERT_SUCCESS(umfDisjointPoolParamsCreate(&params));
300+
PoolWrapper poolWrapper(providerWrapper.get(), umfDisjointPoolOps(),
301+
params);
302+
303+
const int alloc_size = 512; // Size of each allocation
304+
const int n_allocations = 10; // Number of allocations
305+
306+
// Allocate memory
307+
std::vector<void*> ptrs;
308+
for (int i = 0; i < n_allocations; i++) {
309+
void *ptr = umfPoolMalloc(poolWrapper.get(), alloc_size);
310+
ASSERT_NE(ptr, nullptr);
311+
ptrs.push_back(ptr);
312+
}
313+
314+
// Get memory metrics
315+
size_t used_memory = 0;
316+
size_t reserved_memory = 0;
317+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.used_memory",
318+
poolWrapper.get(), &used_memory, sizeof(used_memory)));
319+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.reserved_memory",
320+
poolWrapper.get(), &reserved_memory, sizeof(reserved_memory)));
321+
322+
// Used memory should be at least the total allocated
323+
ASSERT_GE(used_memory, n_allocations * alloc_size);
324+
325+
// Reserved memory should be at least the used memory
326+
ASSERT_GE(reserved_memory, used_memory);
327+
328+
// Free all memory
329+
for (void *ptr : ptrs) {
330+
ASSERT_SUCCESS(umfPoolFree(poolWrapper.get(), ptr));
331+
}
332+
333+
// Check metrics after free
334+
size_t used_memory_after = 0;
335+
size_t reserved_memory_after = 0;
336+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.used_memory",
337+
poolWrapper.get(), &used_memory_after, sizeof(used_memory_after)));
338+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.disjoint.reserved_memory",
339+
poolWrapper.get(), &reserved_memory_after, sizeof(reserved_memory_after)));
340+
341+
// Used memory should be 0 after freeing
342+
ASSERT_EQ(used_memory_after, 0);
343+
// Reserved memory should remain the same (pooling)
344+
ASSERT_EQ(reserved_memory_after, reserved_memory);
345+
346+
// Clean up
347+
ASSERT_SUCCESS(umfDisjointPoolParamsDestroy(params));
348+
ASSERT_SUCCESS(umfOsMemoryProviderParamsDestroy(os_memory_provider_params));
349+
}
350+
351+
TEST_F(test, disjointCtlMemoryMetricsInvalidArgs) {
352+
umf_os_memory_provider_params_handle_t os_memory_provider_params = nullptr;
353+
if (UMF_RESULT_ERROR_NOT_SUPPORTED ==
354+
umfOsMemoryProviderParamsCreate(&os_memory_provider_params)) {
355+
GTEST_SKIP() << "OS memory provider is not supported!";
356+
}
357+
358+
ProviderWrapper providerWrapper(umfOsMemoryProviderOps(),
359+
os_memory_provider_params);
360+
if (providerWrapper.get() == NULL) {
361+
GTEST_SKIP() << "OS memory provider is not supported!";
362+
}
363+
364+
umf_disjoint_pool_params_handle_t params = nullptr;
365+
ASSERT_SUCCESS(umfDisjointPoolParamsCreate(&params));
366+
PoolWrapper poolWrapper(providerWrapper.get(), umfDisjointPoolOps(),
367+
params);
368+
369+
// Test invalid arguments
370+
size_t value = 0;
371+
372+
// NULL arg pointer
373+
ASSERT_EQ(umfCtlGet("umf.pool.by_handle.disjoint.used_memory",
374+
poolWrapper.get(), NULL, sizeof(value)),
375+
UMF_RESULT_ERROR_INVALID_ARGUMENT);
376+
377+
// Size too small
378+
ASSERT_EQ(umfCtlGet("umf.pool.by_handle.disjoint.used_memory",
379+
poolWrapper.get(), &value, sizeof(int)),
380+
UMF_RESULT_ERROR_INVALID_ARGUMENT);
381+
382+
// Same tests for reserved_memory
383+
ASSERT_EQ(umfCtlGet("umf.pool.by_handle.disjoint.reserved_memory",
384+
poolWrapper.get(), NULL, sizeof(value)),
385+
UMF_RESULT_ERROR_INVALID_ARGUMENT);
386+
387+
ASSERT_EQ(umfCtlGet("umf.pool.by_handle.disjoint.reserved_memory",
388+
poolWrapper.get(), &value, sizeof(int)),
389+
UMF_RESULT_ERROR_INVALID_ARGUMENT);
390+
391+
// Clean up
392+
ASSERT_SUCCESS(umfDisjointPoolParamsDestroy(params));
393+
ASSERT_SUCCESS(umfOsMemoryProviderParamsDestroy(os_memory_provider_params));
394+
}

0 commit comments

Comments
 (0)