Skip to content

Commit b03a51a

Browse files
committed
expose disjoint pool stats thru CTL
1 parent d291734 commit b03a51a

File tree

3 files changed

+315
-4
lines changed

3 files changed

+315
-4
lines changed

src/memory_pool.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ typedef struct by_name_arg_t {
313313
} by_name_arg_t;
314314

315315
// parses optional size_t argument. if arg is not integer then sets out to size_max
316-
int by_name_index_parser(const void *arg, void *dest, size_t dest_size) {
316+
static int by_name_index_parser(const void *arg, void *dest, size_t dest_size) {
317317
size_t *out = (size_t *)dest;
318318

319319
if (arg == NULL) {

src/pool/pool_disjoint.c

Lines changed: 169 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,178 @@ CTL_READ_HANDLER(reserved_memory)(void *ctx, umf_ctl_query_source_t source,
148148
return UMF_RESULT_SUCCESS;
149149
}
150150

151-
static const umf_ctl_node_t CTL_NODE(stats)[] = {CTL_LEAF_RO(used_memory),
152-
CTL_LEAF_RO(reserved_memory)};
151+
static umf_result_t CTL_READ_HANDLER(count)(void *ctx,
152+
umf_ctl_query_source_t source,
153+
void *arg, size_t size,
154+
umf_ctl_index_utlist_t *indexes) {
155+
(void)source, (void)indexes;
156+
157+
disjoint_pool_t *pool = (disjoint_pool_t *)ctx;
158+
if (arg == NULL || size != sizeof(size_t)) {
159+
return UMF_RESULT_ERROR_INVALID_ARGUMENT;
160+
}
161+
162+
if (*(size_t *)indexes->arg != SIZE_MAX) {
163+
LOG_ERR("to read bucket count, you must call it without bucket id");
164+
return UMF_RESULT_ERROR_INVALID_ARGUMENT;
165+
}
166+
*(size_t *)arg = pool->buckets_num;
167+
168+
return UMF_RESULT_SUCCESS;
169+
}
170+
171+
#define DEFINE_STATS_HANDLER(NAME, MEMBER) \
172+
static umf_result_t CTL_READ_HANDLER(NAME)( \
173+
void *ctx, umf_ctl_query_source_t source, void *arg, size_t size, \
174+
umf_ctl_index_utlist_t *indexes) { \
175+
(void)source; \
176+
(void)indexes; \
177+
disjoint_pool_t *pool = (disjoint_pool_t *)ctx; \
178+
\
179+
if (arg == NULL || size != sizeof(size_t)) { \
180+
return UMF_RESULT_ERROR_INVALID_ARGUMENT; \
181+
} \
182+
\
183+
if (!pool->params.pool_trace) { \
184+
LOG_ERR("pool trace is disabled, cannot read " #NAME); \
185+
return UMF_RESULT_ERROR_NOT_SUPPORTED; \
186+
} \
187+
\
188+
size_t total = 0; \
189+
for (size_t i = 0; i < pool->buckets_num; ++i) { \
190+
bucket_t *bucket = pool->buckets[i]; \
191+
utils_mutex_lock(&bucket->bucket_lock); \
192+
total += bucket->MEMBER; \
193+
utils_mutex_unlock(&bucket->bucket_lock); \
194+
} \
195+
\
196+
*(size_t *)arg = total; \
197+
return UMF_RESULT_SUCCESS; \
198+
}
199+
200+
DEFINE_STATS_HANDLER(alloc_nr, alloc_count)
201+
DEFINE_STATS_HANDLER(alloc_pool_nr, alloc_pool_count)
202+
DEFINE_STATS_HANDLER(free_nr, free_count)
203+
DEFINE_STATS_HANDLER(curr_slabs_in_use, curr_slabs_in_use)
204+
DEFINE_STATS_HANDLER(curr_slabs_in_pool, curr_slabs_in_pool)
205+
DEFINE_STATS_HANDLER(max_slabs_in_use, max_slabs_in_use)
206+
DEFINE_STATS_HANDLER(max_slabs_in_pool, max_slabs_in_pool)
207+
208+
static const umf_ctl_node_t CTL_NODE(stats)[] = {
209+
CTL_LEAF_RO(used_memory),
210+
CTL_LEAF_RO(reserved_memory),
211+
CTL_LEAF_RO(alloc_nr),
212+
CTL_LEAF_RO(alloc_pool_nr),
213+
CTL_LEAF_RO(free_nr),
214+
CTL_LEAF_RO(curr_slabs_in_use),
215+
CTL_LEAF_RO(curr_slabs_in_pool),
216+
CTL_LEAF_RO(max_slabs_in_use),
217+
CTL_LEAF_RO(max_slabs_in_pool),
218+
CTL_NODE_END,
219+
};
220+
221+
#undef DEFINE_STATS_HANDLER
222+
223+
#ifdef UMF_DEVELOPER_MODE
224+
#define VALIDATE_BUCKETS_NAME(indexes) \
225+
if (strcmp("buckets", indexes->name) != 0) { \
226+
return UMF_RESULT_ERROR_INVALID_ARGUMENT; \
227+
}
228+
#else
229+
#define VALIDATE_BUCKETS_NAME(indexes) \
230+
do { \
231+
} while (0);
232+
#endif
233+
234+
#define DEFINE_BUCKET_STATS_HANDLER(NAME, MEMBER) \
235+
static umf_result_t CTL_READ_HANDLER(NAME, perBucket)( \
236+
void *ctx, umf_ctl_query_source_t source, void *arg, size_t size, \
237+
umf_ctl_index_utlist_t *indexes) { \
238+
(void)source; \
239+
\
240+
disjoint_pool_t *pool = (disjoint_pool_t *)ctx; \
241+
if (arg == NULL || size != sizeof(size_t)) { \
242+
LOG_ERR("arg is NULL or size is not sizeof(size_t)"); \
243+
return UMF_RESULT_ERROR_INVALID_ARGUMENT; \
244+
} \
245+
\
246+
VALIDATE_BUCKETS_NAME(indexes); \
247+
if (strcmp(#MEMBER, "size") != 0 && !pool->params.pool_trace) { \
248+
LOG_ERR("pool trace is disabled, cannot read " #NAME); \
249+
return UMF_RESULT_ERROR_NOT_SUPPORTED; \
250+
} \
251+
\
252+
size_t idx; \
253+
idx = *(size_t *)indexes->arg; \
254+
\
255+
if (idx >= pool->buckets_num) { \
256+
LOG_ERR("bucket id %zu is out of range [0, %zu)", idx, \
257+
pool->buckets_num); \
258+
return UMF_RESULT_ERROR_INVALID_ARGUMENT; \
259+
} \
260+
\
261+
bucket_t *bucket = pool->buckets[idx]; \
262+
*(size_t *)arg = bucket->MEMBER; \
263+
\
264+
return UMF_RESULT_SUCCESS; \
265+
}
266+
267+
DEFINE_BUCKET_STATS_HANDLER(alloc_nr, alloc_count)
268+
DEFINE_BUCKET_STATS_HANDLER(alloc_pool_nr, alloc_pool_count)
269+
DEFINE_BUCKET_STATS_HANDLER(free_nr, free_count)
270+
DEFINE_BUCKET_STATS_HANDLER(curr_slabs_in_use, curr_slabs_in_use)
271+
DEFINE_BUCKET_STATS_HANDLER(curr_slabs_in_pool, curr_slabs_in_pool)
272+
DEFINE_BUCKET_STATS_HANDLER(max_slabs_in_use, max_slabs_in_use)
273+
DEFINE_BUCKET_STATS_HANDLER(max_slabs_in_pool, max_slabs_in_pool)
274+
275+
static const umf_ctl_node_t CTL_NODE(stats, perBucket)[] = {
276+
CTL_LEAF_RO(alloc_nr, perBucket),
277+
CTL_LEAF_RO(alloc_pool_nr, perBucket),
278+
CTL_LEAF_RO(free_nr, perBucket),
279+
CTL_LEAF_RO(curr_slabs_in_use, perBucket),
280+
CTL_LEAF_RO(curr_slabs_in_pool, perBucket),
281+
CTL_LEAF_RO(max_slabs_in_use, perBucket),
282+
CTL_LEAF_RO(max_slabs_in_pool, perBucket),
283+
CTL_NODE_END,
284+
};
285+
286+
// Not a counter; but it is read exactly like other per-bucket stats, so we can use macro.
287+
DEFINE_BUCKET_STATS_HANDLER(size, size)
288+
289+
#undef DEFINE_BUCKET_STATS_HANDLER
290+
291+
static const umf_ctl_node_t CTL_NODE(buckets)[] = {
292+
CTL_LEAF_RO(count), CTL_LEAF_RO(size, perBucket),
293+
CTL_CHILD(stats, perBucket), CTL_NODE_END};
294+
295+
static int bucket_id_parser(const void *arg, void *dest, size_t dest_size) {
296+
size_t *out = (size_t *)dest;
297+
298+
if (arg == NULL) {
299+
*out = SIZE_MAX;
300+
return 1; // node n
301+
}
302+
303+
int ret = ctl_arg_unsigned(arg, dest, dest_size);
304+
if (ret) {
305+
*out = SIZE_MAX;
306+
return 1;
307+
}
308+
309+
return 0;
310+
}
311+
312+
static const struct ctl_argument CTL_ARG(buckets) = {
313+
sizeof(size_t),
314+
{{0, sizeof(size_t), CTL_ARG_TYPE_UNSIGNED_LONG_LONG, bucket_id_parser},
315+
CTL_ARG_PARSER_END}};
153316

154317
static void initialize_disjoint_ctl(void) {
155318
CTL_REGISTER_MODULE(&disjoint_ctl_root, stats);
156-
// CTL_REGISTER_MODULE(&disjoint_ctl_root, name);
319+
CTL_REGISTER_MODULE(&disjoint_ctl_root, buckets);
320+
// TODO: this is hack. Need some way to register module as node with argument
321+
disjoint_ctl_root.root[disjoint_ctl_root.first_free - 1].arg =
322+
&CTL_ARG(buckets);
157323
}
158324

159325
umf_result_t disjoint_pool_ctl(void *hPool,

test/pools/disjoint_pool_ctl.cpp

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,148 @@ TEST_F(test, disjointCtlMemoryMetricsInvalidArgs) {
421421
ASSERT_SUCCESS(umfDisjointPoolParamsDestroy(params));
422422
ASSERT_SUCCESS(umfOsMemoryProviderParamsDestroy(os_memory_provider_params));
423423
}
424+
425+
TEST_F(test, disjointCtlBucketStats) {
426+
umf_os_memory_provider_params_handle_t os_memory_provider_params = nullptr;
427+
if (UMF_RESULT_ERROR_NOT_SUPPORTED ==
428+
umfOsMemoryProviderParamsCreate(&os_memory_provider_params)) {
429+
GTEST_SKIP() << "OS memory provider is not supported!";
430+
}
431+
432+
ProviderWrapper providerWrapper(umfOsMemoryProviderOps(),
433+
os_memory_provider_params);
434+
if (providerWrapper.get() == NULL) {
435+
GTEST_SKIP() << "OS memory provider is not supported!";
436+
}
437+
438+
umf_disjoint_pool_params_handle_t params = nullptr;
439+
ASSERT_SUCCESS(umfDisjointPoolParamsCreate(&params));
440+
441+
// Set minimum slab size
442+
size_t slab_min_size = 64 * 1024;
443+
ASSERT_SUCCESS(umfDisjointPoolParamsSetSlabMinSize(params, slab_min_size));
444+
ASSERT_SUCCESS(umfDisjointPoolParamsSetCapacity(params, 4));
445+
ASSERT_SUCCESS(umfDisjointPoolParamsSetTrace(params, 3));
446+
447+
PoolWrapper poolWrapper(providerWrapper.get(), umfDisjointPoolOps(),
448+
params);
449+
450+
size_t arg = 0;
451+
size_t count = 0;
452+
const size_t alloc_size = 128;
453+
size_t used_bucket = SIZE_MAX;
454+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.{}.buckets.count", &count,
455+
sizeof(count), poolWrapper.get()));
456+
EXPECT_EQ(count, 57ull);
457+
458+
auto expected_bucket_size = [](size_t i) -> size_t {
459+
// Even indexes: 8 << (i/2) => 8,16,32,64,...
460+
// Odd indexes: 12 << (i/2) => 12,24,48,96,...
461+
return (i % 2 == 0) ? (size_t(8) << (i / 2)) : (size_t(12) << (i / 2));
462+
};
463+
464+
for (size_t i = 0; i < count; i++) {
465+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.{}.buckets.{}.size", &arg,
466+
sizeof(arg), poolWrapper.get(), i));
467+
EXPECT_EQ(arg, expected_bucket_size(i)) << "Failed for bucket: " << i;
468+
if (arg >= alloc_size && used_bucket == SIZE_MAX) {
469+
used_bucket = i; // Find the bucket that matches alloc_size
470+
}
471+
}
472+
473+
std::unordered_map<std::string, size_t> stats = {
474+
{"alloc_nr", 0ull},
475+
{"alloc_pool_nr", 0ull},
476+
{"free_nr", 0ull},
477+
{"curr_slabs_in_use", 0ull},
478+
{"curr_slabs_in_pool", 0ull},
479+
{"max_slabs_in_use", 0ull},
480+
{"max_slabs_in_pool", 0ull},
481+
};
482+
483+
for (const auto &s : stats) {
484+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.{}.stats.{}", &arg,
485+
sizeof(arg), poolWrapper.get(),
486+
s.first.c_str()));
487+
EXPECT_EQ(arg, s.second) << "Failed for stat: " << s.first;
488+
}
489+
490+
for (size_t i = 0; i < count; i++) {
491+
for (const auto &s : stats) {
492+
ASSERT_SUCCESS(
493+
umfCtlGet("umf.pool.by_handle.{}.buckets.{}.stats.{}", &arg,
494+
sizeof(arg), poolWrapper.get(), i, s.first.c_str()));
495+
EXPECT_EQ(arg, i == used_bucket ? s.second : 0)
496+
<< "Failed for stat: " << s.first << "bucket: " << i;
497+
}
498+
}
499+
500+
const size_t n_allocations = 10; // Number of allocations
501+
502+
// Allocate memory
503+
std::vector<void *> ptrs;
504+
for (size_t i = 0; i < n_allocations; i++) {
505+
void *ptr = umfPoolMalloc(poolWrapper.get(), alloc_size);
506+
ASSERT_NE(ptr, nullptr);
507+
ptrs.push_back(ptr);
508+
}
509+
510+
stats = {
511+
{"alloc_nr", 10ull},
512+
{"alloc_pool_nr", 9ull},
513+
{"free_nr", 0ull},
514+
{"curr_slabs_in_use", 1ull},
515+
{"curr_slabs_in_pool", 0ull},
516+
{"max_slabs_in_use", 1ull},
517+
{"max_slabs_in_pool", 0ull},
518+
};
519+
520+
for (const auto &s : stats) {
521+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.{}.stats.{}", &arg,
522+
sizeof(arg), poolWrapper.get(),
523+
s.first.c_str()));
524+
EXPECT_EQ(arg, s.second) << "Failed for stat: " << s.first;
525+
}
526+
for (size_t i = 0; i < count; i++) {
527+
for (const auto &s : stats) {
528+
ASSERT_SUCCESS(
529+
umfCtlGet("umf.pool.by_handle.{}.buckets.{}.stats.{}", &arg,
530+
sizeof(arg), poolWrapper.get(), i, s.first.c_str()));
531+
EXPECT_EQ(arg, i == used_bucket ? s.second : 0)
532+
<< "Failed for stat: " << s.first << "bucket: " << i;
533+
}
534+
}
535+
536+
// Free all memory
537+
for (void *ptr : ptrs) {
538+
ASSERT_SUCCESS(umfPoolFree(poolWrapper.get(), ptr));
539+
}
540+
541+
stats = {
542+
{"alloc_nr", 10ull}, {"alloc_pool_nr", 9ull},
543+
{"free_nr", 10ull}, {"curr_slabs_in_use", 0ull},
544+
{"curr_slabs_in_pool", 1ull}, {"max_slabs_in_use", 1ull},
545+
{"max_slabs_in_pool", 1ull},
546+
};
547+
548+
for (const auto &s : stats) {
549+
ASSERT_SUCCESS(umfCtlGet("umf.pool.by_handle.{}.stats.{}", &arg,
550+
sizeof(arg), poolWrapper.get(),
551+
s.first.c_str()));
552+
EXPECT_EQ(arg, s.second) << "Failed for stat: " << s.first;
553+
}
554+
555+
for (size_t i = 0; i < count; i++) {
556+
for (const auto &s : stats) {
557+
ASSERT_SUCCESS(
558+
umfCtlGet("umf.pool.by_handle.{}.buckets.{}.stats.{}", &arg,
559+
sizeof(arg), poolWrapper.get(), i, s.first.c_str()));
560+
EXPECT_EQ(arg, i == used_bucket ? s.second : 0)
561+
<< "Failed for stat: " << s.first << "bucket: " << i;
562+
}
563+
}
564+
565+
// Clean up
566+
ASSERT_SUCCESS(umfDisjointPoolParamsDestroy(params));
567+
ASSERT_SUCCESS(umfOsMemoryProviderParamsDestroy(os_memory_provider_params));
568+
}

0 commit comments

Comments
 (0)