diff --git a/.github/workflows/reusable_compatibility.yml b/.github/workflows/reusable_compatibility.yml index fbd17a2f43..c7f84d6e39 100644 --- a/.github/workflows/reusable_compatibility.yml +++ b/.github/workflows/reusable_compatibility.yml @@ -110,7 +110,7 @@ jobs: run: > UMF_LOG="level:warning;flush:debug;output:stderr;pid:no" LD_LIBRARY_PATH=${{github.workspace}}/latest_version/build/lib/ - ctest --output-on-failure + ctest --output-on-failure -E "umf-mempolicy" # disable tests that rely on internal structures windows-build: name: Windows diff --git a/include/umf/base.h b/include/umf/base.h index 8dad184f2b..cc6b0ccbd7 100644 --- a/include/umf/base.h +++ b/include/umf/base.h @@ -50,6 +50,43 @@ typedef enum umf_result_t { UMF_RESULT_ERROR_UNKNOWN = 0x7ffffffe ///< Unknown or internal error } umf_result_t; +/// @brief Type of the CTL query +typedef enum umf_ctl_query_type { + CTL_QUERY_READ, + CTL_QUERY_WRITE, + CTL_QUERY_RUNNABLE, + CTL_QUERY_SUBTREE, + + MAX_CTL_QUERY_TYPE +} umf_ctl_query_type_t; + +/// +/// @brief Get value of a specified attribute at the given name. +/// @param name name of an attribute to be retrieved +/// @param ctx pointer to the pool or the provider +/// @param arg [out] pointer to the variable where the value will be stored +/// @return UMF_RESULT_SUCCESS on success or UMF_RESULT_ERROR_UNKNOWN on failure. +/// +umf_result_t umfCtlGet(const char *name, void *ctx, void *arg); + +/// +/// @brief Set value of a specified attribute at the given name. +/// @param name name of an attribute to be set +/// @param ctx pointer to the pool or the provider +/// @param arg [in] pointer to the value that will be set +/// @return UMF_RESULT_SUCCESS on success or UMF_RESULT_ERROR_UNKNOWN on failure. +/// +umf_result_t umfCtlSet(const char *name, void *ctx, void *arg); + +/// +/// @brief Execute callback related with the specified attribute. +/// @param name name of an attribute to be executed +/// @param ctx pointer to the pool or the provider +/// @param arg [in/out] pointer to the value, can be used as an input or output +/// @return UMF_RESULT_SUCCESS on success or UMF_RESULT_ERROR_UNKNOWN on failure. +/// +umf_result_t umfCtlExec(const char *name, void *ctx, void *arg); + #ifdef __cplusplus } #endif diff --git a/include/umf/memory_pool_ops.h b/include/umf/memory_pool_ops.h index 657f40aeaa..bf44383b44 100644 --- a/include/umf/memory_pool_ops.h +++ b/include/umf/memory_pool_ops.h @@ -125,6 +125,22 @@ typedef struct umf_memory_pool_ops_t { /// The value is undefined if the previous allocation was successful. /// umf_result_t (*get_last_allocation_error)(void *pool); + + /// + /// @brief Control operation for the memory pool. + /// The function is used to perform various control operations + /// on the memory pool. + /// + /// @param hPool handle to the memory pool. + /// @param operationType type of the operation to be performed. + /// @param name name associated with the operation. + /// @param arg argument for the operation. + /// @param queryType type of the query to be performed. + /// + /// @return umf_result_t result of the control operation. + /// + umf_result_t (*ctl)(void *hPool, int operationType, const char *name, + void *arg, umf_ctl_query_type_t queryType); } umf_memory_pool_ops_t; #ifdef __cplusplus diff --git a/include/umf/memory_provider_ops.h b/include/umf/memory_provider_ops.h index aaddd503b7..638f2975ba 100644 --- a/include/umf/memory_provider_ops.h +++ b/include/umf/memory_provider_ops.h @@ -82,7 +82,6 @@ typedef struct umf_memory_provider_ext_ops_t { /// umf_result_t (*allocation_split)(void *hProvider, void *ptr, size_t totalSize, size_t firstSize); - } umf_memory_provider_ext_ops_t; /// @@ -250,6 +249,23 @@ typedef struct umf_memory_provider_ops_t { /// @brief Optional IPC ops. The API allows sharing of memory objects across different processes. /// umf_memory_provider_ipc_ops_t ipc; + + /// + /// @brief Control operation for the memory provider. + /// The function is used to perform various control operations + /// on the memory provider. + /// + /// @param hProvider handle to the memory provider. + /// @param operationType type of the operation to be performed. + /// @param name name associated with the operation. + /// @param arg argument for the operation. + /// @param queryType type of the query to be performed. + /// + /// @return umf_result_t result of the control operation. + /// + umf_result_t (*ctl)(void *hProvider, int operationType, const char *name, + void *arg, umf_ctl_query_type_t queryType); + } umf_memory_provider_ops_t; #ifdef __cplusplus diff --git a/src/ctl/ctl.c b/src/ctl/ctl.c index 4db11ac21f..99ab2d96e6 100644 --- a/src/ctl/ctl.c +++ b/src/ctl/ctl.c @@ -24,6 +24,8 @@ #include #include +#include + #include "base_alloc/base_alloc_global.h" #include "utils/utils_common.h" #include "utlist.h" @@ -43,8 +45,9 @@ #define CTL_QUERY_NODE_SEPARATOR "." #define CTL_VALUE_ARG_SEPARATOR "," +/* GLOBAL TREE */ static int ctl_global_first_free = 0; -static struct ctl_node CTL_NODE(global)[CTL_MAX_ENTRIES]; +static umf_ctl_node_t CTL_NODE(global)[CTL_MAX_ENTRIES]; /* * This is the top level node of the ctl tree structure. Each node can contain @@ -57,7 +60,7 @@ static struct ctl_node CTL_NODE(global)[CTL_MAX_ENTRIES]; * convenience. */ struct ctl { - struct ctl_node root[CTL_MAX_ENTRIES]; + umf_ctl_node_t root[CTL_MAX_ENTRIES]; int first_free; }; @@ -78,17 +81,52 @@ char *Strdup(const char *s) { return p; } +umf_result_t umfCtlGet(const char *name, void *ctx, void *arg) { + if (name == NULL || arg == NULL || ctx == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + return ctl_query(NULL, ctx, CTL_QUERY_PROGRAMMATIC, name, CTL_QUERY_READ, + arg) + ? UMF_RESULT_ERROR_UNKNOWN + : UMF_RESULT_SUCCESS; +} + +umf_result_t umfCtlSet(const char *name, void *ctx, void *arg) { + if (name == NULL || arg == NULL || ctx == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + return ctl_query(NULL, ctx, CTL_QUERY_PROGRAMMATIC, name, CTL_QUERY_WRITE, + arg) + ? UMF_RESULT_ERROR_UNKNOWN + : UMF_RESULT_SUCCESS; +} + +umf_result_t umfCtlExec(const char *name, void *ctx, void *arg) { + if (name == NULL || arg == NULL || ctx == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + return ctl_query(NULL, ctx, CTL_QUERY_PROGRAMMATIC, name, + CTL_QUERY_RUNNABLE, arg) + ? UMF_RESULT_ERROR_UNKNOWN + : UMF_RESULT_SUCCESS; +} + /* * ctl_find_node -- (internal) searches for a matching entry point in the * provided nodes * + * Name offset is used to return the offset of the name in the query string. * The caller is responsible for freeing all of the allocated indexes, * regardless of the return value. */ -static const struct ctl_node *ctl_find_node(const struct ctl_node *nodes, - const char *name, - struct ctl_index_utlist *indexes) { - const struct ctl_node *n = NULL; +static const umf_ctl_node_t *ctl_find_node(const umf_ctl_node_t *nodes, + const char *name, + umf_ctl_index_utlist_t *indexes, + size_t *name_offset) { + assert(nodes != NULL); + assert(name != NULL); + assert(name_offset != NULL); + const umf_ctl_node_t *n = NULL; char *sptr = NULL; char *parse_str = Strdup(name); if (parse_str == NULL) { @@ -102,6 +140,11 @@ static const struct ctl_node *ctl_find_node(const struct ctl_node *nodes, * in the main ctl tree. */ while (node_name != NULL) { + *name_offset = node_name - parse_str; + if (n != NULL && n->type == CTL_NODE_SUBTREE) { + // if a subtree occurs, the subtree handler should be called + break; + } char *endptr; /* * Ignore errno from strtol: FreeBSD returns EINVAL if no @@ -111,7 +154,7 @@ static const struct ctl_node *ctl_find_node(const struct ctl_node *nodes, int tmp_errno = errno; long index_value = strtol(node_name, &endptr, 0); errno = tmp_errno; - struct ctl_index_utlist *index_entry = NULL; + umf_ctl_index_utlist_t *index_entry = NULL; if (endptr != node_name) { /* a valid index */ index_entry = umf_ba_global_alloc(sizeof(*index_entry)); if (index_entry == NULL) { @@ -128,6 +171,7 @@ static const struct ctl_node *ctl_find_node(const struct ctl_node *nodes, break; } } + if (n->name == NULL) { goto error; } @@ -152,11 +196,11 @@ static const struct ctl_node *ctl_find_node(const struct ctl_node *nodes, * ctl_delete_indexes -- * (internal) removes and frees all entries on the index list */ -static void ctl_delete_indexes(struct ctl_index_utlist *indexes) { +static void ctl_delete_indexes(umf_ctl_index_utlist_t *indexes) { if (!indexes) { return; } - struct ctl_index_utlist *elem, *tmp; + umf_ctl_index_utlist_t *elem, *tmp; LL_FOREACH_SAFE(indexes, elem, tmp) { LL_DELETE(indexes, elem); if (elem) { @@ -201,8 +245,8 @@ static void *ctl_parse_args(const struct ctl_argument *arg_proto, char *arg) { * ctl_query_get_real_args -- (internal) returns a pointer with actual argument * structure as required by the node callback */ -static void *ctl_query_get_real_args(const struct ctl_node *n, void *write_arg, - enum ctl_query_source source) { +static void *ctl_query_get_real_args(const umf_ctl_node_t *n, void *write_arg, + umf_ctl_query_source_t source) { void *real_arg = NULL; switch (source) { case CTL_QUERY_CONFIG_INPUT: @@ -222,9 +266,8 @@ static void *ctl_query_get_real_args(const struct ctl_node *n, void *write_arg, * ctl_query_cleanup_real_args -- (internal) cleanups relevant argument * structures allocated as a result of the get_real_args call */ -static void ctl_query_cleanup_real_args(const struct ctl_node *n, - void *real_arg, - enum ctl_query_source source) { +static void ctl_query_cleanup_real_args(const umf_ctl_node_t *n, void *real_arg, + umf_ctl_query_source_t source) { /* suppress unused-parameter errors */ (void)n; @@ -242,23 +285,38 @@ static void ctl_query_cleanup_real_args(const struct ctl_node *n, /* * ctl_exec_query_read -- (internal) calls the read callback of a node */ -static int ctl_exec_query_read(void *ctx, const struct ctl_node *n, - enum ctl_query_source source, void *arg, - struct ctl_index_utlist *indexes) { +static int ctl_exec_query_read(void *ctx, const umf_ctl_node_t *n, + umf_ctl_query_source_t source, void *arg, + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { + (void)extra_name, (void)query_type; + assert(n != NULL); + assert(n->cb[CTL_QUERY_READ] != NULL); + assert(MAX_CTL_QUERY_TYPE != query_type); + if (arg == NULL) { errno = EINVAL; return -1; } - return n->cb[CTL_QUERY_READ](ctx, source, arg, indexes); + return n->cb[CTL_QUERY_READ](ctx, source, arg, indexes, NULL, + MAX_CTL_QUERY_TYPE); } /* * ctl_exec_query_write -- (internal) calls the write callback of a node */ -static int ctl_exec_query_write(void *ctx, const struct ctl_node *n, - enum ctl_query_source source, void *arg, - struct ctl_index_utlist *indexes) { +static int ctl_exec_query_write(void *ctx, const umf_ctl_node_t *n, + umf_ctl_query_source_t source, void *arg, + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { + (void)extra_name, (void)query_type; + assert(n != NULL); + assert(n->cb[CTL_QUERY_WRITE] != NULL); + assert(MAX_CTL_QUERY_TYPE != query_type); + if (arg == NULL) { errno = EINVAL; return -1; @@ -269,7 +327,8 @@ static int ctl_exec_query_write(void *ctx, const struct ctl_node *n, return -1; } - int ret = n->cb[CTL_QUERY_WRITE](ctx, source, real_arg, indexes); + int ret = n->cb[CTL_QUERY_WRITE](ctx, source, real_arg, indexes, NULL, + MAX_CTL_QUERY_TYPE); ctl_query_cleanup_real_args(n, real_arg, source); return ret; @@ -278,26 +337,50 @@ static int ctl_exec_query_write(void *ctx, const struct ctl_node *n, /* * ctl_exec_query_runnable -- (internal) calls the run callback of a node */ -static int ctl_exec_query_runnable(void *ctx, const struct ctl_node *n, - enum ctl_query_source source, void *arg, - struct ctl_index_utlist *indexes) { - return n->cb[CTL_QUERY_RUNNABLE](ctx, source, arg, indexes); +static int ctl_exec_query_runnable(void *ctx, const umf_ctl_node_t *n, + umf_ctl_query_source_t source, void *arg, + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { + (void)extra_name, (void)query_type; + assert(n != NULL); + assert(n->cb[CTL_QUERY_RUNNABLE] != NULL); + assert(MAX_CTL_QUERY_TYPE != query_type); + return n->cb[CTL_QUERY_RUNNABLE](ctx, source, arg, indexes, NULL, + MAX_CTL_QUERY_TYPE); } -static int (*ctl_exec_query[MAX_CTL_QUERY_TYPE])( - void *ctx, const struct ctl_node *n, enum ctl_query_source source, - void *arg, struct ctl_index_utlist *indexes) = { +static int ctl_exec_query_subtree(void *ctx, const umf_ctl_node_t *n, + umf_ctl_query_source_t source, void *arg, + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { + assert(n != NULL); + assert(n->cb[CTL_QUERY_SUBTREE] != NULL); + assert(MAX_CTL_QUERY_TYPE != query_type); + return n->cb[CTL_QUERY_SUBTREE](ctx, source, arg, indexes, extra_name, + query_type); +} + +typedef int (*umf_ctl_exec_query_t)(void *ctx, const umf_ctl_node_t *n, + umf_ctl_query_source_t source, void *arg, + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type); + +static umf_ctl_exec_query_t ctl_exec_query[MAX_CTL_QUERY_TYPE] = { ctl_exec_query_read, ctl_exec_query_write, ctl_exec_query_runnable, + ctl_exec_query_subtree, }; /* * ctl_query -- (internal) parses the name and calls the appropriate methods * from the ctl tree */ -int ctl_query(struct ctl *ctl, void *ctx, enum ctl_query_source source, - const char *name, enum ctl_query_type type, void *arg) { +int ctl_query(struct ctl *ctl, void *ctx, umf_ctl_query_source_t source, + const char *name, umf_ctl_query_type_t type, void *arg) { if (name == NULL) { errno = EINVAL; return -1; @@ -308,29 +391,36 @@ int ctl_query(struct ctl *ctl, void *ctx, enum ctl_query_source source, * easily retrieve the index values. The list is cleared once the ctl * query has been handled. */ - struct ctl_index_utlist *indexes = NULL; + umf_ctl_index_utlist_t *indexes = NULL; indexes = Zalloc(sizeof(*indexes)); if (!indexes) { return -1; } int ret = -1; + size_t name_offset = 0; - const struct ctl_node *n = ctl_find_node(CTL_NODE(global), name, indexes); + const umf_ctl_node_t *n = + ctl_find_node(CTL_NODE(global), name, indexes, &name_offset); if (n == NULL && ctl) { ctl_delete_indexes(indexes); indexes = NULL; - n = ctl_find_node(ctl->root, name, indexes); + n = ctl_find_node(ctl->root, name, indexes, &name_offset); } - if (n == NULL || n->type != CTL_NODE_LEAF || n->cb[type] == NULL) { + // if the appropriate node (leaf or subtree) is not found, then return error + if (n == NULL || + (n->type != CTL_NODE_LEAF && n->type != CTL_NODE_SUBTREE) || + n->cb[n->type == CTL_NODE_SUBTREE ? CTL_QUERY_SUBTREE : type] == NULL) { errno = EINVAL; goto out; } - ret = ctl_exec_query[type](ctx, n, source, arg, indexes); - + const char *extra_name = &name[0] + name_offset; + ret = + ctl_exec_query[n->type == CTL_NODE_SUBTREE ? CTL_QUERY_SUBTREE : type]( + ctx, n, source, arg, indexes, extra_name, type); out: ctl_delete_indexes(indexes); @@ -341,10 +431,10 @@ int ctl_query(struct ctl *ctl, void *ctx, enum ctl_query_source source, * ctl_register_module_node -- adds a new node to the CTL tree root. */ void ctl_register_module_node(struct ctl *c, const char *name, - struct ctl_node *n) { - struct ctl_node *nnode = c == NULL - ? &CTL_NODE(global)[ctl_global_first_free++] - : &c->root[c->first_free++]; + umf_ctl_node_t *n) { + umf_ctl_node_t *nnode = c == NULL + ? &CTL_NODE(global)[ctl_global_first_free++] + : &c->root[c->first_free++]; nnode->children = n; nnode->type = CTL_NODE_NAMED; diff --git a/src/ctl/ctl.h b/src/ctl/ctl.h index 9327b01afe..968998fc22 100644 --- a/src/ctl/ctl.h +++ b/src/ctl/ctl.h @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2016-2024 Intel Corporation + * Copyright (C) 2016-2025 Intel Corporation * * Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception @@ -21,19 +21,21 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif struct ctl; -struct ctl_index_utlist { +typedef struct ctl_index_utlist { const char *name; long value; struct ctl_index_utlist *next; -}; +} umf_ctl_index_utlist_t; -enum ctl_query_source { +typedef enum ctl_query_source { CTL_UNKNOWN_QUERY_SOURCE, /* query executed directly from the program */ CTL_QUERY_PROGRAMMATIC, @@ -41,24 +43,19 @@ enum ctl_query_source { CTL_QUERY_CONFIG_INPUT, MAX_CTL_QUERY_SOURCE -}; - -enum ctl_query_type { - CTL_QUERY_READ, - CTL_QUERY_WRITE, - CTL_QUERY_RUNNABLE, +} umf_ctl_query_source_t; - MAX_CTL_QUERY_TYPE -}; - -typedef int (*node_callback)(void *ctx, enum ctl_query_source type, void *arg, - struct ctl_index_utlist *indexes); +typedef int (*node_callback)(void *ctx, umf_ctl_query_source_t type, void *arg, + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type); enum ctl_node_type { CTL_NODE_UNKNOWN, CTL_NODE_NAMED, CTL_NODE_LEAF, CTL_NODE_INDEXED, + CTL_NODE_SUBTREE, MAX_CTL_NODE }; @@ -91,7 +88,7 @@ struct ctl_argument { * CTL Tree node structure, do not use directly. All the necessary functionality * is provided by the included macros. */ -struct ctl_node { +typedef struct ctl_node { const char *name; enum ctl_node_type type; @@ -99,11 +96,13 @@ struct ctl_node { const struct ctl_argument *arg; const struct ctl_node *children; -}; +} umf_ctl_node_t; struct ctl *ctl_new(void); void ctl_delete(struct ctl *stats); +void initialize_global_ctl(void); + int ctl_load_config_from_string(struct ctl *ctl, void *ctx, const char *cfg_string); int ctl_load_config_from_file(struct ctl *ctl, void *ctx, const char *cfg_file); @@ -138,8 +137,8 @@ int ctl_arg_string(const void *arg, void *dest, size_t dest_size); #define CTL_NODE(name, ...) ctl_node_##__VA_ARGS__##_##name -int ctl_query(struct ctl *ctl, void *ctx, enum ctl_query_source source, - const char *name, enum ctl_query_type type, void *arg); +int ctl_query(struct ctl *ctl, void *ctx, umf_ctl_query_source_t source, + const char *name, umf_ctl_query_type_t type, void *arg); /* Declaration of a new child node */ #define CTL_CHILD(name, ...) \ @@ -161,6 +160,8 @@ int ctl_query(struct ctl *ctl, void *ctx, enum ctl_query_source source, #define CTL_RUNNABLE_HANDLER(name, ...) ctl_##__VA_ARGS__##_##name##_runnable +#define CTL_SUBTREE_HANDLER(name, ...) ctl_##__VA_ARGS__##_##name##_subtree + #define CTL_ARG(name) ctl_arg_##name /* @@ -170,7 +171,8 @@ int ctl_query(struct ctl *ctl, void *ctx, enum ctl_query_source source, #define CTL_LEAF_RO(name, ...) \ { \ CTL_STR(name), CTL_NODE_LEAF, \ - {CTL_READ_HANDLER(name, __VA_ARGS__), NULL, NULL}, NULL, NULL \ + {CTL_READ_HANDLER(name, __VA_ARGS__), NULL, NULL, NULL}, NULL, \ + NULL \ } /* @@ -180,7 +182,7 @@ int ctl_query(struct ctl *ctl, void *ctx, enum ctl_query_source source, #define CTL_LEAF_WO(name, ...) \ { \ CTL_STR(name), CTL_NODE_LEAF, \ - {NULL, CTL_WRITE_HANDLER(name, __VA_ARGS__), NULL}, \ + {NULL, CTL_WRITE_HANDLER(name, __VA_ARGS__), NULL, NULL}, \ &CTL_ARG(name), NULL \ } @@ -191,7 +193,22 @@ int ctl_query(struct ctl *ctl, void *ctx, enum ctl_query_source source, #define CTL_LEAF_RUNNABLE(name, ...) \ { \ CTL_STR(name), CTL_NODE_LEAF, \ - {NULL, NULL, CTL_RUNNABLE_HANDLER(name, __VA_ARGS__)}, NULL, NULL \ + {NULL, NULL, CTL_RUNNABLE_HANDLER(name, __VA_ARGS__), NULL}, NULL, \ + NULL \ + } + +#define CTL_LEAF_SUBTREE(name, ...) \ + { \ + CTL_STR(name), CTL_NODE_SUBTREE, \ + {NULL, NULL, NULL, CTL_SUBTREE_HANDLER(name, __VA_ARGS__)}, NULL, \ + NULL \ + } + +#define CTL_LEAF_SUBTREE2(name, fun, ...) \ + { \ + CTL_STR(name), CTL_NODE_SUBTREE, \ + {NULL, NULL, NULL, CTL_SUBTREE_HANDLER(fun, __VA_ARGS__)}, NULL, \ + NULL \ } /* @@ -201,7 +218,7 @@ int ctl_query(struct ctl *ctl, void *ctx, enum ctl_query_source source, #define CTL_LEAF_RW(name) \ { \ CTL_STR(name), CTL_NODE_LEAF, \ - {CTL_READ_HANDLER(name), CTL_WRITE_HANDLER(name), NULL}, \ + {CTL_READ_HANDLER(name), CTL_WRITE_HANDLER(name), NULL, NULL}, \ &CTL_ARG(name), NULL \ } diff --git a/src/libumf.c b/src/libumf.c index f8f6cc61ff..aad0140bbf 100644 --- a/src/libumf.c +++ b/src/libumf.c @@ -11,6 +11,7 @@ #include "base_alloc_global.h" #include "ipc_cache.h" +#include "memory_provider_internal.h" #include "memspace_internal.h" #include "pool/pool_scalable_internal.h" #include "provider_cuda_internal.h" @@ -26,6 +27,11 @@ umf_memory_tracker_handle_t TRACKER = NULL; static unsigned long long umfRefCount = 0; +static umf_ctl_node_t CTL_NODE(umf)[] = {CTL_CHILD(provider), CTL_CHILD(pool), + CTL_NODE_END}; + +void initialize_global_ctl(void) { CTL_REGISTER_MODULE(NULL, umf); } + int umfInit(void) { if (utils_fetch_and_add64(&umfRefCount, 1) == 0) { utils_log_init(); @@ -44,6 +50,7 @@ int umfInit(void) { } LOG_DEBUG("UMF IPC cache initialized"); + initialize_global_ctl(); } if (TRACKER) { diff --git a/src/libumf.def b/src/libumf.def index ce8820a8fa..dd0ddfbfc2 100644 --- a/src/libumf.def +++ b/src/libumf.def @@ -119,6 +119,9 @@ EXPORTS umfScalablePoolParamsSetKeepAllMemory ; Added in UMF_0.11 umfCUDAMemoryProviderParamsSetAllocFlags + umfCtlExec + umfCtlGet + umfCtlSet umfDisjointPoolOps umfDisjointPoolParamsCreate umfDisjointPoolParamsDestroy diff --git a/src/libumf.map b/src/libumf.map index 6582fd0f8d..5e97acc093 100644 --- a/src/libumf.map +++ b/src/libumf.map @@ -117,6 +117,9 @@ UMF_0.10 { UMF_0.11 { umfCUDAMemoryProviderParamsSetAllocFlags; + umfCtlExec; + umfCtlGet; + umfCtlSet; umfDisjointPoolOps; umfDisjointPoolParamsCreate; umfDisjointPoolParamsDestroy; diff --git a/src/memory_pool.c b/src/memory_pool.c index ef2c0fa66b..1b61555de7 100644 --- a/src/memory_pool.c +++ b/src/memory_pool.c @@ -22,6 +22,32 @@ #include "memory_provider_internal.h" #include "provider_tracking.h" +static int CTL_SUBTREE_HANDLER(by_handle_pool)(void *ctx, + umf_ctl_query_source_t source, + void *arg, + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t queryType) { + (void)indexes, (void)source; + umf_memory_pool_handle_t hPool = (umf_memory_pool_handle_t)ctx; + hPool->ops.ctl(hPool, /*unused*/ 0, extra_name, arg, queryType); + return 0; +} + +umf_ctl_node_t CTL_NODE(pool)[] = {CTL_LEAF_SUBTREE2(by_handle, by_handle_pool), + CTL_NODE_END}; + +static umf_result_t umfDefaultCtlPoolHandle(void *hPool, int operationType, + const char *name, void *arg, + umf_ctl_query_type_t queryType) { + (void)hPool; + (void)operationType; + (void)name; + (void)arg; + (void)queryType; + return UMF_RESULT_ERROR_NOT_SUPPORTED; +} + static umf_result_t umfPoolCreateInternal(const umf_memory_pool_ops_t *ops, umf_memory_provider_handle_t provider, void *params, @@ -58,6 +84,10 @@ static umf_result_t umfPoolCreateInternal(const umf_memory_pool_ops_t *ops, pool->ops = *ops; pool->tag = NULL; + if (NULL == pool->ops.ctl) { + pool->ops.ctl = umfDefaultCtlPoolHandle; + } + if (NULL == utils_mutex_init(&pool->lock)) { LOG_ERR("Failed to initialize mutex for pool"); ret = UMF_RESULT_ERROR_UNKNOWN; diff --git a/src/memory_pool_internal.h b/src/memory_pool_internal.h index ab3378163d..4e3c316966 100644 --- a/src/memory_pool_internal.h +++ b/src/memory_pool_internal.h @@ -26,7 +26,6 @@ extern "C" { typedef struct umf_memory_pool_t { void *pool_priv; - umf_memory_pool_ops_t ops; umf_pool_create_flags_t flags; // Memory provider used by the pool. @@ -34,6 +33,9 @@ typedef struct umf_memory_pool_t { utils_mutex_t lock; void *tag; + + // ops should be the last due to possible change size in the future + umf_memory_pool_ops_t ops; } umf_memory_pool_t; #ifdef __cplusplus diff --git a/src/memory_provider.c b/src/memory_provider.c index ce6a10a207..fdc8725e09 100644 --- a/src/memory_provider.c +++ b/src/memory_provider.c @@ -18,8 +18,23 @@ #include "base_alloc_global.h" #include "libumf.h" #include "memory_provider_internal.h" +#include "umf/base.h" #include "utils_assert.h" +static int CTL_SUBTREE_HANDLER(by_handle_provider)( + void *ctx, umf_ctl_query_source_t source, void *arg, + umf_ctl_index_utlist_t *indexes, const char *extra_name, + umf_ctl_query_type_t queryType) { + (void)indexes, (void)source; + umf_memory_provider_handle_t hProvider = (umf_memory_provider_handle_t)ctx; + hProvider->ops.ctl(hProvider->provider_priv, /*unused*/ 0, extra_name, arg, + queryType); + return 0; +} + +umf_ctl_node_t CTL_NODE(provider)[] = { + CTL_LEAF_SUBTREE2(by_handle, by_handle_provider), CTL_NODE_END}; + static umf_result_t umfDefaultPurgeLazy(void *provider, void *ptr, size_t size) { (void)provider; @@ -93,6 +108,17 @@ static umf_result_t umfDefaultCloseIPCHandle(void *provider, void *ptr, return UMF_RESULT_ERROR_NOT_SUPPORTED; } +static umf_result_t umfDefaultCtlHandle(void *provider, int operationType, + const char *name, void *arg, + umf_ctl_query_type_t queryType) { + (void)provider; + (void)operationType; + (void)name; + (void)arg; + (void)queryType; + return UMF_RESULT_ERROR_NOT_SUPPORTED; +} + void assignOpsExtDefaults(umf_memory_provider_ops_t *ops) { if (!ops->ext.purge_lazy) { ops->ext.purge_lazy = umfDefaultPurgeLazy; @@ -124,6 +150,9 @@ void assignOpsIpcDefaults(umf_memory_provider_ops_t *ops) { if (!ops->ipc.close_ipc_handle) { ops->ipc.close_ipc_handle = umfDefaultCloseIPCHandle; } + if (!ops->ctl) { + ops->ctl = umfDefaultCtlHandle; + } } static bool validateOpsMandatory(const umf_memory_provider_ops_t *ops) { diff --git a/src/memory_provider_internal.h b/src/memory_provider_internal.h index dd1111a236..4b4ec8b2de 100644 --- a/src/memory_provider_internal.h +++ b/src/memory_provider_internal.h @@ -14,18 +14,24 @@ #include +#include "ctl/ctl.h" + #ifdef __cplusplus extern "C" { #endif typedef struct umf_memory_provider_t { - umf_memory_provider_ops_t ops; void *provider_priv; + // ops should be the last due to possible change size in the future + umf_memory_provider_ops_t ops; } umf_memory_provider_t; void *umfMemoryProviderGetPriv(umf_memory_provider_handle_t hProvider); umf_memory_provider_handle_t *umfGetLastFailedMemoryProviderPtr(void); +extern umf_ctl_node_t CTL_NODE(provider)[]; +extern umf_ctl_node_t CTL_NODE(pool)[]; + #ifdef __cplusplus } #endif diff --git a/src/pool/pool_scalable.c b/src/pool/pool_scalable.c index 8a9fd88c11..f688875292 100644 --- a/src/pool/pool_scalable.c +++ b/src/pool/pool_scalable.c @@ -13,6 +13,8 @@ #include #include +#include +#include #include #include #include @@ -114,6 +116,10 @@ static const char *tbb_symbol[TBB_POOL_SYMBOLS_MAX] = { #endif }; +struct ctl *pool_scallable_ctl_root; + +static UTIL_ONCE_FLAG ctl_initialized = UTIL_ONCE_FLAG_INIT; + static void init_tbb_callbacks_once(void) { const char *lib_name = tbb_symbol[TBB_LIB_NAME]; tbb_callbacks.lib_handle = utils_open_library(lib_name, 0); @@ -405,6 +411,38 @@ static umf_result_t tbb_get_last_allocation_error(void *pool) { return TLS_last_allocation_error; } +static int CTL_READ_HANDLER(tracking_enabled)(void *ctx, + umf_ctl_query_source_t source, + void *arg, + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { + /* suppress unused-parameter errors */ + (void)source, (void)indexes, (void)ctx, (void)extra_name, (void)query_type; + + int *arg_out = arg; + umf_memory_pool_handle_t pool = (umf_memory_pool_handle_t)ctx; + *arg_out = pool->flags & UMF_POOL_CREATE_FLAG_DISABLE_TRACKING ? 0 : 1; + return 0; +} + +static const umf_ctl_node_t CTL_NODE(params)[] = {CTL_LEAF_RO(tracking_enabled), + CTL_NODE_END}; + +static void initialize_pool_ctl(void) { + pool_scallable_ctl_root = ctl_new(); + CTL_REGISTER_MODULE(pool_scallable_ctl_root, params); +} + +static umf_result_t pool_ctl(void *hPool, int operationType, const char *name, + void *arg, umf_ctl_query_type_t query_type) { + (void)operationType; // unused + umf_memory_pool_handle_t pool_provider = (umf_memory_pool_handle_t)hPool; + utils_init_once(&ctl_initialized, initialize_pool_ctl); + return ctl_query(pool_scallable_ctl_root, pool_provider, + CTL_QUERY_PROGRAMMATIC, name, query_type, arg); +} + static umf_memory_pool_ops_t UMF_SCALABLE_POOL_OPS = { .version = UMF_POOL_OPS_VERSION_CURRENT, .initialize = tbb_pool_initialize, @@ -415,7 +453,8 @@ static umf_memory_pool_ops_t UMF_SCALABLE_POOL_OPS = { .aligned_malloc = tbb_aligned_malloc, .malloc_usable_size = tbb_malloc_usable_size, .free = tbb_free, - .get_last_allocation_error = tbb_get_last_allocation_error}; + .get_last_allocation_error = tbb_get_last_allocation_error, + .ctl = pool_ctl}; umf_memory_pool_ops_t *umfScalablePoolOps(void) { return &UMF_SCALABLE_POOL_OPS; diff --git a/src/provider/provider_os_memory.c b/src/provider/provider_os_memory.c index bd5ea9c691..9a487a5af7 100644 --- a/src/provider/provider_os_memory.c +++ b/src/provider/provider_os_memory.c @@ -13,10 +13,12 @@ #include #include +#include #include +#include +#include #include #include - // OS Memory Provider requires HWLOC #if defined(UMF_NO_HWLOC) @@ -166,6 +168,33 @@ static const char *Native_error_str[] = { "HWLOC topology discovery failed", }; +struct ctl *os_memory_ctl_root; + +static UTIL_ONCE_FLAG ctl_initialized = UTIL_ONCE_FLAG_INIT; + +static int CTL_READ_HANDLER(ipc_enabled)(void *ctx, + umf_ctl_query_source_t source, + void *arg, + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { + /* suppress unused-parameter errors */ + (void)source, (void)indexes, (void)ctx, (void)extra_name, (void)query_type; + + int *arg_out = arg; + os_memory_provider_t *os_provider = (os_memory_provider_t *)ctx; + *arg_out = os_provider->IPC_enabled; + return 0; +} + +static const umf_ctl_node_t CTL_NODE(params)[] = {CTL_LEAF_RO(ipc_enabled), + CTL_NODE_END}; + +static void initialize_os_ctl(void) { + os_memory_ctl_root = ctl_new(); + CTL_REGISTER_MODULE(os_memory_ctl_root, params); +} + static void os_store_last_native_error(int32_t native_error, int errno_value) { TLS_last_native_error.native_error = native_error; TLS_last_native_error.errno_value = errno_value; @@ -1401,6 +1430,15 @@ static umf_result_t os_close_ipc_handle(void *provider, void *ptr, return UMF_RESULT_SUCCESS; } +static umf_result_t os_ctl(void *hProvider, int operationType, const char *name, + void *arg, umf_ctl_query_type_t query_type) { + (void)operationType; // unused + os_memory_provider_t *os_provider = (os_memory_provider_t *)hProvider; + utils_init_once(&ctl_initialized, initialize_os_ctl); + return ctl_query(os_memory_ctl_root, os_provider, CTL_QUERY_PROGRAMMATIC, + name, query_type, arg); +} + static umf_memory_provider_ops_t UMF_OS_MEMORY_PROVIDER_OPS = { .version = UMF_PROVIDER_OPS_VERSION_CURRENT, .initialize = os_initialize, @@ -1419,7 +1457,9 @@ static umf_memory_provider_ops_t UMF_OS_MEMORY_PROVIDER_OPS = { .ipc.get_ipc_handle = os_get_ipc_handle, .ipc.put_ipc_handle = os_put_ipc_handle, .ipc.open_ipc_handle = os_open_ipc_handle, - .ipc.close_ipc_handle = os_close_ipc_handle}; + .ipc.close_ipc_handle = os_close_ipc_handle, + .ctl = os_ctl, +}; umf_memory_provider_ops_t *umfOsMemoryProviderOps(void) { return &UMF_OS_MEMORY_PROVIDER_OPS; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ecdde95e12..32bdd4c14c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -208,8 +208,14 @@ add_umf_test( LIBS ${UMF_LOGGER_LIBS}) add_umf_test( - NAME ctl - SRCS ctl/test.cpp ctl/ctl_debug.c ../src/ctl/ctl.c ${BA_SOURCES_FOR_TEST} + NAME ctl_unittest + SRCS ctl/ctl_unittest.cpp ctl/ctl_debug.c ../src/ctl/ctl.c + ${BA_SOURCES_FOR_TEST} + LIBS ${UMF_UTILS_FOR_TEST}) + +add_umf_test( + NAME ctl_api + SRCS ctl/ctl_api.cpp ${BA_SOURCES_FOR_TEST} LIBS ${UMF_UTILS_FOR_TEST}) add_umf_test( diff --git a/test/ctl/config.txt b/test/ctl/config.txt index 5d4f9c62bd..52c8febadd 100644 --- a/test/ctl/config.txt +++ b/test/ctl/config.txt @@ -1 +1,3 @@ -debug.heap.alloc_pattern=321 \ No newline at end of file +debug.heap.alloc_pattern=321; +debug.heap.enable_logging=1; +debug.heap.log_level=5; diff --git a/test/ctl/ctl_api.cpp b/test/ctl/ctl_api.cpp new file mode 100644 index 0000000000..ff6491c169 --- /dev/null +++ b/test/ctl/ctl_api.cpp @@ -0,0 +1,142 @@ +/* + * + * Copyright (C) 2025 Intel Corporation + * + * Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + */ + +#include + +#include +#include +#include +#include +#include + +#include "../common/base.hpp" +#include "gtest/gtest.h" + +using namespace umf_test; + +TEST_F(test, ctl_by_handle_os_provider) { + umf_memory_provider_handle_t hProvider = NULL; + umf_os_memory_provider_params_handle_t os_memory_provider_params = NULL; + umf_memory_provider_ops_t *os_provider_ops = umfOsMemoryProviderOps(); + if (os_provider_ops == NULL) { + GTEST_SKIP() << "OS memory provider is not supported!"; + } + + int ret = umfOsMemoryProviderParamsCreate(&os_memory_provider_params); + ret = umfMemoryProviderCreate(os_provider_ops, os_memory_provider_params, + &hProvider); + ASSERT_EQ(ret, UMF_RESULT_SUCCESS); + + int ipc_enabled = 0xBAD; + ret = umfCtlGet("umf.provider.by_handle.params.ipc_enabled", hProvider, + &ipc_enabled); + ASSERT_EQ(ret, UMF_RESULT_SUCCESS); + ASSERT_EQ(ipc_enabled, 0); + + umfOsMemoryProviderParamsDestroy(os_memory_provider_params); + umfMemoryProviderDestroy(hProvider); +} + +// Create a memory provider and a memory pool +umf_memory_provider_handle_t create_memory_provider() { + umf_memory_provider_ops_t *provider_ops = umfOsMemoryProviderOps(); + umf_os_memory_provider_params_handle_t params = NULL; + umf_memory_provider_handle_t provider; + + int ret = umfOsMemoryProviderParamsCreate(¶ms); + if (ret != UMF_RESULT_SUCCESS) { + return 0; + } + + ret = umfMemoryProviderCreate(provider_ops, params, &provider); + umfOsMemoryProviderParamsDestroy(params); + if (ret != UMF_RESULT_SUCCESS) { + return 0; + } + + return provider; +} + +class CtlTest : public ::testing::Test { + public: + class CtlException : public std::exception { + public: + CtlException(const char *msg) : msg(msg) {} + const char *what() const noexcept override { return msg; } + + private: + const char *msg; + }; + + void SetUp() override { + provider = NULL; + pool = NULL; + } + + void instantiatePool(umf_memory_pool_ops_t *pool_ops, void *pool_params, + umf_pool_create_flags_t flags = 0) { + freeResources(); + provider = create_memory_provider(); + if (provider == NULL) { + throw CtlException("Failed to create a memory provider!"); + } + int ret = umfPoolCreate(pool_ops, provider, pool_params, flags, &pool); + if (ret != UMF_RESULT_SUCCESS) { + throw CtlException("Failed to create a memory provider!"); + } + } + + template + void validateQuery( + std::function + ctlApiFunction, + const char *name, T expectedValue, umf_result_t expected) { + T value = 0xBAD; + umf_result_t ret = ctlApiFunction(name, pool, &value); + ASSERT_EQ(ret, expected); + if (ret == UMF_RESULT_SUCCESS) { + ASSERT_EQ(value, expectedValue); + } + ASSERT_EQ(ret, UMF_RESULT_SUCCESS); + } + + void TearDown() override { freeResources(); } + + private: + void freeResources() { + if (pool) { + umfPoolDestroy(pool); + } + if (provider) { + umfMemoryProviderDestroy(provider); + } + } + + umf_memory_provider_handle_t provider; + umf_memory_pool_handle_t pool; +}; + +TEST_F(CtlTest, ctl_by_handle_scalablePool) { + try { + instantiatePool(umfScalablePoolOps(), NULL); + validateQuery(umfCtlGet, + "umf.pool.by_handle.params.tracking_enabled", 1, + UMF_RESULT_SUCCESS); + + instantiatePool(umfScalablePoolOps(), NULL, + UMF_POOL_CREATE_FLAG_DISABLE_TRACKING); + validateQuery(umfCtlGet, + "umf.pool.by_handle.params.tracking_enabled", 0, + UMF_RESULT_SUCCESS); + } catch (CtlTest::CtlException &e) { + GTEST_SKIP() << e.what(); + } catch (...) { + GTEST_FAIL() << "Unknown exception!"; + } +} diff --git a/test/ctl/ctl_debug.c b/test/ctl/ctl_debug.c index 711cb5e179..5bc2920eab 100644 --- a/test/ctl/ctl_debug.c +++ b/test/ctl/ctl_debug.c @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2024 Intel Corporation + * Copyright (C) 2024-2025 Intel Corporation * * Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception @@ -25,11 +25,13 @@ struct ctl *get_debug_ctl(void) { return ctl_debug; } * CTL_WRITE_HANDLER(alloc_pattern) -- sets the alloc_pattern field in heap */ static int CTL_WRITE_HANDLER(alloc_pattern)(void *ctx, - enum ctl_query_source source, + umf_ctl_query_source_t source, void *arg, - struct ctl_index_utlist *indexes) { + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { /* suppress unused-parameter errors */ - (void)source, (void)indexes, (void)ctx; + (void)source, (void)indexes, (void)ctx, (void)extra_name, (void)query_type; int arg_in = *(int *)arg; alloc_pattern = arg_in; @@ -40,11 +42,13 @@ static int CTL_WRITE_HANDLER(alloc_pattern)(void *ctx, * CTL_READ_HANDLER(alloc_pattern) -- returns alloc_pattern heap field */ static int CTL_READ_HANDLER(alloc_pattern)(void *ctx, - enum ctl_query_source source, + umf_ctl_query_source_t source, void *arg, - struct ctl_index_utlist *indexes) { + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { /* suppress unused-parameter errors */ - (void)source, (void)indexes, (void)ctx; + (void)source, (void)indexes, (void)ctx, (void)extra_name, (void)query_type; int *arg_out = arg; *arg_out = alloc_pattern; @@ -52,11 +56,13 @@ static int CTL_READ_HANDLER(alloc_pattern)(void *ctx, } static int CTL_WRITE_HANDLER(enable_logging)(void *ctx, - enum ctl_query_source source, + umf_ctl_query_source_t source, void *arg, - struct ctl_index_utlist *indexes) { + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { /* suppress unused-parameter errors */ - (void)source, (void)indexes, (void)ctx; + (void)source, (void)indexes, (void)ctx, (void)extra_name, (void)query_type; int arg_in = *(int *)arg; enable_logging = arg_in; @@ -64,33 +70,40 @@ static int CTL_WRITE_HANDLER(enable_logging)(void *ctx, } static int CTL_READ_HANDLER(enable_logging)(void *ctx, - enum ctl_query_source source, + umf_ctl_query_source_t source, void *arg, - struct ctl_index_utlist *indexes) { + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { /* suppress unused-parameter errors */ - (void)source, (void)indexes, (void)ctx; + (void)source, (void)indexes, (void)ctx, (void)extra_name, (void)query_type; int *arg_out = arg; *arg_out = enable_logging; return 0; } -static int CTL_WRITE_HANDLER(log_level)(void *ctx, enum ctl_query_source source, +static int CTL_WRITE_HANDLER(log_level)(void *ctx, + umf_ctl_query_source_t source, void *arg, - struct ctl_index_utlist *indexes) { + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { /* suppress unused-parameter errors */ - (void)source, (void)indexes, (void)ctx; + (void)source, (void)indexes, (void)ctx, (void)extra_name, (void)query_type; int arg_in = *(int *)arg; log_level = arg_in; return 0; } -static int CTL_READ_HANDLER(log_level)(void *ctx, enum ctl_query_source source, +static int CTL_READ_HANDLER(log_level)(void *ctx, umf_ctl_query_source_t source, void *arg, - struct ctl_index_utlist *indexes) { + umf_ctl_index_utlist_t *indexes, + const char *extra_name, + umf_ctl_query_type_t query_type) { /* suppress unused-parameter errors */ - (void)source, (void)indexes, (void)ctx; + (void)source, (void)indexes, (void)ctx, (void)extra_name, (void)query_type; int *arg_out = arg; *arg_out = log_level; @@ -103,15 +116,15 @@ static const struct ctl_argument CTL_ARG(enable_logging) = CTL_ARG_BOOLEAN; static const struct ctl_argument CTL_ARG(log_level) = CTL_ARG_INT; -static const struct ctl_node CTL_NODE(heap)[] = {CTL_LEAF_RW(alloc_pattern), - CTL_LEAF_RW(enable_logging), - CTL_LEAF_RW(log_level), +static const umf_ctl_node_t CTL_NODE(heap)[] = {CTL_LEAF_RW(alloc_pattern), + CTL_LEAF_RW(enable_logging), + CTL_LEAF_RW(log_level), - CTL_NODE_END}; + CTL_NODE_END}; -static const struct ctl_node CTL_NODE(debug)[] = {CTL_CHILD(heap), +static const umf_ctl_node_t CTL_NODE(debug)[] = {CTL_CHILD(heap), - CTL_NODE_END}; + CTL_NODE_END}; /* * debug_ctl_register -- registers ctl nodes for "debug" module diff --git a/test/ctl/test.cpp b/test/ctl/ctl_unittest.cpp similarity index 100% rename from test/ctl/test.cpp rename to test/ctl/ctl_unittest.cpp