From 0496fe8397636bbef812aac2cea73186e7f129d7 Mon Sep 17 00:00:00 2001 From: Alexander Mohr Date: Tue, 30 Jul 2024 17:45:05 +0200 Subject: [PATCH] trace-load-limits: restrict trace load for applications In a shared system many developers log as much as they want into DLT. This can lead into overloading the logging system resulting in bad performance and dropped logs. This commit introduces trace load limits to restrict applications to a certain log volume measured in bytes/s. It is based on #134 but extends this heavily. Trace load limits are configured via a space separted configuraiton file. The format of the file follows: APPID [CTXID] SOFT_LIMIT HARD_LIMIT The most matching entry will be selected for each log, meaning that either app and context must match or at least the app id, for which a default is created when not configured. This allows to configure trace load for single contexts which can be used for example to limit different applications in the qnx slog to a given budget without affecting others or to give a file transfer unlimited bandwidth. It is recommended to always specify a budget for the application id without the contexts to ensure new contexts and internal logs like DLTL can be logged. Applications are starting up with a default limit defined via CMake variables TRACE_LOAD_USER_HARD_LIMIT, TRACE_LOAD_USER_SOFT_LIMIT. As soon as the connection to the daemon was succesull each configuration entry matching the app id will be sent to the client via an application message. If no configuration is found TRACE_LOAD_DAEMON_HARD_LIMIT and TRACE_LOAD_USER_SOFT_LIMIT will be used instead. The two staged configuration process makes sure that the daemon default can be set to 0, forcing developers to define a limit for their application while making sure that applications are able to log before they received the log levels. Measuring the trace load is done in the daemon and the client. Dropping messages on the client side is the primary mechanism and prevents sending logs to the daemon only to be dropped there, which reduces the load of the IPC. Measuring again on daemon side makes sure that rouge clients are still limited to the trace load defined. Exceeding the limit soft will produce the following logs: ECU1- DEMO DLTL log warn V 1 [Trace load exceeded trace soft limit on apid: DEMO. (soft limit: 2000 bytes/sec, current: 2414 bytes/sec)] ECU1- DEMO DLTL log warn V 1 [Trace load exceeded trace soft limit on apid: DEMO, ctid TEST.(soft limit: 150 bytes/sec, current: 191 bytes/sec)] Exceeding the hard limit will produce the same message but the text '42 messages discarded.' is appended and messages will be dropped. Dropped messages are lost and cannot be recovered, which forces developers to get their logging volume under control. As debug and trace load are usually disabled for production and thus do not impact the performance of actual systems these logs are not accounted for in trace load limits. In practice this turned out to be very usefull to improve developer experience while maintaining good performance, as devs know that debug and trace logs are only available during development and important information has to be logged on a higher level. To simplify creating a trace limit base line the script 'utils/calculate-load.py' is provided which makes suggestions for the limits based on actual log volume. Signed-off-by: Alexander Mohr --- CMakeLists.txt | 41 ++ doc/dlt-daemon.1.md | 4 + include/dlt/dlt_common.h | 110 ++++ include/dlt/dlt_types.h | 3 + include/dlt/dlt_user.h.in | 3 + src/daemon/CMakeLists.txt | 6 + src/daemon/dlt-daemon.c | 609 +++++++++++++++++++- src/daemon/dlt-daemon.h | 17 + src/daemon/dlt-trace-load.conf | 39 ++ src/daemon/dlt_daemon_client.c | 8 + src/daemon/dlt_daemon_common.c | 224 ++++++- src/daemon/dlt_daemon_common.h | 38 ++ src/daemon/dlt_daemon_connection.c | 15 + src/daemon/dlt_daemon_connection_types.h | 6 + src/daemon/dlt_daemon_event_handler_types.h | 3 + src/lib/dlt_user.c | 247 +++++++- src/shared/dlt_common.c | 379 +++++++++++- src/shared/dlt_user_shared.h | 10 + src/shared/dlt_user_shared_cfg.h | 1 + tests/CMakeLists.txt | 24 +- tests/gtest_dlt_daemon.cpp | 490 ++++++++++++++++ tests/gtest_dlt_daemon_common.cpp | 272 +++++++++ tests/gtest_dlt_user.cpp | 45 ++ util/calculate-load.py | 254 ++++++++ 24 files changed, 2813 insertions(+), 35 deletions(-) create mode 100644 src/daemon/dlt-trace-load.conf create mode 100644 tests/gtest_dlt_daemon.cpp create mode 100644 util/calculate-load.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b8521270..b311c2ddc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,8 @@ option(WITH_DLT_QNX_SYSTEM "Set to ON to build QNX system binary dlt-qnx-system" option(WITH_DLT_FILE_LOGGING_SYSLOG_FALLBACK "Set to ON to enable fallback to syslog if dlt logging to file fails" OFF) option(WITH_DLT_NETWORK_TRACE "Set to ON to enable network trace (if message queue is supported)" ON) option(WITH_DLT_LOG_LEVEL_APP_CONFIG "Set to ON to enable default log levels based on application ids" OFF) +option(WITH_DLT_TRACE_LOAD_CTRL "Set to ON to enable trace load control in libdlt and dlt-daemon" OFF) + set(DLT_IPC "FIFO" CACHE STRING "UNIX_SOCKET,FIFO") @@ -370,6 +372,44 @@ if(WITH_DLT_LOG_LEVEL_APP_CONFIG) add_definitions(-DDLT_LOG_LEVEL_APP_CONFIG) endif() + +if(WITH_DLT_TRACE_LOAD_CTRL) + add_definitions(-DDLT_TRACE_LOAD_CTRL_ENABLE) + + # Configure limits for client + if(NOT TRACE_LOAD_USER_SOFT_LIMIT) + set(TRACE_LOAD_USER_SOFT_LIMIT 83333) + endif() + + if(NOT TRACE_LOAD_USER_HARD_LIMIT) + set(TRACE_LOAD_USER_HARD_LIMIT 100000) + endif() + + if (TRACE_LOAD_USER_SOFT_LIMIT GREATER TRACE_LOAD_USER_HARD_LIMIT) + message(FATAL_ERROR "TRACE_LOAD_USER_SOFT_LIMIT must be less or equal than TRACE_LOAD_USER_HARD_LIMIT") + endif() + + add_definitions(-DDLT_TRACE_LOAD_CLIENT_HARD_LIMIT_DEFAULT=${TRACE_LOAD_USER_HARD_LIMIT}) + add_definitions(-DDLT_TRACE_LOAD_CLIENT_SOFT_LIMIT_DEFAULT=${TRACE_LOAD_USER_SOFT_LIMIT}) + + # Configure limits for daemon + if(NOT TRACE_LOAD_DAEMON_SOFT_LIMIT) + set(TRACE_LOAD_DAEMON_SOFT_LIMIT 0) + endif() + + if(NOT TRACE_LOAD_DAEMON_HARD_LIMIT) + set(TRACE_LOAD_DAEMON_HARD_LIMIT 0) + endif() + + if (TRACE_LOAD_DAEMON_SOFT_LIMIT GREATER TRACE_LOAD_DAEMON_HARD_LIMIT) + message(FATAL_ERROR "TRACE_LOAD_DAEMON_SOFT_LIMIT must be less or equal than TRACE_LOAD_DAEMON_HARD_LIMIT") + endif() + + add_definitions(-DDLT_TRACE_LOAD_DAEMON_HARD_LIMIT_DEFAULT=${TRACE_LOAD_DAEMON_HARD_LIMIT}) + add_definitions(-DDLT_TRACE_LOAD_DAEMON_SOFT_LIMIT_DEFAULT=${TRACE_LOAD_DAEMON_SOFT_LIMIT}) + +endif(WITH_DLT_TRACE_LOAD_CTRL) + add_subdirectory(doc) add_subdirectory(src) add_subdirectory(include) @@ -489,6 +529,7 @@ message(STATUS "WITH_EXTENDED_FILTERING = ${WITH_EXTENDED_FILTERING}") message(STATUS "WITH_DLT_DISABLE_MACRO = ${WITH_DLT_DISABLE_MACRO}") message(STATUS "WITH_DLT_FILE_LOGGING_SYSLOG_FALLBACK = ${WITH_DLT_FILE_LOGGING_SYSLOG_FALLBACK}") message(STATUS "WITH_DLT_LOG_LEVEL_APP_CONFIG = ${WITH_DLT_LOG_LEVEL_APP_CONFIG}") +message(STATUS "WITH_DLT_TRACE_LOAD_CTRL = ${WITH_DLT_TRACE_LOAD_CTRL}" ) message(STATUS "Change a value with: cmake -D=") message(STATUS "-------------------------------------------------------------------------------") message(STATUS) diff --git a/doc/dlt-daemon.1.md b/doc/dlt-daemon.1.md index d4ac4cad8..d33bb2142 100644 --- a/doc/dlt-daemon.1.md +++ b/doc/dlt-daemon.1.md @@ -46,6 +46,10 @@ COVESA system or more likely on a external tester device. : Load an alternative configuration for app id log level defaults. By default, the configuration file /etc/dlt-log-levels.conf is loaded. +-l + +: Load an alternative trace load configuration file. By default the configuration file /etc/dlt-trace-load.conf is loaded. + # EXAMPLES diff --git a/include/dlt/dlt_common.h b/include/dlt/dlt_common.h index d03b68fbf..99064fd6d 100644 --- a/include/dlt/dlt_common.h +++ b/include/dlt/dlt_common.h @@ -828,6 +828,116 @@ extern "C" { # endif + +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +/* For trace load control feature */ + +#include +/* For trace load control */ +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + +/* Number of slots in window for recording trace load (Default: 60) + * Average trace load in this window will be used as trace load + * Older time data than this size will be removed from trace load + */ +#define DLT_TRACE_LOAD_WINDOW_SIZE (60) + +/* Window resolution in unit of timestamp (Default: 10000 x 0.1 msec = 1 sec) + * This value is same as size of 1 slot of window. + * Actual window size in sec can be calculated by + * DLT_TRACE_LOAD_WINDOW_SIZE x DLT_TRACE_LOAD_WINDOW_RESOLUTION / DLT_TIMESTAMP_RESOLUTION. + * (Default: 60 x 10000 / 10000 = 60 sec) + * FIXME: When timestamp resolution of dlt is changed from 0.1 msec, + * then DLT_TRACE_LOAD_WINDOW_RESOLUTION value also has to be updated accordingly. + */ +#define DLT_TRACE_LOAD_WINDOW_RESOLUTION (10000) + +/* Special Context ID for output soft_limit/hard_limit over warning message (DLT LIMITS) */ +#define DLT_INTERNAL_CONTEXT_ID ("DLTL") + +/* Frequency in which warning messages are logged in seconds when an application is over the soft limit + * Unit of this value is Number of slot of window. + * NOTE: Size of the slot depends on value of DLT_TRACE_LOAD_WINDOW_RESOLUTION + * (Default: 10 slots = 10000 x 0.1 msec = 10 sec) + */ +#define DLT_SOFT_LIMIT_WARN_FREQUENCY (10) + +/* Frequency in which warning messages are logged in seconds when an application is over the hard limit + * Unit of this value is Number of slot of window. + * NOTE: Size of the slot depends on value of DLT_TRACE_LOAD_WINDOW_RESOLUTION + * (Default: 10 slots = 10000 x 0.1 msec = 10 sec) + */ +#define DLT_HARD_LIMIT_WARN_FREQUENCY (10) + +/* Timestamp resolution of 1 second (Default: 10000 -> 1/10000 = 0.0001sec = 0.1msec) + * This value is defined as reciprocal of the resolution (1 / DLT_TIMESTAMP_RESOLUTION) + * FIXME: When timestamp resolution of dlt is changed from 0.1 msec, + * then DLT_TIMESTAMP_RESOLUTION value also has to be updated accordingly. + */ +#define DLT_TIMESTAMP_RESOLUTION (10000) + +#endif + +typedef struct +{ + // Window for recording total bytes for each slots [bytes] + uint64_t window[DLT_TRACE_LOAD_WINDOW_SIZE]; + uint64_t total_bytes_of_window; // Grand total bytes of whole window [bytes] + uint32_t curr_slot; // Current slot No. of window [slot No.] + uint32_t last_slot; // Last slot No. of window [slot No.] + uint32_t curr_abs_slot; // Current absolute slot No. of window [slot No.] + uint32_t last_abs_slot; // Last absolute slot No. of window [slot No.] + uint64_t avg_trace_load; // Average trace load of whole window [bytes/sec] + uint32_t hard_limit_over_counter; // Discarded message counter due to hard limit over [msg] + uint32_t hard_limit_over_bytes; // Discarded message bytes due to hard limit over [msg] + uint32_t slot_left_soft_limit_warn; // Slot left to output next warning of soft limit over [slot No.] + uint32_t slot_left_hard_limit_warn; // Slot left to output next warning of hard limit over [slot No.] + bool is_over_soft_limit; // Flag if trace load has been over soft limit + bool is_over_hard_limit; // Flag if trace load has been over hard limit +} DltTraceLoadStat; + +/* + * The parameter of trace load settings + */ +typedef struct +{ + char apid[DLT_ID_SIZE]; /**< Application id for which the settings are valid */ + char ctid[DLT_ID_SIZE]; /**< Context id for which the settings are valid, this is optional */ + + uint32_t soft_limit; /**< Warning threshold, if load is above soft limit a warning will be logged but message won't be discarded */ + uint32_t hard_limit; /**< limit threshold, if load is above hard limit a warning will be logged and message will be discarded */ + + DltTraceLoadStat tl_stat; +} DltTraceLoadSettings; + +extern pthread_rwlock_t trace_load_rw_lock; + +#ifndef UINT32_MAX +#define UINT32_MAX 0xFFFFFFFF +#endif + +/* Precomputation */ +static const uint64_t TIMESTAMP_BASED_WINDOW_SIZE = DLT_TRACE_LOAD_WINDOW_SIZE * DLT_TRACE_LOAD_WINDOW_RESOLUTION; +typedef DltReturnValue (DltLogInternal)(DltLogLevelType loglevel, const char *text, void* params); +bool dlt_check_trace_load( + DltTraceLoadSettings* tl_settings, + int32_t log_level, + uint32_t timestamp, + int32_t size, + DltLogInternal internal_dlt_log, + void *internal_dlt_log_params); + +/** + * Find the runtime trace load settings for the given application id and context id. + * @param settings Array with all settings + * @param settings_count Size of settings + * @param apid The apid to search for + * @param ctid The context id to search for, can be NULL + * @return A sorted array with all settings that match the given apid and ctid + */ +DltTraceLoadSettings* dlt_find_runtime_trace_load_settings(DltTraceLoadSettings *settings, uint32_t settings_count, const char* apid, const char* ctid); +#endif + /** * Helper function to print a byte array in hex. * @param ptr pointer to the byte array. diff --git a/include/dlt/dlt_types.h b/include/dlt/dlt_types.h index e36a32c6b..a60fd3f22 100644 --- a/include/dlt/dlt_types.h +++ b/include/dlt/dlt_types.h @@ -83,6 +83,9 @@ typedef unsigned int speed_t; */ typedef enum { +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + DLT_RETURN_LOAD_EXCEEDED = -9, +#endif DLT_RETURN_FILESZERR = -8, DLT_RETURN_LOGGING_DISABLED = -7, DLT_RETURN_USER_BUFFER_FULL = -6, diff --git a/include/dlt/dlt_user.h.in b/include/dlt/dlt_user.h.in index e068ff9d4..42254acec 100644 --- a/include/dlt/dlt_user.h.in +++ b/include/dlt/dlt_user.h.in @@ -268,6 +268,9 @@ typedef struct DltUserConnectionState connection_state; # endif uint16_t log_buf_len; /**< length of message buffer, by default: DLT_USER_BUF_MAX_SIZE */ +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + pthread_rwlock_t trace_load_limit_lock; +#endif } DltUser; typedef int (*dlt_injection_callback_id)(uint32_t, void *, uint32_t, void *); diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 37c3c1a53..a20fa0d96 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -104,3 +104,9 @@ if (WITH_DLT_LOG_LEVEL_APP_CONFIG) DESTINATION ${CONFIGURATION_FILES_DIR} COMPONENT base) endif() + +if (WITH_DLT_TRACE_LOAD_CTRL) + INSTALL(FILES dlt-trace-load.conf + DESTINATION ${CONFIGURATION_FILES_DIR} + COMPONENT base) +endif() diff --git a/src/daemon/dlt-daemon.c b/src/daemon/dlt-daemon.c index 15ac8630a..dcdea2d8e 100644 --- a/src/daemon/dlt-daemon.c +++ b/src/daemon/dlt-daemon.c @@ -89,12 +89,38 @@ \{ */ -static int dlt_daemon_log_internal(DltDaemon *daemon, DltDaemonLocal *daemon_local, char *str, int verbose); +#define DLT_DAEMON_APP_ID "DLTD" +#define DLT_DAEMON_CTX_ID "INTM" + + +static int dlt_daemon_log_internal(DltDaemon *daemon, + DltDaemonLocal *daemon_local, char *str, + DltLogLevelType level, const char *app_id, + const char *ctx_id, int verbose); static int dlt_daemon_check_numeric_setting(char *token, char *value, unsigned long *data); +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +#if defined(DLT_LIB_USE_FIFO_IPC) && !defined(DLT_SHM_ENABLE) +inline static DltReceiver* dlt_daemon_get_fifo_receiver(const DltDaemonLocal *daemon_local); +inline static int dlt_daemon_start_receiving_fifo(DltDaemonLocal *daemon_local); +inline static int dlt_daemon_stop_receiving_fifo(DltDaemonLocal *daemon_local); +#endif + +struct DltTraceLoadLogParams { + DltDaemon *daemon; + DltDaemonLocal *daemon_local; + int verbose; + char *app_id; +}; + +static DltReturnValue dlt_daemon_output_internal_msg(DltLogLevelType loglevel, const char *text, void *params); + +pthread_rwlock_t trace_load_rw_lock; +#endif + /* used in main event loop and signal handler */ int g_exit = 0; @@ -110,6 +136,9 @@ static char dlt_timer_conn_types[DLT_TIMER_UNKNOWN + 1] = { [DLT_TIMER_SYSTEMD] = DLT_CONNECTION_SYSTEMD_TIMER, #endif [DLT_TIMER_GATEWAY] = DLT_CONNECTION_GATEWAY_TIMER, +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + [DLT_TIMER_STAT] = DLT_CONNECTION_STAT_TIMER, +#endif [DLT_TIMER_UNKNOWN] = DLT_CONNECTION_TYPE_MAX }; @@ -120,6 +149,9 @@ static char dlt_timer_names[DLT_TIMER_UNKNOWN + 1][32] = { [DLT_TIMER_SYSTEMD] = "Systemd watchdog", #endif [DLT_TIMER_GATEWAY] = "Gateway", +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + [DLT_TIMER_STAT] = "Statistics", +#endif [DLT_TIMER_UNKNOWN] = "Unknown timer" }; @@ -205,6 +237,10 @@ void usage() printf(" -a filename The filename for load default app id log levels (Default: " CONFIGURATION_FILES_DIR "/dlt-log-levels.conf)\n"); #endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + printf(" -l filename The filename for load limits (Default: " CONFIGURATION_FILES_DIR "/dlt-trace-load.conf)\n"); +#endif + # } /* usage() */ @@ -245,6 +281,9 @@ int option_handling(DltDaemonLocal *daemon_local, int argc, char *argv[]) #endif #ifdef DLT_LOG_LEVEL_APP_CONFIG strcpy(options + strlen(options), "a:"); +#endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + strcpy(options + strlen(options), "l:"); #endif while ((c = getopt(argc, argv, options)) != -1) switch (c) { @@ -265,6 +304,13 @@ int option_handling(DltDaemonLocal *daemon_local, int argc, char *argv[]) break; } #endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + case 'l': + { + strncpy(daemon_local->flags.lvalue, optarg, NAME_MAX); + break; + } +#endif #ifdef DLT_DAEMON_USE_FIFO_IPC case 't': { @@ -296,12 +342,16 @@ int option_handling(DltDaemonLocal *daemon_local, int argc, char *argv[]) usage(); return -2; /* return no error */ } + case '?': { if ((optopt == 'c') || (optopt == 't') || (optopt == 'p') #ifdef DLT_LOG_LEVEL_APP_CONFIG || (optopt == 'a') #endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + || (optopt == 'l') +#endif ) fprintf (stderr, "Option -%c requires an argument.\n", optopt); else if (isprint (optopt)) @@ -425,6 +475,9 @@ int option_file_parser(DltDaemonLocal *daemon_local) #endif daemon_local->flags.ipNodes = NULL; daemon_local->flags.injectionMode = 1; +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + daemon_local->flags.statInterval = 0; +#endif /* open configuration file */ if (daemon_local->flags.cvalue[0]) @@ -851,6 +904,21 @@ int option_file_parser(DltDaemonLocal *daemon_local) else if (strcmp(token, "InjectionMode") == 0) { daemon_local->flags.injectionMode = atoi(value); } +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + else if(strcmp(token,"StatisticsInterval") == 0) + { + int const intval = atoi(value); + if ( intval >= 0) + { + daemon_local->flags.statInterval = intval; + dlt_vlog(LOG_WARNING, "Option: %s=%s\n",token,value); + } + else + { + dlt_vlog(LOG_WARNING, "Invalid value for StatisticsInterval: %i. Must be more >= 0\n", intval); + } + } +#endif else { fprintf(stderr, "Unknown option: %s=%s\n", token, value); } @@ -1034,6 +1102,237 @@ int app_id_default_log_level_config_parser(DltDaemon *daemon, } #endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +static bool is_ascii_only(const char *str) { + while (*str) { + if ((unsigned char)*str > 127) { + return false; + } + str++; + } + return true; +} + +/** + * Load configuration file parser + */ +int trace_load_config_file_parser(DltDaemon *daemon, DltDaemonLocal *daemon_local) +{ + FILE *pFile; + const int max_tokens = 4; + const int min_tokens = 3; + char tokens[max_tokens][value_length]; + char line[value_length - 1]; + char app_id_value[value_length]; + char ctx_id_value[value_length]; + char soft_limit_value[value_length]; + char hard_limit_value[value_length]; + int i; + uint32_t soft_limit; + uint32_t hard_limit; + + char *pch; + const char *filename; + + bool skipped; + + if (daemon->preconfigured_trace_load_settings != NULL) { + free(daemon->preconfigured_trace_load_settings); + daemon->preconfigured_trace_load_settings = NULL; + daemon->preconfigured_trace_load_settings_count = 0; + } + daemon->preconfigured_trace_load_settings = malloc(sizeof(DltTraceLoadSettings)); + + /* open configuration file */ + filename = daemon_local->flags.lvalue[0] + ? daemon_local->flags.lvalue + : CONFIGURATION_FILES_DIR "/dlt-trace-load.conf"; + + pFile = fopen (filename, "r"); + if (pFile == NULL) { + dlt_vlog(LOG_WARNING, "Cannot open trace load configuration file: %s\n", + filename); + return -errno; + } + + while (1) { + /* fetch line from configuration file */ + if (fgets(line, value_length - 1, pFile) == NULL) { + break; + } + + pch = strtok(line, " "); + app_id_value[0] = 0; + ctx_id_value[0] = 0; + soft_limit_value[0] = 0; + hard_limit_value[0] = 0; + soft_limit = 0U; + hard_limit = 0U; + memset(tokens, 0, sizeof(tokens)); + i = 0; + + skipped = false; + while (pch != NULL && i < max_tokens) { + /* ignore empty lines and new lines */ + if (strncmp(pch, "#", 1) == 0 || strncmp(pch, "\n", 1) == 0 || + strncmp(pch, "\r", 1) == 0 || strncmp(pch, " ", 1) == 0) { + skipped = true; + break; + } + strncpy(tokens[i], pch, sizeof(tokens[i]) - 1); + pch = strtok(NULL, " "); + ++i; + } + + if (skipped && i < min_tokens) + continue; + + if (pch != NULL + && (pch[0] != '\n') + && (pch[0] != '\t') + && (pch[0] != ' ') + && (pch[0] != '#')) { + dlt_vlog(LOG_WARNING, + "Invalid trace load settings: too many tokens in line '%s'\n", line); + continue; + } + + bool has_ctx_id = i == max_tokens; + int soft_limit_idx = has_ctx_id ? 2 : 1; + int hard_limit_idx = has_ctx_id ? 3 : 2; + + strncpy(app_id_value, tokens[0], sizeof(app_id_value) - 1); + if ((strlen(app_id_value) == 0) + || (strlen(app_id_value) > DLT_ID_SIZE) + || (!is_ascii_only(app_id_value))) { + dlt_vlog(LOG_WARNING, + "Invalid apid for trace load settings: app id: '%s'\n", app_id_value); + continue; + } + + if (has_ctx_id) { + strncpy(ctx_id_value, tokens[1], sizeof(ctx_id_value) - 1); + if ((strlen(ctx_id_value) == 0) + || (strlen(ctx_id_value) > DLT_ID_SIZE) + || (!is_ascii_only(ctx_id_value))) { + dlt_vlog(LOG_WARNING, + "Invalid ctid for trace load settings: context id: '%s'\n", ctx_id_value); + continue; + } + } + + if (strlen(tokens[soft_limit_idx]) == 0) { + dlt_vlog(LOG_WARNING, + "Invalid soft_limit for trace load settings: app id: '%.4s', '%s'\n", + app_id_value, tokens[soft_limit_idx]); + continue; + } + + if (strlen(tokens[hard_limit_idx]) == 0) { + dlt_vlog(LOG_WARNING, + "Invalid hard_limit for trace load settings: app id: '%.4s', '%s'\n", + app_id_value, tokens[hard_limit_idx]); + continue; + } + + strncpy(soft_limit_value, tokens[soft_limit_idx], + sizeof(soft_limit_value) - 1); + strncpy(hard_limit_value, tokens[hard_limit_idx], + sizeof(hard_limit_value) - 1); + + errno = 0; + char *endptr; + endptr = NULL; + soft_limit = strtoul(soft_limit_value, &endptr, 10); + if ((errno != 0) + || ((soft_limit == 0) && (soft_limit_value[0] != '0')) + || (soft_limit_value[0] == '-') + || ((*endptr != '\n') && (*endptr != '\0'))) { + dlt_vlog(LOG_WARNING, + "Invalid soft_limit for trace load settings: app id: '%.4s', soft_limit '%s'\n", + app_id_value, soft_limit_value); + continue; + } + + errno = 0; + endptr = NULL; + hard_limit = strtoul(hard_limit_value, &endptr, 10); + if ((errno != 0) + || ((hard_limit == 0) && (hard_limit_value[0] != '0')) + || (hard_limit_value[0] == '-') + || ((*endptr != '\n') && (*endptr != '\0'))) { + dlt_vlog(LOG_WARNING, + "Invalid hard_limit for trace load settings: app id: '%.4s', hard_limit '%s'\n", + app_id_value, hard_limit_value); + continue; + } + + if (soft_limit > hard_limit) { + dlt_vlog(LOG_WARNING, + "Invalid trace load settings: app id: '%.4s', soft limit %u is greater than hard limit %u\n", + app_id_value, soft_limit, hard_limit); + continue; + } + + DltTraceLoadSettings *settings = NULL; + int num_settings = 0; + DltReturnValue rv = dlt_daemon_find_preconfigured_trace_load_settings( + daemon, app_id_value, ctx_id_value, &settings, &num_settings, + 0); + if (rv != DLT_RETURN_OK || num_settings != 0) { + dlt_vlog(LOG_WARNING, + "App id '%.4s' is already configured, or an error occurred, skipping entry\n", + app_id_value); + if (settings != NULL) { + free(settings); + } + continue; + } + + /* allocate one more element in the trace load settings */ + DltTraceLoadSettings *tmp = + realloc(daemon->preconfigured_trace_load_settings, + (++daemon->preconfigured_trace_load_settings_count) * + sizeof(DltTraceLoadSettings)); + + if (tmp == NULL) { + dlt_log(LOG_CRIT, + "Failed to allocate memory for trace load settings\n"); + return DLT_RETURN_ERROR; + } + + daemon->preconfigured_trace_load_settings = tmp; + settings = &daemon->preconfigured_trace_load_settings + [daemon->preconfigured_trace_load_settings_count - 1]; + memset(settings, 0, sizeof(DltTraceLoadSettings)); + settings->soft_limit = soft_limit; + settings->hard_limit = hard_limit; + + memcpy(settings->apid, app_id_value, DLT_ID_SIZE); + if (has_ctx_id) { + memcpy(settings->ctid, ctx_id_value, DLT_ID_SIZE); + dlt_vlog(LOG_INFO, + "Configured trace limits for app id '%.4s', ctx id '%.4s', soft limit: %u, hard_limit: %u\n", + app_id_value, ctx_id_value, soft_limit, hard_limit); + } else { + dlt_vlog(LOG_INFO, + "Configured trace limits for app id '%.4s', soft limit: %u, hard_limit: %u\n", + app_id_value, soft_limit, hard_limit); + } + + + + } /* while */ + fclose(pFile); + + // sort limits to improve search performance + qsort(daemon->preconfigured_trace_load_settings, daemon->preconfigured_trace_load_settings_count, + sizeof(DltTraceLoadSettings), + dlt_daemon_compare_trace_load_settings); + return 0; +} +#endif + static int dlt_mkdir_recursive(const char *dir) { int ret = 0; @@ -1122,7 +1421,9 @@ static DltReturnValue dlt_daemon_create_pipes_dir(char *dir) return ret; } #endif - +// This will be defined when unit testing, so functions +// from this file can be tested without defining main twice +#ifndef DLT_DAEMON_UNIT_TESTS_NO_MAIN /** * Main function of tool. */ @@ -1250,6 +1551,14 @@ int main(int argc, char *argv[]) } #endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + /* Load control trace load configuration file without setting `back` to prevent exit if file is missing */ + pthread_rwlock_init(&trace_load_rw_lock, NULL); + if (trace_load_config_file_parser(&daemon, &daemon_local) < 0) { + dlt_vlog(LOG_WARNING, "trace_load_config_file_parser() failed, using defaults for all app ids!\n"); + } +#endif + /* --- Daemon init phase 2 end --- */ if (daemon_local.flags.offlineLogstorageDirPath[0]) @@ -1302,6 +1611,11 @@ int main(int argc, char *argv[]) DLT_TIMER_GATEWAY); } +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + /* create fd for timer statistics */ + create_timer_fd(&daemon_local, daemon_local.flags.statInterval, daemon_local.flags.statInterval, DLT_TIMER_STAT); +#endif + /* For offline tracing we still can use the same states */ /* as for socket sending. Using this trick we see the traces */ /* In the offline trace AND in the socket stream. */ @@ -1325,9 +1639,9 @@ int main(int argc, char *argv[]) daemon_local.flags.vflag) == 0)) daemon.runtime_context_cfg_loaded = 1; - dlt_daemon_log_internal(&daemon, - &daemon_local, + dlt_daemon_log_internal(&daemon, &daemon_local, "Daemon launched. Starting to output traces...", + DLT_LOG_INFO, DLT_DAEMON_APP_ID, DLT_DAEMON_CTX_ID, daemon_local.flags.vflag); /* Even handling loop. */ @@ -1338,7 +1652,8 @@ int main(int argc, char *argv[]) snprintf(local_str, DLT_DAEMON_TEXTBUFSIZE, "Exiting DLT daemon... [%d]", g_signo); - dlt_daemon_log_internal(&daemon, &daemon_local, local_str, + dlt_daemon_log_internal(&daemon, &daemon_local, local_str, DLT_LOG_INFO, + DLT_DAEMON_APP_ID, DLT_DAEMON_CTX_ID, daemon_local.flags.vflag); dlt_vlog(LOG_NOTICE, "%s%s", local_str, "\n"); @@ -1359,6 +1674,7 @@ int main(int argc, char *argv[]) return 0; } /* main() */ +#endif int dlt_daemon_local_init_p1(DltDaemon *daemon, DltDaemonLocal *daemon_local, int verbose) { @@ -2260,7 +2576,9 @@ void dlt_daemon_daemonize(int verbose) * would cause an endless loop because dlt_daemon_log_internal() would itself again try * to open the offline trace file. * This is a dlt-daemon only function. The libdlt has no equivalent function available. */ -int dlt_daemon_log_internal(DltDaemon *daemon, DltDaemonLocal *daemon_local, char *str, int verbose) +int dlt_daemon_log_internal(DltDaemon *daemon, DltDaemonLocal *daemon_local, + char *str, DltLogLevelType level, + const char *app_id, const char *ctx_id, int verbose) { DltMessage msg = { 0 }; static uint8_t uiMsgCount = 0; @@ -2438,7 +2756,8 @@ int dlt_daemon_process_client_connect(DltDaemon *daemon, "New client connection #%d established, Total Clients : %d", in_sock, daemon_local->client_connections); - dlt_daemon_log_internal(daemon, daemon_local, local_str, + dlt_daemon_log_internal(daemon, daemon_local, local_str, DLT_LOG_INFO, + DLT_DAEMON_APP_ID, DLT_DAEMON_CTX_ID, daemon_local->flags.vflag); dlt_vlog(LOG_DEBUG, "%s%s", local_str, "\n"); @@ -2458,6 +2777,11 @@ int dlt_daemon_process_client_connect(DltDaemon *daemon, /* send new log state to all applications */ daemon->connectionState = 1; dlt_daemon_user_send_all_log_state(daemon, verbose); + +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + /* Reset number of received bytes from FIFO */ + daemon->bytes_recv = 0; +#endif } return 0; @@ -2849,6 +3173,14 @@ int dlt_daemon_process_user_messages(DltDaemon *daemon, return -1; } +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + /* Count up number of received bytes from FIFO */ + if (receiver->bytesRcvd > receiver->lastBytesRcvd) + { + daemon->bytes_recv += receiver->bytesRcvd - receiver->lastBytesRcvd; + } +#endif + /* look through buffer as long as data is in there */ while ((receiver->bytesRcvd >= min_size) && run_loop) { dlt_daemon_process_user_message_func func = NULL; @@ -3065,13 +3397,18 @@ int dlt_daemon_process_user_message_register_application(DltDaemon *daemon, application->apid, application->pid, application->application_description); - dlt_daemon_log_internal(daemon, - daemon_local, - local_str, + dlt_daemon_log_internal(daemon, daemon_local, local_str, DLT_LOG_INFO, + DLT_DAEMON_APP_ID, DLT_DAEMON_CTX_ID, daemon_local->flags.vflag); dlt_vlog(LOG_DEBUG, "%s%s", local_str, "\n"); } +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + if (dlt_daemon_user_send_trace_load_config(daemon, application, verbose) != DLT_RETURN_OK) + dlt_vlog(LOG_WARNING, "Cannot send trace config to Apid: %.4s, PID: %d\n", + application->apid, application->pid); +#endif + return 0; } @@ -3214,7 +3551,9 @@ int dlt_daemon_process_user_message_register_context(DltDaemon *daemon, context->context_description); if (verbose) - dlt_daemon_log_internal(daemon, daemon_local, local_str, verbose); + dlt_daemon_log_internal(daemon, daemon_local, local_str, + DLT_LOG_INFO, DLT_DAEMON_APP_ID, + DLT_DAEMON_CTX_ID, verbose); dlt_vlog(LOG_DEBUG, "%s%s", local_str, "\n"); } @@ -3369,10 +3708,9 @@ int dlt_daemon_process_user_message_unregister_application(DltDaemon *daemon, DLT_DAEMON_TEXTBUFSIZE, "Unregistered ApID '%.4s'", userapp.apid); - dlt_daemon_log_internal(daemon, - daemon_local, - local_str, - verbose); + dlt_daemon_log_internal(daemon, daemon_local, local_str, + DLT_LOG_INFO, DLT_DAEMON_APP_ID, + DLT_DAEMON_CTX_ID, verbose); dlt_vlog(LOG_DEBUG, "%s%s", local_str, "\n"); } } @@ -3437,10 +3775,9 @@ int dlt_daemon_process_user_message_unregister_context(DltDaemon *daemon, userctxt.apid); if (verbose) - dlt_daemon_log_internal(daemon, - daemon_local, - local_str, - verbose); + dlt_daemon_log_internal(daemon, daemon_local, local_str, + DLT_LOG_INFO, DLT_DAEMON_APP_ID, + DLT_DAEMON_CTX_ID, verbose); dlt_vlog(LOG_DEBUG, "%s%s", local_str, "\n"); } @@ -3519,7 +3856,7 @@ int dlt_daemon_process_user_message_log(DltDaemon *daemon, return DLT_DAEMON_ERROR_UNKNOWN; } -#ifdef DLT_LOG_LEVEL_APP_CONFIG +#if defined(DLT_LOG_LEVEL_APP_CONFIG) || defined(DLT_TRACE_LOAD_CTRL_ENABLE) DltDaemonApplication *app = dlt_daemon_application_find( daemon, daemon_local->msg.extendedheader->apid, daemon->ecuid, verbose); #endif @@ -3532,6 +3869,11 @@ int dlt_daemon_process_user_message_log(DltDaemon *daemon, #endif ); + // check trace_load +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + keep_message &= trace_load_keep_message(app, size, daemon, daemon_local, verbose); +#endif + if (keep_message) dlt_daemon_client_send_message_to_all_client(daemon, daemon_local, verbose); @@ -3571,7 +3913,7 @@ int dlt_daemon_process_user_message_log(DltDaemon *daemon, return DLT_DAEMON_ERROR_UNKNOWN; } -#ifdef DLT_LOG_LEVEL_APP_CONFIG +#if defined(DLT_LOG_LEVEL_APP_CONFIG) || defined(DLT_TRACE_LOAD_CTRL_ENABLE) DltDaemonApplication *app = dlt_daemon_application_find( daemon, daemon_local->msg.extendedheader->apid, daemon->ecuid, verbose); #endif @@ -3584,6 +3926,12 @@ int dlt_daemon_process_user_message_log(DltDaemon *daemon, #endif ); + // check trace_load +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + keep_message &= + trace_load_keep_message(app, size, daemon, daemon_local, verbose); +#endif + if (keep_message) dlt_daemon_client_send_message_to_all_client(daemon, daemon_local, verbose); @@ -3611,7 +3959,8 @@ bool enforce_context_ll_and_ts_keep_message(DltDaemonLocal *daemon_local #endif ) { - if (!daemon_local->flags.enforceContextLLAndTS || + if (app == NULL || + !daemon_local->flags.enforceContextLLAndTS || !daemon_local->msg.extendedheader) { return true; } @@ -3630,6 +3979,51 @@ bool enforce_context_ll_and_ts_keep_message(DltDaemonLocal *daemon_local return mtin <= daemon_local->flags.contextLogLevel; } + +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +bool trace_load_keep_message(DltDaemonApplication *app, + const int size, DltDaemon *const daemon, + DltDaemonLocal *const daemon_local, + int verbose) +{ + bool keep_message = true; + if (app == NULL || !daemon_local->msg.extendedheader) { + return keep_message; + } + + DltMessage* msg = &daemon_local->msg; + const int mtin = DLT_GET_MSIN_MTIN(msg->extendedheader->msin); + + struct DltTraceLoadLogParams params = { + daemon, + daemon_local, + verbose, + app->apid, + }; + + DltTraceLoadSettings *trace_load_settings = + dlt_find_runtime_trace_load_settings( + app->trace_load_settings, app->trace_load_settings_count, + app->apid, msg->extendedheader->ctid); + + if (trace_load_settings != NULL) { + keep_message = dlt_check_trace_load( + trace_load_settings, mtin, msg->headerextra.tmsp, size, + dlt_daemon_output_internal_msg, (void *)(¶ms)); + } + else { + dlt_vlog( + LOG_ERR, + "Failed to lookup trace load limits for %s, " + "dropping message, likely app was not registered properly\n", + app->apid); + keep_message = false; + } + + return keep_message; +} +#endif + int dlt_daemon_process_user_message_set_app_ll_ts(DltDaemon *daemon, DltDaemonLocal *daemon_local, DltReceiver *rec, @@ -3786,6 +4180,12 @@ int dlt_daemon_send_ringbuffer_to_client(DltDaemon *daemon, DltDaemonLocal *daem } if (dlt_buffer_get_message_count(&(daemon->client_ringbuffer)) <= 0) { +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +#if defined(DLT_DAEMON_USE_FIFO_IPC) && !defined(DLT_SHM_ENABLE) + dlt_daemon_start_receiving_fifo(daemon_local); + length = 0; +#endif +#endif dlt_daemon_change_state(daemon, DLT_DAEMON_STATE_SEND_DIRECT); return DLT_DAEMON_ERROR_OK; } @@ -3806,11 +4206,34 @@ int dlt_daemon_send_ringbuffer_to_client(DltDaemon *daemon, DltDaemonLocal *daem dlt_daemon_change_state(daemon, DLT_DAEMON_STATE_SEND_BUFFER); if (dlt_buffer_get_message_count(&(daemon->client_ringbuffer)) <= 0) { +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +#if defined(DLT_LIB_USE_FIFO_IPC) && !defined(DLT_SHM_ENABLE) + /* Restart receiving data from FIFO */ + dlt_daemon_start_receiving_fifo(daemon_local); +#endif +#endif dlt_daemon_change_state(daemon, DLT_DAEMON_STATE_SEND_DIRECT); return DLT_DAEMON_ERROR_OK; } } +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +#if defined(DLT_LIB_USE_FIFO_IPC) && !defined(DLT_SHM_ENABLE) + /* Stop receiving data from FIFO + * if Transfer speed is slower than FIFO receiving speed + * This prevents buffer overflow during SEND BUFFER state + */ + if (daemon->bytes_recv > daemon->bytes_sent) + { + dlt_daemon_stop_receiving_fifo(daemon_local); + } + else + { + dlt_daemon_start_receiving_fifo(daemon_local); + } +#endif +#endif + return DLT_DAEMON_ERROR_OK; } @@ -3989,12 +4412,152 @@ int dlt_daemon_close_socket(int sock, DltDaemon *daemon, DltDaemonLocal *daemon_ "Client connection #%d closed. Total Clients : %d", sock, daemon_local->client_connections); - dlt_daemon_log_internal(daemon, daemon_local, local_str, daemon_local->flags.vflag); + dlt_daemon_log_internal(daemon, daemon_local, local_str, DLT_LOG_INFO, + DLT_DAEMON_APP_ID, DLT_DAEMON_CTX_ID, + daemon_local->flags.vflag); dlt_vlog(LOG_DEBUG, "%s%s", local_str, "\n"); return 0; } +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + +static DltReturnValue dlt_daemon_output_internal_msg( + const DltLogLevelType loglevel, const char *const text, void* const params) { + struct DltTraceLoadLogParams* log_params = (struct DltTraceLoadLogParams*)params; + return dlt_daemon_log_internal( + log_params->daemon, log_params->daemon_local, (char *)text, loglevel, + log_params->app_id, DLT_INTERNAL_CONTEXT_ID, log_params->verbose); +} + +#if defined(DLT_LIB_USE_FIFO_IPC) && !defined(DLT_SHM_ENABLE) +inline static DltReceiver* dlt_daemon_get_fifo_receiver(const DltDaemonLocal *const daemon_local) +{ + static DltReceiver* fifo_receiver = NULL; + DltConnection* temp = NULL; + + /* Find FIFO receiver is done only once */ + if (fifo_receiver != NULL ) + { + return fifo_receiver; + } + + if (daemon_local == NULL) + { + dlt_log(LOG_ERR, "Failed to get FIFO receiver: Invalid parameter\n"); + } + else + { + temp = daemon_local->pEvent.connections; + temp = dlt_connection_get_next(temp, DLT_CON_MASK_APP_MSG); + if (temp != NULL && temp->receiver != NULL) + { + fifo_receiver = temp->receiver; + } + else + { + dlt_log(LOG_ERR, "Failed to get FIFO receiver: Not found\n"); + } + } + + return fifo_receiver; +} + +inline static int dlt_daemon_start_receiving_fifo(DltDaemonLocal *const daemon_local) +{ + DltReceiver* fifo_receiver = dlt_daemon_get_fifo_receiver(daemon_local); + if (dlt_connection_create(daemon_local, + &daemon_local->pEvent, + fifo_receiver->fd, + EPOLLIN, + DLT_CONNECTION_APP_MSG)) + { + dlt_log(LOG_ERR, "Failed to start receiving FIFO\n"); + return -1; + } + + return 0; +} + +inline static int dlt_daemon_stop_receiving_fifo(DltDaemonLocal *const daemon_local) +{ + DltReceiver* fifo_receiver = dlt_daemon_get_fifo_receiver(daemon_local); + if (dlt_connection_create(daemon_local, + &daemon_local->pEvent, + fifo_receiver->fd, + 0, + DLT_CONNECTION_APP_MSG)) + { + dlt_log(LOG_ERR, "Failed to stop receiving FIFO\n"); + return -1; + } + + return 0; +} + +int dlt_daemon_process_statistics_timer(DltDaemon *const daemon, + DltDaemonLocal *const daemon_local, + DltReceiver *const receiver, + int verbose) +{ + char local_str[DLT_DAEMON_TEXTBUFSIZE]; + static uint32_t prev_stat_time = 0; + static const char *stateToStr[] + = {"Init", "Buffer", "Buffer Full", "Send Buffer", "Send Direct", "Log Mode Off"}; + + PRINT_FUNCTION_VERBOSE(verbose); + + if((daemon_local == NULL) || (daemon == NULL) || (receiver == NULL)) + { + dlt_vlog(LOG_ERR, "%s: invalid parameters\n", __func__); + return -1; + } + + uint64_t expir; + const ssize_t res = read(receiver->fd, &expir, sizeof(expir)); + if(res < 0) + { + dlt_vlog(LOG_WARNING, "%s: Fail to read timer (%s)\n", __func__, strerror(errno)); + /* Activity received on timer_wd, but unable to read the fd: + let's go on sending notification */ + } + + uint32_t curr_time = dlt_uptime(); + if (curr_time == prev_stat_time) + { + /* This case should never happen */ + return 0; + } + + /* Calculate transfer speed and FIFO receiving speed */ + const uint32_t diff_time = curr_time - prev_stat_time; + const int32_t transfer_speed = ((int64_t)daemon->bytes_sent * DLT_UPTIME_RESOLUTION_Of_1SEC) / diff_time; + const int32_t fifo_speed = ((int64_t)daemon->bytes_recv * DLT_UPTIME_RESOLUTION_Of_1SEC) / diff_time; + + snprintf(local_str, DLT_DAEMON_TEXTBUFSIZE, + "Transfer speed: %d bytes/sec, " + "FIFO speed: %d bytes/sec, " + "Total Clients: %d, " + "Default LL/TS: %d/%d, " + "State: %s, ", + transfer_speed, + fifo_speed, + daemon_local->client_connections, + daemon->default_log_level, + daemon->default_trace_status, + stateToStr[daemon->state]); + dlt_daemon_log_internal( + daemon, daemon_local, local_str, verbose, DLT_LOG_INFO, DLT_DAEMON_APP_ID, DLT_DAEMON_CTX_ID); + daemon->bytes_sent = 0; + daemon->bytes_recv = 0; + prev_stat_time = curr_time; + + return 0; +} +#endif +#endif + + /** \} */ diff --git a/src/daemon/dlt-daemon.h b/src/daemon/dlt-daemon.h index a734802f4..4a7107bbf 100644 --- a/src/daemon/dlt-daemon.h +++ b/src/daemon/dlt-daemon.h @@ -101,6 +101,9 @@ typedef struct char cvalue[NAME_MAX + 1]; /**< (String: Directory) Filename of DLT configuration file (Default: /etc/dlt.conf) */ #ifdef DLT_LOG_LEVEL_APP_CONFIG char avalue[NAME_MAX + 1]; /**< (String: Directory) Filename of the app id default level config (Default: /etc/dlt-log-levels.conf) */ +#endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + char lvalue[NAME_MAX + 1]; /**< (String: Directory) Filename of DLT trace limit file (Default: /etc/dlt-trace_load.conf) */ #endif int sharedMemorySize; /**< (int) Size of shared memory (Default: 100000) */ int sendMessageTime; /**< (Boolean) Send periodic Message Time if client is connected (Default: 0) */ @@ -146,6 +149,9 @@ typedef struct int enforceContextLLAndTS; /**< (Boolean) Enforce log-level, trace-status not to exceed contextLogLevel, contextTraceStatus */ DltBindAddress_t* ipNodes; /**< (String: BindAddress) The daemon accepts connections only on this list of IP addresses */ int injectionMode; /**< (Boolean) Injection mode */ +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + int statInterval; /**< (int) Statistics interval of transfer speed outputs (sec) (default: 0 - not output) */ +#endif } DltDaemonFlags; /** * The global parameters of a dlt daemon. @@ -286,4 +292,15 @@ int create_timer_fd(DltDaemonLocal *daemon_local, int period_sec, int starts_in, int dlt_daemon_close_socket(int sock, DltDaemon *daemon, DltDaemonLocal *daemon_local, int verbose); +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +bool trace_load_keep_message( + DltDaemonApplication *app, int size, DltDaemon *daemon, DltDaemonLocal *daemon_local, int verbose); +int dlt_daemon_process_statistics_timer(DltDaemon *daemon, DltDaemonLocal *daemon_local, DltReceiver *receiver, int verbose); +// Functions that are only exposed for testing and should not be public +// for normal builds +#ifdef DLT_UNIT_TESTS +int trace_load_config_file_parser(DltDaemon *daemon, DltDaemonLocal *daemon_local); +#endif +#endif + #endif /* DLT_DAEMON_H */ diff --git a/src/daemon/dlt-trace-load.conf b/src/daemon/dlt-trace-load.conf new file mode 100644 index 000000000..0834b98f4 --- /dev/null +++ b/src/daemon/dlt-trace-load.conf @@ -0,0 +1,39 @@ +# Configuration file for DLT daemon trace load settings +# This file allows configuration of trace load limits +# If no limit is set here the defaults will be used. +# They are configured via the two defines below in the source code. +# TRACE_LOAD_DAEMON_SOFT_LIMIT +# TRACE_LOAD_DAEMON_HARD_LIMIT +# +# APPID: The application id to limit +# CTXID: The optional context id to limit, if the context id is not given, the limit is applied to all contexts of the application +# Therefore the best match is used, a context can override the limit of the application, as each line will be +# treated as separate quota. +# SOFT_LIMIT: The warning limit, if more data than this is logged, a warning is written into dlt +# HARD_LIMIT: If an application surpasses this limit, data will be discarded and a warning will be logged! +# SOFT_LIMIT and HARD_LIMIT are in byte/s +# Warnings will be issues in the interval configured via DLT_USER_HARD_LIMIT_OVER_MSG_INTERVAL +# the default for this is 1s +# +# !!!! +# Note: this file is space separated, and wildcards are not supported +# !!!! +# +# APPID [CTXID] SOFT_LIMIT HARD_LIMIT + +# Allow 100000 bytes for all contexts on JOUR +SYS 83333 100000 + +# Allow QSYM to log 100000 bytes, but only on context QSLA +QSYM QSLA 83333 100000 + +# Allow total 5555 bytes for all contexts on TEST +# But only 100 bytes for context FOO +TEST 2222 5555 +TEST FOO 42 100 + +# BAR BAR gets 84 bytes +# Every other context in BAR gets 42 bytes +BAR 42 42 +BAR BAR 84 84 + diff --git a/src/daemon/dlt_daemon_client.c b/src/daemon/dlt_daemon_client.c index 6405da2d2..5b6fd5bcc 100644 --- a/src/daemon/dlt_daemon_client.c +++ b/src/daemon/dlt_daemon_client.c @@ -158,6 +158,14 @@ static int dlt_daemon_client_send_all_multiple(DltDaemon *daemon, sent = 1; } /* for */ +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + if (sent) + { + const uint32_t serial_header = daemon->sendserialheader ? sizeof(dltSerialHeader) : 0; + daemon->bytes_sent += size1 + size2 + serial_header; + } +#endif + return sent; } diff --git a/src/daemon/dlt_daemon_common.c b/src/daemon/dlt_daemon_common.c index 7077d4b68..1a59b6cbc 100644 --- a/src/daemon/dlt_daemon_common.c +++ b/src/daemon/dlt_daemon_common.c @@ -231,6 +231,74 @@ DltDaemonContextLogSettings *dlt_daemon_find_app_log_level_config( #endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +int dlt_daemon_compare_trace_load_settings(const void *a, const void *b) { + const DltTraceLoadSettings *s1 = (const DltTraceLoadSettings *)a; + const DltTraceLoadSettings *s2 = (const DltTraceLoadSettings *)b; + + int cmp = strncmp(s1->apid, s2->apid, DLT_ID_SIZE); + if (cmp != 0) { + return cmp; + } + + return strncmp(s1->ctid, s2->ctid, DLT_ID_SIZE); +} + +DltReturnValue dlt_daemon_find_preconfigured_trace_load_settings( + DltDaemon *const daemon, const char *apid, const char *ctid, DltTraceLoadSettings **settings, int *num_settings, int verbose) +{ + PRINT_FUNCTION_VERBOSE(verbose); + int i; + *num_settings = 0; + *settings = NULL; + + if ((daemon == NULL) || (apid == NULL)) { + dlt_vlog(LOG_ERR, "%s: Wrong parameters", __func__); + return DLT_RETURN_WRONG_PARAMETER; + } + + if (NULL == daemon->preconfigured_trace_load_settings || daemon->preconfigured_trace_load_settings_count == 0) { + return DLT_RETURN_OK; + } + + for (i = 0; i < daemon->preconfigured_trace_load_settings_count; ++i) { + // check if we can exit already, the trace load settings are sorted + // and if the apid does not match anymore, but we already have settings + // means we collected all settings + if (strncmp(apid, daemon->preconfigured_trace_load_settings[i].apid, DLT_ID_SIZE) != 0) { + if ((*num_settings) != 0) + break; + continue; + } + + // If a ctid is passed, we only want to return entries where both match + if (ctid != NULL && strlen(ctid) > 0) { + if (strncmp(ctid, daemon->preconfigured_trace_load_settings[i].ctid, DLT_ID_SIZE) != 0) { + continue; + } + } + + // Reallocate memory for the settings array with an additional slot for the new setting + DltTraceLoadSettings *temp = realloc(*settings, (*num_settings + 1) * sizeof(DltTraceLoadSettings)); + if (temp == NULL) { + dlt_vlog(LOG_ERR, "Failed to allocate memory for trace load settings\n"); + free(*settings); // Free any previously allocated memory + *settings = NULL; + *num_settings = 0; + return DLT_RETURN_ERROR; + } + *settings = temp; + // Copy preconfigured trace load settings into the app settings + (*settings)[*num_settings] = daemon->preconfigured_trace_load_settings[i]; + (*num_settings)++; + } + + qsort(*settings, (size_t)*num_settings, sizeof(DltTraceLoadSettings), + dlt_daemon_compare_trace_load_settings); + return DLT_RETURN_OK; +} +#endif + int dlt_daemon_init_runtime_configuration(DltDaemon *daemon, const char *runtime_directory, int verbose) { PRINT_FUNCTION_VERBOSE(verbose); @@ -318,6 +386,12 @@ int dlt_daemon_init(DltDaemon *daemon, daemon->state = DLT_DAEMON_STATE_INIT; /* initial logging state */ +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + daemon->preconfigured_trace_load_settings = NULL; + daemon->bytes_sent = 0; + daemon->bytes_recv = 0; +#endif + daemon->sendserialheader = 0; daemon->timingpackets = 0; @@ -368,6 +442,13 @@ int dlt_daemon_free(DltDaemon *daemon, int verbose) free(daemon->app_id_log_level_settings); } #endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + if (daemon->preconfigured_trace_load_settings != NULL) { + free(daemon->preconfigured_trace_load_settings); + daemon->preconfigured_trace_load_settings = NULL; + } + pthread_rwlock_destroy(&trace_load_rw_lock); +#endif if (app_recv_buffer) free(app_recv_buffer); @@ -472,7 +553,13 @@ int dlt_daemon_applications_clear(DltDaemon *daemon, char *ecu, int verbose) if (user_list->applications[i].context_log_level_settings) free(user_list->applications[i].context_log_level_settings); #endif - +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + if (user_list->applications[i].trace_load_settings) { + free(user_list->applications[i].trace_load_settings); + user_list->applications[i].trace_load_settings = NULL; + user_list->applications[i].trace_load_settings_count = 0; + } +#endif free(user_list->applications[i].application_description); user_list->applications[i].application_description = NULL; } @@ -586,6 +673,10 @@ DltDaemonApplication *dlt_daemon_application_add(DltDaemon *daemon, application->num_contexts = 0; application->user_handle = DLT_FD_INIT; application->owns_user_handle = false; +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + application->trace_load_settings = NULL; + application->trace_load_settings_count = 0; +#endif new_application = 1; @@ -676,6 +767,60 @@ DltDaemonApplication *dlt_daemon_application_add(DltDaemon *daemon, #ifdef DLT_LOG_LEVEL_APP_CONFIG application->num_context_log_level_settings = 0; application->context_log_level_settings = NULL; +#endif +#if DLT_TRACE_LOAD_CTRL_ENABLE + if (application->trace_load_settings == NULL) { + DltTraceLoadSettings* pre_configured_trace_load_settings = NULL; + int num_settings = 0; + DltReturnValue rv = dlt_daemon_find_preconfigured_trace_load_settings( + daemon, + application->apid, + NULL /*load settings for all contexts*/, + &pre_configured_trace_load_settings, + &num_settings, + verbose); + + DltTraceLoadSettings *app_level = NULL; + if ((rv == DLT_RETURN_OK) && + (pre_configured_trace_load_settings != NULL) && + (num_settings != 0)) { + application->trace_load_settings = pre_configured_trace_load_settings; + application->trace_load_settings_count = num_settings; + app_level = dlt_find_runtime_trace_load_settings( + application->trace_load_settings, + application->trace_load_settings_count, application->apid, + NULL); + } + + // app is not configured, set daemon defaults + if (app_level == NULL) { + DltTraceLoadSettings *temp = realloc(application->trace_load_settings, + (application->trace_load_settings_count + 1) * + sizeof(DltTraceLoadSettings)); + + if (temp != NULL) { + application->trace_load_settings = temp; + ++application->trace_load_settings_count; + + app_level = &application->trace_load_settings[application->trace_load_settings_count - 1]; + memset(app_level, 0, sizeof(DltTraceLoadSettings)); + app_level[0].hard_limit = DLT_TRACE_LOAD_DAEMON_HARD_LIMIT_DEFAULT; + app_level[0].soft_limit = DLT_TRACE_LOAD_DAEMON_SOFT_LIMIT_DEFAULT; + memcpy(&app_level[0].apid, apid, DLT_ID_SIZE); + memset(&app_level[0].tl_stat, 0, sizeof(DltTraceLoadStat)); + } else { + dlt_vlog(DLT_LOG_FATAL, "Failed to allocate memory for trace load settings\n"); + } + + // We inserted the application id at the end, to make sure + // Lookups are working properly later on, we have to sort the list again. + qsort(application->trace_load_settings, + (size_t)application->trace_load_settings_count, + sizeof(DltTraceLoadSettings), + dlt_daemon_compare_trace_load_settings); + } + } + #endif return application; @@ -708,6 +853,13 @@ int dlt_daemon_application_del(DltDaemon *daemon, application->application_description = NULL; } +#if DLT_TRACE_LOAD_CTRL_ENABLE + if (application->trace_load_settings != NULL) { + free(application->trace_load_settings); + application->trace_load_settings = NULL; + application->trace_load_settings_count = 0; + } +#endif pos = (int) (application - (user_list->applications)); /* move all applications above pos to pos */ @@ -1859,3 +2011,73 @@ void dlt_daemon_trigger_systemd_watchdog_if_necessary(unsigned int* last_trigger } } #endif + +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +int dlt_daemon_user_send_trace_load_config(DltDaemon *const daemon, DltDaemonApplication *app, const int verbose) +{ + DltUserHeader userheader; + DltUserControlMsgTraceSettingMsg* trace_load_settings_user_msg; + uint32_t trace_load_settings_count; + DltReturnValue ret; + + + PRINT_FUNCTION_VERBOSE(verbose); + + if ((daemon == NULL) || (app == NULL)) return -1; + + if (dlt_user_set_userheader(&userheader, DLT_USER_MESSAGE_TRACE_LOAD) < DLT_RETURN_OK) return -1; + + DltTraceLoadSettings* app_settings = app->trace_load_settings; + + if (app_settings != NULL) { + trace_load_settings_count = app->trace_load_settings_count; + trace_load_settings_user_msg = malloc(sizeof(DltUserControlMsgTraceSettingMsg) * trace_load_settings_count); + for (uint32_t i = 0U; i < trace_load_settings_count; i++) { + // App id is not transmitted as the user library only + // has one application ID + memcpy(trace_load_settings_user_msg[i].ctid, app_settings[i].ctid, DLT_ID_SIZE); + trace_load_settings_user_msg[i].soft_limit = app_settings[i].soft_limit; + trace_load_settings_user_msg[i].hard_limit = app_settings[i].hard_limit; + + if (app_settings[i].ctid[0] == '\0') { + dlt_vlog(LOG_NOTICE, "Sending trace load config to app %.4s, soft limit %u, hard limit %u\n", + app->apid, + app_settings[i].soft_limit, + app_settings[i].hard_limit); + } else { + dlt_vlog(LOG_NOTICE, "Sending trace load config to app %.4s, ctid %.4s, soft limit %u, hard limit %u\n", + app->apid, + app_settings[i].ctid, + app_settings[i].soft_limit, + app_settings[i].hard_limit); + } + + } + } + else { + dlt_vlog(LOG_INFO, + "No trace load settings for application %s, setting daemon defaults.\n", app->apid); + + trace_load_settings_count = 1; + trace_load_settings_user_msg = malloc(sizeof(DltUserControlMsgTraceSettingMsg)); + + memset(trace_load_settings_user_msg, 0, sizeof(DltTraceLoadSettings)); + trace_load_settings_user_msg[0].soft_limit = DLT_TRACE_LOAD_DAEMON_SOFT_LIMIT_DEFAULT; + trace_load_settings_user_msg[0].hard_limit = DLT_TRACE_LOAD_DAEMON_HARD_LIMIT_DEFAULT; + } + + /* log to FIFO */ + ret = dlt_user_log_out3_with_timeout(app->user_handle, + &(userheader), sizeof(DltUserHeader), + &(trace_load_settings_count), sizeof(uint32_t), + trace_load_settings_user_msg, sizeof(DltUserControlMsgTraceSettingMsg) * trace_load_settings_count); + + if (ret < DLT_RETURN_OK && errno == EPIPE) { + dlt_daemon_application_reset_user_handle(daemon, app, verbose); + } + + free(trace_load_settings_user_msg); + + return ret; +} +#endif diff --git a/src/daemon/dlt_daemon_common.h b/src/daemon/dlt_daemon_common.h index 687a95c01..fc91ee934 100644 --- a/src/daemon/dlt_daemon_common.h +++ b/src/daemon/dlt_daemon_common.h @@ -143,6 +143,10 @@ typedef struct DltDaemonContextLogSettings *context_log_level_settings; int num_context_log_level_settings; #endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + DltTraceLoadSettings* trace_load_settings; + uint32_t trace_load_settings_count; +#endif } DltDaemonApplication; /** @@ -208,6 +212,12 @@ typedef struct DltDaemonContextLogSettings *app_id_log_level_settings; /**< Settings for app id specific log levels */ int num_app_id_log_level_settings; /** < count of log level settings */ #endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + DltTraceLoadSettings *preconfigured_trace_load_settings; /**< Settings for trace load */ + int preconfigured_trace_load_settings_count; /** < count of trace load settings */ + int bytes_sent; + int bytes_recv; +#endif } DltDaemon; /** @@ -287,6 +297,22 @@ DltDaemonContextLogSettings *dlt_daemon_find_configured_app_id_ctx_id_settings( DltDaemonContextLogSettings *dlt_daemon_find_app_log_level_config( const DltDaemonApplication *app, const char *ctid); +#endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +/** + * Find information about trace load settings for a specific apid + * @param daemon pointer to dlt daemon structure + * @param apid pointer to the application id + * @param ctid pointer to the context id, may be NULL + * @param settings pointer to the settings array + * @param num_settings number of settings found matching the given data + * @param verbose if set to true verbose information is printed out + * @return pointer to load settings if found, NULL otherwise + */ +DltReturnValue +dlt_daemon_find_preconfigured_trace_load_settings(DltDaemon *const daemon, const char *apid, const char *ctid, DltTraceLoadSettings **settings, int *num_settings, int verbose); +int dlt_daemon_compare_trace_load_settings(const void *a, const void *b); + #endif /** @@ -504,6 +530,18 @@ int dlt_daemon_user_send_log_level(DltDaemon *daemon, DltDaemonContext *context, */ int dlt_daemon_user_send_log_state(DltDaemon *daemon, DltDaemonApplication *app, int verbose); +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +/** + * Send user message DLT_USER_MESSAGE_TRACE_LOAD to user application + * This message configures the warning and limit thresholds for an application + * @param daemon pointer to dlt daemon structure + * @param app pointer to application for response + * @param verbose if set to true verbose information is printed out. + * @return negative value if there was an error + */ +int dlt_daemon_user_send_trace_load_config(DltDaemon *daemon, DltDaemonApplication *app, int verbose); +#endif + /** * Send user messages to all user applications using default context, or trace status * to update those values diff --git a/src/daemon/dlt_daemon_connection.c b/src/daemon/dlt_daemon_connection.c index b24df9c38..ccb3c5e79 100644 --- a/src/daemon/dlt_daemon_connection.c +++ b/src/daemon/dlt_daemon_connection.c @@ -212,6 +212,14 @@ DLT_STATIC DltReceiver *dlt_connection_get_receiver(DltDaemonLocal *daemon_local dlt_receiver_init(ret, fd, DLT_RECEIVE_SOCKET, DLT_DAEMON_RCVBUFSIZESOCK); break; +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + case DLT_CONNECTION_STAT_TIMER: + ret = calloc(1, sizeof(DltReceiver)); + if (ret) { + dlt_receiver_init(ret, fd, DLT_RECEIVE_SOCKET, DLT_DAEMON_RCVBUFSIZE); + } + break; +#endif case DLT_CONNECTION_CLIENT_MSG_SERIAL: ret = calloc(1, sizeof(DltReceiver)); @@ -326,6 +334,13 @@ void *dlt_connection_get_callback(DltConnection *con) case DLT_CONNECTION_GATEWAY_TIMER: ret = dlt_gateway_process_gateway_timer; break; +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + case DLT_CONNECTION_STAT_TIMER: +#if defined(DLT_LIB_USE_FIFO_IPC) && !defined(DLT_SHM_ENABLE) + ret = dlt_daemon_process_statistics_timer; +#endif + break; +#endif default: ret = NULL; } diff --git a/src/daemon/dlt_daemon_connection_types.h b/src/daemon/dlt_daemon_connection_types.h index 470e4abf0..aff8e3e5a 100644 --- a/src/daemon/dlt_daemon_connection_types.h +++ b/src/daemon/dlt_daemon_connection_types.h @@ -51,6 +51,9 @@ typedef enum { DLT_CONNECTION_CONTROL_MSG, DLT_CONNECTION_GATEWAY, DLT_CONNECTION_GATEWAY_TIMER, +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + DLT_CONNECTION_STAT_TIMER, +#endif DLT_CONNECTION_TYPE_MAX } DltConnectionType; @@ -80,6 +83,9 @@ typedef struct DltConnection { DltConnectionStatus status; /**< Status of connection */ struct DltConnection *next; /**< For multiple client connection using linked list */ int ev_mask; /**< Mask to set when registering the connection for events */ +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + int remaining_size; /**< Remaining data size for sending data. This value will be set to non-zero when data could not be sent fully */ +#endif } DltConnection; #endif /* DLT_DAEMON_CONNECTION_TYPES_H */ diff --git a/src/daemon/dlt_daemon_event_handler_types.h b/src/daemon/dlt_daemon_event_handler_types.h index 92a6ff0c5..6f7561ccb 100644 --- a/src/daemon/dlt_daemon_event_handler_types.h +++ b/src/daemon/dlt_daemon_event_handler_types.h @@ -45,6 +45,9 @@ typedef enum { DLT_TIMER_SYSTEMD, #endif DLT_TIMER_GATEWAY, +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + DLT_TIMER_STAT, +#endif DLT_TIMER_UNKNOWN } DltTimers; diff --git a/src/lib/dlt_user.c b/src/lib/dlt_user.c index 6591b93c4..e856f8f5d 100644 --- a/src/lib/dlt_user.c +++ b/src/lib/dlt_user.c @@ -189,7 +189,7 @@ typedef struct static void dlt_user_housekeeperthread_function(void *ptr); static void dlt_user_atexit_handler(void); static DltReturnValue dlt_user_log_init(DltContext *handle, DltContextData *log); -static DltReturnValue dlt_user_log_send_log(DltContextData *log, int mtype); +static DltReturnValue dlt_user_log_send_log(DltContextData *log, int mtype, int *sent_size); static DltReturnValue dlt_user_log_send_register_application(void); static DltReturnValue dlt_user_log_send_unregister_application(void); static DltReturnValue dlt_user_log_send_register_context(DltContextData *log); @@ -224,6 +224,14 @@ static DltReturnValue dlt_user_log_write_sized_string_utils_attr(DltContextData static DltReturnValue dlt_unregister_app_util(bool force_sending_messages); +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +/* For trace load control feature */ +static DltReturnValue dlt_user_output_internal_msg(DltLogLevelType loglevel, const char *text, void* params); +DltTraceLoadSettings* trace_load_settings = NULL; +uint32_t trace_load_settings_count = 0; +pthread_rwlock_t trace_load_rw_lock; +#endif + DltReturnValue dlt_user_check_library_version(const char *user_major_version, const char *user_minor_version) { char lib_major_version[DLT_USER_MAX_LIB_VERSION_LENGTH]; @@ -504,7 +512,22 @@ DltReturnValue dlt_init(void) " Shared memory %s cannot be created!\n", dltShmName); #endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + /* initialize for trace load check */ + + pthread_rwlock_init(&trace_load_rw_lock, NULL); + pthread_rwlock_wrlock(&trace_load_rw_lock); + + trace_load_settings = malloc(sizeof(DltTraceLoadSettings)); + memset(trace_load_settings, 0, sizeof(DltTraceLoadSettings)); + trace_load_settings[0].soft_limit = DLT_TRACE_LOAD_CLIENT_SOFT_LIMIT_DEFAULT; + trace_load_settings[0].hard_limit = DLT_TRACE_LOAD_CLIENT_HARD_LIMIT_DEFAULT; + strncpy(trace_load_settings[0].apid, dlt_user.appID, DLT_ID_SIZE); + trace_load_settings_count = 1; + pthread_rwlock_unlock(&trace_load_rw_lock); + +#endif #ifdef DLT_LIB_USE_UNIX_SOCKET_IPC if (dlt_initialize_socket_connection() != DLT_RETURN_OK) @@ -1184,6 +1207,16 @@ DltReturnValue dlt_free(void) pthread_cond_destroy(&mq_init_condition); #endif /* DLT_NETWORK_TRACE_ENABLE */ + +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + if (trace_load_settings != NULL) { + free(trace_load_settings); + trace_load_settings = NULL; + } + trace_load_settings_count = 0; + pthread_rwlock_destroy(&trace_load_rw_lock); +#endif + pthread_mutex_destroy(&dlt_mutex); /* allow the user app to do dlt_init() again. */ @@ -1268,6 +1301,10 @@ DltReturnValue dlt_register_app(const char *apid, const char *description) DLT_SEM_FREE(); +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + strncpy(trace_load_settings[0].apid, dlt_user.appID, DLT_ID_SIZE); +#endif + ret = dlt_user_log_send_register_application(); if ((ret == DLT_RETURN_OK) && (dlt_user.dlt_log_handle != -1)) @@ -1917,7 +1954,7 @@ DltReturnValue dlt_user_log_write_finish(DltContextData *log) if (log == NULL) return DLT_RETURN_WRONG_PARAMETER; - ret = dlt_user_log_send_log(log, DLT_TYPE_LOG); + ret = dlt_user_log_send_log(log, DLT_TYPE_LOG, NULL); dlt_user_free_buffer(&(log->buffer)); @@ -1931,7 +1968,7 @@ DltReturnValue dlt_user_log_write_finish_w_given_buffer(DltContextData *log) if (log == NULL) return DLT_RETURN_WRONG_PARAMETER; - ret = dlt_user_log_send_log(log, DLT_TYPE_LOG); + ret = dlt_user_log_send_log(log, DLT_TYPE_LOG, NULL); return ret; } @@ -3018,7 +3055,7 @@ DltReturnValue dlt_user_trace_network_segmented_start(uint32_t *id, } /* Send log */ - ret = dlt_user_log_send_log(&log, DLT_TYPE_NW_TRACE); + ret = dlt_user_log_send_log(&log, DLT_TYPE_NW_TRACE, NULL); dlt_user_free_buffer(&(log.buffer)); @@ -3098,7 +3135,7 @@ DltReturnValue dlt_user_trace_network_segmented_segment(uint32_t id, return DLT_RETURN_ERROR; } - ret = dlt_user_log_send_log(&log, DLT_TYPE_NW_TRACE); + ret = dlt_user_log_send_log(&log, DLT_TYPE_NW_TRACE, NULL); /* Send log */ dlt_user_free_buffer(&(log.buffer)); @@ -3155,7 +3192,7 @@ DltReturnValue dlt_user_trace_network_segmented_end(uint32_t id, DltContext *han return DLT_RETURN_ERROR; } - ret = dlt_user_log_send_log(&log, DLT_TYPE_NW_TRACE); + ret = dlt_user_log_send_log(&log, DLT_TYPE_NW_TRACE, NULL); /* Send log */ dlt_user_free_buffer(&(log.buffer)); @@ -3462,7 +3499,7 @@ DltReturnValue dlt_user_trace_network_truncated(DltContext *handle, } } - ret = dlt_user_log_send_log(&log, DLT_TYPE_NW_TRACE); + ret = dlt_user_log_send_log(&log, DLT_TYPE_NW_TRACE, NULL); dlt_user_free_buffer(&(log.buffer)); @@ -3894,11 +3931,17 @@ DltReturnValue dlt_user_log_init(DltContext *handle, DltContextData *log) return ret; } -DltReturnValue dlt_user_log_send_log(DltContextData *log, int mtype) +DltReturnValue dlt_user_log_send_log(DltContextData *log, const int mtype, int *const sent_size) { DltMessage msg; DltUserHeader userheader; int32_t len; +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + uint32_t time_stamp; +#else + // shut up warning + (void)sent_size; +#endif DltReturnValue ret = DLT_RETURN_OK; @@ -3968,6 +4011,10 @@ DltReturnValue dlt_user_log_send_log(DltContextData *log, int mtype) msg.headerextra.tmsp = log->user_timestamp; } +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + time_stamp = msg.headerextra.tmsp; +#endif + if (dlt_message_set_extraparameters(&msg, 0) == DLT_RETURN_ERROR) return DLT_RETURN_ERROR; @@ -4108,6 +4155,31 @@ DltReturnValue dlt_user_log_send_log(DltContextData *log, int mtype) msg.standardheader->len = DLT_HTOBE_16(dlt_user.corrupt_message_size_size); # endif +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + /* check trace load before output */ + if (!sent_size) + { + DltTraceLoadSettings* settings = + dlt_find_runtime_trace_load_settings( + trace_load_settings, trace_load_settings_count, dlt_user.appID, log->handle->contextID); + + if (!dlt_check_trace_load( + settings, + log->log_level, time_stamp, + sizeof(DltUserHeader) + + msg.headersize - sizeof(DltStorageHeader) + + log->size, + dlt_user_output_internal_msg, + NULL)) + { + return DLT_RETURN_LOAD_EXCEEDED; + } + } + else + { + *sent_size = (sizeof(DltUserHeader) + msg.headersize - sizeof(DltStorageHeader) + log->size); + } +#endif ret = dlt_user_log_out3(dlt_user.dlt_log_handle, &(userheader), sizeof(DltUserHeader), @@ -4118,8 +4190,12 @@ DltReturnValue dlt_user_log_send_log(DltContextData *log, int mtype) } DltReturnValue process_error_ret = DLT_RETURN_OK; - /* store message in ringbuffer, if an error has occured */ + /* store message in ringbuffer, if an error has occurred */ +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + if (((ret!=DLT_RETURN_OK) || (dlt_user.appID[0] == '\0')) && !sent_size) +#else if ((ret != DLT_RETURN_OK) || (dlt_user.appID[0] == '\0')) +#endif process_error_ret = dlt_user_log_out_error_handling(&(userheader), sizeof(DltUserHeader), msg.headerbuffer + sizeof(DltStorageHeader), @@ -4531,6 +4607,13 @@ DltReturnValue dlt_user_log_check_user_message(void) DltUserControlMsgLogState *userlogstate; unsigned char *userbuffer; +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + DltUserControlMsgTraceSettingMsg *trace_load_settings_user_messages; + uint32_t trace_load_settings_user_messages_count = 0; + uint32_t trace_load_settings_user_message_bytes_required = 0; + unsigned long trace_load_settings_alloc_size = 0; +#endif + /* For delayed calling of injection callback, to avoid deadlock */ DltUserInjectionCallback delayed_injection_callback; DltUserLogLevelChangedCallback delayed_log_level_changed_callback; @@ -4761,6 +4844,79 @@ DltReturnValue dlt_user_log_check_user_message(void) return DLT_RETURN_ERROR; } break; +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + case DLT_USER_MESSAGE_TRACE_LOAD: + { + /* + * at least user header and message length is available + */ + trace_load_settings_user_message_bytes_required = + (int32_t) (sizeof(DltUserHeader) + sizeof(uint32_t )); + if (receiver->bytesRcvd < (int32_t)trace_load_settings_user_message_bytes_required) { + // Not enough data to read the message length + leave_while = 1; + break; + } + + // Read trace settings count from buffer. + trace_load_settings_user_messages_count = (uint32_t)(*(receiver->buf + sizeof(DltUserHeader))); + trace_load_settings_user_message_bytes_required += + trace_load_settings_user_messages_count * sizeof(DltUserControlMsgTraceSettingMsg); + if (receiver->bytesRcvd < (int32_t)trace_load_settings_user_message_bytes_required) { + // Not enough data to read trace settings + leave_while = 1; + break; + } + + trace_load_settings_user_messages = + (DltUserControlMsgTraceSettingMsg *)(receiver->buf + sizeof(DltUserHeader) + sizeof(uint32_t)); + + pthread_rwlock_wrlock(&trace_load_rw_lock); + + // Remove the default created at startup + if (trace_load_settings != NULL) { + free(trace_load_settings); + } + + char msg[255]; + trace_load_settings_alloc_size = sizeof(DltTraceLoadSettings) * trace_load_settings_user_messages_count; + trace_load_settings = malloc(trace_load_settings_alloc_size); + memset(trace_load_settings, 0, trace_load_settings_alloc_size); + for (i = 0; i < trace_load_settings_user_messages_count; i++) { + strncpy(trace_load_settings[i].apid, dlt_user.appID, DLT_ID_SIZE); + strncpy(trace_load_settings[i].ctid, trace_load_settings_user_messages[i].ctid, DLT_ID_SIZE); + trace_load_settings[i].soft_limit = trace_load_settings_user_messages[i].soft_limit; + trace_load_settings[i].hard_limit = trace_load_settings_user_messages[i].hard_limit; + + if (trace_load_settings[i].ctid[0] == '\0') { + snprintf( + msg, sizeof(msg), + "Received trace load settings: apid=%.4s, soft_limit=%u, hard_limit=%u\n", + trace_load_settings[i].apid, + trace_load_settings[i].soft_limit, + trace_load_settings[i].hard_limit); + } else { + snprintf( + msg, sizeof(msg), + "Received trace load settings: apid=%.4s, ctid=%.4s, soft_limit=%u, hard_limit=%u\n", + trace_load_settings[i].apid, + trace_load_settings[i].ctid, + trace_load_settings[i].soft_limit, + trace_load_settings[i].hard_limit); + } + dlt_user_output_internal_msg(DLT_LOG_INFO, msg, NULL); + } + trace_load_settings_count = trace_load_settings_user_messages_count; + + pthread_rwlock_unlock(&trace_load_rw_lock); + + /* keep not read data in buffer */ + if (dlt_receiver_remove(receiver, trace_load_settings_user_message_bytes_required) + == DLT_RETURN_ERROR) { + return DLT_RETURN_ERROR; + } + } +#endif default: { dlt_log(LOG_WARNING, "Invalid user message type received!\n"); @@ -5172,8 +5328,81 @@ static void dlt_fork_child_fork_handler() g_dlt_is_child = 1; dlt_user_init_state = INIT_UNITIALIZED; dlt_user.dlt_log_handle = -1; +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + pthread_rwlock_unlock(&trace_load_rw_lock); +#endif } + +#if defined(DLT_TRACE_LOAD_CTRL_ENABLE) +static DltReturnValue dlt_user_output_internal_msg( + const DltLogLevelType loglevel, const char *const text, void* const params) +{ + (void)params; // parameter is not needed + static DltContext handle; + DltContextData log; + int ret; + int sent_size = 0; + + if (!handle.contextID[0]) + { + // Register Special Context ID for output DLT library internal message + ret = dlt_register_context(&handle, DLT_INTERNAL_CONTEXT_ID, "DLT user library internal context"); + if (ret < DLT_RETURN_OK) + { + return ret; + } + } + + if (dlt_user.verbose_mode == 0) + { + return DLT_RETURN_ERROR; + } + + if (loglevel < DLT_USER_LOG_LEVEL_NOT_SET || loglevel >= DLT_LOG_MAX) + { + dlt_vlog(LOG_ERR, "Loglevel %d is outside valid range", loglevel); + return DLT_RETURN_WRONG_PARAMETER; + } + + if (text == NULL) + { + return DLT_RETURN_WRONG_PARAMETER; + } + + ret = dlt_user_log_write_start(&handle, &log, loglevel); + + // Ok means below threshold + // see src/dlt-qnx-system/dlt-qnx-slogger2-adapter.cpp::sloggerinfo_callback for reference + if (ret == DLT_RETURN_OK) + { + return ret; + } + + if (ret != DLT_RETURN_TRUE) + { + dlt_vlog(LOG_ERR, "Loglevel %d is disabled", loglevel); + } + + + if (log.buffer == NULL) + { + return DLT_RETURN_LOGGING_DISABLED; + } + + ret = dlt_user_log_write_string(&log, text); + if (ret < DLT_RETURN_OK) + { + return ret; + } + + ret = dlt_user_log_send_log(&log, DLT_TYPE_LOG, &sent_size); + + /* Return number of bytes if message was successfully sent */ + return (ret == DLT_RETURN_OK) ? sent_size : ret; +} +#endif + DltReturnValue dlt_user_log_out_error_handling(void *ptr1, size_t len1, void *ptr2, size_t len2, void *ptr3, size_t len3) { diff --git a/src/shared/dlt_common.c b/src/shared/dlt_common.c index d6f39058f..362349642 100644 --- a/src/shared/dlt_common.c +++ b/src/shared/dlt_common.c @@ -104,6 +104,30 @@ int dlt_buffer_minimize_size(DltBuffer *buf); void dlt_buffer_write_block(DltBuffer *buf, int *write, const unsigned char *data, unsigned int size); void dlt_buffer_read_block(DltBuffer *buf, int *read, unsigned char *data, unsigned int size); +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +static int32_t dlt_output_soft_limit_over_warning( + DltTraceLoadSettings* tl_settings, + DltLogInternal log_internal, + void *log_params); + +static int32_t dlt_output_hard_limit_warning( + DltTraceLoadSettings* tl_settings, + DltLogInternal log_internal, + void *log_params); + +static bool dlt_user_cleanup_window(DltTraceLoadStat *tl_stat); + +static int32_t dlt_switch_slot_if_needed( + DltTraceLoadSettings* tl_settings, + DltLogInternal log_internal, + void *log_internal_params, + uint32_t timestamp); + +static void dlt_record_trace_load(DltTraceLoadStat *const tl_stat, int32_t size); +static inline bool dlt_is_over_trace_load_soft_limit(DltTraceLoadSettings* tl_settings); +static inline bool dlt_is_over_trace_load_hard_limit(DltTraceLoadSettings* tl_settings, int size); +#endif + void dlt_print_hex(uint8_t *ptr, int size) { int num; @@ -4135,4 +4159,357 @@ bool dlt_extract_base_name_without_ext(const char* const abs_file_name, char* ba strncpy(base_name, abs_file_name, length); base_name[length] = '\0'; return true; -} \ No newline at end of file +} + +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +static int32_t dlt_output_soft_limit_over_warning( + DltTraceLoadSettings* const tl_settings, + DltLogInternal log_internal, + void *const log_params) +{ + char local_str[255]; + + if (!tl_settings || !tl_settings->tl_stat.is_over_soft_limit || tl_settings->tl_stat.slot_left_soft_limit_warn) + { + /* No need to output warning message */ + return 0; + } + + /* Calculate extra trace load which was over limit */ + const uint64_t dropped_message_load + = (tl_settings->tl_stat.hard_limit_over_bytes * DLT_TIMESTAMP_RESOLUTION) + / TIMESTAMP_BASED_WINDOW_SIZE; + const uint64_t curr_trace_load = tl_settings->tl_stat.avg_trace_load + dropped_message_load; + if (curr_trace_load <= tl_settings->soft_limit) { + /* No need to output warning message */ + return 0; + } + + /* Warning for exceeded soft limit */ + if (tl_settings->ctid[0] == 0) { + snprintf(local_str, sizeof(local_str), + "Trace load exceeded trace soft limit on apid %.4s " + "(soft limit: %u bytes/sec, current: %lu bytes/sec)", + tl_settings->apid, + tl_settings->soft_limit, + curr_trace_load); + } else { + snprintf(local_str, sizeof(local_str), + "Trace load exceeded trace soft limit on apid %.4s, ctid %.4s " + "(soft limit: %u bytes/sec, current: %lu bytes/sec)", + tl_settings->apid, + tl_settings->ctid, + tl_settings->soft_limit, + curr_trace_load); + } + + // must be signed int for error return value + int32_t sent_size = log_internal(DLT_LOG_WARN, local_str, log_params); + if (sent_size < DLT_RETURN_OK) + { + /* Output warning message via other route for safety */ + dlt_log(DLT_LOG_WARN, local_str); + sent_size = 0; + } + + /* Turn off the flag after sending warning message */ + tl_settings->tl_stat.is_over_soft_limit = false; + tl_settings->tl_stat.slot_left_soft_limit_warn = DLT_SOFT_LIMIT_WARN_FREQUENCY; + + return sent_size; +} + +static int32_t dlt_output_hard_limit_warning( + DltTraceLoadSettings* const tl_settings, + DltLogInternal log_internal, + void *const log_params) +{ + char local_str[255]; + if (!tl_settings || !tl_settings->tl_stat.is_over_hard_limit || tl_settings->tl_stat.slot_left_hard_limit_warn) + { + /* No need to output warning message */ + return 0; + } + + /* Calculate extra trace load which was over limit */ + const uint64_t dropped_message_load + = (tl_settings->tl_stat.hard_limit_over_bytes * DLT_TIMESTAMP_RESOLUTION) + / TIMESTAMP_BASED_WINDOW_SIZE; + const uint64_t curr_trace_load = tl_settings->tl_stat.avg_trace_load + dropped_message_load; + if (curr_trace_load <= tl_settings->hard_limit) { + /* No need to output warning message */ + return 0; + } + + if (tl_settings->ctid[0] == 0) { + snprintf(local_str, sizeof(local_str), + "Trace load exceeded trace hard limit on apid %.4s " + "(hard limit: %u bytes/sec, current: %lu bytes/sec) %u messages discarded. ", + tl_settings->apid, + tl_settings->hard_limit, + curr_trace_load, + tl_settings->tl_stat.hard_limit_over_counter); + } else { + snprintf(local_str, sizeof(local_str), + "Trace load exceeded trace hard limit on apid %.4s, ctid %.4s." + "(hard limit: %u bytes/sec, current: %lu bytes/sec) %u messages discarded.", + tl_settings->apid, + tl_settings->ctid, + tl_settings->hard_limit, + curr_trace_load, + tl_settings->tl_stat.hard_limit_over_counter); + } + + // must be signed int for error return + int32_t sent_size = log_internal(DLT_LOG_WARN, local_str, log_params); + if (sent_size < DLT_RETURN_OK) + { + /* Output warning message via other route for safety */ + dlt_log(DLT_LOG_WARN, local_str); + sent_size = 0; + } + + /* Turn off the flag after sending warning message */ + tl_settings->tl_stat.is_over_hard_limit = false; + tl_settings->tl_stat.hard_limit_over_counter = 0; + tl_settings->tl_stat.hard_limit_over_bytes = 0; + tl_settings->tl_stat.slot_left_hard_limit_warn = DLT_HARD_LIMIT_WARN_FREQUENCY; + + return sent_size; +} + +static bool dlt_user_cleanup_window(DltTraceLoadStat *const tl_stat) +{ + if (!tl_stat) + { + return false; + } + + uint32_t elapsed_slots = 0; + /* check if overflow of timestamp happened, after ~119 hours */ + if (tl_stat->curr_abs_slot < tl_stat->last_abs_slot) { + /* calculate where the next slot starts according to the last slot + * This works because the value after the uint32 rollover equals is equal to the remainder that did not fit + * into uint32 before. Therefore, we always have slots that are DLT_TIMESTAMP_RESOLUTION long + * */ + const uint32_t next_slot_start = + DLT_TIMESTAMP_RESOLUTION + tl_stat->last_abs_slot; + + /* Check if we are already in the next slot */ + if (next_slot_start <= tl_stat->curr_abs_slot) { + /* Calculate relative amount of elapsed slots */ + elapsed_slots = (tl_stat->curr_abs_slot - next_slot_start) / DLT_TIMESTAMP_RESOLUTION + 1; + } + /* else we are not in the next slot yet */ + } else { + /* no rollover, get difference between slots to get amount of elapsed slots */ + elapsed_slots = (tl_stat->curr_abs_slot - tl_stat->last_abs_slot); + } + + if (!elapsed_slots) + { + /* Same slot can be still used. No need to cleanup slot */ + return false; + } + + /* Slot-Based Count down for next warning messages */ + tl_stat->slot_left_soft_limit_warn = (tl_stat->slot_left_soft_limit_warn > elapsed_slots) ? + (tl_stat->slot_left_soft_limit_warn - elapsed_slots) : 0; + + tl_stat->slot_left_hard_limit_warn = (tl_stat->slot_left_hard_limit_warn > elapsed_slots) ? + (tl_stat->slot_left_hard_limit_warn - elapsed_slots) : 0; + + /* Clear whole window when time elapsed longer than window size from last message */ + if (elapsed_slots >= DLT_TRACE_LOAD_WINDOW_SIZE) + { + tl_stat->total_bytes_of_window = 0; + memset(tl_stat->window, 0, sizeof(tl_stat->window)); + return true; + } + + /* Clear skipped no data slots */ + uint32_t temp_slot = tl_stat->last_slot; + while (temp_slot != tl_stat->curr_slot) + { + temp_slot++; + temp_slot %= DLT_TRACE_LOAD_WINDOW_SIZE; + tl_stat->total_bytes_of_window -= tl_stat->window[temp_slot]; + tl_stat->window[temp_slot] = 0; + } + + return true; +} + +static int32_t dlt_switch_slot_if_needed( + DltTraceLoadSettings* const tl_settings, + DltLogInternal log_internal, + void* const log_internal_params, + const uint32_t timestamp) +{ + if (!tl_settings) + { + return 0; + } + + /* Get new window slot No. */ + tl_settings->tl_stat.curr_abs_slot = timestamp / DLT_TRACE_LOAD_WINDOW_RESOLUTION; + tl_settings->tl_stat.curr_slot = tl_settings->tl_stat.curr_abs_slot % DLT_TRACE_LOAD_WINDOW_SIZE; + + /* Cleanup window */ + if (!dlt_user_cleanup_window(&tl_settings->tl_stat)) + { + /* No need to switch slot because same slot can be still used */ + return 0; + } + + /* If slot is switched and trace load has been over soft/hard limit + * in previous slot, warning messages may be sent. + * The warning messages will be also counted as trace load. + */ + const int32_t sent_warn_msg_bytes = + dlt_output_soft_limit_over_warning(tl_settings, log_internal, log_internal_params) + + dlt_output_hard_limit_warning(tl_settings, log_internal, log_internal_params); + return sent_warn_msg_bytes; +} + +static void dlt_record_trace_load(DltTraceLoadStat *const tl_stat, const int32_t size) +{ + if (!tl_stat) + { + return; + } + + /* Record trace load to current slot by message size of + * original message and warning message if it was sent + */ + tl_stat->window[tl_stat->curr_slot] += size; + tl_stat->total_bytes_of_window += size; + + /* Keep the latest time information */ + tl_stat->last_abs_slot = tl_stat->curr_abs_slot; + tl_stat->last_slot = tl_stat->curr_slot; + + /* Calculate average trace load [bytes/sec] in window + * The division is necessary to normalize the average to bytes per second even if + * the slot size is not equal to 1s + * */ + tl_stat->avg_trace_load + = (tl_stat->total_bytes_of_window * DLT_TIMESTAMP_RESOLUTION) / TIMESTAMP_BASED_WINDOW_SIZE; +} + +static inline bool dlt_is_over_trace_load_soft_limit(DltTraceLoadSettings* const tl_settings) +{ + if (tl_settings + && (tl_settings->tl_stat.avg_trace_load > tl_settings->soft_limit || tl_settings->soft_limit == 0)) + { + /* Mark as soft limit over */ + tl_settings->tl_stat.is_over_soft_limit = true; + return true; + } + + return false; +} + +static inline bool dlt_is_over_trace_load_hard_limit( + DltTraceLoadSettings* const tl_settings, const int size) +{ + if (tl_settings + && (tl_settings->tl_stat.avg_trace_load > tl_settings->hard_limit + || tl_settings->hard_limit == 0)) + { + /* Mark as limit over */ + tl_settings->tl_stat.is_over_hard_limit = true; + tl_settings->tl_stat.hard_limit_over_counter++; + tl_settings->tl_stat.hard_limit_over_bytes += size; + + /* Delete size of limit over message from window */ + tl_settings->tl_stat.window[tl_settings->tl_stat.curr_slot] -= size; + tl_settings->tl_stat.total_bytes_of_window -= size; + return true; + } + + return false; +} + +bool dlt_check_trace_load( + DltTraceLoadSettings * const tl_settings, + const int32_t log_level, + const uint32_t timestamp, + const int32_t size, + DltLogInternal internal_dlt_log, + void* const internal_dlt_log_params) +{ + /* Unconditionally allow message which has log level: Debug/Verbose to be output */ + if (log_level == DLT_LOG_DEBUG || log_level == DLT_LOG_VERBOSE) + { + return true; + } + + if (size < 0) + { + dlt_vlog(LOG_ERR, "Invalid size: %d", size); + return false; + } + + pthread_rwlock_rdlock(&trace_load_rw_lock); + + /* Switch window slot according to timestamp + * If warning messages for hard/soft limit over are sent, + * the message size will be returned. + */ + const int32_t sent_warn_msg_bytes = dlt_switch_slot_if_needed( + tl_settings, internal_dlt_log, internal_dlt_log_params, timestamp); + + /* Record trace load */ + dlt_record_trace_load(&tl_settings->tl_stat, size + sent_warn_msg_bytes); + + /* Check if trace load is over the soft limit. + * Even if trace load is over the soft limit, message will not be discarded. + * Only the warning message will be output + */ + dlt_is_over_trace_load_soft_limit(tl_settings); + + /* Check if trace load is over hard limit. + * If trace load is over the limit, message will be discarded. + */ + const bool allow_output = !dlt_is_over_trace_load_hard_limit(tl_settings, size); + + pthread_rwlock_unlock(&trace_load_rw_lock); + + return allow_output; +} + +DltTraceLoadSettings* +dlt_find_runtime_trace_load_settings(DltTraceLoadSettings *settings, uint32_t settings_count, const char* apid, const char* ctid) { + if ((apid == NULL) || (strlen(apid) == 0)) + return NULL; + + DltTraceLoadSettings* app_level = NULL; + size_t ctid_len = (ctid != NULL) ? strlen(ctid) : 0; + + for (uint32_t i = 0; i < settings_count; ++i) { + if (strncmp(apid, settings->apid, DLT_ID_SIZE) != 0) { + if (app_level == NULL) + continue; + // settings are sorted. + // If we found a configuration entry which matches the app id already + // we can exit here because no more entries with the app id will follow anymore. + break; + } + + if (settings[i].ctid[0] == '\0') { + app_level = &settings[i]; + if (ctid_len == 0) + return &settings[i]; + continue; + } + + if ((ctid_len > 0) && (strncmp(ctid, settings[i].ctid, DLT_ID_SIZE) == 0)) { + return &settings[i]; + } + } + + return app_level; +} + +#endif diff --git a/src/shared/dlt_user_shared.h b/src/shared/dlt_user_shared.h index 5e447076e..aee42cea7 100644 --- a/src/shared/dlt_user_shared.h +++ b/src/shared/dlt_user_shared.h @@ -181,6 +181,16 @@ typedef struct char apid[4]; /**< application which lost messages */ } DLT_PACKED DltUserControlMsgBufferOverflow; +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +typedef struct +{ + char ctid[DLT_ID_SIZE]; + uint32_t soft_limit; + uint32_t hard_limit; +} DLT_PACKED DltUserControlMsgTraceSettingMsg; + +#endif + /************************************************************************************************** * The folowing functions are used shared between the user lib and the daemon implementation **************************************************************************************************/ diff --git a/src/shared/dlt_user_shared_cfg.h b/src/shared/dlt_user_shared_cfg.h index d2fdc958a..773bb316e 100644 --- a/src/shared/dlt_user_shared_cfg.h +++ b/src/shared/dlt_user_shared_cfg.h @@ -90,6 +90,7 @@ #define DLT_USER_MESSAGE_LOG_MODE 11 #define DLT_USER_MESSAGE_LOG_STATE 12 #define DLT_USER_MESSAGE_MARKER 13 +#define DLT_USER_MESSAGE_TRACE_LOAD 14 #define DLT_USER_MESSAGE_NOT_SUPPORTED 16 /* Internal defined values */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8d6b4cf4d..91983b329 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,7 +26,10 @@ else() set(LIBRARIES socket) endif() -if (WITH_SYSTEMD OR WITH_SYSTEMD_SOCKET_ACTIVATION OR WITH_SYSTEMD_WATCHDOG) +if (WITH_SYSTEMD + OR WITH_SYSTEMD_SOCKET_ACTIVATION + OR WITH_SYSTEMD_WATCHDOG + OR WITH_SYSTEMD_JOURNAL) set(LIBRARIES ${LIBRARIES} systemd) endif() @@ -62,6 +65,7 @@ string(CONCAT TEST_ENV_IPv6 "${TEST_ENV};" #################### set(TARGET_LIST gtest_dlt_common gtest_dlt_user + gtest_dlt_daemon gtest_dlt_daemon_common dlt_env_ll_unit_test) @@ -69,6 +73,24 @@ foreach(target IN LISTS TARGET_LIST) set(target_SRCS ${target}) if(${target} STREQUAL "gtest_dlt_daemon_common") set(target_SRCS ${target_SRCS} ${PROJECT_SOURCE_DIR}/src/daemon/dlt_daemon_common.c) + elseif(${target} STREQUAL "gtest_dlt_daemon") + set(target_SRCS ${target_SRCS} + ../src/daemon/dlt-daemon.c + ../src/daemon/dlt_daemon_client.c + ../src/daemon/dlt_daemon_common.c + ../src/daemon/dlt_daemon_connection.c + ../src/daemon/dlt_daemon_event_handler.c + ../src/daemon/dlt_daemon_offline_logstorage.c + ../src/daemon/dlt_daemon_serial.c + ../src/daemon/dlt_daemon_socket.c + ../src/daemon/dlt_daemon_unix_socket.c + ../src/gateway/dlt_gateway.c + ../src/offlinelogstorage/dlt_offline_logstorage_behavior.c + ../src/offlinelogstorage/dlt_offline_logstorage.c + ../src/shared/dlt_config_file_parser.c + ../src/shared/dlt_offline_trace.c + ) + add_compile_definitions(${target} DLT_DAEMON_UNIT_TESTS_NO_MAIN) endif() add_executable(${target} ${target_SRCS}) target_link_libraries(${target} ${DLT_LIBRARIES}) diff --git a/tests/gtest_dlt_daemon.cpp b/tests/gtest_dlt_daemon.cpp new file mode 100644 index 000000000..3680ebae0 --- /dev/null +++ b/tests/gtest_dlt_daemon.cpp @@ -0,0 +1,490 @@ + +/* + * SPDX license identifier: MPL-2.0 + * + * Copyright (C) 2024, Mercedes Benz Tech Innovation GmbH + * + * This file is part of GENIVI Project DLT - Diagnostic Log and Trace. + * + * This Source Code Form is subject to the terms of the + * Mozilla Public License (MPL), v. 2.0. + * If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * For further information see https://www.covesa.global/. + */ + +/*! + * \author + * Alexander Mohr + * + * \copyright Copyright © 2024 Mercedes Benz Tech Innovation GmbH. \n + * License MPL-2.0: Mozilla Public License version 2.0 http://mozilla.org/MPL/2.0/. + * + * \file gtest_dlt_daemon.cpp + */ + +#include "dlt_daemon_common_cfg.h" +#include +#include +#include +#include + +extern "C" +{ +#include "dlt-daemon.h" +#include "dlt-daemon_cfg.h" +#include "dlt_user_cfg.h" +#include "dlt_version.h" +#include "dlt_client.h" +#include "dlt_protocol.h" +} +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + +const int _trace_load_send_size = 100; + +static void init_daemon(DltDaemon* daemon, char* ecu) { + DltGateway gateway; + strcpy(ecu, "ECU1"); + + EXPECT_EQ(0, + dlt_daemon_init(daemon, DLT_DAEMON_RINGBUFFER_MIN_SIZE, DLT_DAEMON_RINGBUFFER_MAX_SIZE, + DLT_DAEMON_RINGBUFFER_STEP_SIZE, DLT_RUNTIME_DEFAULT_DIRECTORY, DLT_LOG_INFO, + DLT_TRACE_STATUS_OFF, 0, 0)); + + dlt_set_id(daemon->ecuid, ecu); + EXPECT_EQ(0, dlt_daemon_init_user_information(daemon, &gateway, 0, 0)); +} + +static void setup_trace_load_settings(DltDaemon& daemon) +{ + daemon.preconfigured_trace_load_settings_count = 6; + daemon.preconfigured_trace_load_settings = + (DltTraceLoadSettings *)malloc(daemon.preconfigured_trace_load_settings_count * sizeof(DltTraceLoadSettings)); + memset(daemon.preconfigured_trace_load_settings, 0, daemon.preconfigured_trace_load_settings_count * sizeof(DltTraceLoadSettings)); + + // APP0 only has app id + strcpy(daemon.preconfigured_trace_load_settings[0].apid, "APP0"); + daemon.preconfigured_trace_load_settings[0].soft_limit = 1000; + daemon.preconfigured_trace_load_settings[0].hard_limit = 2987; + + // APP1 has only three contexts, no app id + strcpy(daemon.preconfigured_trace_load_settings[1].apid, "APP1"); + strcpy(daemon.preconfigured_trace_load_settings[1].ctid, "CT01"); + daemon.preconfigured_trace_load_settings[1].soft_limit = 100; + daemon.preconfigured_trace_load_settings[1].hard_limit = 200; + + strcpy(daemon.preconfigured_trace_load_settings[2].apid, "APP1"); + strcpy(daemon.preconfigured_trace_load_settings[2].ctid, "CT02"); + daemon.preconfigured_trace_load_settings[2].soft_limit = 300; + daemon.preconfigured_trace_load_settings[2].hard_limit = 400; + + strcpy(daemon.preconfigured_trace_load_settings[3].apid, "APP1"); + strcpy(daemon.preconfigured_trace_load_settings[3].ctid, "CT03"); + daemon.preconfigured_trace_load_settings[3].soft_limit = 500; + daemon.preconfigured_trace_load_settings[3].hard_limit = 600; + + // APP2 has app id and context + strcpy(daemon.preconfigured_trace_load_settings[4].apid, "APP2"); + strcpy(daemon.preconfigured_trace_load_settings[4].ctid, "CT01"); + daemon.preconfigured_trace_load_settings[4].soft_limit = 700; + daemon.preconfigured_trace_load_settings[4].hard_limit = 800; + + strcpy(daemon.preconfigured_trace_load_settings[5].apid, "APP2"); + daemon.preconfigured_trace_load_settings[5].soft_limit = 900; + daemon.preconfigured_trace_load_settings[5].hard_limit = 1000; + + // APP3 is not configured at all, but will be added via application_add + // to make sure we assign default value in that case + + qsort(daemon.preconfigured_trace_load_settings, daemon.preconfigured_trace_load_settings_count, + sizeof(DltTraceLoadSettings), dlt_daemon_compare_trace_load_settings); +} + +TEST(t_trace_load_config_file_parser, normal) +{ + DltDaemon daemon; + daemon.preconfigured_trace_load_settings = NULL; + daemon.preconfigured_trace_load_settings_count = 0; + DltDaemonLocal daemon_local; + + // XXXXXX is required by mkstemp + char filename_template[] = "t_trace_load_config_file_parser.XXXXXX"; + int fd = mkstemp(filename_template); + + ASSERT_NE(fd, -1) << "Unable to create temporary file: " << strerror(errno); + + // Convert file descriptor to FILE* with fdopen + FILE *temp = fdopen(fd, "w"); + + if (temp == NULL) { + close(fd); + GTEST_FAIL() << "Unable to open file stream"; + } + + fprintf(temp, "QSYM QSLA 83333 100000\n"); + fprintf(temp, "TEST 123 456\n"); + fprintf(temp, "TEST FOO 42 100\n"); + fprintf(temp, "BAR 42 42 \n"); + fprintf(temp, "FOO BAR 84 84\n"); + fprintf(temp, "AP12 CTX1 100 200\n"); + fflush(temp); + fclose(temp); + + strcpy(daemon_local.flags.lvalue, filename_template); + + trace_load_config_file_parser(&daemon, &daemon_local); + + EXPECT_EQ(daemon.preconfigured_trace_load_settings_count, 6); + + EXPECT_EQ(strncmp(daemon.preconfigured_trace_load_settings[0].apid, "AP12", DLT_ID_SIZE), 0); + EXPECT_EQ(strncmp(daemon.preconfigured_trace_load_settings[0].ctid, "CTX1", DLT_ID_SIZE), 0); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[0].soft_limit, 100); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[0].hard_limit, 200); + + EXPECT_EQ(strncmp(daemon.preconfigured_trace_load_settings[1].apid, "BAR", DLT_ID_SIZE), 0); + EXPECT_EQ(strlen(daemon.preconfigured_trace_load_settings[1].ctid), 0); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[1].soft_limit, 42); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[1].hard_limit, 42); + + EXPECT_EQ(strncmp(daemon.preconfigured_trace_load_settings[2].apid, "FOO", DLT_ID_SIZE), 0); + EXPECT_EQ(strncmp(daemon.preconfigured_trace_load_settings[2].ctid, "BAR", DLT_ID_SIZE), 0); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[2].soft_limit, 84); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[2].hard_limit, 84); + + EXPECT_EQ(strncmp(daemon.preconfigured_trace_load_settings[3].apid, "QSYM", DLT_ID_SIZE), 0); + EXPECT_EQ(strncmp(daemon.preconfigured_trace_load_settings[3].ctid, "QSLA", DLT_ID_SIZE), 0); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[3].soft_limit, 83333); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[3].hard_limit, 100000); + + EXPECT_EQ(strncmp(daemon.preconfigured_trace_load_settings[4].apid, "TEST", DLT_ID_SIZE), 0); + EXPECT_EQ(strlen(daemon.preconfigured_trace_load_settings[4].ctid), 0); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[4].soft_limit, 123); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[4].hard_limit, 456); + + EXPECT_EQ(strncmp(daemon.preconfigured_trace_load_settings[5].apid, "TEST", DLT_ID_SIZE), 0); + EXPECT_EQ(strncmp(daemon.preconfigured_trace_load_settings[5].ctid, "FOO", DLT_ID_SIZE), 0); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[5].soft_limit, 42); + EXPECT_EQ(daemon.preconfigured_trace_load_settings[5].hard_limit, 100); + + free(daemon.preconfigured_trace_load_settings); + unlink(filename_template); +} + +TEST(t_trace_load_config_file_parser, errors) +{ + DltDaemon daemon; + daemon.preconfigured_trace_load_settings = NULL; + daemon.preconfigured_trace_load_settings_count = 0; + DltDaemonLocal daemon_local; + + // XXXXXX is required by mkstemp + char filename_template[] = "t_trace_load_config_file_parser.XXXXXX"; + int fd = mkstemp(filename_template); + + ASSERT_NE(fd, -1) << "Unable to create temporary file: " << strerror(errno); + + // Convert file descriptor to FILE* with fdopen + FILE *temp = fdopen(fd, "w"); + + if (temp == NULL) { + close(fd); + GTEST_FAIL() << "Unable to open file stream"; + } + + // Test case 1: ID Length Exceeded + fprintf(temp, "APPID1234 100 200\n"); + fprintf(temp, "APP1 CTXID123 100 200\n"); + + // Test case 2: Missing Fields + fprintf(temp, "APP2 100\n"); + + // Test case 3: Extra Fields + fprintf(temp, "APP3 CTX1 100 200 EXTRA\n"); + + // Test case 4: Invalid ID Characters + fprintf(temp, "\U0001F480 100 200\n"); + fprintf(temp, "APP4 \U0001F480 100 200\n"); + fprintf(temp, "\U0001F480 \U0001F480 100 200\n"); + + // Test case 5: Non-numeric Limits + fprintf(temp, "APP5 CTX1 42 HARD\n"); + fprintf(temp, "APP6 CTX1 SOFT 42\n"); + fprintf(temp, "APP6 CTX1 SOFT HARD\n"); + + // Test case 6: Negative Limits + fprintf(temp, "APP7 CTX1 -100 200\n"); + fprintf(temp, "APP8 CTX1 100 -200\n"); + fprintf(temp, "APP9 CTX1 -100 -200\n"); + + // Test case 7: Limits Out of Order + fprintf(temp, "AP10 CTX1 300 200\n"); + + // Test case 8: Whitespace Issues + fprintf(temp, "APP1100 200\n"); + + // Test case 9: Optional CTXID Omitted Incorrectly + fprintf(temp, "APP1CTX1 100 200\n"); + + // Test case 10: Comments Misplacement or Format + fprintf(temp, "APP1 CTX1 100 # This is a comment 200\n"); + fprintf(temp, "APP1 CTX1 100 200 // This is a comment\n"); + + // Test case 11: empty lines + fprintf(temp, " \n"); + fprintf(temp, "\n"); + + // Test case 12: missing new line + fprintf(temp, "APP1 100 200 APP2 300 400\n"); + + // Test case 13: data longer than value length + char long_data[4096]; + memset(long_data, 'A', sizeof(long_data)); + fprintf(temp, "%s 100 200 APP2 300 400\n", long_data); + + // Test case 14: tabs + fprintf(temp, "APP1\t100\t200\n"); + fprintf(temp, "APP1\t100\t200\t\n"); + fprintf(temp, "APP1 100 200\t \n"); + fprintf(temp, "APP1\t100 200 \n"); + + fflush(temp); + fclose(temp); + + strcpy(daemon_local.flags.lvalue, filename_template); + + trace_load_config_file_parser(&daemon, &daemon_local); + EXPECT_EQ(daemon.preconfigured_trace_load_settings_count, 0); + free(daemon.preconfigured_trace_load_settings); + unlink(filename_template); +} + +static void matches_default_element(DltDaemonApplication *app, DltTraceLoadSettings *settings) { + EXPECT_STREQ(settings->apid, app->apid); + EXPECT_STREQ(settings->ctid, ""); + EXPECT_EQ(settings->soft_limit, DLT_TRACE_LOAD_DAEMON_SOFT_LIMIT_DEFAULT); + EXPECT_EQ(settings->hard_limit, DLT_TRACE_LOAD_DAEMON_HARD_LIMIT_DEFAULT); +} + +TEST(t_find_runtime_trace_load_settings, normal) { + DltDaemon daemon; + const int num_apps = 4; + const char* app_ids[num_apps] = {"APP0", "APP1", "APP2", "APP3"}; + DltDaemonApplication *apps[num_apps] = {}; + + DltTraceLoadSettings* settings; + char ecu[DLT_ID_SIZE] = {}; + + pid_t pid = 0; + int fd = 15; + const char *desc = "HELLO_TEST"; + + init_daemon(&daemon, ecu); + setup_trace_load_settings(daemon); + + for (int i = 0; i < num_apps; i++) { + apps[i] = dlt_daemon_application_add(&daemon, (char *)app_ids[i], pid, (char *)desc, fd, ecu, 0); + EXPECT_FALSE(apps[i]->trace_load_settings == NULL); + } + + // Find settings for APP0 with context that has not been configured + settings = dlt_find_runtime_trace_load_settings( + apps[0]->trace_load_settings, apps[0]->trace_load_settings_count, + apps[0]->apid, "FOO"); + EXPECT_EQ(memcmp(settings, &daemon.preconfigured_trace_load_settings[0], sizeof(DltTraceLoadSettings)), 0); + + // Find settings for APP1 with context that has been configured + settings = dlt_find_runtime_trace_load_settings( + apps[1]->trace_load_settings, apps[1]->trace_load_settings_count, + apps[1]->apid, "CT01"); + EXPECT_EQ(memcmp(settings, &daemon.preconfigured_trace_load_settings[1], sizeof(DltTraceLoadSettings)), 0); + + settings = dlt_find_runtime_trace_load_settings( + apps[1]->trace_load_settings, apps[1]->trace_load_settings_count, + apps[1]->apid, "CT02"); + EXPECT_EQ(memcmp(settings, &daemon.preconfigured_trace_load_settings[2], sizeof(DltTraceLoadSettings)), 0); + + settings = dlt_find_runtime_trace_load_settings( + apps[1]->trace_load_settings, apps[1]->trace_load_settings_count, + apps[1]->apid, "CT03"); + EXPECT_EQ(memcmp(settings, &daemon.preconfigured_trace_load_settings[3], sizeof(DltTraceLoadSettings)), 0); + + // This context does not have a configuration, so the default element should be returned + settings = dlt_find_runtime_trace_load_settings( + apps[1]->trace_load_settings, apps[1]->trace_load_settings_count, + apps[1]->apid, "CTXX"); + matches_default_element(apps[1], settings); + + // APP2 has app id and context configured + settings = dlt_find_runtime_trace_load_settings( + apps[2]->trace_load_settings, apps[2]->trace_load_settings_count, + apps[2]->apid, "CTXX"); + EXPECT_EQ(memcmp(settings, &daemon.preconfigured_trace_load_settings[4], sizeof(DltTraceLoadSettings)), 0); + + settings = dlt_find_runtime_trace_load_settings( + apps[2]->trace_load_settings, apps[2]->trace_load_settings_count, + apps[2]->apid, "CT01"); + EXPECT_EQ(memcmp(settings, &daemon.preconfigured_trace_load_settings[5], sizeof(DltTraceLoadSettings)), 0); + + // Find settings for APP3 that has not been configured at all + settings = dlt_find_runtime_trace_load_settings( + apps[3]->trace_load_settings, apps[3]->trace_load_settings_count, + apps[3]->apid, "TEST"); + matches_default_element(apps[3], settings); + + // Nullptr for everything + settings = dlt_find_runtime_trace_load_settings(NULL, 0, NULL, NULL); + EXPECT_TRUE(settings == NULL); + + // Nullptr for app + settings = dlt_find_runtime_trace_load_settings( + apps[0]->trace_load_settings, apps[0]->trace_load_settings_count, NULL, + NULL); + EXPECT_TRUE(settings == NULL); + + // Empty appid + settings = dlt_find_runtime_trace_load_settings( + apps[0]->trace_load_settings, apps[0]->trace_load_settings_count, "", + NULL); + EXPECT_TRUE(settings == NULL); + + // Context id invalid + settings = dlt_find_runtime_trace_load_settings( + apps[0]->trace_load_settings, apps[0]->trace_load_settings_count, + apps[0]->apid, "FOOBAR"); + EXPECT_EQ(memcmp(settings, &daemon.preconfigured_trace_load_settings[0], sizeof(DltTraceLoadSettings)), 0); + + // remove application, add it again and check if the settings are still there + dlt_daemon_application_del(&daemon, apps[0], ecu, 0); + apps[0] = dlt_daemon_application_add(&daemon, (char *)app_ids[0], pid, (char *)desc, fd, ecu, 0); + settings = dlt_find_runtime_trace_load_settings( + apps[0]->trace_load_settings, apps[0]->trace_load_settings_count, + apps[0]->apid, "FOO"); + EXPECT_EQ(memcmp(settings, &daemon.preconfigured_trace_load_settings[0], sizeof(DltTraceLoadSettings)), 0); + + EXPECT_EQ(0, dlt_daemon_free(&daemon, 0)); + + // Test after freeing daemon + settings = dlt_find_runtime_trace_load_settings( + apps[0]->trace_load_settings, apps[0]->trace_load_settings_count, + apps[0]->apid, NULL); + EXPECT_TRUE(settings == NULL); + EXPECT_TRUE(apps[0]->trace_load_settings == NULL); +} + +TEST(t_trace_load_keep_message, normal) { + DltDaemon daemon; + DltDaemonLocal daemon_local = {}; + const int num_apps = 4; + const char* app_ids[num_apps] = {"APP0", "APP1", "APP2", "APP3"}; + DltDaemonApplication *apps[num_apps] = {}; + + char ecu[DLT_ID_SIZE] = {}; + + pid_t pid = 0; + int fd = 15; + const char *desc = "HELLO_TEST"; + + const auto set_extended_header = [&daemon_local]() { + daemon_local.msg.extendedheader = (DltExtendedHeader *)(daemon_local.msg.headerbuffer + sizeof(DltStorageHeader) + + sizeof(DltStandardHeader)); + memset(daemon_local.msg.extendedheader, 0, sizeof(DltExtendedHeader)); + }; + + const auto set_extended_header_log_level = [&daemon_local](unsigned int log_level) { + daemon_local.msg.extendedheader->msin = ((daemon_local.msg.extendedheader->msin) & ~DLT_MSIN_MTIN) | ((log_level) << DLT_MSIN_MTIN_SHIFT); + }; + + const auto log_until_hard_limit_reached = [&daemon, &daemon_local] (DltDaemonApplication *app) { + while (trace_load_keep_message(app, _trace_load_send_size, &daemon, &daemon_local, 0)); + }; + + const auto check_debug_and_trace_can_log = [&](DltDaemonApplication* app) { + // messages for debug and verbose logs are never dropped + set_extended_header_log_level(DLT_LOG_VERBOSE); + EXPECT_TRUE(trace_load_keep_message(app, + app->trace_load_settings->hard_limit * 10, + &daemon, &daemon_local, 0)); + set_extended_header_log_level(DLT_LOG_DEBUG); + EXPECT_TRUE(trace_load_keep_message(app, + app->trace_load_settings->hard_limit * 10, + &daemon, &daemon_local, 0)); + + set_extended_header_log_level(DLT_LOG_INFO); + }; + + init_daemon(&daemon, ecu); + setup_trace_load_settings(daemon); + + for (int i = 0; i < num_apps; i++) { + apps[i] = dlt_daemon_application_add(&daemon, (char *)app_ids[i], pid, (char *)desc, fd, ecu, 0); + EXPECT_FALSE(apps[i]->trace_load_settings == NULL); + } + + // messages without extended header will be kept + daemon_local.msg.extendedheader = NULL; + EXPECT_TRUE(trace_load_keep_message( + apps[0], apps[0]->trace_load_settings->soft_limit, &daemon, &daemon_local, 0)); + + set_extended_header(); + check_debug_and_trace_can_log(apps[0]); + + // messages for apps that have not been registered should be dropped + DltDaemonApplication app = {}; + EXPECT_FALSE(trace_load_keep_message(&app, 42, &daemon, &daemon_local, 0)); + + // Test if hard limit is reached for applications that only configure an application id + // Meaning that the limit is shared between all contexts + memcpy(daemon_local.msg.extendedheader->ctid, "CT01", DLT_ID_SIZE); + log_until_hard_limit_reached(apps[0]); + EXPECT_FALSE(trace_load_keep_message(apps[0], _trace_load_send_size, &daemon, &daemon_local, 0)); + memcpy(daemon_local.msg.extendedheader->ctid, "CT02", DLT_ID_SIZE); + EXPECT_FALSE(trace_load_keep_message(apps[0], _trace_load_send_size, &daemon, &daemon_local, 0)); + // Even after exhausting the limits, make sure debug and trace still work + check_debug_and_trace_can_log(apps[0]); + + // APP1 has only three contexts, no app id + memcpy(daemon_local.msg.extendedheader->ctid, "CT01", DLT_ID_SIZE); + log_until_hard_limit_reached(apps[1]); + EXPECT_FALSE(trace_load_keep_message(apps[1], _trace_load_send_size, &daemon, &daemon_local, 0)); + // CT01 has reached its limit, make sure CT02 still can log + memcpy(daemon_local.msg.extendedheader->ctid, "CT02", DLT_ID_SIZE); + EXPECT_TRUE(trace_load_keep_message(apps[1], _trace_load_send_size, &daemon, &daemon_local, 0)); + // Set CT02 to hard limit to hard limit 0, which should drop all messages + apps[1]->trace_load_settings[2].hard_limit = 0; + EXPECT_FALSE(trace_load_keep_message(apps[1], _trace_load_send_size, &daemon, &daemon_local, 0)); + + // APP2 has context and app id configured + // Exhaust app limit first + memcpy(daemon_local.msg.extendedheader->ctid, "CTXX", DLT_ID_SIZE); + log_until_hard_limit_reached(apps[2]); + EXPECT_FALSE(trace_load_keep_message(apps[2], _trace_load_send_size, &daemon, &daemon_local, 0)); + // Context logging should still be possible + memcpy(daemon_local.msg.extendedheader->ctid, "CT01", DLT_ID_SIZE); + EXPECT_TRUE(trace_load_keep_message(apps[2], _trace_load_send_size, &daemon, &daemon_local, 0)); + + // Test not configured context + memcpy(daemon_local.msg.extendedheader->ctid, "CTXX", DLT_ID_SIZE); + EXPECT_EQ( + trace_load_keep_message(apps[1], _trace_load_send_size, &daemon, &daemon_local, 0), + DLT_TRACE_LOAD_DAEMON_HARD_LIMIT_DEFAULT != 0); + + EXPECT_EQ(0, dlt_daemon_free(&daemon, 0)); +} + +#endif + +/*##############################################################################################################################*/ +/*##############################################################################################################################*/ +/*##############################################################################################################################*/ + + + + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + ::testing::FLAGS_gtest_break_on_failure = true; + /*::testing::FLAGS_gtest_filter = "*.normal"; */ + return RUN_ALL_TESTS(); +} diff --git a/tests/gtest_dlt_daemon_common.cpp b/tests/gtest_dlt_daemon_common.cpp index f7c036f46..3334c934e 100644 --- a/tests/gtest_dlt_daemon_common.cpp +++ b/tests/gtest_dlt_daemon_common.cpp @@ -1727,6 +1727,278 @@ TEST(t_dlt_daemon_user_send_log_state, nullpointer) } /* End Method: dlt_daemon_common::dlt_daemon_user_send_log_state */ + +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE +TEST(t_dlt_daemon_find_preconfigured_trace_load_settings, nullpointer) +{ + int num_settings; + DltTraceLoadSettings *settings = NULL; + DltDaemon daemon; + + EXPECT_EQ(dlt_daemon_find_preconfigured_trace_load_settings( + NULL, "APP1", "CTID", &settings, &num_settings, 0), + DLT_RETURN_WRONG_PARAMETER); + EXPECT_EQ(dlt_daemon_find_preconfigured_trace_load_settings( + &daemon, NULL, "CTID", &settings, &num_settings, 0), + DLT_RETURN_WRONG_PARAMETER); +} + +TEST(t_dlt_daemon_find_preconfigured_trace_load_settings, empty_trace_settings) +{ + int num_settings; + DltTraceLoadSettings *settings = NULL; + DltDaemon daemon; + daemon.preconfigured_trace_load_settings = NULL; + + EXPECT_EQ(dlt_daemon_find_preconfigured_trace_load_settings( + &daemon, "APP1", NULL, &settings, &num_settings, 0), + DLT_RETURN_OK); + + // test wrong trace load settings count + daemon.preconfigured_trace_load_settings = (DltTraceLoadSettings *)malloc( + sizeof(DltTraceLoadSettings)); + daemon.preconfigured_trace_load_settings_count = 0; + + EXPECT_EQ(dlt_daemon_find_preconfigured_trace_load_settings( + &daemon, "APP1", "CTID", &settings, &num_settings, 0), + DLT_RETURN_OK); + + free(daemon.preconfigured_trace_load_settings); +} + +TEST(t_dlt_daemon_find_preconfigured_trace_load_settings, app_id_not_found) +{ + int num_settings = 42; + DltTraceLoadSettings *settings = (DltTraceLoadSettings *)42; + DltDaemon daemon; + + // test wrong trace load settings count + daemon.preconfigured_trace_load_settings = (DltTraceLoadSettings *)malloc( + sizeof(DltTraceLoadSettings)); + memset(daemon.preconfigured_trace_load_settings, 0, + sizeof(DltTraceLoadSettings)); + + strcpy(daemon.preconfigured_trace_load_settings[0].apid, "APP2"); + daemon.preconfigured_trace_load_settings_count = 1; + + EXPECT_EQ(dlt_daemon_find_preconfigured_trace_load_settings( + &daemon, "APP1", NULL, &settings, &num_settings, 0), + DLT_RETURN_OK); + EXPECT_EQ(num_settings, 0); + EXPECT_TRUE(settings == NULL); + + free(daemon.preconfigured_trace_load_settings); +} + +static void setup_trace_load_settings(DltDaemon &daemon) +{ + daemon.preconfigured_trace_load_settings_count = 7; + daemon.preconfigured_trace_load_settings = (DltTraceLoadSettings *)malloc( + daemon.preconfigured_trace_load_settings_count * sizeof(DltTraceLoadSettings)); + memset(daemon.preconfigured_trace_load_settings, 0, + daemon.preconfigured_trace_load_settings_count * sizeof(DltTraceLoadSettings)); + + strcpy(daemon.preconfigured_trace_load_settings[0].apid, "APP2"); + strcpy(daemon.preconfigured_trace_load_settings[0].ctid, "CTID"); + + strcpy(daemon.preconfigured_trace_load_settings[1].apid, "APP1"); + strcpy(daemon.preconfigured_trace_load_settings[1].ctid, "CTID"); + daemon.preconfigured_trace_load_settings[1].soft_limit = 21; + daemon.preconfigured_trace_load_settings[1].hard_limit = 42; + + strcpy(daemon.preconfigured_trace_load_settings[2].apid, "APP1"); + strcpy(daemon.preconfigured_trace_load_settings[2].ctid, "CT02"); + daemon.preconfigured_trace_load_settings[2].soft_limit = 11; + daemon.preconfigured_trace_load_settings[2].hard_limit = 22; + + strcpy(daemon.preconfigured_trace_load_settings[3].apid, "APP1"); + daemon.preconfigured_trace_load_settings[3].soft_limit = 44; + daemon.preconfigured_trace_load_settings[3].hard_limit = 55; + + strcpy(daemon.preconfigured_trace_load_settings[4].apid, "APP3"); + strcpy(daemon.preconfigured_trace_load_settings[4].ctid, "CT03"); + daemon.preconfigured_trace_load_settings[4].soft_limit = 111; + daemon.preconfigured_trace_load_settings[4].hard_limit = 222; + + strcpy(daemon.preconfigured_trace_load_settings[5].apid, "APP3"); + strcpy(daemon.preconfigured_trace_load_settings[5].ctid, "CT01"); + daemon.preconfigured_trace_load_settings[5].soft_limit = 333; + daemon.preconfigured_trace_load_settings[5].hard_limit = 444; + + strcpy(daemon.preconfigured_trace_load_settings[6].apid, "APP1"); + strcpy(daemon.preconfigured_trace_load_settings[6].ctid, "CT03"); + daemon.preconfigured_trace_load_settings[6].soft_limit = 555; + daemon.preconfigured_trace_load_settings[6].hard_limit = 666; + + qsort(daemon.preconfigured_trace_load_settings, daemon.preconfigured_trace_load_settings_count, + sizeof(DltTraceLoadSettings), dlt_daemon_compare_trace_load_settings); +} + +TEST(t_dlt_daemon_find_preconfigured_trace_load_settings, search_with_context) +{ + int num_settings = 42; + DltTraceLoadSettings *settings = NULL; + DltDaemon daemon; + + setup_trace_load_settings(daemon); + + EXPECT_EQ(dlt_daemon_find_preconfigured_trace_load_settings( + &daemon, "APP1", "CTID", &settings, &num_settings, 0), + DLT_RETURN_OK); + EXPECT_EQ(num_settings, 1); + EXPECT_EQ(memcmp(settings, &daemon.preconfigured_trace_load_settings[3], + sizeof(DltTraceLoadSettings)), + 0); + + free(settings); + free(daemon.preconfigured_trace_load_settings); +} + +TEST(t_dlt_daemon_find_preconfigured_trace_load_settings, search_with_app_id) +{ + int num_settings = 42; + DltTraceLoadSettings *settings = NULL; + DltDaemon daemon; + + setup_trace_load_settings(daemon); + + EXPECT_EQ(dlt_daemon_find_preconfigured_trace_load_settings( + &daemon, "APP1", NULL, &settings, &num_settings, 0), + DLT_RETURN_OK); + EXPECT_EQ(num_settings, 4); + for (int i = 0; i < num_settings; i++) { + EXPECT_EQ(memcmp(&settings[i], + &daemon.preconfigured_trace_load_settings[i], + sizeof(DltTraceLoadSettings)),0); + } + + free(settings); + free(daemon.preconfigured_trace_load_settings); +} + +TEST(t_dlt_daemon_application_add, trace_load_settings_not_found) +{ + DltDaemon daemon; + DltGateway gateway; + const char *apid = "TEST"; + pid_t pid = 0; + const char *desc = "HELLO_TEST"; + DltDaemonApplication *app = NULL; + char ecu[] = "ECU1"; + int fd = 15; + + EXPECT_EQ(0, dlt_daemon_init(&daemon, DLT_DAEMON_RINGBUFFER_MIN_SIZE, + DLT_DAEMON_RINGBUFFER_MAX_SIZE, + DLT_DAEMON_RINGBUFFER_STEP_SIZE, + DLT_RUNTIME_DEFAULT_DIRECTORY, DLT_LOG_INFO, + DLT_TRACE_STATUS_OFF, 0, 0)); + dlt_set_id(daemon.ecuid, ecu); + EXPECT_EQ(0, dlt_daemon_init_user_information(&daemon, &gateway, 0, 0)); + EXPECT_EQ(DLT_RETURN_OK, + strncmp(daemon.ecuid, daemon.user_list[0].ecu, DLT_ID_SIZE)); + + app = dlt_daemon_application_add(&daemon, (char *)apid, pid, (char *)desc, + fd, ecu, 0); + EXPECT_FALSE(app->trace_load_settings == NULL); + EXPECT_EQ(app->trace_load_settings_count, 1); + EXPECT_EQ(app->trace_load_settings[0].soft_limit, + DLT_TRACE_LOAD_DAEMON_SOFT_LIMIT_DEFAULT); + EXPECT_EQ(app->trace_load_settings[0].hard_limit, + DLT_TRACE_LOAD_DAEMON_HARD_LIMIT_DEFAULT); + EXPECT_STREQ(app->trace_load_settings[0].apid, apid); + EXPECT_TRUE(app->trace_load_settings[0].ctid == NULL || + strlen(app->trace_load_settings[0].ctid) == 0); + + EXPECT_LE(0, dlt_daemon_application_del(&daemon, app, ecu, 0)); + EXPECT_LE(0, dlt_daemon_applications_clear(&daemon, ecu, 0)); + + dlt_daemon_application_del(&daemon, app, ecu, 0); + EXPECT_TRUE(app->trace_load_settings == NULL); + EXPECT_EQ(0, dlt_daemon_free(&daemon, 0)); +} + +TEST(t_dlt_daemon_application_add, trace_load_settings_configured) +{ + DltDaemon daemon; + DltGateway gateway; + const char *apid = "APP1"; + pid_t pid = 0; + const char *desc = "HELLO_TEST"; + DltDaemonApplication *app = NULL; + char ecu[] = "ECU1"; + int fd = 15; + + EXPECT_EQ(0, dlt_daemon_init(&daemon, DLT_DAEMON_RINGBUFFER_MIN_SIZE, + DLT_DAEMON_RINGBUFFER_MAX_SIZE, + DLT_DAEMON_RINGBUFFER_STEP_SIZE, + DLT_RUNTIME_DEFAULT_DIRECTORY, DLT_LOG_INFO, + DLT_TRACE_STATUS_OFF, 0, 0)); + + // must be done after daemon init, else data is overwritten + setup_trace_load_settings(daemon); + + dlt_set_id(daemon.ecuid, ecu); + EXPECT_EQ(0, dlt_daemon_init_user_information(&daemon, &gateway, 0, 0)); + EXPECT_EQ(DLT_RETURN_OK, + strncmp(daemon.ecuid, daemon.user_list[0].ecu, DLT_ID_SIZE)); + + app = dlt_daemon_application_add(&daemon, (char *)apid, pid, (char *)desc, + fd, ecu, 0); + EXPECT_FALSE(app->trace_load_settings == NULL); + EXPECT_EQ(app->trace_load_settings_count, 4); + + for (uint32_t i = 0; i < app->trace_load_settings_count; i++) { + EXPECT_EQ(memcmp(&app->trace_load_settings[i], + &daemon.preconfigured_trace_load_settings[i], + sizeof(DltTraceLoadSettings)), 0); + } + + EXPECT_LE(0, dlt_daemon_application_del(&daemon, app, ecu, 0)); + EXPECT_LE(0, dlt_daemon_applications_clear(&daemon, ecu, 0)); + + dlt_daemon_application_del(&daemon, app, ecu, 0); + EXPECT_TRUE(app->trace_load_settings == NULL); + EXPECT_EQ(0, dlt_daemon_free(&daemon, 0)); +} + +TEST(t_dlt_daemon_user_send_trace_load_config, normal) +{ + DltDaemon daemon; + DltGateway gateway; + const char *apid = "APP1"; + pid_t pid = 0; + const char *desc = "HELLO_TEST"; + DltDaemonApplication *app = NULL; + char ecu[] = "ECU1"; + int fd = 15; + + EXPECT_EQ(0, dlt_daemon_init(&daemon, DLT_DAEMON_RINGBUFFER_MIN_SIZE, + DLT_DAEMON_RINGBUFFER_MAX_SIZE, + DLT_DAEMON_RINGBUFFER_STEP_SIZE, + DLT_RUNTIME_DEFAULT_DIRECTORY, DLT_LOG_INFO, + DLT_TRACE_STATUS_OFF, 0, 0)); + + dlt_set_id(daemon.ecuid, ecu); + EXPECT_EQ(0, dlt_daemon_init_user_information(&daemon, &gateway, 0, 0)); + EXPECT_EQ(DLT_RETURN_OK, + strncmp(daemon.ecuid, daemon.user_list[0].ecu, DLT_ID_SIZE)); + + app = dlt_daemon_application_add(&daemon, (char *)apid, pid, (char *)desc, + fd, ecu, 0); + dlt_daemon_user_send_trace_load_config(&daemon, app, 0); + dlt_daemon_application_del(&daemon, app, ecu, 0); + + setup_trace_load_settings(daemon); + + app = dlt_daemon_application_add(&daemon, (char *)apid, pid, (char *)desc, + fd, ecu, 0); + dlt_daemon_user_send_trace_load_config(&daemon, app, 0); + dlt_daemon_application_del(&daemon, app, ecu, 0); + EXPECT_EQ(0, dlt_daemon_free(&daemon, 0)); +} + +#endif + /*##############################################################################################################################*/ /*##############################################################################################################################*/ /*##############################################################################################################################*/ diff --git a/tests/gtest_dlt_user.cpp b/tests/gtest_dlt_user.cpp index dfd7860db..de7938b64 100644 --- a/tests/gtest_dlt_user.cpp +++ b/tests/gtest_dlt_user.cpp @@ -5333,6 +5333,51 @@ TEST(t_dlt_user_shutdown_while_init_is_running, normal) { EXPECT_EQ(last_free, DLT_RETURN_OK); } + +#ifdef DLT_TRACE_LOAD_CTRL_ENABLE + +/*/////////////////////////////////////// */ +/* t_dlt_user_run_into_trace_limit */ +TEST(t_dlt_user_run_into_trace_limit, normal) +{ + DltContext context; + DltContextData contextData; + unsigned int data; + + EXPECT_LE(DLT_RETURN_OK, dlt_register_app("TLMT", "dlt_user.c tests")); + EXPECT_LE(DLT_RETURN_OK, dlt_register_context(&context, "TEST", "dlt_user.c t_dlt_user_log_write_uint normal")); + + auto loadExceededReceived = false; + + for (int i = 0; i < DLT_TRACE_LOAD_CLIENT_HARD_LIMIT_DEFAULT;++i) { + /* normal values */ + EXPECT_LE(DLT_RETURN_OK, dlt_user_log_write_start(&context, &contextData, DLT_LOG_DEFAULT)); + data = 0; + EXPECT_LE(DLT_RETURN_OK, dlt_user_log_write_uint(&contextData, data)); + data = 1; + EXPECT_LE(DLT_RETURN_OK, dlt_user_log_write_uint(&contextData, data)); + data = 2; + EXPECT_LE(DLT_RETURN_OK, dlt_user_log_write_uint(&contextData, data)); + data = UINT_MAX; + EXPECT_LE(DLT_RETURN_OK, dlt_user_log_write_uint(&contextData, data)); + + loadExceededReceived = dlt_user_log_write_finish(&contextData) == DLT_RETURN_LOAD_EXCEEDED; + if (loadExceededReceived) { + // values are averaged over a minute, therefore a spike in load also triggers the limit + EXPECT_LE(i, DLT_TRACE_LOAD_CLIENT_HARD_LIMIT_DEFAULT); + break; + } + } + + EXPECT_TRUE(loadExceededReceived); + + // unregister return values are not checked because error is returned due to full local buffers + dlt_unregister_context(&context); + dlt_unregister_app(); +} + +#endif + /*/////////////////////////////////////// */ /* main */ int main(int argc, char **argv) diff --git a/util/calculate-load.py b/util/calculate-load.py new file mode 100644 index 000000000..15a2c0911 --- /dev/null +++ b/util/calculate-load.py @@ -0,0 +1,254 @@ +import argparse +import re +import subprocess +import os +import json +import math +from datetime import datetime, timedelta, timezone + + +# Function to calculate average payload size and round up to the next full integer +def calculate_payload_size_in_window(payload_sizes): + return sum(payload_sizes) + + +# Parse command-line arguments +def parse_arguments(): + parser = argparse.ArgumentParser( + description= + 'Process DLT messages to calculate the highest average payload size.') + + # Add the mode argument with choices for 'online' and 'offline' + parser.add_argument('--soft-limit-factor', + type=float, + default=1.5, + help='Factor to multiply the measured values with to get the soft limit recommendation (default: 1.1)') + parser.add_argument('--hard-limit-factor', + type=float, + default=2, + help='Factor to multiply the measured values with to get the hard limit recommendation (default: 1.3)') + parser.add_argument('--factor-multiplier', + type=int, + default=10, + help='Multiplier used for rounding the limits up. I.e. 13 -> 20, 123 -> 130 (default: 10)') + + parser.add_argument('--sum-contexts', + action='store_true', + default=False, + help='If set to true, all contexts will be summed, otherwise one measurement per context (default: True') + + # Add the mode argument with choices for 'online' and 'offline' + parser.add_argument('-m', '--mode', + type=str, + choices=['online', 'offline'], + default='online', + help='Mode to process DLT messages, online will read logs directly via dlt-receive, ' + 'offline will expect a dlt offline trace converted ' + 'to text using dlt-convert -a file > dlt.txt (default: online)') + + parser.add_argument('-o', '--output', + type=str, + choices=['json', 'dlt'], + default='dlt', + help='Output mode of script (default: json)') + + parser.add_argument('-e', '--ecu', + type=str, + default=None, + required=True, + help='The ECU ID to filter DLT messages') + + parser.add_argument('-p', '--port', + type=int, + default=3490, + help='Port to receive DLT messages (default: 3490) (only used in online mode)') + parser.add_argument('-a', '--address', + type=str, + default='127.0.0.1', + help='Address to receive DLT messages (default: 127.0.0.1) (only used in online mode)') + parser.add_argument('-t', '--time', + type=int, + default=300, + help='Runtime duration in seconds (default: 300) (only used in online mode)') + + # Create a subgroup for offline mode arguments + offline_group = parser.add_argument_group('Offline Mode Arguments') + offline_group.add_argument('-f', '--file', + type=str, + help='Path to the DLT file when in offline mode') + + parser.add_argument( + '-d', + '--debug', + action='store_true', + help='Include debug logs in the average (default: false)') + parser.add_argument( + '-v', + '--verbose', + action='store_true', + help='Include verbose logs in the average (default: false)') + + parser.add_argument('app_id', + type=str, + nargs='?', + help='Application ID to filter DLT messages, if not passed, all messages will be processed') + + parser.add_argument('context_id', + type=str, + default=None, + nargs='?', + help='Optional ID to filter DLT messages, needs application id!') + + args = parser.parse_args() + if not args.ecu: + parser.error("ECU ID is required.") + + # Validate the arguments based on the mode + if args.mode == 'offline': + if not args.file: + parser.error("Offline mode requires a file path using the -f or --file option.") + if not os.path.isfile(args.file): + parser.error("File path provided does not exist.") + else: + # In online mode, the file should not be used + if args.file: + parser.error("File path is not used in online mode.") + return args + + +def get_next_line_from_file(file): + with open(file, 'r', errors='ignore') as file: + for line in file: + yield line.strip() + + +def get_next_line_from_dlt_receive(process): + return process.stdout.readline().decode('utf-8', 'ignore').strip() + +def keep_running(end_time): + if end_time: + return datetime.now(timezone.utc) < end_time + return True + +def round_up_to(n, multiplier): + return math.ceil(n / multiplier) * multiplier + +def main(): + args = parse_arguments() + end_time = None + load = {} + + if args.mode == 'offline': + file_reader = get_next_line_from_file(args.file) + else: + # Prepare the command to receive messages with provided arguments + command = f"dlt-receive -a {args.address} -p {args.port}" + process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) + # Set the duration for which to receive messages + end_time = datetime.now(timezone.utc) + timedelta(seconds=args.time) + + if not args.app_id: + line_regex = re.compile(rf"{args.ecu}-{{0,3}}\s+(\w{{1,4}}-{{0,3}})\s+(\w{{1,4}}-{{0,3}})\s+log") + else: + if args.context_id: + line_regex = re.compile(rf"{args.ecu}-{{0,3}}\s+({args.app_id}-{{0,3}})\s+({args.context_id}-{{0,3}})\s+log") + else: + line_regex = re.compile(rf"{args.ecu}-{{0,3}}\s+({args.app_id}-{{0,3}})\s+(\w{{1,4}}-{{0,3}})\s+log") + + try: + # Process messages until the end time is reached + while keep_running(end_time): + if args.mode == 'offline': + line = next(file_reader) + else: + line = get_next_line_from_dlt_receive(process) + + if not line: + continue # Skip empty lines + + match = line_regex.search(line) + if not match: + continue + line_app_id = match.group(1).rstrip('-') + line_context_id = match.group(2).rstrip('-') + + # Check if the line contains the Application ID + if (('log debug' in line and not args.debug) or + ('log verbose' in line and not args.verbose)): + continue # Skip this line as it's a debug/verbose log and the flags are not set + + # Extract the payload size using regex + match = re.search(r'\[(.*)$', line) + if not match: + continue + if line_app_id not in load: + load[line_app_id] = {} + if line_context_id not in load[line_app_id]: + load[line_app_id][line_context_id] = {} + load[line_app_id][line_context_id]["payload_sizes"] = [] + load[line_app_id][line_context_id]["highest_average"] = 0 + load[line_app_id][line_context_id]["window_start_time"] = datetime.now(timezone.utc) + + payload = match.group(1).strip()[:-1] + payload_size = len(payload) + + load[line_app_id][line_context_id]["payload_sizes"].append(payload_size) + + # Check if 60 seconds have passed or if we're at the end of the runtime + current_time = datetime.now(timezone.utc) + if (current_time - load[line_app_id][line_context_id]["window_start_time"]).total_seconds() >= 60 or not keep_running(end_time): + average_payload_size = calculate_payload_size_in_window(load[line_app_id][line_context_id]["payload_sizes"]) + load[line_app_id][line_context_id]["highest_average"] = max(load[line_app_id][line_context_id]["highest_average"], average_payload_size) + load[line_app_id][line_context_id]["payload_sizes"] = [] # Reset for the next window + load[line_app_id][line_context_id]["window_start_time"] = current_time # Reset window start time + except StopIteration: + # done reading file + pass + finally: + results = {} + + # If there are any remaining payload sizes that have not been averaged, do so now + for app_id, context_ids in load.items(): + app_id_total = 0 + results[app_id] = {} + results[app_id]["contexts"] = {} + for context_id, data in context_ids.items(): + payload_sizes = data["payload_sizes"] + average_payload_size = calculate_payload_size_in_window(payload_sizes) + highest_average = max(data["highest_average"], average_payload_size) / 60 + app_id_total += highest_average + + if not args.sum_contexts: + results[app_id]["contexts"][context_id] = {} + results[app_id]["contexts"][context_id]["measured"] = highest_average + results[app_id]["contexts"][context_id]["soft"] = round_up_to(highest_average * args.soft_limit_factor, args.factor_multiplier) + results[app_id]["contexts"][context_id]["hard"] = round_up_to(highest_average * args.hard_limit_factor, args.factor_multiplier) + results[app_id]["contexts"] = dict(sorted(results[app_id]["contexts"].items())) + + results[app_id] + if not args.context_id: + results[app_id]["measured"] = app_id_total + results[app_id]["soft"] = round_up_to(app_id_total * args.soft_limit_factor, args.factor_multiplier) + results[app_id]["hard"] = round_up_to(app_id_total * args.hard_limit_factor, args.factor_multiplier) + + results = dict(sorted(results.items())) + if args.output == "json": + print(json.dumps(results, indent=4)) + else: + if args.sum_contexts: + for app_id, data in results.items(): + print(f"{app_id} {data['soft']} {data['hard']}") + else: + for app_id, context_ids in results.items(): + for context_id, data in context_ids["contexts"].items(): + print(f"{app_id} {context_id} {data['soft']} {data['hard']}") + + if args.mode == 'offline': + pass + else: + # Ensure the subprocess is terminated + process.terminate() + + +if __name__ == "__main__": + main()