Skip to content

Commit e06c14b

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

File tree

3 files changed

+321
-4
lines changed

3 files changed

+321
-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: 175 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ static umf_result_t CTL_READ_HANDLER(name)(void *ctx,
4444
disjoint_pool_t *pool = (disjoint_pool_t *)ctx;
4545

4646
if (arg == NULL) {
47+
LOG_ERR("arg is NULL");
4748
return UMF_RESULT_ERROR_INVALID_ARGUMENT;
4849
}
4950

@@ -64,6 +65,7 @@ static umf_result_t CTL_WRITE_HANDLER(name)(void *ctx,
6465
(void)source, (void)indexes, (void)size;
6566
disjoint_pool_t *pool = (disjoint_pool_t *)ctx;
6667
if (arg == NULL) {
68+
LOG_ERR("arg is NULL");
6769
return UMF_RESULT_ERROR_INVALID_ARGUMENT;
6870
}
6971

@@ -81,6 +83,7 @@ CTL_READ_HANDLER(used_memory)(void *ctx, umf_ctl_query_source_t source,
8183
disjoint_pool_t *pool = (disjoint_pool_t *)ctx;
8284

8385
if (arg == NULL || size != sizeof(size_t)) {
86+
LOG_ERR("arg is NULL or size is not sizeof(size_t)");
8487
return UMF_RESULT_ERROR_INVALID_ARGUMENT;
8588
}
8689

@@ -119,6 +122,7 @@ CTL_READ_HANDLER(reserved_memory)(void *ctx, umf_ctl_query_source_t source,
119122
disjoint_pool_t *pool = (disjoint_pool_t *)ctx;
120123

121124
if (arg == NULL || size != sizeof(size_t)) {
125+
LOG_ERR("arg is NULL or size is not sizeof(size_t)");
122126
return UMF_RESULT_ERROR_INVALID_ARGUMENT;
123127
}
124128

@@ -148,12 +152,180 @@ CTL_READ_HANDLER(reserved_memory)(void *ctx, umf_ctl_query_source_t source,
148152
return UMF_RESULT_SUCCESS;
149153
}
150154

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

154323
static void initialize_disjoint_ctl(void) {
155324
CTL_REGISTER_MODULE(&disjoint_ctl_root, stats);
156-
// CTL_REGISTER_MODULE(&disjoint_ctl_root, name);
325+
CTL_REGISTER_MODULE(&disjoint_ctl_root, buckets);
326+
// TODO: this is hack. Need some way to register module as node with argument
327+
disjoint_ctl_root.root[disjoint_ctl_root.first_free - 1].arg =
328+
&CTL_ARG(buckets);
157329
}
158330

159331
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)