diff --git a/coreruntime/nimblenet/config_manager/include/config_manager.hpp b/coreruntime/nimblenet/config_manager/include/config_manager.hpp index 92ac458e..fca40f01 100644 --- a/coreruntime/nimblenet/config_manager/include/config_manager.hpp +++ b/coreruntime/nimblenet/config_manager/include/config_manager.hpp @@ -1,202 +1,202 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include -#include -#include - -#include "database_constants.hpp" -#include "logger_constants.hpp" -#include "nlohmann_json.hpp" - -using json = nlohmann::json; - -class CommandCenter; - -/** - * @class Config - * @brief Holds configuration settings for the application passed in initialize API. This - * includes device identity, client credentials, database settings, model information, and runtime - * flags. - */ -class Config { - /** Mutex to protect access to modelIds. */ - mutable std::mutex _configMutex; - - /** List of model identifiers. */ - std::vector modelIds; - - /** - * @brief Initializes the configuration from a JSON object. - * - * @param j JSON object containing configuration fields. - */ - void init(const nlohmann::json& j); - - public: - /** Raw JSON string representing the configuration. */ - std::string configJsonString; - - /** Tag representing the compatibility version of the configuration. */ - std::string compatibilityTag; - - /** Unique device identifier passed on by caller. */ - std::string deviceId; - - /** - * Unique client identifier. - * Used for identifying the client when connecting to a secure SaaS - * platform. - */ - std::string clientId; - - /** Host address for server communication to the SaaS platform. */ - std::string host; - - /** Client secret for authentication. Used along with clientId for secure SaaS platform access. */ - std::string clientSecret; - - /** Internal device identifier added by the SDK. */ - std::string internalDeviceId; - - /** Table metadata for use in on-device DB. */ - std::vector tableInfos; - - /** Debug flag to enable verbose logging or diagnostic behavior. */ - bool debug = false; - - /** - * @brief Maximum number of inputs to persist. - * - * @note To be deprecated. - */ - int maxInputsToSave = 0; - - /** Maximum size of the database in kilobytes. */ - float maxDBSizeKBs = dbconstants::MaxDBSizeKBs; - - /** Maximum size of event logs in kilobytes. */ - float maxEventsSizeKBs = loggerconstants::MaxEventsSizeKBs; - - /** List of cohort identifiers where this configuration will be used. */ - nlohmann::json cohortIds = nlohmann::json::array(); - - /** Flag to indicate whether assets should be fetched from cloud or provided from disk. */ - bool online = false; - -#ifdef SIMULATION_MODE - /** - * @brief Flag indicating whether time is simulated. - * Defaults to true in simulation mode. - */ - bool isTimeSimulated = true; -#else - /** Time simulation is disabled outside of simulation. */ - bool isTimeSimulated = false; -#endif // SIMULATION_MODE - - /** - * @brief Returns a C-style string representing the current configuration state. - * - * @return A dynamically allocated char* string (must be freed by the caller). - */ - char* c_str() { - std::string tables = "["; - for (const auto& it : tableInfos) { - tables += it.dump() + ","; - } - tables += "]"; - std::string models = "["; - for (const auto& model : modelIds) { - models += model + ","; - } - models += "]"; - auto cohortDump = cohortIds.dump(); - - char* ret; - asprintf(&ret, - "deviceId=%s,clientId=%s,clientSecret=****,host=%s,compatibilityTag=%s," - "modelIds=%s, " - "databaseConfig=%s, debug:%s, maxInputsToSave:%d, online:%d, internalDeviceId: %s, " - "isTimeSimulated:%d, maxDBSizeKBs:%f, maxEventSizeKBS: %f, cohorts: %s", - deviceId.c_str(), clientId.c_str(), host.c_str(), compatibilityTag.c_str(), - models.c_str(), tables.c_str(), debug ? "true" : "false", maxInputsToSave, online, - internalDeviceId.c_str(), isTimeSimulated, maxDBSizeKBs, maxEventsSizeKBs, - cohortDump.c_str()); - return ret; - } - - /** - * @brief Checks if the configuration is in debug mode. - * - * @return True if debug is enabled, false otherwise. - */ - bool isDebug() const { return debug; } - - /** - * @brief Retrieves a thread-safe copy of the list of model IDs. - * - * @return Vector of model ID strings. - */ - std::vector get_modelIds() const { - std::lock_guard lck(_configMutex); - auto models = modelIds; - return models; - } - - /** - * @brief Adds a new model ID to the list if it's not already present. - * - * @param modelId The model identifier to add. - * @return True if the model ID was added, false if it already existed. - */ - bool add_model(const std::string& modelId) { - std::lock_guard lck(_configMutex); - auto it = std::find(modelIds.begin(), modelIds.end(), modelId); - if (it == modelIds.end()) { - modelIds.push_back(modelId); - return true; - } - return false; - } - - /** - * @brief Constructs the configuration from a JSON string. - * - * @param configJsonString Raw JSON string containing configuration. - */ - Config(const std::string& configJsonString); - - /** - * @brief Constructs the configuration from a JSON object. - * - * @param json JSON object containing configuration. - */ - Config(const nlohmann::json& json); - - /** Default constructor is deleted. */ - Config() = delete; - - /** Copy constructor is deleted. */ - Config(const Config&) = delete; - - /** Grant access to private members for CommandCenter. */ - friend class CommandCenter; -}; - -/** - * @brief Serializes selected Config fields to a JSON object. These fields are exposed in the - * workflow script. - * - * @param j JSON object to populate. - * @param config Configuration object to serialize. - */ -inline const void to_json(nlohmann::json& j, const Config& config) { - j = nlohmann::json{{"compatibilityTag", config.compatibilityTag}, - {"cohortIds", config.cohortIds}}; -} +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +#include "database_constants.hpp" +#include "logger_constants.hpp" +#include "nlohmann_json.hpp" + +using json = nlohmann::json; + +class CommandCenter; + +/** + * @class Config + * @brief Holds configuration settings for the application passed in initialize API. This + * includes device identity, client credentials, database settings, model information, and runtime + * flags. + */ +class Config { + /** Mutex to protect access to modelIds. */ + mutable std::mutex _configMutex; + + /** List of model identifiers. */ + std::vector modelIds; + + /** + * @brief Initializes the configuration from a JSON object. + * + * @param j JSON object containing configuration fields. + */ + void init(const nlohmann::json& j); + + public: + /** Raw JSON string representing the configuration. */ + std::string configJsonString; + + /** Tag representing the compatibility version of the configuration. */ + std::string compatibilityTag; + + /** Unique device identifier passed on by caller. */ + std::string deviceId; + + /** + * Unique client identifier. + * Used for identifying the client when connecting to a secure SaaS + * platform. + */ + std::string clientId; + + /** Host address for server communication to the SaaS platform. */ + std::string host; + + /** Client secret for authentication. Used along with clientId for secure SaaS platform access. */ + std::string clientSecret; + + /** Internal device identifier added by the SDK. */ + std::string internalDeviceId; + + /** Table metadata for use in on-device DB. */ + std::vector tableInfos; + + /** Debug flag to enable verbose logging or diagnostic behavior. */ + bool debug = false; + + /** + * @brief Maximum number of inputs to persist. + * + * @note To be deprecated. + */ + int maxInputsToSave = 0; + + /** Maximum size of the database in kilobytes. */ + float maxDBSizeKBs = dbconstants::MaxDBSizeKBs; + + /** Maximum size of event logs in kilobytes. */ + float maxEventsSizeKBs = loggerconstants::MaxEventsSizeKBs; + + /** List of cohort identifiers where this configuration will be used. */ + nlohmann::json cohortIds = nlohmann::json::array(); + + /** Flag to indicate whether assets should be fetched from cloud or provided from disk. */ + bool online = false; + +#ifdef SIMULATION_MODE + /** + * @brief Flag indicating whether time is simulated. + * Defaults to true in simulation mode. + */ + bool isTimeSimulated = true; +#else + /** Time simulation is disabled outside of simulation. */ + bool isTimeSimulated = false; +#endif // SIMULATION_MODE + + /** + * @brief Returns a C-style string representing the current configuration state. + * + * @return A dynamically allocated char* string (must be freed by the caller). + */ + char* c_str() { + std::string tables = "["; + for (const auto& it : tableInfos) { + tables += it.dump() + ","; + } + tables += "]"; + std::string models = "["; + for (const auto& model : modelIds) { + models += model + ","; + } + models += "]"; + auto cohortDump = cohortIds.dump(); + + char* ret; + asprintf(&ret, + "deviceId=%s,clientId=%s,clientSecret=****,host=%s,compatibilityTag=%s," + "modelIds=%s, " + "databaseConfig=%s, debug:%s, maxInputsToSave:%d, online:%d, internalDeviceId: %s, " + "isTimeSimulated:%d, maxDBSizeKBs:%f, maxEventSizeKBS: %f, cohorts: %s", + deviceId.c_str(), clientId.c_str(), host.c_str(), compatibilityTag.c_str(), + models.c_str(), tables.c_str(), debug ? "true" : "false", maxInputsToSave, online, + internalDeviceId.c_str(), isTimeSimulated, maxDBSizeKBs, maxEventsSizeKBs, + cohortDump.c_str()); + return ret; + } + + /** + * @brief Checks if the configuration is in debug mode. + * + * @return True if debug is enabled, false otherwise. + */ + bool isDebug() const { return debug; } + + /** + * @brief Retrieves a thread-safe copy of the list of model IDs. + * + * @return Vector of model ID strings. + */ + std::vector get_modelIds() const { + std::lock_guard lck(_configMutex); + auto models = modelIds; + return models; + } + + /** + * @brief Adds a new model ID to the list if it's not already present. + * + * @param modelId The model identifier to add. + * @return True if the model ID was added, false if it already existed. + */ + bool add_model(const std::string& modelId) { + std::lock_guard lck(_configMutex); + auto it = std::find(modelIds.begin(), modelIds.end(), modelId); + if (it == modelIds.end()) { + modelIds.push_back(modelId); + return true; + } + return false; + } + + /** + * @brief Constructs the configuration from a JSON string. + * + * @param configJsonString Raw JSON string containing configuration. + */ + Config(const std::string& configJsonString); + + /** + * @brief Constructs the configuration from a JSON object. + * + * @param json JSON object containing configuration. + */ + Config(const nlohmann::json& json); + + /** Default constructor is deleted. */ + Config() = delete; + + /** Copy constructor is deleted. */ + Config(const Config&) = delete; + + /** Grant access to private members for CommandCenter. */ + friend class CommandCenter; +}; + +/** + * @brief Serializes selected Config fields to a JSON object. These fields are exposed in the + * workflow script. + * + * @param j JSON object to populate. + * @param config Configuration object to serialize. + */ +inline const void to_json(nlohmann::json& j, const Config& config) { + j = nlohmann::json{{"compatibilityTag", config.compatibilityTag}, + {"cohortIds", config.cohortIds}}; +} diff --git a/coreruntime/nimblenet/config_manager/src/config_manager.cpp b/coreruntime/nimblenet/config_manager/src/config_manager.cpp index 1fdcb2bb..735cf349 100644 --- a/coreruntime/nimblenet/config_manager/src/config_manager.cpp +++ b/coreruntime/nimblenet/config_manager/src/config_manager.cpp @@ -1,122 +1,122 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "config_manager.hpp" - -#include "core_sdk_constants.hpp" -#include "logger.hpp" -#include "util.hpp" - -using namespace std; -using json = nlohmann::json; - -Config::Config(const nlohmann::json& j) { init(j); } - -void Config::init(const nlohmann::json& j) { - if (j.find("databaseConfig") != j.end()) { - j.at("databaseConfig").get_to(tableInfos); - } - if (j.find("maxInputsToSave") != j.end()) { - j.at("maxInputsToSave").get_to(maxInputsToSave); - } - if (j.find("modelIds") != j.end()) { - THROW("%s", "modelIds key should not be present in config."); - } - if (j.find("cohortIds") != j.end()) { - if (!j.at("cohortIds").is_array()) { - THROW("%s", "CohortIds must be array of cohorts."); - } - j.at("cohortIds").get_to(cohortIds); - } - -// Set isTimeSimulated flag only in SIMULATION_MODE/TESTING, by default the value will be -// false -#if defined(SIMULATION_MODE) || defined(TESTING) - if (j.find("isTimeSimulated") != j.end()) { - j.at("isTimeSimulated").get_to(isTimeSimulated); - } -#endif - if (j.find("debug") != j.end()) { - j.at("debug").get_to(debug); - } - if (j.find("online") != j.end()) { - j.at("online").get_to(online); - } - - if (j.find("maxDBSizeKBs") != j.end()) { - j.at("maxDBSizeKBs").get_to(maxDBSizeKBs); - } - if (j.find("maxEventsSizeKBs") != j.end()) { - j.at("maxEventsSizeKBs").get_to(maxEventsSizeKBs); - } - if (online) { - j.at("compatibilityTag").get_to(compatibilityTag); - } else { - if (!j.contains("compatibilityTag") || - (j.contains("compatibilityTag") && j.at("compatibilityTag") == "")) { - compatibilityTag = coresdkconstants::DefaultCompatibilityTag; - } else { - j.at("compatibilityTag").get_to(compatibilityTag); - } - } - - if (online) { - j.at("clientId").get_to(clientId); - if (clientId == "") { - THROW("%s", "Expected clientId, found empty string"); - } - - j.at("clientSecret").get_to(clientSecret); - if (clientSecret == "") { - THROW("%s", "Expected clientSecret, found empty string"); - } - -#ifdef SIMULATION_MODE - j.at("clientId").get_to(internalDeviceId); - j.at("clientId").get_to(deviceId); -#else - j.at("internalDeviceId").get_to(internalDeviceId); - if (internalDeviceId == "") { - THROW("%s", "Expected internalDeviceId, found empty string"); - } - if (j.find("deviceId") != j.end()) { - j.at("deviceId").get_to(deviceId); - } - // In case deviceId was not given in config or given as empty string, use internalDeviceId - if (deviceId == "") { - deviceId = internalDeviceId; - } -#endif - j.at("host").get_to(host); - while (host.back() == '/') { - host = host.substr(0, host.size() - 1); - } - if (host.empty()) { - // FEATURE: Can use regex here to check if it's a proper URL - THROW("%s", "Expected host to be a proper URL, found empty"); - } - } - configJsonString = j.dump(); - - if (j.find("sessionId") != j.end()) { - auto sessionIdString = j.at("sessionId").get(); - util::set_session_id(sessionIdString); - } else { - util::set_session_id(""); - } -} - -Config::Config(const std::string& configJsonString) { - nlohmann::json j; - try { - j = nlohmann::json::parse(configJsonString); - } catch (std::exception& e) { - THROW("error=%s in config parsing", e.what()); - } catch (...) { - THROW("%s", "configstr not a valid json"); - } - init(j); -} +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "config_manager.hpp" + +#include "core_sdk_constants.hpp" +#include "logger.hpp" +#include "util.hpp" + +using namespace std; +using json = nlohmann::json; + +Config::Config(const nlohmann::json& j) { init(j); } + +void Config::init(const nlohmann::json& j) { + if (j.find("databaseConfig") != j.end()) { + j.at("databaseConfig").get_to(tableInfos); + } + if (j.find("maxInputsToSave") != j.end()) { + j.at("maxInputsToSave").get_to(maxInputsToSave); + } + if (j.find("modelIds") != j.end()) { + THROW("%s", "modelIds key should not be present in config."); + } + if (j.find("cohortIds") != j.end()) { + if (!j.at("cohortIds").is_array()) { + THROW("%s", "CohortIds must be array of cohorts."); + } + j.at("cohortIds").get_to(cohortIds); + } + +// Set isTimeSimulated flag only in SIMULATION_MODE/TESTING, by default the value will be +// false +#if defined(SIMULATION_MODE) || defined(TESTING) + if (j.find("isTimeSimulated") != j.end()) { + j.at("isTimeSimulated").get_to(isTimeSimulated); + } +#endif + if (j.find("debug") != j.end()) { + j.at("debug").get_to(debug); + } + if (j.find("online") != j.end()) { + j.at("online").get_to(online); + } + + if (j.find("maxDBSizeKBs") != j.end()) { + j.at("maxDBSizeKBs").get_to(maxDBSizeKBs); + } + if (j.find("maxEventsSizeKBs") != j.end()) { + j.at("maxEventsSizeKBs").get_to(maxEventsSizeKBs); + } + if (online) { + j.at("compatibilityTag").get_to(compatibilityTag); + } else { + if (!j.contains("compatibilityTag") || + (j.contains("compatibilityTag") && j.at("compatibilityTag") == "")) { + compatibilityTag = coresdkconstants::DefaultCompatibilityTag; + } else { + j.at("compatibilityTag").get_to(compatibilityTag); + } + } + + if (online) { + j.at("clientId").get_to(clientId); + if (clientId == "") { + THROW("%s", "Expected clientId, found empty string"); + } + + j.at("clientSecret").get_to(clientSecret); + if (clientSecret == "") { + THROW("%s", "Expected clientSecret, found empty string"); + } + +#ifdef SIMULATION_MODE + j.at("clientId").get_to(internalDeviceId); + j.at("clientId").get_to(deviceId); +#else + j.at("internalDeviceId").get_to(internalDeviceId); + if (internalDeviceId == "") { + THROW("%s", "Expected internalDeviceId, found empty string"); + } + if (j.find("deviceId") != j.end()) { + j.at("deviceId").get_to(deviceId); + } + // In case deviceId was not given in config or given as empty string, use internalDeviceId + if (deviceId == "") { + deviceId = internalDeviceId; + } +#endif + j.at("host").get_to(host); + while (host.back() == '/') { + host = host.substr(0, host.size() - 1); + } + if (host.empty()) { + // FEATURE: Can use regex here to check if it's a proper URL + THROW("%s", "Expected host to be a proper URL, found empty"); + } + } + configJsonString = j.dump(); + + if (j.find("sessionId") != j.end()) { + auto sessionIdString = j.at("sessionId").get(); + util::set_session_id(sessionIdString); + } else { + util::set_session_id(""); + } +} + +Config::Config(const std::string& configJsonString) { + nlohmann::json j; + try { + j = nlohmann::json::parse(configJsonString); + } catch (std::exception& e) { + THROW("error=%s in config parsing", e.what()); + } catch (...) { + THROW("%s", "configstr not a valid json"); + } + init(j); +} diff --git a/coreruntime/nimblenet/core_sdk/include/core_sdk.hpp b/coreruntime/nimblenet/core_sdk/include/core_sdk.hpp index 25d8fcfa..8ef0e540 100644 --- a/coreruntime/nimblenet/core_sdk/include/core_sdk.hpp +++ b/coreruntime/nimblenet/core_sdk/include/core_sdk.hpp @@ -1,377 +1,377 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "config_manager.hpp" -#include "core_sdk_constants.hpp" -#include "core_sdk_structs.hpp" -#include "data_variable.hpp" -#include "executor_structs.h" -#include "job_scheduler.hpp" -#include "log_sender.hpp" +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "config_manager.hpp" +#include "core_sdk_constants.hpp" +#include "core_sdk_structs.hpp" +#include "data_variable.hpp" +#include "executor_structs.h" +#include "job_scheduler.hpp" +#include "log_sender.hpp" #include "map_data_variable.hpp" -#include "nimble_net/c_tensor.h" -#include "resource_loader.hpp" -#include "resource_manager.hpp" -#include "result.hpp" -#include "server_api.hpp" -#include "user_events_manager.hpp" -#include "util.hpp" -#ifdef SIMULATION_MODE -#include "nimblenet_py_interface.hpp" -#endif // SIMULATION_MODE - -class TaskManager; -class ResourceManager; -class Executor; -class LogSender; -class UserEventsManager; -class Config; - -/** - * @brief CoreSDK is the main interface for managing SDK lifecycle across deployments. - * - * It has the following main functions: - * 1. During SDK initialization, CoreSDK makes sure that the correct - * deployment from disk/SaaS platform is loaded and corresponding CommandCenter is created. - * 2. CoreSDK also starts a background thread which makes all the network calls for downloading - * assets or sending logs. - * 3. Acts as an interface between nimblenet.cpp and commandCenter. All the public APIs exposed via - * nimblenet.hpp are routed to the correct commandCenter via CoreSDK. - */ -class CoreSDK { - std::shared_ptr _atomicCommandCenter = - nullptr; /**< Current CommandCenter being used by the SDK. */ - std::atomic _threadRunning = false; /**< Indicates if the background thread is active. */ - - /**< Retries and priority values reset when internet is turned back on. */ - std::atomic _cloudConfigFetchRetries = coresdkconstants::DefaultFetchCloudConfigRetries; - std::atomic _sendCrashLogRetries = coresdkconstants::DefaultSendCrashLogRetries; - std::atomic _threadPriorityTries = coresdkconstants::DefaultThreadPriorityTries; - - std::mutex _initMutex; /**< Ensures thread-safe initialization. */ - ne::NullableAtomicPtr - _atomicServerAPI; /**< Atomic pointer to the Server API interface. */ - std::shared_ptr _atomicExternalSender = - nullptr; /**< Shared reference to external log sender. */ - std::shared_ptr _atomicExternalLogger = - nullptr; /**< Shared reference to external logger. */ - - LogSender* _logSender = nullptr; /**< LogSender instance for handling logs. */ - Database* _database = nullptr; /**< Local database reference for storage. */ - std::shared_ptr _config = nullptr; /**< Configuration object for the SDK. */ - std::atomic _initializeSuccess = - false; /**< True if initialization completed successfully. */ - std::atomic _commandCenterReady = false; /**< True if CommandCenter is initialized. */ - - CloudConfigResponse _deviceConfiguration; /**< Cached configuration retrieved from device. */ - bool _cloudConfigFetched = false; /**< Whether cloud config has been fetched at least once. */ - MetricsAgent _metricsAgent; /**< Agent to collect and emit metrics. */ - std::thread _cmdThread; /**< Thread for background command center tasks. */ - std::shared_ptr - _jobScheduler; /**< Schedules background jobs like uploads or syncs. */ - - bool _isCleanupDone = false; /**< True if cleanup has been performed after shutdown. */ - Executor* _mlExecutor = nullptr; /**< Pointer to ML execution engine. */ - -#ifdef SIMULATION_MODE - std::vector - _simulatedUserEvents; /**< Stores simulated events in simulator mode. */ - -#endif // SIMULATION_MODE - - /** - * @brief Load deployment configuration from device storage. - */ - Deployment load_deployment_from_device(); - - /** - * @brief Load fallback (older) deployment configuration. - */ - Deployment load_old_deployment_from_device(); - - /** - * @brief Returns the initialized CommandCenter. - */ - std::shared_ptr command_center() { - if (!_commandCenterReady.load()) return nullptr; - return std::atomic_load(&_atomicCommandCenter); - } - - std::shared_ptr serverAPI() { return _atomicServerAPI.load(); } - - std::shared_ptr externalLogger() { return std::atomic_load(&_atomicExternalLogger); } - - std::shared_ptr externalLogSender() { - return std::atomic_load(&_atomicExternalSender); - } - - /** - * @brief Creates a new CommandCenter. - */ - void new_command_center(const Deployment& cloudConfig); - - /** - * @brief Replace old commandCenter with the new one. - */ - void replace_command_center(const Deployment& cloudConfig); - - /** - * @brief Schedule logs upload in background. - */ - void schedule_work_manager(const CloudConfigResponse& cloudConfig); - - /** - * @brief Saves cloud config locally. - */ - bool save_cloud_config_on_device(const CloudConfigResponse& cloudConfig); - - /** - * @brief Returns the latest cloud config etag. - */ - std::string get_latest_eTag(); - - /** - * @brief Loads deployment in offline mode i.e. with the files present on disk. - */ - Deployment load_deployment_offline(); - - /** - * @brief Function used by the background thread to make API calls and send logs. - */ - void perform_long_running_tasks(); - - /** - * @brief Flush logs/metrics and send via API. - */ - void send_logs_and_metrics(); - - /** - * @brief Creates a new instance of thread and invokes perform_long_running_task function. - */ - void thread_initializer(); - - /** - * @brief Updates server and logger configs. - */ - void update_resource_configs(const CloudConfigResponse& validCoreSDKConfig); - - /** - * @brief Send crash logs. - */ - void send_crash_logs(); - - /** - * @brief Does the minimal initialize of SDK, this is done so we can get logs even if there are - * failure in fetching deployment from cloud. - */ - void atomic_repeatable_minimal_initialize(const MinimalInitializationConfig& minInitConfig); - NimbleNetStatus* process_add_user_event_response(const UserEventsData& userEventsData, - CUserEventsData* cUserEventsData); - NimbleNetStatus* process_module_info(const nlohmann::json assetsJson, const std::string& homeDir); - /** - * @brief Copies an asset from the provided asset.location to homedir with the name accepted by - * SDK. - */ - static void copy_module(const std::shared_ptr asset, Deployment& deployment, - bool addToDeployment); -#ifdef SIMULATION_MODE - /** - * @brief Add simulated user events in internal vector. - */ - bool add_simulation_user_events(nlohmann::json& eventMapJsonString, const std::string& tableName); - /** - * @brief Validates whether the event sent as input is valid. - */ - bool validateUserEvent(nlohmann::json& userEventJson); -#endif - - public: - /** - * @brief Default constructor. - */ - CoreSDK() = default; - - /** - * @brief Destructor to ensure cleanup. - */ - ~CoreSDK(); - - /** - * @brief Initializes the CoreSDK. - */ - void initialize_coreSDK(); - - /** - * @brief Loads previously saved cloud configuration. - */ - void load_cloud_config_from_device(); - - /** - * @brief Adds a user event with raw JSON string input. - */ - NimbleNetStatus* add_user_event(const std::string& eventMapJsonString, - const std::string& eventType, CUserEventsData* cUserEventsData); - - /** - * @brief Adds a structured user event. - */ - NimbleNetStatus* add_user_event(const OpReturnType event, const std::string& eventType, - CUserEventsData* cUserEventsData); - - /** - * @brief Fetches the latest cloud config and updates SDK configurations. - */ - std::pair get_cloud_config_and_update_configurations(); - - /** - * @brief Updates the session ID used in logs and metrics. - */ - void update_session(const std::string& sessionIdString) { util::set_session_id(sessionIdString); } - - /** - * @brief Returns reference to the metrics agent. - */ - MetricsAgent& get_metrics_agent() { return _metricsAgent; } - - /** - * @brief Returns current configuration. - */ - std::shared_ptr get_config() const { return _config; } - - /** - * @brief Logs a metric using internal logger. - */ - void log_metrics(const char* metricType, const nlohmann::json& metric); - - /** - * @brief Logs a raw JSON metric string. - */ - void write_metric(const char* metricType, const char* metricJson); - - /** - * @brief Callback for when internet is detected. - */ - void internet_switched_on(); - - /** - * @brief Saves labels corresponding to inference inputs. - * - * @note To be deprecated. - */ - bool save_labels_for_inference_input(const std::string& modelId, const InferenceRequest& inputs, - const InferenceRequest& labels); - - /** - * @brief Logs execution time of a script or method. - */ - void write_run_method_metric(const char* methodName, long long int androidTime); - - /** - * @brief Initializes SDK with configuration. - */ - NimbleNetStatus* initialize(std::shared_ptr config); - - /** - * @brief Loads a task into memory. - */ - bool load_task(const std::string& taskName, const std::string& taskVersion, - std::string&& taskCode); - - /** - * @brief Runs a task with raw C-style tensors. - */ - NimbleNetStatus* run_task(const char* taskName, const char* functionName, const CTensors inputs, - CTensors* outputs); - - /** - * @brief Runs a task using map-style inputs and outputs. - */ - NimbleNetStatus* run_task(const char* taskName, const char* functionName, - std::shared_ptr inputs, - std::shared_ptr outputs); - - /** - * @brief Frees memory allocated for outputs. - */ - bool deallocate_output_memory(CTensors* output); - - NimbleNetStatus* load_modules(const char* assetsJson, const char* homeDir); - - NimbleNetStatus* load_modules(const OpReturnType assetsJson, const std::string& homeDir); - - NimbleNetStatus* load_modules(const nlohmann::json assetsJson, const std::string& homeDir); - - /** - * @brief Reloads a model with a given execution provider config. - */ - bool reload_model_with_epConfig(const char* modelName, const char* epConfig); - - /** - * @brief Loads a model from local files. - */ - bool load_model_from_file(const char* modelFilePath, const char* inferenceConfigFilePath, - const char* modelId, const char* epConfigJsonChar); - - /** - * @brief Sends events to the server. - */ - bool send_events(const char* params); - - /** - * @brief Checks whether the SDK is ready for operations. - */ - NimbleNetStatus* is_ready(); - - /** - * @brief Registers cleanup callback for the current thread. - */ - static void attach_cleanup_to_thread(); - - /** - * @brief Ensures CoreSDK reaches expected initialization state. - */ - void achieve_state(); - -#ifdef TESTING - /** - * @brief Constructor for testing with direct config injection. - */ - CoreSDK(std::shared_ptr config) { _config = config; }; -#endif - -#ifdef SIMULATION_MODE - /** - * @brief Adds user events from a local file. - */ - bool add_events_from_file(const char* userEventsInputFilePath, const char* tableName); - - /** - * @brief Adds user events from a memory buffer. - */ - bool add_events_from_buffer(const char* userEventsInputBuffer, const char* tableName); - - /** - * @brief Adds simulation user events up to a specific timestamp. - */ - bool add_simulation_user_events_upto_timestamp(int64_t timestamp); - - /** - * @brief Runs a task with inputs until a timestamp is reached. - */ - bool run_task_upto_timestamp(const char* taskName, const char* functionName, const CTensors input, - CTensors* output, int64_t timestamp); -#endif // SIMULATION_MODE -}; +#include "nimble_net/c_tensor.h" +#include "resource_loader.hpp" +#include "resource_manager.hpp" +#include "result.hpp" +#include "server_api.hpp" +#include "user_events_manager.hpp" +#include "util.hpp" +#ifdef SIMULATION_MODE +#include "nimblenet_py_interface.hpp" +#endif // SIMULATION_MODE + +class TaskManager; +class ResourceManager; +class Executor; +class LogSender; +class UserEventsManager; +class Config; + +/** + * @brief CoreSDK is the main interface for managing SDK lifecycle across deployments. + * + * It has the following main functions: + * 1. During SDK initialization, CoreSDK makes sure that the correct + * deployment from disk/SaaS platform is loaded and corresponding CommandCenter is created. + * 2. CoreSDK also starts a background thread which makes all the network calls for downloading + * assets or sending logs. + * 3. Acts as an interface between nimblenet.cpp and commandCenter. All the public APIs exposed via + * nimblenet.hpp are routed to the correct commandCenter via CoreSDK. + */ +class CoreSDK { + std::shared_ptr _atomicCommandCenter = + nullptr; /**< Current CommandCenter being used by the SDK. */ + std::atomic _threadRunning = false; /**< Indicates if the background thread is active. */ + + /**< Retries and priority values reset when internet is turned back on. */ + std::atomic _cloudConfigFetchRetries = coresdkconstants::DefaultFetchCloudConfigRetries; + std::atomic _sendCrashLogRetries = coresdkconstants::DefaultSendCrashLogRetries; + std::atomic _threadPriorityTries = coresdkconstants::DefaultThreadPriorityTries; + + std::mutex _initMutex; /**< Ensures thread-safe initialization. */ + ne::NullableAtomicPtr + _atomicServerAPI; /**< Atomic pointer to the Server API interface. */ + std::shared_ptr _atomicExternalSender = + nullptr; /**< Shared reference to external log sender. */ + std::shared_ptr _atomicExternalLogger = + nullptr; /**< Shared reference to external logger. */ + + LogSender* _logSender = nullptr; /**< LogSender instance for handling logs. */ + Database* _database = nullptr; /**< Local database reference for storage. */ + std::shared_ptr _config = nullptr; /**< Configuration object for the SDK. */ + std::atomic _initializeSuccess = + false; /**< True if initialization completed successfully. */ + std::atomic _commandCenterReady = false; /**< True if CommandCenter is initialized. */ + + CloudConfigResponse _deviceConfiguration; /**< Cached configuration retrieved from device. */ + bool _cloudConfigFetched = false; /**< Whether cloud config has been fetched at least once. */ + MetricsAgent _metricsAgent; /**< Agent to collect and emit metrics. */ + std::thread _cmdThread; /**< Thread for background command center tasks. */ + std::shared_ptr + _jobScheduler; /**< Schedules background jobs like uploads or syncs. */ + + bool _isCleanupDone = false; /**< True if cleanup has been performed after shutdown. */ + Executor* _mlExecutor = nullptr; /**< Pointer to ML execution engine. */ + +#ifdef SIMULATION_MODE + std::vector + _simulatedUserEvents; /**< Stores simulated events in simulator mode. */ + +#endif // SIMULATION_MODE + + /** + * @brief Load deployment configuration from device storage. + */ + Deployment load_deployment_from_device(); + + /** + * @brief Load fallback (older) deployment configuration. + */ + Deployment load_old_deployment_from_device(); + + /** + * @brief Returns the initialized CommandCenter. + */ + std::shared_ptr command_center() { + if (!_commandCenterReady.load()) return nullptr; + return std::atomic_load(&_atomicCommandCenter); + } + + std::shared_ptr serverAPI() { return _atomicServerAPI.load(); } + + std::shared_ptr externalLogger() { return std::atomic_load(&_atomicExternalLogger); } + + std::shared_ptr externalLogSender() { + return std::atomic_load(&_atomicExternalSender); + } + + /** + * @brief Creates a new CommandCenter. + */ + void new_command_center(const Deployment& cloudConfig); + + /** + * @brief Replace old commandCenter with the new one. + */ + void replace_command_center(const Deployment& cloudConfig); + + /** + * @brief Schedule logs upload in background. + */ + void schedule_work_manager(const CloudConfigResponse& cloudConfig); + + /** + * @brief Saves cloud config locally. + */ + bool save_cloud_config_on_device(const CloudConfigResponse& cloudConfig); + + /** + * @brief Returns the latest cloud config etag. + */ + std::string get_latest_eTag(); + + /** + * @brief Loads deployment in offline mode i.e. with the files present on disk. + */ + Deployment load_deployment_offline(); + + /** + * @brief Function used by the background thread to make API calls and send logs. + */ + void perform_long_running_tasks(); + + /** + * @brief Flush logs/metrics and send via API. + */ + void send_logs_and_metrics(); + + /** + * @brief Creates a new instance of thread and invokes perform_long_running_task function. + */ + void thread_initializer(); + + /** + * @brief Updates server and logger configs. + */ + void update_resource_configs(const CloudConfigResponse& validCoreSDKConfig); + + /** + * @brief Send crash logs. + */ + void send_crash_logs(); + + /** + * @brief Does the minimal initialize of SDK, this is done so we can get logs even if there are + * failure in fetching deployment from cloud. + */ + void atomic_repeatable_minimal_initialize(const MinimalInitializationConfig& minInitConfig); + NimbleNetStatus* process_add_user_event_response(const UserEventsData& userEventsData, + CUserEventsData* cUserEventsData); + NimbleNetStatus* process_module_info(const nlohmann::json assetsJson, const std::string& homeDir); + /** + * @brief Copies an asset from the provided asset.location to homedir with the name accepted by + * SDK. + */ + static void copy_module(const std::shared_ptr asset, Deployment& deployment, + bool addToDeployment); +#ifdef SIMULATION_MODE + /** + * @brief Add simulated user events in internal vector. + */ + bool add_simulation_user_events(nlohmann::json& eventMapJsonString, const std::string& tableName); + /** + * @brief Validates whether the event sent as input is valid. + */ + bool validateUserEvent(nlohmann::json& userEventJson); +#endif + + public: + /** + * @brief Default constructor. + */ + CoreSDK() = default; + + /** + * @brief Destructor to ensure cleanup. + */ + ~CoreSDK(); + + /** + * @brief Initializes the CoreSDK. + */ + void initialize_coreSDK(); + + /** + * @brief Loads previously saved cloud configuration. + */ + void load_cloud_config_from_device(); + + /** + * @brief Adds a user event with raw JSON string input. + */ + NimbleNetStatus* add_user_event(const std::string& eventMapJsonString, + const std::string& eventType, CUserEventsData* cUserEventsData); + + /** + * @brief Adds a structured user event. + */ + NimbleNetStatus* add_user_event(const OpReturnType event, const std::string& eventType, + CUserEventsData* cUserEventsData); + + /** + * @brief Fetches the latest cloud config and updates SDK configurations. + */ + std::pair get_cloud_config_and_update_configurations(); + + /** + * @brief Updates the session ID used in logs and metrics. + */ + void update_session(const std::string& sessionIdString) { util::set_session_id(sessionIdString); } + + /** + * @brief Returns reference to the metrics agent. + */ + MetricsAgent& get_metrics_agent() { return _metricsAgent; } + + /** + * @brief Returns current configuration. + */ + std::shared_ptr get_config() const { return _config; } + + /** + * @brief Logs a metric using internal logger. + */ + void log_metrics(const char* metricType, const nlohmann::json& metric); + + /** + * @brief Logs a raw JSON metric string. + */ + void write_metric(const char* metricType, const char* metricJson); + + /** + * @brief Callback for when internet is detected. + */ + void internet_switched_on(); + + /** + * @brief Saves labels corresponding to inference inputs. + * + * @note To be deprecated. + */ + bool save_labels_for_inference_input(const std::string& modelId, const InferenceRequest& inputs, + const InferenceRequest& labels); + + /** + * @brief Logs execution time of a script or method. + */ + void write_run_method_metric(const char* methodName, long long int androidTime); + + /** + * @brief Initializes SDK with configuration. + */ + NimbleNetStatus* initialize(std::shared_ptr config); + + /** + * @brief Loads a task into memory. + */ + bool load_task(const std::string& taskName, const std::string& taskVersion, + std::string&& taskCode); + + /** + * @brief Runs a task with raw C-style tensors. + */ + NimbleNetStatus* run_task(const char* taskName, const char* functionName, const CTensors inputs, + CTensors* outputs); + + /** + * @brief Runs a task using map-style inputs and outputs. + */ + NimbleNetStatus* run_task(const char* taskName, const char* functionName, + std::shared_ptr inputs, + std::shared_ptr outputs); + + /** + * @brief Frees memory allocated for outputs. + */ + bool deallocate_output_memory(CTensors* output); + + NimbleNetStatus* load_modules(const char* assetsJson, const char* homeDir); + + NimbleNetStatus* load_modules(const OpReturnType assetsJson, const std::string& homeDir); + + NimbleNetStatus* load_modules(const nlohmann::json assetsJson, const std::string& homeDir); + + /** + * @brief Reloads a model with a given execution provider config. + */ + bool reload_model_with_epConfig(const char* modelName, const char* epConfig); + + /** + * @brief Loads a model from local files. + */ + bool load_model_from_file(const char* modelFilePath, const char* inferenceConfigFilePath, + const char* modelId, const char* epConfigJsonChar); + + /** + * @brief Sends events to the server. + */ + bool send_events(const char* params); + + /** + * @brief Checks whether the SDK is ready for operations. + */ + NimbleNetStatus* is_ready(); + + /** + * @brief Registers cleanup callback for the current thread. + */ + static void attach_cleanup_to_thread(); + + /** + * @brief Ensures CoreSDK reaches expected initialization state. + */ + void achieve_state(); + +#ifdef TESTING + /** + * @brief Constructor for testing with direct config injection. + */ + CoreSDK(std::shared_ptr config) { _config = config; }; +#endif + +#ifdef SIMULATION_MODE + /** + * @brief Adds user events from a local file. + */ + bool add_events_from_file(const char* userEventsInputFilePath, const char* tableName); + + /** + * @brief Adds user events from a memory buffer. + */ + bool add_events_from_buffer(const char* userEventsInputBuffer, const char* tableName); + + /** + * @brief Adds simulation user events up to a specific timestamp. + */ + bool add_simulation_user_events_upto_timestamp(int64_t timestamp); + + /** + * @brief Runs a task with inputs until a timestamp is reached. + */ + bool run_task_upto_timestamp(const char* taskName, const char* functionName, const CTensors input, + CTensors* output, int64_t timestamp); +#endif // SIMULATION_MODE +}; diff --git a/coreruntime/nimblenet/nimble_net/include/nimblenet.h b/coreruntime/nimblenet/nimble_net/include/nimblenet.h index 4fb8b1ef..f9e79cb4 100644 --- a/coreruntime/nimblenet/nimble_net/include/nimblenet.h +++ b/coreruntime/nimblenet/nimble_net/include/nimblenet.h @@ -1,200 +1,200 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "executor_structs.h" - -#ifdef SIMULATION_MODE -#include -#endif +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "executor_structs.h" + +#ifdef SIMULATION_MODE +#include +#endif #include "nimble_net/c_tensor.h" - -#pragma GCC visibility push(default) - -#ifdef __cplusplus -extern "C" { -#endif - -// ============================== -// Public C-style NimbleNet API Functions -// ============================== - -/** - * @brief Initializes the NimbleNet runtime with the given configuration. - * - * @param configJson JSON string containing config parameters. - * @param homeDirectory Path to the directory on device where SDK with store all the assets, logs - * and user events. - * @return Pointer to NimbleNetStatus indicating success/failure. - */ -NimbleNetStatus* initialize_nimblenet(const char* configJson, const char* homeDirectory); - -/** - * @brief Adds a single event to the event store. - * - * @param eventMapJsonString JSON string representing event key-value pairs. - * @param eventType String identifier for event type. - * @param cUserEventsData Pointer to struct with updated eventType and event. - * @return Pointer to NimbleNetStatus indicating result. - */ -NimbleNetStatus* add_event(const char* eventMapJsonString, const char* eventType, - CUserEventsData* cUserEventsData); - -/** - * @brief Returns whether the SDK is ready to accept delitepy function calls, user events etc. - * - * @return Pointer to NimbleNetStatus representing readiness. - */ -NimbleNetStatus* is_ready(); - -/** - * @brief Runs a method from the delitepy script with the given inputs and collects outputs. - * - * @param functionName Name of the method to invoke. - * @param inputs Struct containing input tensors. - * @param outputs Pointer to an output tensor struct to be filled. - * @return NimbleNetStatus* indicating success/failure. - */ -NimbleNetStatus* run_method(const char* functionName, const CTensors inputs, CTensors* outputs); - -/** - * @brief Updates the session context with the given session ID string. - */ -void update_session(const char* sessionIdString); - -/** - * @brief Frees allocated nimblenet resources. - */ -void deallocate_nimblenet(); - -/** - * @brief Copies assets provided from disk into homeDirectory. - */ -NimbleNetStatus* load_modules(const char* assetsJson, const char* homeDirectory); - -/** - * @brief Sends a crash log to the monitoring backend. - * - * @param errorMessage Description or traceback of the crash. - */ -void send_crash_log(const char* errorMessage); - -/** - * @brief Records a generic metric to internal logs. - */ -void write_metric(const char* metricType, const char* metricJson); - -/** - * @brief Records timing data for a run_method invocation. - */ -void write_run_method_metric(const char* methodName, long long int totalTimeInUSecs); - -/** - * @brief Sends all the events stored on disk to cloud. - * - * @param params Optional parameters (e.g. metadata). - * @param homeDirectory Filesystem path context. - * @return true if successful, false otherwise. - */ -bool send_events(const char* params, const char* homeDirectory); - -/** - * @brief Indicates to NimbleNet that network access is restored. - */ -void internet_switched_on(); - -/** - * @brief Associates labels with a given model input for training or validation. - * @deprecated - * - * @param modelId Identifier of the model. - * @param input Input tensors. - * @param label Corresponding label tensors. - * @return true on success, false on failure. - */ -bool save_labels_for_inference_input(const char* modelId, const InferenceRequest input, - const InferenceRequest label); - -/** - * @brief Frees memory allocated to output tensors. - */ -bool deallocate_output_memory2(CTensors* output); - -#ifdef SIMULATION_MODE -// ============================== -// Simulation/Test Mode Functions -// ============================== - -/** - * @brief Loads user events from a file and adds them to the event table. - */ -bool add_events_from_file(const char* userEventsFilePath, const char* tableName); - -/** - * @brief Adds user events from an in-memory buffer. - */ -bool add_events_from_buffer(const char* userEventsBuffer, const char* tableName); - -/** - * @brief Runs a method from delitepy script up to the specified simulation timestamp. - */ -bool run_task_upto_timestamp(const char* functionName, const CTensors input, CTensors* output, - int64_t timestamp); - -/** - * @brief Returns build flags used while compiling. Used when running tests in nimblenet_py. - */ -const char** get_build_flags(); -#endif // SIMULATION_MODE - -// ============================== -// Utilities (Testing / App-Specific) -// ============================== - -/** - * @brief Resets internal NimbleNet state. - */ -void reset(); - -/** - * @brief Deletes the local NimbleNet database (used for events, etc.). - * - * @note use this method only if sqlite based database is used i.e. code compiled with NOAQL flag as - * false. - */ -void delete_database(); - -/** - * @brief Reloads a model with a new execution provider configuration. - */ -bool reload_model_with_epConfig(const char* modelName, const char* epConfig); - -/** - * @brief Loads a model and its inference configuration from disk. - */ -bool load_model_from_file(const char* modelFilePath, const char* inferenceConfigFilePath, - const char* modelId, const char* epConfigJsonChar); - -/** - * @brief Parses a JSON string and returns a pointer to a created JSON object. - */ -void* create_json_object_from_string(const char* json_string); - -/** - * @brief Loads a serialized delitepy script into memory for execution. - */ -bool load_task(const char* taskCode); - -/** - * @brief Attaches cleanup logic to the current thread for handling crashes. - */ -bool attach_cleanup_to_thread(); - -#ifdef __cplusplus -} -#endif - -#pragma GCC visibility pop + +#pragma GCC visibility push(default) + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================== +// Public C-style NimbleNet API Functions +// ============================== + +/** + * @brief Initializes the NimbleNet runtime with the given configuration. + * + * @param configJson JSON string containing config parameters. + * @param homeDirectory Path to the directory on device where SDK with store all the assets, logs + * and user events. + * @return Pointer to NimbleNetStatus indicating success/failure. + */ +NimbleNetStatus* initialize_nimblenet(const char* configJson, const char* homeDirectory); + +/** + * @brief Adds a single event to the event store. + * + * @param eventMapJsonString JSON string representing event key-value pairs. + * @param eventType String identifier for event type. + * @param cUserEventsData Pointer to struct with updated eventType and event. + * @return Pointer to NimbleNetStatus indicating result. + */ +NimbleNetStatus* add_event(const char* eventMapJsonString, const char* eventType, + CUserEventsData* cUserEventsData); + +/** + * @brief Returns whether the SDK is ready to accept delitepy function calls, user events etc. + * + * @return Pointer to NimbleNetStatus representing readiness. + */ +NimbleNetStatus* is_ready(); + +/** + * @brief Runs a method from the delitepy script with the given inputs and collects outputs. + * + * @param functionName Name of the method to invoke. + * @param inputs Struct containing input tensors. + * @param outputs Pointer to an output tensor struct to be filled. + * @return NimbleNetStatus* indicating success/failure. + */ +NimbleNetStatus* run_method(const char* functionName, const CTensors inputs, CTensors* outputs); + +/** + * @brief Updates the session context with the given session ID string. + */ +void update_session(const char* sessionIdString); + +/** + * @brief Frees allocated nimblenet resources. + */ +void deallocate_nimblenet(); + +/** + * @brief Copies assets provided from disk into homeDirectory. + */ +NimbleNetStatus* load_modules(const char* assetsJson, const char* homeDirectory); + +/** + * @brief Sends a crash log to the monitoring backend. + * + * @param errorMessage Description or traceback of the crash. + */ +void send_crash_log(const char* errorMessage); + +/** + * @brief Records a generic metric to internal logs. + */ +void write_metric(const char* metricType, const char* metricJson); + +/** + * @brief Records timing data for a run_method invocation. + */ +void write_run_method_metric(const char* methodName, long long int totalTimeInUSecs); + +/** + * @brief Sends all the events stored on disk to cloud. + * + * @param params Optional parameters (e.g. metadata). + * @param homeDirectory Filesystem path context. + * @return true if successful, false otherwise. + */ +bool send_events(const char* params, const char* homeDirectory); + +/** + * @brief Indicates to NimbleNet that network access is restored. + */ +void internet_switched_on(); + +/** + * @brief Associates labels with a given model input for training or validation. + * @deprecated + * + * @param modelId Identifier of the model. + * @param input Input tensors. + * @param label Corresponding label tensors. + * @return true on success, false on failure. + */ +bool save_labels_for_inference_input(const char* modelId, const InferenceRequest input, + const InferenceRequest label); + +/** + * @brief Frees memory allocated to output tensors. + */ +bool deallocate_output_memory2(CTensors* output); + +#ifdef SIMULATION_MODE +// ============================== +// Simulation/Test Mode Functions +// ============================== + +/** + * @brief Loads user events from a file and adds them to the event table. + */ +bool add_events_from_file(const char* userEventsFilePath, const char* tableName); + +/** + * @brief Adds user events from an in-memory buffer. + */ +bool add_events_from_buffer(const char* userEventsBuffer, const char* tableName); + +/** + * @brief Runs a method from delitepy script up to the specified simulation timestamp. + */ +bool run_task_upto_timestamp(const char* functionName, const CTensors input, CTensors* output, + int64_t timestamp); + +/** + * @brief Returns build flags used while compiling. Used when running tests in nimblenet_py. + */ +const char** get_build_flags(); +#endif // SIMULATION_MODE + +// ============================== +// Utilities (Testing / App-Specific) +// ============================== + +/** + * @brief Resets internal NimbleNet state. + */ +void reset(); + +/** + * @brief Deletes the local NimbleNet database (used for events, etc.). + * + * @note use this method only if sqlite based database is used i.e. code compiled with NOAQL flag as + * false. + */ +void delete_database(); + +/** + * @brief Reloads a model with a new execution provider configuration. + */ +bool reload_model_with_epConfig(const char* modelName, const char* epConfig); + +/** + * @brief Loads a model and its inference configuration from disk. + */ +bool load_model_from_file(const char* modelFilePath, const char* inferenceConfigFilePath, + const char* modelId, const char* epConfigJsonChar); + +/** + * @brief Parses a JSON string and returns a pointer to a created JSON object. + */ +void* create_json_object_from_string(const char* json_string); + +/** + * @brief Loads a serialized delitepy script into memory for execution. + */ +bool load_task(const char* taskCode); + +/** + * @brief Attaches cleanup logic to the current thread for handling crashes. + */ +bool attach_cleanup_to_thread(); + +#ifdef __cplusplus +} +#endif + +#pragma GCC visibility pop diff --git a/coreruntime/nimblenet/nimble_net/src/nimblenet.cpp b/coreruntime/nimblenet/nimble_net/src/nimblenet.cpp index b10c37db..cc1baca3 100644 --- a/coreruntime/nimblenet/nimble_net/src/nimblenet.cpp +++ b/coreruntime/nimblenet/nimble_net/src/nimblenet.cpp @@ -1,293 +1,293 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "nimblenet.h" - -#include - -#include "config_manager.hpp" -#include "core_sdk.hpp" -#include "executor_structs.h" -#include "native_interface.hpp" -#include "nimblenet.hpp" -#include "util.hpp" - -#ifndef MINIMAL_BUILD -#include "concurrent_executor_variable.hpp" -#endif // MINIMAL_BUILD - -using namespace std; - -std::unique_ptr coreSDK = std::make_unique(); - -// nimblenet C start -/** - * Initialise logger -> load user configs -> initialize coreSDK - */ -NimbleNetStatus* initialize_nimblenet_unwrapped(const char* configJson, const char* homeDirectory) { - auto config = std::shared_ptr(new Config(std::string(configJson))); - logger->set_debug_flag(config->debug); - nativeinterface::HOMEDIR = std::string(homeDirectory) + "/"; - if (!nativeinterface::create_folder(nativeinterface::HOMEDIR)) { - return util::nimblestatus(1, "%s", "Could not create homeDir"); - } - - bool initLogger; - initLogger = logger->init_logger(nativeinterface::HOMEDIR + loggerconstants::LogDir); - - // Do not initialize nimbleSDK if the logger is unable to initialize - if (!initLogger) return util::nimblestatus(TERMINAL_ERROR, "%s", "unable to init logger"); - - return coreSDK->initialize(config); -} - -void send_crash_log(const char* errorMessage) { - nativeinterface::save_file_on_device_common(errorMessage, "segfault.log"); -} - -NimbleNetStatus* initialize_nimblenet(const char* configJson, const char* homeDirectory) { - TRY_CATCH_RETURN_NIMBLESTATUS(initialize_nimblenet_unwrapped(configJson, homeDirectory)); -} - -void write_metric(const char* metricType, const char* metricJson) { - TRY_CATCH_RETURN_VOID(coreSDK->write_metric(metricType, metricJson)); -} - -NimbleNetStatus* add_event(const char* eventMapJsonString, const char* eventType, - CUserEventsData* cUserEventsData) { - return nimblenet::add_event(std::string{eventMapJsonString}, std::string{eventType}, - cUserEventsData); -} - -NimbleNetStatus* add_event(const OpReturnType event, const char* eventType, - CUserEventsData* cUserEventsData) { - return nimblenet::add_event(event, std::string{eventType}, cUserEventsData); -} - -NimbleNetStatus* is_ready() { TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->is_ready()); } - -void update_session(const char* sessionIdString) { coreSDK->update_session(sessionIdString); } - -void deallocate_nimblenet() { - coreSDK.reset(); - logger.reset(); - -#ifndef MINIMAL_BUILD - ConcurrentExecutorVariable::reset_threadpool(); -#endif // MINIMAL_BUILD -} - -void internet_switched_on() { TRY_CATCH_RETURN_VOID(coreSDK->internet_switched_on()); } - -bool save_labels_for_inference_input(const char* modelId, const InferenceRequest inputs, - const InferenceRequest labels) { - return coreSDK->save_labels_for_inference_input(modelId, inputs, labels); -} - -void write_run_method_metric(const char* methodName, long long int totalTimeInUSecs) { - TRY_CATCH_RETURN_VOID(coreSDK->write_run_method_metric(methodName, totalTimeInUSecs)); -} - -NimbleNetStatus* run_method(const char* functionName, const CTensors inputs, CTensors* outputs) { - TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->run_task(GLOBALTASKNAME, functionName, inputs, outputs)); -} - -bool deallocate_output_memory2(CTensors* output) { - TRY_CATCH_RETURN_DEFAULT(coreSDK->deallocate_output_memory(output), false); -} - -NimbleNetStatus* load_modules(const char* assetsJson, const char* homeDir) { - TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->load_modules(assetsJson, homeDir)); -} - -#ifdef SIMULATION_MODE - -const char** get_build_flags() { - const char** buildFlags = new const char*[10]; - int idx = 0; - -#ifdef GENAI - buildFlags[idx++] = "GENAI"; -#endif // GENAI - -#ifdef ORT_EXTENSIONS - buildFlags[idx++] = "ORT_EXTENSIONS"; -#endif // ORT_EXTENSIONS - -#ifdef MINIMAL_BUILD - buildFlags[idx++] = "MINIMAL_BUILD"; -#endif // MINIMAL_BUILD - - // NOTE: This should always come in the end - buildFlags[idx] = nullptr; - return buildFlags; -} - -#endif // SIMULATION_MODE -// nimblenet C end -#ifdef SIMULATION_MODE - -bool add_events_from_file(const char* userInputFilePath, const char* tableName) { - return coreSDK->add_events_from_file(userInputFilePath, tableName); -} - -bool add_events_from_buffer(const char* userInputBuffer, const char* tableName) { - return coreSDK->add_events_from_buffer(userInputBuffer, tableName); -} - -bool run_task_upto_timestamp(const char* functionName, const CTensors input, CTensors* output, - int64_t timestamp) { - return coreSDK->run_task_upto_timestamp(GLOBALTASKNAME, functionName, input, output, timestamp); -} - -#endif - -// nimblenetInternal C start - -void reset() { - coreSDK = std::make_unique(); - logger = std::make_shared(); - Time::reset(); - -#ifndef MINIMAL_BUILD - ConcurrentExecutorVariable::reset_threadpool(); -#endif // MINIMAL_BUILD -} - -// Load model and inference configs from a given file and then save in _session -bool load_model_from_file(const char* modelFilePath, const char* inferenceConfigFilePath, - const char* modelId, const char* epConfigJsonChar) { - return coreSDK->load_model_from_file(modelFilePath, inferenceConfigFilePath, modelId, - epConfigJsonChar); -} - -void delete_database() { - auto fileName = (nativeinterface::HOMEDIR + DEFAULT_SQLITE_DB_NAME); - remove(fileName.c_str()); -} - -bool reload_model_with_epConfig(const char* modelName, const char* epConfig) { - return coreSDK->reload_model_with_epConfig(modelName, epConfig); -} - -void* create_json_object_from_string(const char* json_string) { - try { - nlohmann::json* j = new nlohmann::json(nlohmann::json::parse(json_string)); - return reinterpret_cast(j); - } catch (...) { - return nullptr; - } -} - -bool load_task(const char* taskCode) { - return coreSDK->load_task(GLOBALTASKNAME, "1.0.0", taskCode); -} - -bool attach_cleanup_to_thread() { - CoreSDK::attach_cleanup_to_thread(); - return true; -} - -bool send_events(const char* params, const char* homeDirectory) { - nativeinterface::HOMEDIR = std::string(homeDirectory) + "/"; - nativeinterface::create_folder(nativeinterface::HOMEDIR); - bool initLogger; - initLogger = logger->init_logger(nativeinterface::HOMEDIR + loggerconstants::LogDir); - - // Do not initialize nimbleSDK if the logger is unable to initialize - if (!initLogger) return false; - - return coreSDK->send_events(params); -} - -// nimblenetInternal C end - -/// adding implementations for cxx header - -namespace nimblenet { -NimbleNetStatus* initialize_nimblenet(const std::string& configJson, - const std::string& homeDirectory) { - return ::initialize_nimblenet(configJson.c_str(), homeDirectory.c_str()); -} - -NimbleNetStatus* add_event(const std::string& eventMapJsonString, const std::string& eventType, - CUserEventsData* cUserEventsData) { - TRY_CATCH_RETURN_NIMBLESTATUS( - coreSDK->add_user_event(eventMapJsonString, eventType, cUserEventsData)); -} - -NimbleNetStatus* add_event(const OpReturnType event, const std::string& eventType, - CUserEventsData* cUserEventsData) { - TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->add_user_event(event, eventType, cUserEventsData)); -} - -// v2 -NimbleNetStatus* run_method(const std::string& functionName, - std::shared_ptr inputs, - std::shared_ptr outputs) { - TRY_CATCH_RETURN_NIMBLESTATUS( - coreSDK->run_task(GLOBALTASKNAME, functionName.c_str(), inputs, outputs)); -} - -NimbleNetStatus* is_ready() { return ::is_ready(); } - -void update_session(const std::string& sessionIdString) { - return ::update_session(sessionIdString.c_str()); -} - -void deallocate_nimblenet() { ::deallocate_nimblenet(); } - -NimbleNetStatus* load_modules(const OpReturnType assetsJson, const std::string& homeDir) { - TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->load_modules(assetsJson, homeDir)); -} - -NimbleNetStatus* load_modules(const nlohmann::json assetsJson, const std::string& homeDir) { - TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->load_modules(assetsJson, homeDir)); -} - -// internal -void send_crash_log(const std::string& errorMessage) { - return ::send_crash_log(errorMessage.c_str()); -} - -void internet_switched_on() { return ::internet_switched_on(); } - -void write_metric(const std::string& metricType, const std::string& metricJson) { - return ::write_metric(metricType.c_str(), metricJson.c_str()); -} - -void write_run_method_metric(const std::string& methodName, long long int totalTimeInUSecs) { - return ::write_run_method_metric(methodName.c_str(), totalTimeInUSecs); -} - -bool send_events(const std::string& params, const std::string& homeDirectory) { - return ::send_events(params.c_str(), homeDirectory.c_str()); -} - -} // namespace nimblenet - -namespace nimblenetInternal { -////////// For lambda testing and DemoApp - -bool reload_model_with_epConfig(const std::string& modelName, const std::string& epConfig) { - return ::reload_model_with_epConfig(modelName.c_str(), epConfig.c_str()); -} - -bool load_model_from_file(const std::string& modelFilePath, - const std::string& inferenceConfigFilePath, const std::string& modelId, - const std::string& epConfigJsonChar) { - return ::load_model_from_file(modelFilePath.c_str(), inferenceConfigFilePath.c_str(), - modelId.c_str(), epConfigJsonChar.c_str()); -} - -void reset() { ::reset(); } - -void delete_database() { ::delete_database(); } - -bool attach_cleanup_to_thread() { return ::attach_cleanup_to_thread(); } - -////////// -} // namespace nimblenetInternal +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "nimblenet.h" + +#include + +#include "config_manager.hpp" +#include "core_sdk.hpp" +#include "executor_structs.h" +#include "native_interface.hpp" +#include "nimblenet.hpp" +#include "util.hpp" + +#ifndef MINIMAL_BUILD +#include "concurrent_executor_variable.hpp" +#endif // MINIMAL_BUILD + +using namespace std; + +std::unique_ptr coreSDK = std::make_unique(); + +// nimblenet C start +/** + * Initialise logger -> load user configs -> initialize coreSDK + */ +NimbleNetStatus* initialize_nimblenet_unwrapped(const char* configJson, const char* homeDirectory) { + auto config = std::shared_ptr(new Config(std::string(configJson))); + logger->set_debug_flag(config->debug); + nativeinterface::HOMEDIR = std::string(homeDirectory) + "/"; + if (!nativeinterface::create_folder(nativeinterface::HOMEDIR)) { + return util::nimblestatus(1, "%s", "Could not create homeDir"); + } + + bool initLogger; + initLogger = logger->init_logger(nativeinterface::HOMEDIR + loggerconstants::LogDir); + + // Do not initialize nimbleSDK if the logger is unable to initialize + if (!initLogger) return util::nimblestatus(TERMINAL_ERROR, "%s", "unable to init logger"); + + return coreSDK->initialize(config); +} + +void send_crash_log(const char* errorMessage) { + nativeinterface::save_file_on_device_common(errorMessage, "segfault.log"); +} + +NimbleNetStatus* initialize_nimblenet(const char* configJson, const char* homeDirectory) { + TRY_CATCH_RETURN_NIMBLESTATUS(initialize_nimblenet_unwrapped(configJson, homeDirectory)); +} + +void write_metric(const char* metricType, const char* metricJson) { + TRY_CATCH_RETURN_VOID(coreSDK->write_metric(metricType, metricJson)); +} + +NimbleNetStatus* add_event(const char* eventMapJsonString, const char* eventType, + CUserEventsData* cUserEventsData) { + return nimblenet::add_event(std::string{eventMapJsonString}, std::string{eventType}, + cUserEventsData); +} + +NimbleNetStatus* add_event(const OpReturnType event, const char* eventType, + CUserEventsData* cUserEventsData) { + return nimblenet::add_event(event, std::string{eventType}, cUserEventsData); +} + +NimbleNetStatus* is_ready() { TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->is_ready()); } + +void update_session(const char* sessionIdString) { coreSDK->update_session(sessionIdString); } + +void deallocate_nimblenet() { + coreSDK.reset(); + logger.reset(); + +#ifndef MINIMAL_BUILD + ConcurrentExecutorVariable::reset_threadpool(); +#endif // MINIMAL_BUILD +} + +void internet_switched_on() { TRY_CATCH_RETURN_VOID(coreSDK->internet_switched_on()); } + +bool save_labels_for_inference_input(const char* modelId, const InferenceRequest inputs, + const InferenceRequest labels) { + return coreSDK->save_labels_for_inference_input(modelId, inputs, labels); +} + +void write_run_method_metric(const char* methodName, long long int totalTimeInUSecs) { + TRY_CATCH_RETURN_VOID(coreSDK->write_run_method_metric(methodName, totalTimeInUSecs)); +} + +NimbleNetStatus* run_method(const char* functionName, const CTensors inputs, CTensors* outputs) { + TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->run_task(GLOBALTASKNAME, functionName, inputs, outputs)); +} + +bool deallocate_output_memory2(CTensors* output) { + TRY_CATCH_RETURN_DEFAULT(coreSDK->deallocate_output_memory(output), false); +} + +NimbleNetStatus* load_modules(const char* assetsJson, const char* homeDir) { + TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->load_modules(assetsJson, homeDir)); +} + +#ifdef SIMULATION_MODE + +const char** get_build_flags() { + const char** buildFlags = new const char*[10]; + int idx = 0; + +#ifdef GENAI + buildFlags[idx++] = "GENAI"; +#endif // GENAI + +#ifdef ORT_EXTENSIONS + buildFlags[idx++] = "ORT_EXTENSIONS"; +#endif // ORT_EXTENSIONS + +#ifdef MINIMAL_BUILD + buildFlags[idx++] = "MINIMAL_BUILD"; +#endif // MINIMAL_BUILD + + // NOTE: This should always come in the end + buildFlags[idx] = nullptr; + return buildFlags; +} + +#endif // SIMULATION_MODE +// nimblenet C end +#ifdef SIMULATION_MODE + +bool add_events_from_file(const char* userInputFilePath, const char* tableName) { + return coreSDK->add_events_from_file(userInputFilePath, tableName); +} + +bool add_events_from_buffer(const char* userInputBuffer, const char* tableName) { + return coreSDK->add_events_from_buffer(userInputBuffer, tableName); +} + +bool run_task_upto_timestamp(const char* functionName, const CTensors input, CTensors* output, + int64_t timestamp) { + return coreSDK->run_task_upto_timestamp(GLOBALTASKNAME, functionName, input, output, timestamp); +} + +#endif + +// nimblenetInternal C start + +void reset() { + coreSDK = std::make_unique(); + logger = std::make_shared(); + Time::reset(); + +#ifndef MINIMAL_BUILD + ConcurrentExecutorVariable::reset_threadpool(); +#endif // MINIMAL_BUILD +} + +// Load model and inference configs from a given file and then save in _session +bool load_model_from_file(const char* modelFilePath, const char* inferenceConfigFilePath, + const char* modelId, const char* epConfigJsonChar) { + return coreSDK->load_model_from_file(modelFilePath, inferenceConfigFilePath, modelId, + epConfigJsonChar); +} + +void delete_database() { + auto fileName = (nativeinterface::HOMEDIR + DEFAULT_SQLITE_DB_NAME); + remove(fileName.c_str()); +} + +bool reload_model_with_epConfig(const char* modelName, const char* epConfig) { + return coreSDK->reload_model_with_epConfig(modelName, epConfig); +} + +void* create_json_object_from_string(const char* json_string) { + try { + nlohmann::json* j = new nlohmann::json(nlohmann::json::parse(json_string)); + return reinterpret_cast(j); + } catch (...) { + return nullptr; + } +} + +bool load_task(const char* taskCode) { + return coreSDK->load_task(GLOBALTASKNAME, "1.0.0", taskCode); +} + +bool attach_cleanup_to_thread() { + CoreSDK::attach_cleanup_to_thread(); + return true; +} + +bool send_events(const char* params, const char* homeDirectory) { + nativeinterface::HOMEDIR = std::string(homeDirectory) + "/"; + nativeinterface::create_folder(nativeinterface::HOMEDIR); + bool initLogger; + initLogger = logger->init_logger(nativeinterface::HOMEDIR + loggerconstants::LogDir); + + // Do not initialize nimbleSDK if the logger is unable to initialize + if (!initLogger) return false; + + return coreSDK->send_events(params); +} + +// nimblenetInternal C end + +/// adding implementations for cxx header + +namespace nimblenet { +NimbleNetStatus* initialize_nimblenet(const std::string& configJson, + const std::string& homeDirectory) { + return ::initialize_nimblenet(configJson.c_str(), homeDirectory.c_str()); +} + +NimbleNetStatus* add_event(const std::string& eventMapJsonString, const std::string& eventType, + CUserEventsData* cUserEventsData) { + TRY_CATCH_RETURN_NIMBLESTATUS( + coreSDK->add_user_event(eventMapJsonString, eventType, cUserEventsData)); +} + +NimbleNetStatus* add_event(const OpReturnType event, const std::string& eventType, + CUserEventsData* cUserEventsData) { + TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->add_user_event(event, eventType, cUserEventsData)); +} + +// v2 +NimbleNetStatus* run_method(const std::string& functionName, + std::shared_ptr inputs, + std::shared_ptr outputs) { + TRY_CATCH_RETURN_NIMBLESTATUS( + coreSDK->run_task(GLOBALTASKNAME, functionName.c_str(), inputs, outputs)); +} + +NimbleNetStatus* is_ready() { return ::is_ready(); } + +void update_session(const std::string& sessionIdString) { + return ::update_session(sessionIdString.c_str()); +} + +void deallocate_nimblenet() { ::deallocate_nimblenet(); } + +NimbleNetStatus* load_modules(const OpReturnType assetsJson, const std::string& homeDir) { + TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->load_modules(assetsJson, homeDir)); +} + +NimbleNetStatus* load_modules(const nlohmann::json assetsJson, const std::string& homeDir) { + TRY_CATCH_RETURN_NIMBLESTATUS(coreSDK->load_modules(assetsJson, homeDir)); +} + +// internal +void send_crash_log(const std::string& errorMessage) { + return ::send_crash_log(errorMessage.c_str()); +} + +void internet_switched_on() { return ::internet_switched_on(); } + +void write_metric(const std::string& metricType, const std::string& metricJson) { + return ::write_metric(metricType.c_str(), metricJson.c_str()); +} + +void write_run_method_metric(const std::string& methodName, long long int totalTimeInUSecs) { + return ::write_run_method_metric(methodName.c_str(), totalTimeInUSecs); +} + +bool send_events(const std::string& params, const std::string& homeDirectory) { + return ::send_events(params.c_str(), homeDirectory.c_str()); +} + +} // namespace nimblenet + +namespace nimblenetInternal { +////////// For lambda testing and DemoApp + +bool reload_model_with_epConfig(const std::string& modelName, const std::string& epConfig) { + return ::reload_model_with_epConfig(modelName.c_str(), epConfig.c_str()); +} + +bool load_model_from_file(const std::string& modelFilePath, + const std::string& inferenceConfigFilePath, const std::string& modelId, + const std::string& epConfigJsonChar) { + return ::load_model_from_file(modelFilePath.c_str(), inferenceConfigFilePath.c_str(), + modelId.c_str(), epConfigJsonChar.c_str()); +} + +void reset() { ::reset(); } + +void delete_database() { ::delete_database(); } + +bool attach_cleanup_to_thread() { return ::attach_cleanup_to_thread(); } + +////////// +} // namespace nimblenetInternal diff --git a/coreruntime/nimblenet/resource_manager/include/resource_manager.hpp b/coreruntime/nimblenet/resource_manager/include/resource_manager.hpp index c19f2abb..36a8d799 100644 --- a/coreruntime/nimblenet/resource_manager/include/resource_manager.hpp +++ b/coreruntime/nimblenet/resource_manager/include/resource_manager.hpp @@ -1,61 +1,61 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include -#include - -#include "config_manager.hpp" -#include "resource_manager_structs.hpp" -#include "server_api_structs.hpp" - -class CommandCenter; - -class ResourceManager { - CommandCenter* _commandCenter; - - std::map _loadResourceRetries; - std::map _resourcesDownloaded; - std::string get_plan_from_device(const std::string& modelId, const ModelMetadata& metadata); - PlanData download_plan_metadata(const std::string& modelId, const ModelMetadata& metadata); - std::string download_plan(const std::string& modelId, const ModelMetadata& metadata); - std::string save_plan_metadata_on_device(const std::string& modelId, const PlanData& planData); - - PlanData get_plan_metadata_from_device(const std::string& modelId, const ModelMetadata& metadata); - void set_resource_downloaded(const std::string& modelId); -#ifdef SCRIPTING - int load_task(const std::string& taskId); -#endif - - public: - bool can_resource_retry(const std::string& modelId); - void update_resource_retries(const std::string& resourceId); - - bool is_resource_downloaded(const std::string& modelId); - - PlanData get_plandata_from_device(const std::string& modelId, const ModelMetadata& metadata); - void reset_model_retries(const std::string& modelId); - PlanData update_plan_if_required(const std::string& modelId, const ModelMetadata& metadata); - void remove_plan_from_device(const std::string& modelId, const ModelMetadata& metadata); - - ResourceManager(CommandCenter* commandCenter); - -#ifdef SCRIPTING - TaskResponse update_task_if_required(const std::string& taskId, const TaskMetadata& metadata); - bool save_task_on_device(const std::string& taskId, const TaskResponse& taskResponse); - TaskResponse load_task_from_device(const std::string& taskId, const TaskMetadata& metadata); - -#endif - - // #ifdef SIMULATION_MODE - static PlanData get_inference_plan_data_from_device(const char* modelFilePath, - const char* inferenceConfigFilePath); - - static PlanData get_inference_plan_data_from_device(const char* modelFilePath); - - // #endif -}; +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include "config_manager.hpp" +#include "resource_manager_structs.hpp" +#include "server_api_structs.hpp" + +class CommandCenter; + +class ResourceManager { + CommandCenter* _commandCenter; + + std::map _loadResourceRetries; + std::map _resourcesDownloaded; + std::string get_plan_from_device(const std::string& modelId, const ModelMetadata& metadata); + PlanData download_plan_metadata(const std::string& modelId, const ModelMetadata& metadata); + std::string download_plan(const std::string& modelId, const ModelMetadata& metadata); + std::string save_plan_metadata_on_device(const std::string& modelId, const PlanData& planData); + + PlanData get_plan_metadata_from_device(const std::string& modelId, const ModelMetadata& metadata); + void set_resource_downloaded(const std::string& modelId); +#ifdef SCRIPTING + int load_task(const std::string& taskId); +#endif + + public: + bool can_resource_retry(const std::string& modelId); + void update_resource_retries(const std::string& resourceId); + + bool is_resource_downloaded(const std::string& modelId); + + PlanData get_plandata_from_device(const std::string& modelId, const ModelMetadata& metadata); + void reset_model_retries(const std::string& modelId); + PlanData update_plan_if_required(const std::string& modelId, const ModelMetadata& metadata); + void remove_plan_from_device(const std::string& modelId, const ModelMetadata& metadata); + + ResourceManager(CommandCenter* commandCenter); + +#ifdef SCRIPTING + TaskResponse update_task_if_required(const std::string& taskId, const TaskMetadata& metadata); + bool save_task_on_device(const std::string& taskId, const TaskResponse& taskResponse); + TaskResponse load_task_from_device(const std::string& taskId, const TaskMetadata& metadata); + +#endif + + // #ifdef SIMULATION_MODE + static PlanData get_inference_plan_data_from_device(const char* modelFilePath, + const char* inferenceConfigFilePath); + + static PlanData get_inference_plan_data_from_device(const char* modelFilePath); + + // #endif +}; diff --git a/coreruntime/nimblenet/resource_manager/src/resource_manager.cpp b/coreruntime/nimblenet/resource_manager/src/resource_manager.cpp index 6b12441b..1845930a 100644 --- a/coreruntime/nimblenet/resource_manager/src/resource_manager.cpp +++ b/coreruntime/nimblenet/resource_manager/src/resource_manager.cpp @@ -1,168 +1,168 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "resource_manager.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -#include "command_center.hpp" -#include "logger.hpp" -#include "native_interface.hpp" -#include "nimble_net_util.hpp" -#include "nlohmann/json_fwd.hpp" -#include "resource_manager_constants.hpp" -#include "server_api.hpp" -#include "util.hpp" - -using namespace std; - -using json = nlohmann::json; - -#define PARTSIZE 1000000 - -ResourceManager::ResourceManager(CommandCenter* commandCenter) { _commandCenter = commandCenter; } - -PlanData ResourceManager::get_plandata_from_device(const std::string& modelId, - const ModelMetadata& metadata) { - PlanData planData = get_plan_metadata_from_device(modelId, metadata); - if (!planData.valid) return PlanData(); - planData.planFileName = get_plan_from_device(modelId, metadata); - if (planData.planFileName == "") return PlanData(); - return planData; -} - -bool ResourceManager::is_resource_downloaded(const std::string& modelId) { - if (_resourcesDownloaded.find(modelId) != _resourcesDownloaded.end()) { - return true; - } - return false; -} - -void ResourceManager::set_resource_downloaded(const std::string& modelId) { - _resourcesDownloaded[modelId] = true; -} - -std::string ResourceManager::get_plan_from_device(const std::string& modelId, - const ModelMetadata& metadata) { - std::string fileName = modelId + metadata.version + rmconstants::InferenceFileName; - if (nativeinterface::get_file_size_common(fileName)) { - return nativeinterface::get_full_file_path_common(fileName); - } - return ""; -} - -std::string ResourceManager::save_plan_metadata_on_device(const std::string& modelId, - const PlanData& planData) { - return nativeinterface::save_file_on_device_common(nlohmann::json(planData).dump(), - modelId + planData.version + - to_string(planData.epConfigVersion) + - rmconstants::InferenceMetadataFileName); -} - -PlanData ResourceManager::get_plan_metadata_from_device(const std::string& modelId, - const ModelMetadata& metadata) { - std::string planMetadataString; - if (!nativeinterface::get_file_from_device_common(modelId + metadata.version + - to_string(metadata.epConfigVersion) + - rmconstants::InferenceMetadataFileName, - planMetadataString)) - return PlanData(); - return jsonparser::get(planMetadataString); -} - -void ResourceManager::remove_plan_from_device(const std::string& modelId, - const ModelMetadata& metadata) { - std::string metadataFile = nativeinterface::get_full_file_path_common( - modelId + metadata.version + to_string(metadata.epConfigVersion) + - rmconstants::InferenceMetadataFileName); - int didRemove = remove(metadataFile.c_str()); - if (didRemove) { - LOG_TO_ERROR("%s could not be removed from the system. Failed with error %d", - metadataFile.c_str(), didRemove); - } - std::string planFile = nativeinterface::get_full_file_path_common(modelId + metadata.version + - rmconstants::InferenceFileName); - didRemove = remove(planFile.c_str()); - if (didRemove) { - LOG_TO_ERROR("%s could not be removed from the system. Failed with error %d", planFile.c_str(), - didRemove); - } -} - -void ResourceManager::reset_model_retries(const std::string& modelId) { - _loadResourceRetries[modelId] = rmconstants::LoadResourceRetries; -} - -void ResourceManager::update_resource_retries(const std::string& resourceId) { - if (_loadResourceRetries.find(resourceId) != _loadResourceRetries.end()) { - _loadResourceRetries[resourceId]--; - if (_loadResourceRetries[resourceId] < 0) { - LOG_TO_DEBUG("No retries for resourceId=%s left.", resourceId.c_str()); - } - } -} - -bool ResourceManager::can_resource_retry(const std::string& resourceId) { - if (_loadResourceRetries.find(resourceId) != _loadResourceRetries.end()) { - return (_loadResourceRetries[resourceId] > 0); - } else { - // this is the check before first try - _loadResourceRetries[resourceId] = rmconstants::LoadResourceRetries; - return (_loadResourceRetries[resourceId] > 0); - } -} -#ifdef SCRIPTING - -bool ResourceManager::save_task_on_device(const std::string& taskId, - const TaskResponse& taskResponse) { - return nativeinterface::compress_and_save_file_on_device( - nlohmann::json(taskResponse).dump(), - taskId + taskResponse.version + rmconstants::TaskDataFileName); -} - -TaskResponse ResourceManager::load_task_from_device(const std::string& taskId, - const TaskMetadata& metadata) { - const auto [success, taskResponseString] = nativeinterface::read_potentially_compressed_file( - taskId + metadata.version + rmconstants::TaskDataFileName); - if (!success) { - return TaskResponse(); - } - TaskResponse ret = jsonparser::get(taskResponseString); - if (!ret.valid) - LOG_TO_ERROR("Could not parse taskResponse from file on device taskId=%s version=%s", - taskId.c_str(), metadata.version.c_str()); - return ret; -} - -#endif - -PlanData ResourceManager::get_inference_plan_data_from_device(const char* modelFilePath, - const char* inferenceConfigFilePath) { - PlanData planData; - string planMetadatastring; - if (!nativeinterface::get_file_from_device_common(inferenceConfigFilePath, planMetadatastring, - true)) { - return planData; - } - planData.inferenceConfig = planMetadatastring; - planData.valid = true; - planData.planFileName = modelFilePath; - return planData; -} - -PlanData ResourceManager::get_inference_plan_data_from_device(const char* modelFilePath) { - PlanData planData; - planData.planFileName = modelFilePath; - planData.valid = true; - return planData; -} +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "resource_manager.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "command_center.hpp" +#include "logger.hpp" +#include "native_interface.hpp" +#include "nimble_net_util.hpp" +#include "nlohmann/json_fwd.hpp" +#include "resource_manager_constants.hpp" +#include "server_api.hpp" +#include "util.hpp" + +using namespace std; + +using json = nlohmann::json; + +#define PARTSIZE 1000000 + +ResourceManager::ResourceManager(CommandCenter* commandCenter) { _commandCenter = commandCenter; } + +PlanData ResourceManager::get_plandata_from_device(const std::string& modelId, + const ModelMetadata& metadata) { + PlanData planData = get_plan_metadata_from_device(modelId, metadata); + if (!planData.valid) return PlanData(); + planData.planFileName = get_plan_from_device(modelId, metadata); + if (planData.planFileName == "") return PlanData(); + return planData; +} + +bool ResourceManager::is_resource_downloaded(const std::string& modelId) { + if (_resourcesDownloaded.find(modelId) != _resourcesDownloaded.end()) { + return true; + } + return false; +} + +void ResourceManager::set_resource_downloaded(const std::string& modelId) { + _resourcesDownloaded[modelId] = true; +} + +std::string ResourceManager::get_plan_from_device(const std::string& modelId, + const ModelMetadata& metadata) { + std::string fileName = modelId + metadata.version + rmconstants::InferenceFileName; + if (nativeinterface::get_file_size_common(fileName)) { + return nativeinterface::get_full_file_path_common(fileName); + } + return ""; +} + +std::string ResourceManager::save_plan_metadata_on_device(const std::string& modelId, + const PlanData& planData) { + return nativeinterface::save_file_on_device_common(nlohmann::json(planData).dump(), + modelId + planData.version + + to_string(planData.epConfigVersion) + + rmconstants::InferenceMetadataFileName); +} + +PlanData ResourceManager::get_plan_metadata_from_device(const std::string& modelId, + const ModelMetadata& metadata) { + std::string planMetadataString; + if (!nativeinterface::get_file_from_device_common(modelId + metadata.version + + to_string(metadata.epConfigVersion) + + rmconstants::InferenceMetadataFileName, + planMetadataString)) + return PlanData(); + return jsonparser::get(planMetadataString); +} + +void ResourceManager::remove_plan_from_device(const std::string& modelId, + const ModelMetadata& metadata) { + std::string metadataFile = nativeinterface::get_full_file_path_common( + modelId + metadata.version + to_string(metadata.epConfigVersion) + + rmconstants::InferenceMetadataFileName); + int didRemove = remove(metadataFile.c_str()); + if (didRemove) { + LOG_TO_ERROR("%s could not be removed from the system. Failed with error %d", + metadataFile.c_str(), didRemove); + } + std::string planFile = nativeinterface::get_full_file_path_common(modelId + metadata.version + + rmconstants::InferenceFileName); + didRemove = remove(planFile.c_str()); + if (didRemove) { + LOG_TO_ERROR("%s could not be removed from the system. Failed with error %d", planFile.c_str(), + didRemove); + } +} + +void ResourceManager::reset_model_retries(const std::string& modelId) { + _loadResourceRetries[modelId] = rmconstants::LoadResourceRetries; +} + +void ResourceManager::update_resource_retries(const std::string& resourceId) { + if (_loadResourceRetries.find(resourceId) != _loadResourceRetries.end()) { + _loadResourceRetries[resourceId]--; + if (_loadResourceRetries[resourceId] < 0) { + LOG_TO_DEBUG("No retries for resourceId=%s left.", resourceId.c_str()); + } + } +} + +bool ResourceManager::can_resource_retry(const std::string& resourceId) { + if (_loadResourceRetries.find(resourceId) != _loadResourceRetries.end()) { + return (_loadResourceRetries[resourceId] > 0); + } else { + // this is the check before first try + _loadResourceRetries[resourceId] = rmconstants::LoadResourceRetries; + return (_loadResourceRetries[resourceId] > 0); + } +} +#ifdef SCRIPTING + +bool ResourceManager::save_task_on_device(const std::string& taskId, + const TaskResponse& taskResponse) { + return nativeinterface::compress_and_save_file_on_device( + nlohmann::json(taskResponse).dump(), + taskId + taskResponse.version + rmconstants::TaskDataFileName); +} + +TaskResponse ResourceManager::load_task_from_device(const std::string& taskId, + const TaskMetadata& metadata) { + const auto [success, taskResponseString] = nativeinterface::read_potentially_compressed_file( + taskId + metadata.version + rmconstants::TaskDataFileName); + if (!success) { + return TaskResponse(); + } + TaskResponse ret = jsonparser::get(taskResponseString); + if (!ret.valid) + LOG_TO_ERROR("Could not parse taskResponse from file on device taskId=%s version=%s", + taskId.c_str(), metadata.version.c_str()); + return ret; +} + +#endif + +PlanData ResourceManager::get_inference_plan_data_from_device(const char* modelFilePath, + const char* inferenceConfigFilePath) { + PlanData planData; + string planMetadatastring; + if (!nativeinterface::get_file_from_device_common(inferenceConfigFilePath, planMetadatastring, + true)) { + return planData; + } + planData.inferenceConfig = planMetadatastring; + planData.valid = true; + planData.planFileName = modelFilePath; + return planData; +} + +PlanData ResourceManager::get_inference_plan_data_from_device(const char* modelFilePath) { + PlanData planData; + planData.planFileName = modelFilePath; + planData.valid = true; + return planData; +} diff --git a/coreruntime/nimblenet/server_api/include/server_api.hpp b/coreruntime/nimblenet/server_api/include/server_api.hpp index fabb3fa0..37bc20b4 100644 --- a/coreruntime/nimblenet/server_api/include/server_api.hpp +++ b/coreruntime/nimblenet/server_api/include/server_api.hpp @@ -1,274 +1,274 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include -#include -#include - -#include "config_manager.hpp" -#include "core_sdk_structs.hpp" -#include "job_scheduler.hpp" -#include "json.hpp" -#include "logger.hpp" -#include "native_interface_structs.hpp" -#include "nimble_net_util.hpp" -#include "server_api_constants.hpp" -#include "server_api_structs.hpp" - -/** - * @brief Handles all server API interactions, including registration, cloud config and asset retrieval. - * - * This class manages communication with the backend server, including device registration, asset downloads, - * cloud configuration retrieval, and event registration. It maintains state such as headers, hosts, - * and retry counters, and logs network metrics. - */ -class ServerAPI { - MetricsAgent* _metricsAgent; /**< Pointer to the metrics agent for logging network metrics. */ - nlohmann::json HEADERS = nlohmann::json::array(); /**< HTTP headers for requests. */ - std::string QUERY = ""; /**< Query string for requests. */ - std::string HOST = ""; /**< Main server host. */ - std::string CDN_HOST = ""; /**< CDN host for asset downloads. */ - std::string ADS_HOST = ""; /**< ADS host for private asset downloads. */ - std::shared_ptr _config; /**< Configuration object. */ - std::map requestToHostMap; /**< Maps request types to hosts. */ - std::atomic registerRetries = serverconstants::MaxRegisterRetries; /**< Registration retry counter. */ - std::map _currentStatusMap; /**< Tracks current download status by URL. */ - std::atomic registerDone = false; /**< Indicates if registration is complete. */ - - /** - * @brief Generates a unique request ID for tracking server API calls. - * - * The request ID is constructed by concatenating the deviceId and the current timestamp in milliseconds, - * ensuring uniqueness for each request. - * - * @return Unique request ID string containing deviceId and timestamp. - */ - std::string get_requestId() const; - - /** - * @brief Gets the appropriate host for a given request type. - * - * @param reqType The type of request (e.g., asset, register_event). - * @param defaultHost The default host to use if not mapped. - * @return The resolved host string. - */ - std::string get_host(const std::string& reqType, const std::string& defaultHost); - - /** - * @brief Constructs the asset URL for a given asset. - * - * @param asset Shared pointer to the asset. - * @param defaultHost The default host to use. - * @return The asset URL string. - */ - std::string get_asset_url(std::shared_ptr asset, const std::string& defaultHost); - - /** - * @brief Sends a network request to the server. - * - * @param body Request body as a string. - * @param headers HTTP headers as JSON. - * @param url Target URL. - * @param method HTTP method (e.g., "GET", "POST"). - * @param length Optional content length (default -1). - * @return Shared pointer to the network response. - */ - const std::shared_ptr send_request(const std::string& body, - nlohmann::json headers, - const std::string& url, - const std::string& method, int length = -1); - - /** - * @brief Initiates an asynchronous file download. - * - * @param url The URL to download from. - * @param fileName The local file name to save to. - * @return The download status. - */ - FileDownloadStatus download_file_async(const std::string& url, const std::string& fileName); - - /** - * @brief Constructs the cloud config URL from a config object. - * - * @param config Shared pointer to the config object. - * @return The cloud config URL string. - */ - std::string get_cloudconfig_url(const std::shared_ptr config); - - public: - /** - * @brief Constructs a ServerAPI instance. - * - * @param metricsAgent Pointer to the metrics agent. - * @param config Shared pointer to the configuration object. - */ - ServerAPI(MetricsAgent* metricsAgent, std::shared_ptr config) { - _metricsAgent = metricsAgent; - HOST = config->host; - CDN_HOST = config->host; - auto pos = CDN_HOST.find("://"); - if (pos != std::string::npos) { - CDN_HOST.insert(pos + 3, "cdn-"); - } - _config = config; - } - - /** - * @brief Destructor for ServerAPI. - */ - ~ServerAPI() {} - - /** - * @brief Checks if the server API is initialized (registration complete). - * - * @return True if initialized, false otherwise. - */ - bool is_init() const { return registerDone.load(); } - - /** - * @brief Retrieves an asset from the server synchronously. - * - * @param asset Shared pointer to the asset. - * @return Optional string containing the asset data if successful. - */ - std::optional get_asset(std::shared_ptr asset); - - /** - * @brief Initiates an asynchronous asset download. - * - * @param asset Shared pointer to the asset. - * @return The download status. - */ - FileDownloadStatus get_asset_async(std::shared_ptr asset); - -#ifdef GENAI - /** - * @brief Downloads and prepares an LLM asset (GENAI only). - * - * @param asset Shared pointer to the asset. - * @return The download status. - */ - FileDownloadStatus get_llm(std::shared_ptr asset); -#endif - - /** - * @brief Registers the device with the server. - * - * @return True if registration succeeded, false otherwise. - */ - bool device_register(); - - /** - * @brief Uploads logs to the server. - * - * @param logrequest Log request body. - * @return True if upload succeeded, false otherwise. - */ - bool upload_logs(const LogRequestBody& logrequest); - - /** - * @brief Initializes the server API (performs registration if needed). - * - * @return True if initialization succeeded, false otherwise. - */ - bool init(); - - /** - * @brief Retrieves the cloud configuration from the server. - * - * @param eTag ETag for cache validation. - * @param retries Number of retries on authentication error (default: MaxAuthErrorRetries). - * @return Pair of CloudConfigResponse and Deployment. - */ - std::pair get_cloud_config( - std::string eTag, int retries = serverconstants::MaxAuthErrorRetries); - - /** - * @brief Constructs the cloud config URL from a config JSON string. - * - * @param configJson JSON string representing the config. - * @return The cloud config URL string. - */ - std::string get_cloudconfig_url(const std::string& configJson); - - /** - * @brief Resets the registration retry counter to the maximum value. - */ - void reset_register_retries(); - - /** - * @brief Updates the request-to-host mapping. - * - * @param reqMap Map from request type to host identifier. - */ - void update_request_to_host_map(const std::map& reqMap); - - /** - * @brief Updates the ADS host for private asset downloads. - * - * @param adsHost The new ADS host string. - */ - void update_ads_host(const std::string& adsHost); - - /** - * @brief Registers a new event with the server. - * - * @param eventName Name of the event to register. - */ - void register_new_event(const std::string& eventName); -}; - -/** - * @brief Job for registering a new event with the server asynchronously. - * - * This job attempts to register a new event with the server, retrying if the ServerAPI is not initialized. - */ -struct RegisterNewEventJob : public Job { - std::string eventName; /**< Name of the event to register. */ - std::shared_ptr serverAPI; /**< ServerAPI instance to use. */ - std::shared_ptr jobScheduler; /**< JobScheduler instance. */ - - /** - * @brief Constructs a RegisterNewEventJob. - * - * @param newEventName Name of the event to register. - * @param serverAPI_ Shared pointer to the ServerAPI instance. - * @param jobScheduler_ Shared pointer to the JobScheduler instance. - */ - RegisterNewEventJob(const std::string& newEventName, std::shared_ptr serverAPI_, - std::shared_ptr jobScheduler_) - : Job("RegisterNewEventJob") { - eventName = newEventName; - serverAPI = serverAPI_; - jobScheduler = jobScheduler_; - } - - /** - * @brief Destructor for RegisterNewEventJob. - */ - ~RegisterNewEventJob() override = default; - - /** - * @brief Processes the job: attempts to register the event, retrying if ServerAPI is not initialized. - * - * @return Status::RETRY if not initialized, Status::COMPLETE otherwise. - */ - Job::Status process() override { - try { - if (!serverAPI->is_init()) { - return Status::RETRY; - } - serverAPI->register_new_event(eventName); - } catch (const std::runtime_error& e) { - LOG_TO_ERROR("Got error throw in RegisterNewEventJob that will be ignored: %s", e.what()); - } catch (...) { - LOG_TO_ERROR("Got unknown error thrown in RegisterNewEventJob that will be ignored"); - } - return Status::COMPLETE; - } -}; +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +#include "config_manager.hpp" +#include "core_sdk_structs.hpp" +#include "job_scheduler.hpp" +#include "json.hpp" +#include "logger.hpp" +#include "native_interface_structs.hpp" +#include "nimble_net_util.hpp" +#include "server_api_constants.hpp" +#include "server_api_structs.hpp" + +/** + * @brief Handles all server API interactions, including registration, cloud config and asset retrieval. + * + * This class manages communication with the backend server, including device registration, asset downloads, + * cloud configuration retrieval, and event registration. It maintains state such as headers, hosts, + * and retry counters, and logs network metrics. + */ +class ServerAPI { + MetricsAgent* _metricsAgent; /**< Pointer to the metrics agent for logging network metrics. */ + nlohmann::json HEADERS = nlohmann::json::array(); /**< HTTP headers for requests. */ + std::string QUERY = ""; /**< Query string for requests. */ + std::string HOST = ""; /**< Main server host. */ + std::string CDN_HOST = ""; /**< CDN host for asset downloads. */ + std::string ADS_HOST = ""; /**< ADS host for private asset downloads. */ + std::shared_ptr _config; /**< Configuration object. */ + std::map requestToHostMap; /**< Maps request types to hosts. */ + std::atomic registerRetries = serverconstants::MaxRegisterRetries; /**< Registration retry counter. */ + std::map _currentStatusMap; /**< Tracks current download status by URL. */ + std::atomic registerDone = false; /**< Indicates if registration is complete. */ + + /** + * @brief Generates a unique request ID for tracking server API calls. + * + * The request ID is constructed by concatenating the deviceId and the current timestamp in milliseconds, + * ensuring uniqueness for each request. + * + * @return Unique request ID string containing deviceId and timestamp. + */ + std::string get_requestId() const; + + /** + * @brief Gets the appropriate host for a given request type. + * + * @param reqType The type of request (e.g., asset, register_event). + * @param defaultHost The default host to use if not mapped. + * @return The resolved host string. + */ + std::string get_host(const std::string& reqType, const std::string& defaultHost); + + /** + * @brief Constructs the asset URL for a given asset. + * + * @param asset Shared pointer to the asset. + * @param defaultHost The default host to use. + * @return The asset URL string. + */ + std::string get_asset_url(std::shared_ptr asset, const std::string& defaultHost); + + /** + * @brief Sends a network request to the server. + * + * @param body Request body as a string. + * @param headers HTTP headers as JSON. + * @param url Target URL. + * @param method HTTP method (e.g., "GET", "POST"). + * @param length Optional content length (default -1). + * @return Shared pointer to the network response. + */ + const std::shared_ptr send_request(const std::string& body, + nlohmann::json headers, + const std::string& url, + const std::string& method, int length = -1); + + /** + * @brief Initiates an asynchronous file download. + * + * @param url The URL to download from. + * @param fileName The local file name to save to. + * @return The download status. + */ + FileDownloadStatus download_file_async(const std::string& url, const std::string& fileName); + + /** + * @brief Constructs the cloud config URL from a config object. + * + * @param config Shared pointer to the config object. + * @return The cloud config URL string. + */ + std::string get_cloudconfig_url(const std::shared_ptr config); + + public: + /** + * @brief Constructs a ServerAPI instance. + * + * @param metricsAgent Pointer to the metrics agent. + * @param config Shared pointer to the configuration object. + */ + ServerAPI(MetricsAgent* metricsAgent, std::shared_ptr config) { + _metricsAgent = metricsAgent; + HOST = config->host; + CDN_HOST = config->host; + auto pos = CDN_HOST.find("://"); + if (pos != std::string::npos) { + CDN_HOST.insert(pos + 3, "cdn-"); + } + _config = config; + } + + /** + * @brief Destructor for ServerAPI. + */ + ~ServerAPI() {} + + /** + * @brief Checks if the server API is initialized (registration complete). + * + * @return True if initialized, false otherwise. + */ + bool is_init() const { return registerDone.load(); } + + /** + * @brief Retrieves an asset from the server synchronously. + * + * @param asset Shared pointer to the asset. + * @return Optional string containing the asset data if successful. + */ + std::optional get_asset(std::shared_ptr asset); + + /** + * @brief Initiates an asynchronous asset download. + * + * @param asset Shared pointer to the asset. + * @return The download status. + */ + FileDownloadStatus get_asset_async(std::shared_ptr asset); + +#ifdef GENAI + /** + * @brief Downloads and prepares an LLM asset (GENAI only). + * + * @param asset Shared pointer to the asset. + * @return The download status. + */ + FileDownloadStatus get_llm(std::shared_ptr asset); +#endif + + /** + * @brief Registers the device with the server. + * + * @return True if registration succeeded, false otherwise. + */ + bool device_register(); + + /** + * @brief Uploads logs to the server. + * + * @param logrequest Log request body. + * @return True if upload succeeded, false otherwise. + */ + bool upload_logs(const LogRequestBody& logrequest); + + /** + * @brief Initializes the server API (performs registration if needed). + * + * @return True if initialization succeeded, false otherwise. + */ + bool init(); + + /** + * @brief Retrieves the cloud configuration from the server. + * + * @param eTag ETag for cache validation. + * @param retries Number of retries on authentication error (default: MaxAuthErrorRetries). + * @return Pair of CloudConfigResponse and Deployment. + */ + std::pair get_cloud_config( + std::string eTag, int retries = serverconstants::MaxAuthErrorRetries); + + /** + * @brief Constructs the cloud config URL from a config JSON string. + * + * @param configJson JSON string representing the config. + * @return The cloud config URL string. + */ + std::string get_cloudconfig_url(const std::string& configJson); + + /** + * @brief Resets the registration retry counter to the maximum value. + */ + void reset_register_retries(); + + /** + * @brief Updates the request-to-host mapping. + * + * @param reqMap Map from request type to host identifier. + */ + void update_request_to_host_map(const std::map& reqMap); + + /** + * @brief Updates the ADS host for private asset downloads. + * + * @param adsHost The new ADS host string. + */ + void update_ads_host(const std::string& adsHost); + + /** + * @brief Registers a new event with the server. + * + * @param eventName Name of the event to register. + */ + void register_new_event(const std::string& eventName); +}; + +/** + * @brief Job for registering a new event with the server asynchronously. + * + * This job attempts to register a new event with the server, retrying if the ServerAPI is not initialized. + */ +struct RegisterNewEventJob : public Job { + std::string eventName; /**< Name of the event to register. */ + std::shared_ptr serverAPI; /**< ServerAPI instance to use. */ + std::shared_ptr jobScheduler; /**< JobScheduler instance. */ + + /** + * @brief Constructs a RegisterNewEventJob. + * + * @param newEventName Name of the event to register. + * @param serverAPI_ Shared pointer to the ServerAPI instance. + * @param jobScheduler_ Shared pointer to the JobScheduler instance. + */ + RegisterNewEventJob(const std::string& newEventName, std::shared_ptr serverAPI_, + std::shared_ptr jobScheduler_) + : Job("RegisterNewEventJob") { + eventName = newEventName; + serverAPI = serverAPI_; + jobScheduler = jobScheduler_; + } + + /** + * @brief Destructor for RegisterNewEventJob. + */ + ~RegisterNewEventJob() override = default; + + /** + * @brief Processes the job: attempts to register the event, retrying if ServerAPI is not initialized. + * + * @return Status::RETRY if not initialized, Status::COMPLETE otherwise. + */ + Job::Status process() override { + try { + if (!serverAPI->is_init()) { + return Status::RETRY; + } + serverAPI->register_new_event(eventName); + } catch (const std::runtime_error& e) { + LOG_TO_ERROR("Got error throw in RegisterNewEventJob that will be ignored: %s", e.what()); + } catch (...) { + LOG_TO_ERROR("Got unknown error thrown in RegisterNewEventJob that will be ignored"); + } + return Status::COMPLETE; + } +}; diff --git a/coreruntime/nimblenet/server_api/include/server_api_structs.hpp b/coreruntime/nimblenet/server_api/include/server_api_structs.hpp index b63e6638..4a2d9dee 100644 --- a/coreruntime/nimblenet/server_api/include/server_api_structs.hpp +++ b/coreruntime/nimblenet/server_api/include/server_api_structs.hpp @@ -1,360 +1,360 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include - -#include "asset_manager.hpp" -#include "core_sdk_constants.hpp" -#include "log_sender.hpp" -#include "logger.hpp" -#include "logger_constants.hpp" -#include "nlohmann_json.hpp" -#include "resource_manager_constants.hpp" -#include "time_manager.hpp" -#include "util.hpp" - -#ifdef GENAI -#include "base_llm_executor.hpp" -#endif // GENAI - -using json = nlohmann::json; - -/** - * @brief Request structure for device registration. - * - * Contains client ID, device ID, and a list of model IDs to register. - */ -struct RegisterRequest { - std::string clientId; /**< Client identifier. */ - std::string deviceId; /**< Device identifier. */ - std::vector modelIds; /**< List of model IDs. */ - - /** - * @brief Constructs a RegisterRequest. - * - * @param clientId_ Client identifier. - * @param deviceId_ Device identifier. - * @param models_ List of model IDs. - */ - RegisterRequest(const std::string& clientId_, const std::string& deviceId_, - const std::vector& models_) { - clientId = clientId_; - deviceId = deviceId_; - modelIds = models_; - } -}; - -/** - * @brief Response structure for device registration. - * - * Contains HTTP headers and query parameters returned by the server. - */ -struct RegisterResponse { - nlohmann::json headers; /**< HTTP headers returned by the server. */ - std::string queryParams; /**< Query parameters returned by the server. */ -}; - -/** - * @brief Response structure for a task request. - * - * Contains the task AST, version, task name, and validity flag. - */ -struct TaskResponse { - nlohmann::json taskAST; /**< Task AST as JSON. */ - std::string version; /**< Task version string. */ - std::string taskName; /**< Name of the task. */ - bool valid = false; /**< Indicates if the response is valid. */ -}; - -/** - * @brief Metadata for a model. - * - * Contains version, execution provider config version, and validity flag. - */ -struct ModelMetadata { - std::string version; /**< Model version string. */ - int epConfigVersion; /**< Execution provider config version. */ - bool valid; /**< Indicates if the metadata is valid. */ - - ModelMetadata() { - version = ""; - epConfigVersion = -1; - valid = false; - } -}; - -/** - * @brief Metadata for a task. - * - * Contains version and validity flag. - */ -struct TaskMetadata { - std::string version; /**< Task version string. */ - bool valid; /**< Indicates if the metadata is valid. */ - - TaskMetadata() { - version = ""; - valid = false; - } -}; - -/** - * @brief Response structure for a model download request. - * - * Contains status and file name. - */ -struct DownloadModelResponse { - int status = 0; /**< Download status code. */ - std::string fileName; /**< Name of the downloaded file. */ -}; - -/** - * @brief Logger configuration structure. - * - * Contains sender and writer configuration for logging. - */ -struct LoggerConfig { - LogSendingConfig senderConfig; /**< Configuration for log sending. */ - LogWritingConfig writerConfig; /**< Configuration for log writing. */ -}; - -/** - * @brief Converts JSON to LoggerConfig. - * - * @param j JSON object. - * @param loggerConfig LoggerConfig to populate. - */ -void from_json(const json j, LoggerConfig& loggerConfig); - -/** - * @brief Converts LoggerConfig to JSON. - * - * @param j JSON object to populate. - * @param loggerConfig LoggerConfig to convert. - */ -void to_json(json& j, const LoggerConfig& loggerConfig); - -/** - * @brief State of the cloud configuration. - */ -enum class CloudConfigState { Invalid, Valid, Unmodified }; - -/** - * @brief Deployment information structure. - * - * Contains deployment ID, update flag, script asset, module assets, and eTag. - */ -struct Deployment { - int Id = -1; /**< Deployment ID. */ - bool forceUpdate = false; /**< Indicates if a force update is required. */ - std::shared_ptr script; /**< Main script asset. */ - std::vector> modules; /**< List of module assets. */ - std::string eTag; /**< Entity tag for versioning. */ - - /** - * @brief Retrieves a module asset by name and type. - * - * @param moduleName Name of the module. - * @param type Asset type. - * @return Shared pointer to the asset if found, nullptr otherwise. - */ - std::shared_ptr get_module(const std::string& moduleName, AssetType type) const { - for (auto module : modules) { - if (module->name == moduleName && module->type == type) { - return module; - } - } - return nullptr; - } -}; - -/** - * @brief Converts JSON to Deployment. - * - * @param j JSON object. - * @param dep Deployment to populate. - */ -void from_json(const json& j, Deployment& dep); - -/** - * @brief Converts Deployment to JSON. - * - * @param j JSON object to populate. - * @param dep Deployment to convert. - */ -void to_json(json& j, const Deployment& dep); - -/** - * @brief Cloud configuration response structure. - * - * Contains configuration parameters, logger configs, server time, pegged device time, state, and ads host. - */ -struct CloudConfigResponse { - std::map requestToHostMap; /**< Maps request types to hosts. */ - int inferenceMetricLogInterval = loggerconstants::InferenceMetricLogInterval; /**< Inference metric log interval. */ - long long int threadSleepTimeUSecs = coresdkconstants::LongRunningThreadSleepUTime; /**< Thread sleep time in microseconds. */ - float fileDeleteTimeInDays = coresdkconstants::FileDeleteTimeInDays; /**< File delete time in days. */ - LoggerConfig nimbleLoggerConfig; /**< Nimble logger configuration. */ - LoggerConfig externalLoggerConfig; /**< External logger configuration. */ - uint64_t serverTimeMicros = 0; /**< Server time in microseconds from UTC. */ - PeggedDeviceTime peggedDeviceTime; /**< Local and server time at config fetch. */ - CloudConfigState state = CloudConfigState::Invalid; /**< State of the cloud config. */ - std::string adsHost = ""; /**< ADS host for private assets. */ - -#ifdef GENAI - LLMExecutorConfig llmExecutorConfig; /**< LLM executor configuration (GENAI only). */ -#endif // GENAI - - CloudConfigResponse() { - nimbleLoggerConfig.senderConfig._host = loggerconstants::DefaultLogUploadURL; - nimbleLoggerConfig.senderConfig.valid = true; - nimbleLoggerConfig.senderConfig._secretKey = nimbleLoggerConfig.senderConfig._defaultSecretKey; - } -}; - -/** - * @brief Log request body structure. - * - * Contains host, headers, and body for a log upload request. - */ -struct LogRequestBody { - std::string host; /**< Host endpoint for log upload. */ - json headers; /**< HTTP headers for the log request. */ - std::string body; /**< Log request body. */ - - /** - * @brief Constructs a LogRequestBody. - * - * @param logheaders HTTP headers. - * @param logbody Log body string. - * @param hostendpoint Host endpoint string. - */ - LogRequestBody(const json& logheaders, const std::string& logbody, - const std::string& hostendpoint) { - host = hostendpoint; - body = logbody; - headers = logheaders; - } -}; - -/** - * @brief Authentication information structure. - * - * Contains validity flag, API headers, and API query string. - */ -struct AuthenticationInfo { - bool valid = false; /**< Indicates if the authentication info is valid. */ - std::string apiHeaders; /**< API headers as a string. */ - std::string apiQuery; /**< API query string. */ -}; - -/** - * @brief Converts JSON to ModelMetadata. - * - * @param j JSON object. - * @param metadata ModelMetadata to populate. - */ -void from_json(const json& j, ModelMetadata& metadata); - -/** - * @brief Converts ModelMetadata to JSON. - * - * @param j JSON object to populate. - * @param metadata ModelMetadata to convert. - */ -void to_json(json& j, const ModelMetadata& metadata); - -/** - * @brief Converts JSON to TaskMetadata. - * - * @param j JSON object. - * @param metadata TaskMetadata to populate. - */ -void from_json(const json& j, TaskMetadata& metadata); - -/** - * @brief Converts TaskMetadata to JSON. - * - * @param j JSON object to populate. - * @param metadata TaskMetadata to convert. - */ -void to_json(json& j, const TaskMetadata& metadata); - -/** - * @brief Converts JSON to CloudConfigResponse. - * - * @param j JSON object. - * @param logKeyResponse CloudConfigResponse to populate. - */ -void from_json(const json& j, CloudConfigResponse& logKeyResponse); - -/** - * @brief Converts CloudConfigResponse to JSON. - * - * @param j JSON object to populate. - * @param cloudConfig CloudConfigResponse to convert. - */ -void to_json(json& j, const CloudConfigResponse& cloudConfig); - -/** - * @brief Converts JSON to RegisterResponse. - * - * @param j JSON object. - * @param registerResponse RegisterResponse to populate. - */ -void from_json(const json& j, RegisterResponse& registerResponse); - -// AuthenticationInfo -/** - * @brief Converts JSON to AuthenticationInfo. - * - * @param j JSON object. - * @param info AuthenticationInfo to populate. - */ -void from_json(const json& j, AuthenticationInfo& info); - -/** - * @brief Converts AuthenticationInfo to JSON. - * - * @param j JSON object to populate. - * @param authInfo AuthenticationInfo to convert. - */ -void to_json(json& j, const AuthenticationInfo& authInfo); - -/** - * @brief Parses cloud config and deployment from JSON. - * - * @param j JSON object containing both cloud config and deployment. - * @return Pair of CloudConfigResponse and Deployment. - */ -std::pair get_config_and_deployment_from_json( - const nlohmann::json& j); - -/** - * @brief Converts JSON to TaskResponse. - * - * @param j JSON object. - * @param task TaskResponse to populate. - */ -inline const void from_json(const json& j, TaskResponse& task) { - j.at("AST").get_to(task.taskAST); - if (j.find("version") != j.end()) { - j.at("version").get_to(task.version); - } - task.valid = true; -} - -/** - * @brief Converts TaskResponse to JSON. - * - * @param j JSON object to populate. - * @param task TaskResponse to convert. - */ -inline const void to_json(json& j, const TaskResponse& task) { - j = json{{"AST", task.taskAST}, {"version", task.version}}; -} +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "asset_manager.hpp" +#include "core_sdk_constants.hpp" +#include "log_sender.hpp" +#include "logger.hpp" +#include "logger_constants.hpp" +#include "nlohmann_json.hpp" +#include "resource_manager_constants.hpp" +#include "time_manager.hpp" +#include "util.hpp" + +#ifdef GENAI +#include "base_llm_executor.hpp" +#endif // GENAI + +using json = nlohmann::json; + +/** + * @brief Request structure for device registration. + * + * Contains client ID, device ID, and a list of model IDs to register. + */ +struct RegisterRequest { + std::string clientId; /**< Client identifier. */ + std::string deviceId; /**< Device identifier. */ + std::vector modelIds; /**< List of model IDs. */ + + /** + * @brief Constructs a RegisterRequest. + * + * @param clientId_ Client identifier. + * @param deviceId_ Device identifier. + * @param models_ List of model IDs. + */ + RegisterRequest(const std::string& clientId_, const std::string& deviceId_, + const std::vector& models_) { + clientId = clientId_; + deviceId = deviceId_; + modelIds = models_; + } +}; + +/** + * @brief Response structure for device registration. + * + * Contains HTTP headers and query parameters returned by the server. + */ +struct RegisterResponse { + nlohmann::json headers; /**< HTTP headers returned by the server. */ + std::string queryParams; /**< Query parameters returned by the server. */ +}; + +/** + * @brief Response structure for a task request. + * + * Contains the task AST, version, task name, and validity flag. + */ +struct TaskResponse { + nlohmann::json taskAST; /**< Task AST as JSON. */ + std::string version; /**< Task version string. */ + std::string taskName; /**< Name of the task. */ + bool valid = false; /**< Indicates if the response is valid. */ +}; + +/** + * @brief Metadata for a model. + * + * Contains version, execution provider config version, and validity flag. + */ +struct ModelMetadata { + std::string version; /**< Model version string. */ + int epConfigVersion; /**< Execution provider config version. */ + bool valid; /**< Indicates if the metadata is valid. */ + + ModelMetadata() { + version = ""; + epConfigVersion = -1; + valid = false; + } +}; + +/** + * @brief Metadata for a task. + * + * Contains version and validity flag. + */ +struct TaskMetadata { + std::string version; /**< Task version string. */ + bool valid; /**< Indicates if the metadata is valid. */ + + TaskMetadata() { + version = ""; + valid = false; + } +}; + +/** + * @brief Response structure for a model download request. + * + * Contains status and file name. + */ +struct DownloadModelResponse { + int status = 0; /**< Download status code. */ + std::string fileName; /**< Name of the downloaded file. */ +}; + +/** + * @brief Logger configuration structure. + * + * Contains sender and writer configuration for logging. + */ +struct LoggerConfig { + LogSendingConfig senderConfig; /**< Configuration for log sending. */ + LogWritingConfig writerConfig; /**< Configuration for log writing. */ +}; + +/** + * @brief Converts JSON to LoggerConfig. + * + * @param j JSON object. + * @param loggerConfig LoggerConfig to populate. + */ +void from_json(const json j, LoggerConfig& loggerConfig); + +/** + * @brief Converts LoggerConfig to JSON. + * + * @param j JSON object to populate. + * @param loggerConfig LoggerConfig to convert. + */ +void to_json(json& j, const LoggerConfig& loggerConfig); + +/** + * @brief State of the cloud configuration. + */ +enum class CloudConfigState { Invalid, Valid, Unmodified }; + +/** + * @brief Deployment information structure. + * + * Contains deployment ID, update flag, script asset, module assets, and eTag. + */ +struct Deployment { + int Id = -1; /**< Deployment ID. */ + bool forceUpdate = false; /**< Indicates if a force update is required. */ + std::shared_ptr script; /**< Main script asset. */ + std::vector> modules; /**< List of module assets. */ + std::string eTag; /**< Entity tag for versioning. */ + + /** + * @brief Retrieves a module asset by name and type. + * + * @param moduleName Name of the module. + * @param type Asset type. + * @return Shared pointer to the asset if found, nullptr otherwise. + */ + std::shared_ptr get_module(const std::string& moduleName, AssetType type) const { + for (auto module : modules) { + if (module->name == moduleName && module->type == type) { + return module; + } + } + return nullptr; + } +}; + +/** + * @brief Converts JSON to Deployment. + * + * @param j JSON object. + * @param dep Deployment to populate. + */ +void from_json(const json& j, Deployment& dep); + +/** + * @brief Converts Deployment to JSON. + * + * @param j JSON object to populate. + * @param dep Deployment to convert. + */ +void to_json(json& j, const Deployment& dep); + +/** + * @brief Cloud configuration response structure. + * + * Contains configuration parameters, logger configs, server time, pegged device time, state, and ads host. + */ +struct CloudConfigResponse { + std::map requestToHostMap; /**< Maps request types to hosts. */ + int inferenceMetricLogInterval = loggerconstants::InferenceMetricLogInterval; /**< Inference metric log interval. */ + long long int threadSleepTimeUSecs = coresdkconstants::LongRunningThreadSleepUTime; /**< Thread sleep time in microseconds. */ + float fileDeleteTimeInDays = coresdkconstants::FileDeleteTimeInDays; /**< File delete time in days. */ + LoggerConfig nimbleLoggerConfig; /**< Nimble logger configuration. */ + LoggerConfig externalLoggerConfig; /**< External logger configuration. */ + uint64_t serverTimeMicros = 0; /**< Server time in microseconds from UTC. */ + PeggedDeviceTime peggedDeviceTime; /**< Local and server time at config fetch. */ + CloudConfigState state = CloudConfigState::Invalid; /**< State of the cloud config. */ + std::string adsHost = ""; /**< ADS host for private assets. */ + +#ifdef GENAI + LLMExecutorConfig llmExecutorConfig; /**< LLM executor configuration (GENAI only). */ +#endif // GENAI + + CloudConfigResponse() { + nimbleLoggerConfig.senderConfig._host = loggerconstants::DefaultLogUploadURL; + nimbleLoggerConfig.senderConfig.valid = true; + nimbleLoggerConfig.senderConfig._secretKey = nimbleLoggerConfig.senderConfig._defaultSecretKey; + } +}; + +/** + * @brief Log request body structure. + * + * Contains host, headers, and body for a log upload request. + */ +struct LogRequestBody { + std::string host; /**< Host endpoint for log upload. */ + json headers; /**< HTTP headers for the log request. */ + std::string body; /**< Log request body. */ + + /** + * @brief Constructs a LogRequestBody. + * + * @param logheaders HTTP headers. + * @param logbody Log body string. + * @param hostendpoint Host endpoint string. + */ + LogRequestBody(const json& logheaders, const std::string& logbody, + const std::string& hostendpoint) { + host = hostendpoint; + body = logbody; + headers = logheaders; + } +}; + +/** + * @brief Authentication information structure. + * + * Contains validity flag, API headers, and API query string. + */ +struct AuthenticationInfo { + bool valid = false; /**< Indicates if the authentication info is valid. */ + std::string apiHeaders; /**< API headers as a string. */ + std::string apiQuery; /**< API query string. */ +}; + +/** + * @brief Converts JSON to ModelMetadata. + * + * @param j JSON object. + * @param metadata ModelMetadata to populate. + */ +void from_json(const json& j, ModelMetadata& metadata); + +/** + * @brief Converts ModelMetadata to JSON. + * + * @param j JSON object to populate. + * @param metadata ModelMetadata to convert. + */ +void to_json(json& j, const ModelMetadata& metadata); + +/** + * @brief Converts JSON to TaskMetadata. + * + * @param j JSON object. + * @param metadata TaskMetadata to populate. + */ +void from_json(const json& j, TaskMetadata& metadata); + +/** + * @brief Converts TaskMetadata to JSON. + * + * @param j JSON object to populate. + * @param metadata TaskMetadata to convert. + */ +void to_json(json& j, const TaskMetadata& metadata); + +/** + * @brief Converts JSON to CloudConfigResponse. + * + * @param j JSON object. + * @param logKeyResponse CloudConfigResponse to populate. + */ +void from_json(const json& j, CloudConfigResponse& logKeyResponse); + +/** + * @brief Converts CloudConfigResponse to JSON. + * + * @param j JSON object to populate. + * @param cloudConfig CloudConfigResponse to convert. + */ +void to_json(json& j, const CloudConfigResponse& cloudConfig); + +/** + * @brief Converts JSON to RegisterResponse. + * + * @param j JSON object. + * @param registerResponse RegisterResponse to populate. + */ +void from_json(const json& j, RegisterResponse& registerResponse); + +// AuthenticationInfo +/** + * @brief Converts JSON to AuthenticationInfo. + * + * @param j JSON object. + * @param info AuthenticationInfo to populate. + */ +void from_json(const json& j, AuthenticationInfo& info); + +/** + * @brief Converts AuthenticationInfo to JSON. + * + * @param j JSON object to populate. + * @param authInfo AuthenticationInfo to convert. + */ +void to_json(json& j, const AuthenticationInfo& authInfo); + +/** + * @brief Parses cloud config and deployment from JSON. + * + * @param j JSON object containing both cloud config and deployment. + * @return Pair of CloudConfigResponse and Deployment. + */ +std::pair get_config_and_deployment_from_json( + const nlohmann::json& j); + +/** + * @brief Converts JSON to TaskResponse. + * + * @param j JSON object. + * @param task TaskResponse to populate. + */ +inline const void from_json(const json& j, TaskResponse& task) { + j.at("AST").get_to(task.taskAST); + if (j.find("version") != j.end()) { + j.at("version").get_to(task.version); + } + task.valid = true; +} + +/** + * @brief Converts TaskResponse to JSON. + * + * @param j JSON object to populate. + * @param task TaskResponse to convert. + */ +inline const void to_json(json& j, const TaskResponse& task) { + j = json{{"AST", task.taskAST}, {"version", task.version}}; +} diff --git a/coreruntime/nimblenet/server_api/src/server_api.cpp b/coreruntime/nimblenet/server_api/src/server_api.cpp index 16c4598f..514925a8 100644 --- a/coreruntime/nimblenet/server_api/src/server_api.cpp +++ b/coreruntime/nimblenet/server_api/src/server_api.cpp @@ -1,411 +1,411 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "server_api.hpp" - -#include -#include - -#include "asset_manager.hpp" -#include "client.h" -#include "config_manager.hpp" -#include "core_utils/shard.hpp" -#include "json.hpp" -#include "logger.hpp" -#include "nimble_net_util.hpp" -#include "resource_manager_structs.hpp" -#include "time_manager.hpp" -#include "util.hpp" - -using json = nlohmann::json; - -#include "native_interface.hpp" - -using namespace std; - -#define GETPLANMETRIC "getplan" -#define REGISTERMETRIC "register" -#define GETCLOUDCONFIGMETRIC "getCloudConfig" -#define GETMODELVERSIONMETRIC "getModelVersion" -#define GETTASKMETRIC "getTask" -#define LOGMETRIC "logMetric" -static inline const std::string NETWORK = "network"; -static inline const std::string ASYNCDOWNLOAD = "asyncdownload"; - -struct NetworkMetric { - std::string requestId_ = ""; - std::string url_ = ""; - int statusCode_ = -1; - long long int timeTakenInMicros_ = -1; - - NetworkMetric(const std::string& requestId, const std::string& url, int statusCode, - long long int time) { - requestId_ = requestId; - url_ = url; - statusCode_ = statusCode; - timeTakenInMicros_ = time; - } -}; - -inline const void to_json(nlohmann::json& j, const NetworkMetric& metric) { - j["requestId"] = metric.requestId_; - - auto position = metric.url_.find('?'); - if (position != std::string::npos) { - j["url"] = metric.url_.substr(0, position); - } else { - j["url"] = metric.url_; - } - - j["statusCode"] = metric.statusCode_; - j["timeUsecs"] = metric.timeTakenInMicros_; -} - -inline const void to_json(nlohmann::json& j, const FileDownloadInfo& m) { - j["requestId"] = m.requestId; - j["prevStatusCode"] = m.prevStatus; - j["currentStatusCode"] = m.currentStatus; - j["reasonCode"] = m.currentStatusReasonCode; - j["timeElapsedUSecs"] = m.timeElapsedInMicro; -} - -nlohmann::json convertHeadersToLowercase(const nlohmann::json& headersJson) { - nlohmann::json lowerHeadersJson; - for (const auto& [key, value] : headersJson.items()) { - std::string lowerKey = key; - std::transform(lowerKey.begin(), lowerKey.end(), lowerKey.begin(), ::tolower); - lowerHeadersJson[lowerKey] = value; - } - return lowerHeadersJson; -} - -bool is_success(const shared_ptr response) { - if (response->r.statusCode >= 200 && response->r.statusCode < 300) return true; - return false; -} - -bool is_failure(const shared_ptr response) { - if ((response->r.statusCode >= 400 && response->r.statusCode < 600) || - response->r.statusCode == EMPTY_ERROR_CODE) - return true; - return false; -} - -// Generates unique Request Ids to be sent with Server API Calls -std::string ServerAPI::get_requestId() const { - return _config->deviceId + "-" + - to_string(std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count()); -} - -const std::shared_ptr ServerAPI::send_request(const std::string& body, - nlohmann::json headers, - const std::string& url, - const std::string& method, - int length) { - auto requestId = get_requestId(); - headers.push_back(nlohmann::json::object({{"Request-Id", requestId}})); - auto start = Time::get_high_resolution_clock_time(); - auto response = nativeinterface::send_request(body, headers.dump(), url, method, length); - auto timeTaken = Time::get_elapsed_time_in_micro(start); - NetworkMetric m(requestId, url, response->r.statusCode, timeTaken); - _metricsAgent->log_metrics(NETWORK.c_str(), nlohmann::json(m)); - return response; -} - -std::string ServerAPI::get_host(const std::string& reqType, const std::string& defaultHost) { -#ifdef TESTING - return HOST; -#endif - if (requestToHostMap.find(reqType) != requestToHostMap.end()) { - if (requestToHostMap[reqType] == serverconstants::CDNHostIdentifier) - return CDN_HOST; - else if (requestToHostMap[reqType] == serverconstants::ServiceHostIdentifier) - return HOST; - } - return defaultHost; -} - -std::string ServerAPI::get_asset_url(std::shared_ptr asset, const std::string& defaultHost) { - if (asset->location.isPrivate) { - return ADS_HOST + asset->location.path; - } - auto reqType = assetmanager::get_string_from_asset_type(asset->type); - std::string host = get_host(reqType, defaultHost); - return host + serverconstants::ModelService + serverconstants::ApiVersionV4 + - asset->location.path; -} - -bool ServerAPI::init() { - if (registerDone) return true; - if (registerRetries == 0) return false; - registerRetries--; - string authInfoString; - if (nativeinterface::get_file_from_device_common(serverconstants::AuthInfoFile, authInfoString)) { - AuthenticationInfo info = jsonparser::get(authInfoString); - if (info.valid) { - try { - HEADERS = nlohmann::json::parse(info.apiHeaders); - QUERY = info.apiQuery; - return registerDone = true; - } catch (...) { - LOG_TO_ERROR("%s", "saved headers not parsed"); - // pass - } - } - } - if (device_register()) { - return registerDone = true; - } - LOG_TO_ERROR("%s", "Registeration failed"); - return false; -} - -void ServerAPI::update_request_to_host_map(const std::map& reqMap) { - requestToHostMap = reqMap; -} - -void ServerAPI::update_ads_host(const std::string& adsHost) { ADS_HOST = adsHost; } - -void ServerAPI::reset_register_retries() { registerRetries = serverconstants::MaxRegisterRetries; } - -FileDownloadStatus ServerAPI::download_file_async(const std::string& url, - const std::string& fileName) { - // NOTE : Request Id is now added at the outer layer for these requests. - auto headers = HEADERS; - const auto currentTime = Time::get_high_resolution_clock_time(); - const auto fileDownloadInfo = - nativeinterface::download_to_file_async(url, headers.dump(), fileName); - LOG_VERBOSE("Downloading URL %s into file %s, prev status %d, current status %d", url.c_str(), - fileName.c_str(), fileDownloadInfo.prevStatus, fileDownloadInfo.currentStatus); - - auto it = _currentStatusMap.find(url); - if (it != _currentStatusMap.end()) { - if (fileDownloadInfo.currentStatus != it->second) { - auto metricJson = nlohmann::json(fileDownloadInfo); - metricJson["url"] = url; - _metricsAgent->log_metrics(ASYNCDOWNLOAD.c_str(), metricJson); - } - } - _currentStatusMap[url] = fileDownloadInfo.currentStatus; - - return fileDownloadInfo.currentStatus; -} - -bool ServerAPI::device_register() { - json body; - body["deviceId"] = _config->deviceId; - json registerHeaders = json::array(); - auto requestId = get_requestId(); - registerHeaders.push_back( - json::object({{"ClientSecret", _config->clientSecret}, {"Request-Id", requestId}})); - - string URL = HOST + serverconstants::ModelService + serverconstants::ApiVersionV4 + "/clients/" + - _config->clientId + "/register"; - - const auto netResponse = ServerAPI::send_request(body.dump(), registerHeaders, URL, "POST"); - - if (is_failure(netResponse)) { - LOG_TO_ERROR("Device Registration Failed with status_code=%d .", netResponse->r.statusCode); - return false; - } - string responseString(netResponse->r.body, netResponse->r.bodyLength); - RegisterResponse response = jsonparser::get(responseString); - json headers = response.headers; - HEADERS = headers; - QUERY = ""; - if (response.queryParams.length() != 0) { - QUERY = "?" + response.queryParams; - } - AuthenticationInfo info; - info.apiHeaders = headers.dump(); - info.apiQuery = QUERY; - nativeinterface::save_file_on_device_common(nlohmann::json(info).dump(), - serverconstants::AuthInfoFile); - LOG_TO_INFO("%s", "Device Registration Successful"); - - return true; -} - -bool ServerAPI::upload_logs(const LogRequestBody& logrequest) { - auto requestId = get_requestId(); - nlohmann::json logMetric; - string host = logrequest.host; - json headers = logrequest.headers; - const auto response = ServerAPI::send_request(logrequest.body.c_str(), headers, host, "POST"); - string responseString(response->r.body, response->r.bodyLength); - if (is_success(response)) { - return true; - } - return false; -} - -std::pair ServerAPI::get_cloud_config(std::string eTag, - int retries) { - std::string URL = get_cloudconfig_url(_config); - auto startTime = DeviceTime::current_time(); - - auto headers = HEADERS; - if (!eTag.empty()) { - headers.push_back(nlohmann::json::object({{"If-None-Match", eTag}})); - } - - const auto response = ServerAPI::send_request("", headers, URL, "GET"); - if (is_failure(response)) { - LOG_TO_ERROR("Error in cloud config with status code %d", response->r.statusCode); - if (retries > 0) { - if (response->r.statusCode == AUTH_ERR) { - if (device_register()) return get_cloud_config(eTag, retries - 1); - } - } - - return std::make_pair(CloudConfigResponse(), Deployment()); - } - - if (response->r.statusCode == UNMODIFIED) { - LOG_TO_INFO("%s", "Cloud config is unmodified"); - CloudConfigResponse configResponse = CloudConfigResponse(); - configResponse.state = CloudConfigState::Unmodified; - return std::make_pair(configResponse, Deployment()); - } - - string responseString(response->r.body, response->r.bodyLength); - auto [configResponse, deployment] = - get_config_and_deployment_from_json(nlohmann::json::parse(responseString)); - - try { - nlohmann::json headersJson = - convertHeadersToLowercase(nlohmann::json::parse(response->r.headers)); - - if (const auto etagIt = headersJson.find("etag"); etagIt != headersJson.end()) { - deployment.eTag = etagIt.value(); - } - - const std::string neDate = headersJson.at("ne-date"); - auto serverTime = EpochTime::from_seconds(std::stoll(neDate)); - - if (const auto ageIt = headersJson.find("age"); ageIt != headersJson.end()) { - serverTime = serverTime + Duration::from_seconds(std::stoll(std::string{ageIt.value()})); - } - - configResponse.peggedDeviceTime = PeggedDeviceTime{startTime, serverTime}; - } catch (const std::exception& e) { - LOG_TO_ERROR("Unable to parse cloud config response headers as json: %s. Headers: %s", e.what(), - response->r.headers); - } - - LOG_TO_DEBUG("%s", "Found Cloud Config"); - return std::make_pair(configResponse, deployment); -} - -std::optional ServerAPI::get_asset(std::shared_ptr asset) { - string URL = get_asset_url(asset, CDN_HOST); - const auto response = ServerAPI::send_request("", HEADERS, URL, "GET"); - if (is_failure(response)) { - LOG_TO_ERROR("Error in get_asset of type=%s with status code %d", - assetmanager::get_string_from_asset_type(asset->type).c_str(), - response->r.statusCode); - if (response->r.statusCode == AUTH_ERR) { - device_register(); - } - return std::nullopt; - } - - string responseString(response->r.body, response->r.bodyLength); - return std::optional(responseString); -} - -FileDownloadStatus ServerAPI::get_asset_async(std::shared_ptr asset) { - string URL = get_asset_url(asset, CDN_HOST); - auto fileName = asset->get_file_name_on_device(); - return ServerAPI::download_file_async(URL, fileName); -} - -#ifdef GENAI -FileDownloadStatus ServerAPI::get_llm(std::shared_ptr asset) { - string URL = get_asset_url(asset, CDN_HOST); - auto gzFileName = asset->get_file_name_on_device() + ".zip.gz"; - auto zipFileName = asset->get_file_name_on_device() + ".zip"; - - // Unzipping the archive takes significant time in case of large models. In a scenario where user - // closes the app in the middle of unzip we want to be able to unzip from the archive present on - // device instead of downloading again. - if (nativeinterface::file_exists_common(zipFileName)) { - util::delete_folder_recursively(asset->get_file_name_on_device()); - if (nativeinterface::unzip_archive(zipFileName, asset->get_file_name_on_device())) { - if (nativeinterface::delete_file(zipFileName)) { - return FileDownloadStatus::DOWNLOAD_SUCCESS; - } - } - } - - auto fileDownloadStatus = ServerAPI::download_file_async(URL, gzFileName); - if (fileDownloadStatus == FileDownloadStatus::DOWNLOAD_SUCCESS) { - // Decompress .zip.gz to .zip - if (!nativeinterface::decompress_file(gzFileName, zipFileName)) { - LOG_TO_CLIENT_ERROR("Could not decompress file: %s", gzFileName.c_str()); - return FileDownloadStatus::DOWNLOAD_FAILURE; - } - // Delete .gz.zip - if (!nativeinterface::delete_file(gzFileName, false)) { - LOG_TO_CLIENT_ERROR("Could not delete file: %s", gzFileName.c_str()); - return FileDownloadStatus::DOWNLOAD_FAILURE; - } - // Unarchive .zip to folder - if (!nativeinterface::unzip_archive(zipFileName, asset->get_file_name_on_device())) { - LOG_TO_CLIENT_ERROR("Could not unqip archive: %s", zipFileName.c_str()); - return FileDownloadStatus::DOWNLOAD_FAILURE; - } - // Delete .zip - if (!nativeinterface::delete_file(zipFileName)) { - LOG_TO_ERROR("Could not delete file: %s", zipFileName.c_str()); - return FileDownloadStatus::DOWNLOAD_SUCCESS; - } - } - return fileDownloadStatus; -} -#endif // GENAI - -void ServerAPI::register_new_event(const std::string& eventName) { - string host = get_host("register_event", HOST); - string URL = host + serverconstants::ModelService + serverconstants::ApiVersionV4 + "/clients/" + - _config->clientId + "/events/" + eventName + "/register" + QUERY; - const auto response = ServerAPI::send_request("", HEADERS, URL, "GET"); - if (is_failure(response)) { - LOG_TO_ERROR("Register Event failed for %s", eventName.c_str()); - } -} - -std::string ServerAPI::get_cloudconfig_url(const std::shared_ptr config) { - string host = get_host("cloudConfig", HOST); - - std::string queryParams = QUERY; - std::string shardNumberQuery = - "shardNumber=" + std::to_string(util::calculate_shard_number(config->deviceId)); - if (queryParams == "") { - queryParams = "?" + shardNumberQuery; - } else { - queryParams += "&" + shardNumberQuery; - } - - std::string cohortIdQuery = "cohortIds=" + config->cohortIds.dump(); - queryParams += "&" + cohortIdQuery; - - std::string deviceIdQuery = "deviceId=" + config->deviceId; - - queryParams += "&" + deviceIdQuery; - - string URL = host + serverconstants::ModelService + serverconstants::ApiVersionV4 + "/clients/" + - config->clientId + "/deployments/" + config->compatibilityTag + "/config" + - queryParams; - return URL; -} - -std::string ServerAPI::get_cloudconfig_url(const std::string& configJson) { - auto config = nlohmann::json::parse(configJson); - return get_cloudconfig_url(std::make_shared(config)); -} +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "server_api.hpp" + +#include +#include + +#include "asset_manager.hpp" +#include "client.h" +#include "config_manager.hpp" +#include "core_utils/shard.hpp" +#include "json.hpp" +#include "logger.hpp" +#include "nimble_net_util.hpp" +#include "resource_manager_structs.hpp" +#include "time_manager.hpp" +#include "util.hpp" + +using json = nlohmann::json; + +#include "native_interface.hpp" + +using namespace std; + +#define GETPLANMETRIC "getplan" +#define REGISTERMETRIC "register" +#define GETCLOUDCONFIGMETRIC "getCloudConfig" +#define GETMODELVERSIONMETRIC "getModelVersion" +#define GETTASKMETRIC "getTask" +#define LOGMETRIC "logMetric" +static inline const std::string NETWORK = "network"; +static inline const std::string ASYNCDOWNLOAD = "asyncdownload"; + +struct NetworkMetric { + std::string requestId_ = ""; + std::string url_ = ""; + int statusCode_ = -1; + long long int timeTakenInMicros_ = -1; + + NetworkMetric(const std::string& requestId, const std::string& url, int statusCode, + long long int time) { + requestId_ = requestId; + url_ = url; + statusCode_ = statusCode; + timeTakenInMicros_ = time; + } +}; + +inline const void to_json(nlohmann::json& j, const NetworkMetric& metric) { + j["requestId"] = metric.requestId_; + + auto position = metric.url_.find('?'); + if (position != std::string::npos) { + j["url"] = metric.url_.substr(0, position); + } else { + j["url"] = metric.url_; + } + + j["statusCode"] = metric.statusCode_; + j["timeUsecs"] = metric.timeTakenInMicros_; +} + +inline const void to_json(nlohmann::json& j, const FileDownloadInfo& m) { + j["requestId"] = m.requestId; + j["prevStatusCode"] = m.prevStatus; + j["currentStatusCode"] = m.currentStatus; + j["reasonCode"] = m.currentStatusReasonCode; + j["timeElapsedUSecs"] = m.timeElapsedInMicro; +} + +nlohmann::json convertHeadersToLowercase(const nlohmann::json& headersJson) { + nlohmann::json lowerHeadersJson; + for (const auto& [key, value] : headersJson.items()) { + std::string lowerKey = key; + std::transform(lowerKey.begin(), lowerKey.end(), lowerKey.begin(), ::tolower); + lowerHeadersJson[lowerKey] = value; + } + return lowerHeadersJson; +} + +bool is_success(const shared_ptr response) { + if (response->r.statusCode >= 200 && response->r.statusCode < 300) return true; + return false; +} + +bool is_failure(const shared_ptr response) { + if ((response->r.statusCode >= 400 && response->r.statusCode < 600) || + response->r.statusCode == EMPTY_ERROR_CODE) + return true; + return false; +} + +// Generates unique Request Ids to be sent with Server API Calls +std::string ServerAPI::get_requestId() const { + return _config->deviceId + "-" + + to_string(std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count()); +} + +const std::shared_ptr ServerAPI::send_request(const std::string& body, + nlohmann::json headers, + const std::string& url, + const std::string& method, + int length) { + auto requestId = get_requestId(); + headers.push_back(nlohmann::json::object({{"Request-Id", requestId}})); + auto start = Time::get_high_resolution_clock_time(); + auto response = nativeinterface::send_request(body, headers.dump(), url, method, length); + auto timeTaken = Time::get_elapsed_time_in_micro(start); + NetworkMetric m(requestId, url, response->r.statusCode, timeTaken); + _metricsAgent->log_metrics(NETWORK.c_str(), nlohmann::json(m)); + return response; +} + +std::string ServerAPI::get_host(const std::string& reqType, const std::string& defaultHost) { +#ifdef TESTING + return HOST; +#endif + if (requestToHostMap.find(reqType) != requestToHostMap.end()) { + if (requestToHostMap[reqType] == serverconstants::CDNHostIdentifier) + return CDN_HOST; + else if (requestToHostMap[reqType] == serverconstants::ServiceHostIdentifier) + return HOST; + } + return defaultHost; +} + +std::string ServerAPI::get_asset_url(std::shared_ptr asset, const std::string& defaultHost) { + if (asset->location.isPrivate) { + return ADS_HOST + asset->location.path; + } + auto reqType = assetmanager::get_string_from_asset_type(asset->type); + std::string host = get_host(reqType, defaultHost); + return host + serverconstants::ModelService + serverconstants::ApiVersionV4 + + asset->location.path; +} + +bool ServerAPI::init() { + if (registerDone) return true; + if (registerRetries == 0) return false; + registerRetries--; + string authInfoString; + if (nativeinterface::get_file_from_device_common(serverconstants::AuthInfoFile, authInfoString)) { + AuthenticationInfo info = jsonparser::get(authInfoString); + if (info.valid) { + try { + HEADERS = nlohmann::json::parse(info.apiHeaders); + QUERY = info.apiQuery; + return registerDone = true; + } catch (...) { + LOG_TO_ERROR("%s", "saved headers not parsed"); + // pass + } + } + } + if (device_register()) { + return registerDone = true; + } + LOG_TO_ERROR("%s", "Registeration failed"); + return false; +} + +void ServerAPI::update_request_to_host_map(const std::map& reqMap) { + requestToHostMap = reqMap; +} + +void ServerAPI::update_ads_host(const std::string& adsHost) { ADS_HOST = adsHost; } + +void ServerAPI::reset_register_retries() { registerRetries = serverconstants::MaxRegisterRetries; } + +FileDownloadStatus ServerAPI::download_file_async(const std::string& url, + const std::string& fileName) { + // NOTE : Request Id is now added at the outer layer for these requests. + auto headers = HEADERS; + const auto currentTime = Time::get_high_resolution_clock_time(); + const auto fileDownloadInfo = + nativeinterface::download_to_file_async(url, headers.dump(), fileName); + LOG_VERBOSE("Downloading URL %s into file %s, prev status %d, current status %d", url.c_str(), + fileName.c_str(), fileDownloadInfo.prevStatus, fileDownloadInfo.currentStatus); + + auto it = _currentStatusMap.find(url); + if (it != _currentStatusMap.end()) { + if (fileDownloadInfo.currentStatus != it->second) { + auto metricJson = nlohmann::json(fileDownloadInfo); + metricJson["url"] = url; + _metricsAgent->log_metrics(ASYNCDOWNLOAD.c_str(), metricJson); + } + } + _currentStatusMap[url] = fileDownloadInfo.currentStatus; + + return fileDownloadInfo.currentStatus; +} + +bool ServerAPI::device_register() { + json body; + body["deviceId"] = _config->deviceId; + json registerHeaders = json::array(); + auto requestId = get_requestId(); + registerHeaders.push_back( + json::object({{"ClientSecret", _config->clientSecret}, {"Request-Id", requestId}})); + + string URL = HOST + serverconstants::ModelService + serverconstants::ApiVersionV4 + "/clients/" + + _config->clientId + "/register"; + + const auto netResponse = ServerAPI::send_request(body.dump(), registerHeaders, URL, "POST"); + + if (is_failure(netResponse)) { + LOG_TO_ERROR("Device Registration Failed with status_code=%d .", netResponse->r.statusCode); + return false; + } + string responseString(netResponse->r.body, netResponse->r.bodyLength); + RegisterResponse response = jsonparser::get(responseString); + json headers = response.headers; + HEADERS = headers; + QUERY = ""; + if (response.queryParams.length() != 0) { + QUERY = "?" + response.queryParams; + } + AuthenticationInfo info; + info.apiHeaders = headers.dump(); + info.apiQuery = QUERY; + nativeinterface::save_file_on_device_common(nlohmann::json(info).dump(), + serverconstants::AuthInfoFile); + LOG_TO_INFO("%s", "Device Registration Successful"); + + return true; +} + +bool ServerAPI::upload_logs(const LogRequestBody& logrequest) { + auto requestId = get_requestId(); + nlohmann::json logMetric; + string host = logrequest.host; + json headers = logrequest.headers; + const auto response = ServerAPI::send_request(logrequest.body.c_str(), headers, host, "POST"); + string responseString(response->r.body, response->r.bodyLength); + if (is_success(response)) { + return true; + } + return false; +} + +std::pair ServerAPI::get_cloud_config(std::string eTag, + int retries) { + std::string URL = get_cloudconfig_url(_config); + auto startTime = DeviceTime::current_time(); + + auto headers = HEADERS; + if (!eTag.empty()) { + headers.push_back(nlohmann::json::object({{"If-None-Match", eTag}})); + } + + const auto response = ServerAPI::send_request("", headers, URL, "GET"); + if (is_failure(response)) { + LOG_TO_ERROR("Error in cloud config with status code %d", response->r.statusCode); + if (retries > 0) { + if (response->r.statusCode == AUTH_ERR) { + if (device_register()) return get_cloud_config(eTag, retries - 1); + } + } + + return std::make_pair(CloudConfigResponse(), Deployment()); + } + + if (response->r.statusCode == UNMODIFIED) { + LOG_TO_INFO("%s", "Cloud config is unmodified"); + CloudConfigResponse configResponse = CloudConfigResponse(); + configResponse.state = CloudConfigState::Unmodified; + return std::make_pair(configResponse, Deployment()); + } + + string responseString(response->r.body, response->r.bodyLength); + auto [configResponse, deployment] = + get_config_and_deployment_from_json(nlohmann::json::parse(responseString)); + + try { + nlohmann::json headersJson = + convertHeadersToLowercase(nlohmann::json::parse(response->r.headers)); + + if (const auto etagIt = headersJson.find("etag"); etagIt != headersJson.end()) { + deployment.eTag = etagIt.value(); + } + + const std::string neDate = headersJson.at("ne-date"); + auto serverTime = EpochTime::from_seconds(std::stoll(neDate)); + + if (const auto ageIt = headersJson.find("age"); ageIt != headersJson.end()) { + serverTime = serverTime + Duration::from_seconds(std::stoll(std::string{ageIt.value()})); + } + + configResponse.peggedDeviceTime = PeggedDeviceTime{startTime, serverTime}; + } catch (const std::exception& e) { + LOG_TO_ERROR("Unable to parse cloud config response headers as json: %s. Headers: %s", e.what(), + response->r.headers); + } + + LOG_TO_DEBUG("%s", "Found Cloud Config"); + return std::make_pair(configResponse, deployment); +} + +std::optional ServerAPI::get_asset(std::shared_ptr asset) { + string URL = get_asset_url(asset, CDN_HOST); + const auto response = ServerAPI::send_request("", HEADERS, URL, "GET"); + if (is_failure(response)) { + LOG_TO_ERROR("Error in get_asset of type=%s with status code %d", + assetmanager::get_string_from_asset_type(asset->type).c_str(), + response->r.statusCode); + if (response->r.statusCode == AUTH_ERR) { + device_register(); + } + return std::nullopt; + } + + string responseString(response->r.body, response->r.bodyLength); + return std::optional(responseString); +} + +FileDownloadStatus ServerAPI::get_asset_async(std::shared_ptr asset) { + string URL = get_asset_url(asset, CDN_HOST); + auto fileName = asset->get_file_name_on_device(); + return ServerAPI::download_file_async(URL, fileName); +} + +#ifdef GENAI +FileDownloadStatus ServerAPI::get_llm(std::shared_ptr asset) { + string URL = get_asset_url(asset, CDN_HOST); + auto gzFileName = asset->get_file_name_on_device() + ".zip.gz"; + auto zipFileName = asset->get_file_name_on_device() + ".zip"; + + // Unzipping the archive takes significant time in case of large models. In a scenario where user + // closes the app in the middle of unzip we want to be able to unzip from the archive present on + // device instead of downloading again. + if (nativeinterface::file_exists_common(zipFileName)) { + util::delete_folder_recursively(asset->get_file_name_on_device()); + if (nativeinterface::unzip_archive(zipFileName, asset->get_file_name_on_device())) { + if (nativeinterface::delete_file(zipFileName)) { + return FileDownloadStatus::DOWNLOAD_SUCCESS; + } + } + } + + auto fileDownloadStatus = ServerAPI::download_file_async(URL, gzFileName); + if (fileDownloadStatus == FileDownloadStatus::DOWNLOAD_SUCCESS) { + // Decompress .zip.gz to .zip + if (!nativeinterface::decompress_file(gzFileName, zipFileName)) { + LOG_TO_CLIENT_ERROR("Could not decompress file: %s", gzFileName.c_str()); + return FileDownloadStatus::DOWNLOAD_FAILURE; + } + // Delete .gz.zip + if (!nativeinterface::delete_file(gzFileName, false)) { + LOG_TO_CLIENT_ERROR("Could not delete file: %s", gzFileName.c_str()); + return FileDownloadStatus::DOWNLOAD_FAILURE; + } + // Unarchive .zip to folder + if (!nativeinterface::unzip_archive(zipFileName, asset->get_file_name_on_device())) { + LOG_TO_CLIENT_ERROR("Could not unqip archive: %s", zipFileName.c_str()); + return FileDownloadStatus::DOWNLOAD_FAILURE; + } + // Delete .zip + if (!nativeinterface::delete_file(zipFileName)) { + LOG_TO_ERROR("Could not delete file: %s", zipFileName.c_str()); + return FileDownloadStatus::DOWNLOAD_SUCCESS; + } + } + return fileDownloadStatus; +} +#endif // GENAI + +void ServerAPI::register_new_event(const std::string& eventName) { + string host = get_host("register_event", HOST); + string URL = host + serverconstants::ModelService + serverconstants::ApiVersionV4 + "/clients/" + + _config->clientId + "/events/" + eventName + "/register" + QUERY; + const auto response = ServerAPI::send_request("", HEADERS, URL, "GET"); + if (is_failure(response)) { + LOG_TO_ERROR("Register Event failed for %s", eventName.c_str()); + } +} + +std::string ServerAPI::get_cloudconfig_url(const std::shared_ptr config) { + string host = get_host("cloudConfig", HOST); + + std::string queryParams = QUERY; + std::string shardNumberQuery = + "shardNumber=" + std::to_string(util::calculate_shard_number(config->deviceId)); + if (queryParams == "") { + queryParams = "?" + shardNumberQuery; + } else { + queryParams += "&" + shardNumberQuery; + } + + std::string cohortIdQuery = "cohortIds=" + config->cohortIds.dump(); + queryParams += "&" + cohortIdQuery; + + std::string deviceIdQuery = "deviceId=" + config->deviceId; + + queryParams += "&" + deviceIdQuery; + + string URL = host + serverconstants::ModelService + serverconstants::ApiVersionV4 + "/clients/" + + config->clientId + "/deployments/" + config->compatibilityTag + "/config" + + queryParams; + return URL; +} + +std::string ServerAPI::get_cloudconfig_url(const std::string& configJson) { + auto config = nlohmann::json::parse(configJson); + return get_cloudconfig_url(std::make_shared(config)); +} diff --git a/coreruntime/nimblenet/util/include/logger.hpp b/coreruntime/nimblenet/util/include/logger.hpp index 415843cf..0d99cce5 100644 --- a/coreruntime/nimblenet/util/include/logger.hpp +++ b/coreruntime/nimblenet/util/include/logger.hpp @@ -1,443 +1,443 @@ -/* - * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include -#include -#include - -#include -#include -#include -#include - -#include "client.h" -#include "core_utils/atomic_ptr.hpp" -#include "core_utils/fmt.hpp" -#include "logger_constants.hpp" -#include "time_manager.hpp" - -/** - * @brief Configuration for writing logs to disk. - */ -struct LogWritingConfig { - int maxLogFileSizeKB = loggerconstants::MaxLogFileSizeKB; /**< Maximum log file size in KB. */ - std::map logTypesToWrite; /**< Log types to write. */ - std::map eventTypesToWrite; /**< Event types to write. */ - bool scriptVerbose = false; /**< Enable verbose script logging. */ - bool collectEvents = false; /**< Enable event collection. */ -}; - -/** - * @brief Populates a LogWritingConfig from a JSON object. - * - * @param j The JSON object. - * @param config The LogWritingConfig to populate. - */ -static inline void from_json(const nlohmann::json& j, LogWritingConfig& config) { - if (j.find("maxLogFileSizeKB") != j.end()) { - j.at("maxLogFileSizeKB").get_to(config.maxLogFileSizeKB); - } - if (j.find("eventTypesToWrite") != j.end()) { - j.at("eventTypesToWrite").get_to(config.eventTypesToWrite); - } - if (j.find("scriptVerbose") != j.end()) { - j.at("scriptVerbose").get_to(config.scriptVerbose); - } - if (j.find("logTypesToWrite") != j.end()) { - j.at("logTypesToWrite").get_to(config.logTypesToWrite); - } - if (j.find("collectEvents") != j.end()) { - j.at("collectEvents").get_to(config.collectEvents); - } -} - -/** - * @brief Serializes a LogWritingConfig to a JSON object. - * - * @param j The JSON object to populate. - * @param config The LogWritingConfig to serialize. - */ -static inline void to_json(nlohmann::json& j, const LogWritingConfig& config) { - j = nlohmann::json{{"maxLogFileSizeKB", config.maxLogFileSizeKB}, - {"scriptVerbose", config.scriptVerbose}, - {"eventTypesToWrite", config.eventTypesToWrite}, - {"logTypesToWrite", config.logTypesToWrite}, - {"collectEvents", config.collectEvents}}; -} - -namespace ne { -/** - * @brief Gets the file size for a given file path. - * - * @param fullFilePath The full path to the file. - * @return File size in bytes, or 0 if not found or is a directory. - */ -static inline int64_t get_file_size(const std::string& fullFilePath) { - struct stat fileInfo; - if (stat(fullFilePath.c_str(), &fileInfo) != 0) { - return 0; - } - if (S_ISDIR(fileInfo.st_mode)) { - // is a directory - return 0; - } - - // Extract the last access time - return fileInfo.st_size; -}; -} // namespace ne - -/** - * @brief Provides thread-safe logging to disk with file rotation for both logs and events. - */ -class Logger { - std::string _writeFile; /**< Current log file name. */ - std::string _logDirectory; /**< Directory for log files. */ - FILE* _writeFilePtr = NULL; /**< File pointer for writing logs. */ - ne::AtomicPtr _atomicLogConfig; /**< Atomic pointer to log config. */ - std::mutex _logMutex; /**< Mutex for thread-safe logging. */ - bool _isClientDebug = false; /**< Enable client debug logging. */ - std::atomic _dirSize = 0; /**< Current directory size in bytes. */ - int64_t _maxDirSize = loggerconstants::MaxEventsSizeKBs * 1024; /**< Max allowed directory size. */ - - std::atomic _logVerbose = true; /**< Enable verbose logging. */ - std::atomic _logError = true; /**< Enable error logging. */ - std::atomic _logWarning = true; /**< Enable warning logging. */ - - public: - /** - * @brief Constructs a Logger with a given log config. - * - * @param logConfig The log writing configuration. - */ - Logger(const LogWritingConfig& logConfig) { - _atomicLogConfig.store(std::make_shared(logConfig)); - } - - /** - * @brief Default constructor for Logger. - */ - Logger() {}; - - static ne::AtomicPtr sessionId; /**< Session ID for logging. */ - - /** - * @brief Initializes the logger with a directory for log files. - * - * @param logDir Directory for log files. - * @return True if initialization succeeded. - */ - bool init_logger(const std::string& logDir); - - /** - * @brief Sets the maximum size limit for the log directory. - * - * @param maxSizeInKBs Maximum size in KB. - */ - void set_max_size_limit(int64_t maxSizeInKBs) { _maxDirSize = maxSizeInKBs * 1024; } - - /** - * @brief Recomputes the total disk size used by log files in the log directory. - */ - void recompute_disk_size() { - int64_t dirSize = 0; - try { - DIR* dir = opendir(_logDirectory.c_str()); - if (!dir) { - return; - } else { - struct dirent* entry; - while ((entry = readdir(dir)) != nullptr) { - // Get the name of the file - std::string filename = entry->d_name; - if (filename == "." || filename == "..") // Skip current and parent directory entries - continue; - - // Construct full path - std::string fullpath = _logDirectory + filename; - - dirSize += ne::get_file_size(fullpath); - } - closedir(dir); - } - } catch (...) { - log_fatal("Unable to check for directory to write logs for nimbleSDK with exception"); - return; - } - _dirSize = dirSize; - } - - /** - * @brief Writes a log message to the log file, with log rotation if size limit is reached. - * - * @param message The log message. - * @param type The log type (e.g., INFO, ERROR). - * @param currentDate The current date string (optional, defaults to UTC date). - */ - void write_log(const char* message, const char* type, - const std::string& currentDate = Time::get_date_UTC()); - - /** - * @brief Updates the log configuration atomically. - * - * @param config The new log writing configuration. - */ - void update_log_config(const LogWritingConfig& config); - - /** - * @brief Enables or disables client debug logging. - * - * @param debug True to enable, false to disable. - */ - void set_debug_flag(bool debug) { _isClientDebug = debug; } - - /** - * @brief Breaks the current log file and returns the new file name. - * - * @return The new log file name, or an empty string if the file was empty and not rotated. - */ - std::string take_lock_and_break_current_file(); - - /** - * @brief Performs cleanup on segmentation fault (no-op). - * - * @param sigNum Signal number. - */ - void perform_segfault_cleanup(int sigNum) {}; - - /** - * @brief Returns the directory used for log files. - * - * @return The log directory path. - */ - std::string get_directory() const { return _logDirectory; }; - - /** - * @brief Logs a verbose message. - * - * @param format printf-style format string. - * @param ... Arguments for the format string. - */ - void LOGVERBOSE(const char* format, ...) { - NE_VARIADIC_FMT(format); - log_verbose(buf.str); - } - - /** - * @brief Logs a debug message. - * - * @param format printf-style format string. - * @param ... Arguments for the format string. - */ - void LOGDEBUG(const char* format, ...) { - if (!_logVerbose) return; - NE_VARIADIC_FMT(format); - write_log(buf.str, "DEBUG"); -#ifdef NDEBUG - // non debug -#else - log_debug(buf.str); -#endif - } - - /** - * @brief Logs an info message. - * - * @param format printf-style format string. - * @param ... Arguments for the format string. - */ - void LOGINFO(const char* format, ...) { - if (!_logVerbose) return; - NE_VARIADIC_FMT(format); - write_log(buf.str, "INFO"); -#ifdef NDEBUG - // non debug -#else - log_info(buf.str); -#endif - } - - /** - * @brief Logs a client info message. - * - * @param format printf-style format string. - * @param ... Arguments for the format string. - */ - void LOGCLIENTINFO(const char* format, ...) { - NE_VARIADIC_FMT(format); - write_log(buf.str, "INFO"); - log_info(buf.str); - } - - /** - * @brief Logs a warning message. - * - * @param format printf-style format string. - * @param ... Arguments for the format string. - */ - void LOGWARN(const char* format, ...) { - if (!_logWarning) return; - NE_VARIADIC_FMT(format); - write_log(buf.str, "WARN"); - log_warn(buf.str); - } - - /** - * @brief Logs an error message. - * - * @param format printf-style format string. - * @param ... Arguments for the format string. - */ - void LOGERROR(const char* format, ...) { - if (!_logError) return; - NE_VARIADIC_FMT(format); - write_log(buf.str, "ERROR"); -#ifdef NDEBUG - // non debug -#else - log_error(buf.str); -#endif - }; - - /** - * @brief Logs a client error message. - * - * @param format printf-style format string. - * @param ... Arguments for the format string. - */ - void LOGCLIENTERROR(const char* format, ...) { - NE_VARIADIC_FMT(format); - write_log(buf.str, "ERROR"); - log_error(buf.str); - }; - - /** - * @brief Logs a metrics event. - * - * @param metricType The type of metric. - * @param metricJsonString The metric data as a JSON string. - */ - void LOGMETRICS(const char* metricType, const char* metricJsonString) { - auto logConfig = _atomicLogConfig.load(); - auto found = logConfig->logTypesToWrite.find(metricType); - if (found != logConfig->logTypesToWrite.end() && found->second == false) { - return; - } - - auto buf = ne::fmt("%s ::: %s", metricType, metricJsonString); - write_log(buf.str, "METRICS"); - } - - /** - * @brief Logs a client debug message. - * - * @param format printf-style format string. - * @param ... Arguments for the format string. - */ - void CLIENTDEBUGLOG(const char* format, ...) { - if (!_isClientDebug) { - return; - } - NE_VARIADIC_FMT(format); - log_debug(buf.str); - } - - /** - * @brief Logs a script event if enabled. - * - * @param deploymentId Deployment ID for the script. - * @param metricType The type of metric. - * @param metricJsonString The metric data as a JSON string. - */ - void script_log(int deploymentId, const char* metricType, const char* metricJsonString); - - /** - * @brief Logs an event if the event type is enabled. - * - * @param eventType The type of event. - * @param rawEventJsonString The event data as a JSON string. - * @return True if the event was logged or skipped due to config, false if the event type is not enabled. - */ - bool event_log(const char* eventType, const char* rawEventJsonString); - - /** - * @brief Checks if an event type is new (not previously registered). - * - * @param eventType The event type to check. - * @return True if the event type is new, false otherwise. - */ - bool is_new_event_type(const std::string& eventType) { - auto inserted = _atomicLogConfig.load()->eventTypesToWrite.insert({eventType, false}); - return inserted.second; - } - - /** - * @brief Destructor for Logger. Closes the log file if open. - */ - ~Logger() { - if (_writeFilePtr) { - fclose(_writeFilePtr); - } - } - - private: - /** - * @brief Breaks the current log file and returns the new file name (internal). - * - * @param newFileName The new file name. - * @param logMutexUniqueLock Unique lock for thread safety. - * @return The new log file name. - */ - std::string break_current_file(std::string newFileName, - std::unique_lock&& logMutexUniqueLock); -}; - -extern std::shared_ptr logger; - -#define LOG_TO_ERROR(fmt, ...) logger->LOGERROR(fmt, ##__VA_ARGS__) -#define LOG_TO_CLIENT_ERROR(fmt, ...) logger->LOGCLIENTERROR(fmt, ##__VA_ARGS__) -#define LOG_TO_INFO(fmt, ...) logger->LOGINFO(fmt, ##__VA_ARGS__) -#define LOG_TO_CLIENT_INFO(fmt, ...) logger->LOGCLIENTINFO(fmt, ##__VA_ARGS__) -#define LOG_TO_WARN(fmt, ...) logger->LOGWARN(fmt, ##__VA_ARGS__) -#define LOG_TO_DEBUG(fmt, ...) logger->LOGDEBUG(fmt, ##__VA_ARGS__) -#define LOG_TO_CLIENT_DEBUG(fmt, ...) logger->CLIENTDEBUGLOG(fmt, ##__VA_ARGS__) - -#if defined(ENABLE_VERBOSE_LOGGING) && defined(ALLOW_VERBOSE_LOGGING) -#define LOG_VERBOSE(fmt, ...) logger->LOGVERBOSE(fmt, ##__VA_ARGS__) -#else // ENABLE_VERBOSE_LOGGING -#define LOG_VERBOSE(...) \ - do { \ - } while (false) -#endif // ENABLE_VERBOSE_LOGGING - -/** - * @brief Defining a custom error category - */ -class NimbleEdgeError : public std::error_category { - int _errorcode = 0; - - public: - const char* name() const noexcept override { return "NimbleEdgeError"; } - - std::string message(int ev) const override { return "Unknown error"; } - - NimbleEdgeError(int errorCode) { _errorcode = errorCode; } -}; - -/** - * Converting throwBuffer.str to std::string explicitly just to ensure std::system_error doesn't - * directly store the pointer we give it - */ -#define NETHROW(code, format, ...) \ - do { \ - auto throwBuffer = ne::fmt(format, ##__VA_ARGS__); \ - auto errorCategory = NimbleEdgeError(code); \ - auto systemError = \ - std::system_error(std::error_code(code, errorCategory), std::string{throwBuffer.str}); \ - throw systemError; \ - } while (0) +/* + * SPDX-FileCopyrightText: (C) 2025 DeliteAI Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include "client.h" +#include "core_utils/atomic_ptr.hpp" +#include "core_utils/fmt.hpp" +#include "logger_constants.hpp" +#include "time_manager.hpp" + +/** + * @brief Configuration for writing logs to disk. + */ +struct LogWritingConfig { + int maxLogFileSizeKB = loggerconstants::MaxLogFileSizeKB; /**< Maximum log file size in KB. */ + std::map logTypesToWrite; /**< Log types to write. */ + std::map eventTypesToWrite; /**< Event types to write. */ + bool scriptVerbose = false; /**< Enable verbose script logging. */ + bool collectEvents = false; /**< Enable event collection. */ +}; + +/** + * @brief Populates a LogWritingConfig from a JSON object. + * + * @param j The JSON object. + * @param config The LogWritingConfig to populate. + */ +static inline void from_json(const nlohmann::json& j, LogWritingConfig& config) { + if (j.find("maxLogFileSizeKB") != j.end()) { + j.at("maxLogFileSizeKB").get_to(config.maxLogFileSizeKB); + } + if (j.find("eventTypesToWrite") != j.end()) { + j.at("eventTypesToWrite").get_to(config.eventTypesToWrite); + } + if (j.find("scriptVerbose") != j.end()) { + j.at("scriptVerbose").get_to(config.scriptVerbose); + } + if (j.find("logTypesToWrite") != j.end()) { + j.at("logTypesToWrite").get_to(config.logTypesToWrite); + } + if (j.find("collectEvents") != j.end()) { + j.at("collectEvents").get_to(config.collectEvents); + } +} + +/** + * @brief Serializes a LogWritingConfig to a JSON object. + * + * @param j The JSON object to populate. + * @param config The LogWritingConfig to serialize. + */ +static inline void to_json(nlohmann::json& j, const LogWritingConfig& config) { + j = nlohmann::json{{"maxLogFileSizeKB", config.maxLogFileSizeKB}, + {"scriptVerbose", config.scriptVerbose}, + {"eventTypesToWrite", config.eventTypesToWrite}, + {"logTypesToWrite", config.logTypesToWrite}, + {"collectEvents", config.collectEvents}}; +} + +namespace ne { +/** + * @brief Gets the file size for a given file path. + * + * @param fullFilePath The full path to the file. + * @return File size in bytes, or 0 if not found or is a directory. + */ +static inline int64_t get_file_size(const std::string& fullFilePath) { + struct stat fileInfo; + if (stat(fullFilePath.c_str(), &fileInfo) != 0) { + return 0; + } + if (S_ISDIR(fileInfo.st_mode)) { + // is a directory + return 0; + } + + // Extract the last access time + return fileInfo.st_size; +}; +} // namespace ne + +/** + * @brief Provides thread-safe logging to disk with file rotation for both logs and events. + */ +class Logger { + std::string _writeFile; /**< Current log file name. */ + std::string _logDirectory; /**< Directory for log files. */ + FILE* _writeFilePtr = NULL; /**< File pointer for writing logs. */ + ne::AtomicPtr _atomicLogConfig; /**< Atomic pointer to log config. */ + std::mutex _logMutex; /**< Mutex for thread-safe logging. */ + bool _isClientDebug = false; /**< Enable client debug logging. */ + std::atomic _dirSize = 0; /**< Current directory size in bytes. */ + int64_t _maxDirSize = loggerconstants::MaxEventsSizeKBs * 1024; /**< Max allowed directory size. */ + + std::atomic _logVerbose = true; /**< Enable verbose logging. */ + std::atomic _logError = true; /**< Enable error logging. */ + std::atomic _logWarning = true; /**< Enable warning logging. */ + + public: + /** + * @brief Constructs a Logger with a given log config. + * + * @param logConfig The log writing configuration. + */ + Logger(const LogWritingConfig& logConfig) { + _atomicLogConfig.store(std::make_shared(logConfig)); + } + + /** + * @brief Default constructor for Logger. + */ + Logger() {}; + + static ne::AtomicPtr sessionId; /**< Session ID for logging. */ + + /** + * @brief Initializes the logger with a directory for log files. + * + * @param logDir Directory for log files. + * @return True if initialization succeeded. + */ + bool init_logger(const std::string& logDir); + + /** + * @brief Sets the maximum size limit for the log directory. + * + * @param maxSizeInKBs Maximum size in KB. + */ + void set_max_size_limit(int64_t maxSizeInKBs) { _maxDirSize = maxSizeInKBs * 1024; } + + /** + * @brief Recomputes the total disk size used by log files in the log directory. + */ + void recompute_disk_size() { + int64_t dirSize = 0; + try { + DIR* dir = opendir(_logDirectory.c_str()); + if (!dir) { + return; + } else { + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + // Get the name of the file + std::string filename = entry->d_name; + if (filename == "." || filename == "..") // Skip current and parent directory entries + continue; + + // Construct full path + std::string fullpath = _logDirectory + filename; + + dirSize += ne::get_file_size(fullpath); + } + closedir(dir); + } + } catch (...) { + log_fatal("Unable to check for directory to write logs for nimbleSDK with exception"); + return; + } + _dirSize = dirSize; + } + + /** + * @brief Writes a log message to the log file, with log rotation if size limit is reached. + * + * @param message The log message. + * @param type The log type (e.g., INFO, ERROR). + * @param currentDate The current date string (optional, defaults to UTC date). + */ + void write_log(const char* message, const char* type, + const std::string& currentDate = Time::get_date_UTC()); + + /** + * @brief Updates the log configuration atomically. + * + * @param config The new log writing configuration. + */ + void update_log_config(const LogWritingConfig& config); + + /** + * @brief Enables or disables client debug logging. + * + * @param debug True to enable, false to disable. + */ + void set_debug_flag(bool debug) { _isClientDebug = debug; } + + /** + * @brief Breaks the current log file and returns the new file name. + * + * @return The new log file name, or an empty string if the file was empty and not rotated. + */ + std::string take_lock_and_break_current_file(); + + /** + * @brief Performs cleanup on segmentation fault (no-op). + * + * @param sigNum Signal number. + */ + void perform_segfault_cleanup(int sigNum) {}; + + /** + * @brief Returns the directory used for log files. + * + * @return The log directory path. + */ + std::string get_directory() const { return _logDirectory; }; + + /** + * @brief Logs a verbose message. + * + * @param format printf-style format string. + * @param ... Arguments for the format string. + */ + void LOGVERBOSE(const char* format, ...) { + NE_VARIADIC_FMT(format); + log_verbose(buf.str); + } + + /** + * @brief Logs a debug message. + * + * @param format printf-style format string. + * @param ... Arguments for the format string. + */ + void LOGDEBUG(const char* format, ...) { + if (!_logVerbose) return; + NE_VARIADIC_FMT(format); + write_log(buf.str, "DEBUG"); +#ifdef NDEBUG + // non debug +#else + log_debug(buf.str); +#endif + } + + /** + * @brief Logs an info message. + * + * @param format printf-style format string. + * @param ... Arguments for the format string. + */ + void LOGINFO(const char* format, ...) { + if (!_logVerbose) return; + NE_VARIADIC_FMT(format); + write_log(buf.str, "INFO"); +#ifdef NDEBUG + // non debug +#else + log_info(buf.str); +#endif + } + + /** + * @brief Logs a client info message. + * + * @param format printf-style format string. + * @param ... Arguments for the format string. + */ + void LOGCLIENTINFO(const char* format, ...) { + NE_VARIADIC_FMT(format); + write_log(buf.str, "INFO"); + log_info(buf.str); + } + + /** + * @brief Logs a warning message. + * + * @param format printf-style format string. + * @param ... Arguments for the format string. + */ + void LOGWARN(const char* format, ...) { + if (!_logWarning) return; + NE_VARIADIC_FMT(format); + write_log(buf.str, "WARN"); + log_warn(buf.str); + } + + /** + * @brief Logs an error message. + * + * @param format printf-style format string. + * @param ... Arguments for the format string. + */ + void LOGERROR(const char* format, ...) { + if (!_logError) return; + NE_VARIADIC_FMT(format); + write_log(buf.str, "ERROR"); +#ifdef NDEBUG + // non debug +#else + log_error(buf.str); +#endif + }; + + /** + * @brief Logs a client error message. + * + * @param format printf-style format string. + * @param ... Arguments for the format string. + */ + void LOGCLIENTERROR(const char* format, ...) { + NE_VARIADIC_FMT(format); + write_log(buf.str, "ERROR"); + log_error(buf.str); + }; + + /** + * @brief Logs a metrics event. + * + * @param metricType The type of metric. + * @param metricJsonString The metric data as a JSON string. + */ + void LOGMETRICS(const char* metricType, const char* metricJsonString) { + auto logConfig = _atomicLogConfig.load(); + auto found = logConfig->logTypesToWrite.find(metricType); + if (found != logConfig->logTypesToWrite.end() && found->second == false) { + return; + } + + auto buf = ne::fmt("%s ::: %s", metricType, metricJsonString); + write_log(buf.str, "METRICS"); + } + + /** + * @brief Logs a client debug message. + * + * @param format printf-style format string. + * @param ... Arguments for the format string. + */ + void CLIENTDEBUGLOG(const char* format, ...) { + if (!_isClientDebug) { + return; + } + NE_VARIADIC_FMT(format); + log_debug(buf.str); + } + + /** + * @brief Logs a script event if enabled. + * + * @param deploymentId Deployment ID for the script. + * @param metricType The type of metric. + * @param metricJsonString The metric data as a JSON string. + */ + void script_log(int deploymentId, const char* metricType, const char* metricJsonString); + + /** + * @brief Logs an event if the event type is enabled. + * + * @param eventType The type of event. + * @param rawEventJsonString The event data as a JSON string. + * @return True if the event was logged or skipped due to config, false if the event type is not enabled. + */ + bool event_log(const char* eventType, const char* rawEventJsonString); + + /** + * @brief Checks if an event type is new (not previously registered). + * + * @param eventType The event type to check. + * @return True if the event type is new, false otherwise. + */ + bool is_new_event_type(const std::string& eventType) { + auto inserted = _atomicLogConfig.load()->eventTypesToWrite.insert({eventType, false}); + return inserted.second; + } + + /** + * @brief Destructor for Logger. Closes the log file if open. + */ + ~Logger() { + if (_writeFilePtr) { + fclose(_writeFilePtr); + } + } + + private: + /** + * @brief Breaks the current log file and returns the new file name (internal). + * + * @param newFileName The new file name. + * @param logMutexUniqueLock Unique lock for thread safety. + * @return The new log file name. + */ + std::string break_current_file(std::string newFileName, + std::unique_lock&& logMutexUniqueLock); +}; + +extern std::shared_ptr logger; + +#define LOG_TO_ERROR(fmt, ...) logger->LOGERROR(fmt, ##__VA_ARGS__) +#define LOG_TO_CLIENT_ERROR(fmt, ...) logger->LOGCLIENTERROR(fmt, ##__VA_ARGS__) +#define LOG_TO_INFO(fmt, ...) logger->LOGINFO(fmt, ##__VA_ARGS__) +#define LOG_TO_CLIENT_INFO(fmt, ...) logger->LOGCLIENTINFO(fmt, ##__VA_ARGS__) +#define LOG_TO_WARN(fmt, ...) logger->LOGWARN(fmt, ##__VA_ARGS__) +#define LOG_TO_DEBUG(fmt, ...) logger->LOGDEBUG(fmt, ##__VA_ARGS__) +#define LOG_TO_CLIENT_DEBUG(fmt, ...) logger->CLIENTDEBUGLOG(fmt, ##__VA_ARGS__) + +#if defined(ENABLE_VERBOSE_LOGGING) && defined(ALLOW_VERBOSE_LOGGING) +#define LOG_VERBOSE(fmt, ...) logger->LOGVERBOSE(fmt, ##__VA_ARGS__) +#else // ENABLE_VERBOSE_LOGGING +#define LOG_VERBOSE(...) \ + do { \ + } while (false) +#endif // ENABLE_VERBOSE_LOGGING + +/** + * @brief Defining a custom error category + */ +class NimbleEdgeError : public std::error_category { + int _errorcode = 0; + + public: + const char* name() const noexcept override { return "NimbleEdgeError"; } + + std::string message(int ev) const override { return "Unknown error"; } + + NimbleEdgeError(int errorCode) { _errorcode = errorCode; } +}; + +/** + * Converting throwBuffer.str to std::string explicitly just to ensure std::system_error doesn't + * directly store the pointer we give it + */ +#define NETHROW(code, format, ...) \ + do { \ + auto throwBuffer = ne::fmt(format, ##__VA_ARGS__); \ + auto errorCategory = NimbleEdgeError(code); \ + auto systemError = \ + std::system_error(std::error_code(code, errorCategory), std::string{throwBuffer.str}); \ + throw systemError; \ + } while (0)