master
c 490 lines 18.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "functions_evloop.h"
4
5 static void functions_evloop_config_cb(const char *transaction, char *function, usec_t *stop_monotonic_ut,
6 bool *cancelled, BUFFER *payload, HTTP_ACCESS access,
7 const char *source, void *data);
8
9 struct functions_evloop_worker_job {
10 bool used;
11 bool running;
12 bool cancelled;
13 usec_t stop_monotonic_ut;
14 char *cmd;
15 const char *transaction;
16 time_t timeout;
17
18 BUFFER *payload;
19 HTTP_ACCESS access;
20 const char *source;
21
22 functions_evloop_worker_execute_t cb;
23 void *cb_data;
24 };
25
26 static void worker_job_cleanup(struct functions_evloop_worker_job *j) {
27 freez((void *)j->cmd);
28 freez((void *)j->transaction);
29 freez((void *)j->source);
30 buffer_free(j->payload);
31 }
32
33 struct rrd_functions_expectation {
34 const char *function;
35 size_t function_length;
36 functions_evloop_worker_execute_t cb;
37 void *cb_data;
38 time_t default_timeout;
39 struct rrd_functions_expectation *prev, *next;
40 };
41
42 struct functions_evloop_globals {
43 const char *tag;
44
45 DICTIONARY *worker_queue;
46 netdata_mutex_t worker_mutex;
47 netdata_cond_t worker_cond_var;
48 size_t workers;
49
50 netdata_mutex_t *stdout_mutex;
51 bool *plugin_should_exit;
52 int *status;
53 bool workers_exit; // all workers are waiting on the same condition - this makes them all exit, when any is cancelled
54
55 ND_THREAD *reader_thread;
56 ND_THREAD **worker_threads;
57
58 struct {
59 DICTIONARY *nodes;
60 } dyncfg;
61
62 struct rrd_functions_expectation *expectations;
63
64 struct buffered_reader reader;
65 BUFFER *buffer;
66 char *words[MAX_FUNCTION_PARAMETERS];
67 struct {
68 size_t last_len; // to remember the last pos - do not use a pointer, the buffer may realloc...
69 bool enabled;
70 char *transaction;
71 char *function;
72 char *timeout_s;
73 char *access;
74 char *source;
75 char *content_type;
76 } deferred;
77 };
78
79 static void rrd_functions_worker_canceller(void *data) {
80 struct functions_evloop_globals *wg = data;
81 netdata_mutex_lock(&wg->worker_mutex);
82 __atomic_store_n(&wg->workers_exit, true, __ATOMIC_RELAXED);
83 netdata_cond_signal(&wg->worker_cond_var);
84 netdata_mutex_unlock(&wg->worker_mutex);
85 }
86
87 static void rrd_functions_worker_globals_worker_main(void *arg) {
88 struct functions_evloop_globals *wg = arg;
89
90 nd_thread_register_canceller(rrd_functions_worker_canceller, wg);
91
92 while (true) {
93 const DICTIONARY_ITEM *acquired = NULL;
94 struct functions_evloop_worker_job *j;
95
96 netdata_mutex_lock(&wg->worker_mutex);
97
98 // Keep the scan and the wait under worker_mutex so a new-job signal
99 // cannot land after we decide to sleep but before the thread blocks.
100 while(!__atomic_load_n(&wg->workers_exit, __ATOMIC_RELAXED) && !nd_thread_signaled_to_cancel()) {
101 dfe_start_write(wg->worker_queue, j) {
102 if(j->running || __atomic_load_n(&j->cancelled, __ATOMIC_RELAXED))
103 continue;
104
105 acquired = dictionary_acquired_item_dup(wg->worker_queue, j_dfe.item);
106 j->running = true;
107 break;
108 }
109 dfe_done(j);
110
111 if(acquired)
112 break;
113
114 netdata_cond_wait(&wg->worker_cond_var, &wg->worker_mutex);
115 }
116
117 netdata_mutex_unlock(&wg->worker_mutex);
118
119 if(__atomic_load_n(&wg->workers_exit, __ATOMIC_RELAXED) || nd_thread_signaled_to_cancel()) {
120 if(acquired)
121 dictionary_acquired_item_release(wg->worker_queue, acquired);
122
123 break;
124 }
125
126 if(acquired) {
127 ND_LOG_STACK lgs[] = {
128 ND_LOG_FIELD_TXT(NDF_REQUEST, j->cmd),
129 ND_LOG_FIELD_END(),
130 };
131 ND_LOG_STACK_PUSH(lgs);
132
133 j = dictionary_acquired_item_value(acquired);
134 j->cb(j->transaction, j->cmd, &j->stop_monotonic_ut, &j->cancelled, j->payload, j->access, j->source, j->cb_data);
135 dictionary_del(wg->worker_queue, j->transaction);
136 dictionary_acquired_item_release(wg->worker_queue, acquired);
137 dictionary_garbage_collect(wg->worker_queue);
138 }
139 }
140 }
141
142 static void worker_add_job(struct functions_evloop_globals *wg, const char *keyword, char *transaction, char *function, char *timeout_s, BUFFER *payload, const char *access, const char *source) {
143 if(!transaction || !*transaction || !timeout_s || !*timeout_s || !function || !*function) {
144 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Received incomplete %s (transaction = '%s', timeout = '%s', function = '%s'). Ignoring it.",
145 keyword,
146 transaction?transaction:"(unset)",
147 timeout_s?timeout_s:"(unset)",
148 function?function:"(unset)");
149 }
150 else {
151 // nd_log(NDLS_COLLECTORS, NDLP_INFO, "WORKER JOB: keyword '%s', transaction '%s', function '%s', timeout '%s', access '%s', source '%s', payload '%s'",
152 // keyword, transaction, function, timeout_s, access, source, payload ? buffer_tostring(payload) : "NONE");
153
154 int timeout = str2i(timeout_s);
155
156 const char *msg = "No function with this name found";
157 bool found = false;
158 struct rrd_functions_expectation *we;
159 for(we = wg->expectations; we ;we = we->next) {
160 if(strncmp(function, we->function, we->function_length) == 0) {
161 if(timeout <= 0)
162 timeout = (int)we->default_timeout;
163
164 struct functions_evloop_worker_job t = {
165 .cmd = strdupz(function),
166 .transaction = strdupz(transaction),
167 .running = false,
168 .cancelled = false,
169 .timeout = timeout,
170 .stop_monotonic_ut = now_monotonic_usec() + (timeout * USEC_PER_SEC),
171 .used = false,
172 .payload = buffer_dup(payload),
173 .access = http_access_from_hex(access),
174 .source = source ? strdupz(source) : NULL,
175 .cb = we->cb,
176 .cb_data = we->cb_data,
177 };
178 struct functions_evloop_worker_job *j = dictionary_set(wg->worker_queue, transaction, &t, sizeof(t));
179 if(j->used) {
180 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Received duplicate function transaction '%s'. Ignoring it.", transaction);
181 worker_job_cleanup(&t);
182 msg = "Duplicate function transaction. Ignoring it.";
183 }
184 else {
185 found = true;
186 j->used = true;
187 netdata_mutex_lock(&wg->worker_mutex);
188 netdata_cond_signal(&wg->worker_cond_var);
189 netdata_mutex_unlock(&wg->worker_mutex);
190 }
191 }
192 }
193
194 if(!found) {
195 netdata_mutex_lock(wg->stdout_mutex);
196 pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_NOT_FOUND, msg);
197 netdata_mutex_unlock(wg->stdout_mutex);
198 }
199 }
200 }
201
202 static bool rrd_function_worker_global_process_input(struct functions_evloop_globals *wg) {
203 if(wg->deferred.enabled) {
204 char *s = (char *)buffer_tostring(wg->buffer);
205
206 if(strstr(&s[wg->deferred.last_len], PLUGINSD_CALL_FUNCTION_PAYLOAD_END "\n") != NULL) {
207 // nd_log(NDLS_COLLECTORS, NDLP_INFO, "FUNCTION PAYLOAD END");
208
209 if(wg->deferred.last_len > 0)
210 // remove the trailing newline from the buffer
211 wg->deferred.last_len--;
212
213 s[wg->deferred.last_len] = '\0';
214 wg->buffer->len = wg->deferred.last_len;
215 wg->buffer->content_type = content_type_string2id(wg->deferred.content_type);
216 worker_add_job(wg, PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN,
217 wg->deferred.transaction, wg->deferred.function,
218 wg->deferred.timeout_s, wg->buffer, wg->deferred.access, wg->deferred.source);
219 buffer_flush(wg->buffer);
220
221 freez(wg->deferred.transaction);
222 freez(wg->deferred.function);
223 freez(wg->deferred.timeout_s);
224 freez(wg->deferred.access);
225 freez(wg->deferred.source);
226 freez(wg->deferred.content_type);
227 memset(&wg->deferred, 0, sizeof(wg->deferred));
228 }
229 else
230 wg->deferred.last_len = wg->buffer->len;
231
232 return false;
233 }
234
235 size_t num_words = quoted_strings_splitter_whitespace((char *)buffer_tostring(wg->buffer), wg->words, _countof(wg->words));
236 const char *keyword = get_word(wg->words, num_words, 0);
237
238 char **words = wg->words;
239 if(keyword && (strcmp(keyword, PLUGINSD_CALL_FUNCTION) == 0)) {
240 char *transaction = get_word(words, num_words, 1);
241 char *timeout_s = get_word(words, num_words, 2);
242 char *function = get_word(words, num_words, 3);
243 char *access = get_word(words, num_words, 4);
244 char *source = get_word(words, num_words, 5);
245 worker_add_job(wg, keyword, transaction, function, timeout_s, NULL, access, source);
246 }
247 else if(keyword && (strcmp(keyword, PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN) == 0)) {
248 char *transaction = get_word(words, num_words, 1);
249 char *timeout_s = get_word(words, num_words, 2);
250 char *function = get_word(words, num_words, 3);
251 char *access = get_word(words, num_words, 4);
252 char *source = get_word(words, num_words, 5);
253 char *content_type = get_word(words, num_words, 6);
254
255 wg->deferred.transaction = strdupz(transaction ? transaction : "");
256 wg->deferred.timeout_s = strdupz(timeout_s ? timeout_s : "");
257 wg->deferred.function = strdupz(function ? function : "");
258 wg->deferred.access = strdupz(access ? access : "");
259 wg->deferred.source = strdupz(source ? source : "");
260 wg->deferred.content_type = strdupz(content_type ? content_type : "");
261 wg->deferred.last_len = 0;
262 wg->deferred.enabled = true;
263 }
264 else if(keyword && strcmp(keyword, PLUGINSD_CALL_FUNCTION_CANCEL) == 0) {
265 char *transaction = get_word(words, num_words, 1);
266 const DICTIONARY_ITEM *acquired = dictionary_get_and_acquire_item(wg->worker_queue, transaction);
267 if(acquired) {
268 struct functions_evloop_worker_job *j = dictionary_acquired_item_value(acquired);
269 __atomic_store_n(&j->cancelled, true, __ATOMIC_RELAXED);
270 dictionary_acquired_item_release(wg->worker_queue, acquired);
271 dictionary_del(wg->worker_queue, transaction);
272 dictionary_garbage_collect(wg->worker_queue);
273 }
274 else
275 nd_log(NDLS_COLLECTORS, NDLP_NOTICE, "Received CANCEL for transaction '%s', but it not available here", transaction);
276 }
277 else if(keyword && strcmp(keyword, PLUGINSD_CALL_FUNCTION_PROGRESS) == 0) {
278 char *transaction = get_word(words, num_words, 1);
279 const DICTIONARY_ITEM *acquired = dictionary_get_and_acquire_item(wg->worker_queue, transaction);
280 if(acquired) {
281 struct functions_evloop_worker_job *j = dictionary_acquired_item_value(acquired);
282
283 functions_stop_monotonic_update_on_progress(&j->stop_monotonic_ut);
284
285 dictionary_acquired_item_release(wg->worker_queue, acquired);
286 }
287 else
288 nd_log(NDLS_COLLECTORS, NDLP_NOTICE, "Received PROGRESS for transaction '%s', but it not available here", transaction);
289 }
290 else if(keyword && strcmp(keyword, PLUGINSD_CALL_QUIT) == 0) {
291 __atomic_store_n(wg->plugin_should_exit, true, __ATOMIC_RELEASE);
292 return true;
293 }
294 else
295 nd_log(NDLS_COLLECTORS, NDLP_NOTICE, "Received unknown command: %s", keyword ? keyword : "(unset)");
296
297 buffer_flush(wg->buffer);
298
299 return false;
300 }
301
302 static void rrd_functions_worker_globals_reader_main(void *arg) {
303 struct functions_evloop_globals *wg = arg;
304
305 buffered_reader_init(&wg->reader);
306 wg->buffer = buffer_create(sizeof(wg->reader.read_buffer) + 2, NULL);
307
308 while(!__atomic_load_n(wg->plugin_should_exit, __ATOMIC_ACQUIRE)) {
309 if(unlikely(!buffered_reader_next_line(&wg->reader, wg->buffer))) {
310 buffered_reader_ret_t ret = buffered_reader_read_timeout(
311 &wg->reader,
312 fileno((FILE *)stdin),
313 2 * 60 * MSEC_PER_SEC,
314 false
315 );
316
317 if(unlikely(ret != BUFFERED_READER_READ_OK && ret != BUFFERED_READER_READ_POLL_TIMEOUT))
318 break;
319
320 continue;
321 }
322
323 if(rrd_function_worker_global_process_input(wg))
324 break;
325 }
326
327 int status = 0;
328 if(!__atomic_load_n(wg->plugin_should_exit, __ATOMIC_ACQUIRE)) {
329 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Read error on stdin");
330 status = 1;
331 }
332
333 buffer_free(wg->buffer);
334
335 if (wg->status)
336 __atomic_store_n(wg->status, status, __ATOMIC_RELEASE);
337 __atomic_store_n(wg->plugin_should_exit, true, __ATOMIC_RELEASE);
338 }
339
340 void worker_queue_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
341 struct functions_evloop_worker_job *j = value;
342 worker_job_cleanup(j);
343 }
344
345 struct functions_evloop_globals *functions_evloop_init(size_t worker_threads, const char *tag,
346 netdata_mutex_t *stdout_mutex, bool *plugin_should_exit,
347 int *status)
348 {
349 struct functions_evloop_globals *wg = callocz(1, sizeof(struct functions_evloop_globals));
350
351 wg->worker_queue = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
352 dictionary_register_delete_callback(wg->worker_queue, worker_queue_delete_cb, NULL);
353
354 wg->dyncfg.nodes = dyncfg_nodes_dictionary_create();
355
356 netdata_mutex_init(&wg->worker_mutex);
357 netdata_cond_init(&wg->worker_cond_var);
358
359 wg->plugin_should_exit = plugin_should_exit;
360 wg->stdout_mutex = stdout_mutex;
361 wg->workers = worker_threads;
362 wg->worker_threads = callocz(wg->workers, sizeof(ND_THREAD *));
363 wg->tag = tag;
364 wg->status = status;
365
366 char tag_buffer[NETDATA_THREAD_TAG_MAX + 1];
367 snprintfz(tag_buffer, NETDATA_THREAD_TAG_MAX, "%s_READER", wg->tag);
368 wg->reader_thread = nd_thread_create(tag_buffer, NETDATA_THREAD_OPTION_DONT_LOG,
369 rrd_functions_worker_globals_reader_main, wg);
370
371 for(size_t i = 0; i < wg->workers ; i++) {
372 snprintfz(tag_buffer, NETDATA_THREAD_TAG_MAX, "%s_WORK[%zu]", wg->tag, i+1);
373 wg->worker_threads[i] = nd_thread_create(tag_buffer, NETDATA_THREAD_OPTION_DONT_LOG,
374 rrd_functions_worker_globals_worker_main, wg);
375 }
376
377 functions_evloop_add_function(wg, "config", functions_evloop_config_cb, 120, wg);
378
379 return wg;
380 }
381
382 void functions_evloop_add_function(struct functions_evloop_globals *wg, const char *function, functions_evloop_worker_execute_t cb, time_t default_timeout, void *data) {
383 struct rrd_functions_expectation *we = callocz(1, sizeof(*we));
384 we->function = function;
385 we->function_length = strlen(we->function);
386 we->cb = cb;
387 we->cb_data = data;
388 we->default_timeout = default_timeout;
389 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(wg->expectations, we, prev, next);
390 }
391
392 void functions_evloop_cancel_threads(struct functions_evloop_globals *wg) {
393 nd_thread_signal_cancel(wg->reader_thread);
394
395 for(size_t i = 0; i < wg->workers ; i++)
396 nd_thread_signal_cancel(wg->worker_threads[i]);
397 }
398
399 // ----------------------------------------------------------------------------
400
401 static void functions_evloop_config_cb(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled,
402 BUFFER *payload, HTTP_ACCESS access, const char *source, void *data) {
403 struct functions_evloop_globals *wg = data;
404
405 CLEAN_BUFFER *result = buffer_create(1024, NULL);
406 int code = dyncfg_node_find_and_call(wg->dyncfg.nodes, transaction, function, stop_monotonic_ut,
407 cancelled, payload, access, source, result);
408
409 netdata_mutex_lock(wg->stdout_mutex);
410 pluginsd_function_result_begin_to_stdout(transaction, code, content_type_id2string(result->content_type), result->expires);
411 printf("%s", buffer_tostring(result));
412 pluginsd_function_result_end_to_stdout();
413 fflush(stdout);
414 netdata_mutex_unlock(wg->stdout_mutex);
415 }
416
417 void functions_evloop_dyncfg_add(struct functions_evloop_globals *wg, const char *id, const char *path,
418 DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type,
419 const char *source, DYNCFG_CMDS cmds,
420 HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
421 dyncfg_cb_t cb, void *data) {
422
423 if(!dyncfg_is_valid_id(id)) {
424 nd_log(NDLS_COLLECTORS, NDLP_ERR, "DYNCFG: id '%s' is invalid. Ignoring dynamic configuration for it.", id);
425 return;
426 }
427
428 struct dyncfg_node tmp = {
429 .cmds = cmds,
430 .type = type,
431 .cb = cb,
432 .data = data,
433 };
434 dictionary_set(wg->dyncfg.nodes, id, &tmp, sizeof(tmp));
435
436 CLEAN_BUFFER *c = buffer_create(100, NULL);
437 dyncfg_cmds2buffer(cmds, c);
438
439 netdata_mutex_lock(wg->stdout_mutex);
440
441 fprintf(stdout,
442 PLUGINSD_KEYWORD_CONFIG " '%s' " PLUGINSD_KEYWORD_CONFIG_ACTION_CREATE " '%s' '%s' '%s' '%s' '%s' '%s' "HTTP_ACCESS_FORMAT" "HTTP_ACCESS_FORMAT"\n",
443 id,
444 dyncfg_id2status(status),
445 dyncfg_id2type(type), path,
446 dyncfg_id2source_type(source_type),
447 source,
448 buffer_tostring(c),
449 (HTTP_ACCESS_FORMAT_CAST)view_access,
450 (HTTP_ACCESS_FORMAT_CAST)edit_access
451 );
452 fflush(stdout);
453
454 netdata_mutex_unlock(wg->stdout_mutex);
455 }
456
457 void functions_evloop_dyncfg_del(struct functions_evloop_globals *wg, const char *id) {
458 if(!dyncfg_is_valid_id(id)) {
459 nd_log(NDLS_COLLECTORS, NDLP_ERR, "DYNCFG: id '%s' is invalid. Ignoring dynamic configuration for it.", id);
460 return;
461 }
462
463 dictionary_del(wg->dyncfg.nodes, id);
464
465 netdata_mutex_lock(wg->stdout_mutex);
466
467 fprintf(stdout,
468 PLUGINSD_KEYWORD_CONFIG " %s " PLUGINSD_KEYWORD_CONFIG_ACTION_DELETE "\n",
469 id);
470 fflush(stdout);
471
472 netdata_mutex_unlock(wg->stdout_mutex);
473 }
474
475 void functions_evloop_dyncfg_status(struct functions_evloop_globals *wg, const char *id, DYNCFG_STATUS status) {
476 if(!dyncfg_is_valid_id(id)) {
477 nd_log(NDLS_COLLECTORS, NDLP_ERR, "DYNCFG: id '%s' is invalid. Ignoring dynamic configuration for it.", id);
478 return;
479 }
480
481 netdata_mutex_lock(wg->stdout_mutex);
482
483 fprintf(stdout,
484 PLUGINSD_KEYWORD_CONFIG " %s " PLUGINSD_KEYWORD_CONFIG_ACTION_STATUS " %s\n",
485 id, dyncfg_id2status(status));
486
487 fflush(stdout);
488
489 netdata_mutex_unlock(wg->stdout_mutex);
490 }