summaryrefslogtreecommitdiffstats
path: root/src/health
diff options
context:
space:
mode:
Diffstat (limited to 'src/health')
-rw-r--r--src/health/REFERENCE.md19
-rw-r--r--src/health/health.h4
-rw-r--r--src/health/health_config.c206
-rw-r--r--src/health/health_dyncfg.c141
-rw-r--r--src/health/health_event_loop.c25
-rw-r--r--src/health/health_json.c8
-rw-r--r--src/health/health_prototypes.c121
-rw-r--r--src/health/health_prototypes.h40
-rw-r--r--src/health/health_variable.c20
-rw-r--r--src/health/rrdcalc.c7
-rw-r--r--src/health/schema.d/health:alert:prototype.json615
11 files changed, 839 insertions, 367 deletions
diff --git a/src/health/REFERENCE.md b/src/health/REFERENCE.md
index 478fd9c9dc..85f1d22817 100644
--- a/src/health/REFERENCE.md
+++ b/src/health/REFERENCE.md
@@ -385,7 +385,7 @@ This line makes a database lookup to find a value. This result of this lookup is
The format is:
```yaml
-lookup: METHOD AFTER [at BEFORE] [every DURATION] [OPTIONS] [of DIMENSIONS]
+lookup: METHOD(GROUPING OPTIONS) AFTER [at BEFORE] [every DURATION] [OPTIONS] [of DIMENSIONS]
```
The full [database query API](https://github.com/netdata/netdata/blob/master/src/web/api/queries/README.md) is supported. In short:
@@ -393,6 +393,8 @@ The full [database query API](https://github.com/netdata/netdata/blob/master/src
- `METHOD` is one of the available [grouping methods](https://github.com/netdata/netdata/blob/master/src/web/api/queries/README.md#grouping-methods) such as `average`, `min`, `max` etc.
This is required.
+ - `GROUPING OPTIONS` are optional and can have the form `CONDITION VALUE`, where `CONDITION` is `!=`, `=`, `<=`, `<`, `>`, `>=` and `VALUE` is a number. The `CONDITION` and `VALUE` are required for `countif`, while `VALUE` is used by `percentile`, `trimmed_mean` and `trimmed_median`.
+
- `AFTER` is a relative number of seconds, but it also accepts a single letter for changing
the units, like `-1s` = 1 second in the past, `-1m` = 1 minute in the past, `-1h` = 1 hour
in the past, `-1d` = 1 day in the past. You need a negative number (i.e. how far in the past
@@ -404,8 +406,19 @@ The full [database query API](https://github.com/netdata/netdata/blob/master/src
- `every DURATION` sets the updated frequency of the lookup (supports single letter units as
above too).
-- `OPTIONS` is a space separated list of `percentage`, `absolute`, `min2max`, `unaligned`,
- `match-ids`, `match-names`. Check the [badges](https://github.com/netdata/netdata/blob/master/src/web/api/badges/README.md) documentation for more info.
+- `OPTIONS` is a space separated list of `percentage`, `absolute`, `min`, `max`, `average`, `sum`,
+ `min2max`, `unaligned`, `match-ids`, `match-names`.
+
+ - `percentage` during time-aggregation, calculate the percentage of the selected dimensions over the total of all dimensions.
+ - `absolute` during time-aggregation, turns all sample values positive before using them.
+ - `min` after time-aggregation of each dimension, return the minimum of all dimensions.
+ - `max` after time-aggregation of each dimension, return the maximum of all dimensions.
+ - `average` after time-aggregation of each dimension, return the average of all dimensions.
+ - `sum` after time-aggregation of each dimension, return the sum of all dimensions (this is the default).
+ - `min2max` after time-aggregation of each dimension, return the delta between the min and the max of the dimensions.
+ - `unligned` prevents shifting the query window to multiples of the query duration.
+ - `match-ids` matches the dimensions based on their IDs (the default is enabled, give `match-names` to disable).
+ - `match-names` matches the dimension based on their names (the default is enabled, give `match-ids` to disable).
- `of DIMENSIONS` is optional and has to be the last parameter. Dimensions have to be separated
by `,` or `|`. The space characters found in dimensions will be kept as-is (a few dimensions
diff --git a/src/health/health.h b/src/health/health.h
index 882309cae1..2a1dd6c656 100644
--- a/src/health/health.h
+++ b/src/health/health.h
@@ -20,6 +20,10 @@ typedef enum __attribute__((packed)) {
HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION = 0x80000000,
} HEALTH_ENTRY_FLAGS;
+#define RRDR_OPTIONS_DATA_SOURCES (RRDR_OPTION_PERCENTAGE|RRDR_OPTION_ANOMALY_BIT)
+#define RRDR_OPTIONS_DIMS_AGGREGATION (RRDR_OPTION_DIMS_MIN|RRDR_OPTION_DIMS_MAX|RRDR_OPTION_DIMS_AVERAGE|RRDR_OPTION_DIMS_MIN2MAX)
+#define RRDR_OPTIONS_REMOVE_OVERLAPPING(options) ((options) &= ~(RRDR_OPTIONS_DIMS_AGGREGATION|RRDR_OPTIONS_DATA_SOURCES))
+
void health_entry_flags_to_json_array(BUFFER *wb, const char *key, HEALTH_ENTRY_FLAGS flags);
#ifndef HEALTH_LISTEN_PORT
diff --git a/src/health/health_config.c b/src/health/health_config.c
index 948f263a0b..d8c735c3f6 100644
--- a/src/health/health_config.c
+++ b/src/health/health_config.c
@@ -162,25 +162,21 @@ static inline int isvariableterm(const char s) {
return 1;
}
-static inline int health_parse_db_lookup(
- size_t line, const char *filename, char *string,
- RRDR_TIME_GROUPING *group_method, int *after, int *before, int *every,
- RRDR_OPTIONS *options, STRING **dimensions
-) {
- netdata_log_debug(D_HEALTH, "Health configuration parsing database lookup %zu@%s: %s", line, filename, string);
-
- if(*dimensions) string_freez(*dimensions);
- *dimensions = NULL;
- *after = 0;
- *before = 0;
- *every = 0;
- *options = 0;
+static inline int health_parse_db_lookup(size_t line, const char *filename, char *string, struct rrd_alert_config *ac) {
+ if(ac->dimensions) string_freez(ac->dimensions);
+ ac->dimensions = NULL;
+ ac->after = 0;
+ ac->before = 0;
+ ac->update_every = 0;
+ ac->options = 0;
+ ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_EQUAL;
+ ac->time_group_value = NAN;
char *s = string, *key;
// first is the group method
key = s;
- while(*s && !isspace(*s)) s++;
+ while(*s && !isspace(*s) && *s != '(') s++;
while(*s && isspace(*s)) *s++ = '\0';
if(!*s) {
netdata_log_error("Health configuration invalid chart calculation at line %zu of file '%s': expected group method followed by the 'after' time, but got '%s'",
@@ -188,25 +184,103 @@ static inline int health_parse_db_lookup(
return 0;
}
- if((*group_method = time_grouping_parse(key, RRDR_GROUPING_UNDEFINED)) == RRDR_GROUPING_UNDEFINED) {
+ bool group_options = false;
+ if(*s == '(') {
+ *s++ = '\0';
+ group_options = true;
+ }
+
+ if((ac->time_group = time_grouping_parse(key, RRDR_GROUPING_UNDEFINED)) == RRDR_GROUPING_UNDEFINED) {
netdata_log_error("Health configuration at line %zu of file '%s': invalid group method '%s'",
line, filename, key);
return 0;
}
+ if(group_options) {
+ if(*s == '!') {
+ s++;
+ if(*s == '=') s++;
+ ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_NOT_EQUAL;
+ }
+ else if(*s == '<') {
+ s++;
+ if(*s == '>') {
+ s++;
+ ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_NOT_EQUAL;
+ }
+ else if(*s == '=') {
+ s++;
+ ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER_EQUAL;
+ }
+ else
+ ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER;
+ }
+ else if(*s == '>') {
+ if(*s == '=') {
+ s++;
+ ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS_EQUAL;
+ }
+ else
+ ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS;
+ }
+
+ while(*s && isspace(*s)) s++;
+
+ if(*s) {
+ if(isdigit(*s) || *s == '.') {
+ ac->time_group_value = str2ndd(s, &s);
+ while(s && *s && isspace(*s)) s++;
+
+ if(!s || *s != ')') {
+ netdata_log_error("Health configuration at line %zu of file '%s': missing closing parenthesis after number in aggregation method on '%s'",
+ line, filename, key);
+ return 0;
+ }
+ }
+ }
+ else if(*s != ')') {
+ netdata_log_error("Health configuration at line %zu of file '%s': missing closing parenthesis after method on '%s'",
+ line, filename, key);
+ return 0;
+ }
+
+ s++;
+ }
+
+ switch (ac->time_group) {
+ default:
+ break;
+
+ case RRDR_GROUPING_COUNTIF:
+ if(isnan(ac->time_group_value))
+ ac->time_group_value = 0;
+ break;
+
+ case RRDR_GROUPING_TRIMMED_MEAN:
+ case RRDR_GROUPING_TRIMMED_MEDIAN:
+ if(isnan(ac->time_group_value))
+ ac->time_group_value = 5;
+ break;
+
+ case RRDR_GROUPING_PERCENTILE:
+ if(isnan(ac->time_group_value))
+ ac->time_group_value = 95;
+ break;
+ }
+
// then is the 'after' time
key = s;
while(*s && !isspace(*s)) s++;
while(*s && isspace(*s)) *s++ = '\0';
- if(!config_parse_duration(key, after)) {
+ if(!config_parse_duration(key, &ac->after)) {
netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' after group method",
line, filename, key);
return 0;
}
// sane defaults
- *every = ABS(*after);
+ ac->update_every = ABS(ac->after);
// now we may have optional parameters
while(*s) {
@@ -220,7 +294,7 @@ static inline int health_parse_db_lookup(
while(*s && !isspace(*s)) s++;
while(*s && isspace(*s)) *s++ = '\0';
- if (!config_parse_duration(value, before)) {
+ if (!config_parse_duration(value, &ac->before)) {
netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
line, filename, value, key);
}
@@ -230,34 +304,46 @@ static inline int health_parse_db_lookup(
while(*s && !isspace(*s)) s++;
while(*s && isspace(*s)) *s++ = '\0';
- if (!config_parse_duration(value, every)) {
+ if (!config_parse_duration(value, &ac->update_every)) {
netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
line, filename, value, key);
}
}
else if(!strcasecmp(key, "absolute") || !strcasecmp(key, "abs") || !strcasecmp(key, "absolute_sum")) {
- *options |= RRDR_OPTION_ABSOLUTE;
+ ac->options |= RRDR_OPTION_ABSOLUTE;
}
else if(!strcasecmp(key, "min2max")) {
- *options |= RRDR_OPTION_MIN2MAX;
+ ac->options |= RRDR_OPTION_DIMS_MIN2MAX;
+ }
+ else if(!strcasecmp(key, "average")) {
+ ac->options |= RRDR_OPTION_DIMS_AVERAGE;
+ }
+ else if(!strcasecmp(key, "min")) {
+ ac->options |= RRDR_OPTION_DIMS_MIN;
+ }
+ else if(!strcasecmp(key, "max")) {
+ ac->options |= RRDR_OPTION_DIMS_MAX;
+ }
+ else if(!strcasecmp(key, "sum")) {
+ ;
}
else if(!strcasecmp(key, "null2zero")) {
- *options |= RRDR_OPTION_NULL2ZERO;
+ ac->options |= RRDR_OPTION_NULL2ZERO;
}
else if(!strcasecmp(key, "percentage")) {
- *options |= RRDR_OPTION_PERCENTAGE;
+ ac->options |= RRDR_OPTION_PERCENTAGE;
}
else if(!strcasecmp(key, "unaligned")) {
- *options |= RRDR_OPTION_NOT_ALIGNED;
+ ac->options |= RRDR_OPTION_NOT_ALIGNED;
}
else if(!strcasecmp(key, "anomaly-bit")) {
- *options |= RRDR_OPTION_ANOMALY_BIT;
+ ac->options |= RRDR_OPTION_ANOMALY_BIT;
}
else if(!strcasecmp(key, "match-ids") || !strcasecmp(key, "match_ids")) {
- *options |= RRDR_OPTION_MATCH_IDS;
+ ac->options |= RRDR_OPTION_MATCH_IDS;
}
else if(!strcasecmp(key, "match-names") || !strcasecmp(key, "match_names")) {
- *options |= RRDR_OPTION_MATCH_NAMES;
+ ac->options |= RRDR_OPTION_MATCH_NAMES;
}
else if(!strcasecmp(key, "of")) {
char *find = NULL;
@@ -266,7 +352,7 @@ static inline int health_parse_db_lookup(
if(find) {
*find = '\0';
}
- *dimensions = string_strdupz(s);
+ ac->dimensions = string_strdupz(s);
}
if(!find) {
@@ -340,6 +426,46 @@ static inline void strip_quotes(char *s) {
}
}
+static void replace_green_red(RRD_ALERT_PROTOTYPE *ap, NETDATA_DOUBLE green, NETDATA_DOUBLE red) {
+ if(!isnan(green)) {
+ STRING *green_str = string_strdupz("green");
+ expression_hardcode_variable(ap->config.calculation, green_str, green);
+ expression_hardcode_variable(ap->config.warning, green_str, green);
+ expression_hardcode_variable(ap->config.critical, green_str, green);
+ string_freez(green_str);
+ }
+
+ if(!isnan(red)) {
+ STRING *red_str = string_strdupz("red");
+ expression_hardcode_variable(ap->config.calculation, red_str, red);
+ expression_hardcode_variable(ap->config.warning, red_str, red);
+ expression_hardcode_variable(ap->config.critical, red_str, red);
+ string_freez(red_str);
+ }
+}
+
+static void dims_grouping_from_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
+ if(ap->config.options & RRDR_OPTION_DIMS_MIN)
+ ap->config.dims_group = ALERT_LOOKUP_DIMS_MIN;
+ else if(ap->config.options & RRDR_OPTION_DIMS_MAX)
+ ap->config.dims_group = ALERT_LOOKUP_DIMS_MAX;
+ else if(ap->config.options & RRDR_OPTION_DIMS_MIN2MAX)
+ ap->config.dims_group = ALERT_LOOKUP_DIMS_MIN2MAX;
+ else if(ap->config.options & RRDR_OPTION_DIMS_AVERAGE)
+ ap->config.dims_group = ALERT_LOOKUP_DIMS_AVERAGE;
+ else
+ ap->config.dims_group = ALERT_LOOKUP_DIMS_SUM;
+}
+
+static void lookup_data_source_from_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
+ if(ap->config.options & RRDR_OPTION_PERCENTAGE)
+ ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_PERCENTAGES;
+ else if(ap->config.options & RRDR_OPTION_ANOMALY_BIT)
+ ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_ANOMALIES;
+ else
+ ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_SAMPLES;
+}
+
#define PARSE_HEALTH_CONFIG_LOG_DUPLICATE_STRING_MSG(ax, member) do { \
if(strcmp(string2str(ax->member), value) != 0) \
netdata_log_error( \
@@ -391,8 +517,6 @@ static inline void strip_quotes(char *s) {
} \
} while(0)
-
-
int health_readfile(const char *filename, void *data __maybe_unused, bool stock_config) {
netdata_log_debug(D_HEALTH, "Health configuration reading file '%s'", filename);
@@ -466,6 +590,8 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
RRD_ALERT_PROTOTYPE *ap = NULL;
struct rrd_alert_config *ac = NULL;
struct rrd_alert_match *am = NULL;
+ NETDATA_DOUBLE green = NAN;
+ NETDATA_DOUBLE red = NAN;
size_t line = 0, append = 0;
char *s;
@@ -522,6 +648,9 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
if((hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) || (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY))) {
if(ap) {
+ lookup_data_source_from_rrdr_options(ap);
+ dims_grouping_from_rrdr_options(ap);
+ replace_green_red(ap, green, red);
health_prototype_add(ap);
freez(ap);
}
@@ -544,8 +673,8 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
ap->match.is_template = (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY));
ap->config.source = health_source_file(line, filename);
ap->config.source_type = stock_config ? DYNCFG_SOURCE_TYPE_STOCK : DYNCFG_SOURCE_TYPE_USER;
- ap->config.green = NAN;
- ap->config.red = NAN;
+ green = NAN;
+ red = NAN;
ap->config.delay_multiplier = 1;
ap->config.warn_repeat_every = health_globals.config.default_warn_repeat_every;
ap->config.crit_repeat_every = health_globals.config.default_crit_repeat_every;
@@ -593,11 +722,7 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
PARSE_HEALTH_CONFIG_LINE_STRING(ac, type);
}
else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
- ac->lookup = string_strdupz(value);
- health_parse_db_lookup(line, filename, value,
- &ac->group, &ac->after, &ac->before,
- &ac->update_every, &ac->options,
- &ac->dimensions);
+ health_parse_db_lookup(line, filename, value, ac);
}
else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
if(!config_parse_duration(value, &ac->update_every))
@@ -608,7 +733,7 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
}
else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
char *e;
- ac->green = str2ndd(value, &e);
+ green = str2ndd(value, &e);
if(e && *e) {
netdata_log_error(
"Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
@@ -618,7 +743,7 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
}
else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
char *e;
- ac->red = str2ndd(value, &e);
+ red = str2ndd(value, &e);
if(e && *e) {
netdata_log_error(
"Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
@@ -705,6 +830,9 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
}
if(ap) {
+ lookup_data_source_from_rrdr_options(ap);
+ dims_grouping_from_rrdr_options(ap);
+ replace_green_red(ap, green, red);
health_prototype_add(ap);
freez(ap);
}
diff --git a/src/health/health_dyncfg.c b/src/health/health_dyncfg.c
index d5261773e1..933f038183 100644
--- a/src/health/health_dyncfg.c
+++ b/src/health/health_dyncfg.c
@@ -9,6 +9,50 @@ static void health_dyncfg_register_prototype(RRD_ALERT_PROTOTYPE *ap);
// ---------------------------------------------------------------------------------------------------------------------
// parse the json object of an alert definition
+static void dims_grouping_to_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
+ ap->config.options &= ~(RRDR_OPTIONS_DIMS_AGGREGATION);
+
+ switch(ap->config.dims_group) {
+ default:
+ case ALERT_LOOKUP_DIMS_SUM:
+ break;
+
+ case ALERT_LOOKUP_DIMS_AVERAGE:
+ ap->config.options |= RRDR_OPTION_DIMS_AVERAGE;
+ break;
+
+ case ALERT_LOOKUP_DIMS_MIN:
+ ap->config.options |= RRDR_OPTION_DIMS_MIN;
+ break;
+
+ case ALERT_LOOKUP_DIMS_MAX:
+ ap->config.options |= RRDR_OPTION_DIMS_MAX;
+ break;
+
+ case ALERT_LOOKUP_DIMS_MIN2MAX:
+ ap->config.options |= RRDR_OPTION_DIMS_MIN2MAX;
+ break;
+ }
+}
+
+static void data_source_to_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
+ ap->config.options &= ~(RRDR_OPTIONS_DATA_SOURCES);
+
+ switch(ap->config.data_source) {
+ default:
+ case ALERT_LOOKUP_DATA_SOURCE_SAMPLES:
+ break;
+
+ case ALERT_LOOKUP_DATA_SOURCE_PERCENTAGES:
+ ap->config.options |= RRDR_OPTION_PERCENTAGE;
+ break;
+
+ case ALERT_LOOKUP_DATA_SOURCE_ANOMALIES:
+ ap->config.options |= RRDR_OPTION_ANOMALY_BIT;
+ break;
+ }
+}
+
static bool parse_match(json_object *jobj, const char *path, struct rrd_alert_match *match, BUFFER *error) {
STRING *on = NULL;
JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "on", on, error, true);
@@ -26,7 +70,25 @@ static bool parse_match(json_object *jobj, const char *path, struct rrd_alert_ma
static bool parse_config_value_database_lookup(json_object *jobj, const char *path, struct rrd_alert_config *config, BUFFER *error) {
JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "after", config->after, error);
JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "before", config->before, error);
- JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "grouping", time_grouping_txt2id, config->group, error);
+ JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "time_group", time_grouping_txt2id, config->time_group, error);
+ JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "dims_group", alerts_dims_grouping2id, config->dims_group, error);
+ JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "data_source", alerts_data_sources2id, config->data_source, error);
+
+ switch(config->time_group) {
+ default:
+ break;
+
+ case RRDR_GROUPING_COUNTIF:
+ JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "time_group_condition", alerts_group_condition2id, config->time_group_condition, error);
+ // fall through
+
+ case RRDR_GROUPING_TRIMMED_MEAN:
+ case RRDR_GROUPING_TRIMMED_MEDIAN:
+ case RRDR_GROUPING_PERCENTILE:
+ JSONC_PARSE_DOUBLE_OR_ERROR_AND_RETURN(jobj, path, "time_group_value", config->time_group_value, error);
+ break;
+ }
+
JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, path, "options", rrdr_options_parse_one, config->options, error);
JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "dimensions", config->dimensions, error, true);
return true;
@@ -40,8 +102,6 @@ static bool parse_config_value(json_object *jobj, const char *path, struct rrd_a
}
static bool parse_config_conditions(json_object *jobj, const char *path, struct rrd_alert_config *config, BUFFER *error) {
- JSONC_PARSE_DOUBLE_OR_ERROR_AND_RETURN(jobj, path, "green", config->green, error);
- JSONC_PARSE_DOUBLE_OR_ERROR_AND_RETURN(jobj, path, "red", config->red, error);
JSONC_PARSE_TXT2EXPRESSION_OR_ERROR_AND_RETURN(jobj, path, "warning_condition", config->warning, error);
JSONC_PARSE_TXT2EXPRESSION_OR_ERROR_AND_RETURN(jobj, path, "critical_condition", config->critical, error);
return true;
@@ -70,20 +130,21 @@ static bool parse_config_action(json_object *jobj, const char *path, struct rrd_
return true;
}
-static bool parse_config(json_object *jobj, const char *path, struct rrd_alert_config *config, BUFFER *error) {
+static bool parse_config(json_object *jobj, const char *path, RRD_ALERT_PROTOTYPE *ap, BUFFER *error) {
// we shouldn't parse these from the payload - they are given to us via the function call
- // JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, "source_type", dyncfg_source_type2id, config->source_type);
- // JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, "source", config->source);
+ // JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "source_type", dyncfg_source_type2id, ap->config.source_type, error);
+ // JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "source", ap->config.source, error, true);
- JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "summary", config->summary, error, true);
- JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "info", config->info, error, true);
- JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "type", config->type, error, true);
- JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "component", config->component, error, true);
- JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "classification", config->classification, error, true);
+ JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "summary", ap->config.summary, error, true);
+ JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "info", ap->config.info, error, true);
+ JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "type", ap->config.type, error, true);
+ JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "component", ap->config.component, error, true);
+ JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "classification", ap->config.classification, error, true);
- JSONC_PARSE_SUBOBJECT(jobj, path, "value", config, parse_config_value, error);
- JSONC_PARSE_SUBOBJECT(jobj, path, "conditions", config, parse_config_conditions, error);
- JSONC_PARSE_SUBOBJECT(jobj, path, "action", config, parse_config_action, error);
+ JSONC_PARSE_SUBOBJECT(jobj, path, "value", &ap->config, parse_config_value, error);
+ JSONC_PARSE_SUBOBJECT(jobj, path, "conditions", &ap->config, parse_config_conditions, error);
+ JSONC_PARSE_SUBOBJECT(jobj, path, "action", &ap->config, parse_config_action, error);
+ JSONC_PARSE_SUBOBJECT(jobj, path, "match", &ap->match, parse_match, error);
return true;
}
@@ -126,8 +187,7 @@ static bool parse_prototype(json_object *jobj, const char *path, RRD_ALERT_PROTO
return false;
}
- JSONC_PARSE_SUBOBJECT(rule, path, "match", &ap->match, parse_match, error);
- JSONC_PARSE_SUBOBJECT(rule, path, "config", &ap->config, parse_config, error);
+ JSONC_PARSE_SUBOBJECT(rule, path, "config", ap, parse_config, error);
ap = NULL; // so that we will create another one, if available
}
@@ -177,6 +237,9 @@ static RRD_ALERT_PROTOTYPE *health_prototype_payload_parse(const char *payload,
goto cleanup;
}
+ data_source_to_rrdr_options(ap);
+ dims_grouping_to_rrdr_options(ap);
+
if(ap->match.enabled)
base->_internal.enabled = true;
}
@@ -204,18 +267,6 @@ static inline void health_prototype_rule_to_json_array_member(BUFFER *wb, RRD_AL
buffer_json_member_add_boolean(wb, "enabled", ap->match.enabled);
buffer_json_member_add_string(wb, "type", ap->match.is_template ? "template" : "instance");
- buffer_json_member_add_object(wb, "match");
- {
- if(ap->match.is_template)
- buffer_json_member_add_string(wb, "on", string2str(ap->match.on.context));
- else
- buffer_json_member_add_string(wb, "on", string2str(ap->match.on.chart));
-
- buffer_json_member_add_string_or_empty(wb, "host_labels", ap->match.host_labels ? string2str(ap->match.host_labels) : "*");
- buffer_json_member_add_string_or_empty(wb, "instance_labels", ap->match.chart_labels ? string2str(ap->match.chart_labels) : "*");
- }
- buffer_json_object_close(wb); // match
-
buffer_json_member_add_object(wb, "config");
{
if(!for_hashing) {
@@ -224,6 +275,18 @@ static inline void health_prototype_rule_to_json_array_member(BUFFER *wb, RRD_AL
buffer_json_member_add_string(wb, "source", string2str(ap->config.source));
}
+ buffer_json_member_add_object(wb, "match");
+ {
+ if(ap->match.is_template)
+ buffer_json_member_add_string(wb, "on", string2str(ap->match.on.context));
+ else
+ buffer_json_member_add_string(wb, "on", string2str(ap->match.on.chart));
+
+ buffer_json_member_add_string_or_empty(wb, "host_labels", ap->match.host_labels ? string2str(ap->match.host_labels) : "*");
+ buffer_json_member_add_string_or_empty(wb, "instance_labels", ap->match.chart_labels ? string2str(ap->match.chart_labels) : "*");
+ }
+ buffer_json_object_close(wb); // match
+
buffer_json_member_add_string(wb, "summary", string2str(ap->config.summary));
buffer_json_member_add_string(wb, "info", string2str(ap->config.info));
@@ -237,8 +300,12 @@ static inline void health_prototype_rule_to_json_array_member(BUFFER *wb, RRD_AL
{
buffer_json_member_add_int64(wb, "after", ap->config.after);
buffer_json_member_add_int64(wb, "before", ap->config.before);
- buffer_json_member_add_string(wb, "grouping", time_grouping_id2txt(ap->config.group));
- rrdr_options_to_buffer_json_array(wb, "options", ap->config.options);
+ buffer_json_member_add_string(wb, "time_group", time_grouping_id2txt(ap->config.time_group));
+ buffer_json_member_add_string(wb, "time_group_condition", alerts_group_conditions_id2txt(ap->config.time_group_condition));
+ buffer_json_member_add_double(wb, "time_group_value", ap->config.time_group_value);
+ buffer_json_member_add_string(wb, "dims_group", alerts_dims_grouping_id2group(ap->config.dims_group));
+ buffer_json_member_add_string(wb, "data_source", alerts_data_source_id2source(ap->config.data_source));
+ rrdr_options_to_buffer_json_array(wb, "options", RRDR_OPTIONS_REMOVE_OVERLAPPING(ap->config.options));
buffer_json_member_add_string(wb, "dimensions", string2str(ap->config.dimensions));
}
buffer_json_object_close(wb); // database lookup
@@ -251,8 +318,6 @@ static inline void health_prototype_rule_to_json_array_member(BUFFER *wb, RRD_AL
buffer_json_member_add_object(wb, "conditions");
{
- buffer_json_member_add_double(wb, "green", ap->config.green);
- buffer_json_member_add_double(wb, "red", ap->config.red);
buffer_json_member_add_string(wb, "warning_condition", expression_source(ap->config.warning));
buffer_json_member_add_string(wb, "critical_condition", expression_source(ap->config.critical));
}
@@ -556,10 +621,10 @@ static void health_dyncfg_register_prototype(RRD_ALERT_PROTOTYPE *ap) {
ap->_internal.enabled ? DYNCFG_STATUS_ACCEPTED : DYNCFG_STATUS_DISABLED, DYNCFG_TYPE_JOB,
ap->config.source_type, string2str(ap->config.source),
DYNCFG_CMD_SCHEMA | DYNCFG_CMD_GET | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE |
- DYNCFG_CMD_UPDATE | DYNCFG_CMD_TEST |
+ DYNCFG_CMD_UPDATE |
(ap->config.source_type == DYNCFG_SOURCE_TYPE_DYNCFG && !ap->_internal.is_on_disk ? DYNCFG_CMD_REMOVE : 0),
- HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG,
- HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG,
+ HTTP_ACCESS_NONE,
+ HTTP_ACCESS_NONE,
dyncfg_health_cb, NULL);
#ifdef NETDATA_TEST_HEALTH_PROTOTYPES_JSON_AND_PARSING
@@ -589,9 +654,9 @@ void health_dyncfg_register_all_prototypes(void) {
DYNCFG_HEALTH_ALERT_PROTOTYPE_PREFIX, "/health/alerts/prototypes",
DYNCFG_STATUS_ACCEPTED, DYNCFG_TYPE_TEMPLATE,
DYNCFG_SOURCE_TYPE_INTERNAL, "internal",
- DYNCFG_CMD_SCHEMA | DYNCFG_CMD_ADD | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE | DYNCFG_CMD_TEST,
- HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG,
- HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG,
+ DYNCFG_CMD_SCHEMA | DYNCFG_CMD_ADD | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
+ HTTP_ACCESS_NONE,
+ HTTP_ACCESS_NONE,
dyncfg_health_cb, NULL);
dfe_start_read(health_globals.prototypes.dict, ap) {
diff --git a/src/health/health_event_loop.c b/src/health/health_event_loop.c
index 9ec3d00f90..a4b8caff34 100644
--- a/src/health/health_event_loop.c
+++ b/src/health/health_event_loop.c
@@ -340,8 +340,31 @@ static void health_event_loop(void) {
/* time_t old_db_timestamp = rc->db_before; */
int value_is_null = 0;
+ char group_options_buf[100];
+ const char *group_options = group_options_buf;
+ switch(rc->config.time_group) {
+ default:
+ group_options = NULL;
+ break;
+
+ case RRDR_GROUPING_PERCENTILE:
+ case RRDR_GROUPING_TRIMMED_MEAN:
+ case RRDR_GROUPING_TRIMMED_MEDIAN:
+ snprintfz(group_options_buf, sizeof(group_options_buf),
+ NETDATA_DOUBLE_FORMAT_AUTO,
+ rc->config.time_group_value);
+ break;
+
+ case RRDR_GROUPING_COUNTIF:
+ snprintfz(group_options_buf, sizeof(group_options_buf),
+ "%s" NETDATA_DOUBLE_FORMAT_AUTO,
+ alerts_group_conditions_id2txt(rc->config.time_group_condition),
+ rc->config.time_group_value);
+ break;
+ }
+
int ret = rrdset2value_api_v1(rc->rrdset, NULL, &rc->value, rrdcalc_dimensions(rc), 1,
- rc->config.after, rc->config.before, rc->config.group, NULL,
+ rc->config.after, rc->config.before, rc->config.time_group, group_options,
0, rc->config.options | RRDR_OPTION_SELECTED_TIER,
&rc->db_after,&rc->db_before,
NULL, NULL, NULL,
diff --git a/src/health/health_json.c b/src/health/health_json.c
index 3815e50949..68bfb5229a 100644
--- a/src/health/health_json.c
+++ b/src/health/health_json.c
@@ -129,11 +129,11 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
"\t\t\t\"lookup_options\": \"",
(unsigned long) rc->db_after,
(unsigned long) rc->db_before,
- time_grouping_id2txt(rc->config.group),
+ time_grouping_id2txt(rc->config.time_group),
rc->config.after,
rc->config.before
);
- buffer_data_options2string(wb, rc->config.options);
+ rrdr_options_to_buffer(wb, rc->config.options);
buffer_strcat(wb, "\",\n");
}
@@ -153,11 +153,11 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
}
buffer_strcat(wb, "\t\t\t\"green\":");
- buffer_print_netdata_double(wb, rc->config.green);
+ buffer_print_netdata_double(wb, NAN);
buffer_strcat(wb, ",\n");
buffer_strcat(wb, "\t\t\t\"red\":");
- buffer_print_netdata_double(wb, rc->config.red);
+ buffer_print_netdata_double(wb, NAN);
buffer_strcat(wb, ",\n");
buffer_strcat(wb,