@cryptotaxi247 / netdata-1 / commits / 60a73e90d

Easily disable alarms, by persisting the silencers configuration (#6274)

* Alarms begin! * Alarms web interface comments! * Alarms web interface comments 2! * Alarms bringing Christopher work! * Alarms bringing Christopher work! * Alarms commenting code that will be rewritten! * Alarms json-c begin! * Alarms json-c end! * Alarms missed script! * Alarms fix json-c parser and change script to test LIST! * Alarms fix test script! * Alarms documentation! * Alarms script step 1! * Alarms fix script! * Alarms fix testing script and code! * Alarms missing arguments to pkg_check_modules * SSL_backend indentation! * Alarms, description in Makefile * Alarms missing extern! * Alarms compilation! * Alarms libnetdata/health! * Alarms fill library! * Alarms fill CMakeList! * Alarm fix version! * Alarm remove readme! * Alarm fix readme version!

thiagoftsm committed Jun 27, 2019 at 08:16 UTC 60a73e90de2aa1c2eaae2ebbc45dd1fb96034df2
40 files changed +1637 -125
CMakeLists.txt
+16 -1
@@ -130,6 +130,14 @@ set(NETDATA_COMMON_CFLAGS ${NETDATA_COMMON_CFLAGS} ${OPENSSL_CFLAGS_OTHER})
130 set(NETDATA_COMMON_LIBRARIES ${NETDATA_COMMON_LIBRARIES} ${OPENSSL_LIBRARIES})
131 set(NETDATA_COMMON_INCLUDE_DIRS ${NETDATA_COMMON_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS})
132
133 +# -----------------------------------------------------------------------------
134 +# JSON-C used to health
135 +
136 +pkg_check_modules(JSON REQUIRED json-c)
137 +set(NETDATA_COMMON_CFLAGS ${NETDATA_COMMON_CFLAGS} ${JSONC_CFLAGS_OTHER})
138 +set(NETDATA_COMMON_LIBRARIES ${NETDATA_COMMON_LIBRARIES} ${JSON_LIBRARIES})
139 +set(NETDATA_COMMON_INCLUDE_DIRS ${NETDATA_COMMON_INCLUDE_DIRS} ${JSON_INCLUDE_DIRS})
140 +
141 # -----------------------------------------------------------------------------
142 # Detect libcap
143
@@ -305,8 +313,15 @@ set(LIBNETDATA_FILES
313 libnetdata/threads/threads.h
314 libnetdata/url/url.c
315 libnetdata/url/url.h
316 + libnetdata/health/health.c
317 + libnetdata/health/health.h
318 libnetdata/socket/security.c
309 - libnetdata/socket/security.h)
319 + libnetdata/socket/security.h
320 + libnetdata/json/json.c
321 + libnetdata/json/json.h
322 + libnetdata/json/jsmn.c
323 + libnetdata/json/jsmn.h
324 + )
325
326 add_library(libnetdata OBJECT ${LIBNETDATA_FILES})
327
Makefile.am
+9 -3
@@ -159,8 +159,14 @@ LIBNETDATA_FILES = \
159 libnetdata/storage_number/storage_number.h \
160 libnetdata/threads/threads.c \
161 libnetdata/threads/threads.h \
162 - libnetdata/url/url.c \
163 - libnetdata/url/url.h \
162 + libnetdata/url/url.c \
163 + libnetdata/url/url.h \
164 + libnetdata/json/jsmn.h \
165 + libnetdata/json/jsmn.c \
166 + libnetdata/json/json.h \
167 + libnetdata/json/json.c \
168 + libnetdata/health/health.c \
169 + libnetdata/health/health.h \
170 $(NULL)
171
172 APPS_PLUGIN_FILES = \
@@ -506,12 +512,12 @@ endif
512 NETDATA_COMMON_LIBS = \
513 $(OPTIONAL_MATH_LIBS) \
514 $(OPTIONAL_ZLIB_LIBS) \
509 - $(OPTIONAL_SSL_LIBS) \
515 $(OPTIONAL_UUID_LIBS) \
516 $(OPTIONAL_UV_LIBS) \
517 $(OPTIONAL_LZ4_LIBS) \
518 $(OPTIONAL_JUDY_LIBS) \
519 $(OPTIONAL_SSL_LIBS) \
520 + $(OPTIONAL_JSONC_LIBS) \
521 $(NULL)
522
523 sbin_PROGRAMS += netdata
configure.ac
+39
@@ -142,6 +142,12 @@ AC_ARG_ENABLE(
142 ,
143 [enable_https="detect"]
144 )
145 +AC_ARG_ENABLE(
146 + [jsonc],
147 + [AS_HELP_STRING([--enable-jsonc], [Enable JSON-C support @<:@default autodetect@:>@])],
148 + ,
149 + [enable_jsonc="detect"]
150 +)
151 AC_ARG_ENABLE(
152 [dbengine],
153 [AS_HELP_STRING([--disable-dbengine], [disable netdata dbengine @<:@default autodetect@:>@])],
@@ -347,6 +353,20 @@ AC_CHECK_LIB(
353 OPTIONAL_SSL_CFLAGS="${SSL_CFLAGS}"
354 OPTIONAL_SSL_LIBS="${SSL_LIBS}"
355
356 +# -----------------------------------------------------------------------------
357 +# JSON-C library
358 +
359 +PKG_CHECK_MODULES([JSON],[json-c],AC_CHECK_LIB(
360 + [json-c],
361 + [json_object_get_type],
362 + [JSONC_LIBS="-ljson-c"]),AC_CHECK_LIB(
363 + [json],
364 + [json_object_get_type],
365 + [JSONC_LIBS="-ljson"])
366 + )
367 +
368 +OPTIONAL_JSONC_LIBS="${JSONC_LIBS}"
369 +
370 # -----------------------------------------------------------------------------
371 # DB engine and HTTPS
372 test "${enable_dbengine}" = "yes" -a -z "${UV_LIBS}" && \
@@ -381,6 +401,22 @@ fi
401 AC_MSG_RESULT([${enable_https}])
402 AM_CONDITIONAL([ENABLE_HTTPS], [test "${enable_https}" = "yes"])
403
404 +# -----------------------------------------------------------------------------
405 +# JSON-C
406 +test "${enable_jsonc}" = "yes" -a -z "${JSONC_LIBS}" && \
407 + AC_MSG_ERROR([JSON-C required but not found. Try installing 'libjson-c-dev' or 'json-c'.])
408 +
409 +AC_MSG_CHECKING([if json-c should be used])
410 +if test "${enable_jsonc}" != "no" -a "${JSONC_LIBS}"; then
411 + enable_jsonc="yes"
412 + AC_DEFINE([ENABLE_JSONC], [1], [netdata json-c usability])
413 +else
414 + enable_jsonc="no"
415 +fi
416 +AC_MSG_RESULT([${enable_jsonc}])
417 +AM_CONDITIONAL([ENABLE_JSONC], [test "${enable_jsonc}" = "yes"])
418 +
419 +
420 # -----------------------------------------------------------------------------
421 # compiler options
422
@@ -961,6 +997,7 @@ AC_SUBST([OPTIONAL_UV_LIBS])
997 AC_SUBST([OPTIONAL_LZ4_LIBS])
998 AC_SUBST([OPTIONAL_JUDY_LIBS])
999 AC_SUBST([OPTIONAL_SSL_LIBS])
1000 +AC_SUBST([OPTIONAL_JSONC_LIBS])
1001 AC_SUBST([OPTIONAL_NFACCT_CFLAGS])
1002 AC_SUBST([OPTIONAL_NFACCT_LIBS])
1003 AC_SUBST([OPTIONAL_ZLIB_CFLAGS])
@@ -1027,6 +1064,8 @@ AC_CONFIG_FILES([
1064 libnetdata/config/Makefile
1065 libnetdata/dictionary/Makefile
1066 libnetdata/eval/Makefile
1067 + libnetdata/json/Makefile
1068 + libnetdata/health/Makefile
1069 libnetdata/locks/Makefile
1070 libnetdata/log/Makefile
1071 libnetdata/popen/Makefile
health/health.c
+96 -5
@@ -1,5 +1,6 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 +#include <libnetdata/json/json.h>
4 #include "health.h"
5
6 struct health_cmdapi_thread_status {
@@ -13,18 +14,79 @@ unsigned int default_health_enabled = 1;
14 // ----------------------------------------------------------------------------
15 // health initialization
16
17 +/**
18 + * User Config directory
19 + *
20 + * Get the config directory for health and return it.
21 + *
22 + * @return a pointer to the user config directory
23 + */
24 inline char *health_user_config_dir(void) {
25 char buffer[FILENAME_MAX + 1];
26 snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_user_config_dir);
27 return config_get(CONFIG_SECTION_HEALTH, "health configuration directory", buffer);
28 }
29
30 +/**
31 + * Stock Config Directory
32 + *
33 + * Get the Stock config directory and return it.
34 + *
35 + * @return a pointer to the stock config directory.
36 + */
37 inline char *health_stock_config_dir(void) {
38 char buffer[FILENAME_MAX + 1];
39 snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_stock_config_dir);
40 return config_get(CONFIG_SECTION_HEALTH, "stock health configuration directory", buffer);
41 }
42
43 +
44 +/**
45 + * Silencers init
46 + *
47 + * Function used to initialize the silencer structure.
48 + */
49 +void health_silencers_init(void) {
50 + silencers = mallocz(sizeof(SILENCERS));
51 + silencers->all_alarms=0;
52 + silencers->stype=STYPE_NONE;
53 + silencers->silencers=NULL;
54 +
55 + char filename[FILENAME_MAX + 1];
56 + snprintfz(filename, FILENAME_MAX, "%s/health.silencers.json", netdata_configured_varlib_dir);
57 + silencers_filename = config_get(CONFIG_SECTION_HEALTH, "silencers file", filename);
58 +
59 + struct stat statbuf;
60 + if (!stat(silencers_filename,&statbuf)) {
61 + off_t length = statbuf.st_size;
62 + if (length && length < HEALTH_SILENCERS_MAX_FILE_LEN) {
63 + FILE *fd = fopen(silencers_filename, "r");
64 + if (fd) {
65 + char *str = mallocz((length+1)* sizeof(char));
66 + if(str) {
67 + fread(str, sizeof(char), length, fd);
68 + str[length] = 0x00;
69 + json_parse(str, NULL, health_silencers_json_read_callback);
70 + freez(str);
71 + info("Parsed health silencers file %s", silencers_filename);
72 + }
73 + fclose(fd);
74 + } else {
75 + error("Cannot open the file %s",silencers_filename);
76 + }
77 + } else {
78 + error("Health silencers file %s has the size %ld that is out of range[ 1 , %d ]. Aborting read.", silencers_filename, length, HEALTH_SILENCERS_MAX_FILE_LEN);
79 + }
80 + } else {
81 + error("Cannot open the file %s",silencers_filename);
82 + }
83 +}
84 +
85 +/**
86 + * Health Init
87 + *
88 + * Initialize the health thread.
89 + */
90 void health_init(void) {
91 debug(D_HEALTH, "Health configuration initializing");
92
@@ -32,11 +94,20 @@ void health_init(void) {
94 debug(D_HEALTH, "Health is disabled.");
95 return;
96 }
97 +
98 + health_silencers_init();
99 }
100
101 // ----------------------------------------------------------------------------
102 // re-load health configuration
103
104 +/**
105 + * Reload host
106 + *
107 + * Reload configuration for a specific host.
108 + *
109 + * @param host the structure of the host that the function will reload the configuration.
110 + */
111 void health_reload_host(RRDHOST *host) {
112 if(unlikely(!host->health_enabled))
113 return;
@@ -84,6 +155,11 @@ void health_reload_host(RRDHOST *host) {
155 rrdhost_unlock(host);
156 }
157
158 +/**
159 + * Reload
160 + *
161 + * Reload the host configuration for all hosts.
162 + */
163 void health_reload(void) {
164
165 rrd_rdlock();
@@ -425,6 +501,16 @@ SILENCE_TYPE check_silenced(RRDCALC *rc, char* host, SILENCERS *silencers) {
501 return STYPE_NONE;
502 }
503
504 +/**
505 + * Update Disabled Silenced
506 + *
507 + * Update the variable rrdcalc_flags of the structure RRDCALC according with the values of the host structure
508 + *
509 + * @param host structure that contains information about the host monitored.
510 + * @param rc structure with information about the alarm
511 + *
512 + * @return It returns 1 case rrdcalc_flags is DISABLED or 0 otherwise
513 + */
514 int update_disabled_silenced(RRDHOST *host, RRDCALC *rc) {
515 uint32_t rrdcalc_flags_old = rc->rrdcalc_flags;
516 // Clear the flags
@@ -454,6 +540,15 @@ int update_disabled_silenced(RRDHOST *host, RRDCALC *rc) {
540 return 0;
541 }
542
543 +/**
544 + * Health Main
545 + *
546 + * The main thread of the health system. In this function all the alarms will be processed.
547 + *
548 + * @param ptr is a pointer to the netdata_static_thread structure.
549 + *
550 + * @return It always returns NULL
551 + */
552 void *health_main(void *ptr) {
553 netdata_thread_cleanup_push(health_main_cleanup, ptr);
554
@@ -465,11 +560,6 @@ void *health_main(void *ptr) {
560
561 unsigned int loop = 0;
562
468 - silencers = mallocz(sizeof(SILENCERS));
469 - silencers->all_alarms=0;
470 - silencers->stype=STYPE_NONE;
471 - silencers->silencers=NULL;
472 -
563 while(!netdata_exit) {
564 loop++;
565 debug(D_HEALTH, "Health monitoring iteration no %u started", loop);
@@ -523,6 +613,7 @@ void *health_main(void *ptr) {
613 // the first loop is to lookup values from the db
614 for (rc = host->alarms; rc; rc = rc->next) {
615
616 + //case it is disabled I will get the next rc
617 if (update_disabled_silenced(host, rc))
618 continue;
619
health/health.h
+4 -40
@@ -35,16 +35,9 @@ extern unsigned int default_health_enabled;
35 #define HEALTH_LISTEN_BACKLOG 4096
36 #endif
37
38 -#define HEALTH_ALARM_KEY "alarm"
39 -#define HEALTH_TEMPLATE_KEY "template"
38 +
39 #define HEALTH_ON_KEY "on"
41 -#define HEALTH_CONTEXT_KEY "context"
42 -#define HEALTH_CHART_KEY "chart"
43 -#define HEALTH_HOST_KEY "hosts"
44 -#define HEALTH_OS_KEY "os"
45 -#define HEALTH_FAMILIES_KEY "families"
46 -#define HEALTH_LOOKUP_KEY "lookup"
47 -#define HEALTH_CALC_KEY "calc"
40 +
41 #define HEALTH_EVERY_KEY "every"
42 #define HEALTH_GREEN_KEY "green"
43 #define HEALTH_RED_KEY "red"
@@ -57,38 +50,9 @@ extern unsigned int default_health_enabled;
50 #define HEALTH_DELAY_KEY "delay"
51 #define HEALTH_OPTIONS_KEY "options"
52
60 -typedef struct silencer {
61 - char *alarms;
62 - SIMPLE_PATTERN *alarms_pattern;
63 -
64 - char *hosts;
65 - SIMPLE_PATTERN *hosts_pattern;
66 -
67 - char *contexts;
68 - SIMPLE_PATTERN *contexts_pattern;
69 -
70 - char *charts;
71 - SIMPLE_PATTERN *charts_pattern;
72 -
73 - char *families;
74 - SIMPLE_PATTERN *families_pattern;
75 -
76 - struct silencer *next;
77 -} SILENCER;
78 -
79 -typedef enum silence_type {
80 - STYPE_NONE,
81 - STYPE_DISABLE_ALARMS,
82 - STYPE_SILENCE_NOTIFICATIONS
83 -} SILENCE_TYPE;
84 -
85 -typedef struct silencers {
86 - int all_alarms;
87 - SILENCE_TYPE stype;
88 - SILENCER *silencers;
89 -} SILENCERS;
53 +#define HEALTH_SILENCERS_MAX_FILE_LEN 10000
54
91 -SILENCERS *silencers;
55 +char *silencers_filename;
56
57 extern void health_init(void);
58 extern void *health_main(void *ptr);
health/health_config.c
+1 -1
@@ -481,7 +481,7 @@ static int health_readfile(const char *filename, void *data) {
481 if(append < HEALTH_CONF_MAX_LINE)
482 continue;
483 else {
484 - error("Health configuration has too long muli-line at line %zu of file '%s'.", line, filename);
484 + error("Health configuration has too long multi-line at line %zu of file '%s'.", line, filename);
485 }
486 }
487 append = 0;
libnetdata/Makefile.am
+2
@@ -21,6 +21,8 @@ SUBDIRS = \
21 storage_number \
22 threads \
23 url \
24 + json \
25 + health \
26 $(NULL)
27
28 dist_noinst_DATA = \
libnetdata/health/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 +
7 +dist_noinst_DATA = \
8 + $(NULL)
libnetdata/health/health.c new
+154
@@ -0,0 +1,154 @@
1 +#include "health.h"
2 +
3 +/**
4 + * Create Silencer
5 + *
6 + * Allocate a new silencer to Netdata.
7 + *
8 + * @return It returns the address off the silencer on success and NULL otherwise
9 + */
10 +SILENCER *create_silencer(void) {
11 + SILENCER *t = callocz(1, sizeof(SILENCER));
12 + debug(D_HEALTH, "HEALTH command API: Created empty silencer");
13 +
14 + return t;
15 +}
16 +
17 +/**
18 + * Health Silencers add
19 + *
20 + * Add more one silencer to the list of silenecers.
21 + *
22 + * @param silencer
23 + */
24 +void health_silencers_add(SILENCER *silencer) {
25 + // Add the created instance to the linked list in silencers
26 + silencer->next = silencers->silencers;
27 + silencers->silencers = silencer;
28 + debug(D_HEALTH, "HEALTH command API: Added silencer %s:%s:%s:%s:%s", silencer->alarms,
29 + silencer->charts, silencer->contexts, silencer->hosts, silencer->families
30 + );
31 +}
32 +
33 +/**
34 + * Silencers Add Parameter
35 + *
36 + * Create a new silencer and adjust the variables
37 + *
38 + * @param silencer a pointer to the silencer that will be adjusted
39 + * @param key the key value sent by client
40 + * @param value the value sent to the key
41 + *
42 + * @return It returns the silencer configured on success and NULL otherwise
43 + */
44 +SILENCER *health_silencers_addparam(SILENCER *silencer, char *key, char *value) {
45 + static uint32_t
46 + hash_alarm = 0,
47 + hash_template = 0,
48 + hash_chart = 0,
49 + hash_context = 0,
50 + hash_host = 0,
51 + hash_families = 0;
52 +
53 + if (unlikely(!hash_alarm)) {
54 + hash_alarm = simple_uhash(HEALTH_ALARM_KEY);
55 + hash_template = simple_uhash(HEALTH_TEMPLATE_KEY);
56 + hash_chart = simple_uhash(HEALTH_CHART_KEY);
57 + hash_context = simple_uhash(HEALTH_CONTEXT_KEY);
58 + hash_host = simple_uhash(HEALTH_HOST_KEY);
59 + hash_families = simple_uhash(HEALTH_FAMILIES_KEY);
60 + }
61 +
62 + uint32_t hash = simple_uhash(key);
63 + if (unlikely(silencer == NULL)) {
64 + if (
65 + (hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) ||
66 + (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY)) ||
67 + (hash == hash_chart && !strcasecmp(key, HEALTH_CHART_KEY)) ||
68 + (hash == hash_context && !strcasecmp(key, HEALTH_CONTEXT_KEY)) ||
69 + (hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) ||
70 + (hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY))
71 + ) {
72 + silencer = create_silencer();
73 + if(!silencer) {
74 + error("Cannot add a new silencer to Netdata");
75 + return NULL;
76 + }
77 + }
78 + }
79 +
80 + if (hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) {
81 + silencer->alarms = strdupz(value);
82 + silencer->alarms_pattern = simple_pattern_create(silencer->alarms, NULL, SIMPLE_PATTERN_EXACT);
83 + } else if (hash == hash_chart && !strcasecmp(key, HEALTH_CHART_KEY)) {
84 + silencer->charts = strdupz(value);
85 + silencer->charts_pattern = simple_pattern_create(silencer->charts, NULL, SIMPLE_PATTERN_EXACT);
86 + } else if (hash == hash_context && !strcasecmp(key, HEALTH_CONTEXT_KEY)) {
87 + silencer->contexts = strdupz(value);
88 + silencer->contexts_pattern = simple_pattern_create(silencer->contexts, NULL, SIMPLE_PATTERN_EXACT);
89 + } else if (hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) {
90 + silencer->hosts = strdupz(value);
91 + silencer->hosts_pattern = simple_pattern_create(silencer->hosts, NULL, SIMPLE_PATTERN_EXACT);
92 + } else if (hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY)) {
93 + silencer->families = strdupz(value);
94 + silencer->families_pattern = simple_pattern_create(silencer->families, NULL, SIMPLE_PATTERN_EXACT);
95 + }
96 +
97 + return silencer;
98 +}
99 +
100 +/**
101 + * JSON Read Callback
102 + *
103 + * Callback called by netdata to create the silencer.
104 + *
105 + * @param e the main json structure
106 + *
107 + * @return It always return 0.
108 + */
109 +int health_silencers_json_read_callback(JSON_ENTRY *e)
110 +{
111 + switch(e->type) {
112 + case JSON_OBJECT:
113 +#ifndef ENABLE_JSONC
114 + e->callback_function = health_silencers_json_read_callback;
115 + if(e->name && strcmp(e->name,"")) {
116 + // init silencer
117 + debug(D_HEALTH, "JSON: Got object with a name, initializing new silencer for %s",e->name);
118 +#endif
119 + e->callback_data = create_silencer();
120 + if(e->callback_data) {
121 + health_silencers_add(e->callback_data);
122 + }
123 +#ifndef ENABLE_JSONC
124 + }
125 +#endif
126 + break;
127 +
128 + case JSON_ARRAY:
129 + e->callback_function = health_silencers_json_read_callback;
130 + break;
131 +
132 + case JSON_STRING:
133 + if(!strcmp(e->name,"type")) {
134 + debug(D_HEALTH, "JSON: Processing type=%s",e->data.string);
135 + if (!strcmp(e->data.string,"SILENCE")) silencers->stype = STYPE_SILENCE_NOTIFICATIONS;
136 + else if (!strcmp(e->data.string,"DISABLE")) silencers->stype = STYPE_DISABLE_ALARMS;
137 + } else {
138 + debug(D_HEALTH, "JSON: Adding %s=%s", e->name, e->data.string);
139 + health_silencers_addparam(e->callback_data, e->name, e->data.string);
140 + }
141 + break;
142 +
143 + case JSON_BOOLEAN:
144 + debug(D_HEALTH, "JSON: Processing all_alarms");
145 + silencers->all_alarms=e->data.boolean?1:0;
146 + break;
147 +
148 + case JSON_NUMBER:
149 + case JSON_NULL:
150 + break;
151 + }
152 +
153 + return 0;
154 +}
libnetdata/health/health.h new
+54
@@ -0,0 +1,54 @@
1 +#ifndef NETDATA_HEALTH_LIB
2 +# define NETDATA_HEALTH_LIB 1
3 +
4 +# include "../libnetdata.h"
5 +
6 +#define HEALTH_ALARM_KEY "alarm"
7 +#define HEALTH_TEMPLATE_KEY "template"
8 +#define HEALTH_CONTEXT_KEY "context"
9 +#define HEALTH_CHART_KEY "chart"
10 +#define HEALTH_HOST_KEY "hosts"
11 +#define HEALTH_OS_KEY "os"
12 +#define HEALTH_FAMILIES_KEY "families"
13 +#define HEALTH_LOOKUP_KEY "lookup"
14 +#define HEALTH_CALC_KEY "calc"
15 +
16 +typedef struct silencer {
17 + char *alarms;
18 + SIMPLE_PATTERN *alarms_pattern;
19 +
20 + char *hosts;
21 + SIMPLE_PATTERN *hosts_pattern;
22 +
23 + char *contexts;
24 + SIMPLE_PATTERN *contexts_pattern;
25 +
26 + char *charts;
27 + SIMPLE_PATTERN *charts_pattern;
28 +
29 + char *families;
30 + SIMPLE_PATTERN *families_pattern;
31 +
32 + struct silencer *next;
33 +} SILENCER;
34 +
35 +typedef enum silence_type {
36 + STYPE_NONE,
37 + STYPE_DISABLE_ALARMS,
38 + STYPE_SILENCE_NOTIFICATIONS
39 +} SILENCE_TYPE;
40 +
41 +typedef struct silencers {
42 + int all_alarms;
43 + SILENCE_TYPE stype;
44 + SILENCER *silencers;
45 +} SILENCERS;
46 +
47 +SILENCERS *silencers;
48 +
49 +extern SILENCER *create_silencer(void);
50 +extern int health_silencers_json_read_callback(JSON_ENTRY *e);
51 +extern void health_silencers_add(SILENCER *silencer);
52 +extern SILENCER * health_silencers_addparam(SILENCER *silencer, char *key, char *value);
53 +
54 +#endif
libnetdata/json/Makefile.am new
+9
@@ -0,0 +1,9 @@
1 +# SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +AUTOMAKE_OPTIONS = subdir-objects
4 +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
5 +
6 +
7 +dist_noinst_DATA = \
8 + README.md \
9 + $(NULL)
libnetdata/json/README.md new
+5
@@ -0,0 +1,5 @@
1 +# json
2 +
3 +`json` contains a parser for json strings, based on `jsmn` (https://github.com/zserge/jsmn), but case you have installed the JSON-C library, the installation script will prefer it, you can also force its use with `--enable-jsonc` in the compilation time.
4 +
5 +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Flibnetdata%2Fjson%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)]()
libnetdata/json/jsmn.c new
+326
@@ -0,0 +1,326 @@
1 +#include <stdlib.h>
2 +
3 +#include "jsmn.h"
4 +
5 +/**
6 + * Alloc token
7 + *
8 + * Allocates a fresh unused token from the token pull.
9 + *
10 + * @param parser the controller
11 + * @param tokens the tokens I am working
12 + * @param num_tokens the number total of tokens.
13 + *
14 + * @return it returns the next token to work.
15 + */
16 +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
17 + jsmntok_t *tokens, size_t num_tokens) {
18 + jsmntok_t *tok;
19 + if (parser->toknext >= num_tokens) {
20 + return NULL;
21 + }
22 + tok = &tokens[parser->toknext++];
23 + tok->start = tok->end = -1;
24 + tok->size = 0;
25 +#ifdef JSMN_PARENT_LINKS
26 + tok->parent = -1;
27 +#endif
28 + return tok;
29 +}
30 +
31 +/**
32 + * Fill Token
33 + *
34 + * Fills token type and boundaries.
35 + *
36 + * @param token the structure to set the values
37 + * @param type is the token type
38 + * @param start is the first position of the value
39 + * @param end is the end of the value
40 + */
41 +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
42 + int start, int end) {
43 + token->type = type;
44 + token->start = start;
45 + token->end = end;
46 + token->size = 0;
47 +}
48 +
49 +/**
50 + * Parse primitive
51 + *
52 + * Fills next available token with JSON primitive.
53 + *
54 + * @param parser is the control structure
55 + * @param js is the json string
56 + * @param type is the token type
57 + */
58 +static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
59 + size_t len, jsmntok_t *tokens, size_t num_tokens) {
60 + jsmntok_t *token;
61 + int start;
62 +
63 + start = parser->pos;
64 +
65 + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
66 + switch (js[parser->pos]) {
67 +#ifndef JSMN_STRICT
68 + /* In strict mode primitive must be followed by "," or "}" or "]" */
69 + case ':':
70 +#endif
71 + case '\t' : case '\r' : case '\n' : case ' ' :
72 + case ',' : case ']' : case '}' :
73 + goto found;
74 + }
75 + if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
76 + parser->pos = start;
77 + return JSMN_ERROR_INVAL;
78 + }
79 + }
80 +#ifdef JSMN_STRICT
81 + /* In strict mode primitive must be followed by a comma/object/array */
82 + parser->pos = start;
83 + return JSMN_ERROR_PART;
84 +#endif
85 +
86 + found:
87 + if (tokens == NULL) {
88 + parser->pos--;
89 + return 0;
90 + }
91 + token = jsmn_alloc_token(parser, tokens, num_tokens);
92 + if (token == NULL) {
93 + parser->pos = start;
94 + return JSMN_ERROR_NOMEM;
95 + }
96 + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
97 +#ifdef JSMN_PARENT_LINKS
98 + token->parent = parser->toksuper;
99 +#endif
100 + parser->pos--;
101 + return 0;
102 +}
103 +
104 +/**
105 + * Parse string
106 + *
107 + * Fills next token with JSON string.
108 + *
109 + * @param parser is the control structure
110 + * @param js is the json string
111 + * @param len is the js length
112 + * @param tokens is structure with the tokens mapped.
113 + * @param num_tokens is the total number of tokens
114 + *
115 + * @return It returns 0 on success and another integer otherwise
116 + */
117 +static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
118 + size_t len, jsmntok_t *tokens, size_t num_tokens) {
119 + jsmntok_t *token;
120 +
121 + int start = parser->pos;
122 +
123 + parser->pos++;
124 +
125 + /* Skip starting quote */
126 + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
127 + char c = js[parser->pos];
128 +
129 + /* Quote: end of string */
130 + if (c == '\"') {
131 + if (tokens == NULL) {
132 + return 0;
133 + }
134 + token = jsmn_alloc_token(parser, tokens, num_tokens);
135 + if (token == NULL) {
136 + parser->pos = start;
137 + return JSMN_ERROR_NOMEM;
138 + }
139 + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
140 +#ifdef JSMN_PARENT_LINKS
141 + token->parent = parser->toksuper;
142 +#endif
143 + return 0;
144 + }
145 +
146 + /* Backslash: Quoted symbol expected */
147 + if (c == '\\') {
148 + parser->pos++;
149 + switch (js[parser->pos]) {
150 + /* Allowed escaped symbols */
151 + case '\"': case '/' : case '\\' : case 'b' :
152 + case 'f' : case 'r' : case 'n' : case 't' :
153 + break;
154 + /* Allows escaped symbol \uXXXX */
155 + case 'u':
156 + parser->pos++;
157 + int i = 0;
158 + for(; i < 4 && js[parser->pos] != '\0'; i++) {
159 + /* If it isn't a hex character we have an error */
160 + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
161 + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
162 + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
163 + parser->pos = start;
164 + return JSMN_ERROR_INVAL;
165 + }
166 + parser->pos++;
167 + }
168 + parser->pos--;
169 + break;
170 + /* Unexpected symbol */
171 + default:
172 + parser->pos = start;
173 + return JSMN_ERROR_INVAL;
174 + }
175 + }
176 + }
177 + parser->pos = start;
178 + return JSMN_ERROR_PART;
179 +}
180 +
181 +/**
182 + * JSMN Parse
183 + *
184 + * Parse JSON string and fill tokens.
185 + *
186 + * @param parser the auxiliar vector used to parser
187 + * @param js the string to parse
188 + * @param len the string length
189 + * @param tokens the place to map the tokens
190 + * @param num_tokens the number of tokens present in the tokens structure.
191 + *
192 + * @return It returns the number of tokens present in the string on success or a negative number otherwise
193 + */
194 +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
195 + jsmntok_t *tokens, unsigned int num_tokens) {
196 + jsmnerr_t r;
197 + int i;
198 + jsmntok_t *token;
199 + int count = 0;
200 +
201 + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
202 + char c;
203 + jsmntype_t type;
204 +
205 + c = js[parser->pos];
206 + switch (c) {
207 + case '{': case '[':
208 + count++;
209 + if (tokens == NULL) {
210 + break;
211 + }
212 + token = jsmn_alloc_token(parser, tokens, num_tokens);
213 + if (token == NULL)
214 + return JSMN_ERROR_NOMEM;
215 + if (parser->toksuper != -1) {
216 + tokens[parser->toksuper].size++;
217 +#ifdef JSMN_PARENT_LINKS
218 + token->parent = parser->toksuper;
219 +#endif
220 + }
221 + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
222 + token->start = parser->pos;
223 + parser->toksuper = parser->toknext - 1;
224 + break;
225 + case '}': case ']':
226 + if (tokens == NULL)
227 + break;
228 + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
229 +#ifdef JSMN_PARENT_LINKS
230 + if (parser->toknext < 1) {
231 + return JSMN_ERROR_INVAL;
232 + }
233 + token = &tokens[parser->toknext - 1];
234 + for (;;) {
235 + if (token->start != -1 && token->end == -1) {
236 + if (token->type != type) {
237 + return JSMN_ERROR_INVAL;
238 + }
239 + token->end = parser->pos + 1;
240 + parser->toksuper = token->parent;
241 + break;
242 + }
243 + if (token->parent == -1) {
244 + break;
245 + }
246 + token = &tokens[token->parent];
247 + }
248 +#else
249 + for (i = parser->toknext - 1; i >= 0; i--) {
250 + token = &tokens[i];
251 + if (token->start != -1 && token->end == -1) {
252 + if (token->type != type) {
253 + return JSMN_ERROR_INVAL;
254 + }
255 + parser->toksuper = -1;
256 + token->end = parser->pos + 1;
257 + break;
258 + }
259 + }
260 + /* Error if unmatched closing bracket */
261 + if (i == -1) return JSMN_ERROR_INVAL;
262 + for (; i >= 0; i--) {
263 + token = &tokens[i];
264 + if (token->start != -1 && token->end == -1) {
265 + parser->toksuper = i;
266 + break;
267 + }
268 + }
269 +#endif
270 + break;
271 + case '\"':
272 + r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
273 + if (r < 0) return r;
274 + count++;
275 + if (parser->toksuper != -1 && tokens != NULL)
276 + tokens[parser->toksuper].size++;
277 + break;
278 + case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ':
279 + break;
280 +#ifdef JSMN_STRICT
281 + /* In strict mode primitives are: numbers and booleans */
282 + case '-': case '0': case '1' : case '2': case '3' : case '4':
283 + case '5': case '6': case '7' : case '8': case '9':
284 + case 't': case 'f': case 'n' :
285 +#else
286 + /* In non-strict mode every unquoted value is a primitive */
287 + default:
288 +#endif
289 + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
290 + if (r < 0) return r;
291 + count++;
292 + if (parser->toksuper != -1 && tokens != NULL)
293 + tokens[parser->toksuper].size++;
294 + break;
295 +
296 +#ifdef JSMN_STRICT
297 + /* Unexpected char in strict mode */
298 + default:
299 + return JSMN_ERROR_INVAL;
300 +#endif
301 + }
302 + }
303 +
304 + for (i = parser->toknext - 1; i >= 0; i--) {
305 + /* Unmatched opened object or array */
306 + if (tokens[i].start != -1 && tokens[i].end == -1) {
307 + return JSMN_ERROR_PART;
308 + }
309 + }
310 +
311 + return count;
312 +}
313 +
314 +/**
315 + * JSMN Init
316 + *
317 + * Creates a new parser based over a given buffer with an array of tokens
318 + * available.
319 + *
320 + * @param parser is the structure with values to reset
321 + */
322 +void jsmn_init(jsmn_parser *parser) {
323 + parser->pos = 0;
324 + parser->toknext = 0;
325 + parser->toksuper = -1;
326 +}
\ No newline at end of file
libnetdata/json/jsmn.h new
+75
@@ -0,0 +1,75 @@
1 +#ifndef __JSMN_H_
2 +#define __JSMN_H_
3 +
4 +#ifdef __cplusplus
5 +extern "C" {
6 +#endif
7 +
8 +#include <stddef.h>
9 +/**
10 + * JSON type identifier. Basic types are:
11 + * o Object
12 + * o Array
13 + * o String
14 + * o Other primitive: number, boolean (true/false) or null
15 + */
16 +typedef enum {
17 + JSMN_PRIMITIVE = 0,
18 + JSMN_OBJECT = 1,
19 + JSMN_ARRAY = 2,
20 + JSMN_STRING = 3
21 +} jsmntype_t;
22 +
23 +typedef enum {
24 + /* Not enough tokens were provided */
25 + JSMN_ERROR_NOMEM = -1,
26 + /* Invalid character inside JSON string */
27 + JSMN_ERROR_INVAL = -2,
28 + /* The string is not a full JSON packet, more bytes expected */
29 + JSMN_ERROR_PART = -3,
30 +} jsmnerr_t;
31 +
32 +/**
33 + * JSON token description.
34 + *
35 + * @param type type (object, array, string etc.)
36 + * @param start start position in JSON data string
37 + * @param end end position in JSON data string
38 + */
39 +typedef struct {
40 + jsmntype_t type;
41 + int start;
42 + int end;
43 + int size;
44 +#ifdef JSMN_PARENT_LINKS
45 + int parent;
46 +#endif
47 +} jsmntok_t;
48 +
49 +/**
50 + * JSON parser. Contains an array of token blocks available. Also stores
51 + * the string being parsed now and current position in that string
52 + */
53 +typedef struct {
54 + unsigned int pos; /* offset in the JSON string */
55 + unsigned int toknext; /* next token to allocate */
56 + int toksuper; /* superior token node, e.g parent object or array */
57 +} jsmn_parser;
58 +
59 +/**
60 + * Create JSON parser over an array of tokens
61 + */
62 +void jsmn_init(jsmn_parser *parser);
63 +
64 +/**
65 + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
66 + * a single JSON object.
67 + */
68 +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
69 + jsmntok_t *tokens, unsigned int num_tokens);
70 +
71 +#ifdef __cplusplus
72 +}
73 +#endif
74 +
75 +#endif /* __JSMN_H_ */
\ No newline at end of file
libnetdata/json/json.c new
+546
@@ -0,0 +1,546 @@
1 +#include "jsmn.h"
2 +#include "../libnetdata.h"
3 +#include "json.h"
4 +#include "libnetdata/libnetdata.h"
5 +#include "../../health/health.h"
6 +
7 +#define JSON_TOKENS 1024
8 +
9 +int json_tokens = JSON_TOKENS;
10 +
11 +/**
12 + * Json Tokenise
13 + *
14 + * Map the string given inside tokens.
15 + *
16 + * @param js is the string used to create the tokens
17 + * @param len is the string length
18 + * @param count the number of tokens present in the string
19 + *
20 + * @return it returns the json parsed in tokens
21 + */
22 +#ifdef ENABLE_JSONC
23 +json_object *json_tokenise(char *js) {
24 + if(!js) {
25 + error("JSON: json string is empty.");
26 + return NULL;
27 + }
28 +
29 + json_object *token = json_tokener_parse(js);
30 + if(!token) {
31 + error("JSON: Invalid json string.");
32 + return NULL;
33 + }
34 +
35 + return token;
36 +}
37 +#else
38 +jsmntok_t *json_tokenise(char *js, size_t len, size_t *count)
39 +{
40 + int n = json_tokens;
41 + if(!js || !len) {
42 + error("JSON: json string is empty.");
43 + return NULL;
44 + }
45 +
46 + jsmn_parser parser;
47 + jsmn_init(&parser);
48 +
49 + jsmntok_t *tokens = mallocz(sizeof(jsmntok_t) * n);
50 + if(!tokens) return NULL;
51 +
52 + int ret = jsmn_parse(&parser, js, len, tokens, n);
53 + while (ret == JSMN_ERROR_NOMEM) {
54 + n *= 2;
55 + jsmntok_t *new = reallocz(tokens, sizeof(jsmntok_t) * n);
56 + if(!new) {
57 + freez(tokens);
58 + return NULL;
59 + }
60 + tokens = new;
61 + ret = jsmn_parse(&parser, js, len, tokens, n);
62 + }
63 +
64 + if (ret == JSMN_ERROR_INVAL) {
65 + error("JSON: Invalid json string.");
66 + freez(tokens);
67 + return NULL;
68 + }
69 + else if (ret == JSMN_ERROR_PART) {
70 + error("JSON: Truncated JSON string.");
71 + freez(tokens);
72 + return NULL;
73 + }
74 +
75 + if(count) *count = (size_t)ret;
76 +
77 + if(json_tokens < n) json_tokens = n;
78 + return tokens;
79 +}
80 +#endif
81 +
82 +/**
83 + * Callback Print
84 + *
85 + * Set callback print case necesary and wrinte an information inside a buffer to write in the log.
86 + *
87 + * @param e a pointer for a structure that has the complete information about json structure.
88 + *
89 + * @return It always return 0
90 + */
91 +int json_callback_print(JSON_ENTRY *e)
92 +{
93 + BUFFER *wb=buffer_create(300);
94 +
95 + buffer_sprintf(wb,"%s = ", e->name);
96 + char txt[50];
97 + switch(e->type) {
98 + case JSON_OBJECT:
99 + e->callback_function = json_callback_print;
100 + buffer_strcat(wb,"OBJECT");
101 + break;
102 +
103 + case JSON_ARRAY:
104 + e->callback_function = json_callback_print;
105 + sprintf(txt,"ARRAY[%lu]", e->data.items);
106 + buffer_strcat(wb, txt);
107 + break;
108 +
109 + case JSON_STRING:
110 + buffer_strcat(wb, e->data.string);
111 + break;
112 +
113 + case JSON_NUMBER:
114 + sprintf(txt,"%Lf", e->data.number);
115 + buffer_strcat(wb,txt);
116 +
117 + break;
118 +
119 + case JSON_BOOLEAN:
120 + buffer_strcat(wb, e->data.boolean?"TRUE":"FALSE");
121 + break;
122 +
123 + case JSON_NULL:
124 + buffer_strcat(wb,"NULL");
125 + break;
126 + }
127 + info("JSON: %s", buffer_tostring(wb));
128 + buffer_free(wb);
129 + return 0;
130 +}
131 +
132 +/**
133 + * JSONC Set String
134 + *
135 + * Set the string value of the structure JSON_ENTRY.
136 + *
137 + * @param e the output structure
138 + */
139 +static inline void json_jsonc_set_string(JSON_ENTRY *e,char *key,const char *value) {
140 + size_t length = strlen(key);
141 + e->type = JSON_STRING;
142 + memcpy(e->name,key,length);
143 + e->name[length] = 0x00;
144 + e->data.string = (char *) value;
145 +}
146 +
147 +
148 +#ifdef ENABLE_JSONC
149 +/**
150 + * JSONC set Boolean
151 + *
152 + * Set the boolean value of the structure JSON_ENTRY
153 + *
154 + * @param e the output structure
155 + * @param value the input value
156 + */
157 +static inline void json_jsonc_set_boolean(JSON_ENTRY *e,int value) {
158 + e->type = JSON_BOOLEAN;
159 + e->data.boolean = value;
160 +}
161 +
162 +/**
163 + * Parse Array
164 + *
165 + * Parse the array object.
166 + *
167 + * @param ptr the pointer for the object that we will parse.
168 + * @param callback_data additional data to be used together the callback function
169 + * @param callback_function function used to create a silencer.
170 + */
171 +static inline void json_jsonc_parse_array(json_object *ptr, void *callback_data,int (*callback_function)(struct json_entry *)) {
172 + int end = json_object_array_length(ptr);
173 + JSON_ENTRY e;
174 +
175 + if(end) {
176 + int i;
177 + i = 0;
178 +
179 + enum json_type type;
180 + do {
181 + json_object *jvalue = json_object_array_get_idx(ptr, i);
182 + if(jvalue) {
183 + e.callback_data = callback_data;
184 + e.type = JSON_OBJECT;
185 + callback_function(&e);
186 + json_object_object_foreach(jvalue, key, val) {
187 + type = json_object_get_type(val);
188 + if (type == json_type_array) {
189 + e.type = JSON_ARRAY;
190 + json_jsonc_parse_array(val, callback_data, callback_function);
191 + } else if (type == json_type_object) {
192 + json_walk(val,callback_data,callback_function);
193 + } else if (type == json_type_string) {
194 + json_jsonc_set_string(&e,key,json_object_get_string(val));
195 + callback_function(&e);
196 + } else if (type == json_type_boolean) {
197 + json_jsonc_set_boolean(&e,json_object_get_boolean(val));
198 + callback_function(&e);
199 + }
200 + }
201 + }
202 +
203 + } while (++i < end);
204 + }
205 +}
206 +#else
207 +
208 +/**
209 + * Walk string
210 + *
211 + * Set JSON_ENTRY to string and map the values from jsmntok_t.
212 + *
213 + * @param js the original string
214 + * @param t the tokens
215 + * @param start the first position
216 + * @param e the output structure.
217 + *
218 + * @return It always return 1
219 + */
220 +size_t json_walk_string(char *js, jsmntok_t *t, size_t start, JSON_ENTRY *e)
221 +{
222 + char old = js[t[start].end];
223 + js[t[start].end] = '\0';
224 + e->original_string = &js[t[start].start];
225 +
226 + e->type = JSON_STRING;
227 + e->data.string = e->original_string;
228 + if(e->callback_function) e->callback_function(e);
229 + js[t[start].end] = old;
230 + return 1;
231 +}
232 +
233 +/**
234 + * Walk Primitive
235 + *
236 + * Define the data type of the string
237 + *
238 + * @param js the original string
239 + * @param t the tokens
240 + * @param start the first position
241 + * @param e the output structure.
242 + *
243 + * @return It always return 1
244 + */
245 +size_t json_walk_primitive(char *js, jsmntok_t *t, size_t start, JSON_ENTRY *e)
246 +{
247 + char old = js[t[start].end];
248 + js[t[start].end] = '\0';
249 + e->original_string = &js[t[start].start];
250 +
251 + switch(e->original_string[0]) {
252 + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
253 + case '8': case '9': case '-': case '.':
254 + e->type = JSON_NUMBER;
255 + e->data.number = strtold(e->original_string, NULL);
256 + break;
257 +
258 + case 't': case 'T':
259 + e->type = JSON_BOOLEAN;
260 + e->data.boolean = 1;
261 + break;
262 +
263 + case 'f': case 'F':
264 + e->type = JSON_BOOLEAN;
265 + e->data.boolean = 0;
266 + break;
267 +
268 + case 'n': case 'N':
269 + default:
270 + e->type = JSON_NULL;
271 + break;
272 + }
273 + if(e->callback_function) e->callback_function(e);
274 + js[t[start].end] = old;
275 + return 1;
276 +}
277 +
278 +/**
279 + * Array
280 + *
281 + * Measure the array length
282 + *
283 + * @param js the original string
284 + * @param t the tokens
285 + * @param nest the length of structure t
286 + * @param start the first position
287 + * @param e the output structure.
288 + *
289 + * @return It returns the array length
290 + */
291 +size_t json_walk_array(char *js, jsmntok_t *t, size_t nest, size_t start, JSON_ENTRY *e)
292 +{
293 + JSON_ENTRY ne = {
294 + .name = "",
295 + .fullname = "",
296 + .callback_data = NULL,
297 + .callback_function = NULL
298 + };
299 +
300 + char old = js[t[start].end];
301 + js[t[start].end] = '\0';
302 + ne.original_string = &js[t[start].start];
303 +
304 + memcpy(&ne, e, sizeof(JSON_ENTRY));
305 + ne.type = JSON_ARRAY;
306 + ne.data.items = t[start].size;
307 + ne.callback_function = NULL;
308 + ne.name[0]='\0';
309 + ne.fullname[0]='\0';
310 + if(e->callback_function) e->callback_function(&ne);
311 + js[t[start].end] = old;
312 +
313 + size_t i, init = start, size = t[start].size;
314 +
315 + start++;
316 + for(i = 0; i < size ; i++) {
317 + ne.pos = i;
318 + if (!e->name || !e->fullname || strlen(e->name) > JSON_NAME_LEN - 24 || strlen(e->fullname) > JSON_FULLNAME_LEN -24) {
319 + info("JSON: JSON walk_array ignoring element with name:%s fullname:%s",e->name, e->fullname);
320 + continue;
321 + }
322 + sprintf(ne.name, "%s[%lu]", e->name, i);
323 + sprintf(ne.fullname, "%s[%lu]", e->fullname, i);
324 +
325 + switch(t[start].type) {
326 + case JSMN_PRIMITIVE:
327 + start += json_walk_primitive(js, t, start, &ne);
328 + break;
329 +
330 + case JSMN_OBJECT:
331 + start += json_walk_object(js, t, nest + 1, start, &ne);
332 + break;
333 +
334 + case JSMN_ARRAY:
335 + start += json_walk_array(js, t, nest + 1, start, &ne);
336 + break;
337 +
338 + case JSMN_STRING:
339 + start += json_walk_string(js, t, start, &ne);
340 + break;
341 + }
342 + }
343 + return start - init;
344 +}
345 +
346 +/**
347 + * Object
348 + *
349 + * Measure the Object length
350 + *
351 + * @param js the original string
352 + * @param t the tokens
353 + * @param nest the length of structure t
354 + * @param start the first position
355 + * @param e the output structure.
356 + *
357 + * @return It returns the Object length
358 + */
359 +size_t json_walk_object(char *js, jsmntok_t *t, size_t nest, size_t start, JSON_ENTRY *e)
360 +{
361 + JSON_ENTRY ne = {
362 + .name = "",
363 + .fullname = "",
364 + .callback_data = NULL,
365 + .callback_function = NULL
366 + };
367 +
368 + char old = js[t[start].end];
369 + js[t[start].end] = '\0';
370 + ne.original_string = &js[t[start].start];
371 + memcpy(&ne, e, sizeof(JSON_ENTRY));
372 + ne.type = JSON_OBJECT;
373 + ne.callback_function = NULL;
374 + if(e->callback_function) e->callback_function(&ne);
375 + js[t[start].end] = old;
376 +
377 + int key = 1;
378 + size_t i, init = start, size = t[start].size;
379 +
380 + start++;
381 + for(i = 0; i < size ; i++) {
382 + switch(t[start].type) {
383 + case JSMN_PRIMITIVE:
384 + start += json_walk_primitive(js, t, start, &ne);
385 + key = 1;
386 + break;
387 +
388 + case JSMN_OBJECT:
389 + start += json_walk_object(js, t, nest + 1, start, &ne);
390 + key = 1;
391 + break;
392 +
393 + case JSMN_ARRAY:
394 + start += json_walk_array(js, t, nest + 1, start, &ne);
395 + key = 1;
396 + break;
397 +
398 + case JSMN_STRING:
399 + default:
400 + if(key) {
401 + int len = t[start].end - t[start].start;
402 + if (unlikely(len>JSON_NAME_LEN)) len=JSON_NAME_LEN;
403 + strncpy(ne.name, &js[t[start].start], len);
404 + ne.name[len] = '\0';
405 + len=strlen(e->fullname) + strlen(e->fullname[0]?".":"") + strlen(ne.name);
406 + char *c = mallocz((len+1)*sizeof(char));
407 + sprintf(c,"%s%s%s", e->fullname, e->fullname[0]?".":"", ne.name);
408 + if (unlikely(len>JSON_FULLNAME_LEN)) len=JSON_FULLNAME_LEN;
409 + strncpy(ne.fullname, c, len);
410 + freez(c);
411 + start++;
412 + key = 0;
413 + }
414 + else {
415 + start += json_walk_string(js, t, start, &ne);
416 + key = 1;
417 + }
418 + break;
419 + }
420 + }
421 + return start - init;
422 +}
423 +#endif
424 +
425 +/**
426 + * Tree
427 + *
428 + * Call the correct walk function according its type.
429 + *
430 + * @param t the json object to work
431 + * @param callback_data additional data to be used together the callback function
432 + * @param callback_function function used to create a silencer.
433 + *
434 + * @return It always return 1
435 + */
436 +#ifdef ENABLE_JSONC
437 +size_t json_walk(json_object *t, void *callback_data, int (*callback_function)(struct json_entry *)) {
438 + JSON_ENTRY e;
439 +
440 + e.callback_data = callback_data;
441 + enum json_type type;
442 + json_object_object_foreach(t, key, val) {
443 + type = json_object_get_type(val);
444 + if (type == json_type_array) {
445 + e.type = JSON_ARRAY;
446 + json_jsonc_parse_array(val,NULL,health_silencers_json_read_callback);
447 + } else if (type == json_type_object) {
448 + e.type = JSON_OBJECT;
449 + } else if (type == json_type_string) {
450 + json_jsonc_set_string(&e,key,json_object_get_string(val));
451 + callback_function(&e);
452 + } else if (type == json_type_boolean) {
453 + json_jsonc_set_boolean(&e,json_object_get_boolean(val));
454 + callback_function(&e);
455 + }
456 + }
457 +
458 + return 1;
459 +}
460 +#else
461 +/**
462 + * Tree
463 + *
464 + * Call the correct walk function according its type.
465 + *
466 + * @param js the original string
467 + * @param t the tokens
468 + * @param callback_data additional data to be used together the callback function
469 + * @param callback_function function used to create a silencer.
470 + *
471 + * @return It always return 1
472 + */
473 +size_t json_walk_tree(char *js, jsmntok_t *t, void *callback_data, int (*callback_function)(struct json_entry *))
474 +{
475 + JSON_ENTRY e = {
476 + .name = "",
477 + .fullname = "",
478 + .callback_data = callback_data,
479 + .callback_function = callback_function
480 + };
481 +
482 + switch (t[0].type) {
483 + case JSMN_OBJECT:
484 + e.type = JSON_OBJECT;
485 + json_walk_object(js, t, 0, 0, &e);
486 + break;
487 +
488 + case JSMN_ARRAY:
489 + e.type = JSON_ARRAY;
490 + json_walk_array(js, t, 0, 0, &e);
491 + break;
492 +
493 + case JSMN_PRIMITIVE:
494 + case JSMN_STRING:
495 + break;
496 + }
497 +
498 + return 1;
499 +}
500 +#endif
501 +
502 +/**
503 + * JSON Parse
504 + *
505 + * Parse the json message with the callback function
506 + *
507 + * @param js the string that the callback function will parse
508 + * @param callback_data additional data to be used together the callback function
509 + * @param callback_function function used to create a silencer.
510 + *
511 + * @return JSON_OK case everything happend as expected, JSON_CANNOT_PARSE case there were errors in the
512 + * parsing procces and JSON_CANNOT_DOWNLOAD case the string given(js) is NULL.
513 + */
514 +int json_parse(char *js, void *callback_data, int (*callback_function)(JSON_ENTRY *))
515 +{
516 + if(js) {
517 +#ifdef ENABLE_JSONC
518 + json_object *tokens = json_tokenise(js);
519 +#else
520 + size_t count;
521 + jsmntok_t *tokens = json_tokenise(js, strlen(js), &count);
522 +#endif
523 +
524 + if(tokens) {
525 +#ifdef ENABLE_JSONC
526 + json_walk(tokens, callback_data, callback_function);
527 + json_object_put(tokens);
528 +#else
529 + json_walk_tree(js, tokens, callback_data, callback_function);
530 + freez(tokens);
531 +#endif
532 + return JSON_OK;
533 + }
534 +
535 + return JSON_CANNOT_PARSE;
536 + }
537 +
538 + return JSON_CANNOT_DOWNLOAD;
539 +}
540 +
541 +/*
542 +int json_test(char *str)
543 +{
544 + return json_parse(str, NULL, json_callback_print);
545 +}
546 + */
\ No newline at end of file
libnetdata/json/json.h new
+72
@@ -0,0 +1,72 @@
1 +#ifndef CHECKIN_JSON_H
2 +#define CHECKIN_JSON_H 1
3 +
4 +
5 +#if ENABLE_JSONC
6 +# include <json-c/json.h>
7 +#endif
8 +
9 +#include "jsmn.h"
10 +
11 +//https://www.ibm.com/support/knowledgecenter/en/SS9H2Y_7.6.0/com.ibm.dp.doc/json_parserlimits.html
12 +#define JSON_NAME_LEN 256
13 +#define JSON_FULLNAME_LEN 1024
14 +
15 +typedef enum {
16 + JSON_OBJECT = 0,
17 + JSON_ARRAY = 1,
18 + JSON_STRING = 2,
19 + JSON_NUMBER = 3,
20 + JSON_BOOLEAN = 4,
21 + JSON_NULL = 5,
22 +} JSON_ENTRY_TYPE;
23 +
24 +typedef struct json_entry {
25 + JSON_ENTRY_TYPE type;
26 + char name[JSON_NAME_LEN + 1];
27 + char fullname[JSON_FULLNAME_LEN + 1];
28 + union {
29 + char *string; // type == JSON_STRING
30 + long double number; // type == JSON_NUMBER
31 + int boolean; // type == JSON_BOOLEAN
32 + size_t items; // type == JSON_ARRAY
33 + } data;
34 + size_t pos; // the position of this item in its parent
35 +
36 + char *original_string;
37 +
38 + void *callback_data;
39 + int (*callback_function)(struct json_entry *);
40 +} JSON_ENTRY;
41 +
42 +// ----------------------------------------------------------------------------
43 +// public functions
44 +
45 +#define JSON_OK 0
46 +#define JSON_CANNOT_DOWNLOAD 1
47 +#define JSON_CANNOT_PARSE 2
48 +
49 +int json_parse(char *js, void *callback_data, int (*callback_function)(JSON_ENTRY *));
50 +
51 +
52 +// ----------------------------------------------------------------------------
53 +// private functions
54 +
55 +#ifdef ENABLE_JSONC
56 +json_object *json_tokenise(char *js);
57 +size_t json_walk(json_object *t, void *callback_data, int (*callback_function)(struct json_entry *));
58 +#else
59 +jsmntok_t *json_tokenise(char *js, size_t len, size_t *count);
60 +size_t json_walk_tree(char *js, jsmntok_t *t, void *callback_data, int (*callback_function)(struct json_entry *));
61 +#endif
62 +
63 +size_t json_walk_object(char *js, jsmntok_t *t, size_t nest, size_t start, JSON_ENTRY *e);
64 +size_t json_walk_array(char *js, jsmntok_t *t, size_t nest, size_t start, JSON_ENTRY *e);
65 +size_t json_walk_string(char *js, jsmntok_t *t, size_t start, JSON_ENTRY *e);
66 +size_t json_walk_primitive(char *js, jsmntok_t *t, size_t start, JSON_ENTRY *e);
67 +
68 +int json_callback_print(JSON_ENTRY *e);
69 +
70 +
71 +
72 +#endif
\ No newline at end of file
libnetdata/libnetdata.h
+2
@@ -310,5 +310,7 @@ extern char *netdata_configured_host_prefix;
310 #include "statistical/statistical.h"
311 #include "adaptive_resortable_list/adaptive_resortable_list.h"
312 #include "url/url.h"
313 +#include "json/json.h"
314 +#include "health/health.h"
315
316 #endif // NETDATA_LIB_H
netdata-installer.sh
+2
@@ -175,6 +175,7 @@ USAGE: ${PROGRAM} [options]
175 --libs-are-really-here If you get errors about missing zlib or libuuid but you know it is available, you might
176 have a broken pkg-config. Use this option to proceed without checking pkg-config.
177 --disable-telemetry Use this flag to opt-out from our anonymous telemetry progam.
178 + --enable-json Enable to use JSON-C library instead the internal Netdata library.
179
180 Netdata will by default be compiled with gcc optimization -O2
181 If you need to pass different CFLAGS, use something like this:
@@ -222,6 +223,7 @@ while [ -n "${1}" ]; do
223 "--disable-x86-sse") NETDATA_CONFIGURE_OPTIONS="${NETDATA_CONFIGURE_OPTIONS//--disable-x86-sse/} --disable-x86-sse";;
224 "--disable-telemetry") NETDATA_DISABLE_TELEMETRY=1;;
225 "--disable-go") NETDATA_DISABLE_GO=1;;
226 + "--enable-jsonc") NETDATA_CONFIGURE_OPTIONS="${NETDATA_CONFIGURE_OPTIONS//--enable-jsonc/} --enable-jsonc";;
227 "--install")
228 NETDATA_PREFIX="${2}/netdata"
229 shift 1
tests/health_mgmtapi/expected_list/ALARM_CPU_IOWAIT-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "SILENCE", "silencers": [ { "alarm": "*10min_cpu_iowait" }, { "alarm": "*10min_cpu_usage *load_trigger" } ] }
tests/health_mgmtapi/expected_list/ALARM_CPU_USAGE-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "SILENCE", "silencers": [ { "alarm": "*10min_cpu_usage *load_trigger", "context": "system.cpu" }, { "alarm": "*10min_cpu_usage *load_trigger", "chart": "system.load" } ] }
tests/health_mgmtapi/expected_list/CONTEXT_SYSTEM_CPU-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "DISABLE", "silencers": [ { "context": "system.cpu" }, { "chart": "system.load" } ] }
tests/health_mgmtapi/expected_list/DISABLE-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "DISABLE", "silencers": [ { "alarm": "*10min_cpu_usage *load_trigger" } ] }
tests/health_mgmtapi/expected_list/DISABLE_ALL-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": true, "type": "DISABLE", "silencers": [] }
tests/health_mgmtapi/expected_list/DISABLE_ALL_ERROR-list.json new
+1
@@ -0,0 +1 @@
1 +Auth Error
tests/health_mgmtapi/expected_list/DISABLE_SYSTEM_LOAD-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "DISABLE", "silencers": [ { "chart": "system.load" } ] }
tests/health_mgmtapi/expected_list/FAMILIES_LOAD-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "None", "silencers": [ { "families": "load" } ] }
tests/health_mgmtapi/expected_list/HOSTS-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "SILENCE", "silencers": [ { "hosts": "*" } ] }
tests/health_mgmtapi/expected_list/RESET-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "None", "silencers": [] }
tests/health_mgmtapi/expected_list/SILENCE-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "SILENCE", "silencers": [ { "alarm": "*10min_cpu_usage *load_trigger" } ] }
tests/health_mgmtapi/expected_list/SILENCE_2-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "SILENCE", "silencers": [ { "families": "load" } ] }
tests/health_mgmtapi/expected_list/SILENCE_3-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "SILENCE", "silencers": [] } WARNING: SILENCE or DISABLE command is ineffective without defining any alarm selectors.
tests/health_mgmtapi/expected_list/SILENCE_ALARM_CPU_USAGE-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "SILENCE", "silencers": [ { "alarm": "*10min_cpu_usage *load_trigger", "chart": "system.load" } ] }
tests/health_mgmtapi/expected_list/SILENCE_ALARM_CPU_USAGE_LOAD_TRIGGER-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": false, "type": "SILENCE", "silencers": [ { "alarm": "*10min_cpu_usage *load_trigger" } ] }
tests/health_mgmtapi/expected_list/SILENCE_ALL-list.json new
+1
@@ -0,0 +1 @@
1 +{ "all": true, "type": "SILENCE", "silencers": [] }
tests/health_mgmtapi/health-cmdapi-test.sh.in
+38 -2
@@ -27,7 +27,7 @@ check () {
27 elif [ "${r}" != "${2}" ] ; then
28 echo -e " ${GRAY}WARNING: 'Got ${r}'. Expected '${2}'"
29 iter=$((iter+1))
30 - if [ $iter -lt 10 ] ; then
30 + if [ $iter -lt 10 ] ; then
31 echo -e " ${GRAY}Repeating test "
32 check "$1" "$2"
33 else
@@ -53,6 +53,20 @@ cmd () {
53 fi
54 }
55
56 +check_list() {
57 + RESPONSE=$(curl -s "http://$URL/api/v1/manage/health?cmd=LIST" -H "X-Auth-Token: $TOKEN" 2>&1)
58 +
59 + NAME="$1-list.json"
60 + echo $RESPONSE > $NAME
61 + diff $NAME expected_list/$NAME 1>/dev/null 2>&1
62 + if [ $? -eq 0 ]; then
63 + echo -e "${GREEN}Success: The list command got the correct answer for $NAME!"
64 + else
65 + echo -e "${RED}ERROR: the files $NAME and expected_list/$NAME does not match."
66 + exit 1
67 + fi
68 +}
69 +
70 WHITE='\033[0;37m'
71 RED='\033[0;31m'
72 GREEN='\033[0;32m'
@@ -90,11 +104,13 @@ err=0
104 # Test default state
105 cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
106 check "Default State" "False False False False False False"
107 + check_list "RESET"
108
109 # Test auth failure
110 TOKEN="Wrong token"
111 cmd "cmd=DISABLE ALL" "$HEALTH_CMDAPI_MSG_AUTHERROR"
112 check "Default State" "False False False False False False"
113 + check_list "DISABLE_ALL_ERROR"
114
115 # Set correct token
116 TOKEN="${CORRECT_TOKEN}"
@@ -102,87 +118,107 @@ err=0
118 # Test disable
119 cmd "cmd=DISABLE ALL" "$HEALTH_CMDAPI_MSG_DISABLEALL"
120 check "All disabled" "True False True False True False"
121 + check_list "DISABLE_ALL"
122
123 # Reset
124 cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
125 check "Default State" "False False False False False False"
126 + check_list "RESET"
127
128 # Test silence
129 cmd "cmd=SILENCE ALL" "$HEALTH_CMDAPI_MSG_SILENCEALL"
130 check "All silenced" "False True False True False True"
131 + check_list "SILENCE_ALL"
132
133 # Reset
134 cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
135 check "Default State" "False False False False False False"
136 + check_list "RESET"
137
138 # Add silencer by name
139 printf -v resp "$HEALTH_CMDAPI_MSG_SILENCE\n$HEALTH_CMDAPI_MSG_ADDED"
140 cmd "cmd=SILENCE&alarm=*10min_cpu_usage *load_trigger" "${resp}"
141 check "Silence notifications for alarm1 and load_trigger" "False True False False False True"
142 + check_list "SILENCE_ALARM_CPU_USAGE_LOAD_TRIGGER"
143
144 # Convert to disable health checks
145 cmd "cmd=DISABLE" "$HEALTH_CMDAPI_MSG_DISABLE"
146 check "Disable notifications for alarm1 and load_trigger" "True False False False True False"
147 + check_list "DISABLE"
148
149 # Convert back to silence notifications
150 cmd "cmd=SILENCE" "$HEALTH_CMDAPI_MSG_SILENCE"
151 check "Silence notifications for alarm1 and load_trigger" "False True False False False True"
152 + check_list "SILENCE"
153
154 # Add second silencer by name
155 cmd "alarm=*10min_cpu_iowait" "$HEALTH_CMDAPI_MSG_ADDED"
156 check "Silence notifications for alarm1,alarm2 and load_trigger" "False True False True False True"
157 + check_list "ALARM_CPU_IOWAIT"
158
159 # Reset
160 cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
161 + check_list "RESET"
162
163 # Add silencer by chart
164 printf -v resp "$HEALTH_CMDAPI_MSG_DISABLE\n$HEALTH_CMDAPI_MSG_ADDED"
165 cmd "cmd=DISABLE&chart=system.load" "${resp}"
166 check "Default State" "False False False False True False"
167 + check_list "DISABLE_SYSTEM_LOAD"
168
169 # Add silencer by context
170 cmd "context=system.cpu" "$HEALTH_CMDAPI_MSG_ADDED"
171 check "Default State" "True False True False True False"
172 + check_list "CONTEXT_SYSTEM_CPU"
173
174 # Reset
175 cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
176 + check_list "RESET"
177
178 # Add second condition to a selector (AND)
179 printf -v resp "$HEALTH_CMDAPI_MSG_SILENCE\n$HEALTH_CMDAPI_MSG_ADDED"
180 cmd "cmd=SILENCE&alarm=*10min_cpu_usage *load_trigger&chart=system.load" "${resp}"
181 check "Silence notifications load_trigger" "False False False False False True"
182 + check_list "SILENCE_ALARM_CPU_USAGE"
183
184 # Add second selector with two conditions
185 cmd "alarm=*10min_cpu_usage *load_trigger&context=system.cpu" "$HEALTH_CMDAPI_MSG_ADDED"
186 check "Silence notifications load_trigger" "False True False False False True"
187 + check_list "ALARM_CPU_USAGE"
188
189 # Reset
190 cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
191 + check_list "RESET"
192
193 # Add silencer without a command to disable or silence alarms
194 printf -v resp "$HEALTH_CMDAPI_MSG_ADDED\n$HEALTH_CMDAPI_MSG_STYPEWARNING"
195 cmd "families=load" "${resp}"
196 check "Family selector with no command" "False False False False False False"
197 + check_list "FAMILIES_LOAD"
198
199 # Add silence command
200 cmd "cmd=SILENCE" "$HEALTH_CMDAPI_MSG_SILENCE"
201 check "Silence family load" "False False False False False True"
202 + check_list "SILENCE_2"
203
204 # Reset
205 cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
206 + check_list "RESET"
207
208 # Add command without silencers
209 printf -v resp "$HEALTH_CMDAPI_MSG_SILENCE\n$HEALTH_CMDAPI_MSG_NOSELECTORWARNING"
210 cmd "cmd=SILENCE" "${resp}"
211 check "Command with no selector" "False False False False False False"
212 + check_list "SILENCE_3"
213
214 # Add hosts silencer
215 cmd "hosts=*" "$HEALTH_CMDAPI_MSG_ADDED"
216 check "Silence all hosts" "False True False True False True"
217 + check_list "HOSTS"
218
219 # Reset
220 cmd "cmd=RESET" "$HEALTH_CMDAPI_MSG_RESET"
185 -
221 + check_list "RESET"
222
223 if [ $err -gt 0 ] ; then
224 echo "$err error(s) found"
web/api/health/README.md
+50 -1
@@ -45,6 +45,7 @@ The following will return an SVG badge of the alarm named `NAME`, attached to th
45 ## Health Management API
46
47 Netdata v1.12 and beyond provides a command API to control health checks and notifications at runtime. The feature is especially useful for maintenance periods, during which you receive meaningless alarms.
48 +From Netdata v1.16.0 and beyond, the configuration controlled via the API commands is [persisted across netdata restarts](#persistence).
49
50 Specifically, the API allows you to:
51 - Disable health checks completely. Alarm conditions will not be evaluated at all and no entries will be added to the alarm log.
@@ -142,6 +143,43 @@ Example 2.2: Add one more selector, to also silence alarms for cpu1 and cpu2
143 http://localhost/api/v1/manage/health?families=cpu1 cpu2
144 ```
145
146 +### List silencers
147 +
148 +The command `LIST` was added in netdata v1.16.0 and returns a JSON with the current status of the silencers.
149 +
150 +```
151 + curl "http://myserver/api/v1/manage/health?cmd=LIST" -H "X-Auth-Token: Mytoken"
152 +```
153 +
154 +As an example, the following response shows that we have two silencers configured, one for an alarm called `samplealarm` and one for alarms with context `random` on host `myhost`
155 +```
156 +json
157 +{
158 + "all": false,
159 + "type": "SILENCE",
160 + "silencers": [
161 + {
162 + "alarm": "samplealarm"
163 + },
164 + {
165 + "context": "random",
166 + "hosts": "myhost"
167 + }
168 + ]
169 +}
170 +```
171 +
172 +The response below shows that we have disabled all health checks.
173 +
174 +```
175 +json
176 +{
177 + "all": true,
178 + "type": "DISABLE",
179 + "silencers": []
180 +}
181 +```
182 +
183 ### Responses
184
185 - "Auth Error" : Token authentication failed
@@ -151,10 +189,21 @@ http://localhost/api/v1/manage/health?families=cpu1 cpu2
189 - "Health checks disabled for alarms matching the selectors" : Added to the response for a cmd=DISABLE
190 - "Alarm notifications silenced for alarms matching the selectors" : Added to the response for a cmd=SILENCE
191 - "Alarm selector added" : Added to the response when a new selector is added
154 -- "Invalid key. Ignoring it." : Wrong name of a parameter. Added to the response and ignored.
192 - "WARNING: Added alarm selector to silence/disable alarms without a SILENCE or DISABLE command." : Added to the response if a selector is added without a selector-specific command.
193 - "WARNING: SILENCE or DISABLE command is ineffective without defining any alarm selectors." : Added to the response if a selector-specific command is issued without a selector.
194
195 +### Persistence
196 +
197 +From netdata v1.13.1 and beyond, the silencers configuration is persisted to disk and loaded when netdata starts.
198 +The JSON string returned by the [LIST command](#list-silencers) is automatically saved to the `silencers file`, every time a command alters the silencers configuration.
199 +The file's location is configurable in `netdata.conf`. The default is shown below:
200 +
201 +```
202 +[health]
203 + # silencers file = /var/lib/netdata/health.silencers.json
204 +```
205 +
206 +
207 ### Further reading
208
209 The test script under [tests/health_mgmtapi](../../../tests/health_mgmtapi) contains a series of tests that you can either run or read through to understand the various calls and responses better.
web/api/health/health_cmdapi.c
+107 -67
@@ -1,17 +1,16 @@
1 //
2 -// Created by christopher on 11/12/18.
2 +// Created by Christopher on 11/12/18.
3 //
4
5 #include "health_cmdapi.h"
6
7 -
8 -static SILENCER *create_silencer(void) {
9 - SILENCER *t = callocz(1, sizeof(SILENCER));
10 - debug(D_HEALTH, "HEALTH command API: Created empty silencer");
11 -
12 - return t;
13 -}
14 -
7 +/**
8 + * Free Silencers
9 + *
10 + * Clean the silencer structure
11 + *
12 + * @param t is the structure that will be cleaned.
13 + */
14 void free_silencers(SILENCER *t) {
15 if (!t) return;
16 if (t->next) free_silencers(t->next);
@@ -31,38 +30,104 @@ void free_silencers(SILENCER *t) {
30 return;
31 }
32
33 +/**
34 + * Silencers to JSON Entry
35 + *
36 + * Fill the buffer with the other values given.
37 + *
38 + * @param wb a pointer to the output buffer
39 + * @param var the json variable
40 + * @param val the json value
41 + * @param hasprev has it a previous value?
42 + *
43 + * @return
44 + */
45 +int health_silencers2json_entry(BUFFER *wb, char* var, char* val, int hasprev) {
46 + if (val) {
47 + buffer_sprintf(wb, "%s\n\t\t\t\"%s\": \"%s\"", (hasprev)?",":"", var, val);
48 + return 1;
49 + } else {
50 + return hasprev;
51 + }
52 +}
53
54 +/**
55 + * Silencer to JSON
56 + *
57 + * Write the silencer values using JSON format inside a buffer.
58 + *
59 + * @param wb is the buffer to write the silencers.
60 + */
61 +void health_silencers2json(BUFFER *wb) {
62 + buffer_sprintf(wb, "{\n\t\"all\": %s,"
63 + "\n\t\"type\": \"%s\","
64 + "\n\t\"silencers\": [",
65 + (silencers->all_alarms)?"true":"false",
66 + (silencers->stype == STYPE_NONE)?"None":((silencers->stype == STYPE_DISABLE_ALARMS)?"DISABLE":"SILENCE"));
67 +
68 + SILENCER *silencer;
69 + int i = 0, j = 0;
70 + for(silencer = silencers->silencers; silencer ; silencer = silencer->next) {
71 + if(likely(i)) buffer_strcat(wb, ",");
72 + buffer_strcat(wb, "\n\t\t{");
73 + j=health_silencers2json_entry(wb, HEALTH_ALARM_KEY, silencer->alarms, j);
74 + j=health_silencers2json_entry(wb, HEALTH_CHART_KEY, silencer->charts, j);
75 + j=health_silencers2json_entry(wb, HEALTH_CONTEXT_KEY, silencer->contexts, j);
76 + j=health_silencers2json_entry(wb, HEALTH_HOST_KEY, silencer->hosts, j);
77 + health_silencers2json_entry(wb, HEALTH_FAMILIES_KEY, silencer->families, j);
78 + j=0;
79 + buffer_strcat(wb, "\n\t\t}");
80 + i++;
81 + }
82 + if(likely(i)) buffer_strcat(wb, "\n\t");
83 + buffer_strcat(wb, "]\n}\n");
84 +}
85
86 +/**
87 + * Silencer to FILE
88 + *
89 + * Write the sliencer buffer to a file.
90 + * @param wb
91 + */
92 +void health_silencers2file(BUFFER *wb) {
93 + if (wb->len == 0) return;
94 +
95 + FILE *fd = fopen(silencers_filename, "wb");
96 + if(fd) {
97 + size_t written = (size_t)fprintf(fd, "%s", wb->buffer) ;
98 + if (written == wb->len ) {
99 + info("Silencer changes written to %s", silencers_filename);
100 + }
101 + fclose(fd);
102 + return;
103 + }
104 + error("Silencer changes could not be written to %s. Error %s", silencers_filename, strerror(errno));
105 +}
106 +
107 +/**
108 + * Request V1 MGMT Health
109 + *
110 + * Function called by api to management the health.
111 + *
112 + * @param host main structure with client information!
113 + * @param w is the structure with all information of the client request.
114 + * @param url is the url that netdata is working
115 + *
116 + * @return It returns 200 on success and another code otherwise.
117 + */
118 int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, char *url) {
119 int ret = 400;
120 (void) host;
121
40 -
41 -
122 BUFFER *wb = w->response.data;
123 buffer_flush(wb);
124 wb->contenttype = CT_TEXT_PLAIN;
125
126 buffer_flush(w->response.data);
127
48 - static uint32_t
49 - hash_alarm = 0,
50 - hash_template = 0,
51 - hash_chart = 0,
52 - hash_context = 0,
53 - hash_host = 0,
54 - hash_families = 0;
55 -
56 - if (unlikely(!hash_alarm)) {
57 - hash_alarm = simple_uhash(HEALTH_ALARM_KEY);
58 - hash_template = simple_uhash(HEALTH_TEMPLATE_KEY);
59 - hash_chart = simple_uhash(HEALTH_CHART_KEY);
60 - hash_context = simple_uhash(HEALTH_CONTEXT_KEY);
61 - hash_host = simple_uhash(HEALTH_HOST_KEY);
62 - hash_families = simple_uhash(HEALTH_FAMILIES_KEY);
63 - }
64 -
128 + //Local instance of the silencer
129 SILENCER *silencer = NULL;
130 + int config_changed = 1;
131
132 if (!w->auth_bearer_token) {
133 buffer_strcat(wb, HEALTH_CMDAPI_MSG_AUTHERROR);
@@ -85,6 +150,7 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c
150
151 // name and value are now the parameters
152 if (!strcmp(key, "cmd")) {
153 + //In this "if" we are working with the global silencers.
154 if (!strcmp(value, HEALTH_CMDAPI_CMD_SILENCEALL)) {
155 silencers->all_alarms = 1;
156 silencers->stype = STYPE_SILENCE_NOTIFICATIONS;
@@ -105,50 +171,19 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c
171 free_silencers(silencers->silencers);
172 silencers->silencers = NULL;
173 buffer_strcat(wb, HEALTH_CMDAPI_MSG_RESET);
174 + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_LIST)) {
175 + w->response.data->contenttype = CT_APPLICATION_JSON;
176 + health_silencers2json(wb);
177 + config_changed=0;
178 }
179 } else {
110 - uint32_t hash = simple_uhash(key);
111 - if (unlikely(silencer == NULL)) {
112 - if (
113 - (hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) ||
114 - (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY)) ||
115 - (hash == hash_chart && !strcasecmp(key, HEALTH_CHART_KEY)) ||
116 - (hash == hash_context && !strcasecmp(key, HEALTH_CONTEXT_KEY)) ||
117 - (hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) ||
118 - (hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY))
119 - ) {
120 - silencer = create_silencer();
121 - }
122 - }
123 -
124 - if (hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) {
125 - silencer->alarms = strdupz(value);
126 - silencer->alarms_pattern = simple_pattern_create(silencer->alarms, NULL, SIMPLE_PATTERN_EXACT);
127 - } else if (hash == hash_chart && !strcasecmp(key, HEALTH_CHART_KEY)) {
128 - silencer->charts = strdupz(value);
129 - silencer->charts_pattern = simple_pattern_create(silencer->charts, NULL, SIMPLE_PATTERN_EXACT);
130 - } else if (hash == hash_context && !strcasecmp(key, HEALTH_CONTEXT_KEY)) {
131 - silencer->contexts = strdupz(value);
132 - silencer->contexts_pattern = simple_pattern_create(silencer->contexts, NULL, SIMPLE_PATTERN_EXACT);
133 - } else if (hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) {
134 - silencer->hosts = strdupz(value);
135 - silencer->hosts_pattern = simple_pattern_create(silencer->hosts, NULL, SIMPLE_PATTERN_EXACT);
136 - } else if (hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY)) {
137 - silencer->families = strdupz(value);
138 - silencer->families_pattern = simple_pattern_create(silencer->families, NULL, SIMPLE_PATTERN_EXACT);
139 - } else {
140 - buffer_strcat(wb, HEALTH_CMDAPI_MSG_INVALID_KEY);
141 - }
180 + //In this else we work with local silencer
181 + silencer = health_silencers_addparam(silencer,key,value);
182 }
143 -
183 }
184 +
185 if (likely(silencer)) {
146 - // Add the created instance to the linked list in silencers
147 - silencer->next = silencers->silencers;
148 - silencers->silencers = silencer;
149 - debug(D_HEALTH, "HEALTH command API: Added silencer %s:%s:%s:%s:%s", silencer->alarms,
150 - silencer->charts, silencer->contexts, silencer->hosts, silencer->families
151 - );
186 + health_silencers_add(silencer);
187 buffer_strcat(wb, HEALTH_CMDAPI_MSG_ADDED);
188 if (silencers->stype == STYPE_NONE) {
189 buffer_strcat(wb, HEALTH_CMDAPI_MSG_STYPEWARNING);
@@ -162,5 +197,10 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c
197 }
198 w->response.data = wb;
199 buffer_no_cacheable(w->response.data);
200 + if (ret == 200 && config_changed) {
201 + BUFFER *jsonb = buffer_create(200);
202 + health_silencers2json(jsonb);
203 + health_silencers2file(jsonb);
204 + }
205 return ret;
206 }
web/api/health/health_cmdapi.h
+1 -1
@@ -12,6 +12,7 @@
12 #define HEALTH_CMDAPI_CMD_SILENCE "SILENCE"
13 #define HEALTH_CMDAPI_CMD_DISABLE "DISABLE"
14 #define HEALTH_CMDAPI_CMD_RESET "RESET"
15 +#define HEALTH_CMDAPI_CMD_LIST "LIST"
16
17 #define HEALTH_CMDAPI_MSG_AUTHERROR "Auth Error\n"
18 #define HEALTH_CMDAPI_MSG_SILENCEALL "All alarm notifications are silenced\n"
@@ -20,7 +21,6 @@
21 #define HEALTH_CMDAPI_MSG_DISABLE "Health checks disabled for alarms matching the selectors\n"
22 #define HEALTH_CMDAPI_MSG_SILENCE "Alarm notifications silenced for alarms matching the selectors\n"
23 #define HEALTH_CMDAPI_MSG_ADDED "Alarm selector added\n"
23 -#define HEALTH_CMDAPI_MSG_INVALID_KEY "Invalid key. Ignoring it.\n"
24 #define HEALTH_CMDAPI_MSG_STYPEWARNING "WARNING: Added alarm selector to silence/disable alarms without a SILENCE or DISABLE command.\n"
25 #define HEALTH_CMDAPI_MSG_NOSELECTORWARNING "WARNING: SILENCE or DISABLE command is ineffective without defining any alarm selectors.\n"
26
web/api/netdata-swagger.json
+3 -2
@@ -664,7 +664,7 @@
664 {
665 "name": "cmd",
666 "in": "query",
667 - "description": "DISABLE ALL: No alarm criteria are evaluated, nothing is written in the alarm log. SILENCE ALL: No notifications are sent. RESET: Return to the default state. DISABLE/SILENCE: Set the mode to be used for the alarms matching the criteria of the alarm selectors.",
667 + "description": "DISABLE ALL: No alarm criteria are evaluated, nothing is written in the alarm log. SILENCE ALL: No notifications are sent. RESET: Return to the default state. DISABLE/SILENCE: Set the mode to be used for the alarms matching the criteria of the alarm selectors. LIST: Show active configuration.",
668 "required": false,
669 "type": "string",
670 "enum": [
@@ -672,7 +672,8 @@
672 "SILENCE ALL",
673 "DISABLE",
674 "SILENCE",
675 - "RESET"
675 + "RESET",
676 + "LIST"
677 ]
678 },
679 {
web/api/netdata-swagger.yaml
+2 -2
@@ -437,10 +437,10 @@ paths:
437 parameters:
438 - name: cmd
439 in: query
440 - description: 'DISABLE ALL: No alarm criteria are evaluated, nothing is written in the alarm log. SILENCE ALL: No notifications are sent. RESET: Return to the default state. DISABLE/SILENCE: Set the mode to be used for the alarms matching the criteria of the alarm selectors.'
440 + description: 'DISABLE ALL: No alarm criteria are evaluated, nothing is written in the alarm log. SILENCE ALL: No notifications are sent. RESET: Return to the default state. DISABLE/SILENCE: Set the mode to be used for the alarms matching the criteria of the alarm selectors. LIST: Show active configuration.'
441 required: false
442 type: string
443 - enum: ['DISABLE ALL', 'SILENCE ALL', 'DISABLE', 'SILENCE', 'RESET']
443 + enum: ['DISABLE ALL', 'SILENCE ALL', 'DISABLE', 'SILENCE', 'RESET', 'LIST']
444 - name: alarm
445 in: query
446 description: 'The expression provided will match both `alarm` and `template` names.'