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;