// SPDX-License-Identifier: GPL-3.0-or-later #include "ebpf.h" #include "ebpf_functions.h" /***************************************************************** * EBPF SELECT MODULE *****************************************************************/ /** * Select Module * * @param thread_name name of the thread we are looking for. * * @return it returns a pointer for the module that has thread_name on success or NULL otherwise. */ ebpf_module_t *ebpf_functions_select_module(const char *thread_name) { int i; for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { if (strcmp(ebpf_modules[i].thread_name, thread_name) == 0) { return &ebpf_modules[i]; } } return NULL; } /***************************************************************** * EBPF HELP FUNCTIONS *****************************************************************/ /** * Thread Help * * Shows help with all options accepted by thread function. * * @param transaction the transaction id that Netdata sent for this function execution */ static void ebpf_function_thread_manipulation_help(const char *transaction) { pthread_mutex_lock(&lock); pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600); fprintf(stdout, "%s", "ebpf.plugin / thread\n" "\n" "Function `thread` allows user to control eBPF threads.\n" "\n" "The following filters are supported:\n" "\n" " thread:NAME\n" " Shows information for the thread NAME. Names are listed inside `ebpf.d.conf`.\n" "\n" " enable:NAME:PERIOD\n" " Enable a specific thread named `NAME` to run a specific PERIOD in seconds. When PERIOD is not\n" " specified plugin will use the default 300 seconds\n" "\n" " disable:NAME\n" " Disable a sp.\n" "\n" "Filters can be combined. Each filter can be given only one time.\n" "Process thread is not controlled by functions until we finish the creation of functions per thread..\n" ); pluginsd_function_result_end_to_stdout(); fflush(stdout); pthread_mutex_unlock(&lock); } /***************************************************************** * EBPF ERROR FUNCTIONS *****************************************************************/ /** * Function error * * Show error when a wrong function is given * * @param transaction the transaction id that Netdata sent for this function execution * @param code the error code to show with the message. * @param msg the error message */ static void ebpf_function_error(const char *transaction, int code, const char *msg) { char buffer[PLUGINSD_LINE_MAX + 1]; json_escape_string(buffer, msg, PLUGINSD_LINE_MAX); pluginsd_function_result_begin_to_stdout(transaction, code, "application/json", now_realtime_sec()); fprintf(stdout, "{\"status\":%d,\"error_message\":\"%s\"}", code, buffer); pluginsd_function_result_end_to_stdout(); } /***************************************************************** * EBPF THREAD FUNCTION *****************************************************************/ /** * Function enable * * Enable a specific thread. * * @param transaction the transaction id that Netdata sent for this function execution * @param function function name and arguments given to thread. * @param line_buffer buffer used to parse args * @param line_max Number of arguments given * @param timeout The function timeout * @param em The structure with thread information */ static void ebpf_function_thread_manipulation(const char *transaction, char *function __maybe_unused, char *line_buffer __maybe_unused, int line_max __maybe_unused, int timeout __maybe_unused, ebpf_module_t *em) { char *words[PLUGINSD_MAX_WORDS] = { NULL }; char message[512]; uint32_t show_specific_thread = 0; size_t num_words = quoted_strings_splitter_pluginsd(function, words, PLUGINSD_MAX_WORDS); for(int i = 1; i < PLUGINSD_MAX_WORDS ;i++) { const char *keyword = get_word(words, num_words, i); if (!keyword) break; ebpf_module_t *lem; if(strncmp(keyword, EBPF_THREADS_ENABLE_CATEGORY, sizeof(EBPF_THREADS_ENABLE_CATEGORY) -1) == 0) { char thread_name[128]; int period = -1; const char *name = &keyword[sizeof(EBPF_THREADS_ENABLE_CATEGORY) - 1]; char *separator = strchr(name, ':'); if (separator) { strncpyz(thread_name, name, separator - name); period = str2i(++separator); } else { strncpyz(thread_name, name, strlen(name)); } lem = ebpf_functions_select_module(thread_name); if (!lem) { snprintfz(message, 511, "%s%s", EBPF_PLUGIN_THREAD_FUNCTION_ERROR_THREAD_NOT_FOUND, name); ebpf_function_error(transaction, HTTP_RESP_NOT_FOUND, message); return; } pthread_mutex_lock(&ebpf_exit_cleanup); if (lem->enabled > NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { struct netdata_static_thread *st = lem->thread; // Load configuration again ebpf_update_module(lem, default_btf, running_on_kernel, isrh); // another request for thread that already ran, cleanup and restart if (st->thread) freez(st->thread); if (period <= 0) period = EBPF_DEFAULT_LIFETIME; st->thread = mallocz(sizeof(netdata_thread_t)); lem->enabled = NETDATA_THREAD_EBPF_FUNCTION_RUNNING; lem->lifetime = period; #ifdef NETDATA_INTERNAL_CHECKS netdata_log_info("Starting thread %s with lifetime = %d", thread_name, period); #endif netdata_thread_create(st->thread, st->name, NETDATA_THREAD_OPTION_DEFAULT, st->start_routine, lem); } else { lem->running_time = 0; if (period > 0) // user is modifying period to run lem->lifetime = period; #ifdef NETDATA_INTERNAL_CHECKS netdata_log_info("Thread %s had lifetime updated for %d", thread_name, period); #endif } pthread_mutex_unlock(&ebpf_exit_cleanup); } else if(strncmp(keyword, EBPF_THREADS_DISABLE_CATEGORY, sizeof(EBPF_THREADS_DISABLE_CATEGORY) -1) == 0) { const char *name = &keyword[sizeof(EBPF_THREADS_DISABLE_CATEGORY) - 1]; lem = ebpf_functions_select_module(name); if (!lem) { snprintfz(message, 511, "%s%s", EBPF_PLUGIN_THREAD_FUNCTION_ERROR_THREAD_NOT_FOUND, name); ebpf_function_error(transaction, HTTP_RESP_NOT_FOUND, message); return; } pthread_mutex_lock(&ebpf_exit_cleanup); if (lem->enabled < NETDATA_THREAD_EBPF_STOPPING && lem->thread->thread) { lem->lifetime = 0; lem->running_time = lem->update_every; netdata_thread_cancel(*lem->thread->thread); } pthread_mutex_unlock(&ebpf_exit_cleanup); } else if(strncmp(keyword, EBPF_THREADS_SELECT_THREAD, sizeof(EBPF_THREADS_SELECT_THREAD) -1) == 0) { const char *name = &keyword[sizeof(EBPF_THREADS_SELECT_THREAD) - 1]; lem = ebpf_functions_select_module(name); if (!lem) { snprintfz(message, 511, "%s%s", EBPF_PLUGIN_THREAD_FUNCTION_ERROR_THREAD_NOT_FOUND, name); ebpf_function_error(transaction, HTTP_RESP_NOT_FOUND, message); return; } show_specific_thread |= 1<thread_id; } else if(strncmp(keyword, "help", 4) == 0) { ebpf_function_thread_manipulation_help(transaction); return; } } time_t expires = now_realtime_sec() + em->update_every; BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX, NULL); buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_NEWLINE_ON_ARRAY_ITEMS); buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK); buffer_json_member_add_string(wb, "type", "table"); buffer_json_member_add_time_t(wb, "update_every", em->update_every); buffer_json_member_add_string(wb, "help", EBPF_PLUGIN_THREAD_FUNCTION_DESCRIPTION); // Collect data buffer_json_member_add_array(wb, "data"); int i; for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { if (show_specific_thread && !(show_specific_thread & 1<thread_name); // description buffer_json_add_array_item_string(wb, wem->thread_description); // Either it is not running or received a disabled signal and it is stopping. if (wem->enabled > NETDATA_THREAD_EBPF_FUNCTION_RUNNING || (!wem->lifetime && (int)wem->running_time == wem->update_every)) { // status buffer_json_add_array_item_string(wb, EBPF_THREAD_STATUS_STOPPED); // Time remaining buffer_json_add_array_item_uint64(wb, 0); // action buffer_json_add_array_item_string(wb, "NULL"); } else { // status buffer_json_add_array_item_string(wb, EBPF_THREAD_STATUS_RUNNING); // Time remaining buffer_json_add_array_item_uint64(wb, (wem->lifetime) ? (wem->lifetime - wem->running_time) : 0); // action buffer_json_add_array_item_string(wb, "Enabled/Disabled"); } buffer_json_array_close(wb); } buffer_json_array_close(wb); // data buffer_json_member_add_object(wb, "columns"); { int fields_id = 0; // IMPORTANT! // THE ORDER SHOULD BE THE SAME WITH THE VALUES! buffer_rrdf_table_add_field(wb, fields_id++, "Thread", "Thread Name", RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL); buffer_rrdf_table_add_field(wb, fields_id++, "Description", "Thread Desc", RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL); buffer_rrdf_table_add_field(wb, fields_id++, "Status", "Thread Status", RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL); buffer_rrdf_table_add_field(wb, fields_id++, "Time", "Time Remaining", RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER, 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_NONE, NULL); buffer_rrdf_table_add_field(wb, fields_id++, "Action", "Thread Action", RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL); } buffer_json_object_close(wb); // columns buffer_json_member_add_string(wb, "default_sort_column", "Thread"); buffer_json_member_add_object(wb, "charts"); { // Threads buffer_json_member_add_object(wb, "eBPFThreads"); { buffer_json_member_add_string(wb, "name", "Threads"); buffer_json_member_add_string(wb, "type", "line"); buffer_json_member_add_array(wb, "columns"); { buffer_json_add_array_item_string(wb, "Threads"); } buffer_json_array_close(wb); } buffer_json_object_close(wb); // Life Time buffer_json_member_add_object(wb, "eBPFLifeTime"); { buffer_json_member_add_string(wb, "name", "LifeTime"); buffer_json_member_add_string(wb, "type", "line"); buffer_json_member_add_array(wb, "columns"); { buffer_json_add_array_item_string(wb, "Threads"); buffer_json_add_array_item_string(wb, "Time"); } buffer_json_array_close(wb); } buffer_json_object_close(wb); } buffer_json_object_close(wb); // charts // Do we use only on fields that can be groupped? buffer_json_member_add_object(wb, "group_by"); { // group by Status buffer_json_member_add_object(wb, "Status"); { buffer_json_member_add_string(wb, "name", "Thread status"); buffer_json_member_add_array(wb, "columns"); { buffer_json_add_array_item_string(wb, "Status"); } buffer_json_array_close(wb); } buffer_json_object_close(wb); } buffer_json_object_close(wb); // group_by buffer_json_member_add_time_t(wb, "expires", expires); buffer_json_finalize(wb); // Lock necessary to avoid race condition pthread_mutex_lock(&lock); pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "application/json", expires); fwrite(buffer_tostring(wb), buffer_strlen(wb), 1, stdout); pluginsd_function_result_end_to_stdout(); fflush(stdout); pthread_mutex_unlock(&lock); buffer_free(wb); } /***************************************************************** * EBPF FUNCTION THREAD *****************************************************************/ /** * FUNCTION thread. * * @param ptr a `ebpf_module_t *`. * * @return always NULL. */ void *ebpf_function_thread(void *ptr) { ebpf_module_t *em = (ebpf_module_t *)ptr; char buffer[PLUGINSD_LINE_MAX + 1]; char *s = NULL; while(!ebpf_exit_plugin && (s = fgets(buffer, PLUGINSD_LINE_MAX, stdin))) { char *words[PLUGINSD_MAX_WORDS] = { NULL }; size_t num_words = quoted_strings_splitter_pluginsd(buffer, words, PLUGINSD_MAX_WORDS); const char *keyword = get_word(words, num_words, 0); if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION) == 0) { char *transaction = get_word(words, num_words, 1); char *timeout_s = get_word(words, num_words, 2); char *function = get_word(words, num_words, 3); if(!transaction || !*transaction || !timeout_s || !*timeout_s || !function || !*function) { netdata_log_error("Received incomplete %s (transaction = '%s', timeout = '%s', function = '%s'). Ignoring it.", keyword, transaction?transaction:"(unset)", timeout_s?timeout_s:"(unset)", function?function:"(unset)"); } else { int timeout = str2i(timeout_s); if (!strncmp(function, EBPF_FUNCTION_THREAD, sizeof(EBPF_FUNCTION_THREAD) - 1)) ebpf_function_thread_manipulation(transaction, function, buffer, PLUGINSD_LINE_MAX + 1, timeout, em); else ebpf_function_error(transaction, HTTP_RESP_NOT_FOUND, "No function with this name found in ebpf.plugin."); } } else netdata_log_error("Received unknown command: %s", keyword ? keyword : "(unset)"); } return NULL; }