Skip to content

Commit e249023

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

File tree

2 files changed

+332
-2
lines changed

2 files changed

+332
-2
lines changed

src/pool/pool_disjoint.c

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

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

78154
static void initialize_disjoint_ctl(void) {
79155
CTL_REGISTER_MODULE(&disjoint_ctl_root, disjoint);

test/pools/disjoint_pool_ctl.cpp

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

0 commit comments

Comments
 (0)