@cryptotaxi247 / netdata-1 / commits / ed3ba4451

functions cancelling (#15977)

Costa Tsaousis committed Sep 18, 2023 at 19:21 UTC ed3ba445145a8403e77b1d4d00bbe944460a4530
24 files changed +1623 -827
Makefile.am
+2
@@ -156,6 +156,8 @@ LIBNETDATA_FILES = \
156 libnetdata/eval/eval.h \
157 libnetdata/facets/facets.c \
158 libnetdata/facets/facets.h \
159 + libnetdata/functions_evloop/functions_evloop.c \
160 + libnetdata/functions_evloop/functions_evloop.h \
161 libnetdata/gorilla/gorilla.h \
162 libnetdata/gorilla/gorilla.cc \
163 libnetdata/inlined.h \
collectors/apps.plugin/apps_plugin.c
+11 -63
@@ -4677,7 +4677,7 @@ static void apps_plugin_function_processes_help(const char *transaction) {
4677 buffer_json_add_array_item_double(wb, _tmp); \
4678 } while(0)
4679
4680 -static void function_processes(const char *transaction, char *function __maybe_unused, char *line_buffer __maybe_unused, int line_max __maybe_unused, int timeout __maybe_unused) {
4680 +static void function_processes(const char *transaction, char *function __maybe_unused, int timeout __maybe_unused, bool *cancelled __maybe_unused) {
4681 struct pid_stat *p;
4682
4683 char *words[PLUGINSD_MAX_WORDS] = { NULL };
@@ -5531,62 +5531,6 @@ static void function_processes(const char *transaction, char *function __maybe_u
5531
5532 static bool apps_plugin_exit = false;
5533
5534 -static void *reader_main(void *arg __maybe_unused) {
5535 - char buffer[PLUGINSD_LINE_MAX + 1];
5536 -
5537 - char *s = NULL;
5538 - while(!apps_plugin_exit && (s = fgets(buffer, PLUGINSD_LINE_MAX, stdin))) {
5539 -
5540 - char *words[PLUGINSD_MAX_WORDS] = { NULL };
5541 - size_t num_words = quoted_strings_splitter_pluginsd(buffer, words, PLUGINSD_MAX_WORDS);
5542 -
5543 - const char *keyword = get_word(words, num_words, 0);
5544 -
5545 - if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION) == 0) {
5546 - char *transaction = get_word(words, num_words, 1);
5547 - char *timeout_s = get_word(words, num_words, 2);
5548 - char *function = get_word(words, num_words, 3);
5549 -
5550 - if(!transaction || !*transaction || !timeout_s || !*timeout_s || !function || !*function) {
5551 - netdata_log_error("Received incomplete %s (transaction = '%s', timeout = '%s', function = '%s'). Ignoring it.",
5552 - keyword,
5553 - transaction?transaction:"(unset)",
5554 - timeout_s?timeout_s:"(unset)",
5555 - function?function:"(unset)");
5556 - }
5557 - else {
5558 - int timeout = str2i(timeout_s);
5559 - if(timeout <= 0) timeout = PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT;
5560 -
5561 -// internal_error(true, "Received function '%s', transaction '%s', timeout %d", function, transaction, timeout);
5562 -
5563 - netdata_mutex_lock(&apps_and_stdout_mutex);
5564 -
5565 - if(strncmp(function, "processes", strlen("processes")) == 0)
5566 - function_processes(transaction, function, buffer, PLUGINSD_LINE_MAX + 1, timeout);
5567 - else {
5568 - pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_NOT_FOUND,
5569 - "No function with this name found in apps.plugin.");
5570 - }
5571 -
5572 - netdata_mutex_unlock(&apps_and_stdout_mutex);
5573 -
5574 -// internal_error(true, "Done with function '%s', transaction '%s', timeout %d", function, transaction, timeout);
5575 - }
5576 - }
5577 - else
5578 - netdata_log_error("Received unknown command: %s", keyword?keyword:"(unset)");
5579 - }
5580 -
5581 - if(!s || feof(stdin) || ferror(stdin)) {
5582 - apps_plugin_exit = true;
5583 - netdata_log_error("Received error on stdin.");
5584 - }
5585 -
5586 - exit(1);
5587 - return NULL;
5588 -}
5589 -
5534 int main(int argc, char **argv) {
5535 // debug_flags = D_PROCFILE;
5536 stderror = stderr;
@@ -5690,10 +5634,17 @@ int main(int argc, char **argv) {
5634
5635 all_pids = callocz(sizeof(struct pid_stat *), (size_t) pid_max + 1);
5636
5693 - netdata_thread_t reader_thread;
5694 - netdata_thread_create(&reader_thread, "APPS_READER", NETDATA_THREAD_OPTION_DONT_LOG, reader_main, NULL);
5695 - netdata_mutex_lock(&apps_and_stdout_mutex);
5637 + // ------------------------------------------------------------------------
5638 + // the event loop for functions
5639 +
5640 + struct functions_evloop_globals *wg =
5641 + functions_evloop_init(1, "APPS", &apps_and_stdout_mutex, &apps_plugin_exit);
5642
5643 + functions_evloop_add_function(wg, "processes", function_processes, PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT);
5644 +
5645 + // ------------------------------------------------------------------------
5646 +
5647 + netdata_mutex_lock(&apps_and_stdout_mutex);
5648 APPS_PLUGIN_GLOBAL_FUNCTIONS();
5649
5650 usec_t step = update_every * USEC_PER_SEC;
@@ -5717,12 +5668,10 @@ int main(int argc, char **argv) {
5668 struct pollfd pollfd = { .fd = fileno(stdout), .events = POLLERR };
5669 if (unlikely(poll(&pollfd, 1, 0) < 0)) {
5670 netdata_mutex_unlock(&apps_and_stdout_mutex);
5720 - netdata_thread_cancel(reader_thread);
5671 fatal("Cannot check if a pipe is available");
5672 }
5673 if (unlikely(pollfd.revents & POLLERR)) {
5674 netdata_mutex_unlock(&apps_and_stdout_mutex);
5725 - netdata_thread_cancel(reader_thread);
5675 fatal("Received error on read pipe.");
5676 }
5677
@@ -5733,7 +5682,6 @@ int main(int argc, char **argv) {
5682 netdata_log_error("Cannot collect /proc data for running processes. Disabling apps.plugin...");
5683 printf("DISABLE\n");
5684 netdata_mutex_unlock(&apps_and_stdout_mutex);
5736 - netdata_thread_cancel(reader_thread);
5685 exit(1);
5686 }
5687
collectors/plugins.d/plugins_d.h
-83
@@ -10,48 +10,6 @@
10 #define PLUGINSD_CMD_MAX (FILENAME_MAX*2)
11 #define PLUGINSD_STOCK_PLUGINS_DIRECTORY_PATH 0
12
13 -#define PLUGINSD_KEYWORD_CHART "CHART"
14 -#define PLUGINSD_KEYWORD_CHART_DEFINITION_END "CHART_DEFINITION_END"
15 -#define PLUGINSD_KEYWORD_DIMENSION "DIMENSION"
16 -#define PLUGINSD_KEYWORD_BEGIN "BEGIN"
17 -#define PLUGINSD_KEYWORD_SET "SET"
18 -#define PLUGINSD_KEYWORD_END "END"
19 -#define PLUGINSD_KEYWORD_FLUSH "FLUSH"
20 -#define PLUGINSD_KEYWORD_DISABLE "DISABLE"
21 -#define PLUGINSD_KEYWORD_VARIABLE "VARIABLE"
22 -#define PLUGINSD_KEYWORD_LABEL "LABEL"
23 -#define PLUGINSD_KEYWORD_OVERWRITE "OVERWRITE"
24 -#define PLUGINSD_KEYWORD_CLABEL "CLABEL"
25 -#define PLUGINSD_KEYWORD_CLABEL_COMMIT "CLABEL_COMMIT"
26 -#define PLUGINSD_KEYWORD_FUNCTION "FUNCTION"
27 -#define PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN "FUNCTION_RESULT_BEGIN"
28 -#define PLUGINSD_KEYWORD_FUNCTION_RESULT_END "FUNCTION_RESULT_END"
29 -
30 -#define PLUGINSD_KEYWORD_REPLAY_CHART "REPLAY_CHART"
31 -#define PLUGINSD_KEYWORD_REPLAY_BEGIN "RBEGIN"
32 -#define PLUGINSD_KEYWORD_REPLAY_SET "RSET"
33 -#define PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE "RDSTATE"
34 -#define PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE "RSSTATE"
35 -#define PLUGINSD_KEYWORD_REPLAY_END "REND"
36 -
37 -#define PLUGINSD_KEYWORD_BEGIN_V2 "BEGIN2"
38 -#define PLUGINSD_KEYWORD_SET_V2 "SET2"
39 -#define PLUGINSD_KEYWORD_END_V2 "END2"
40 -
41 -#define PLUGINSD_KEYWORD_HOST_DEFINE "HOST_DEFINE"
42 -#define PLUGINSD_KEYWORD_HOST_DEFINE_END "HOST_DEFINE_END"
43 -#define PLUGINSD_KEYWORD_HOST_LABEL "HOST_LABEL"
44 -#define PLUGINSD_KEYWORD_HOST "HOST"
45 -
46 -#define PLUGINSD_KEYWORD_DYNCFG_ENABLE "DYNCFG_ENABLE"
47 -#define PLUGINSD_KEYWORD_DYNCFG_REGISTER_MODULE "DYNCFG_REGISTER_MODULE"
48 -
49 -#define PLUGINSD_KEYWORD_REPORT_JOB_STATUS "REPORT_JOB_STATUS"
50 -
51 -#define PLUGINSD_KEYWORD_EXIT "EXIT"
52 -
53 -#define PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT 10 // seconds
54 -
13 #define PLUGINSD_LINE_MAX_SSL_READ 512
14
15 #define PLUGINSD_MAX_WORDS 20
@@ -99,45 +57,4 @@ void pluginsd_process_thread_cleanup(void *ptr);
57
58 size_t pluginsd_initialize_plugin_directories();
59
102 -#define pluginsd_function_result_begin_to_buffer(wb, transaction, code, content_type, expires) \
103 - buffer_sprintf(wb \
104 - , PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN " \"%s\" %d \"%s\" %ld\n" \
105 - , (transaction) ? (transaction) : "" \
106 - , (int)(code) \
107 - , (content_type) ? (content_type) : "" \
108 - , (long int)(expires) \
109 - )
110 -
111 -#define pluginsd_function_result_end_to_buffer(wb) \
112 - buffer_strcat(wb, "\n" PLUGINSD_KEYWORD_FUNCTION_RESULT_END "\n")
113 -
114 -#define pluginsd_function_result_begin_to_stdout(transaction, code, content_type, expires) \
115 - fprintf(stdout \
116 - , PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN " \"%s\" %d \"%s\" %ld\n" \
117 - , (transaction) ? (transaction) : "" \
118 - , (int)(code) \
119 - , (content_type) ? (content_type) : "" \
120 - , (long int)(expires) \
121 - )
122 -
123 -#define pluginsd_function_result_end_to_stdout() \
124 - fprintf(stdout, "\n" PLUGINSD_KEYWORD_FUNCTION_RESULT_END "\n")
125 -
126 -static inline void pluginsd_function_json_error_to_stdout(const char *transaction, int code, const char *msg) {
127 - char buffer[PLUGINSD_LINE_MAX + 1];
128 - json_escape_string(buffer, msg, PLUGINSD_LINE_MAX);
129 -
130 - pluginsd_function_result_begin_to_stdout(transaction, code, "application/json", now_realtime_sec());
131 - fprintf(stdout, "{\"status\":%d,\"error_message\":\"%s\"}", code, buffer);
132 - pluginsd_function_result_end_to_stdout();
133 - fflush(stdout);
134 -}
135 -
136 -static inline void pluginsd_function_result_to_stdout(const char *transaction, int code, const char *content_type, time_t expires, BUFFER *result) {
137 - pluginsd_function_result_begin_to_stdout(transaction, code, content_type, expires);
138 - fwrite(buffer_tostring(result), buffer_strlen(result), 1, stdout);
139 - pluginsd_function_result_end_to_stdout();
140 - fflush(stdout);
141 -}
142 -
60 #endif /* NETDATA_PLUGINS_D_H */
collectors/plugins.d/pluginsd_parser.c
+74 -31
@@ -733,14 +733,15 @@ static inline PARSER_RC pluginsd_dimension(char **words, size_t num_words, PARSE
733 struct inflight_function {
734 int code;
735 int timeout;
736 - BUFFER *destination_wb;
736 STRING *function;
738 - void (*callback)(BUFFER *wb, int code, void *callback_data);
739 - void *callback_data;
737 + BUFFER *result_body_wb;
738 + rrd_function_result_callback_t result_cb;
739 + void *result_cb_data;
740 usec_t timeout_ut;
741 usec_t started_ut;
742 usec_t sent_ut;
743 const char *payload;
744 + PARSER *parser;
745 };
746
747 static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void *func, void *parser_ptr) {
@@ -751,10 +752,12 @@ static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void
752 // leave this code as default, so that when the dictionary is destroyed this will be sent back to the caller
753 pf->code = HTTP_RESP_GATEWAY_TIMEOUT;
754
755 + const char *transaction = dictionary_acquired_item_name(item);
756 +
757 char buffer[2048 + 1];
758 snprintfz(buffer, 2048, "%s %s %d \"%s\"\n",
759 pf->payload ? "FUNCTION_PAYLOAD" : "FUNCTION",
757 - dictionary_acquired_item_name(item),
760 + transaction,
761 pf->timeout,
762 string2str(pf->function));
763
@@ -765,7 +768,7 @@ static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void
768
769 if(ret < 0) {
770 netdata_log_error("FUNCTION '%s': failed to send it to the plugin, error %zd", string2str(pf->function), ret);
768 - rrd_call_function_error(pf->destination_wb, "Failed to communicate with collector", HTTP_RESP_SERVICE_UNAVAILABLE);
771 + rrd_call_function_error(pf->result_body_wb, "Failed to communicate with collector", HTTP_RESP_SERVICE_UNAVAILABLE);
772 }
773 else {
774 internal_error(LOG_FUNCTIONS,
@@ -782,7 +785,7 @@ static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void
785
786 if(ret < 0) {
787 netdata_log_error("FUNCTION_PAYLOAD '%s': failed to send function to plugin, error %zd", string2str(pf->function), ret);
785 - rrd_call_function_error(pf->destination_wb, "Failed to communicate with collector", HTTP_RESP_SERVICE_UNAVAILABLE);
788 + rrd_call_function_error(pf->result_body_wb, "Failed to communicate with collector", HTTP_RESP_SERVICE_UNAVAILABLE);
789 }
790 else {
791 internal_error(LOG_FUNCTIONS,
@@ -798,8 +801,8 @@ static bool inflight_functions_conflict_callback(const DICTIONARY_ITEM *item __m
801 struct inflight_function *pf = new_func;
802
803 netdata_log_error("PLUGINSD_PARSER: duplicate UUID on pending function '%s' detected. Ignoring the second one.", string2str(pf->function));
801 - pf->code = rrd_call_function_error(pf->destination_wb, "This request is already in progress", HTTP_RESP_BAD_REQUEST);
802 - pf->callback(pf->destination_wb, pf->code, pf->callback_data);
804 + pf->code = rrd_call_function_error(pf->result_body_wb, "This request is already in progress", HTTP_RESP_BAD_REQUEST);
805 + pf->result_cb(pf->result_body_wb, pf->code, pf->result_cb_data);
806 string_freez(pf->function);
807
808 return false;
@@ -811,9 +814,9 @@ static void inflight_functions_delete_callback(const DICTIONARY_ITEM *item __may
814 internal_error(LOG_FUNCTIONS,
815 "FUNCTION '%s' result of transaction '%s' received from collector (%zu bytes, request %llu usec, response %llu usec)",
816 string2str(pf->function), dictionary_acquired_item_name(item),
814 - buffer_strlen(pf->destination_wb), pf->sent_ut - pf->started_ut, now_realtime_usec() - pf->sent_ut);
817 + buffer_strlen(pf->result_body_wb), pf->sent_ut - pf->started_ut, now_realtime_usec() - pf->sent_ut);
818
816 - pf->callback(pf->destination_wb, pf->code, pf->callback_data);
819 + pf->result_cb(pf->result_body_wb, pf->code, pf->result_cb_data);
820 string_freez(pf->function);
821 }
822
@@ -833,8 +836,8 @@ static void inflight_functions_garbage_collect(PARSER *parser, usec_t now) {
836 "FUNCTION '%s' removing expired transaction '%s', after %llu usec.",
837 string2str(pf->function), pf_dfe.name, now - pf->started_ut);
838
836 - if(!buffer_strlen(pf->destination_wb) || pf->code == HTTP_RESP_OK)
837 - pf->code = rrd_call_function_error(pf->destination_wb,
839 + if(!buffer_strlen(pf->result_body_wb) || pf->code == HTTP_RESP_OK)
840 + pf->code = rrd_call_function_error(pf->result_body_wb,
841 "Timeout waiting for collector response.",
842 HTTP_RESP_GATEWAY_TIMEOUT);
843
@@ -847,35 +850,73 @@ static void inflight_functions_garbage_collect(PARSER *parser, usec_t now) {
850 dfe_done(pf);
851 }
852
853 +void pluginsd_function_cancel(void *data) {
854 + struct inflight_function *look_for = data, *t;
855 +
856 + bool sent = false;
857 + dfe_start_read(look_for->parser->inflight.functions, t) {
858 + if(look_for == t) {
859 + const char *transaction = t_dfe.name;
860 +
861 + internal_error(true, "PLUGINSD: sending function cancellation to plugin for transaction '%s'", transaction);
862 +
863 + char buffer[2048 + 1];
864 + snprintfz(buffer, 2048, "%s %s\n",
865 + PLUGINSD_KEYWORD_FUNCTION_CANCEL,
866 + transaction);
867 +
868 + // send the command to the plugin
869 + ssize_t ret = send_to_plugin(buffer, t->parser);
870 + if(ret < 0)
871 + sent = true;
872 +
873 + break;
874 + }
875 + }
876 + dfe_done(t);
877 +
878 + if(sent <= 0)
879 + netdata_log_error("PLUGINSD: FUNCTION_CANCEL request didn't match any pending function requests in pluginsd.d.");
880 +}
881 +
882 // this is the function that is called from
883 // rrd_call_function_and_wait() and rrd_call_function_async()
852 -static int pluginsd_execute_function_callback(BUFFER *destination_wb, int timeout, const char *function, void *collector_data, void (*callback)(BUFFER *wb, int code, void *callback_data), void *callback_data) {
853 - PARSER *parser = collector_data;
884 +static int pluginsd_function_execute_cb(BUFFER *result_body_wb, int timeout, const char *function,
885 + void *execute_cb_data,
886 + rrd_function_result_callback_t result_cb, void *result_cb_data,
887 + rrd_function_is_cancelled_cb_t is_cancelled_cb __maybe_unused,
888 + void *is_cancelled_cb_data __maybe_unused,
889 + rrd_function_register_canceller_cb_t register_canceller_cb,
890 + void *register_canceller_db_data) {
891 + PARSER *parser = execute_cb_data;
892
893 usec_t now = now_realtime_usec();
894
895 struct inflight_function tmp = {
896 .started_ut = now,
897 .timeout_ut = now + timeout * USEC_PER_SEC,
860 - .destination_wb = destination_wb,
898 + .result_body_wb = result_body_wb,
899 .timeout = timeout,
900 .function = string_strdupz(function),
863 - .callback = callback,
864 - .callback_data = callback_data,
865 - .payload = NULL
901 + .result_cb = result_cb,
902 + .result_cb_data = result_cb_data,
903 + .payload = NULL,
904 + .parser = parser,
905 };
906
907 uuid_t uuid;
869 - uuid_generate_time(uuid);
908 + uuid_generate_random(uuid);
909
871 - char key[UUID_STR_LEN];
872 - uuid_unparse_lower(uuid, key);
910 + char transaction[UUID_STR_LEN];
911 + uuid_unparse_lower(uuid, transaction);
912
913 dictionary_write_lock(parser->inflight.functions);
914
915 // if there is any error, our dictionary callbacks will call the caller callback to notify
916 // the caller about the error - no need for error handling here.
878 - dictionary_set(parser->inflight.functions, key, &tmp, sizeof(struct inflight_function));
917 + void *t = dictionary_set(parser->inflight.functions, transaction, &tmp, sizeof(struct inflight_function));
918 + if(register_canceller_cb)
919 + register_canceller_cb(register_canceller_db_data, pluginsd_function_cancel, t);
920
921 if(!parser->inflight.smaller_timeout || tmp.timeout_ut < parser->inflight.smaller_timeout)
922 parser->inflight.smaller_timeout = tmp.timeout_ut;
@@ -890,6 +931,8 @@ static int pluginsd_execute_function_callback(BUFFER *destination_wb, int timeou
931 }
932
933 static inline PARSER_RC pluginsd_function(char **words, size_t num_words, PARSER *parser) {
934 + // a plugin or a child is registering a function
935 +
936 bool global = false;
937 size_t i = 1;
938 if(num_words >= 2 && strcmp(get_word(words, num_words, 1), "GLOBAL") == 0) {
@@ -926,7 +969,7 @@ static inline PARSER_RC pluginsd_function(char **words, size_t num_words, PARSER
969 timeout = PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT;
970 }
971
929 - rrd_collector_add_function(host, st, name, timeout, help, false, pluginsd_execute_function_callback, parser);
972 + rrd_function_add(host, st, name, timeout, help, false, pluginsd_function_execute_cb, parser);
973
974 parser->user.data_collections_count++;
975
@@ -973,18 +1016,18 @@ static inline PARSER_RC pluginsd_function_result_begin(char **words, size_t num_
1016 }
1017 else {
1018 if(format && *format)
976 - pf->destination_wb->content_type = functions_format_to_content_type(format);
1019 + pf->result_body_wb->content_type = functions_format_to_content_type(format);
1020
1021 pf->code = code;
1022
980 - pf->destination_wb->expires = expiration;
1023 + pf->result_body_wb->expires = expiration;
1024 if(expiration <= now_realtime_sec())
982 - buffer_no_cacheable(pf->destination_wb);
1025 + buffer_no_cacheable(pf->result_body_wb);
1026 else
984 - buffer_cacheable(pf->destination_wb);
1027 + buffer_cacheable(pf->result_body_wb);
1028 }
1029
987 - parser->defer.response = (pf) ? pf->destination_wb : NULL;
1030 + parser->defer.response = (pf) ? pf->result_body_wb : NULL;
1031 parser->defer.end_keyword = PLUGINSD_KEYWORD_FUNCTION_RESULT_END;
1032 parser->defer.action = pluginsd_function_result_end;
1033 parser->defer.action_data = string_strdupz(key); // it is ok is key is NULL
@@ -1916,11 +1959,11 @@ dyncfg_config_t call_virtual_function_blocking(PARSER *parser, const char *name,
1959 struct inflight_function tmp = {
1960 .started_ut = now,
1961 .timeout_ut = now + VIRT_FNC_TIMEOUT + USEC_PER_SEC,
1919 - .destination_wb = wb,
1962 + .result_body_wb = wb,
1963 .timeout = VIRT_FNC_TIMEOUT,
1964 .function = string_strdupz(name),
1922 - .callback = virt_fnc_got_data_cb,
1923 - .callback_data = &cond,
1965 + .result_cb = virt_fnc_got_data_cb,
1966 + .result_cb_data = &cond,
1967 .payload = payload,
1968 };
1969
collectors/systemd-journal.plugin/systemd-journal.c
+192 -186
@@ -5,8 +5,6 @@
5 * GPL v3+
6 */
7
8 -// TODO - 1) MARKDOC 2) HELP TEXT
9 -
8 #include "collectors/all.h"
9 #include "libnetdata/libnetdata.h"
10 #include "libnetdata/required_dummies.h"
@@ -23,6 +21,7 @@
21 #define SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION (3 * 3600)
22 #define SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY 200
23 #define SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED 50
24 +#define SYSTEMD_JOURNAL_WORKER_THREADS 2
25
26 #define JOURNAL_PARAMETER_HELP "help"
27 #define JOURNAL_PARAMETER_AFTER "after"
@@ -58,9 +57,6 @@
57 static netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
58 static bool plugin_should_exit = false;
59
61 -DICTIONARY *uids = NULL;
62 -DICTIONARY *gids = NULL;
63 -
60 // ----------------------------------------------------------------------------
61
62 static inline sd_journal *netdata_open_systemd_journal(void) {
@@ -95,6 +91,7 @@ typedef enum {
91 ND_SD_JOURNAL_TIMED_OUT,
92 ND_SD_JOURNAL_OK,
93 ND_SD_JOURNAL_NOT_MODIFIED,
94 + ND_SD_JOURNAL_CANCELLED,
95 } ND_SD_JOURNAL_STATUS;
96
97 static inline bool netdata_systemd_journal_seek_to(sd_journal *j, usec_t timestamp) {
@@ -130,21 +127,42 @@ static inline void netdata_systemd_journal_process_row(sd_journal *j, FACETS *fa
127 }
128 }
129
133 -ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_full(sd_journal *j, BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t before_ut, usec_t if_modified_since, usec_t stop_monotonic_ut, usec_t *last_modified) {
130 +static inline ND_SD_JOURNAL_STATUS check_stop(size_t row_counter, const bool *cancelled, usec_t stop_monotonic_ut) {
131 + if((row_counter % 1000) == 0) {
132 + if(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)) {
133 + internal_error(true, "Function has been cancelled");
134 + return ND_SD_JOURNAL_CANCELLED;
135 + }
136 +
137 + if(now_monotonic_usec() > stop_monotonic_ut) {
138 + internal_error(true, "Function timed out");
139 + return ND_SD_JOURNAL_TIMED_OUT;
140 + }
141 + }
142 +
143 + return ND_SD_JOURNAL_OK;
144 +}
145 +
146 +ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_full(
147 + sd_journal *j, BUFFER *wb __maybe_unused, FACETS *facets,
148 + usec_t after_ut, usec_t before_ut,
149 + usec_t if_modified_since, usec_t stop_monotonic_ut, usec_t *last_modified,
150 + bool *cancelled) {
151 if(!netdata_systemd_journal_seek_to(j, before_ut))
152 return ND_SD_JOURNAL_FAILED_TO_SEEK;
153
154 size_t errors_no_timestamp = 0;
155 usec_t first_msg_ut = 0;
139 - bool timed_out = false;
156 size_t row_counter = 0;
157
158 // the entries are not guaranteed to be sorted, so we process up to 100 entries beyond
159 // the end of the query to find possibly useful logs for our time-frame
160 size_t excess_rows_allowed = SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED;
161
162 + ND_SD_JOURNAL_STATUS status = ND_SD_JOURNAL_OK;
163 +
164 facets_rows_begin(facets);
147 - while (sd_journal_previous(j) > 0) {
165 + while (status == ND_SD_JOURNAL_OK && sd_journal_previous(j) > 0) {
166 row_counter++;
167
168 usec_t msg_ut;
@@ -174,10 +192,7 @@ ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_full(sd_journal *j, BUFFER *w
192 netdata_systemd_journal_process_row(j, facets);
193 facets_row_finished(facets, msg_ut);
194
177 - if((row_counter % 100) == 0 && now_monotonic_usec() > stop_monotonic_ut) {
178 - timed_out = true;
179 - break;
180 - }
195 + status = check_stop(row_counter, cancelled, stop_monotonic_ut);
196 }
197
198 if(errors_no_timestamp)
@@ -185,18 +200,19 @@ ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_full(sd_journal *j, BUFFER *w
200
201 *last_modified = first_msg_ut;
202
188 - if(timed_out)
189 - return ND_SD_JOURNAL_TIMED_OUT;
190 -
191 - return ND_SD_JOURNAL_OK;
203 + return status;
204 }
205
194 -ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_forward(sd_journal *j, BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t before_ut, usec_t anchor, size_t entries, usec_t stop_monotonic_ut) {
206 +ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_forward(
207 + sd_journal *j, BUFFER *wb __maybe_unused, FACETS *facets,
208 + usec_t after_ut, usec_t before_ut,
209 + usec_t anchor, size_t entries, usec_t stop_monotonic_ut,
210 + bool *cancelled) {
211 +
212 if(!netdata_systemd_journal_seek_to(j, anchor))
213 return ND_SD_JOURNAL_FAILED_TO_SEEK;
214
215 size_t errors_no_timestamp = 0;
199 - bool timed_out = false;
216 size_t row_counter = 0;
217 size_t rows_added = 0;
218
@@ -204,8 +220,10 @@ ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_forward(sd_journal *j, B
220 // the end of the query to find possibly useful logs for our time-frame
221 size_t excess_rows_allowed = SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED;
222
223 + ND_SD_JOURNAL_STATUS status = ND_SD_JOURNAL_OK;
224 +
225 facets_rows_begin(facets);
208 - while (sd_journal_next(j) > 0) {
226 + while (status == ND_SD_JOURNAL_OK && sd_journal_next(j) > 0) {
227 row_counter++;
228
229 usec_t msg_ut;
@@ -231,27 +249,25 @@ ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_forward(sd_journal *j, B
249 facets_row_finished(facets, msg_ut);
250 rows_added++;
251
234 - if((row_counter % 100) == 0 && now_monotonic_usec() > stop_monotonic_ut) {
235 - timed_out = true;
236 - break;
237 - }
252 + status = check_stop(row_counter, cancelled, stop_monotonic_ut);
253 }
254
255 if(errors_no_timestamp)
256 netdata_log_error("SYSTEMD-JOURNAL: %zu lines did not have timestamps", errors_no_timestamp);
257
243 - if(timed_out)
244 - return ND_SD_JOURNAL_TIMED_OUT;
245 -
246 - return ND_SD_JOURNAL_OK;
258 + return status;
259 }
260
249 -ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_backward(sd_journal *j, BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t before_ut, usec_t anchor, size_t entries, usec_t stop_monotonic_ut) {
261 +ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_backward(
262 + sd_journal *j, BUFFER *wb __maybe_unused, FACETS *facets,
263 + usec_t after_ut, usec_t before_ut,
264 + usec_t anchor, size_t entries, usec_t stop_monotonic_ut,
265 + bool *cancelled) {
266 +
267 if(!netdata_systemd_journal_seek_to(j, anchor))
268 return ND_SD_JOURNAL_FAILED_TO_SEEK;
269
270 size_t errors_no_timestamp = 0;
254 - bool timed_out = false;
271 size_t row_counter = 0;
272 size_t rows_added = 0;
273
@@ -259,8 +275,10 @@ ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_backward(sd_journal *j,
275 // the end of the query to find possibly useful logs for our time-frame
276 size_t excess_rows_allowed = SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED;
277
278 + ND_SD_JOURNAL_STATUS status = ND_SD_JOURNAL_OK;
279 +
280 facets_rows_begin(facets);
263 - while (sd_journal_previous(j) > 0) {
281 + while (status == ND_SD_JOURNAL_OK && sd_journal_previous(j) > 0) {
282 row_counter++;
283
284 usec_t msg_ut;
@@ -286,19 +304,13 @@ ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_backward(sd_journal *j,
304 facets_row_finished(facets, msg_ut);
305 rows_added++;
306
289 - if((row_counter % 100) == 0 && now_monotonic_usec() > stop_monotonic_ut) {
290 - timed_out = true;
291 - break;
292 - }
307 + status = check_stop(row_counter, cancelled, stop_monotonic_ut);
308 }
309
310 if(errors_no_timestamp)
311 netdata_log_error("SYSTEMD-JOURNAL: %zu lines did not have timestamps", errors_no_timestamp);
312
298 - if(timed_out)
299 - return ND_SD_JOURNAL_TIMED_OUT;
300 -
301 - return ND_SD_JOURNAL_OK;
313 + return status;
314 }
315
316 bool netdata_systemd_journal_check_if_modified_since(sd_journal *j, usec_t seek_to, usec_t last_modified) {
@@ -327,7 +339,8 @@ static int netdata_systemd_journal_query(BUFFER *wb, FACETS *facets,
339 usec_t after_ut, usec_t before_ut,
340 usec_t anchor, FACETS_ANCHOR_DIRECTION direction, size_t entries,
341 usec_t if_modified_since, bool data_only,
330 - usec_t stop_monotonic_ut) {
342 + usec_t stop_monotonic_ut,
343 + bool *cancelled) {
344 sd_journal *j = netdata_open_systemd_journal();
345 if(!j)
346 return HTTP_RESP_INTERNAL_SERVER_ERROR;
@@ -341,23 +354,36 @@ static int netdata_systemd_journal_query(BUFFER *wb, FACETS *facets,
354
355 // we can do a data-only query
356 if(direction == FACETS_ANCHOR_DIRECTION_FORWARD)
344 - status = netdata_systemd_journal_query_data_forward(j, wb, facets, after_ut, before_ut, anchor, entries, stop_monotonic_ut);
357 + status = netdata_systemd_journal_query_data_forward(j, wb, facets, after_ut, before_ut, anchor, entries, stop_monotonic_ut, cancelled);
358 else
346 - status = netdata_systemd_journal_query_data_backward(j, wb, facets, after_ut, before_ut, anchor, entries, stop_monotonic_ut);
359 + status = netdata_systemd_journal_query_data_backward(j, wb, facets, after_ut, before_ut, anchor, entries, stop_monotonic_ut, cancelled);
360 }
361 else {
362 // we have to do a full query
363 status = netdata_systemd_journal_query_full(j, wb, facets,
364 after_ut, before_ut, if_modified_since,
352 - stop_monotonic_ut, &last_modified);
365 + stop_monotonic_ut, &last_modified, cancelled);
366 }
367
368 sd_journal_close(j);
369
357 - if(status == ND_SD_JOURNAL_NOT_MODIFIED)
358 - return HTTP_RESP_NOT_MODIFIED;
370 + if(status != ND_SD_JOURNAL_OK && status != ND_SD_JOURNAL_TIMED_OUT) {
371 + buffer_flush(wb);
372 +
373 + switch (status) {
374 + case ND_SD_JOURNAL_CANCELLED:
375 + return HTTP_RESP_CLIENT_CLOSED_REQUEST;
376 +
377 + case ND_SD_JOURNAL_NOT_MODIFIED:
378 + return HTTP_RESP_NOT_MODIFIED;
379
360 - buffer_json_member_add_uint64(wb, "status", status == ND_SD_JOURNAL_FAILED_TO_SEEK ? HTTP_RESP_INTERNAL_SERVER_ERROR : HTTP_RESP_OK);
380 + default:
381 + case ND_SD_JOURNAL_FAILED_TO_SEEK:
382 + return HTTP_RESP_INTERNAL_SERVER_ERROR;
383 + }
384 + }
385 +
386 + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
387 buffer_json_member_add_boolean(wb, "partial", status != ND_SD_JOURNAL_OK);
388 buffer_json_member_add_string(wb, "type", "table");
389
@@ -372,7 +398,7 @@ static int netdata_systemd_journal_query(BUFFER *wb, FACETS *facets,
398 buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + (data_only ? 3600 : 0));
399 buffer_json_finalize(wb);
400
375 - return status == ND_SD_JOURNAL_FAILED_TO_SEEK ? HTTP_RESP_INTERNAL_SERVER_ERROR : HTTP_RESP_OK;
401 + return HTTP_RESP_OK;
402 }
403
404 static void netdata_systemd_journal_function_help(const char *transaction) {
@@ -484,8 +510,8 @@ static FACET_ROW_SEVERITY syslog_priority_to_facet_severity(int priority) {
510 }
511
512 static char *uid_to_username(uid_t uid, char *buffer, size_t buffer_size) {
513 + static __thread char tmp[1024 + 1];
514 struct passwd pw, *result;
488 - char tmp[1024 + 1];
515
516 if (getpwuid_r(uid, &pw, tmp, 1024, &result) != 0 || result == NULL)
517 return NULL;
@@ -495,8 +521,8 @@ static char *uid_to_username(uid_t uid, char *buffer, size_t buffer_size) {
521 }
522
523 static char *gid_to_groupname(gid_t gid, char* buffer, size_t buffer_size) {
524 + static __thread char tmp[1024 + 1];
525 struct group grp, *result;
499 - char tmp[1024 + 1];
526
527 if (getgrgid_r(gid, &grp, tmp, 1024, &result) != 0 || result == NULL)
528 return NULL;
@@ -531,46 +557,106 @@ static void netdata_systemd_journal_transform_priority(FACETS *facets __maybe_un
557 }
558 }
559
534 -static void netdata_systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER *wb, void *data) {
535 - DICTIONARY *cache = data;
536 - const char *v = buffer_tostring(wb);
537 - if(*v && isdigit(*v)) {
538 - const char *sv = dictionary_get(cache, v);
539 - if(!sv) {
540 - char buf[1024 + 1];
541 - int uid = str2i(buffer_tostring(wb));
542 - const char *name = uid_to_username(uid, buf, 1024);
543 - if (!name)
544 - name = v;
545 -
546 - sv = dictionary_set(cache, v, (void *)name, strlen(name) + 1);
547 - }
560 +// ----------------------------------------------------------------------------
561 +// UID and GID transformation
562
549 - buffer_flush(wb);
550 - buffer_strcat(wb, sv);
563 +#define UID_GID_HASHTABLE_SIZE 1000
564 +
565 +struct word_t2str_hashtable_entry {
566 + struct word_t2str_hashtable_entry *next;
567 + Word_t hash;
568 + size_t len;
569 + char str[];
570 +};
571 +
572 +struct word_t2str_hashtable {
573 + SPINLOCK spinlock;
574 + size_t size;
575 + struct word_t2str_hashtable_entry *hashtable[UID_GID_HASHTABLE_SIZE];
576 +};
577 +
578 +struct word_t2str_hashtable uid_hashtable = {
579 + .size = UID_GID_HASHTABLE_SIZE,
580 +};
581 +
582 +struct word_t2str_hashtable gid_hashtable = {
583 + .size = UID_GID_HASHTABLE_SIZE,
584 +};
585 +
586 +struct word_t2str_hashtable_entry **word_t2str_hashtable_slot(struct word_t2str_hashtable *ht, Word_t hash) {
587 + size_t slot = hash % ht->size;
588 + struct word_t2str_hashtable_entry **e = &ht->hashtable[slot];
589 +
590 + while(*e && (*e)->hash != hash)
591 + e = &((*e)->next);
592 +
593 + return e;
594 +}
595 +
596 +const char *uid_to_username_cached(uid_t uid, size_t *length) {
597 + spinlock_lock(&uid_hashtable.spinlock);
598 +
599 + struct word_t2str_hashtable_entry **e = word_t2str_hashtable_slot(&uid_hashtable, uid);
600 + if(!(*e)) {
601 + static __thread char buf[1024 + 1];
602 + const char *name = uid_to_username(uid, buf, 1024);
603 + size_t size = strlen(name) + 1;
604 +
605 + *e = callocz(1, sizeof(struct word_t2str_hashtable_entry) + size);
606 + (*e)->len = size - 1;
607 + (*e)->hash = uid;
608 + memcpy((*e)->str, name, size);
609 }
610 +
611 + spinlock_unlock(&uid_hashtable.spinlock);
612 +
613 + *length = (*e)->len;
614 + return (*e)->str;
615 }
616
554 -static void netdata_systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER *wb, void *data) {
555 - DICTIONARY *cache = data;
617 +const char *gid_to_groupname_cached(gid_t gid, size_t *length) {
618 + spinlock_lock(&gid_hashtable.spinlock);
619 +
620 + struct word_t2str_hashtable_entry **e = word_t2str_hashtable_slot(&gid_hashtable, gid);
621 + if(!(*e)) {
622 + static __thread char buf[1024 + 1];
623 + const char *name = gid_to_groupname(gid, buf, 1024);
624 + size_t size = strlen(name) + 1;
625 +
626 + *e = callocz(1, sizeof(struct word_t2str_hashtable_entry) + size);
627 + (*e)->len = size - 1;
628 + (*e)->hash = gid;
629 + memcpy((*e)->str, name, size);
630 + }
631 +
632 + spinlock_unlock(&gid_hashtable.spinlock);
633 +
634 + *length = (*e)->len;
635 + return (*e)->str;
636 +}
637 +
638 +static void netdata_systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
639 const char *v = buffer_tostring(wb);
640 if(*v && isdigit(*v)) {
558 - const char *sv = dictionary_get(cache, v);
559 - if(!sv) {
560 - char buf[1024 + 1];
561 - int gid = str2i(buffer_tostring(wb));
562 - const char *name = gid_to_groupname(gid, buf, 1024);
563 - if (!name)
564 - name = v;
565 -
566 - sv = dictionary_set(cache, v, (void *)name, strlen(name) + 1);
567 - }
641 + uid_t uid = str2i(buffer_tostring(wb));
642 + size_t len;
643 + const char *name = uid_to_username_cached(uid, &len);
644 + buffer_contents_replace(wb, name, len);
645 + }
646 +}
647
569 - buffer_flush(wb);
570 - buffer_strcat(wb, sv);
648 +static void netdata_systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
649 + const char *v = buffer_tostring(wb);
650 + if(*v && isdigit(*v)) {
651 + gid_t gid = str2i(buffer_tostring(wb));
652 + size_t len;
653 + const char *name = gid_to_groupname_cached(gid, &len);
654 + buffer_contents_replace(wb, name, len);
655 }
656 }
657
658 +// ----------------------------------------------------------------------------
659 +
660 static void netdata_systemd_journal_dynamic_row_id(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data __maybe_unused) {
661 FACET_ROW_KEY_VALUE *pid_rkv = dictionary_get(row->dict, "_PID");
662 const char *pid = pid_rkv ? buffer_tostring(pid_rkv->wb) : FACET_VALUE_UNSET;
@@ -593,47 +679,13 @@ static void netdata_systemd_journal_dynamic_row_id(FACETS *facets __maybe_unused
679 buffer_json_add_array_item_string(json_array, buffer_tostring(rkv->wb));
680 }
681
596 -static void netdata_systemd_journal_rich_message(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data __maybe_unused) {
682 +static void netdata_systemd_journal_rich_message(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row __maybe_unused, void *data __maybe_unused) {
683 buffer_json_add_array_item_object(json_array);
684 buffer_json_member_add_string(json_array, "value", buffer_tostring(rkv->wb));
685 buffer_json_object_close(json_array);
686 }
687
602 -static void function_systemd_journal(const char *transaction, char *function, char *line_buffer __maybe_unused, int line_max __maybe_unused, int timeout __maybe_unused) {
603 - static struct {
604 - BUFFER *tmp;
605 - BUFFER *wb;
606 - BUFFER *function;
607 - int response;
608 - time_t expires;
609 - } cache = {
610 - .tmp = NULL,
611 - .wb = NULL,
612 - .function = NULL,
613 - .response = 0,
614 - .expires = 0,
615 - };
616 -
617 - if(unlikely(!cache.wb)) {
618 - cache.tmp = buffer_create(0, NULL);
619 - cache.wb = buffer_create(0, NULL);
620 - cache.function = buffer_create(0, NULL);
621 - }
622 -
623 - if(buffer_strlen(cache.function) && buffer_strlen(cache.wb) && strcmp(buffer_tostring(cache.function), function) == 0) {
624 - // repeated the same request
625 - netdata_mutex_lock(&stdout_mutex);
626 - if(cache.response == HTTP_RESP_OK)
627 - pluginsd_function_result_to_stdout(transaction, cache.response, "application/json", cache.expires, cache.wb);
628 - else
629 - pluginsd_function_json_error_to_stdout(transaction, cache.response, "failed");
630 - netdata_mutex_unlock(&stdout_mutex);
631 - return;
632 - }
633 -
634 - buffer_flush(cache.tmp);
635 - buffer_strcat(cache.tmp, function);
636 -
688 +static void function_systemd_journal(const char *transaction, char *function, int timeout, bool *cancelled) {
689 BUFFER *wb = buffer_create(0, NULL);
690 buffer_flush(wb);
691 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_NEWLINE_ON_ARRAY_ITEMS);
@@ -682,10 +734,10 @@ static void function_systemd_journal(const char *transaction, char *function, ch
734 facets_register_key_name(facets, "USER_UNIT", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
735
736 facets_register_key_name_transformation(facets, "_UID", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
685 - netdata_systemd_journal_transform_uid, uids);
737 + netdata_systemd_journal_transform_uid, NULL);
738
739 facets_register_key_name_transformation(facets, "_GID", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
688 - netdata_systemd_journal_transform_gid, gids);
740 + netdata_systemd_journal_transform_gid, NULL);
741
742 bool info = false;
743 bool data_only = false;
@@ -858,12 +910,19 @@ static void function_systemd_journal(const char *transaction, char *function, ch
910 facets_set_items(facets, last);
911 facets_set_anchor(facets, anchor, direction);
912 facets_set_query(facets, query);
861 - facets_set_histogram(facets, chart ? chart : "PRIORITY", after_s * USEC_PER_SEC, before_s * USEC_PER_SEC);
913 +
914 + if(chart && *chart)
915 + facets_set_histogram_by_id(facets, chart,
916 + after_s * USEC_PER_SEC, before_s * USEC_PER_SEC);
917 + else
918 + facets_set_histogram_by_name(facets, "PRIORITY",
919 + after_s * USEC_PER_SEC, before_s * USEC_PER_SEC);
920
921 response = netdata_systemd_journal_query(wb, facets, after_s * USEC_PER_SEC, before_s * USEC_PER_SEC,
922 anchor, direction, last,
923 if_modified_since, data_only,
866 - now_monotonic_usec() + (timeout - 1) * USEC_PER_SEC);
924 + now_monotonic_usec() + (timeout - 1) * USEC_PER_SEC,
925 + cancelled);
926
927 if(response != HTTP_RESP_OK) {
928 netdata_mutex_lock(&stdout_mutex);
@@ -872,14 +931,6 @@ static void function_systemd_journal(const char *transaction, char *function, ch
931 goto cleanup;
932 }
933
875 - // keep this response in the cache
876 - cache.response = response;
877 - cache.expires = expires;
878 - buffer_flush(cache.wb);
879 - buffer_fast_strcat(cache.wb, buffer_tostring(wb), buffer_strlen(wb));
880 - buffer_flush(cache.function);
881 - buffer_fast_strcat(cache.function, buffer_tostring(cache.tmp), buffer_strlen(cache.tmp));
882 -
934 output:
935 netdata_mutex_lock(&stdout_mutex);
936 pluginsd_function_result_to_stdout(transaction, response, "application/json", expires, wb);
@@ -890,54 +941,7 @@ cleanup:
941 buffer_free(wb);
942 }
943
893 -static void *reader_main(void *arg __maybe_unused) {
894 - char buffer[PLUGINSD_LINE_MAX + 1];
895 -
896 - char *s = NULL;
897 - while(!plugin_should_exit && (s = fgets(buffer, PLUGINSD_LINE_MAX, stdin))) {
898 -
899 - char *words[PLUGINSD_MAX_WORDS] = { NULL };
900 - size_t num_words = quoted_strings_splitter_pluginsd(buffer, words, PLUGINSD_MAX_WORDS);
901 -
902 - const char *keyword = get_word(words, num_words, 0);
903 -
904 - if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION) == 0) {
905 - char *transaction = get_word(words, num_words, 1);
906 - char *timeout_s = get_word(words, num_words, 2);
907 - char *function = get_word(words, num_words, 3);
908 -
909 - if(!transaction || !*transaction || !timeout_s || !*timeout_s || !function || !*function) {
910 - netdata_log_error("Received incomplete %s (transaction = '%s', timeout = '%s', function = '%s'). Ignoring it.",
911 - keyword,
912 - transaction?transaction:"(unset)",
913 - timeout_s?timeout_s:"(unset)",
914 - function?function:"(unset)");
915 - }
916 - else {
917 - int timeout = str2i(timeout_s);
918 - if(timeout <= 0) timeout = SYSTEMD_JOURNAL_DEFAULT_TIMEOUT;
919 -
920 - if(strncmp(function, SYSTEMD_JOURNAL_FUNCTION_NAME, strlen(SYSTEMD_JOURNAL_FUNCTION_NAME)) == 0)
921 - function_systemd_journal(transaction, function, buffer, PLUGINSD_LINE_MAX + 1, timeout);
922 - else {
923 - netdata_mutex_lock(&stdout_mutex);
924 - pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_NOT_FOUND,
925 - "No function with this name found in systemd-journal.plugin.");
926 - netdata_mutex_unlock(&stdout_mutex);
927 - }
928 - }
929 - }
930 - else
931 - netdata_log_error("Received unknown command: %s", keyword?keyword:"(unset)");
932 - }
933 -
934 - if(!s || feof(stdin) || ferror(stdin)) {
935 - plugin_should_exit = true;
936 - netdata_log_error("Received error on stdin.");
937 - }
938 -
939 - exit(1);
940 -}
944 +// ----------------------------------------------------------------------------
945
946 int main(int argc __maybe_unused, char **argv __maybe_unused) {
947 stderror = stderr;
@@ -952,9 +956,6 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
956 error_log_errors_per_period = 100;
957 error_log_throttle_period = 3600;
958
955 - uids = dictionary_create(0);
956 - gids = dictionary_create(0);
957 -
959 netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
960 if(verify_netdata_host_prefix() == -1) exit(1);
961
@@ -962,22 +963,28 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
963 // debug
964
965 if(argc == 2 && strcmp(argv[1], "debug") == 0) {
965 - char buf[] = "systemd-journal after:-864000 before:0 last:500";
966 + bool cancelled = false;
967 + char buf[] = "systemd-journal after:-2592000 before:0 last:500";
968 // char buf[] = "systemd-journal after:1694511062 before:1694514662 anchor:1694514122024403";
967 - function_systemd_journal("123", buf, "", 0, 30);
969 + function_systemd_journal("123", buf, 30, &cancelled);
970 exit(1);
971 }
972
973 // ------------------------------------------------------------------------
974 + // the event loop for functions
975 +
976 + struct functions_evloop_globals *wg =
977 + functions_evloop_init(SYSTEMD_JOURNAL_WORKER_THREADS, "SDJ", &stdout_mutex, &plugin_should_exit);
978 +
979 + functions_evloop_add_function(wg, SYSTEMD_JOURNAL_FUNCTION_NAME, function_systemd_journal,
980 + SYSTEMD_JOURNAL_DEFAULT_TIMEOUT);
981
973 - netdata_thread_t reader_thread;
974 - netdata_thread_create(&reader_thread, "SDJ_READER", NETDATA_THREAD_OPTION_DONT_LOG, reader_main, NULL);
982
983 // ------------------------------------------------------------------------
984
985 time_t started_t = now_monotonic_sec();
986
980 - size_t iteration;
987 + size_t iteration = 0;
988 usec_t step = 1000 * USEC_PER_MS;
989 bool tty = isatty(fileno(stderr)) == 1;
990
@@ -987,7 +994,9 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
994
995 heartbeat_t hb;
996 heartbeat_init(&hb);
990 - for(iteration = 0; 1 ; iteration++) {
997 + while(!plugin_should_exit) {
998 + iteration++;
999 +
1000 netdata_mutex_unlock(&stdout_mutex);
1001 heartbeat_next(&hb, step);
1002 netdata_mutex_lock(&stdout_mutex);
@@ -1002,8 +1011,5 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
1011 break;
1012 }
1013
1005 - dictionary_destroy(uids);
1006 - dictionary_destroy(gids);
1007 -
1014 exit(0);
1015 }
configure.ac
+1
@@ -2023,6 +2023,7 @@ AC_CONFIG_FILES([
2023 libnetdata/ebpf/Makefile
2024 libnetdata/eval/Makefile
2025 libnetdata/facets/Makefile
2026 + libnetdata/functions_evloop/Makefile
2027 libnetdata/july/Makefile
2028 libnetdata/locks/Makefile
2029 libnetdata/log/Makefile
daemon/main.c
+2
@@ -1904,6 +1904,8 @@ int main(int argc, char **argv) {
1904
1905 replication_initialize();
1906
1907 + rrd_functions_inflight_init();
1908 +
1909 // --------------------------------------------------------------------
1910 // get the certificate and start security
1911
database/rrdfunctions.c
+468 -134
@@ -1,3 +1,4 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 #define NETDATA_RRD_INTERNALS
3 #include "rrd.h"
4
@@ -277,16 +278,15 @@ typedef enum __attribute__((packed)) {
278 // this is 8-bit
279 } RRD_FUNCTION_OPTIONS;
280
280 -struct rrd_collector_function {
281 +struct rrd_host_function {
282 bool sync; // when true, the function is called synchronously
283 RRD_FUNCTION_OPTIONS options; // RRD_FUNCTION_OPTIONS
284 STRING *help;
285 int timeout; // the default timeout of the function
286
286 - int (*function)(BUFFER *wb, int timeout, const char *function, void *collector_data,
287 - function_data_ready_callback callback, void *callback_data);
287 + rrd_function_execute_cb_t execute_cb;
288
289 - void *collector_data;
289 + void *execute_cb_data;
290 struct rrd_collector *collector;
291 };
292
@@ -299,6 +299,7 @@ struct rrd_collector_function {
299
300 struct rrd_collector {
301 int32_t refcount;
302 + int32_t refcount_canceller;
303 pid_t tid;
304 bool running;
305 };
@@ -330,7 +331,7 @@ void rrd_collector_started(void) {
331 thread_rrd_collector = callocz(1, sizeof(struct rrd_collector));
332
333 thread_rrd_collector->tid = gettid();
333 - thread_rrd_collector->running = true;
334 + __atomic_store_n(&thread_rrd_collector->running, true, __ATOMIC_RELAXED);
335 }
336
337 // called once per collector
@@ -338,17 +339,26 @@ void rrd_collector_finished(void) {
339 if(!thread_rrd_collector)
340 return;
341
341 - thread_rrd_collector->running = false;
342 + __atomic_store_n(&thread_rrd_collector->running, false, __ATOMIC_RELAXED);
343 +
344 + int32_t expected = 0;
345 + while(!__atomic_compare_exchange_n(&thread_rrd_collector->refcount_canceller, &expected, -1, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
346 + expected = 0;
347 + sleep_usec(1 * USEC_PER_MS);
348 + }
349 +
350 rrd_collector_free(thread_rrd_collector);
351 thread_rrd_collector = NULL;
352 }
353
354 +#define rrd_collector_running(c) __atomic_load_n(&(c)->running, __ATOMIC_RELAXED)
355 +
356 static struct rrd_collector *rrd_collector_acquire(void) {
357 rrd_collector_started();
358
359 int32_t expected = __atomic_load_n(&thread_rrd_collector->refcount, __ATOMIC_RELAXED), wanted = 0;
360 do {
351 - if(expected < 0 || !thread_rrd_collector->running) {
361 + if(expected < 0 || !rrd_collector_running(thread_rrd_collector)) {
362 internal_fatal(true, "FUNCTIONS: Trying to acquire a collector that is exiting.");
363 return thread_rrd_collector;
364 }
@@ -385,7 +395,7 @@ static void rrd_collector_release(struct rrd_collector *rdc) {
395
396 static void rrd_functions_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func, void *rrdhost) {
397 RRDHOST *host = rrdhost; (void)host;
388 - struct rrd_collector_function *rdcf = func;
398 + struct rrd_host_function *rdcf = func;
399
400 rrd_collector_started();
401 rdcf->collector = rrd_collector_acquire();
@@ -397,33 +407,42 @@ static void rrd_functions_insert_callback(const DICTIONARY_ITEM *item __maybe_un
407
408 static void rrd_functions_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func,
409 void *rrdhost __maybe_unused) {
400 - struct rrd_collector_function *rdcf = func;
410 + struct rrd_host_function *rdcf = func;
411 rrd_collector_release(rdcf->collector);
412 }
413
414 static bool rrd_functions_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func,
415 void *new_func, void *rrdhost) {
416 RRDHOST *host = rrdhost; (void)host;
407 - struct rrd_collector_function *rdcf = func;
408 - struct rrd_collector_function *new_rdcf = new_func;
417 + struct rrd_host_function *rdcf = func;
418 + struct rrd_host_function *new_rdcf = new_func;
419
420 rrd_collector_started();
421
422 bool changed = false;
423
424 if(rdcf->collector != thread_rrd_collector) {
425 + netdata_log_info("FUNCTIONS: function '%s' of host '%s' changed collector from %d to %d",
426 + dictionary_acquired_item_name(item), rrdhost_hostname(host), rdcf->collector->tid, thread_rrd_collector->tid);
427 +
428 struct rrd_collector *old_rdc = rdcf->collector;
429 rdcf->collector = rrd_collector_acquire();
430 rrd_collector_release(old_rdc);
431 changed = true;
432 }
433
421 - if(rdcf->function != new_rdcf->function) {
422 - rdcf->function = new_rdcf->function;
434 + if(rdcf->execute_cb != new_rdcf->execute_cb) {
435 + netdata_log_info("FUNCTIONS: function '%s' of host '%s' changed execute callback",
436 + dictionary_acquired_item_name(item), rrdhost_hostname(host));
437 +
438 + rdcf->execute_cb = new_rdcf->execute_cb;
439 changed = true;
440 }
441
442 if(rdcf->help != new_rdcf->help) {
443 + netdata_log_info("FUNCTIONS: function '%s' of host '%s' changed help text",
444 + dictionary_acquired_item_name(item), rrdhost_hostname(host));
445 +
446 STRING *old = rdcf->help;
447 rdcf->help = new_rdcf->help;
448 string_freez(old);
@@ -433,17 +452,26 @@ static bool rrd_functions_conflict_callback(const DICTIONARY_ITEM *item __maybe_
452 string_freez(new_rdcf->help);
453
454 if(rdcf->timeout != new_rdcf->timeout) {
455 + netdata_log_info("FUNCTIONS: function '%s' of host '%s' changed timeout",
456 + dictionary_acquired_item_name(item), rrdhost_hostname(host));
457 +
458 rdcf->timeout = new_rdcf->timeout;
459 changed = true;
460 }
461
462 if(rdcf->sync != new_rdcf->sync) {
463 + netdata_log_info("FUNCTIONS: function '%s' of host '%s' changed sync/async mode",
464 + dictionary_acquired_item_name(item), rrdhost_hostname(host));
465 +
466 rdcf->sync = new_rdcf->sync;
467 changed = true;
468 }
469
445 - if(rdcf->collector_data != new_rdcf->collector_data) {
446 - rdcf->collector_data = new_rdcf->collector_data;
470 + if(rdcf->execute_cb_data != new_rdcf->execute_cb_data) {
471 + netdata_log_info("FUNCTIONS: function '%s' of host '%s' changed execute callback data",
472 + dictionary_acquired_item_name(item), rrdhost_hostname(host));
473 +
474 + rdcf->execute_cb_data = new_rdcf->execute_cb_data;
475 changed = true;
476 }
477
@@ -454,24 +482,23 @@ static bool rrd_functions_conflict_callback(const DICTIONARY_ITEM *item __maybe_
482 return changed;
483 }
484
457 -
458 -void rrdfunctions_init(RRDHOST *host) {
485 +void rrdfunctions_host_init(RRDHOST *host) {
486 if(host->functions) return;
487
488 host->functions = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
462 - &dictionary_stats_category_functions, sizeof(struct rrd_collector_function));
489 + &dictionary_stats_category_functions, sizeof(struct rrd_host_function));
490
491 dictionary_register_insert_callback(host->functions, rrd_functions_insert_callback, host);
492 dictionary_register_delete_callback(host->functions, rrd_functions_delete_callback, host);
493 dictionary_register_conflict_callback(host->functions, rrd_functions_conflict_callback, host);
494 }
495
469 -void rrdfunctions_destroy(RRDHOST *host) {
496 +void rrdfunctions_host_destroy(RRDHOST *host) {
497 dictionary_destroy(host->functions);
498 }
499
473 -void rrd_collector_add_function(RRDHOST *host, RRDSET *st, const char *name, int timeout, const char *help,
474 - bool sync, function_execute_at_collector function, void *collector_data) {
500 +void rrd_function_add(RRDHOST *host, RRDSET *st, const char *name, int timeout, const char *help,
501 + bool sync, rrd_function_execute_cb_t execute_cb, void *execute_cb_data) {
502
503 // RRDSET *st may be NULL in this function
504 // to create a GLOBAL function
@@ -482,12 +509,12 @@ void rrd_collector_add_function(RRDHOST *host, RRDSET *st, const char *name, int
509 char key[PLUGINSD_LINE_MAX + 1];
510 sanitize_function_text(key, name, PLUGINSD_LINE_MAX);
511
485 - struct rrd_collector_function tmp = {
512 + struct rrd_host_function tmp = {
513 .sync = sync,
514 .timeout = timeout,
515 .options = (st)?RRD_FUNCTION_LOCAL:RRD_FUNCTION_GLOBAL,
489 - .function = function,
490 - .collector_data = collector_data,
516 + .execute_cb = execute_cb,
517 + .execute_cb_data = execute_cb_data,
518 .help = string_strdupz(help),
519 };
520 const DICTIONARY_ITEM *item = dictionary_set_and_acquire_item(host->functions, key, &tmp, sizeof(tmp));
@@ -504,7 +531,7 @@ void rrd_functions_expose_rrdpush(RRDSET *st, BUFFER *wb) {
531 if(!st->functions_view)
532 return;
533
507 - struct rrd_collector_function *tmp;
534 + struct rrd_host_function *tmp;
535 dfe_start_read(st->functions_view, tmp) {
536 buffer_sprintf(wb
537 , PLUGINSD_KEYWORD_FUNCTION " \"%s\" %d \"%s\"\n"
@@ -519,7 +546,7 @@ void rrd_functions_expose_rrdpush(RRDSET *st, BUFFER *wb) {
546 void rrd_functions_expose_global_rrdpush(RRDHOST *host, BUFFER *wb) {
547 rrdhost_flag_clear(host, RRDHOST_FLAG_GLOBAL_FUNCTIONS_UPDATED);
548
522 - struct rrd_collector_function *tmp;
549 + struct rrd_host_function *tmp;
550 dfe_start_read(host->functions, tmp) {
551 if(!(tmp->options & RRD_FUNCTION_GLOBAL))
552 continue;
@@ -534,20 +561,6 @@ void rrd_functions_expose_global_rrdpush(RRDHOST *host, BUFFER *wb) {
561 dfe_done(tmp);
562 }
563
537 -struct rrd_function_call_wait {
538 - bool free_with_signal;
539 - bool data_are_ready;
540 - netdata_mutex_t mutex;
541 - pthread_cond_t cond;
542 - int code;
543 -};
544 -
545 -static void rrd_function_call_wait_free(struct rrd_function_call_wait *tmp) {
546 - pthread_cond_destroy(&tmp->cond);
547 - netdata_mutex_destroy(&tmp->mutex);
548 - freez(tmp);
549 -}
550 -
564 struct {
565 const char *format;
566 HTTP_CONTENT_TYPE content_type;
@@ -596,17 +609,28 @@ int rrd_call_function_error(BUFFER *wb, const char *msg, int code) {
609 return code;
610 }
611
599 -static int rrd_call_function_find(RRDHOST *host, BUFFER *wb, const char *name, size_t key_length, struct rrd_collector_function **rdcf) {
612 +static int rrd_call_function_find(RRDHOST *host, BUFFER *wb, const char *name, size_t key_length, const DICTIONARY_ITEM **item) {
613 char buffer[MAX_FUNCTION_LENGTH + 1];
614
615 strncpyz(buffer, name, MAX_FUNCTION_LENGTH);
616 char *s = NULL;
617
605 - *rdcf = NULL;
618 + bool found = false;
619 + *item = NULL;
620 if(host->functions) {
607 - while (!(*rdcf) && buffer[0]) {
608 - *rdcf = dictionary_get(host->functions, buffer);
609 - if (*rdcf) break;
621 + while (buffer[0]) {
622 + if((*item = dictionary_get_and_acquire_item(host->functions, buffer))) {
623 + found = true;
624 +
625 + struct rrd_host_function *rdcf = dictionary_acquired_item_value(*item);
626 + if(rrd_collector_running(rdcf->collector)) {
627 + break;
628 + }
629 + else {
630 + dictionary_acquired_item_release(host->functions, *item);
631 + *item = NULL;
632 + }
633 + }
634
635 // if s == NULL, set it to the end of the buffer
636 // this should happen only the first time
@@ -623,16 +647,131 @@ static int rrd_call_function_find(RRDHOST *host, BUFFER *wb, const char *name, s
647
648 buffer_flush(wb);
649
626 - if(!(*rdcf))
627 - return rrd_call_function_error(wb, "No collector is supplying this function on this host at this time.", HTTP_RESP_NOT_FOUND);
628 -
629 - if(!(*rdcf)->collector->running)
630 - return rrd_call_function_error(wb, "The collector that registered this function, is not currently running.", HTTP_RESP_SERVICE_UNAVAILABLE);
650 + if(!(*item)) {
651 + if(found)
652 + return rrd_call_function_error(wb,
653 + "The collector that registered this function, is not currently running.",
654 + HTTP_RESP_SERVICE_UNAVAILABLE);
655 + else
656 + return rrd_call_function_error(wb,
657 + "No collector is supplying this function on this host at this time.",
658 + HTTP_RESP_NOT_FOUND);
659 + }
660
661 return HTTP_RESP_OK;
662 }
663
635 -static void rrd_call_function_signal_when_ready(BUFFER *temp_wb __maybe_unused, int code, void *callback_data) {
664 +// ----------------------------------------------------------------------------
665 +
666 +struct rrd_function_inflight {
667 + bool used;
668 +
669 + RRDHOST *host;
670 + const char *transaction;
671 + const char *cmd;
672 + const char *sanitized_cmd;
673 + size_t sanitized_cmd_length;
674 + int timeout;
675 + bool cancelled;
676 +
677 + const DICTIONARY_ITEM *host_function_acquired;
678 +
679 + // the collector
680 + // we acquire this structure at the beginning,
681 + // and we release it at the end
682 + struct rrd_host_function *rdcf;
683 +
684 + struct {
685 + BUFFER *wb;
686 +
687 + // in async mode,
688 + // the function to call to send the result back
689 + rrd_function_result_callback_t cb;
690 + void *data;
691 + } result;
692 +
693 + struct {
694 + // to be called in sync mode
695 + // while the function is running
696 + // to check if the function has been cancelled
697 + rrd_function_is_cancelled_cb_t cb;
698 + void *data;
699 + } is_cancelled;
700 +
701 + struct {
702 + // to be registered by the function itself
703 + // used to signal the function to cancel
704 + rrd_function_canceller_cb_t cb;
705 + void *data;
706 + } canceller;
707 +};
708 +
709 +static DICTIONARY *rrd_functions_inflight_requests = NULL;
710 +
711 +static void rrd_functions_inflight_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
712 + struct rrd_function_inflight *r = value;
713 +
714 + // internal_error(true, "FUNCTIONS: transaction '%s' finished", r->transaction);
715 +
716 + freez((void *)r->transaction);
717 + freez((void *)r->cmd);
718 + freez((void *)r->sanitized_cmd);
719 + dictionary_acquired_item_release(r->host->functions, r->host_function_acquired);
720 +}
721 +
722 +void rrd_functions_inflight_init(void) {
723 + if(rrd_functions_inflight_requests)
724 + return;
725 +
726 + rrd_functions_inflight_requests = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct rrd_function_inflight));
727 +
728 + dictionary_register_delete_callback(rrd_functions_inflight_requests, rrd_functions_inflight_delete_cb, NULL);
729 +}
730 +
731 +void rrd_functions_inflight_destroy(void) {
732 + if(!rrd_functions_inflight_requests)
733 + return;
734 +
735 + dictionary_destroy(rrd_functions_inflight_requests);
736 + rrd_functions_inflight_requests = NULL;
737 +}
738 +
739 +static void rrd_inflight_async_function_register_canceller_cb(void *register_canceller_cb_data, rrd_function_canceller_cb_t canceller_cb, void *canceller_cb_data) {
740 + struct rrd_function_inflight *r = register_canceller_cb_data;
741 + r->canceller.cb = canceller_cb;
742 + r->canceller.data = canceller_cb_data;
743 +}
744 +
745 +// ----------------------------------------------------------------------------
746 +// waiting for async function completion
747 +
748 +struct rrd_function_call_wait {
749 + RRDHOST *host;
750 + const DICTIONARY_ITEM *host_function_acquired;
751 + char *transaction;
752 +
753 + bool free_with_signal;
754 + bool data_are_ready;
755 + netdata_mutex_t mutex;
756 + pthread_cond_t cond;
757 + int code;
758 +};
759 +
760 +static void rrd_inflight_function_cleanup(RRDHOST *host, const DICTIONARY_ITEM *host_function_acquired, const char *transaction) {
761 + dictionary_del(rrd_functions_inflight_requests, transaction);
762 + dictionary_garbage_collect(rrd_functions_inflight_requests);
763 +}
764 +
765 +static void rrd_function_call_wait_free(struct rrd_function_call_wait *tmp) {
766 + rrd_inflight_function_cleanup(tmp->host, tmp->host_function_acquired, tmp->transaction);
767 + freez(tmp->transaction);
768 +
769 + pthread_cond_destroy(&tmp->cond);
770 + netdata_mutex_destroy(&tmp->mutex);
771 + freez(tmp);
772 +}
773 +
774 +static void rrd_async_function_signal_when_ready(BUFFER *temp_wb __maybe_unused, int code, void *callback_data) {
775 struct rrd_function_call_wait *tmp = callback_data;
776 bool we_should_free = false;
777
@@ -658,115 +797,300 @@ static void rrd_call_function_signal_when_ready(BUFFER *temp_wb __maybe_unused,
797 }
798 }
799
661 -int rrd_call_function_and_wait(RRDHOST *host, BUFFER *wb, int timeout, const char *name) {
662 - int code;
800 +static void rrd_inflight_async_function_nowait_finished(BUFFER *wb, int code, void *data) {
801 + struct rrd_function_inflight *r = data;
802
664 - struct rrd_collector_function *rdcf = NULL;
803 + if(r->result.cb)
804 + r->result.cb(wb, code, r->result.data);
805
666 - char key[PLUGINSD_LINE_MAX + 1];
667 - size_t key_length = sanitize_function_text(key, name, PLUGINSD_LINE_MAX);
668 - code = rrd_call_function_find(host, wb, key, key_length, &rdcf);
669 - if(code != HTTP_RESP_OK)
670 - return code;
806 + rrd_inflight_function_cleanup(r->host, r->host_function_acquired, r->transaction);
807 +}
808
672 - if(timeout <= 0)
673 - timeout = rdcf->timeout;
809 +static bool rrd_inflight_async_function_is_cancelled(void *data) {
810 + struct rrd_function_inflight *r = data;
811 + return __atomic_load_n(&r->cancelled, __ATOMIC_RELAXED);
812 +}
813
675 - struct timespec tp;
676 - clock_gettime(CLOCK_REALTIME, &tp);
677 - tp.tv_sec += (time_t)timeout;
814 +static inline int rrd_call_function_async_and_dont_wait(struct rrd_function_inflight *r) {
815 + int code = r->rdcf->execute_cb(r->result.wb, r->timeout, r->sanitized_cmd, r->rdcf->execute_cb_data,
816 + rrd_inflight_async_function_nowait_finished, r,
817 + rrd_inflight_async_function_is_cancelled, r,
818 + rrd_inflight_async_function_register_canceller_cb, r);
819
679 - if(rdcf->sync) {
680 - code = rdcf->function(wb, timeout, key, rdcf->collector_data, NULL, NULL);
820 + if(code != HTTP_RESP_OK) {
821 + if (!buffer_strlen(r->result.wb))
822 + rrd_call_function_error(r->result.wb, "Failed to send request to the collector.", code);
823 +
824 + rrd_inflight_function_cleanup(r->host, r->host_function_acquired, r->transaction);
825 }
682 - else {
683 - struct rrd_function_call_wait *tmp = mallocz(sizeof(struct rrd_function_call_wait));
684 - tmp->free_with_signal = false;
685 - tmp->data_are_ready = false;
686 - netdata_mutex_init(&tmp->mutex);
687 - pthread_cond_init(&tmp->cond, NULL);
688 -
689 - bool we_should_free = true;
690 - BUFFER *temp_wb = buffer_create(PLUGINSD_LINE_MAX + 1, &netdata_buffers_statistics.buffers_functions); // we need it because we may give up on it
691 - temp_wb->content_type = wb->content_type;
692 - code = rdcf->function(temp_wb, timeout, key, rdcf->collector_data, rrd_call_function_signal_when_ready, tmp);
693 - if (code == HTTP_RESP_OK) {
694 - netdata_mutex_lock(&tmp->mutex);
695 -
696 - int rc = 0;
697 - while (rc == 0 && !tmp->data_are_ready) {
698 - // the mutex is unlocked within pthread_cond_timedwait()
699 - rc = pthread_cond_timedwait(&tmp->cond, &tmp->mutex, &tp);
700 - // the mutex is again ours
701 - }
826
703 - if (tmp->data_are_ready) {
704 - // we have a response
705 - buffer_fast_strcat(wb, buffer_tostring(temp_wb), buffer_strlen(temp_wb));
706 - wb->content_type = temp_wb->content_type;
707 - wb->expires = temp_wb->expires;
827 + return code;
828 +}
829
709 - if(wb->expires)
710 - buffer_cacheable(wb);
711 - else
712 - buffer_no_cacheable(wb);
830 +static int rrd_call_function_async_and_wait(struct rrd_function_inflight *r) {
831 + struct timespec tp;
832 + clock_gettime(CLOCK_REALTIME, &tp);
833 + usec_t now_ut = tp.tv_sec * USEC_PER_SEC + tp.tv_nsec / NSEC_PER_USEC;
834 + usec_t end_ut = now_ut + r->timeout * USEC_PER_SEC;
835 +
836 + struct rrd_function_call_wait *tmp = mallocz(sizeof(struct rrd_function_call_wait));
837 + tmp->free_with_signal = false;
838 + tmp->data_are_ready = false;
839 + tmp->host = r->host;
840 + tmp->host_function_acquired = r->host_function_acquired;
841 + tmp->transaction = strdupz(r->transaction);
842 + netdata_mutex_init(&tmp->mutex);
843 + pthread_cond_init(&tmp->cond, NULL);
844 +
845 + // we need a temporary BUFFER, because we may time out and the caller supplied one may vanish
846 + // so, we create a new one we guarantee will survive until the collector finishes...
847 +
848 + bool we_should_free = true;
849 + BUFFER *temp_wb = buffer_create(PLUGINSD_LINE_MAX + 1, &netdata_buffers_statistics.buffers_functions); // we need it because we may give up on it
850 + temp_wb->content_type = r->result.wb->content_type;
851 +
852 + int code = r->rdcf->execute_cb(temp_wb, r->timeout, r->sanitized_cmd, r->rdcf->execute_cb_data,
853 + // we overwrite the result callbacks,
854 + // so that we can clean up the allocations made
855 + rrd_async_function_signal_when_ready, tmp,
856 + rrd_inflight_async_function_is_cancelled, r,
857 + rrd_inflight_async_function_register_canceller_cb, r);
858 +
859 + if (code == HTTP_RESP_OK) {
860 + netdata_mutex_lock(&tmp->mutex);
861 +
862 + bool cancelled = false;
863 + int rc = 0;
864 + while (rc == 0 && !cancelled && !tmp->data_are_ready) {
865 + clock_gettime(CLOCK_REALTIME, &tp);
866 + now_ut = tp.tv_sec * USEC_PER_SEC + tp.tv_nsec / NSEC_PER_USEC;
867 +
868 + if(now_ut >= end_ut) {
869 + rc = ETIMEDOUT;
870 + break;
871 + }
872
714 - code = tmp->code;
873 + tp.tv_nsec += 10 * NSEC_PER_MSEC;
874 + if(tp.tv_nsec > (long)(1 * NSEC_PER_SEC)) {
875 + tp.tv_sec++;
876 + tp.tv_nsec -= 1 * NSEC_PER_SEC;
877 }
716 - else if (rc == ETIMEDOUT) {
717 - // timeout
718 - // we will go away and let the callback free the structure
719 - tmp->free_with_signal = true;
720 - we_should_free = false;
721 - code = rrd_call_function_error(wb, "Timeout while waiting for a response from the collector.", HTTP_RESP_GATEWAY_TIMEOUT);
878 +
879 + // the mutex is unlocked within pthread_cond_timedwait()
880 + rc = pthread_cond_timedwait(&tmp->cond, &tmp->mutex, &tp);
881 + // the mutex is again ours
882 +
883 + if(rc == ETIMEDOUT) {
884 + rc = 0;
885 + if (!tmp->data_are_ready && r->is_cancelled.cb &&
886 + r->is_cancelled.cb(r->is_cancelled.data)) {
887 +// internal_error(true, "FUNCTIONS: transaction '%s' is cancelled while waiting for response",
888 +// r->transaction);
889 + rc = 0;
890 + cancelled = true;
891 + rrd_function_cancel(r->transaction);
892 + break;
893 + }
894 }
895 + }
896 +
897 + if (tmp->data_are_ready) {
898 + // we have a response
899 + buffer_fast_strcat(r->result.wb, buffer_tostring(temp_wb), buffer_strlen(temp_wb));
900 + r->result.wb->content_type = temp_wb->content_type;
901 + r->result.wb->expires = temp_wb->expires;
902 +
903 + if(r->result.wb->expires)
904 + buffer_cacheable(r->result.wb);
905 else
724 - code = rrd_call_function_error(wb, "Failed to get the response from the collector.", HTTP_RESP_INTERNAL_SERVER_ERROR);
906 + buffer_no_cacheable(r->result.wb);
907
726 - netdata_mutex_unlock(&tmp->mutex);
908 + code = tmp->code;
909 }
728 - else {
729 - if(!buffer_strlen(wb))
730 - rrd_call_function_error(wb, "Failed to send request to the collector.", code);
910 + else if (rc == ETIMEDOUT || cancelled) {
911 + // timeout
912 + // we will go away and let the callback free the structure
913 + tmp->free_with_signal = true;
914 + we_should_free = false;
915 +
916 + if(cancelled)
917 + code = rrd_call_function_error(r->result.wb,
918 + "Request cancelled",
919 + HTTP_RESP_CLIENT_CLOSED_REQUEST);
920 + else
921 + code = rrd_call_function_error(r->result.wb,
922 + "Timeout while waiting for a response from the collector.",
923 + HTTP_RESP_GATEWAY_TIMEOUT);
924 }
925 + else
926 + code = rrd_call_function_error(r->result.wb,
927 + "Internal error while communicating with the collector",
928 + HTTP_RESP_INTERNAL_SERVER_ERROR);
929
733 - if (we_should_free) {
734 - rrd_function_call_wait_free(tmp);
735 - buffer_free(temp_wb);
736 - }
930 + netdata_mutex_unlock(&tmp->mutex);
931 + }
932 + else {
933 + if(!buffer_strlen(r->result.wb))
934 + rrd_call_function_error(r->result.wb, "The collector returned an error.", code);
935 + }
936 +
937 + if (we_should_free) {
938 + rrd_function_call_wait_free(tmp);
939 + buffer_free(temp_wb);
940 }
941
942 return code;
943 }
944
742 -int rrd_call_function_async(RRDHOST *host, BUFFER *wb, int timeout, const char *name,
743 - rrd_call_function_async_callback callback, void *callback_data) {
945 +static inline int rrd_call_function_async(struct rrd_function_inflight *r, bool wait) {
946 + if(wait)
947 + return rrd_call_function_async_and_wait(r);
948 + else
949 + return rrd_call_function_async_and_dont_wait(r);
950 +}
951 +
952 +// ----------------------------------------------------------------------------
953 +
954 +int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout, const char *cmd,
955 + bool wait, const char *transaction,
956 + rrd_function_result_callback_t result_cb, void *result_cb_data,
957 + rrd_function_is_cancelled_cb_t is_cancelled_cb, void *is_cancelled_cb_data) {
958 +
959 int code;
960 + char sanitized_cmd[PLUGINSD_LINE_MAX + 1];
961 + const DICTIONARY_ITEM *host_function_acquired = NULL;
962
746 - struct rrd_collector_function *rdcf = NULL;
747 - char key[PLUGINSD_LINE_MAX + 1];
748 - size_t key_length = sanitize_function_text(key, name, PLUGINSD_LINE_MAX);
749 - code = rrd_call_function_find(host, wb, key, key_length, &rdcf);
963 + // ------------------------------------------------------------------------
964 + // find the function
965 +
966 + size_t sanitized_cmd_length = sanitize_function_text(sanitized_cmd, cmd, PLUGINSD_LINE_MAX);
967 + code = rrd_call_function_find(host, result_wb, sanitized_cmd, sanitized_cmd_length, &host_function_acquired);
968 if(code != HTTP_RESP_OK)
969 return code;
970
971 + struct rrd_host_function *rdcf = dictionary_acquired_item_value(host_function_acquired);
972 +
973 if(timeout <= 0)
974 timeout = rdcf->timeout;
975
756 - code = rdcf->function(wb, timeout, key, rdcf->collector_data, callback, callback_data);
976
758 - if(code != HTTP_RESP_OK) {
759 - if (!buffer_strlen(wb))
760 - rrd_call_function_error(wb, "Failed to send request to the collector.", code);
977 + // ------------------------------------------------------------------------
978 + // the function can only be executed in sync mode
979 +
980 + if(rdcf->sync) {
981 + // the caller has to wait
982 +
983 + code = rdcf->execute_cb(result_wb, timeout, sanitized_cmd, rdcf->execute_cb_data,
984 + NULL, NULL, // no callback needed, it is synchronous
985 + is_cancelled_cb, is_cancelled_cb_data, // it is ok to pass these, we block the caller
986 + NULL, NULL); // no need to pass, we will wait
987 +
988 + if (code != HTTP_RESP_OK && !buffer_strlen(result_wb))
989 + rrd_call_function_error(result_wb, "Collector reported error.", code);
990 +
991 + dictionary_acquired_item_release(host->functions, host_function_acquired);
992 + return code;
993 }
994
763 - return code;
995 +
996 + // ------------------------------------------------------------------------
997 + // the function can only be executed in async mode
998 + // put the function into the inflight requests
999 +
1000 + char uuid_str[UUID_STR_LEN];
1001 + if(!transaction) {
1002 + uuid_t uuid;
1003 + uuid_generate_random(uuid);
1004 + uuid_unparse_lower(uuid, uuid_str);
1005 + transaction = uuid_str;
1006 + }
1007 +
1008 + // put the request into the inflight requests
1009 + struct rrd_function_inflight t = {
1010 + .used = false,
1011 + .host = host,
1012 + .cmd = strdupz(cmd),
1013 + .sanitized_cmd = strdupz(sanitized_cmd),
1014 + .sanitized_cmd_length = sanitized_cmd_length,
1015 + .transaction = strdupz(transaction),
1016 + .timeout = timeout,
1017 + .cancelled = false,
1018 + .host_function_acquired = host_function_acquired,
1019 + .rdcf = rdcf,
1020 + .result = {
1021 + .wb = result_wb,
1022 + .cb = result_cb,
1023 + .data = result_cb_data,
1024 + },
1025 + .is_cancelled = {
1026 + .cb = is_cancelled_cb,
1027 + .data = is_cancelled_cb_data,
1028 + }
1029 + };
1030 + struct rrd_function_inflight *r = dictionary_set(rrd_functions_inflight_requests, transaction, &t, sizeof(t));
1031 + if(r->used) {
1032 + netdata_log_info("FUNCTIONS: duplicate transaction '%s', function: '%s'", t.transaction, t.cmd);
1033 + code = rrd_call_function_error(result_wb, "duplicate transaction", HTTP_RESP_BAD_REQUEST);
1034 + freez((void *)t.transaction);
1035 + freez((void *)t.cmd);
1036 + freez((void *)t.sanitized_cmd);
1037 + dictionary_acquired_item_release(r->host->functions, t.host_function_acquired);
1038 + return code;
1039 + }
1040 + r->used = true;
1041 + // internal_error(true, "FUNCTIONS: transaction '%s' started", r->transaction);
1042 +
1043 + return rrd_call_function_async(r, wait);
1044 +}
1045 +
1046 +void rrd_function_cancel(const char *transaction) {
1047 + // internal_error(true, "FUNCTIONS: request to cancel transaction '%s'", transaction);
1048 +
1049 + const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(rrd_functions_inflight_requests, transaction);
1050 + if(!item) {
1051 + netdata_log_info("FUNCTIONS: received a cancel request for transaction '%s', but the transaction is not running.",
1052 + transaction);
1053 + return;
1054 + }
1055 +
1056 + struct rrd_function_inflight *r = dictionary_acquired_item_value(item);
1057 +
1058 + bool cancelled = __atomic_load_n(&r->cancelled, __ATOMIC_RELAXED);
1059 + if(cancelled) {
1060 + netdata_log_info("FUNCTIONS: received a cancel request for transaction '%s', but it is already cancelled.",
1061 + transaction);
1062 + goto cleanup;
1063 + }
1064 +
1065 + __atomic_store_n(&r->cancelled, true, __ATOMIC_RELAXED);
1066 +
1067 + int32_t expected = __atomic_load_n(&r->rdcf->collector->refcount_canceller, __ATOMIC_RELAXED);
1068 + int32_t wanted;
1069 + do {
1070 + if(expected < 0) {
1071 + netdata_log_info("FUNCTIONS: received a cancel request for transaction '%s', but the collector is not running.",
1072 + transaction);
1073 + goto cleanup;
1074 + }
1075 +
1076 + wanted = expected + 1;
1077 + } while(!__atomic_compare_exchange_n(&r->rdcf->collector->refcount_canceller, &expected, wanted, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
1078 +
1079 + if(r->canceller.cb)
1080 + r->canceller.cb(r->canceller.data);
1081 +
1082 + __atomic_sub_fetch(&r->rdcf->collector->refcount_canceller, 1, __ATOMIC_RELAXED);
1083 +
1084 +cleanup:
1085 + dictionary_acquired_item_release(rrd_functions_inflight_requests, item);
1086 }
1087
1088 +// ----------------------------------------------------------------------------
1089 +
1090 static void functions2json(DICTIONARY *functions, BUFFER *wb, const char *ident, const char *kq, const char *sq) {
767 - struct rrd_collector_function *t;
1091 + struct rrd_host_function *t;
1092 dfe_start_read(functions, t) {
769 - if(!t->collector->running) continue;
1093 + if(!rrd_collector_running(t->collector)) continue;
1094
1095 if(t_dfe.counter)
1096 buffer_strcat(wb, ",\n");
@@ -799,9 +1123,9 @@ void host_functions2json(RRDHOST *host, BUFFER *wb) {
1123
1124 buffer_json_member_add_object(wb, "functions");
1125
802 - struct rrd_collector_function *t;
1126 + struct rrd_host_function *t;
1127 dfe_start_read(host->functions, t) {
804 - if(!t->collector->running) continue;
1128 + if(!rrd_collector_running(t->collector)) continue;
1129
1130 buffer_json_member_add_object(wb, t_dfe.name);
1131 buffer_json_member_add_string(wb, "help", string2str(t->help));
@@ -822,9 +1146,9 @@ void host_functions2json(RRDHOST *host, BUFFER *wb) {
1146 void chart_functions_to_dict(DICTIONARY *rrdset_functions_view, DICTIONARY *dst, void *value, size_t value_size) {
1147 if(!rrdset_functions_view || !dst) return;
1148
825 - struct rrd_collector_function *t;
1149 + struct rrd_host_function *t;
1150 dfe_start_read(rrdset_functions_view, t) {
827 - if(!t->collector->running) continue;
1151 + if(!rrd_collector_running(t->collector)) continue;
1152
1153 dictionary_set(dst, t_dfe.name, value, value_size);
1154 }
@@ -834,9 +1158,9 @@ void chart_functions_to_dict(DICTIONARY *rrdset_functions_view, DICTIONARY *dst,
1158 void host_functions_to_dict(RRDHOST *host, DICTIONARY *dst, void *value, size_t value_size, STRING **help) {
1159 if(!host || !host->functions || !dictionary_entries(host->functions) || !dst) return;
1160
837 - struct rrd_collector_function *t;
1161 + struct rrd_host_function *t;
1162 dfe_start_read(host->functions, t) {
839 - if(!t->collector->running) continue;
1163 + if(!rrd_collector_running(t->collector)) continue;
1164
1165 if(help)
1166 *help = t->help;
@@ -846,10 +1170,14 @@ void host_functions_to_dict(RRDHOST *host, DICTIONARY *dst, void *value, size_t
1170 dfe_done(t);
1171 }
1172
1173 +// ----------------------------------------------------------------------------
1174
1175 int rrdhost_function_streaming(BUFFER *wb, int timeout __maybe_unused, const char *function __maybe_unused,
1176 void *collector_data __maybe_unused,
852 - function_data_ready_callback callback __maybe_unused, void *callback_data __maybe_unused) {
1177 + rrd_function_result_callback_t result_cb, void *result_cb_data,
1178 + rrd_function_is_cancelled_cb_t is_cancelled_cb, void *is_cancelled_cb_data,
1179 + rrd_function_register_canceller_cb_t register_cancel_cb, void *register_cancel_db_data) {
1180 +
1181 time_t now = now_realtime_sec();
1182
1183 buffer_flush(wb);
@@ -1468,8 +1796,14 @@ int rrdhost_function_streaming(BUFFER *wb, int timeout __maybe_unused, const cha
1796 buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + 1);
1797 buffer_json_finalize(wb);
1798
1471 - if(callback)
1472 - callback(wb, HTTP_RESP_OK, callback_data);
1799 + int response = HTTP_RESP_OK;
1800 + if(is_cancelled_cb && is_cancelled_cb(is_cancelled_cb_data)) {
1801 + buffer_flush(wb);
1802 + response = HTTP_RESP_CLIENT_CLOSED_REQUEST;
1803 + }
1804 +
1805 + if(result_cb)
1806 + result_cb(wb, response, result_cb_data);
1807
1474 - return HTTP_RESP_OK;
1808 + return response;
1809 }
database/rrdfunctions.h
+28 -13
@@ -1,26 +1,39 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 #ifndef NETDATA_RRDFUNCTIONS_H
3 #define NETDATA_RRDFUNCTIONS_H 1
4
5 +// ----------------------------------------------------------------------------
6 +
7 #include "rrd.h"
8
6 -void rrdfunctions_init(RRDHOST *host);
7 -void rrdfunctions_destroy(RRDHOST *host);
9 +typedef void (*rrd_function_result_callback_t)(BUFFER *wb, int code, void *result_cb_data);
10 +typedef bool (*rrd_function_is_cancelled_cb_t)(void *is_cancelled_cb_data);
11 +typedef void (*rrd_function_canceller_cb_t)(void *data);
12 +typedef void (*rrd_function_register_canceller_cb_t)(void *register_cancel_cb_data, rrd_function_canceller_cb_t cancel_cb, void *cancel_cb_data);
13 +typedef int (*rrd_function_execute_cb_t)(BUFFER *wb, int timeout, const char *function, void *collector_data,
14 + rrd_function_result_callback_t result_cb, void *result_cb_data,
15 + rrd_function_is_cancelled_cb_t is_cancelled_cb, void *is_cancelled_cb_data,
16 + rrd_function_register_canceller_cb_t register_cancel_cb, void *register_cancel_db_data);
17 +
18 +void rrd_functions_inflight_init(void);
19 +void rrdfunctions_host_init(RRDHOST *host);
20 +void rrdfunctions_host_destroy(RRDHOST *host);
21
22 void rrd_collector_started(void);
23 void rrd_collector_finished(void);
24
12 -typedef void (*function_data_ready_callback)(BUFFER *wb, int code, void *callback_data);
13 -
14 -typedef int (*function_execute_at_collector)(BUFFER *wb, int timeout, const char *function, void *collector_data,
15 - function_data_ready_callback callback, void *callback_data);
16 -
17 -void rrd_collector_add_function(RRDHOST *host, RRDSET *st, const char *name, int timeout, const char *help,
18 - bool sync, function_execute_at_collector function, void *collector_data);
25 +// add a function, to be run from the collector
26 +void rrd_function_add(RRDHOST *host, RRDSET *st, const char *name, int timeout, const char *help,
27 + bool sync, rrd_function_execute_cb_t execute_cb, void *execute_cb_data);
28
20 -int rrd_call_function_and_wait(RRDHOST *host, BUFFER *wb, int timeout, const char *name);
29 +// call a function, to be run from anywhere
30 +int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout, const char *cmd,
31 + bool wait, const char *transaction,
32 + rrd_function_result_callback_t result_cb, void *result_cb_data,
33 + rrd_function_is_cancelled_cb_t is_cancelled_cb, void *is_cancelled_cb_data);
34
22 -typedef void (*rrd_call_function_async_callback)(BUFFER *wb, int code, void *callback_data);
23 -int rrd_call_function_async(RRDHOST *host, BUFFER *wb, int timeout, const char *name, rrd_call_function_async_callback, void *callback_data);
35 +// cancel a running function, to be run from anywhere
36 +void rrd_function_cancel(const char *transaction);
37
38 void rrd_functions_expose_rrdpush(RRDSET *st, BUFFER *wb);
39 void rrd_functions_expose_global_rrdpush(RRDHOST *host, BUFFER *wb);
@@ -35,7 +48,9 @@ const char *functions_content_type_to_format(HTTP_CONTENT_TYPE content_type);
48 int rrd_call_function_error(BUFFER *wb, const char *msg, int code);
49
50 int rrdhost_function_streaming(BUFFER *wb, int timeout, const char *function, void *collector_data,
38 - function_data_ready_callback callback, void *callback_data);
51 + rrd_function_result_callback_t result_cb, void *result_cb_data,
52 + rrd_function_is_cancelled_cb_t is_cancelled_cb, void *is_cancelled_cb_data,
53 + rrd_function_register_canceller_cb_t register_cancel_cb, void *register_cancel_db_data);
54
55 #define RRDFUNCTIONS_STREAMING_HELP "Streaming status for parents and children."
56
database/rrdhost.c
+8 -6
@@ -335,7 +335,7 @@ int is_legacy = 1;
335 netdata_mutex_init(&host->receiver_lock);
336
337 if (likely(!archived)) {
338 - rrdfunctions_init(host);
338 + rrdfunctions_host_init(host);
339 host->rrdlabels = rrdlabels_create();
340 rrdhost_initialize_rrdpush_sender(
341 host, rrdpush_enabled, rrdpush_destination, rrdpush_api_key, rrdpush_send_charts_matching);
@@ -665,7 +665,7 @@ static void rrdhost_update(RRDHOST *host
665 if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) {
666 rrdhost_flag_clear(host, RRDHOST_FLAG_ARCHIVED);
667
668 - rrdfunctions_init(host);
668 + rrdfunctions_host_init(host);
669
670 if(!host->rrdlabels)
671 host->rrdlabels = rrdlabels_create();
@@ -1070,9 +1070,9 @@ int rrd_init(char *hostname, struct rrdhost_system_info *system_info, bool unitt
1070 // we register this only on localhost
1071 // for the other nodes, the origin server should register it
1072 rrd_collector_started(); // this creates a collector that runs for as long as netdata runs
1073 - rrd_collector_add_function(localhost, NULL, "streaming", 10,
1074 - RRDFUNCTIONS_STREAMING_HELP, true,
1075 - rrdhost_function_streaming, NULL);
1073 + rrd_function_add(localhost, NULL, "streaming", 10,
1074 + RRDFUNCTIONS_STREAMING_HELP, true,
1075 + rrdhost_function_streaming, NULL);
1076 #endif
1077
1078 if (likely(system_info)) {
@@ -1160,9 +1160,11 @@ static void rrdhost_streaming_sender_structures_free(RRDHOST *host)
1160
1161 rrdpush_sender_thread_stop(host, STREAM_HANDSHAKE_DISCONNECT_HOST_CLEANUP, true); // stop a possibly running thread
1162 cbuffer_free(host->sender->buffer);
1163 +
1164 #ifdef ENABLE_RRDPUSH_COMPRESSION
1165 rrdpush_compressor_destroy(&host->sender->compressor);
1166 #endif
1167 +
1168 replication_cleanup_sender(host->sender);
1169
1170 __atomic_sub_fetch(&netdata_buffers_statistics.rrdhost_senders, sizeof(*host->sender), __ATOMIC_RELAXED);
@@ -1266,7 +1268,7 @@ void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) {
1268 freez(host->node_id);
1269
1270 rrdfamily_index_destroy(host);
1269 - rrdfunctions_destroy(host);
1271 + rrdfunctions_host_destroy(host);
1272 rrdvariables_destroy(host->rrdvars);
1273 if (host == localhost)
1274 rrdvariables_destroy(health_rrdvars);
libnetdata/Makefile.am
+1
@@ -15,6 +15,7 @@ SUBDIRS = \
15 ebpf \
16 eval \
17 facets \
18 + functions_evloop \
19 json \
20 july \
21 health \
libnetdata/buffer/buffer.h
+11
@@ -249,6 +249,17 @@ static inline void buffer_strcat(BUFFER *wb, const char *txt) {
249 buffer_overflow_check(wb);
250 }
251
252 +static inline void buffer_contents_replace(BUFFER *wb, const char *txt, size_t len) {
253 + wb->len = 0;
254 + buffer_need_bytes(wb, len + 1);
255 +
256 + memcpy(wb->buffer, txt, len);
257 + wb->len = len;
258 + wb->buffer[wb->len] = '\0';
259 +
260 + buffer_overflow_check(wb);
261 +}
262 +
263 static inline void buffer_strncat(BUFFER *wb, const char *txt, size_t len) {
264 if(unlikely(!txt || !*txt)) return;
265
libnetdata/facets/facets.c
+488 -301
@@ -1,14 +1,43 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 #include "facets.h"
3
4 #define HISTOGRAM_COLUMNS 100
4 -
5 -static void facets_row_free(FACETS *facets __maybe_unused, FACET_ROW *row);
5 +#define FACETS_KEYS_HASHTABLE_ENTRIES 1000
6 +#define FACETS_VALUES_HASHTABLE_ENTRIES 20
7
8 // ----------------------------------------------------------------------------
9
9 -static inline void uint64_to_char(uint64_t num, char *out) {
10 - static const char id_encoding_characters[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz_0123456789";
10 +static const char id_encoding_characters[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz_0123456789";
11 +static const uint8_t id_encoding_characters_reverse[256] = {
12 + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3,
13 + ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7,
14 + ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11,
15 + ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15,
16 + ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19,
17 + ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23,
18 + ['Y'] = 24, ['Z'] = 25, ['.'] = 26, ['a'] = 27,
19 + ['b'] = 28, ['c'] = 29, ['d'] = 30, ['e'] = 31,
20 + ['f'] = 32, ['g'] = 33, ['h'] = 34, ['i'] = 35,
21 + ['j'] = 36, ['k'] = 37, ['l'] = 38, ['m'] = 39,
22 + ['n'] = 40, ['o'] = 41, ['p'] = 42, ['q'] = 43,
23 + ['r'] = 44, ['s'] = 45, ['t'] = 46, ['u'] = 47,
24 + ['v'] = 48, ['w'] = 49, ['x'] = 50, ['y'] = 51,
25 + ['z'] = 52, ['_'] = 53, ['0'] = 54, ['1'] = 55,
26 + ['2'] = 56, ['3'] = 57, ['4'] = 58, ['5'] = 59,
27 + ['6'] = 60, ['7'] = 61, ['8'] = 62, ['9'] = 63
28 +};
29
30 +__attribute__((constructor)) void initialize_facets_id_encoding_characters_reverse(void) {
31 +
32 +}
33 +
34 +#define FACET_STRING_HASH_SIZE 12
35 +#define FACETS_HASH XXH64_hash_t
36 +#define FACETS_HASH_FUNCTION(src, len) XXH3_64bits(src, len)
37 +#define FACETS_HASH_ZERO (FACETS_HASH)0
38 +
39 +static inline void facets_hash_to_str(FACETS_HASH num, char *out) {
40 + out[11] = '\0';
41 out[10] = id_encoding_characters[num & 63]; num >>= 6;
42 out[9] = id_encoding_characters[num & 63]; num >>= 6;
43 out[8] = id_encoding_characters[num & 63]; num >>= 6;
@@ -22,25 +51,54 @@ static inline void uint64_to_char(uint64_t num, char *out) {
51 out[0] = id_encoding_characters[num & 63];
52 }
53
25 -inline void facets_string_hash(const char *src, size_t len, char *out) {
26 - XXH128_hash_t hash = XXH3_128bits(src, len);
27 -
28 - uint64_to_char(hash.high64, out);
29 - uint64_to_char(hash.low64, &out[11]); // Starts right after the first 64-bit encoded string
54 +static inline FACETS_HASH str_to_facets_hash(const char *str) {
55 + FACETS_HASH num = 0;
56 + int shifts = 6 * (FACET_STRING_HASH_SIZE - 2);
57 +
58 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[0])])) << shifts; shifts -= 6;
59 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[1])])) << shifts; shifts -= 6;
60 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[2])])) << shifts; shifts -= 6;
61 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[3])])) << shifts; shifts -= 6;
62 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[4])])) << shifts; shifts -= 6;
63 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[5])])) << shifts; shifts -= 6;
64 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[6])])) << shifts; shifts -= 6;
65 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[7])])) << shifts; shifts -= 6;
66 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[8])])) << shifts; shifts -= 6;
67 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[9])])) << shifts; shifts -= 6;
68 + num |= ((FACETS_HASH)(id_encoding_characters_reverse[(uint8_t)(str[10])])) << shifts;
69 +
70 + return num;
71 +}
72
31 - out[FACET_STRING_HASH_SIZE - 1] = '\0';
73 +static const char *hash_to_static_string(FACETS_HASH hash) {
74 + static __thread char hash_str[FACET_STRING_HASH_SIZE];
75 + facets_hash_to_str(hash, hash_str);
76 + return hash_str;
77 }
78
34 -static inline void facets_zero_hash(char *out) {
35 - for(int i = 0; i < FACET_STRING_HASH_SIZE ;i++)
36 - out[i] = '0';
79 +static inline bool is_valid_string_hash(const char *s) {
80 + if(strlen(s) != FACET_STRING_HASH_SIZE - 1) {
81 + netdata_log_error("The user supplied key '%s' does not have the right length for a facets hash.", s);
82 + return false;
83 + }
84 +
85 + uint8_t *t = (uint8_t *)s;
86 + while(*t) {
87 + if(id_encoding_characters_reverse[*t] == 0 && *t != id_encoding_characters[0]) {
88 + netdata_log_error("The user supplied key '%s' contains invalid characters for a facets hash.", s);
89 + return false;
90 + }
91 +
92 + t++;
93 + }
94
38 - out[FACET_STRING_HASH_SIZE - 1] = '\0';
95 + return true;
96 }
97
98 // ----------------------------------------------------------------------------
99
100 typedef struct facet_value {
101 + FACETS_HASH hash;
102 const char *name;
103
104 bool selected;
@@ -51,15 +109,21 @@ typedef struct facet_value {
109
110 uint32_t *histogram;
111 uint32_t min, max, sum;
112 +
113 + struct {
114 + struct facet_value *next;
115 + } parent_hashtable;
116 +
117 + struct facet_value *prev, *next;
118 } FACET_VALUE;
119
120 +
121 struct facet_key {
122 FACETS *facets;
123
124 + FACETS_HASH hash;
125 const char *name;
126
61 - DICTIONARY *values;
62 -
127 FACET_KEY_OPTIONS options;
128
129 bool default_selected_for_values; // the default "selected" for all values in the dictionary
@@ -69,14 +133,21 @@ struct facet_key {
133 uint32_t key_values_selected_in_row;
134
135 struct {
72 - char hash[FACET_STRING_HASH_SIZE];
136 + bool enabled;
137 + FACET_VALUE *hashtable[FACETS_VALUES_HASHTABLE_ENTRIES];
138 + FACET_VALUE *ll;
139 + } values;
140 +
141 + struct {
142 + FACETS_HASH hash;
143 bool updated;
144 bool empty;
145 BUFFER *b;
146 + FACET_VALUE *v;
147 } current_value;
148
149 struct {
79 - FACET_VALUE *empty_value;
150 + FACET_VALUE *v;
151 } empty_value;
152
153 uint32_t order;
@@ -91,6 +162,10 @@ struct facet_key {
162 void *data;
163 } transform;
164
165 + struct {
166 + struct facet_key *next;
167 + } parent_hashtable;
168 +
169 struct facet_key *prev, *next;
170 };
171
@@ -111,8 +186,11 @@ struct facets {
186
187 DICTIONARY *accepted_params;
188
114 - FACET_KEY *keys_ll;
115 - DICTIONARY *keys;
189 + struct {
190 + FACET_KEY *hashtable[FACETS_KEYS_HASHTABLE_ENTRIES];
191 + FACET_KEY *ll;
192 + } keys;
193 +
194 FACET_ROW *base; // double linked list of the selected facets rows
195
196 uint32_t items_to_return;
@@ -125,6 +203,7 @@ struct facets {
203 } current_row;
204
205 struct {
206 + FACETS_HASH hash;
207 char *chart;
208 bool enabled;
209 uint32_t slots;
@@ -175,6 +254,253 @@ struct facets {
254
255 // ----------------------------------------------------------------------------
256
257 +static void facets_row_free(FACETS *facets __maybe_unused, FACET_ROW *row);
258 +static inline void facet_value_is_used(FACET_KEY *k, FACET_VALUE *v);
259 +static inline bool facets_key_is_facet(FACETS *facets, FACET_KEY *k);
260 +
261 +// ----------------------------------------------------------------------------
262 +// The FACET_VALUE index within each FACET_KEY
263 +
264 +#define foreach_value_in_key(k, v) \
265 + for((v) = (k)->values.ll; (v) ;(v) = (v)->next)
266 +
267 +#define foreach_value_in_key_done(v) do { ; } while(0)
268 +
269 +static inline void FACETS_VALUES_INDEX_CREATE(FACET_KEY *k) {
270 + k->values.ll = NULL;
271 +}
272 +
273 +static inline void FACETS_VALUES_INDEX_DESTROY(FACET_KEY *k) {
274 + FACET_VALUE *v = k->values.ll;
275 + while(v) {
276 + FACET_VALUE *next = v->next;
277 + freez(v->histogram);
278 + freez((void *)v->name);
279 + freez(v);
280 + v = next;
281 + }
282 + k->values.ll = NULL;
283 + memset(k->values.hashtable, 0, sizeof(k->values.hashtable));
284 + k->values.enabled = false;
285 +}
286 +
287 +static inline void FACET_VALUE_ADD_CONFLICT(FACET_KEY *k, FACET_VALUE *v, const FACET_VALUE * const nv) {
288 + if(!v->name && nv->name)
289 + // an actual value, not a filter
290 + v->name = strdupz(nv->name);
291 +
292 + if(v->name)
293 + facet_value_is_used(k, v);
294 +
295 + internal_fatal(v->name && nv->name && strcmp(v->name, nv->name) != 0,
296 + "value hash conflict: '%s' and '%s' have the same hash '%s'",
297 + v->name, nv->name, hash_to_static_string(v->hash));
298 +
299 + k->facets->operations.values.conflicts++;
300 +}
301 +
302 +static inline FACET_VALUE **facets_values_hashtable_slot(FACET_KEY *k, FACETS_HASH hash) {
303 + size_t slot = hash % FACETS_VALUES_HASHTABLE_ENTRIES;
304 + FACET_VALUE **v = &k->values.hashtable[slot];
305 +
306 + while(*v && (*v)->hash != hash)
307 + v = &((*v)->parent_hashtable.next);
308 +
309 + return v;
310 +}
311 +
312 +static inline FACET_VALUE *FACET_VALUE_ADD_TO_INDEX(FACET_KEY *k, const FACET_VALUE * const tv) {
313 + FACET_VALUE **v_ptr = facets_values_hashtable_slot(k, tv->hash);
314 +
315 + if(*v_ptr) {
316 + // already exists
317 +
318 + FACET_VALUE *v = *v_ptr;
319 + FACET_VALUE_ADD_CONFLICT(k, v, tv);
320 + return v;
321 + }
322 +
323 + // we have to add it
324 +
325 + FACET_VALUE *v = mallocz(sizeof(*v));
326 + *v_ptr = v;
327 +
328 + memcpy(v, tv, sizeof(*v));
329 +
330 + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(k->values.ll, v, prev, next);
331 +
332 + if(!v->selected)
333 + v->selected = k->default_selected_for_values;
334 +
335 + if(v->name) {
336 + // an actual value, not a filter
337 + v->name = strdupz(v->name);
338 + facet_value_is_used(k, v);
339 + }
340 +
341 + k->facets->operations.values.inserts++;
342 +
343 + return v;
344 +}
345 +
346 +static inline void FACET_VALUE_ADD_EMPTY_VALUE_TO_INDEX(FACET_KEY *k) {
347 + static const FACET_VALUE tv = {
348 + .hash = FACETS_HASH_ZERO,
349 + .name = FACET_VALUE_UNSET,
350 + };
351 +
352 + k->current_value.hash = FACETS_HASH_ZERO;
353 +
354 + if(k->empty_value.v) {
355 + FACET_VALUE_ADD_CONFLICT(k, k->empty_value.v, &tv);
356 + k->current_value.v = k->empty_value.v;
357 + }
358 + else {
359 + FACET_VALUE *v = FACET_VALUE_ADD_TO_INDEX(k, &tv);
360 + v->empty = true;
361 + k->empty_value.v = v;
362 + k->current_value.v = v;
363 + }
364 +}
365 +
366 +static inline void FACET_VALUE_ADD_CURRENT_VALUE_TO_INDEX(FACET_KEY *k) {
367 + static __thread FACET_VALUE tv = { 0 };
368 +
369 + tv.hash = FACETS_HASH_FUNCTION(buffer_tostring(k->current_value.b), buffer_strlen(k->current_value.b));
370 + tv.name = buffer_tostring(k->current_value.b);
371 +
372 + k->current_value.v = FACET_VALUE_ADD_TO_INDEX(k, &tv);
373 + k->facets->operations.values.indexed++;
374 +}
375 +
376 +static inline void FACET_VALUE_ADD_OR_UPDATE_SELECTED(FACET_KEY *k, FACETS_HASH hash) {
377 + FACET_VALUE tv = {
378 + .hash = hash,
379 + .selected = true,
380 + };
381 + FACET_VALUE_ADD_TO_INDEX(k, &tv);
382 +}
383 +
384 +static inline FACET_VALUE *FACET_VALUE_GET_CURRENT_VALUE(FACET_KEY *k) {
385 + if(unlikely(!k->current_value.v))
386 + FACET_VALUE_ADD_EMPTY_VALUE_TO_INDEX(k);
387 +
388 + return k->current_value.v;
389 +}
390 +
391 +// ----------------------------------------------------------------------------
392 +// The FACET_KEY index within each FACET
393 +
394 +#define foreach_key_in_facets(facets, k) \
395 + for((k) = (facets)->keys.ll; (k) ;(k) = (k)->next)
396 +
397 +#define foreach_key_in_facets_done(k) do { ; } while(0)
398 +
399 +static inline void facet_key_late_init(FACETS *facets, FACET_KEY *k) {
400 + if(k->values.enabled)
401 + return;
402 +
403 + if(facets_key_is_facet(facets, k)) {
404 + FACETS_VALUES_INDEX_CREATE(k);
405 + k->values.enabled = true;
406 + }
407 +}
408 +
409 +static inline void FACETS_KEYS_INDEX_CREATE(FACETS *facets) {
410 + facets->keys.ll = NULL;
411 +}
412 +
413 +static inline void FACETS_KEYS_INDEX_DESTROY(FACETS *facets) {
414 + FACET_KEY *k = facets->keys.ll;
415 + while(k) {
416 + FACET_KEY *next = k->next;
417 +
418 + FACETS_VALUES_INDEX_DESTROY(k);
419 + buffer_free(k->current_value.b);
420 + freez((void *)k->name);
421 + freez(k);
422 +
423 + k = next;
424 + }
425 + memset(facets->keys.hashtable, 0, sizeof(facets->keys.hashtable));
426 + facets->keys.ll = NULL;
427 +}
428 +
429 +static inline FACET_KEY **facets_keys_hashtable_slot(FACETS *facets, FACETS_HASH hash) {
430 + size_t slot = hash % FACETS_KEYS_HASHTABLE_ENTRIES;
431 + FACET_KEY **k = &facets->keys.hashtable[slot];
432 +
433 + while(*k && (*k)->hash != hash)
434 + k = &((*k)->parent_hashtable.next);
435 +
436 + return k;
437 +}
438 +
439 +static inline FACET_KEY *FACETS_KEY_GET_FROM_INDEX(FACETS *facets, FACETS_HASH hash) {
440 + FACET_KEY **k = facets_keys_hashtable_slot(facets, hash);
441 + return *k;
442 +}
443 +
444 +static inline FACET_KEY *FACETS_KEY_ADD_TO_INDEX(FACETS *facets, FACETS_HASH hash, const char *name, FACET_KEY_OPTIONS options) {
445 + facets->operations.keys.registered++;
446 +
447 + FACET_KEY **k_ptr = facets_keys_hashtable_slot(facets, hash);
448 +
449 + if(likely(*k_ptr)) {
450 + // already exists
451 +
452 + FACET_KEY *k = *k_ptr;
453 +
454 + if(!k->name && name) {
455 + // an actual value, not a filter
456 + k->name = strdupz(name);
457 + facet_key_late_init(facets, k);
458 + }
459 +
460 + internal_fatal(k->name && name && strcmp(k->name, name) != 0,
461 + "key hash conflict: '%s' and '%s' have the same hash '%s'",
462 + k->name, name,
463 + hash_to_static_string(hash));
464 +
465 + if(k->options & FACET_KEY_OPTION_REORDER) {
466 + k->order = facets->order++;
467 + k->options &= ~FACET_KEY_OPTION_REORDER;
468 + }
469 +
470 + return k;
471 + }
472 +
473 + // we have to add it
474 + facets->operations.keys.unique++;
475 +
476 + FACET_KEY *k = callocz(1, sizeof(*k));
477 + *k_ptr = k; // add it to the index
478 +
479 + k->hash = hash;
480 + k->facets = facets;
481 + k->options = options;
482 + k->current_value.b = buffer_create(0, NULL);
483 + k->default_selected_for_values = true;
484 +
485 + if(!(k->options & FACET_KEY_OPTION_REORDER))
486 + k->order = facets->order++;
487 +
488 + if((k->options & FACET_KEY_OPTION_FTS) || (facets->options & FACETS_OPTION_ALL_KEYS_FTS))
489 + facets->keys_filtered_by_query++;
490 +
491 + if(name) {
492 + // an actual value, not a filter
493 + k->name = strdupz(name);
494 + facet_key_late_init(facets, k);
495 + }
496 +
497 + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(facets->keys.ll, k, prev, next);
498 +
499 + return k;
500 +}
501 +
502 +// ----------------------------------------------------------------------------
503 +
504 static usec_t calculate_histogram_bar_width(usec_t after_ut, usec_t before_ut) {
505 // Array of valid durations in seconds
506 static time_t valid_durations_s[] = {
@@ -204,7 +530,7 @@ static inline usec_t facets_histogram_slot_baseline_ut(FACETS *facets, usec_t ut
530 return ut - delta_ut;
531 }
532
207 -void facets_set_histogram(FACETS *facets, const char *chart, usec_t after_ut, usec_t before_ut) {
533 +void facets_set_histogram_by_id(FACETS *facets, const char *key_id, usec_t after_ut, usec_t before_ut) {
534 if(after_ut > before_ut) {
535 usec_t t = after_ut;
536 after_ut = before_ut;
@@ -212,7 +538,17 @@ void facets_set_histogram(FACETS *facets, const char *chart, usec_t after_ut, us
538 }
539
540 facets->histogram.enabled = true;
215 - facets->histogram.chart = chart ? strdupz(chart) : NULL;
541 +
542 + if(key_id && *key_id && strlen(key_id) == FACET_STRING_HASH_SIZE - 1) {
543 + facets->histogram.chart = strdupz(key_id);
544 + facets->histogram.hash = str_to_facets_hash(facets->histogram.chart);
545 + }
546 + else {
547 + freez(facets->histogram.chart);
548 + facets->histogram.chart = NULL;
549 + facets->histogram.hash = FACETS_HASH_ZERO;
550 + }
551 +
552 facets->histogram.slot_width_ut = calculate_histogram_bar_width(after_ut, before_ut);
553 facets->histogram.after_ut = facets_histogram_slot_baseline_ut(facets, after_ut);
554 facets->histogram.before_ut = facets_histogram_slot_baseline_ut(facets, before_ut) + facets->histogram.slot_width_ut;
@@ -224,6 +560,13 @@ void facets_set_histogram(FACETS *facets, const char *chart, usec_t after_ut, us
560 }
561 }
562
563 +void facets_set_histogram_by_name(FACETS *facets, const char *key_name, usec_t after_ut, usec_t before_ut) {
564 + char hash_str[FACET_STRING_HASH_SIZE];
565 + FACETS_HASH hash = FACETS_HASH_FUNCTION(key_name, strlen(key_name));
566 + facets_hash_to_str(hash, hash_str);
567 + facets_set_histogram_by_id(facets, hash_str, after_ut, before_ut);
568 +}
569 +
570 static inline void facets_histogram_update_value(FACETS *facets, FACET_KEY *k __maybe_unused, FACET_VALUE *v, usec_t usec) {
571 if(!facets->histogram.enabled)
572 return;
@@ -253,15 +596,15 @@ static inline void facets_histogram_value_names(BUFFER *wb, FACETS *facets __may
596 if(first_key)
597 buffer_json_add_array_item_string(wb, first_key);
598
256 - if(k && k->values) {
599 + if(k && k->values.enabled) {
600 FACET_VALUE *v;
258 - dfe_start_read(k->values, v){
601 + foreach_value_in_key(k, v) {
602 if (unlikely(!v->histogram))
603 continue;
604
605 buffer_json_add_array_item_string(wb, v->name);
606 }
264 - dfe_done(v);
607 + foreach_value_in_key_done(v);
608 }
609 }
610 buffer_json_array_close(wb); // key
@@ -270,15 +613,15 @@ static inline void facets_histogram_value_names(BUFFER *wb, FACETS *facets __may
613 static inline void facets_histogram_value_units(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
614 buffer_json_member_add_array(wb, key);
615 {
273 - if(k && k->values) {
616 + if(k && k->values.enabled) {
617 FACET_VALUE *v;
275 - dfe_start_read(k->values, v){
618 + foreach_value_in_key(k, v) {
619 if (unlikely(!v->histogram))
620 continue;
621
622 buffer_json_add_array_item_string(wb, "events");
623 }
281 - dfe_done(v);
624 + foreach_value_in_key_done(v);
625 }
626 }
627 buffer_json_array_close(wb); // key
@@ -287,15 +630,15 @@ static inline void facets_histogram_value_units(BUFFER *wb, FACETS *facets __may
630 static inline void facets_histogram_value_min(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
631 buffer_json_member_add_array(wb, key);
632 {
290 - if(k && k->values) {
633 + if(k && k->values.enabled) {
634 FACET_VALUE *v;
292 - dfe_start_read(k->values, v){
635 + foreach_value_in_key(k, v) {
636 if (unlikely(!v->histogram))
637 continue;
638
639 buffer_json_add_array_item_uint64(wb, v->min);
640 }
298 - dfe_done(v);
641 + foreach_value_in_key_done(v);
642 }
643 }
644 buffer_json_array_close(wb); // key
@@ -304,15 +647,15 @@ static inline void facets_histogram_value_min(BUFFER *wb, FACETS *facets __maybe
647 static inline void facets_histogram_value_max(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
648 buffer_json_member_add_array(wb, key);
649 {
307 - if(k && k->values) {
650 + if(k && k->values.enabled) {
651 FACET_VALUE *v;
309 - dfe_start_read(k->values, v){
652 + foreach_value_in_key(k, v) {
653 if (unlikely(!v->histogram))
654 continue;
655
656 buffer_json_add_array_item_uint64(wb, v->max);
657 }
315 - dfe_done(v);
658 + foreach_value_in_key_done(v);
659 }
660 }
661 buffer_json_array_close(wb); // key
@@ -321,15 +664,15 @@ static inline void facets_histogram_value_max(BUFFER *wb, FACETS *facets __maybe
664 static inline void facets_histogram_value_avg(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
665 buffer_json_member_add_array(wb, key);
666 {
324 - if(k && k->values) {
667 + if(k && k->values.enabled) {
668 FACET_VALUE *v;
326 - dfe_start_read(k->values, v){
669 + foreach_value_in_key(k, v) {
670 if (unlikely(!v->histogram))
671 continue;
672
673 buffer_json_add_array_item_double(wb, (double) v->sum / (double) facets->histogram.slots);
674 }
332 - dfe_done(v);
675 + foreach_value_in_key_done(v);
676 }
677 }
678 buffer_json_array_close(wb); // key
@@ -338,15 +681,15 @@ static inline void facets_histogram_value_avg(BUFFER *wb, FACETS *facets __maybe
681 static inline void facets_histogram_value_arp(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
682 buffer_json_member_add_array(wb, key);
683 {
341 - if(k && k->values) {
684 + if(k && k->values.enabled) {
685 FACET_VALUE *v;
343 - dfe_start_read(k->values, v){
686 + foreach_value_in_key(k, v) {
687 if (unlikely(!v->histogram))
688 continue;
689
690 buffer_json_add_array_item_uint64(wb, 0);
691 }
349 - dfe_done(v);
692 + foreach_value_in_key_done(v);
693 }
694 }
695 buffer_json_array_close(wb); // key
@@ -355,15 +698,15 @@ static inline void facets_histogram_value_arp(BUFFER *wb, FACETS *facets __maybe
698 static inline void facets_histogram_value_con(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key, uint32_t sum) {
699 buffer_json_member_add_array(wb, key);
700 {
358 - if(k && k->values) {
701 + if(k && k->values.enabled) {
702 FACET_VALUE *v;
360 - dfe_start_read(k->values, v){
703 + foreach_value_in_key(k, v) {
704 if (unlikely(!v->histogram))
705 continue;
706
707 buffer_json_add_array_item_double(wb, (double) v->sum * 100.0 / (double) sum);
708 }
366 - dfe_done(v);
709 + foreach_value_in_key_done(v);
710 }
711 }
712 buffer_json_array_close(wb); // key
@@ -373,9 +716,9 @@ static void facets_histogram_generate(FACETS *facets, FACET_KEY *k, BUFFER *wb)
716 size_t dimensions = 0;
717 uint32_t min = UINT32_MAX, max = 0, sum = 0, count = 0;
718
376 - if(k && k->values) {
719 + if(k && k->values.enabled) {
720 FACET_VALUE *v;
378 - dfe_start_read(k->values, v){
721 + foreach_value_in_key(k, v) {
722 if (unlikely(!v->histogram))
723 continue;
724
@@ -406,7 +749,7 @@ static void facets_histogram_generate(FACETS *facets, FACET_KEY *k, BUFFER *wb)
749 v->sum += n;
750 }
751 }
409 - dfe_done(v);
752 + foreach_value_in_key_done(v);
753 }
754
755 buffer_json_member_add_object(wb, "summary");
@@ -526,10 +869,10 @@ static void facets_histogram_generate(FACETS *facets, FACET_KEY *k, BUFFER *wb)
869 buffer_json_array_close(wb); // instances
870
871 buffer_json_member_add_array(wb, "dimensions");
529 - if(dimensions && k && k->values) {
872 + if(dimensions && k && k->values.enabled) {
873 size_t pri = 0;
874 FACET_VALUE *v;
532 - dfe_start_read(k->values, v) {
875 + foreach_value_in_key(k, v) {
876 if(unlikely(!v->histogram))
877 continue;
878
@@ -554,7 +897,7 @@ static void facets_histogram_generate(FACETS *facets, FACET_KEY *k, BUFFER *wb)
897 }
898 buffer_json_object_close(wb); // dimension
899 }
557 - dfe_done(v);
900 + foreach_value_in_key_done(v);
901 }
902 buffer_json_array_close(wb); // dimensions
903
@@ -612,7 +955,7 @@ static void facets_histogram_generate(FACETS *facets, FACET_KEY *k, BUFFER *wb)
955 buffer_json_object_close(wb); // point
956
957 buffer_json_member_add_array(wb, "data");
615 - if(k && k->values) {
958 + if(k && k->values.enabled) {
959 usec_t t = facets->histogram.after_ut;
960 for(uint32_t i = 0; i < facets->histogram.slots ;i++) {
961 buffer_json_add_array_item_array(wb); // row
@@ -620,7 +963,7 @@ static void facets_histogram_generate(FACETS *facets, FACET_KEY *k, BUFFER *wb)
963 buffer_json_add_array_item_time_ms(wb, t / USEC_PER_SEC);
964
965 FACET_VALUE *v;
623 - dfe_start_read(k->values, v){
966 + foreach_value_in_key(k, v) {
967 if (unlikely(!v->histogram))
968 continue;
969
@@ -632,7 +975,7 @@ static void facets_histogram_generate(FACETS *facets, FACET_KEY *k, BUFFER *wb)
975
976 buffer_json_array_close(wb); // point
977 }
635 - dfe_done(v);
978 + foreach_value_in_key_done(v);
979 }
980 buffer_json_array_close(wb); // row
981
@@ -689,7 +1032,7 @@ static void facets_histogram_generate(FACETS *facets, FACET_KEY *k, BUFFER *wb)
1032 buffer_json_member_add_object(wb, "view");
1033 {
1034 char title[1024 + 1] = "Events Distribution";
692 - FACET_KEY *kt = dictionary_get(facets->keys, facets->histogram.chart);
1035 + FACET_KEY *kt = FACETS_KEY_GET_FROM_INDEX(facets, facets->histogram.hash);
1036 if(kt && kt->name)
1037 snprintfz(title, 1024, "Events Distribution by %s", kt->name);
1038
@@ -790,138 +1133,12 @@ static inline bool facets_key_is_facet(FACETS *facets, FACET_KEY *k) {
1133 return false;
1134 }
1135
793 -// ----------------------------------------------------------------------------
794 -// FACET_VALUE dictionary hooks
795 -
796 -static void facet_value_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
797 - FACET_VALUE *v = value;
798 - FACET_KEY *k = data;
799 -
800 - if(!v->selected)
801 - v->selected = k->default_selected_for_values;
802 -
803 - if(v->name) {
804 - // an actual value, not a filter
805 - v->name = strdupz(v->name);
806 - facet_value_is_used(k, v);
807 - }
808 -
809 - k->facets->operations.values.inserts++;
810 -}
811 -
812 -static bool facet_value_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data) {
813 - FACET_VALUE *v = old_value;
814 - FACET_VALUE *nv = new_value;
815 - FACET_KEY *k = data;
816 -
817 - if(!v->name && nv->name)
818 - // an actual value, not a filter
819 - v->name = strdupz(nv->name);
820 -
821 - if(v->name)
822 - facet_value_is_used(k, v);
823 -
824 - internal_fatal(v->name && nv->name && strcmp(v->name, nv->name) != 0, "value hash conflict: '%s' and '%s' have the same hash '%s'", v->name, nv->name,
825 - dictionary_acquired_item_name(item));
826 -
827 - k->facets->operations.values.conflicts++;
828 -
829 - return false;
830 -}
831 -
832 -static void facet_value_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
833 - FACET_VALUE *v = value;
834 - freez(v->histogram);
835 - freez((char *)v->name);
836 -}
837 -
838 -// ----------------------------------------------------------------------------
839 -// FACET_KEY dictionary hooks
840 -
841 -static inline void facet_key_late_init(FACETS *facets, FACET_KEY *k) {
842 - if(k->values)
843 - return;
844 -
845 - if(facets_key_is_facet(facets, k)) {
846 - k->values = dictionary_create_advanced(
847 - DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
848 - NULL, sizeof(FACET_VALUE));
849 - dictionary_register_insert_callback(k->values, facet_value_insert_callback, k);
850 - dictionary_register_conflict_callback(k->values, facet_value_conflict_callback, k);
851 - dictionary_register_delete_callback(k->values, facet_value_delete_callback, k);
852 - }
853 -}
854 -
855 -static void facet_key_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
856 - FACET_KEY *k = value;
857 - FACETS *facets = data;
858 -
859 - k->facets = facets;
860 -
861 - if(!(k->options & FACET_KEY_OPTION_REORDER))
862 - k->order = facets->order++;
863 -
864 - if((k->options & FACET_KEY_OPTION_FTS) || (facets->options & FACETS_OPTION_ALL_KEYS_FTS))
865 - facets->keys_filtered_by_query++;
866 -
867 - if(k->name) {
868 - // an actual value, not a filter
869 - k->name = strdupz(k->name);
870 - facet_key_late_init(facets, k);
871 - }
872 -
873 - k->current_value.b = buffer_create(0, NULL);
874 -
875 - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(facets->keys_ll, k, prev, next);
876 -
877 - facets->operations.keys.registered++;
878 - facets->operations.keys.unique++;
879 -}
880 -
881 -static bool facet_key_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data) {
882 - FACET_KEY *k = old_value;
883 - FACET_KEY *nk = new_value;
884 - FACETS *facets = data;
885 -
886 - if(!k->name && nk->name) {
887 - // an actual value, not a filter
888 - k->name = strdupz(nk->name);
889 - facet_key_late_init(facets, k);
890 - }
891 -
892 - internal_fatal(k->name && nk->name && strcmp(k->name, nk->name) != 0, "key hash conflict: '%s' and '%s' have the same hash '%s'", k->name, nk->name,
893 - dictionary_acquired_item_name(item));
894 -
895 - if(k->options & FACET_KEY_OPTION_REORDER) {
896 - k->order = facets->order++;
897 - k->options &= ~FACET_KEY_OPTION_REORDER;
898 - }
899 -
900 - facets->operations.keys.registered++;
901 -
902 - return false;
903 -}
904 -
905 -static void facet_key_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
906 - FACET_KEY *k = value;
907 - FACETS *facets = data;
908 -
909 - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(facets->keys_ll, k, prev, next);
910 -
911 - dictionary_destroy(k->values);
912 - buffer_free(k->current_value.b);
913 - freez((char *)k->name);
914 -}
915 -
1136 // ----------------------------------------------------------------------------
1137
1138 FACETS *facets_create(uint32_t items_to_return, FACETS_OPTIONS options, const char *visible_keys, const char *facet_keys, const char *non_facet_keys) {
1139 FACETS *facets = callocz(1, sizeof(FACETS));
1140 facets->options = options;
921 - facets->keys = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE|DICT_OPTION_FIXED_SIZE, NULL, sizeof(FACET_KEY));
922 - dictionary_register_insert_callback(facets->keys, facet_key_insert_callback, facets);
923 - dictionary_register_conflict_callback(facets->keys, facet_key_conflict_callback, facets);
924 - dictionary_register_delete_callback(facets->keys, facet_key_delete_callback, facets);
1141 + FACETS_KEYS_INDEX_CREATE(facets);
1142
1143 if(facet_keys && *facet_keys)
1144 facets->included_keys = simple_pattern_create(facet_keys, "|", SIMPLE_PATTERN_EXACT, true);
@@ -942,7 +1159,7 @@ FACETS *facets_create(uint32_t items_to_return, FACETS_OPTIONS options, const ch
1159
1160 void facets_destroy(FACETS *facets) {
1161 dictionary_destroy(facets->accepted_params);
945 - dictionary_destroy(facets->keys);
1162 + FACETS_KEYS_INDEX_DESTROY(facets);
1163 simple_pattern_free(facets->visible_keys);
1164 simple_pattern_free(facets->included_keys);
1165 simple_pattern_free(facets->excluded_keys);
@@ -966,14 +1183,7 @@ void facets_accepted_param(FACETS *facets, const char *param) {
1183 }
1184
1185 static inline FACET_KEY *facets_register_key_name_length(FACETS *facets, const char *key, size_t key_length, FACET_KEY_OPTIONS options) {
969 - FACET_KEY tk = {
970 - .name = key,
971 - .options = options,
972 - .default_selected_for_values = true,
973 - };
974 - char hash[FACET_STRING_HASH_SIZE];
975 - facets_string_hash(tk.name, key_length, hash);
976 - return dictionary_set(facets->keys, hash, &tk, sizeof(tk));
1186 + return FACETS_KEY_ADD_TO_INDEX(facets, FACETS_HASH_FUNCTION(key, key_length), key, options);
1187 }
1188
1189 inline FACET_KEY *facets_register_key_name(FACETS *facets, const char *key, FACET_KEY_OPTIONS options) {
@@ -1011,12 +1221,15 @@ void facets_set_anchor(FACETS *facets, usec_t anchor, FACETS_ANCHOR_DIRECTION di
1221 }
1222
1223 inline FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, FACET_KEY_OPTIONS options) {
1014 - FACET_KEY tk = {
1015 - .options = options,
1016 - .default_selected_for_values = true,
1017 - };
1018 - FACET_KEY *k = dictionary_set(facets->keys, key_id, &tk, sizeof(tk));
1224 + if(!is_valid_string_hash(key_id))
1225 + return NULL;
1226
1227 + FACETS_HASH hash = str_to_facets_hash(key_id);
1228 +
1229 + internal_error(strcmp(hash_to_static_string(hash), key_id) != 0,
1230 + "Regenerating the user supplied key, does not produce the same hash string");
1231 +
1232 + FACET_KEY *k = FACETS_KEY_ADD_TO_INDEX(facets, hash, NULL, options);
1233 k->options |= FACET_KEY_OPTION_FACET;
1234 k->options &= ~FACET_KEY_OPTION_NO_FACET;
1235 facet_key_late_init(facets, k);
@@ -1024,14 +1237,14 @@ inline FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, F
1237 return k;
1238 }
1239
1027 -void facets_register_facet_id_filter(FACETS *facets, const char *key_id, char *value_ids, FACET_KEY_OPTIONS options) {
1240 +void facets_register_facet_id_filter(FACETS *facets, const char *key_id, char *value_id, FACET_KEY_OPTIONS options) {
1241 FACET_KEY *k = facets_register_facet_id(facets, key_id, options);
1029 - k->default_selected_for_values = false;
1030 -
1031 - FACET_VALUE tv = {
1032 - .selected = true,
1033 - };
1034 - dictionary_set(k->values, value_ids, &tv, sizeof(tv));
1242 + if(k) {
1243 + if(is_valid_string_hash(value_id)) {
1244 + k->default_selected_for_values = false;
1245 + FACET_VALUE_ADD_OR_UPDATE_SELECTED(k, str_to_facets_hash(value_id));
1246 + }
1247 + }
1248 }
1249
1250 void facets_set_current_row_severity(FACETS *facets, FACET_ROW_SEVERITY severity) {
@@ -1044,26 +1257,34 @@ void facets_data_only_mode(FACETS *facets) {
1257
1258 // ----------------------------------------------------------------------------
1259
1047 -static inline void facets_check_value(FACETS *facets __maybe_unused, FACET_KEY *k) {
1260 +static inline void facets_key_set_empty_value(FACETS *facets, FACET_KEY *k) {
1261 + k->current_value.updated = true;
1262 + k->current_value.empty = true;
1263 +
1264 facets->operations.values.registered++;
1265 + facets->operations.values.empty++;
1266 +
1267 + buffer_contents_replace(k->current_value.b, FACET_VALUE_UNSET, sizeof(FACET_VALUE_UNSET) - 1);
1268 +
1269 + if(k->values.enabled)
1270 + FACET_VALUE_ADD_EMPTY_VALUE_TO_INDEX(k);
1271 + else {
1272 + k->key_found_in_row++;
1273 + k->key_values_selected_in_row++;
1274 + }
1275 +}
1276 +
1277 +static inline void facets_key_check_value(FACETS *facets, FACET_KEY *k) {
1278 + k->current_value.updated = true;
1279 + k->current_value.empty = false;
1280
1050 - if(!k->current_value.updated)
1051 - buffer_flush(k->current_value.b);
1281 + facets->operations.values.registered++;
1282
1283 if(k->transform.cb) {
1284 facets->operations.values.transformed++;
1285 k->transform.cb(facets, k->current_value.b, k->transform.data);
1286 }
1287
1058 - if(!k->current_value.updated) {
1059 - buffer_fast_strcat(k->current_value.b, FACET_VALUE_UNSET, sizeof(FACET_VALUE_UNSET) - 1);
1060 - k->current_value.updated = true;
1061 - k->current_value.empty = true;
1062 - facets->operations.values.empty++;
1063 - }
1064 - else
1065 - k->current_value.empty = false;
1066 -
1288 // bool found = false;
1289 // if(strstr(buffer_tostring(k->current_value), "fprintd") != NULL)
1290 // found = true;
@@ -1074,32 +1295,8 @@ static inline void facets_check_value(FACETS *facets __maybe_unused, FACET_KEY *
1295 facets->current_row.keys_matched_by_query++;
1296 }
1297
1077 - if(k->values) {
1078 - if(k->current_value.empty) {
1079 - facets_zero_hash(k->current_value.hash);
1080 -
1081 - FACET_VALUE tk = {
1082 - .name = FACET_VALUE_UNSET,
1083 - };
1084 -
1085 - if(k->empty_value.empty_value) {
1086 - facet_value_conflict_callback(NULL, k->empty_value.empty_value, &tk, k);
1087 - }
1088 - else {
1089 - FACET_VALUE *tkv = dictionary_set(k->values, k->current_value.hash, &tk, sizeof(tk));
1090 - tkv->empty = true;
1091 - k->empty_value.empty_value = tkv;
1092 - }
1093 - }
1094 - else {
1095 - FACET_VALUE tk = {
1096 - .name = buffer_tostring(k->current_value.b),
1097 - };
1098 - facets_string_hash(tk.name, buffer_strlen(k->current_value.b), k->current_value.hash);
1099 - dictionary_set(k->values, k->current_value.hash, &tk, sizeof(tk));
1100 - facets->operations.values.indexed++;
1101 - }
1102 - }
1298 + if(k->values.enabled)
1299 + FACET_VALUE_ADD_CURRENT_VALUE_TO_INDEX(k);
1300 else {
1301 k->key_found_in_row++;
1302 k->key_values_selected_in_row++;
@@ -1110,18 +1307,15 @@ void facets_add_key_value(FACETS *facets, const char *key, const char *value) {
1307 FACET_KEY *k = facets_register_key_name(facets, key, 0);
1308 buffer_flush(k->current_value.b);
1309 buffer_strcat(k->current_value.b, value);
1113 - k->current_value.updated = true;
1310
1115 - facets_check_value(facets, k);
1311 + facets_key_check_value(facets, k);
1312 }
1313
1314 void facets_add_key_value_length(FACETS *facets, const char *key, size_t key_len, const char *value, size_t value_len) {
1315 FACET_KEY *k = facets_register_key_name_length(facets, key, key_len, 0);
1120 - buffer_flush(k->current_value.b);
1121 - buffer_strncat(k->current_value.b, value, value_len);
1122 - k->current_value.updated = true;
1316 + buffer_contents_replace(k->current_value.b, value, value_len);
1317
1124 - facets_check_value(facets, k);
1318 + facets_key_check_value(facets, k);
1319 }
1320
1321 // ----------------------------------------------------------------------------
@@ -1181,7 +1375,7 @@ static FACET_ROW *facets_row_create(FACETS *facets, usec_t usec, FACET_ROW *into
1375 row->usec = usec;
1376
1377 FACET_KEY *k;
1184 - dfe_start_read(facets->keys, k) {
1378 + foreach_key_in_facets(facets, k) {
1379 FACET_ROW_KEY_VALUE t = {
1380 .tmp = (k->current_value.updated) ? buffer_tostring(k->current_value.b) : FACET_VALUE_UNSET,
1381 .wb = NULL,
@@ -1189,7 +1383,7 @@ static FACET_ROW *facets_row_create(FACETS *facets, usec_t usec, FACET_ROW *into
1383 };
1384 dictionary_set(row->dict, k->name, &t, sizeof(t));
1385 }
1192 - dfe_done(k);
1386 + foreach_key_in_facets_done(k);
1387
1388 return row;
1389 }
@@ -1318,15 +1512,15 @@ static void facets_row_keep(FACETS *facets, usec_t usec) {
1512
1513 void facets_rows_begin(FACETS *facets) {
1514 FACET_KEY *k;
1321 - // dfe_start_read(facets->keys, k) {
1322 - for(k = facets->keys_ll ; k ; k = k->next) {
1515 + foreach_key_in_facets(facets, k) {
1516 k->key_found_in_row = 0;
1517 k->key_values_selected_in_row = 0;
1518 k->current_value.updated = false;
1519 k->current_value.empty = false;
1327 - k->current_value.hash[0] = '\0';
1520 + k->current_value.hash = FACETS_HASH_ZERO;
1521 + k->current_value.v = NULL;
1522 }
1329 - // dfe_done(k);
1523 + foreach_key_in_facets_done(k);
1524
1525 facets->current_row.severity = FACET_ROW_SEVERITY_NORMAL;
1526 facets->current_row.keys_matched_by_query = 0;
@@ -1342,11 +1536,10 @@ void facets_row_finished(FACETS *facets, usec_t usec) {
1536 uint32_t selected_by = 0;
1537
1538 FACET_KEY *k;
1345 - // dfe_start_read(facets->keys, k) {
1346 - for(k = facets->keys_ll ; k ; k = k->next) {
1539 + foreach_key_in_facets(facets, k) {
1540 if(!k->key_found_in_row) {
1541 // put the FACET_VALUE_UNSET value into it
1349 - facets_check_value(facets, k);
1542 + facets_key_set_empty_value(facets, k);
1543 }
1544
1545 internal_fatal(!k->key_found_in_row, "all keys should be found in the row at this point");
@@ -1355,27 +1548,23 @@ void facets_row_finished(FACETS *facets, usec_t usec) {
1548
1549 k->key_found_in_row = 1;
1550
1358 - total_keys += k->key_found_in_row;
1551 + total_keys++;
1552 selected_by += (k->key_values_selected_in_row) ? 1 : 0;
1553 }
1361 - // dfe_done(k);
1554 + foreach_key_in_facets_done(k);
1555
1556 if(selected_by >= total_keys - 1) {
1557 uint32_t found = 0;
1558
1366 - // dfe_start_read(facets->keys, k){
1367 - for(k = facets->keys_ll ; k ; k = k->next) {
1559 + foreach_key_in_facets(facets, k) {
1560 uint32_t counted_by = selected_by;
1561
1562 if (counted_by != total_keys && !k->key_values_selected_in_row)
1563 counted_by++;
1564
1565 if(counted_by == total_keys) {
1374 - if(k->values) {
1375 - if(!k->current_value.hash[0])
1376 - facets_string_hash(buffer_tostring(k->current_value.b), buffer_strlen(k->current_value.b), k->current_value.hash);
1377 -
1378 - FACET_VALUE *v = dictionary_get(k->values, k->current_value.hash);
1566 + if(k->values.enabled) {
1567 + FACET_VALUE *v = FACET_VALUE_GET_CURRENT_VALUE(k);
1568 v->final_facet_value_counter++;
1569
1570 if(selected_by == total_keys)
@@ -1385,7 +1574,7 @@ void facets_row_finished(FACETS *facets, usec_t usec) {
1574 found++;
1575 }
1576 }
1388 - // dfe_done(k);
1577 + foreach_key_in_facets_done(k);
1578
1579 internal_fatal(!found, "We should find at least one facet to count this row");
1580 (void)found;
@@ -1427,20 +1616,20 @@ void facets_accepted_parameters_to_json_array(FACETS *facets, BUFFER *wb, bool w
1616 if(facets->accepted_params) {
1617 void *t;
1618 dfe_start_read(facets->accepted_params, t) {
1430 - buffer_json_add_array_item_string(wb, t_dfe.name);
1431 - }
1619 + buffer_json_add_array_item_string(wb, t_dfe.name);
1620 + }
1621 dfe_done(t);
1622 }
1623
1624 if(with_keys) {
1625 FACET_KEY *k;
1437 - dfe_start_read(facets->keys, k){
1438 - if (!k->values)
1439 - continue;
1626 + foreach_key_in_facets(facets, k){
1627 + if (!k->values.enabled)
1628 + continue;
1629
1441 - buffer_json_add_array_item_string(wb, k_dfe.name);
1442 - }
1443 - dfe_done(k);
1630 + buffer_json_add_array_item_string(wb, hash_to_static_string(k->hash));
1631 + }
1632 + foreach_key_in_facets_done(k);
1633 }
1634 }
1635 buffer_json_array_close(wb); // accepted_params
@@ -1467,13 +1656,13 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1656 buffer_json_member_add_array(wb, "facets");
1657 {
1658 FACET_KEY *k;
1470 - dfe_start_read(facets->keys, k) {
1471 - if(!k->values)
1659 + foreach_key_in_facets(facets, k) {
1660 + if(!k->values.enabled)
1661 continue;
1662
1663 buffer_json_add_array_item_object(wb); // key
1664 {
1476 - buffer_json_member_add_string(wb, "id", k_dfe.name);
1665 + buffer_json_member_add_string(wb, "id", hash_to_static_string(k->hash));
1666 buffer_json_member_add_string(wb, "name", k->name);
1667
1668 if(!k->order)
@@ -1483,22 +1672,22 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1672 buffer_json_member_add_array(wb, "options");
1673 {
1674 FACET_VALUE *v;
1486 - dfe_start_read(k->values, v) {
1675 + foreach_value_in_key(k, v) {
1676 buffer_json_add_array_item_object(wb);
1677 {
1489 - buffer_json_member_add_string(wb, "id", v_dfe.name);
1678 + buffer_json_member_add_string(wb, "id", hash_to_static_string(v->hash));
1679 buffer_json_member_add_string(wb, "name", v->name);
1680 buffer_json_member_add_uint64(wb, "count", v->final_facet_value_counter);
1681 }
1682 buffer_json_object_close(wb);
1683 }
1495 - dfe_done(v);
1684 + foreach_value_in_key_done(v);
1685 }
1686 buffer_json_array_close(wb); // options
1687 }
1688 buffer_json_object_close(wb); // key
1689 }
1501 - dfe_done(k);
1690 + foreach_key_in_facets_done(k);
1691 }
1692 buffer_json_array_close(wb); // facets
1693 }
@@ -1534,11 +1723,11 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1723 NULL);
1724
1725 FACET_KEY *k;
1537 - dfe_start_read(facets->keys, k){
1726 + foreach_key_in_facets(facets, k) {
1727 RRDF_FIELD_OPTIONS options = RRDF_FIELD_OPTS_NONE;
1728 bool visible = k->options & (FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_STICKY);
1729
1541 - if ((facets->options & FACETS_OPTION_ALL_FACETS_VISIBLE && k->values))
1730 + if ((facets->options & FACETS_OPTION_ALL_FACETS_VISIBLE && k->values.enabled))
1731 visible = true;
1732
1733 if (!visible)
@@ -1550,9 +1739,11 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1739 if (k->options & FACET_KEY_OPTION_MAIN_TEXT)
1740 options |= RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_WRAP;
1741
1742 + const char *hash_str = hash_to_static_string(k->hash);
1743 +
1744 buffer_rrdf_table_add_field(
1745 wb, field_id++,
1555 - k_dfe.name, k->name ? k->name : k_dfe.name,
1746 + hash_str, k->name ? k->name : hash_str,
1747 RRDF_FIELD_TYPE_STRING,
1748 (k->options & FACET_KEY_OPTION_RICH_TEXT) ? RRDF_FIELD_VISUAL_RICH : RRDF_FIELD_VISUAL_VALUE,
1749 RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
@@ -1564,7 +1755,7 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1755 options,
1756 FACET_VALUE_UNSET);
1757 }
1567 - dfe_done(k);
1758 + foreach_key_in_facets_done(k);
1759 }
1760 buffer_json_object_close(wb); // columns
1761 }
@@ -1594,8 +1785,7 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1785 buffer_json_object_close(wb);
1786
1787 FACET_KEY *k;
1597 - dfe_start_read(facets->keys, k)
1598 - {
1788 + foreach_key_in_facets(facets, k) {
1789 FACET_ROW_KEY_VALUE *rkv = dictionary_get(row->dict, k->name);
1790
1791 if(unlikely(k->dynamic.cb)) {
@@ -1612,7 +1802,7 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1802 buffer_json_add_array_item_string(wb, buffer_tostring(rkv->wb));
1803 }
1804 }
1615 - dfe_done(k);
1805 + foreach_key_in_facets_done(k);
1806 buffer_json_array_close(wb); // each row
1807 }
1808 }
@@ -1625,38 +1815,35 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1815 }
1816
1817 if(facets->histogram.enabled && !(facets->options & FACETS_OPTION_DISABLE_HISTOGRAM)) {
1628 - const char *first_histogram = NULL;
1818 + FACETS_HASH first_histogram_hash = 0;
1819 buffer_json_member_add_array(wb, "available_histograms");
1820 {
1821 FACET_KEY *k;
1632 - dfe_start_read(facets->keys, k) {
1633 - if (!k->values)
1822 + foreach_key_in_facets(facets, k) {
1823 + if (!k->values.enabled)
1824 continue;
1825
1636 - if(unlikely(!first_histogram))
1637 - first_histogram = k_dfe.name;
1826 + if(unlikely(!first_histogram_hash))
1827 + first_histogram_hash = k->hash;
1828
1829 buffer_json_add_array_item_object(wb);
1640 - buffer_json_member_add_string(wb, "id", k_dfe.name);
1830 + buffer_json_member_add_string(wb, "id", hash_to_static_string(k->hash));
1831 buffer_json_member_add_string(wb, "name", k->name);
1832 buffer_json_object_close(wb);
1833 }
1644 - dfe_done(k);
1834 + foreach_key_in_facets_done(k);
1835 }
1836 buffer_json_array_close(wb);
1837
1838 {
1649 - const char *id = facets->histogram.chart;
1650 - FACET_KEY *k = dictionary_get(facets->keys, id);
1651 - if(!k || !k->values) {
1652 - id = first_histogram;
1653 - k = dictionary_get(facets->keys, id);
1654 - }
1839 + FACET_KEY *k = FACETS_KEY_GET_FROM_INDEX(facets, facets->histogram.hash);
1840 + if(!k || !k->values.enabled)
1841 + k = FACETS_KEY_GET_FROM_INDEX(facets, first_histogram_hash);
1842
1843 buffer_json_member_add_object(wb, "histogram");
1844 {
1658 - buffer_json_member_add_string(wb, "id", id);
1659 - buffer_json_member_add_string(wb, "name", k->name);
1845 + buffer_json_member_add_string(wb, "id", k ? hash_to_static_string(k->hash) : "");
1846 + buffer_json_member_add_string(wb, "name", k ? k->name : "");
1847 buffer_json_member_add_object(wb, "chart");
1848 facets_histogram_generate(facets, k, wb);
1849 buffer_json_object_close(wb);
libnetdata/facets/facets.h
+5 -5
@@ -1,3 +1,5 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 #ifndef FACETS_H
4 #define FACETS_H 1
5
@@ -46,9 +48,6 @@ typedef struct facet_row {
48 typedef struct facets FACETS;
49 typedef struct facet_key FACET_KEY;
50
49 -#define FACET_STRING_HASH_SIZE 23
50 -void facets_string_hash(const char *src, size_t len, char *out);
51 -
51 typedef void (*facets_key_transformer_t)(FACETS *facets __maybe_unused, BUFFER *wb, void *data);
52 typedef void (*facet_dynamic_row_t)(FACETS *facets, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data);
53 FACET_KEY *facets_register_dynamic_key_name(FACETS *facets, const char *key, FACET_KEY_OPTIONS options, facet_dynamic_row_t cb, void *data);
@@ -75,8 +74,9 @@ void facets_set_query(FACETS *facets, const char *query);
74 void facets_set_items(FACETS *facets, uint32_t items);
75 void facets_set_anchor(FACETS *facets, usec_t anchor, FACETS_ANCHOR_DIRECTION direction);
76 FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, FACET_KEY_OPTIONS options);
78 -void facets_register_facet_id_filter(FACETS *facets, const char *key_id, char *value_ids, FACET_KEY_OPTIONS options);
79 -void facets_set_histogram(FACETS *facets, const char *chart, usec_t after_ut, usec_t before_ut);
77 +void facets_register_facet_id_filter(FACETS *facets, const char *key_id, char *value_id, FACET_KEY_OPTIONS options);
78 +void facets_set_histogram_by_id(FACETS *facets, const char *key_id, usec_t after_ut, usec_t before_ut);
79 +void facets_set_histogram_by_name(FACETS *facets, const char *key_name, usec_t after_ut, usec_t before_ut);
80
81 void facets_add_key_value(FACETS *facets, const char *key, const char *value);
82 void facets_add_key_value_length(FACETS *facets, const char *key, size_t key_len, const char *value, size_t value_len);
libnetdata/functions_evloop/Makefile.am new
+8
@@ -0,0 +1,8 @@
1 +# SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +AUTOMAKE_OPTIONS = subdir-objects
4 +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
5 +
6 +dist_noinst_DATA = \
7 + README.md \
8 + $(NULL)
libnetdata/functions_evloop/README.md
libnetdata/functions_evloop/functions_evloop.c new
+207
@@ -0,0 +1,207 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "functions_evloop.h"
4 +
5 +#define MAX_FUNCTION_PARAMETERS 1024
6 +
7 +struct functions_evloop_worker_job {
8 + bool used;
9 + bool running;
10 + bool cancelled;
11 + char *cmd;
12 + const char *transaction;
13 + time_t timeout;
14 + functions_evloop_worker_execute_t cb;
15 +};
16 +
17 +struct rrd_functions_expectation {
18 + const char *function;
19 + size_t function_length;
20 + functions_evloop_worker_execute_t cb;
21 + time_t default_timeout;
22 + struct rrd_functions_expectation *prev, *next;
23 +};
24 +
25 +struct functions_evloop_globals {
26 + const char *tag;
27 +
28 + DICTIONARY *worker_queue;
29 + pthread_mutex_t worker_mutex;
30 + pthread_cond_t worker_cond_var;
31 + size_t workers;
32 +
33 + netdata_mutex_t *stdout_mutex;
34 + bool *plugin_should_exit;
35 +
36 + netdata_thread_t reader_thread;
37 + netdata_thread_t *worker_threads;
38 +
39 + struct rrd_functions_expectation *expectations;
40 +};
41 +
42 +static void *rrd_functions_worker_globals_worker_main(void *arg) {
43 + struct functions_evloop_globals *wg = arg;
44 +
45 + while (true) {
46 + pthread_mutex_lock(&wg->worker_mutex);
47 +
48 + while (dictionary_entries(wg->worker_queue) == 0) {
49 + pthread_cond_wait(&wg->worker_cond_var, &wg->worker_mutex);
50 + }
51 +
52 + const DICTIONARY_ITEM *acquired = NULL;
53 + struct functions_evloop_worker_job *j;
54 + dfe_start_write(wg->worker_queue, j) {
55 + if(j->running || j->cancelled)
56 + continue;
57 +
58 + acquired = dictionary_acquired_item_dup(wg->worker_queue, j_dfe.item);
59 + j->running = true;
60 + break;
61 + }
62 + dfe_done(j);
63 +
64 + pthread_mutex_unlock(&wg->worker_mutex);
65 +
66 + if(acquired) {
67 + j = dictionary_acquired_item_value(acquired);
68 + j->cb(j->transaction, j->cmd, j->timeout, &j->cancelled);
69 + dictionary_del(wg->worker_queue, j->transaction);
70 + dictionary_acquired_item_release(wg->worker_queue, acquired);
71 + dictionary_garbage_collect(wg->worker_queue);
72 + }
73 + }
74 + return NULL;
75 +}
76 +
77 +static void *rrd_functions_worker_globals_reader_main(void *arg) {
78 + struct functions_evloop_globals *wg = arg;
79 +
80 + char buffer[PLUGINSD_LINE_MAX + 1];
81 +
82 + char *s = NULL;
83 + while(!(*wg->plugin_should_exit) && (s = fgets(buffer, PLUGINSD_LINE_MAX, stdin))) {
84 +
85 + char *words[MAX_FUNCTION_PARAMETERS] = { NULL };
86 + size_t num_words = quoted_strings_splitter_pluginsd(buffer, words, MAX_FUNCTION_PARAMETERS);
87 +
88 + const char *keyword = get_word(words, num_words, 0);
89 +
90 + if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION) == 0) {
91 + char *transaction = get_word(words, num_words, 1);
92 + char *timeout_s = get_word(words, num_words, 2);
93 + char *function = get_word(words, num_words, 3);
94 +
95 + if(!transaction || !*transaction || !timeout_s || !*timeout_s || !function || !*function) {
96 + netdata_log_error("Received incomplete %s (transaction = '%s', timeout = '%s', function = '%s'). Ignoring it.",
97 + keyword,
98 + transaction?transaction:"(unset)",
99 + timeout_s?timeout_s:"(unset)",
100 + function?function:"(unset)");
101 + }
102 + else {
103 + int timeout = str2i(timeout_s);
104 +
105 + bool found = false;
106 + struct rrd_functions_expectation *we;
107 + for(we = wg->expectations; we ;we = we->next) {
108 + if(strncmp(function, we->function, we->function_length) == 0) {
109 + struct functions_evloop_worker_job t = {
110 + .cmd = strdupz(function),
111 + .transaction = strdupz(transaction),
112 + .running = false,
113 + .cancelled = false,
114 + .timeout = timeout > 0 ? timeout : we->default_timeout,
115 + .used = false,
116 + .cb = we->cb,
117 + };
118 + struct functions_evloop_worker_job *j = dictionary_set(wg->worker_queue, transaction, &t, sizeof(t));
119 + if(j->used) {
120 + netdata_log_error("Received duplicate function transaction '%s'", transaction);
121 + freez((void *)t.cmd);
122 + freez((void *)t.transaction);
123 + }
124 + else {
125 + found = true;
126 + j->used = true;
127 + pthread_cond_signal(&wg->worker_cond_var);
128 + }
129 + }
130 + }
131 +
132 + if(!found) {
133 + netdata_mutex_lock(wg->stdout_mutex);
134 + pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_NOT_FOUND,
135 + "No function with this name found.");
136 + netdata_mutex_unlock(wg->stdout_mutex);
137 + }
138 + }
139 + }
140 + else if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION_CANCEL) == 0) {
141 + char *transaction = get_word(words, num_words, 1);
142 + const DICTIONARY_ITEM *acquired = dictionary_get_and_acquire_item(wg->worker_queue, transaction);
143 + if(acquired) {
144 + struct functions_evloop_worker_job *j = dictionary_acquired_item_value(acquired);
145 + __atomic_store_n(&j->cancelled, true, __ATOMIC_RELAXED);
146 + dictionary_acquired_item_release(wg->worker_queue, acquired);
147 + dictionary_del(wg->worker_queue, transaction);
148 + dictionary_garbage_collect(wg->worker_queue);
149 + }
150 + else
151 + netdata_log_error("Received CANCEL for transaction '%s', but it not available here", transaction);
152 + }
153 + else
154 + netdata_log_error("Received unknown command: %s", keyword?keyword:"(unset)");
155 + }
156 +
157 + if(!s || feof(stdin) || ferror(stdin)) {
158 + *wg->plugin_should_exit = true;
159 + netdata_log_error("Received error on stdin.");
160 + }
161 +
162 + exit(1);
163 +}
164 +
165 +void worker_queue_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
166 + struct functions_evloop_worker_job *j = value;
167 + freez((void *)j->cmd);
168 + freez((void *)j->transaction);
169 +}
170 +
171 +struct functions_evloop_globals *functions_evloop_init(size_t worker_threads, const char *tag, netdata_mutex_t *stdout_mutex, bool *plugin_should_exit) {
172 + struct functions_evloop_globals *wg = callocz(1, sizeof(struct functions_evloop_globals));
173 +
174 + wg->worker_queue = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
175 + dictionary_register_delete_callback(wg->worker_queue, worker_queue_delete_cb, NULL);
176 +
177 + pthread_mutex_init(&wg->worker_mutex, NULL);
178 + pthread_cond_init(&wg->worker_cond_var, NULL);
179 +
180 + wg->plugin_should_exit = plugin_should_exit;
181 + wg->stdout_mutex = stdout_mutex;
182 + wg->workers = worker_threads;
183 + wg->worker_threads = callocz(wg->workers, sizeof(netdata_thread_t ));
184 + wg->tag = tag;
185 +
186 + char tag_buffer[NETDATA_THREAD_TAG_MAX + 1];
187 + snprintfz(tag_buffer, NETDATA_THREAD_TAG_MAX, "%s_READER", wg->tag);
188 + netdata_thread_create(&wg->reader_thread, tag_buffer, NETDATA_THREAD_OPTION_DONT_LOG,
189 + rrd_functions_worker_globals_reader_main, wg);
190 +
191 + for(size_t i = 0; i < wg->workers ; i++) {
192 + snprintfz(tag_buffer, NETDATA_THREAD_TAG_MAX, "%s_WORK[%zu]", wg->tag, i+1);
193 + netdata_thread_create(&wg->worker_threads[i], tag_buffer, NETDATA_THREAD_OPTION_DONT_LOG,
194 + rrd_functions_worker_globals_worker_main, wg);
195 + }
196 +
197 + return wg;
198 +}
199 +
200 +void functions_evloop_add_function(struct functions_evloop_globals *wg, const char *function, functions_evloop_worker_execute_t cb, time_t default_timeout) {
201 + struct rrd_functions_expectation *we = callocz(1, sizeof(*we));
202 + we->function = function;
203 + we->function_length = strlen(we->function);
204 + we->cb = cb;
205 + we->default_timeout = default_timeout;
206 + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(wg->expectations, we, prev, next);
207 +}
libnetdata/functions_evloop/functions_evloop.h new
+98
@@ -0,0 +1,98 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_FUNCTIONS_EVLOOP_H
4 +#define NETDATA_FUNCTIONS_EVLOOP_H
5 +
6 +#include "../libnetdata.h"
7 +
8 +#define PLUGINSD_KEYWORD_CHART "CHART"
9 +#define PLUGINSD_KEYWORD_CHART_DEFINITION_END "CHART_DEFINITION_END"
10 +#define PLUGINSD_KEYWORD_DIMENSION "DIMENSION"
11 +#define PLUGINSD_KEYWORD_BEGIN "BEGIN"
12 +#define PLUGINSD_KEYWORD_SET "SET"
13 +#define PLUGINSD_KEYWORD_END "END"
14 +#define PLUGINSD_KEYWORD_FLUSH "FLUSH"
15 +#define PLUGINSD_KEYWORD_DISABLE "DISABLE"
16 +#define PLUGINSD_KEYWORD_VARIABLE "VARIABLE"
17 +#define PLUGINSD_KEYWORD_LABEL "LABEL"
18 +#define PLUGINSD_KEYWORD_OVERWRITE "OVERWRITE"
19 +#define PLUGINSD_KEYWORD_CLABEL "CLABEL"
20 +#define PLUGINSD_KEYWORD_CLABEL_COMMIT "CLABEL_COMMIT"
21 +#define PLUGINSD_KEYWORD_FUNCTION "FUNCTION"
22 +#define PLUGINSD_KEYWORD_FUNCTION_CANCEL "FUNCTION_CANCEL"
23 +#define PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN "FUNCTION_RESULT_BEGIN"
24 +#define PLUGINSD_KEYWORD_FUNCTION_RESULT_END "FUNCTION_RESULT_END"
25 +
26 +#define PLUGINSD_KEYWORD_REPLAY_CHART "REPLAY_CHART"
27 +#define PLUGINSD_KEYWORD_REPLAY_BEGIN "RBEGIN"
28 +#define PLUGINSD_KEYWORD_REPLAY_SET "RSET"
29 +#define PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE "RDSTATE"
30 +#define PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE "RSSTATE"
31 +#define PLUGINSD_KEYWORD_REPLAY_END "REND"
32 +
33 +#define PLUGINSD_KEYWORD_BEGIN_V2 "BEGIN2"
34 +#define PLUGINSD_KEYWORD_SET_V2 "SET2"
35 +#define PLUGINSD_KEYWORD_END_V2 "END2"
36 +
37 +#define PLUGINSD_KEYWORD_HOST_DEFINE "HOST_DEFINE"
38 +#define PLUGINSD_KEYWORD_HOST_DEFINE_END "HOST_DEFINE_END"
39 +#define PLUGINSD_KEYWORD_HOST_LABEL "HOST_LABEL"
40 +#define PLUGINSD_KEYWORD_HOST "HOST"
41 +
42 +#define PLUGINSD_KEYWORD_DYNCFG_ENABLE "DYNCFG_ENABLE"
43 +#define PLUGINSD_KEYWORD_DYNCFG_REGISTER_MODULE "DYNCFG_REGISTER_MODULE"
44 +
45 +#define PLUGINSD_KEYWORD_REPORT_JOB_STATUS "REPORT_JOB_STATUS"
46 +
47 +#define PLUGINSD_KEYWORD_EXIT "EXIT"
48 +
49 +#define PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT 10 // seconds
50 +
51 +typedef void (*functions_evloop_worker_execute_t)(const char *transaction, char *function, int timeout, bool *cancelled);
52 +struct functions_evloop_worker_job;
53 +struct functions_evloop_globals *functions_evloop_init(size_t worker_threads, const char *tag, netdata_mutex_t *stdout_mutex, bool *plugin_should_exit);
54 +void functions_evloop_add_function(struct functions_evloop_globals *wg, const char *function, functions_evloop_worker_execute_t cb, time_t default_timeout);
55 +
56 +
57 +#define pluginsd_function_result_begin_to_buffer(wb, transaction, code, content_type, expires) \
58 + buffer_sprintf(wb \
59 + , PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN " \"%s\" %d \"%s\" %ld\n" \
60 + , (transaction) ? (transaction) : "" \
61 + , (int)(code) \
62 + , (content_type) ? (content_type) : "" \
63 + , (long int)(expires) \
64 + )
65 +
66 +#define pluginsd_function_result_end_to_buffer(wb) \
67 + buffer_strcat(wb, "\n" PLUGINSD_KEYWORD_FUNCTION_RESULT_END "\n")
68 +
69 +#define pluginsd_function_result_begin_to_stdout(transaction, code, content_type, expires) \
70 + fprintf(stdout \
71 + , PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN " \"%s\" %d \"%s\" %ld\n" \
72 + , (transaction) ? (transaction) : "" \
73 + , (int)(code) \
74 + , (content_type) ? (content_type) : "" \
75 + , (long int)(expires) \
76 + )
77 +
78 +#define pluginsd_function_result_end_to_stdout() \
79 + fprintf(stdout, "\n" PLUGINSD_KEYWORD_FUNCTION_RESULT_END "\n")
80 +
81 +static inline void pluginsd_function_json_error_to_stdout(const char *transaction, int code, const char *msg) {
82 + char buffer[PLUGINSD_LINE_MAX + 1];
83 + json_escape_string(buffer, msg, PLUGINSD_LINE_MAX);
84 +
85 + pluginsd_function_result_begin_to_stdout(transaction, code, "application/json", now_realtime_sec());
86 + fprintf(stdout, "{\"status\":%d,\"error_message\":\"%s\"}", code, buffer);
87 + pluginsd_function_result_end_to_stdout();
88 + fflush(stdout);
89 +}
90 +
91 +static inline void pluginsd_function_result_to_stdout(const char *transaction, int code, const char *content_type, time_t expires, BUFFER *result) {
92 + pluginsd_function_result_begin_to_stdout(transaction, code, content_type, expires);
93 + fwrite(buffer_tostring(result), buffer_strlen(result), 1, stdout);
94 + pluginsd_function_result_end_to_stdout();
95 + fflush(stdout);
96 +}
97 +
98 +#endif //NETDATA_FUNCTIONS_EVLOOP_H
libnetdata/inlined.h
+2 -2
@@ -394,10 +394,10 @@ static inline NETDATA_DOUBLE str2ndd_encoded(const char *src, char **endptr) {
394 return str2ndd(src, endptr) * sign;
395 }
396
397 -static inline char *strncpyz(char *dst, const char *src, size_t n) {
397 +static inline char *strncpyz(char *dst, const char *src, size_t dst_size_minus_1) {
398 char *p = dst;
399
400 - while (*src && n--)
400 + while (*src && dst_size_minus_1--)
401 *dst++ = *src++;
402
403 *dst = '\0';
libnetdata/libnetdata.h
+1
@@ -842,6 +842,7 @@ extern char *netdata_configured_host_prefix;
842 #include "gorilla/gorilla.h"
843 #include "facets/facets.h"
844 #include "dyn_conf/dyn_conf.h"
845 +#include "functions_evloop/functions_evloop.h"
846
847 // BEWARE: this exists in alarm-notify.sh
848 #define DEFAULT_CLOUD_BASE_URL "https://app.netdata.cloud"
streaming/rrdpush.h
+1
@@ -6,6 +6,7 @@
6 #include "libnetdata/libnetdata.h"
7 #include "daemon/common.h"
8 #include "web/server/web_client.h"
9 +#include "database/rrdfunctions.h"
10 #include "database/rrd.h"
11
12 #define CONNECTED_TO_SIZE 100
streaming/sender.c
+11 -1
@@ -940,6 +940,7 @@ void stream_execute_function_callback(BUFFER *func_wb, int code, void *data) {
940 buffer_strlen(func_wb),
941 now_realtime_usec() - tmp->received_ut);
942 }
943 +
944 string_freez(tmp->transaction);
945 buffer_free(func_wb);
946 freez(tmp);
@@ -989,7 +990,9 @@ void execute_commands(struct sender_state *s) {
990 tmp->transaction = string_strdupz(transaction);
991 BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX + 1, &netdata_buffers_statistics.buffers_functions);
992
992 - int code = rrd_call_function_async(s->host, wb, timeout, function, stream_execute_function_callback, tmp);
993 + int code = rrd_function_run(s->host, wb, timeout, function, false, transaction,
994 + stream_execute_function_callback, tmp, NULL, NULL);
995 +
996 if(code != HTTP_RESP_OK) {
997 if (!buffer_strlen(wb))
998 rrd_call_function_error(wb, "Failed to route request to collector", code);
@@ -998,6 +1001,13 @@ void execute_commands(struct sender_state *s) {
1001 }
1002 }
1003 }
1004 + else if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION_CANCEL) == 0) {
1005 + worker_is_busy(WORKER_SENDER_JOB_FUNCTION_REQUEST);
1006 +
1007 + char *transaction = get_word(words, num_words, 1);
1008 + if(transaction && *transaction)
1009 + rrd_function_cancel(transaction);
1010 + }
1011 else if (keyword && strcmp(keyword, PLUGINSD_KEYWORD_REPLAY_CHART) == 0) {
1012 worker_is_busy(WORKER_SENDER_JOB_REPLAY_REQUEST);
1013
web/api/web_api_v1.c
+3 -1
@@ -1412,7 +1412,9 @@ int web_client_api_request_v1_function(RRDHOST *host, struct web_client *w, char
1412 wb->content_type = CT_APPLICATION_JSON;
1413 buffer_no_cacheable(wb);
1414
1415 - return rrd_call_function_and_wait(host, wb, timeout, function);
1415 + return rrd_function_run(host, wb, timeout, function, true, NULL,
1416 + NULL, NULL,
1417 + web_client_interrupt_callback, w);
1418 }
1419
1420 int web_client_api_request_v1_functions(RRDHOST *host, struct web_client *w, char *url __maybe_unused) {
web/server/static/static-threaded.c
+1 -1
@@ -370,7 +370,7 @@ static int web_server_snd_callback(POLLINFO *pi, short int *events) {
370
371 netdata_log_debug(D_WEB_CLIENT, "%llu: sending data on fd %d.", w->id, fd);
372
373 - int ret = web_client_send(w);
373 + ssize_t ret = web_client_send(w);
374
375 if(unlikely(ret < 0)) {
376 retval = -1;