Browse Source

use gperf for the pluginsd/streaming parser hashtable (#15251)

* use gperf for the pluginsd parser

* simplify pluginsd_parser by removing void pointers to user

* pluginsd_split_words() with inlined pluginsd_space()

* quoted_string_splitter() now uses a map instead of a function for determining spaces

* add stress test for pluginsd parser

* optimized BITMAP256

* optimized rrdpush receiver reception

* optimized rrdpush sender compression

* renames and cleanup

* remove wrong negation

* unify handshake and disconnection reasons

* use parser_find_keyword

* register job names only for the current repertoire
Costa Tsaousis 1 year ago
parent
commit
0d61c11b5f

+ 0 - 2
CMakeLists.txt

@@ -491,8 +491,6 @@ set(LIBNETDATA_FILES
         libnetdata/string/utf8.h
         libnetdata/worker_utilization/worker_utilization.c
         libnetdata/worker_utilization/worker_utilization.h
-		libnetdata/parser/parser.h
-		libnetdata/parser/parser.c
         libnetdata/http/http_defs.h
         )
 

+ 1 - 2
Makefile.am

@@ -168,8 +168,6 @@ LIBNETDATA_FILES = \
     libnetdata/log/log.h \
     libnetdata/onewayalloc/onewayalloc.c \
     libnetdata/onewayalloc/onewayalloc.h \
-    libnetdata/parser/parser.c \
-    libnetdata/parser/parser.h \
     libnetdata/popen/popen.c \
     libnetdata/popen/popen.h \
     libnetdata/procfile/procfile.c \
@@ -425,6 +423,7 @@ PLUGINSD_PLUGIN_FILES = \
     collectors/plugins.d/plugins_d.h \
     collectors/plugins.d/pluginsd_parser.c \
     collectors/plugins.d/pluginsd_parser.h \
+    collectors/plugins.d/gperf-hashtable.h \
     $(NULL)
 
 RRD_PLUGIN_FILES = \

+ 2 - 2
collectors/apps.plugin/apps_plugin.c

@@ -4372,7 +4372,7 @@ static void apps_plugin_function_processes(const char *transaction, char *functi
     struct pid_stat *p;
 
     char *words[PLUGINSD_MAX_WORDS] = { NULL };
-    size_t num_words = pluginsd_split_words(function, words, PLUGINSD_MAX_WORDS);
+    size_t num_words = quoted_strings_splitter_pluginsd(function, words, PLUGINSD_MAX_WORDS);
 
     struct target *category = NULL, *user = NULL, *group = NULL;
     const char *process_name = NULL;
@@ -5219,7 +5219,7 @@ void *reader_main(void *arg __maybe_unused) {
     while(!apps_plugin_exit && (s = fgets(buffer, PLUGINSD_LINE_MAX, stdin))) {
 
         char *words[PLUGINSD_MAX_WORDS] = { NULL };
-        size_t num_words = pluginsd_split_words(buffer, words, PLUGINSD_MAX_WORDS);
+        size_t num_words = quoted_strings_splitter_pluginsd(buffer, words, PLUGINSD_MAX_WORDS);
 
         const char *keyword = get_word(words, num_words, 0);
 

+ 1 - 0
collectors/plugins.d/Makefile.am

@@ -7,5 +7,6 @@ SUBDIRS = \
     $(NULL)
 
 dist_noinst_DATA = \
+    gperf-config.txt \
     README.md \
     $(NULL)

+ 43 - 0
collectors/plugins.d/gperf-config.txt

@@ -0,0 +1,43 @@
+PARSER_KEYWORD;
+%%
+#
+# Plugins Only Keywords
+#
+FLUSH,                  pluginsd_flush,                             PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 1
+DISABLE,                pluginsd_disable,                           PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 2
+EXIT,                   pluginsd_exit,                              PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 3
+HOST,                   pluginsd_host,                              PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 4
+HOST_DEFINE,            pluginsd_host_define,                       PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 5
+HOST_DEFINE_END,        pluginsd_host_define_end,                   PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 6
+HOST_LABEL,             pluginsd_host_labels,                       PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 7
+#
+# Common keywords
+#
+BEGIN,                  pluginsd_begin,                             PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 8
+CHART,                  pluginsd_chart,                             PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 9
+CLABEL,                 pluginsd_clabel,                            PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 10
+CLABEL_COMMIT,          pluginsd_clabel_commit,                     PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 11
+DIMENSION,              pluginsd_dimension,                         PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 12
+END,                    pluginsd_end,                               PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 13
+FUNCTION,               pluginsd_function,                          PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 14
+FUNCTION_RESULT_BEGIN,  pluginsd_function_result_begin,             PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 15
+LABEL,                  pluginsd_label,                             PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 16
+OVERWRITE,              pluginsd_overwrite,                         PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 17
+SET,                    pluginsd_set,                               PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 18
+VARIABLE,               pluginsd_variable,                          PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 19
+#
+# Streaming only keywords
+#
+CLAIMED_ID,             streaming_claimed_id,                       PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 20
+BEGIN2,                 pluginsd_begin_v2,                          PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 21
+SET2,                   pluginsd_set_v2,                            PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 22
+END2,                   pluginsd_end_v2,                            PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 23
+#
+# Streaming Replication keywords
+#
+CHART_DEFINITION_END,   pluginsd_chart_definition_end,              PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 24
+RBEGIN,                 pluginsd_replay_begin,                      PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 25
+RDSTATE,                pluginsd_replay_rrddim_collection_state,    PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 26
+REND,                   pluginsd_replay_end,                        PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 27
+RSET,                   pluginsd_replay_set,                        PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 28
+RSSTATE,                pluginsd_replay_rrdset_collection_state,    PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 29

+ 164 - 0
collectors/plugins.d/gperf-hashtable.h

@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+/* ANSI-C code produced by gperf version 3.1 */
+/* Command-line: gperf --multiple-iterations=1000 --hash-function-name=gperf_keyword_hash_function --lookup-function-name=gperf_lookup_keyword --word-array-name=gperf_keywords --constants-prefix=GPERF_PARSER_ --struct-type --slot-name=keyword --global-table --null-strings --omit-struct-type --output-file=gperf-hashtable.h gperf-config.txt  */
+/* Computed positions: -k'1-2' */
+
+#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
+      && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
+      && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
+      && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
+      && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
+      && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
+      && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
+      && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
+      && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
+      && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
+      && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
+      && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
+      && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
+      && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
+      && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
+      && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
+      && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
+      && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
+      && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
+      && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
+      && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
+      && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
+      && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
+/* The character set is not based on ISO-646.  */
+#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gperf@gnu.org>."
+#endif
+
+
+#define GPERF_PARSER_TOTAL_KEYWORDS 29
+#define GPERF_PARSER_MIN_WORD_LENGTH 3
+#define GPERF_PARSER_MAX_WORD_LENGTH 21
+#define GPERF_PARSER_MIN_HASH_VALUE 4
+#define GPERF_PARSER_MAX_HASH_VALUE 36
+/* maximum key range = 33, duplicates = 0 */
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static unsigned int
+gperf_keyword_hash_function (register const char *str, register size_t len)
+{
+  static unsigned char asso_values[] =
+    {
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 15, 10,  1,  1,  9,
+       4, 37,  0, 20, 37, 37,  9, 37, 14,  0,
+      37, 37,  1,  0, 37,  7, 13, 37, 18, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+      37, 37, 37, 37, 37, 37
+    };
+  return len + asso_values[(unsigned char)str[1]] + asso_values[(unsigned char)str[0]];
+}
+
+static PARSER_KEYWORD gperf_keywords[] =
+  {
+    {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0},
+#line 9 "gperf-config.txt"
+    {"HOST",                   pluginsd_host,                              PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 4},
+#line 42 "gperf-config.txt"
+    {"RSET",                   pluginsd_replay_set,                        PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 28},
+#line 17 "gperf-config.txt"
+    {"CHART",                  pluginsd_chart,                             PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 9},
+    {(char*)0},
+#line 43 "gperf-config.txt"
+    {"RSSTATE",                pluginsd_replay_rrdset_collection_state,    PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 29},
+#line 40 "gperf-config.txt"
+    {"RDSTATE",                pluginsd_replay_rrddim_collection_state,    PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 26},
+#line 12 "gperf-config.txt"
+    {"HOST_LABEL",             pluginsd_host_labels,                       PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 7},
+#line 10 "gperf-config.txt"
+    {"HOST_DEFINE",            pluginsd_host_define,                       PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 5},
+#line 26 "gperf-config.txt"
+    {"SET",                    pluginsd_set,                               PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 18},
+#line 33 "gperf-config.txt"
+    {"SET2",                   pluginsd_set_v2,                            PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 22},
+#line 41 "gperf-config.txt"
+    {"REND",                   pluginsd_replay_end,                        PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 27},
+#line 11 "gperf-config.txt"
+    {"HOST_DEFINE_END",        pluginsd_host_define_end,                   PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 6},
+#line 18 "gperf-config.txt"
+    {"CLABEL",                 pluginsd_clabel,                            PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 10},
+#line 39 "gperf-config.txt"
+    {"RBEGIN",                 pluginsd_replay_begin,                      PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 25},
+#line 6 "gperf-config.txt"
+    {"FLUSH",                  pluginsd_flush,                             PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 1},
+#line 22 "gperf-config.txt"
+    {"FUNCTION",               pluginsd_function,                          PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 14},
+#line 31 "gperf-config.txt"
+    {"CLAIMED_ID",             streaming_claimed_id,                       PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 20},
+#line 38 "gperf-config.txt"
+    {"CHART_DEFINITION_END",   pluginsd_chart_definition_end,              PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 24},
+#line 25 "gperf-config.txt"
+    {"OVERWRITE",              pluginsd_overwrite,                         PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 17},
+#line 19 "gperf-config.txt"
+    {"CLABEL_COMMIT",          pluginsd_clabel_commit,                     PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 11},
+#line 16 "gperf-config.txt"
+    {"BEGIN",                  pluginsd_begin,                             PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 8},
+#line 32 "gperf-config.txt"
+    {"BEGIN2",                 pluginsd_begin_v2,                          PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 21},
+#line 21 "gperf-config.txt"
+    {"END",                    pluginsd_end,                               PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 13},
+#line 34 "gperf-config.txt"
+    {"END2",                   pluginsd_end_v2,                            PARSER_INIT_STREAMING,                       WORKER_PARSER_FIRST_JOB + 23},
+#line 7 "gperf-config.txt"
+    {"DISABLE",                pluginsd_disable,                           PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 2},
+#line 24 "gperf-config.txt"
+    {"LABEL",                  pluginsd_label,                             PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 16},
+#line 20 "gperf-config.txt"
+    {"DIMENSION",              pluginsd_dimension,                         PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 12},
+#line 8 "gperf-config.txt"
+    {"EXIT",                   pluginsd_exit,                              PARSER_INIT_PLUGINSD,                       WORKER_PARSER_FIRST_JOB + 3},
+#line 23 "gperf-config.txt"
+    {"FUNCTION_RESULT_BEGIN",  pluginsd_function_result_begin,             PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 15},
+    {(char*)0}, {(char*)0}, {(char*)0},
+#line 27 "gperf-config.txt"
+    {"VARIABLE",               pluginsd_variable,                          PARSER_INIT_PLUGINSD|PARSER_INIT_STREAMING, WORKER_PARSER_FIRST_JOB + 19}
+  };
+
+PARSER_KEYWORD *
+gperf_lookup_keyword (register const char *str, register size_t len)
+{
+  if (len <= GPERF_PARSER_MAX_WORD_LENGTH && len >= GPERF_PARSER_MIN_WORD_LENGTH)
+    {
+      register unsigned int key = gperf_keyword_hash_function (str, len);
+
+      if (key <= GPERF_PARSER_MAX_HASH_VALUE)
+        {
+          register const char *s = gperf_keywords[key].keyword;
+
+          if (s && *str == *s && !strcmp (str + 1, s + 1))
+            return &gperf_keywords[key];
+        }
+    }
+  return 0;
+}

+ 1 - 1
collectors/plugins.d/plugins_d.c

@@ -18,7 +18,7 @@ inline size_t pluginsd_initialize_plugin_directories()
     }
 
     // Parse it and store it to plugin directories
-    return quoted_strings_splitter(plugins_dir_list, plugin_directories, PLUGINSD_MAX_DIRECTORIES, config_isspace);
+    return quoted_strings_splitter_config(plugins_dir_list, plugin_directories, PLUGINSD_MAX_DIRECTORIES);
 }
 
 static inline void plugin_set_disabled(struct plugind *cd) {

File diff suppressed because it is too large
+ 213 - 260
collectors/plugins.d/pluginsd_parser.c


+ 160 - 5
collectors/plugins.d/pluginsd_parser.h

@@ -5,13 +5,39 @@
 
 #include "daemon/common.h"
 
+#define WORKER_PARSER_FIRST_JOB 3
+
+// this has to be in-sync with the same at receiver.c
+#define WORKER_RECEIVER_JOB_REPLICATION_COMPLETION (WORKER_PARSER_FIRST_JOB - 3)
+
+// PARSER return codes
+typedef enum __attribute__ ((__packed__)) parser_rc {
+    PARSER_RC_OK,       // Callback was successful, go on
+    PARSER_RC_STOP,     // Callback says STOP
+    PARSER_RC_ERROR     // Callback failed (abort rest of callbacks)
+} PARSER_RC;
+
+typedef enum __attribute__ ((__packed__)) parser_input_type {
+    PARSER_INPUT_SPLIT          = (1 << 1),
+    PARSER_DEFER_UNTIL_KEYWORD  = (1 << 2),
+} PARSER_INPUT_TYPE;
+
 typedef enum __attribute__ ((__packed__)) {
     PARSER_INIT_PLUGINSD        = (1 << 1),
     PARSER_INIT_STREAMING       = (1 << 2),
-} PLUGINSD_KEYWORDS;
+} PARSER_REPERTOIRE;
+
+struct parser;
+typedef PARSER_RC (*keyword_function)(char **words, size_t num_words, struct parser *parser);
+
+typedef struct parser_keyword {
+    char *keyword;
+    keyword_function func;
+    PARSER_REPERTOIRE repertoire;
+    size_t worker_job_id;
+} PARSER_KEYWORD;
 
 typedef struct parser_user_object {
-    PARSER  *parser;
     RRDSET *st;
     RRDHOST *host;
     void    *opaque;
@@ -54,9 +80,138 @@ typedef struct parser_user_object {
     } v2;
 } PARSER_USER_OBJECT;
 
-PARSER_RC pluginsd_function(char **words, size_t num_words, void *user);
-PARSER_RC pluginsd_function_result_begin(char **words, size_t num_words, void *user);
+typedef struct parser {
+    uint8_t version;                // Parser version
+    PARSER_REPERTOIRE repertoire;
+    uint32_t flags;
+    int fd;                         // Socket
+    size_t line;
+    FILE *fp_input;                 // Input source e.g. stream
+    FILE *fp_output;                // Stream to send commands to plugin
+
+#ifdef ENABLE_HTTPS
+    NETDATA_SSL *ssl_output;
+#endif
+
+    PARSER_USER_OBJECT user;        // User defined structure to hold extra state between calls
+
+    struct {
+        const char *end_keyword;
+        BUFFER *response;
+        void (*action)(struct parser *parser, void *action_data);
+        void *action_data;
+    } defer;
+
+    struct {
+        DICTIONARY *functions;
+        usec_t smaller_timeout;
+    } inflight;
+
+    struct {
+        SPINLOCK spinlock;
+    } writer;
+
+} PARSER;
+
+static inline int find_first_keyword(const char *src, char *dst, int dst_size, bool *isspace_map) {
+    const char *s = src, *keyword_start;
+
+    while (unlikely(isspace_map[(uint8_t)*s])) s++;
+    keyword_start = s;
+
+    while (likely(*s && !isspace_map[(uint8_t)*s]) && dst_size > 1) {
+        *dst++ = *s++;
+        dst_size--;
+    }
+    *dst = '\0';
+    return dst_size == 0 ? 0 : (int) (s - keyword_start);
+}
+
+PARSER_KEYWORD *gperf_lookup_keyword(register const char *str, register size_t len);
+
+static inline PARSER_KEYWORD *parser_find_keyword(PARSER *parser, const char *command) {
+    PARSER_KEYWORD *t = gperf_lookup_keyword(command, strlen(command));
+    if(t && (t->repertoire & parser->repertoire))
+        return t;
+
+    return NULL;
+}
+
+static 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, isspace_map_pluginsd);
+
+        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 = quoted_strings_splitter_pluginsd(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);
+        worker_is_idle();
+    }
+    else
+        rc = PARSER_RC_ERROR;
+
+    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);
+        }
+
+        error("PLUGINSD: parser_action('%s') failed on line %zu: { %s } (quotes added to show parsing)",
+              command, parser->line, buffer_tostring(wb));
+
+        buffer_free(wb);
+    }
+
+    return (rc == PARSER_RC_ERROR || rc == PARSER_RC_STOP);
+}
+
+PARSER *parser_init(struct parser_user_object *user, FILE *fp_input, FILE *fp_output, int fd, PARSER_INPUT_TYPE flags, void *ssl);
+void parser_init_repertoire(PARSER *parser, PARSER_REPERTOIRE repertoire);
+void parser_destroy(PARSER *working_parser);
+void pluginsd_cleanup_v2(PARSER *parser);
 void inflight_functions_init(PARSER *parser);
-void pluginsd_keywords_init(PARSER *parser, PLUGINSD_KEYWORDS types);
+void pluginsd_keywords_init(PARSER *parser, PARSER_REPERTOIRE repertoire);
 
 #endif //NETDATA_PLUGINSD_PARSER_H

+ 1 - 1
collectors/statsd.plugin/statsd.c

@@ -1456,7 +1456,7 @@ static int statsd_readfile(const char *filename, STATSD_APP *app, STATSD_APP_CHA
             else if (!strcmp(name, "dimension")) {
                 // metric [name [type [multiplier [divisor]]]]
                 char *words[10] = { NULL };
-                size_t num_words = pluginsd_split_words(value, words, 10);
+                size_t num_words = quoted_strings_splitter_pluginsd(value, words, 10);
 
                 int pattern = 0;
                 size_t i = 0;

Some files were not shown because too many files changed in this diff