diff options
Diffstat (limited to 'src/health')
-rw-r--r-- | src/health/REFERENCE.md | 19 | ||||
-rw-r--r-- | src/health/health.h | 4 | ||||
-rw-r--r-- | src/health/health_config.c | 206 | ||||
-rw-r--r-- | src/health/health_dyncfg.c | 141 | ||||
-rw-r--r-- | src/health/health_event_loop.c | 25 | ||||
-rw-r--r-- | src/health/health_json.c | 8 | ||||
-rw-r--r-- | src/health/health_prototypes.c | 121 | ||||
-rw-r--r-- | src/health/health_prototypes.h | 40 | ||||
-rw-r--r-- | src/health/health_variable.c | 20 | ||||
-rw-r--r-- | src/health/rrdcalc.c | 7 | ||||
-rw-r--r-- | src/health/schema.d/health:alert:prototype.json | 615 |
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, |