diff --git a/lite/example/c_example/main.c b/lite/example/c_example/main.c index 4b62c4f58d13ff169e73beab84469431e4daad6e..facd4cabe70aa2f8610ff7d2a32503dc1acc26f1 100644 --- a/lite/example/c_example/main.c +++ b/lite/example/c_example/main.c @@ -14,6 +14,7 @@ #include "lite-c/tensor_c.h" #include +#include #include #define LITE_CAPI_CHECK(_expr) \ @@ -42,7 +43,7 @@ int basic_c_interface(const char* mode_path) { LITE_get_tensor_total_size_in_byte(c_input_tensor, &length_in_byte)); LITE_CAPI_CHECK(LITE_get_tensor_memory(c_input_tensor, &dst_ptr)); //! copy or forward data to network - memset(dst_ptr, 5, length_in_byte); + LITE_memset(dst_ptr, 5, length_in_byte); //! forward LITE_CAPI_CHECK(LITE_forward(c_network)); @@ -66,23 +67,41 @@ int basic_c_interface(const char* mode_path) { float max = -1.0f; float sum = 0.0f; + int is_enable_ipc_debug = LITE_is_enable_ipc_debug_mode(); + float* copy_ptr = NULL; + float* final_dst_ptr = (float*)output_ptr; + if (is_enable_ipc_debug) { + copy_ptr = (float*)(malloc(length_output_in_byte)); + LITE_CAPI_CHECK(LITE_copy_server_tensor_memory( + output_ptr, copy_ptr, length_output_in_byte)); + final_dst_ptr = (float*)copy_ptr; + } + for (size_t i = 0; i < out_length; i++) { - float data = ((float*)(output_ptr))[i]; + float data = final_dst_ptr[i]; sum += data; if (max < data) max = data; } printf("max=%e, sum=%e\n", max, sum); + LITE_destroy_network(c_network); + if (is_enable_ipc_debug) { + free(copy_ptr); + } return 0; } int main(int argc, char** argv) { - if (argc < 2) { - printf("usage: lite_c_examples , just test C interface " + if (argc < 3) { + printf("usage: lite_c_examples is_enable_fork_debug_model , just " + "test C interface " "build.\n"); return -1; } - return basic_c_interface(argv[1]); + if (atoi(argv[1])) { + LITE_enable_lite_ipc_debug(); + } + return basic_c_interface(argv[2]); } // vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}} diff --git a/lite/lite-c/include/lite-c/global_c.h b/lite/lite-c/include/lite-c/global_c.h index 2944d545ea076281bf894271d498e3d404ae7bf9..a3362c50db9996587dd9df70d306a573295198c4 100644 --- a/lite/lite-c/include/lite-c/global_c.h +++ b/lite/lite-c/include/lite-c/global_c.h @@ -26,6 +26,15 @@ LITE_API void LITE_clear_last_error(); */ LITE_API const char* LITE_get_last_error(); +/*! \brief LITE is enable ipc debug mode or not. + * \return if enable ipc debug mode, will return 1, if not will return 0 + */ +LITE_API int LITE_is_enable_ipc_debug_mode(); + +/*! \brief LITE enable ipc debug mode. + */ +LITE_API void LITE_enable_lite_ipc_debug(); + /*! \brief Get device count * \param[in] device_type device type * \return the device count diff --git a/lite/lite-c/include/lite-c/tensor_c.h b/lite/lite-c/include/lite-c/tensor_c.h index 3693a89afcf5673994ea6ef2207291d2a8b7a226..27942965e6885aa9e346e0038245ec9303935b7f 100644 --- a/lite/lite-c/include/lite-c/tensor_c.h +++ b/lite/lite-c/include/lite-c/tensor_c.h @@ -158,6 +158,15 @@ LITE_API int LITE_tensor_share_memory_with( */ LITE_API int LITE_get_tensor_memory(const LiteTensor tensor, void** data); +/** + * \brief copy tensor memory if fork debug mode. + * \param[server_ptr] the ptr valid in lite server + * \param[client_ptr] the ptr only valid in lite client + * \param[size_in_byte] copy size + */ +LITE_API int LITE_copy_server_tensor_memory( + void* server_ptr, void* client_ptr, size_t size_in_byte); + /** * \brief get the memory pointer of a Tensor object. * \param[in] tensor The input Tensor @@ -230,6 +239,11 @@ LITE_API int LITE_tensor_concat( LiteTensor* tensors, int nr_tensor, int dim, LiteDeviceType dst_device, int device_id, LiteTensor* result_tensor); +/** + * \brief fuction like memset + */ +LITE_API void* LITE_memset(void* s, int c, size_t n); + #ifdef __cplusplus } #endif diff --git a/lite/lite-c/src/global.cpp b/lite/lite-c/src/global.cpp index 54d709bf19808d483ad6032be979f2fcd19685ab..fefe0a01ceeef5be2b266a833dcc863f3cb22930 100644 --- a/lite/lite-c/src/global.cpp +++ b/lite/lite-c/src/global.cpp @@ -1,5 +1,6 @@ #include "lite/global.h" #include "common.h" +#include "ipc_helper.h" #include "lite-c/global_c.h" namespace { @@ -47,7 +48,24 @@ void LITE_clear_last_error() { const char* LITE_get_last_error() { LITE_LOCK_GUARD(mtx_error); - return get_global_error().get_error_msg().c_str(); + if (ipc_imp::is_server()) { + return get_global_error().get_error_msg().c_str(); + } else { + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(nullptr); + + IPC_HELP_REMOTE_CALL(raw_shm_ptr, ipc::RemoteFuncId::LITE_GET_LAST_ERROR); + + char* ret_ptr = static_cast(raw_shm_ptr); + return ret_ptr; + } +} + +int LITE_is_enable_ipc_debug_mode() { + return ipc::IpcHelper::is_enable_fork_debug_mode(); +} + +void LITE_enable_lite_ipc_debug() { + ipc_imp::enable_lite_ipc_debug(); } int LITE_get_version(int* major, int* minor, int* patch) { diff --git a/lite/lite-c/src/ipc_helper.cpp b/lite/lite-c/src/ipc_helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eea1a2dc860078d4ddf561ec990ac61d62baa99d --- /dev/null +++ b/lite/lite-c/src/ipc_helper.cpp @@ -0,0 +1,260 @@ +#include "ipc_helper.h" + +#include "lite-c/global_c.h" +#include "lite-c/network_c.h" + +#include "misc.h" + +using namespace ipc_imp; +namespace ipc { + +static void api_remote_call_cb(struct MsgBody* msg) { + LITE_DEBUG( + "into %s: %d remote_func_id: %zu", __func__, __LINE__, msg->remote_func_id); + switch (static_cast(msg->remote_func_id)) { + case RemoteFuncId::LITE_MAKE_NETWORK: { + LiteNetwork network; + //! second args is const LiteConfig config + char* shm_ptr_c = static_cast(msg->shm_ptr); + LiteConfig config; + memcpy(&config, shm_ptr_c, sizeof(LiteConfig)); + //! third args is network_io + LiteNetworkIO network_io; + memcpy(&network_io, shm_ptr_c + sizeof(LiteConfig), sizeof(LiteNetworkIO)); + + int ret = LITE_make_network(&network, config, network_io); + + //! API is block, put ret to shm_ptr + int* ret_ptr = static_cast(msg->shm_ptr); + *ret_ptr = ret; + ret_ptr++; + void* network_p = static_cast(ret_ptr); + memcpy(network_p, &network, sizeof(LiteNetwork)); + }; break; + case RemoteFuncId::LITE_LOAD_MODEL_FROM_PATH: { + LiteNetwork network; + char* shm_ptr_c = static_cast(msg->shm_ptr); + memcpy(&network, shm_ptr_c, sizeof(LiteNetwork)); + + int ret = + LITE_load_model_from_path(network, shm_ptr_c + sizeof(LiteNetwork)); + + //! API is block, put ret to shm_ptr + int* ret_ptr = static_cast(msg->shm_ptr); + *ret_ptr = ret; + }; break; + case RemoteFuncId::LITE_GET_LAST_ERROR: { + auto shm_size = base_get_shm_size(); + + const char* ret = LITE_get_last_error(); + char* ret_ptr = static_cast(msg->shm_ptr); + + auto last_error_str_len = strlen(ret) + 1; + ASSERT_SHM_SIZE(shm_size, last_error_str_len); + strcpy(ret_ptr, ret); + }; break; + case RemoteFuncId::LITE_GET_IO_TENSOR: { + char* shm_ptr_c = static_cast(msg->shm_ptr); + LiteNetwork network; + LiteTensorPhase phase; + LiteTensor tensor; + memcpy(&network, shm_ptr_c, sizeof(LiteNetwork)); + memcpy(&phase, shm_ptr_c + sizeof(LiteNetwork), sizeof(LiteTensorPhase)); + + int ret = LITE_get_io_tensor( + network, shm_ptr_c + sizeof(LiteNetwork) + sizeof(LiteTensorPhase), + phase, &tensor); + + //! API is block, put ret to shm_ptr + int* ret_ptr = static_cast(msg->shm_ptr); + *ret_ptr = ret; + ret_ptr++; + void* lite_tensor_p = static_cast(ret_ptr); + memcpy(lite_tensor_p, &tensor, sizeof(LiteTensor)); + }; break; + case RemoteFuncId::LITE_GET_TENSOR_TOTAL_SIZE_IN_BYTE: { + char* shm_ptr_c = static_cast(msg->shm_ptr); + LiteTensor tensor; + size_t size; + memcpy(&tensor, shm_ptr_c, sizeof(LiteTensor)); + + int ret = LITE_get_tensor_total_size_in_byte(tensor, &size); + + int* ret_ptr = static_cast(msg->shm_ptr); + *ret_ptr = ret; + ret_ptr++; + memcpy(ret_ptr, &size, sizeof(size_t)); + }; break; + case RemoteFuncId::LITE_GET_TENSOR_MEMORY: { + char* shm_ptr_c = static_cast(msg->shm_ptr); + LiteTensor tensor; + void* data; + memcpy(&tensor, shm_ptr_c, sizeof(LiteTensor)); + + int ret = LITE_get_tensor_memory(tensor, &data); + + int* ret_ptr = static_cast(msg->shm_ptr); + *ret_ptr = ret; + ret_ptr++; + memcpy(ret_ptr, &data, sizeof(void*)); + }; break; + case RemoteFuncId::LITE_MEMSET: { + char* shm_ptr_c = static_cast(msg->shm_ptr); + void* s; + int c; + size_t n; + memcpy(&s, shm_ptr_c, sizeof(void*)); + memcpy(&c, shm_ptr_c + sizeof(void*), sizeof(int)); + memcpy(&n, shm_ptr_c + sizeof(void*) + sizeof(int), sizeof(size_t)); + LITE_memset(s, c, n); + }; break; + case RemoteFuncId::LITE_FORWARD: { + char* shm_ptr_c = static_cast(msg->shm_ptr); + LiteNetwork network; + memcpy(&network, shm_ptr_c, sizeof(LiteNetwork)); + + int ret = LITE_forward(network); + + int* ret_ptr = static_cast(msg->shm_ptr); + *ret_ptr = ret; + }; break; + case RemoteFuncId::LITE_WAIT: { + char* shm_ptr_c = static_cast(msg->shm_ptr); + LiteNetwork network; + memcpy(&network, shm_ptr_c, sizeof(LiteNetwork)); + + int ret = LITE_wait(network); + + int* ret_ptr = static_cast(msg->shm_ptr); + *ret_ptr = ret; + }; break; + case RemoteFuncId::LITE_GET_OUTPUT_NAME: { + char* shm_ptr_c = static_cast(msg->shm_ptr); + LiteNetwork network; + size_t index; + const char* name; + memcpy(&network, shm_ptr_c, sizeof(LiteNetwork)); + memcpy(&index, shm_ptr_c + sizeof(LiteNetwork), sizeof(size_t)); + + int ret = LITE_get_output_name(network, index, &name); + auto output_name_len = strlen(name) + 1; + auto shm_size = base_get_shm_size(); + ASSERT_SHM_SIZE(shm_size, output_name_len); + + int* ret_ptr = static_cast(msg->shm_ptr); + *ret_ptr = ret; + ret_ptr++; + void* p = static_cast(ret_ptr); + char* p_c = static_cast(p); + strcpy(p_c, name); + }; break; + case RemoteFuncId::LITE_COPY_SERVER_TENSOR_MEMORY: { + char* shm_ptr_c = static_cast(msg->shm_ptr); + void* server_ptr; + size_t size_in_byte; + memcpy(&server_ptr, shm_ptr_c, sizeof(void*)); + memcpy(&size_in_byte, shm_ptr_c + sizeof(void*), sizeof(size_t)); + memcpy(shm_ptr_c, server_ptr, size_in_byte); + }; break; + case RemoteFuncId::LITE_DESTROY_NETWORK: { + char* shm_ptr_c = static_cast(msg->shm_ptr); + LiteNetwork network; + memcpy(&network, shm_ptr_c, sizeof(LiteNetwork)); + + int ret = LITE_destroy_network(network); + + int* ret_ptr = static_cast(msg->shm_ptr); + *ret_ptr = ret; + }; break; + default: + LITE_THROW("code issue happened: do not handle RemoteFuncId"); + } +}; +bool IpcHelper::sm_is_enable_fork_ipc = false; +IpcHelper::IpcHelper() { + if (!is_server()) { + sm_is_enable_fork_ipc = is_enable_ipc(); + auto shm_mem_conut = base_get_shm_count(); + shm_mem_for_null_consumer_ptr = base_get_shm_ptr(0); + LITE_ASSERT( + shm_mem_for_null_consumer_ptr, "invalid shm_ptr: %p", + shm_mem_for_null_consumer_ptr); + + for (size_t i = 1; i < shm_mem_conut; i++) { + void* shm_ptr = base_get_shm_ptr(i); + LITE_ASSERT(shm_ptr, "invalid shm_ptr: %p", shm_ptr); + //! init consumer ptr as nullptr + m_shm_ptr2consumer_ptr[shm_ptr] = nullptr; + } + + shm_size = base_get_shm_size(); + register_remote_call_cb(&api_remote_call_cb); + } else { + LITE_THROW("code issue happened: can not use for client"); + } +}; + +IpcHelper::~IpcHelper() { + struct MsgBody msg; + msg.type = IPC_SERVER_EXIT; + send_ipc_msg(&msg); + join_server(); +}; + +void* IpcHelper::get_shm_ptr(void* consumer_ptr) { + LITE_LOCK_GUARD(m_mtx); + + if (!consumer_ptr) { + return shm_mem_for_null_consumer_ptr; + } + + void* ret = nullptr; + //! try find old one + for (auto&& i : m_shm_ptr2consumer_ptr) { + if (consumer_ptr == i.second) { + ret = i.first; + break; + } + } + + //! if not find, try alloc a new one + if (!ret) { + for (auto&& i : m_shm_ptr2consumer_ptr) { + if (nullptr == i.second) { + i.second = consumer_ptr; + ret = i.first; + break; + } + } + } + + if (!ret) { + LITE_ERROR( + "can not find any usable shm_mem, may config LITE_DEBUG_IPC_SHM_COUNT " + "big " + "than :%zu", + m_shm_ptr2consumer_ptr.size() + 1); + LITE_ASSERT(ret); + } + + return ret; +}; + +void IpcHelper::release_shm_ptr(void* consumer_ptr) { + LITE_LOCK_GUARD(m_mtx); + + LITE_ASSERT(consumer_ptr, "invalid consumer_ptr ptr"); + for (auto&& i : m_shm_ptr2consumer_ptr) { + if (consumer_ptr == i.second) { + //! release use of shm_mem, then other consumer can use it + i.second = nullptr; + return; + } + } + + LITE_THROW( + "error happened!!, can not find any consumer_ptr in " + "m_shm_ptr2consumer_ptr"); +}; + +} // namespace ipc diff --git a/lite/lite-c/src/ipc_helper.h b/lite/lite-c/src/ipc_helper.h new file mode 100644 index 0000000000000000000000000000000000000000..93bcdc1b0744a7de9c979e1e13bae3b623c33e8f --- /dev/null +++ b/lite/lite-c/src/ipc_helper.h @@ -0,0 +1,101 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "ipc_imp.h" + +#define IPC_INSTACE() ipc::IpcHelper::Instance() + +#define IPC_HELP_REMOTE_CALL(SHM_PTR, REMOTEFUNCID) \ + struct ipc_imp::MsgBody msg; \ + msg.type = ipc_imp::IPC_CALL_REMOTE_API; \ + msg.shm_ptr = static_cast(SHM_PTR); \ + msg.remote_func_id = static_cast(REMOTEFUNCID); \ + IPC_INSTACE().send_ipc_msg(&msg); + +#define ASSERT_SHM_SIZE(SHM_SIZE, NEED_SIZE) \ + do { \ + if (SHM_SIZE < NEED_SIZE) { \ + LITE_ERROR( \ + "shm_size not enough to run this api need vs config: (%fMB " \ + "%fMB), please config it by env: LITE_DEBUG_IPC_SHM_SIZE, for " \ + "example config to 20MB by: export LITE_DEBUG_IPC_SHM_SIZE=20", \ + NEED_SIZE / 1024.0 / 1024.0, SHM_SIZE / 1024.0 / 1024.0); \ + __builtin_trap(); \ + } \ + } while (0) + +namespace ipc { + +template +class Singleton { +public: + Singleton() {} + static T& Instance() { + static T _; + return _; + } +}; + +class IpcHelper : public Singleton { +public: + IpcHelper(const IpcHelper&) = delete; + IpcHelper& operator=(const IpcHelper&) = delete; + IpcHelper(); + + ~IpcHelper(); + + //! send msg with default timeout + struct ipc_imp::MsgBody send_ipc_msg(struct ipc_imp::MsgBody* msg) { + return send_msg(msg, &tv); + } + + //! get shm ptr + void* get_shm_ptr(void* consumer_ptr); + + //! release shm_ptr, NOT free shm_ptr + void release_shm_ptr(void* consumer_ptr); + + //! check shm size + void check_shm_size(size_t need_size) { ASSERT_SHM_SIZE(shm_size, need_size); } + + //! is enable ipc fork debug mode + static bool is_enable_fork_debug_mode() { return sm_is_enable_fork_ipc; } + + static bool sm_is_enable_fork_ipc; + +private: + //! 5 minutes + struct timeval tv = {300, 0}; + + //! map of , + std::map m_shm_ptr2consumer_ptr; + + size_t shm_size = 0; + + //! shm_mem for consumer_ptr == nullptr + void* shm_mem_for_null_consumer_ptr; + + LITE_MUTEX m_mtx; +}; + +enum class RemoteFuncId : size_t { + LITE_MAKE_NETWORK = 1, + LITE_LOAD_MODEL_FROM_PATH = 2, + LITE_GET_LAST_ERROR = 3, + LITE_GET_IO_TENSOR = 4, + LITE_GET_TENSOR_TOTAL_SIZE_IN_BYTE = 5, + LITE_GET_TENSOR_MEMORY = 6, + LITE_MEMSET = 7, + LITE_FORWARD = 8, + LITE_WAIT = 9, + LITE_GET_OUTPUT_NAME = 10, + LITE_COPY_SERVER_TENSOR_MEMORY = 11, + LITE_DESTROY_NETWORK = 12, +}; + +} // namespace ipc diff --git a/lite/lite-c/src/ipc_imp.cpp b/lite/lite-c/src/ipc_imp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fbf4c3560b5a78cd8ea8b33d250a9b08f7c7ae3d --- /dev/null +++ b/lite/lite-c/src/ipc_imp.cpp @@ -0,0 +1,370 @@ +#include "ipc_imp.h" + +#if __linux__ +#include +#include +#endif + +#ifdef __ANDROID__ +#include +#include +#endif + +namespace ipc_imp { + +namespace { + +//! max count if shm +#define MAX_SHM_COUNT 15 +struct ServerConfig { +#ifdef _LITE_SUPPORT_IPC + pid_t server_id; +#else + size_t server_id; +#endif + void* cb; + int fd_s[2]; + int fd_c[2]; + fd_set select_s; + fd_set select_c; + void* shm_ptr[MAX_SHM_COUNT]; + size_t shm_mem_conut; + //! all shm use the same shm_size + size_t shm_size; +}; + +static LITE_MUTEX m_mtx; + +static ServerConfig server_config; + +#ifdef _LITE_SUPPORT_IPC +static size_t config_shm_memory() { + //! default config to 10MB + size_t shm_size = 10 * 1024 * 1024; + //! env to config LITE_DEBUG_IPC_SHM_SIZE + //! for example , export LITE_DEBUG_IPC_SHM_SIZE=20, means config SHM size 20MB + if (auto env = ::std::getenv("LITE_DEBUG_IPC_SHM_SIZE")) + shm_size = std::stoi(env) * 1024 * 1024; + +#ifdef __ANDROID__ + //! special for Android prop, attention: getprop may need permission + char buf[PROP_VALUE_MAX]; + if (__system_property_get("LITE_DEBUG_IPC_SHM_SIZE", buf) > 0) { + shm_size = std::stoi(buf) * 1024 * 1024; + } +#endif + + return shm_size; +} + +//! FIXME: detail at create_server(), at this stage, we only support enable lite fork +//! debug by: LITE_enable_lite_ipc_debug, after issue fix, may support env: +//! LITE_ENABLE_C_API_WITH_FORK_MODE +// bool config_enable_debug_fork() { +// //! debug off, we only support enable fork debug mode with env +// //! LITE_ENABLE_C_API_WITH_FORK_MODE, not support api to config +// //! as we will fork as soon as possible by __attribute__((constructor)), +// //! user may not have to chance to call any normal api before it +// bool ret = false; +// //! env to config LITE_ENABLE_C_API_WITH_FORK_MODE +// //! for example , export LITE_ENABLE_C_API_WITH_FORK_MODE=1, means enable LITE c +// api +// //! with fork mode +// if (auto env = ::std::getenv("LITE_ENABLE_C_API_WITH_FORK_MODE")) { +// if (std::stoi(env) > 0) { +// ret = true; +// } +// } +// +//#ifdef __ANDROID__ +// //! special for Android prop, attention: getprop may need permission +// char buf[PROP_VALUE_MAX]; +// if (__system_property_get("LITE_ENABLE_C_API_WITH_FORK_MODE", buf) > 0) { +// ret = std::stoi(buf); +// if (std::stoi(buf) > 0) { +// ret = true; +// } +// } +//#endif +// +// return ret; +//} +#endif + +static bool is_enable_debug_fork = false; + +//! internal recycle server when IPC_ASSERT happen +static void recycle_server() { + static struct timeval tv = {1, 0}; + struct MsgBody msg; + msg.type = IPC_SERVER_EXIT; + if (server_config.server_id > 0) { + send_msg(&msg, &tv); + } +} + +#define ipc_unlikely(v) __builtin_expect((v), 0) +#define IPC_ASSERT(expr, msg) \ + do { \ + if (ipc_unlikely(!(expr))) { \ + LITE_ERROR("ipc fatal error: assert failed: %s with msg: %s", #expr, msg); \ + recycle_server(); \ + __builtin_trap(); \ + } \ + } while (0) + +#ifdef _LITE_SUPPORT_IPC +static size_t config_shm_memory_count() { + //! default config to 2 + size_t shm_cnt = 2; + //! env to config LITE_DEBUG_IPC_SHM_COUNT + //! for example , export LITE_DEBUG_IPC_SHM_COUNT=8, means config SHM count 8 + if (auto env = ::std::getenv("LITE_DEBUG_IPC_SHM_COUNT")) + shm_cnt = std::stoi(env); + +#ifdef __ANDROID__ + //! special for Android prop, attention: getprop may need permission + char buf[PROP_VALUE_MAX]; + if (__system_property_get("LITE_DEBUG_IPC_SHM_COUNT", buf) > 0) { + shm_cnt = std::stoi(buf); + } +#endif + IPC_ASSERT( + shm_cnt >= 2 && shm_cnt <= MAX_SHM_COUNT, + "error config LITE_DEBUG_IPC_SHM_COUNT, should be range of [2, " + "MAX_SHM_COUNT]"); + + return shm_cnt; +} +#endif + +#ifdef _LITE_SUPPORT_IPC +static void handle_remote_call(struct MsgBody* msg) { + LITE_DEBUG("into %s: %d", __func__, __LINE__); + IPC_ASSERT( + server_config.cb, + "handle_remote_call failed: can not find valid " + "remote_call_cb, please call " + "register_remote_call_cb firstly!!"); + remote_call_cb cb = (remote_call_cb)server_config.cb; + cb(msg); +} + +static void* ipc_mmap( + void* addr, size_t length, int prot, int flags, int fd, off_t offset) { + void* ret = mmap(addr, length, prot, flags, fd, offset); + IPC_ASSERT(ret != MAP_FAILED, "call mmap failed"); + return ret; +} + +static int ipc_munmap(void* addr, size_t length) { + int ret = munmap(addr, length); + IPC_ASSERT(0 == ret, "call munmap failed"); + return ret; +} +#endif + +//! start server as soon as possible +//! FIXME: when use __attribute__((constructor)) on clang, will init before all +//! static class object, which will lead to flatbuffer runtime issue, env config +//! with init_priority, do not take effect on diff cpp src file on clang +// static __attribute__((constructor)) void create_server() { +void create_server() { +#ifdef _LITE_SUPPORT_IPC + LITE_LOCK_GUARD(m_mtx); + LITE_LOG("try to config lite fork debug model"); + if (is_enable_debug_fork) + return; + + is_enable_debug_fork = true; + //! is_enable_debug_fork = config_enable_debug_fork(); + //! init default server_config + server_config.server_id = 0; + + if (!is_enable_debug_fork) + return; + + server_config.cb = nullptr; + server_config.shm_size = config_shm_memory(); + server_config.shm_mem_conut = config_shm_memory_count(); + + for (size_t i = 0; i < server_config.shm_mem_conut; i++) { + server_config.shm_ptr[i] = ipc_mmap( + NULL, server_config.shm_size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANON, -1, 0); + } + + IPC_ASSERT(-1 != pipe(server_config.fd_s), "create server pipe failed"); + IPC_ASSERT(-1 != pipe(server_config.fd_c), "create client pipe failed"); + + FD_ZERO(&server_config.select_s); + FD_ZERO(&server_config.select_c); + //! config server and client + FD_SET(server_config.fd_s[0], &server_config.select_s); + FD_SET(server_config.fd_c[0], &server_config.select_c); + + server_config.server_id = fork(); + + IPC_ASSERT(server_config.server_id >= 0, "call fork failed"); + + if (server_config.server_id > 0) { + LITE_LOG("create lite_ipc_server success pid is: %d", server_config.server_id); + } else { + std::string server_name = "lite_ipc_server"; + // TODO: __APPLE__ do not support PR_SET_NAME and PR_SET_PDEATHSIG + // if caller crash, no have chance to send IPC_SERVER_EXIT +#if __linux__ + LITE_LOG("start server with name: %s....", server_name.c_str()); + prctl(PR_SET_NAME, (unsigned long)server_name.c_str(), 0, 0, 0); + //! auto die if father crash + prctl(PR_SET_PDEATHSIG, SIGKILL); +#else + LITE_LOG("start server...."); +#endif + + while (1) { + LITE_DEBUG("lite_ipc_server wait msg now....."); + int res = + select(server_config.fd_s[0] + 1, &server_config.select_s, NULL, + NULL, NULL); + + IPC_ASSERT( + res > 0, + "select issue happened or timeout(but we do not support timeout)"); + + struct MsgBody msg; + size_t r_size = read(server_config.fd_s[0], &msg, sizeof(msg)); + IPC_ASSERT(r_size == sizeof(msg), "broken pipe msg"); + + struct MsgBody response; + response.type = IPC_SERVER_RESPONSE; + switch (msg.type) { + case IPC_CALL_REMOTE_API: + LITE_DEBUG("handle remote call"); + handle_remote_call(&msg); + break; + case IPC_CONFIG_REMOTE_HANDLE_API: + LITE_DEBUG("handle register remote cb"); + server_config.cb = msg.cb; + break; + default: + IPC_ASSERT(IPC_SERVER_EXIT == msg.type, "code issue happened!!"); + } + + size_t w_size = write(server_config.fd_c[1], &response, sizeof(response)); + IPC_ASSERT(w_size == sizeof(response), "write pip failed"); + + if (IPC_SERVER_EXIT == msg.type) { + LITE_DEBUG("handle exit now"); + for (size_t i = 0; i < server_config.shm_mem_conut; i++) { + ipc_munmap(server_config.shm_ptr[i], server_config.shm_size); + } + exit(0); + } + } + } +#else + //! TODO: imp for Windows with CreateProcess + server_config.server_id = 0; + LITE_ERROR("lite do not support fork debug ipc on this PLATFORM"); + __builtin_trap(); + + return; +#endif +} + +} // namespace +//////////////////////////////////////////////// api imp ///////////////////////// +void register_remote_call_cb(remote_call_cb cb) { + IPC_ASSERT(!server_config.cb, "already register remote_call_cb"); + IPC_ASSERT(cb, "invalid remote_call_cb"); + IPC_ASSERT(server_config.server_id, "register cb need server already up"); + + server_config.cb = (void*)cb; + static struct timeval tv = {5, 0}; + struct MsgBody msg; + msg.type = IPC_CONFIG_REMOTE_HANDLE_API; + msg.cb = (void*)cb; + send_msg(&msg, &tv); +} + +struct MsgBody send_msg(struct MsgBody* msg, struct timeval* timeout) { +#ifdef _LITE_SUPPORT_IPC + IPC_ASSERT(server_config.server_id > 0, "server not ready"); + if (IPC_CALL_REMOTE_API == msg->type) { + IPC_ASSERT( + server_config.cb, + "can not find valid remote_call_cb, please " + "call register_remote_call_cb firstly!!"); + } + + //! send msg to server + size_t w_size = write(server_config.fd_s[1], msg, sizeof(struct MsgBody)); + IPC_ASSERT(w_size == sizeof(struct MsgBody), "write pipe failed"); + + //! now wait server response + struct MsgBody response; + LITE_DEBUG("wait server response"); + + int res = select( + server_config.fd_c[0] + 1, &server_config.select_c, NULL, NULL, timeout); + if (0 == res) { + LITE_ERROR("wait server timeout"); + } + IPC_ASSERT(res > 0, "select issue happened or timeout"); + + size_t r_size = read(server_config.fd_c[0], &response, sizeof(response)); + IPC_ASSERT(sizeof(response) == r_size, "broken pipe msg"); + IPC_ASSERT(IPC_SERVER_RESPONSE == response.type, "error server response type"); + + return response; +#else + struct MsgBody response; + LITE_ERROR("This code should not be called"); + __builtin_trap(); + + return response; +#endif +} + +bool is_server() { + return !server_config.server_id; +} + +bool is_enable_ipc() { + return is_enable_debug_fork; +} + +void join_server() { +#ifdef _LITE_SUPPORT_IPC + if (!is_enable_debug_fork) + return; + + int ret; + waitpid(server_config.server_id, &ret, 0); + if (ret) { + //! when server crash, we mark server_config.server_id to 0 + //! to prevent handle more msg, for example recycle_server + server_config.server_id = 0; + } + IPC_ASSERT( + ret == 0, "child process exit !zero, please check server log for detail"); +#endif +} + +void* base_get_shm_ptr(size_t index) { + return server_config.shm_ptr[index]; +} + +size_t base_get_shm_count() { + return server_config.shm_mem_conut; +} + +size_t base_get_shm_size() { + return server_config.shm_size; +} + +void enable_lite_ipc_debug() { + create_server(); +} +} // namespace ipc_imp diff --git a/lite/lite-c/src/ipc_imp.h b/lite/lite-c/src/ipc_imp.h new file mode 100644 index 0000000000000000000000000000000000000000..124c8dffd1bdb4f34d885fe0733623d6b5b81b60 --- /dev/null +++ b/lite/lite-c/src/ipc_imp.h @@ -0,0 +1,63 @@ +#pragma once + +#undef _LITE_SUPPORT_IPC +#if __linux__ || __unix__ || __APPLE__ +#define _LITE_SUPPORT_IPC +#endif + +#ifdef _LITE_SUPPORT_IPC +#include +#include +#include +#endif + +#include "misc.h" + +namespace ipc_imp { +//! server call api ret +enum MsgType { + IPC_SERVER_RESPONSE = 1, + IPC_CALL_REMOTE_API = 2, + IPC_SERVER_EXIT = 3, + IPC_CONFIG_REMOTE_HANDLE_API = 4, +}; + +struct MsgBody { + enum MsgType type; + //! remote call handle callback + void* cb; + //! remote call function emum, define by user + size_t remote_func_id; + //! mmap region ptr + void* shm_ptr; +}; + +//! block wait server return response msg +struct MsgBody send_msg(struct MsgBody* msg, struct timeval* timeout); + +//! wait server exit +void join_server(); + +typedef void (*remote_call_cb)(struct MsgBody* msg); + +//! register remote call +void register_remote_call_cb(remote_call_cb cb); + +//! is server or not, server or do not enable ipc mode will return true +bool is_server(); + +//! is enable ipc or not +bool is_enable_ipc(); + +//! get shm ptr +void* base_get_shm_ptr(size_t index); + +//! get shm count +size_t base_get_shm_count(); + +// get shm size +size_t base_get_shm_size(); + +// enable fork ipc debug +void enable_lite_ipc_debug(); +} // namespace ipc_imp diff --git a/lite/lite-c/src/network.cpp b/lite/lite-c/src/network.cpp index 8325c73c3af53400336f7c18eb82147c8626b496..149714eb3654cc82848f56952307b00008779d37 100644 --- a/lite/lite-c/src/network.cpp +++ b/lite/lite-c/src/network.cpp @@ -1,5 +1,6 @@ #include "lite/network.h" #include "common.h" +#include "ipc_helper.h" #include "lite-c/network_c.h" #include "../../src/network_impl_base.h" @@ -204,11 +205,32 @@ int LITE_make_network( LiteNetwork* network, const LiteConfig config, const LiteNetworkIO network_io) { LITE_CAPI_BEGIN(); LITE_ASSERT(network, "The network pass to LITE api is null"); - auto lite_network = std::make_shared( - convert_to_lite_config(config), convert_to_lite_io(network_io)); - LITE_LOCK_GUARD(mtx_network); - get_gloabl_network_holder()[lite_network.get()] = lite_network; - *network = lite_network.get(); + if (ipc_imp::is_server()) { + auto lite_network = std::make_shared( + convert_to_lite_config(config), convert_to_lite_io(network_io)); + LITE_LOCK_GUARD(mtx_network); + get_gloabl_network_holder()[lite_network.get()] = lite_network; + *network = lite_network.get(); + } else { + size_t need_size = sizeof(LiteConfig) + sizeof(LiteNetworkIO); + IPC_INSTACE().check_shm_size(need_size); + + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(nullptr); + + //! second args is const LiteConfig config + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &config, sizeof(LiteConfig)); + //! third args is network_io + memcpy(shm_ptr_c + sizeof(LiteNetworkIO), &network_io, sizeof(LiteNetworkIO)); + + IPC_HELP_REMOTE_CALL(raw_shm_ptr, ipc::RemoteFuncId::LITE_MAKE_NETWORK); + + int* ret_ptr = static_cast(raw_shm_ptr); + auto ret = *ret_ptr; + ret_ptr++; + memcpy(network, ret_ptr, sizeof(LiteNetwork)); + return ret; + } LITE_CAPI_END(); } @@ -234,17 +256,53 @@ int LITE_load_model_from_path(LiteNetwork network, const char* model_path) { LITE_CAPI_BEGIN(); LITE_ASSERT(network, "The network pass to LITE api is null"); LITE_ASSERT(model_path, "The model path pass to LITE api is null"); - static_cast(network)->load_model(model_path); + if (ipc_imp::is_server()) { + static_cast(network)->load_model(model_path); + } else { + size_t need_size = sizeof(LiteNetwork) + strlen(model_path) + 1; + IPC_INSTACE().check_shm_size(need_size); + + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(network); + + //! first args is LiteNetwork network + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &network, sizeof(LiteNetwork)); + //! second args is const char* model_path + strcpy(shm_ptr_c + sizeof(LiteNetwork), model_path); + + IPC_HELP_REMOTE_CALL(raw_shm_ptr, ipc::RemoteFuncId::LITE_LOAD_MODEL_FROM_PATH); + + int* ret_ptr = static_cast(raw_shm_ptr); + auto ret = *ret_ptr; + return ret; + } LITE_CAPI_END(); } int LITE_destroy_network(LiteNetwork network) { LITE_CAPI_BEGIN(); LITE_ASSERT(network, "The network pass to LITE api is null"); - LITE_LOCK_GUARD(mtx_network); - auto& global_holder = get_gloabl_network_holder(); - if (global_holder.find(network) != global_holder.end()) { - global_holder.erase(network); + if (ipc_imp::is_server()) { + LITE_LOCK_GUARD(mtx_network); + auto& global_holder = get_gloabl_network_holder(); + if (global_holder.find(network) != global_holder.end()) { + global_holder.erase(network); + } + } else { + size_t need_size = sizeof(LiteNetwork); + IPC_INSTACE().check_shm_size(need_size); + + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(network); + + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &network, sizeof(LiteNetwork)); + + IPC_HELP_REMOTE_CALL(raw_shm_ptr, ipc::RemoteFuncId::LITE_DESTROY_NETWORK); + + int* ret_ptr = static_cast(raw_shm_ptr); + auto ret = *ret_ptr; + ret_ptr++; + return ret; } LITE_CAPI_END(); } @@ -252,14 +310,48 @@ int LITE_destroy_network(LiteNetwork network) { int LITE_forward(const LiteNetwork network) { LITE_CAPI_BEGIN(); LITE_ASSERT(network, "The network pass to LITE api is null"); - static_cast(network)->forward(); + if (ipc_imp::is_server()) { + static_cast(network)->forward(); + } else { + size_t need_size = sizeof(LiteNetwork); + IPC_INSTACE().check_shm_size(need_size); + + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(network); + + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &network, sizeof(LiteNetwork)); + + IPC_HELP_REMOTE_CALL(raw_shm_ptr, ipc::RemoteFuncId::LITE_FORWARD); + + int* ret_ptr = static_cast(raw_shm_ptr); + auto ret = *ret_ptr; + ret_ptr++; + return ret; + } LITE_CAPI_END(); } int LITE_wait(const LiteNetwork network) { LITE_CAPI_BEGIN(); LITE_ASSERT(network, "The network pass to LITE api is null"); - static_cast(network)->wait(); + if (ipc_imp::is_server()) { + static_cast(network)->wait(); + } else { + size_t need_size = sizeof(LiteNetwork); + IPC_INSTACE().check_shm_size(need_size); + + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(network); + + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &network, sizeof(LiteNetwork)); + + IPC_HELP_REMOTE_CALL(raw_shm_ptr, ipc::RemoteFuncId::LITE_WAIT); + + int* ret_ptr = static_cast(raw_shm_ptr); + auto ret = *ret_ptr; + ret_ptr++; + return ret; + } LITE_CAPI_END(); } @@ -268,9 +360,31 @@ int LITE_get_io_tensor( LiteTensor* tensor) { LITE_CAPI_BEGIN(); LITE_ASSERT(network, "The network pass to LITE api is null"); - auto io_tensor = - static_cast(network)->get_io_tensor(io_name, phase); - *tensor = io_tensor.get(); + if (ipc_imp::is_server()) { + auto io_tensor = + static_cast(network)->get_io_tensor(io_name, phase); + *tensor = io_tensor.get(); + } else { + size_t need_size = + sizeof(LiteNetwork) + strlen(io_name) + 1 + sizeof(LiteTensorPhase); + IPC_INSTACE().check_shm_size(need_size); + + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(network); + + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &network, sizeof(LiteNetwork)); + memcpy(shm_ptr_c + sizeof(LiteNetwork), &phase, sizeof(LiteTensorPhase)); + strcpy(shm_ptr_c + sizeof(LiteNetwork) + sizeof(LiteTensorPhase), io_name); + + IPC_HELP_REMOTE_CALL(raw_shm_ptr, ipc::RemoteFuncId::LITE_GET_IO_TENSOR); + + int* ret_ptr = static_cast(raw_shm_ptr); + auto ret = *ret_ptr; + ret_ptr++; + memcpy(tensor, ret_ptr, sizeof(LiteTensor)); + IPC_INSTACE().release_shm_ptr(network); + return ret; + } LITE_CAPI_END(); } @@ -286,8 +400,31 @@ int LITE_get_output_name(const LiteNetwork network, size_t index, const char** n LITE_CAPI_BEGIN(); LITE_ASSERT(network, "The network pass to LITE api is null"); LITE_ASSERT(name, "The name ptr pass to LITE api is null"); - *name = lite::NetworkHelper::implement(static_cast(network)) - ->get_output_name(index); + if (ipc_imp::is_server()) { + *name = lite::NetworkHelper::implement(static_cast(network)) + ->get_output_name(index); + } else { + size_t need_size = sizeof(LiteNetwork) + sizeof(index); + IPC_INSTACE().check_shm_size(need_size); + + //! warn: we use get_shm_ptr with nullptr, not with network + //! so caller need consume this ptr as soon as possible + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(nullptr); + + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &network, sizeof(LiteNetwork)); + memcpy(shm_ptr_c + sizeof(LiteNetwork), &index, sizeof(size_t)); + + IPC_HELP_REMOTE_CALL(raw_shm_ptr, ipc::RemoteFuncId::LITE_GET_OUTPUT_NAME); + + int* ret_ptr = static_cast(raw_shm_ptr); + auto ret = *ret_ptr; + ret_ptr++; + void* p = static_cast(ret_ptr); + char* p_c = static_cast(p); + *name = p_c; + return ret; + } LITE_CAPI_END(); } diff --git a/lite/lite-c/src/tensor.cpp b/lite/lite-c/src/tensor.cpp index b72bec769d2341fb9306aa96d54e223de5aac2f9..0c57cee2b3167af01f0666c56444ba7eb299637c 100644 --- a/lite/lite-c/src/tensor.cpp +++ b/lite/lite-c/src/tensor.cpp @@ -4,6 +4,7 @@ #include #include "../../src/tensor_impl_base.h" #include "common.h" +#include "ipc_helper.h" #include "lite-c/tensor_c.h" const LiteLayout default_layout = { @@ -166,7 +167,70 @@ int LITE_get_tensor_memory(const LiteTensor tensor, void** data) { LITE_CAPI_BEGIN(); LITE_ASSERT(tensor, "The tensor pass to LITE c_api is null"); LITE_ASSERT(data, "The data ptr pass to LITE c_api is null"); - *data = static_cast(tensor)->get_memory_ptr(); + if (ipc_imp::is_server()) { + *data = static_cast(tensor)->get_memory_ptr(); + } else { + size_t need_size = sizeof(LiteTensor); + IPC_INSTACE().check_shm_size(need_size); + + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(nullptr); + + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &tensor, sizeof(LiteTensor)); + + IPC_HELP_REMOTE_CALL(raw_shm_ptr, ipc::RemoteFuncId::LITE_GET_TENSOR_MEMORY); + + int* ret_ptr = static_cast(raw_shm_ptr); + auto ret = *ret_ptr; + ret_ptr++; + memcpy(data, ret_ptr, sizeof(void*)); + return ret; + } + LITE_CAPI_END(); +} + +void* LITE_memset(void* s, int c, size_t n) { + if (ipc_imp::is_server()) { + return memset(s, c, n); + } else { + size_t need_size = sizeof(void*) + sizeof(int) + sizeof(size_t); + IPC_INSTACE().check_shm_size(need_size); + + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(nullptr); + + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &s, sizeof(void*)); + memcpy(shm_ptr_c + sizeof(void*), &c, sizeof(int)); + memcpy(shm_ptr_c + sizeof(void*) + sizeof(int), &n, sizeof(size_t)); + + IPC_HELP_REMOTE_CALL(raw_shm_ptr, ipc::RemoteFuncId::LITE_MEMSET); + + return s; + } +} + +int LITE_copy_server_tensor_memory( + void* server_ptr, void* client_ptr, size_t size_in_byte) { + LITE_CAPI_BEGIN(); + if (ipc_imp::is_server()) { + LITE_ASSERT( + false, "lite not in fork debug mode, please do not call this function"); + } else { + size_t need_size = sizeof(void*) + sizeof(size_t); + IPC_INSTACE().check_shm_size(need_size); + IPC_INSTACE().check_shm_size(size_in_byte); + + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(nullptr); + + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &server_ptr, sizeof(void*)); + memcpy(shm_ptr_c + sizeof(void*), &size_in_byte, sizeof(size_t)); + + IPC_HELP_REMOTE_CALL( + raw_shm_ptr, ipc::RemoteFuncId::LITE_COPY_SERVER_TENSOR_MEMORY); + memcpy(client_ptr, raw_shm_ptr, size_in_byte); + return 0; + } LITE_CAPI_END(); } @@ -186,7 +250,26 @@ int LITE_get_tensor_total_size_in_byte(const LiteTensor tensor, size_t* size) { LITE_CAPI_BEGIN(); LITE_ASSERT(tensor, "The tensor pass to LITE c_api is null"); LITE_ASSERT(size, "The size ptr pass to LITE c_api is null"); - *size = static_cast(tensor)->get_tensor_total_size_in_byte(); + if (ipc_imp::is_server()) { + *size = static_cast(tensor)->get_tensor_total_size_in_byte(); + } else { + size_t need_size = sizeof(LiteTensor); + IPC_INSTACE().check_shm_size(need_size); + + void* raw_shm_ptr = IPC_INSTACE().get_shm_ptr(nullptr); + + char* shm_ptr_c = static_cast(raw_shm_ptr); + memcpy(shm_ptr_c, &tensor, sizeof(LiteTensor)); + + IPC_HELP_REMOTE_CALL( + raw_shm_ptr, ipc::RemoteFuncId::LITE_GET_TENSOR_TOTAL_SIZE_IN_BYTE); + + int* ret_ptr = static_cast(raw_shm_ptr); + auto ret = *ret_ptr; + ret_ptr++; + memcpy(size, ret_ptr, sizeof(size_t)); + return ret; + } LITE_CAPI_END(); } diff --git a/lite/src/misc.cpp b/lite/src/misc.cpp index ecb882fe4b9528d06e43102223db60a76ebeadc9..9e363f3b58f4319fb0ecae69738bd43542b28a8c 100644 --- a/lite/src/misc.cpp +++ b/lite/src/misc.cpp @@ -11,6 +11,7 @@ #ifdef __ANDROID__ #include +#include #endif using namespace lite; @@ -18,7 +19,26 @@ using namespace lite; namespace lite { namespace log_detail { -LiteLogLevel current_log_level = LiteLogLevel::ERROR; +LiteLogLevel config_default_log_level() { + auto default_level = LiteLogLevel::ERROR; + //! env to config LogLevel + //! DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3, NO_LOG = 4 + //! for example , export RUNTIME_OVERRIDE_LOG_LEVEL=0, means set LogLevel to + //! DEBUG + if (auto env = ::std::getenv("RUNTIME_OVERRIDE_LOG_LEVEL")) + default_level = static_cast(std::stoi(env)); + +#ifdef __ANDROID__ + //! special for Android prop, attention: getprop may need permission + char buf[PROP_VALUE_MAX]; + if (__system_property_get("RUNTIME_OVERRIDE_LOG_LEVEL", buf) > 0) { + default_level = static_cast(atoi(buf)); + } +#endif + + return default_level; +} +LiteLogLevel current_log_level = config_default_log_level(); template constexpr size_t countof(T (&)[N]) {