From a836a244a3d2bdd4da1ee2641e3e957850668cea Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 8 May 2023 18:27:04 +0200 Subject: Adding upstream version 1.39.0. Signed-off-by: Daniel Baumann --- libnetdata/parser/parser.c | 225 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 libnetdata/parser/parser.c (limited to 'libnetdata/parser/parser.c') diff --git a/libnetdata/parser/parser.c b/libnetdata/parser/parser.c new file mode 100644 index 000000000..c3eebcd16 --- /dev/null +++ b/libnetdata/parser/parser.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "parser.h" +#include "collectors/plugins.d/pluginsd_parser.h" + +static inline int find_first_keyword(const char *src, char *dst, int dst_size, int (*custom_isspace)(char)) { + const char *s = src, *keyword_start; + + while (unlikely(custom_isspace(*s))) s++; + keyword_start = s; + + while (likely(*s && !custom_isspace(*s)) && dst_size > 1) { + *dst++ = *s++; + dst_size--; + } + *dst = '\0'; + return dst_size == 0 ? 0 : (int) (s - keyword_start); +} + +/* + * Initialize a parser + * user : as defined by the user, will be shared across calls + * input : main input stream (auto detect stream -- file, socket, pipe) + * buffer : This is the buffer to be used (if null a buffer of size will be allocated) + * size : buffer size either passed or will be allocated + * If the buffer is auto allocated, it will auto freed when the parser is destroyed + * + * + */ + +PARSER *parser_init(void *user, FILE *fp_input, FILE *fp_output, int fd, + PARSER_INPUT_TYPE flags, void *ssl __maybe_unused) +{ + PARSER *parser; + + parser = callocz(1, sizeof(*parser)); + parser->user = user; + parser->fd = fd; + parser->fp_input = fp_input; + parser->fp_output = fp_output; +#ifdef ENABLE_HTTPS + parser->ssl_output = ssl; +#endif + parser->flags = flags; + parser->worker_job_next_id = WORKER_PARSER_FIRST_JOB; + + return parser; +} + + +static inline PARSER_KEYWORD *parser_find_keyword(PARSER *parser, const char *command) { + uint32_t hash = parser_hash_function(command); + uint32_t slot = hash % PARSER_KEYWORDS_HASHTABLE_SIZE; + PARSER_KEYWORD *t = parser->keywords.hashtable[slot]; + + if(likely(t && strcmp(t->keyword, command) == 0)) + return t; + + return NULL; +} + +/* + * Add a keyword and the corresponding function that will be called + * Multiple functions may be added + * Input : keyword + * : callback function + * : flags + * Output: > 0 registered function number + * : 0 Error + */ + +void parser_add_keyword(PARSER *parser, char *keyword, keyword_function func) { + if(unlikely(!parser || !keyword || !*keyword || !func)) + fatal("PARSER: invalid parameters"); + + PARSER_KEYWORD *t = callocz(1, sizeof(*t)); + t->worker_job_id = parser->worker_job_next_id++; + t->keyword = strdupz(keyword); + t->func = func; + + uint32_t hash = parser_hash_function(keyword); + uint32_t slot = hash % PARSER_KEYWORDS_HASHTABLE_SIZE; + + if(unlikely(parser->keywords.hashtable[slot])) + fatal("PARSER: hashtable collision between keyword '%s' and '%s' on slot %u. " + "Change the hashtable size and / or the hashing function. " + "Run the unit test to find the optimal values.", + parser->keywords.hashtable[slot]->keyword, + t->keyword, + slot + ); + + parser->keywords.hashtable[slot] = t; + + worker_register_job_name(t->worker_job_id, t->keyword); +} + +/* + * Cleanup a previously allocated parser + */ + +void parser_destroy(PARSER *parser) +{ + if (unlikely(!parser)) + return; + + dictionary_destroy(parser->inflight.functions); + + // Remove keywords + for(size_t i = 0 ; i < PARSER_KEYWORDS_HASHTABLE_SIZE; i++) { + PARSER_KEYWORD *t = parser->keywords.hashtable[i]; + if (t) { + freez(t->keyword); + freez(t); + } + } + + freez(parser); +} + + +/* + * Fetch the next line to process + * + */ + +int parser_next(PARSER *parser, char *buffer, size_t buffer_size) +{ + char *tmp = fgets(buffer, (int)buffer_size, (FILE *)parser->fp_input); + + if (unlikely(!tmp)) { + if (feof((FILE *)parser->fp_input)) + error("PARSER: read failed: end of file"); + + else if (ferror((FILE *)parser->fp_input)) + error("PARSER: read failed: input error"); + + else + error("PARSER: read failed: unknown error"); + + return 1; + } + + return 0; +} + + +/* +* Takes an initialized parser object that has an unprocessed entry (by calling parser_next) +* and if it contains a valid keyword, it will execute all the callbacks +* +*/ + +inline int parser_action(PARSER *parser, char *input) +{ + parser->line++; + + if(unlikely(parser->flags & PARSER_DEFER_UNTIL_KEYWORD)) { + char command[PLUGINSD_LINE_MAX + 1]; + bool has_keyword = find_first_keyword(input, command, PLUGINSD_LINE_MAX, pluginsd_space); + + if(!has_keyword || strcmp(command, parser->defer.end_keyword) != 0) { + if(parser->defer.response) { + buffer_strcat(parser->defer.response, input); + if(buffer_strlen(parser->defer.response) > 10 * 1024 * 1024) { + // more than 10MB of data + // a bad plugin that did not send the end_keyword + internal_error(true, "PLUGINSD: deferred response is too big (%zu bytes). Stopping this plugin.", buffer_strlen(parser->defer.response)); + return 1; + } + } + return 0; + } + else { + // call the action + parser->defer.action(parser, parser->defer.action_data); + + // empty everything + parser->defer.action = NULL; + parser->defer.action_data = NULL; + parser->defer.end_keyword = NULL; + parser->defer.response = NULL; + parser->flags &= ~PARSER_DEFER_UNTIL_KEYWORD; + } + return 0; + } + + char *words[PLUGINSD_MAX_WORDS]; + size_t num_words = pluginsd_split_words(input, words, PLUGINSD_MAX_WORDS); + const char *command = get_word(words, num_words, 0); + + if(unlikely(!command)) + return 0; + + PARSER_RC rc; + PARSER_KEYWORD *t = parser_find_keyword(parser, command); + if(likely(t)) { + worker_is_busy(t->worker_job_id); + rc = (*t->func)(words, num_words, parser->user); + worker_is_idle(); + } + else + rc = PARSER_RC_ERROR; + +#ifdef NETDATA_INTERNAL_CHECKS + if(rc == PARSER_RC_ERROR) { + BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX, NULL); + for(size_t i = 0; i < num_words ;i++) { + if(i) buffer_fast_strcat(wb, " ", 1); + + buffer_fast_strcat(wb, "\"", 1); + const char *s = get_word(words, num_words, i); + buffer_strcat(wb, s?s:""); + buffer_fast_strcat(wb, "\"", 1); + } + + internal_error(true, "PLUGINSD: parser_action('%s') failed on line %zu: { %s } (quotes added to show parsing)", + command, parser->line, buffer_tostring(wb)); + + buffer_free(wb); + } +#endif + + return (rc == PARSER_RC_ERROR || rc == PARSER_RC_STOP); +} -- cgit v1.2.3