@cryptotaxi247 / netdata-1 / commits / c56e086ba

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

This PR was created to fix #3414, here I am completing the job initiated by Christopher, among the newest features that we are bring we have JSON inside the core - We are bringing to the core the capacity to work with JSON files, this is available either using the JSON-C library case it is present in the system or using JSMN library that was incorporated to our core. The preference is to have JSON-C, because it is a more complete library, but case the user does not have the library installed we are keeping the JSMN for we do not lose the feature. Health LIST - We are bringing more one command to the Health API, now with the LIST it is possible to get in JSON format the alarms active with Netdata. Health reorganized - Previously we had duplicated code in different files, this PR is fixing this (Thanks @cakrit !), the Health is now better organized. Removing memory leak - The first implementation of the json.c was creating SILENCERS without to link it in anywhere. Now it has been linked properly. Script updated - We are bringing some changes to the script that tests the Health. This PR also fixes the race condition created by the previous new position of the SILENCERS creation, I had to move it to daemon/main.c, because after various tests, it was confirmed that the error could happen in different parts of the code, case it was not initialized before the threads starts. Component Name health directory health-cmd Additional Information Fixes #6356 and #3414

thiagoftsm committed Jul 1, 2019 at 19:07 UTC c56e086ba341041611c084e7a38d68ef678d6d6f
40 files changed +1642 -121
CMakeLists.txt
+15
@@ -130,6 +130,15 @@ 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 # -----------------------------------------------------------------------------
143 # Detect libcap
144
@@ -305,6 +314,12 @@ set(LIBNETDATA_FILES
314 libnetdata/threads/threads.h
315 libnetdata/url/url.c
316 libnetdata/url/url.h
317 + libnetdata/json/json.c
318 + libnetdata/json/json.h
319 + libnetdata/json/jsmn.c
320 + libnetdata/json/jsmn.h
321 + libnetdata/health/health.c
322 + libnetdata/health/health.h
323 libnetdata/socket/security.c
324 libnetdata/socket/security.h)
325
Makefile.am
+7
@@ -161,6 +161,12 @@ LIBNETDATA_FILES = \
161 libnetdata/threads/threads.h \
162 libnetdata/url/url.c \
163 libnetdata/url/url.h \
164 + libnetdata/json/json.c \
165 + libnetdata/json/json.h \
166 + libnetdata/json/jsmn.c \
167 + libnetdata/json/jsmn.h \
168 + libnetdata/health/health.c \
169 + libnetdata/health/health.h \
170 $(NULL)
171
172 APPS_PLUGIN_FILES = \
@@ -512,6 +518,7 @@ NETDATA_COMMON_LIBS = \
518 $(OPTIONAL_LZ4_LIBS) \
519 $(OPTIONAL_JUDY_LIBS) \
520 $(OPTIONAL_SSL_LIBS) \
521 + $(OPTIONAL_JSONC_LIBS) \
522 $(NULL)
523
524 sbin_PROGRAMS += netdata
configure.ac
+38
@@ -148,6 +148,12 @@ AC_ARG_ENABLE(
148 ,
149 [enable_dbengine="detect"]
150 )
151 +AC_ARG_ENABLE(
152 + [jsonc],
153 + [AS_HELP_STRING([--enable-jsonc], [Enable JSON-C support @<:@default autodetect@:>@])],
154 + ,
155 + [enable_jsonc="detect"]
156 +)
157
158 # -----------------------------------------------------------------------------
159 # netdata required checks
@@ -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,21 @@ 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 # compiler options
421
@@ -975,6 +1010,7 @@ AC_SUBST([OPTIONAL_UV_LIBS])
1010 AC_SUBST([OPTIONAL_LZ4_LIBS])
1011 AC_SUBST([OPTIONAL_JUDY_LIBS])
1012 AC_SUBST([OPTIONAL_SSL_LIBS])
1013 +AC_SUBST([OPTIONAL_JSONC_LIBS])
1014 AC_SUBST([OPTIONAL_NFACCT_CFLAGS])
1015 AC_SUBST([OPTIONAL_NFACCT_LIBS])
1016 AC_SUBST([OPTIONAL_ZLIB_CFLAGS])
@@ -1051,6 +1087,8 @@ AC_CONFIG_FILES([
1087 libnetdata/storage_number/Makefile
1088 libnetdata/threads/Makefile
1089 libnetdata/url/Makefile
1090 + libnetdata/json/Makefile
1091 + libnetdata/health/Makefile
1092 registry/Makefile
1093 streaming/Makefile
1094 system/Makefile
daemon/main.c
+11
@@ -774,6 +774,12 @@ void send_statistics( const char *action, const char *action_result, const char
774 freez(command_to_run);
775 }
776
777 +void set_silencers_filename() {
778 + char filename[FILENAME_MAX + 1];
779 + snprintfz(filename, FILENAME_MAX, "%s/health.silencers.json", netdata_configured_varlib_dir);
780 + silencers_filename = config_get(CONFIG_SECTION_HEALTH, "silencers file", filename);
781 +}
782 +
783 int main(int argc, char **argv) {
784 int i;
785 int config_loaded = 0;
@@ -1101,6 +1107,11 @@ int main(int argc, char **argv) {
1107 security_init();
1108 #endif
1109
1110 + // --------------------------------------------------------------------
1111 + // This is the safest place to start the SILENCERS structure
1112 + set_silencers_filename();
1113 + health_initialize_global_silencers();
1114 +
1115 // --------------------------------------------------------------------
1116 // setup process signals
1117
health/health.c
+84 -6
@@ -13,18 +13,69 @@ unsigned int default_health_enabled = 1;
13 // ----------------------------------------------------------------------------
14 // health initialization
15
16 +/**
17 + * User Config directory
18 + *
19 + * Get the config directory for health and return it.
20 + *
21 + * @return a pointer to the user config directory
22 + */
23 inline char *health_user_config_dir(void) {
24 char buffer[FILENAME_MAX + 1];
25 snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_user_config_dir);
26 return config_get(CONFIG_SECTION_HEALTH, "health configuration directory", buffer);
27 }
28
29 +/**
30 + * Stock Config Directory
31 + *
32 + * Get the Stock config directory and return it.
33 + *
34 + * @return a pointer to the stock config directory.
35 + */
36 inline char *health_stock_config_dir(void) {
37 char buffer[FILENAME_MAX + 1];
38 snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_stock_config_dir);
39 return config_get(CONFIG_SECTION_HEALTH, "stock health configuration directory", buffer);
40 }
41
42 +/**
43 + * Silencers init
44 + *
45 + * Function used to initialize the silencer structure.
46 + */
47 +void health_silencers_init(void) {
48 + struct stat statbuf;
49 + if (!stat(silencers_filename,&statbuf)) {
50 + off_t length = statbuf.st_size;
51 + if (length && length < HEALTH_SILENCERS_MAX_FILE_LEN) {
52 + FILE *fd = fopen(silencers_filename, "r");
53 + if (fd) {
54 + char *str = mallocz((length+1)* sizeof(char));
55 + if(str) {
56 + fread(str, sizeof(char), length, fd);
57 + str[length] = 0x00;
58 + json_parse(str, NULL, health_silencers_json_read_callback);
59 + freez(str);
60 + info("Parsed health silencers file %s", silencers_filename);
61 + }
62 + fclose(fd);
63 + } else {
64 + error("Cannot open the file %s",silencers_filename);
65 + }
66 + } else {
67 + 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);
68 + }
69 + } else {
70 + error("Cannot open the file %s",silencers_filename);
71 + }
72 +}
73 +
74 +/**
75 + * Health Init
76 + *
77 + * Initialize the health thread.
78 + */
79 void health_init(void) {
80 debug(D_HEALTH, "Health configuration initializing");
81
@@ -32,11 +83,20 @@ void health_init(void) {
83 debug(D_HEALTH, "Health is disabled.");
84 return;
85 }
86 +
87 + health_silencers_init();
88 }
89
90 // ----------------------------------------------------------------------------
91 // re-load health configuration
92
93 +/**
94 + * Reload host
95 + *
96 + * Reload configuration for a specific host.
97 + *
98 + * @param host the structure of the host that the function will reload the configuration.
99 + */
100 void health_reload_host(RRDHOST *host) {
101 if(unlikely(!host->health_enabled))
102 return;
@@ -84,6 +144,11 @@ void health_reload_host(RRDHOST *host) {
144 rrdhost_unlock(host);
145 }
146
147 +/**
148 + * Reload
149 + *
150 + * Reload the host configuration for all hosts.
151 + */
152 void health_reload(void) {
153
154 rrd_rdlock();
@@ -430,6 +495,16 @@ SILENCE_TYPE check_silenced(RRDCALC *rc, char* host, SILENCERS *silencers) {
495 return STYPE_NONE;
496 }
497
498 +/**
499 + * Update Disabled Silenced
500 + *
501 + * Update the variable rrdcalc_flags of the structure RRDCALC according with the values of the host structure
502 + *
503 + * @param host structure that contains information about the host monitored.
504 + * @param rc structure with information about the alarm
505 + *
506 + * @return It returns 1 case rrdcalc_flags is DISABLED or 0 otherwise
507 + */
508 int update_disabled_silenced(RRDHOST *host, RRDCALC *rc) {
509 uint32_t rrdcalc_flags_old = rc->rrdcalc_flags;
510 // Clear the flags
@@ -459,6 +534,15 @@ int update_disabled_silenced(RRDHOST *host, RRDCALC *rc) {
534 return 0;
535 }
536
537 +/**
538 + * Health Main
539 + *
540 + * The main thread of the health system. In this function all the alarms will be processed.
541 + *
542 + * @param ptr is a pointer to the netdata_static_thread structure.
543 + *
544 + * @return It always returns NULL
545 + */
546 void *health_main(void *ptr) {
547 netdata_thread_cleanup_push(health_main_cleanup, ptr);
548
@@ -469,12 +553,6 @@ void *health_main(void *ptr) {
553 time_t hibernation_delay = config_get_number(CONFIG_SECTION_HEALTH, "postpone alarms during hibernation for seconds", 60);
554
555 unsigned int loop = 0;
472 -
473 - silencers = mallocz(sizeof(SILENCERS));
474 - silencers->all_alarms=0;
475 - silencers->stype=STYPE_NONE;
476 - silencers->silencers=NULL;
477 -
556 while(!netdata_exit) {
557 loop++;
558 debug(D_HEALTH, "Health monitoring iteration no %u started", loop);
health/health.h
+2 -40
@@ -35,16 +35,7 @@ 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 #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"
39 #define HEALTH_EVERY_KEY "every"
40 #define HEALTH_GREEN_KEY "green"
41 #define HEALTH_RED_KEY "red"
@@ -57,38 +48,9 @@ extern unsigned int default_health_enabled;
48 #define HEALTH_DELAY_KEY "delay"
49 #define HEALTH_OPTIONS_KEY "options"
50
60 -typedef struct silencer {
61 - char *alarms;
62 - SIMPLE_PATTERN *alarms_pattern;
51 +#define HEALTH_SILENCERS_MAX_FILE_LEN 10000
52
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;
90 -
91 -SILENCERS *silencers;
53 +char *silencers_filename;
54
55 extern void health_init(void);
56 extern void *health_main(void *ptr);
health/health_config.c
+1 -1
@@ -490,7 +490,7 @@ static int health_readfile(const char *filename, void *data) {
490 if(append < HEALTH_CONF_MAX_LINE)
491 continue;
492 else {
493 - error("Health configuration has too long muli-line at line %zu of file '%s'.", line, filename);
493 + error("Health configuration has too long multi-line at line %zu of file '%s'.", line, filename);
494 }
495 }
496 append = 0;
libnetdata/Makefile.am
+2
@@ -11,6 +11,8 @@ SUBDIRS = \
11 config \
12 dictionary \
13 eval \
14 + json \
15 + health \
16 locks \
17 log \
18 popen \
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
+170
@@ -0,0 +1,170 @@
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 +}
155 +
156 +/**
157 + * Initialize Global Silencers
158 + *
159 + * Initialize the silencer for the whole netdata system.
160 + *
161 + * @return It returns 0 on success and -1 otherwise
162 + */
163 +int health_initialize_global_silencers() {
164 + silencers = mallocz(sizeof(SILENCERS));
165 + silencers->all_alarms=0;
166 + silencers->stype=STYPE_NONE;
167 + silencers->silencers=NULL;
168 +
169 + return 0;
170 +}
\ No newline at end of file
libnetdata/health/health.h new
+55
@@ -0,0 +1,55 @@
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 +extern int health_initialize_global_silencers();
54 +
55 +#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
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
+49
@@ -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
@@ -155,6 +193,17 @@ http://localhost/api/v1/manage/health?families=cpu1 cpu2
193 - "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.
194 - "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.
195
196 +### Persistence
197 +
198 +From netdata v1.16.0 and beyond, the silencers configuration is persisted to disk and loaded when netdata starts.
199 +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.
200 +The file's location is configurable in `netdata.conf`. The default is shown below:
201 +
202 +```
203 +[health]
204 + # silencers file = /var/lib/netdata/health.silencers.json
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
+105 -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);
@@ -105,50 +170,17 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c
170 free_silencers(silencers->silencers);
171 silencers->silencers = NULL;
172 buffer_strcat(wb, HEALTH_CMDAPI_MSG_RESET);
173 + } else if (!strcmp(value, HEALTH_CMDAPI_CMD_LIST)) {
174 + w->response.data->contenttype = CT_APPLICATION_JSON;
175 + health_silencers2json(wb);
176 + config_changed=0;
177 }
178 } 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 - }
179 + silencer = health_silencers_addparam(silencer, key, value);
180 }
143 -
181 }
182 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 - );
183 + health_silencers_add(silencer);
184 buffer_strcat(wb, HEALTH_CMDAPI_MSG_ADDED);
185 if (silencers->stype == STYPE_NONE) {
186 buffer_strcat(wb, HEALTH_CMDAPI_MSG_STYPEWARNING);
@@ -162,5 +194,11 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c
194 }
195 w->response.data = wb;
196 buffer_no_cacheable(w->response.data);
197 + if (ret == 200 && config_changed) {
198 + BUFFER *jsonb = buffer_create(200);
199 + health_silencers2json(jsonb);
200 + health_silencers2file(jsonb);
201 + }
202 +
203 return ret;
204 }
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.'