@cryptotaxi247 / netdata-1 / commits / 45a1681ad

DYNCFG: alerts improvements (#17165)

* updated schema * move "match" inside "config" in the json representation of the alert * removed green and red from everywhere, but if they exist in the config file they are replaced with fixed numeric values * additional health fields * default permissions for systemd dyncfg; remove test from alerts * added the ability to define time grouping options * updated prototype * Add new fields to the database and store * Read newly stored values when fetching config (not exposed to JSON) * Render new values --------- Co-authored-by: Stelios Fragkakis <52996999+stelfrag@users.noreply.github.com>

Costa Tsaousis committed Mar 18, 2024 at 13:35 UTC 45a1681ad384840a898e3b3e0009cd9f7ffefe03
29 files changed +1108 -527
src/collectors/systemd-journal.plugin/systemd-journal-dyncfg.c
+2 -2
@@ -156,8 +156,8 @@ void systemd_journal_dyncfg_init(struct functions_evloop_globals *wg) {
156 DYNCFG_SOURCE_TYPE_INTERNAL,
157 "internal",
158 DYNCFG_CMD_SCHEMA | DYNCFG_CMD_GET | DYNCFG_CMD_UPDATE,
159 - HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG,
160 - HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG,
159 + HTTP_ACCESS_NONE,
160 + HTTP_ACCESS_NONE,
161 systemd_journal_directories_dyncfg_cb,
162 NULL);
163 }
src/daemon/config/dyncfg.c
+1 -1
@@ -291,7 +291,7 @@ bool dyncfg_add_low_level(RRDHOST *host, const char *id, const char *path,
291 view_access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG;
292
293 if(edit_access == HTTP_ACCESS_NONE)
294 - edit_access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG;
294 + edit_access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG | HTTP_ACCESS_COMMERCIAL_SPACE;
295
296 if(!dyncfg_is_valid_id(id)) {
297 nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: id '%s' is invalid. Ignoring dynamic configuration for it.", id);
src/database/contexts/api_v2.c
+4
@@ -1338,6 +1338,10 @@ static void contexts_v2_alert_config_to_json_from_sql_alert_config_data(struct s
1338
1339 buffer_json_member_add_time_t(wb, "after", t->value.db.after);
1340 buffer_json_member_add_time_t(wb, "before", t->value.db.before);
1341 + buffer_json_member_add_string(wb, "time_group_condition", alerts_group_conditions_id2txt(t->value.db.time_group_condition));
1342 + buffer_json_member_add_double(wb, "time_group_value", t->value.db.time_group_value);
1343 + buffer_json_member_add_string(wb, "dims_group", alerts_dims_grouping_id2group(t->value.db.dims_group));
1344 + buffer_json_member_add_string(wb, "data_source", alerts_data_source_id2source(t->value.db.data_source));
1345 buffer_json_member_add_string(wb, "method", t->value.db.method);
1346 buffer_json_member_add_string(wb, "dimensions", t->value.db.dimensions);
1347 rrdr_options_to_buffer_json_array(wb, "options", (RRDR_OPTIONS)t->value.db.options);
src/database/contexts/rrdcontext.h
+4
@@ -476,6 +476,10 @@ struct sql_alert_config_data {
476 struct {
477 const char *dimensions;
478 const char *method;
479 + ALERT_LOOKUP_TIME_GROUP_CONDITION time_group_condition;
480 + NETDATA_DOUBLE time_group_value;
481 + ALERT_LOOKUP_DIMS_GROUPING dims_group;
482 + ALERT_LOOKUP_DATA_SOURCE data_source;
483 uint32_t options;
484
485 int32_t after;
src/database/sqlite/sqlite_aclk_alert.c
+1 -1
@@ -610,7 +610,7 @@ void aclk_push_alert_config_event(char *node_id __maybe_unused, char *config_has
610 netdata_log_error("aclk_push_alert_config_event: Unexpected param number %d", param);
611
612 BUFFER *tmp_buf = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
613 - buffer_data_options2string(tmp_buf, sqlite3_column_int(res, 28));
613 + rrdr_options_to_buffer(tmp_buf, sqlite3_column_int(res, 28));
614 alarm_config.p_db_lookup_options = strdupz((char *)buffer_tostring(tmp_buf));
615 buffer_free(tmp_buf);
616
src/database/sqlite/sqlite_db_migration.c
+19
@@ -153,6 +153,15 @@ const char *database_migrate_v13_v14[] = {
153 NULL
154 };
155
156 +const char *database_migrate_v16_v17[] = {
157 + "ALTER TABLE alert_hash ADD time_group_condition INT",
158 + "ALTER TABLE alert_hash ADD time_group_value DOUBLE",
159 + "ALTER TABLE alert_hash ADD dims_group INT",
160 + "ALTER TABLE alert_hash ADD data_source INT",
161 + NULL
162 +};
163 +
164 +
165 static int do_migration_v1_v2(sqlite3 *database)
166 {
167 if (table_exists_in_database(database, "host") && !column_exists_in_table(database, "host", "hops"))
@@ -430,6 +439,15 @@ static int do_migration_v15_v16(sqlite3 *database)
439 return 0;
440 }
441
442 +static int do_migration_v16_v17(sqlite3 *database)
443 +{
444 + if (table_exists_in_database(database, "alert_hash") && !column_exists_in_table(database, "alert_hash", "time_group_condition"))
445 + return init_database_batch(database, &database_migrate_v16_v17[0], "meta_migrate");
446 +
447 + return 0;
448 +}
449 +
450 +
451 static int do_migration_v12_v13(sqlite3 *database)
452 {
453 int rc = 0;
@@ -527,6 +545,7 @@ DATABASE_FUNC_MIGRATION_LIST migration_action[] = {
545 {.name = "v13 to v14", .func = do_migration_v13_v14},
546 {.name = "v14 to v15", .func = do_migration_v14_v15},
547 {.name = "v15 to v16", .func = do_migration_v15_v16},
548 + {.name = "v16 to v17", .func = do_migration_v16_v17},
549 // the terminator of this array
550 {.name = NULL, .func = NULL}
551 };
src/database/sqlite/sqlite_health.c
+43 -16
@@ -895,16 +895,19 @@ void sql_health_alarm_log_load(RRDHOST *host)
895 /*
896 * Store an alert config hash in the database
897 */
898 -#define SQL_STORE_ALERT_CONFIG_HASH \
899 - "insert or replace into alert_hash (hash_id, date_updated, alarm, template, " \
900 - "on_key, class, component, type, lookup, every, units, calc, " \
901 - "green, red, warn, crit, exec, to_key, info, delay, options, repeat, host_labels, " \
902 - "p_db_lookup_dimensions, p_db_lookup_method, p_db_lookup_options, p_db_lookup_after, " \
903 - "p_db_lookup_before, p_update_every, source, chart_labels, summary) values (@hash_id,UNIXEPOCH(),@alarm,@template," \
904 - "@on_key,@class,@component,@type,@lookup,@every,@units,@calc," \
905 - "@green,@red,@warn,@crit,@exec,@to_key,@info,@delay,@options,@repeat,@host_labels," \
906 - "@p_db_lookup_dimensions,@p_db_lookup_method,@p_db_lookup_options,@p_db_lookup_after," \
907 - "@p_db_lookup_before,@p_update_every,@source,@chart_labels,@summary)"
898 +#define SQL_STORE_ALERT_CONFIG_HASH \
899 + "insert or replace into alert_hash (hash_id, date_updated, alarm, template, " \
900 + "on_key, class, component, type, lookup, every, units, calc, " \
901 + "green, red, warn, crit, exec, to_key, info, delay, options, repeat, host_labels, " \
902 + "p_db_lookup_dimensions, p_db_lookup_method, p_db_lookup_options, p_db_lookup_after, " \
903 + "p_db_lookup_before, p_update_every, source, chart_labels, summary, time_group_condition, " \
904 + "time_group_value, dims_group, data_source) " \
905 + "values (@hash_id,UNIXEPOCH(),@alarm,@template," \
906 + "@on_key,@class,@component,@type,@lookup,@every,@units,@calc," \
907 + "@green,@red,@warn,@crit,@exec,@to_key,@info,@delay,@options,@repeat,@host_labels," \
908 + "@p_db_lookup_dimensions,@p_db_lookup_method,@p_db_lookup_options,@p_db_lookup_after," \
909 + "@p_db_lookup_before,@p_update_every,@source,@chart_labels,@summary, @time_group_condition, " \
910 + "@time_group_value, @dims_group, @data_source)"
911
912 int sql_alert_store_config(RRD_ALERT_PROTOTYPE *ap __maybe_unused)
913 {
@@ -966,7 +969,8 @@ int sql_alert_store_config(RRD_ALERT_PROTOTYPE *ap __maybe_unused)
969 if (unlikely(rc != SQLITE_OK))
970 goto bind_fail;
971
969 - rc = SQLITE3_BIND_STRING_OR_NULL(res, ap->config.lookup, ++param);
972 + // Rebuild lookup
973 + rc = SQLITE3_BIND_STRING_OR_NULL(res, NULL, ++param); // lookup line
974 if (unlikely(rc != SQLITE_OK))
975 goto bind_fail;
976
@@ -985,11 +989,13 @@ int sql_alert_store_config(RRD_ALERT_PROTOTYPE *ap __maybe_unused)
989 if (unlikely(rc != SQLITE_OK))
990 goto bind_fail;
991
988 - rc = sqlite3_bind_double(res, ++param, ap->config.green);
992 + NETDATA_DOUBLE green = NAN;
993 + rc = sqlite3_bind_double(res, ++param, green);
994 if (unlikely(rc != SQLITE_OK))
995 goto bind_fail;
996
992 - rc = sqlite3_bind_double(res, ++param, ap->config.red);
997 + NETDATA_DOUBLE red = NAN;
998 + rc = sqlite3_bind_double(res, ++param, red);
999 if (unlikely(rc != SQLITE_OK))
1000 goto bind_fail;
1001
@@ -1056,11 +1062,11 @@ int sql_alert_store_config(RRD_ALERT_PROTOTYPE *ap __maybe_unused)
1062 if (unlikely(rc != SQLITE_OK))
1063 goto bind_fail;
1064
1059 - rc = sqlite3_bind_text(res, ++param, time_grouping_id2txt(ap->config.group), -1, SQLITE_STATIC);
1065 + rc = sqlite3_bind_text(res, ++param, time_grouping_id2txt(ap->config.time_group), -1, SQLITE_STATIC);
1066 if (unlikely(rc != SQLITE_OK))
1067 goto bind_fail;
1068
1063 - rc = sqlite3_bind_int(res, ++param, (int) ap->config.options);
1069 + rc = sqlite3_bind_int(res, ++param, (int) RRDR_OPTIONS_REMOVE_OVERLAPPING(ap->config.options));
1070 if (unlikely(rc != SQLITE_OK))
1071 goto bind_fail;
1072
@@ -1109,6 +1115,22 @@ int sql_alert_store_config(RRD_ALERT_PROTOTYPE *ap __maybe_unused)
1115 if (unlikely(rc != SQLITE_OK))
1116 goto bind_fail;
1117
1118 + rc = sqlite3_bind_int(res, ++param, ap->config.time_group_condition);
1119 + if (unlikely(rc != SQLITE_OK))
1120 + goto bind_fail;
1121 +
1122 + rc = sqlite3_bind_double(res, ++param, ap->config.time_group_value);
1123 + if (unlikely(rc != SQLITE_OK))
1124 + goto bind_fail;
1125 +
1126 + rc = sqlite3_bind_int(res, ++param, ap->config.dims_group);
1127 + if (unlikely(rc != SQLITE_OK))
1128 + goto bind_fail;
1129 +
1130 + rc = sqlite3_bind_int(res, ++param, ap->config.data_source);
1131 + if (unlikely(rc != SQLITE_OK))
1132 + goto bind_fail;
1133 +
1134 rc = execute_insert(res);
1135 if (unlikely(rc != SQLITE_DONE))
1136 error_report("Failed to store alert config, rc = %d", rc);
@@ -1838,7 +1860,8 @@ done_only_drop:
1860 "SELECT ah.hash_id, alarm, template, on_key, class, component, type, lookup, every, " \
1861 " units, calc, families, green, red, warn, crit, " \
1862 " exec, to_key, info, delay, options, repeat, host_labels, p_db_lookup_dimensions, p_db_lookup_method, " \
1841 - " p_db_lookup_options, p_db_lookup_after, p_db_lookup_before, p_update_every, source, chart_labels, summary " \
1863 + " p_db_lookup_options, p_db_lookup_after, p_db_lookup_before, p_update_every, source, chart_labels, summary, " \
1864 + " time_group_condition, time_group_value, dims_group, data_source " \
1865 " FROM alert_hash ah, c_%p t where ah.hash_id = t.hash_id"
1866
1867 int sql_get_alert_configuration(
@@ -1943,6 +1966,10 @@ int sql_get_alert_configuration(
1966 acd.source = (const char *) sqlite3_column_text(res, param++);
1967 acd.selectors.chart_labels = (const char *) sqlite3_column_text(res, param++);
1968 acd.summary = (const char *) sqlite3_column_text(res, param++);
1969 + acd.value.db.time_group_condition =(int32_t) sqlite3_column_int(res, param++);
1970 + acd.value.db.time_group_value = sqlite3_column_double(res, param++);
1971 + acd.value.db.dims_group = (int32_t) sqlite3_column_int(res, param++);
1972 + acd.value.db.data_source = (int32_t) sqlite3_column_int(res, param++);
1973
1974 cb(&acd, data);
1975 added++;
src/database/sqlite/sqlite_metadata.c
+1 -1
@@ -4,7 +4,7 @@
4 #include "sqlite3recover.h"
5 //#include "sqlite_db_migration.h"
6
7 -#define DB_METADATA_VERSION 16
7 +#define DB_METADATA_VERSION 17
8
9 const char *database_config[] = {
10 "CREATE TABLE IF NOT EXISTS host(host_id BLOB PRIMARY KEY, hostname TEXT NOT NULL, "
src/health/REFERENCE.md
+16 -3
@@ -385,7 +385,7 @@ This line makes a database lookup to find a value. This result of this lookup is
385 The format is:
386
387 ```yaml
388 -lookup: METHOD AFTER [at BEFORE] [every DURATION] [OPTIONS] [of DIMENSIONS]
388 +lookup: METHOD(GROUPING OPTIONS) AFTER [at BEFORE] [every DURATION] [OPTIONS] [of DIMENSIONS]
389 ```
390
391 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
393 - `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.
394 This is required.
395
396 + - `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`.
397 +
398 - `AFTER` is a relative number of seconds, but it also accepts a single letter for changing
399 the units, like `-1s` = 1 second in the past, `-1m` = 1 minute in the past, `-1h` = 1 hour
400 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
406 - `every DURATION` sets the updated frequency of the lookup (supports single letter units as
407 above too).
408
407 -- `OPTIONS` is a space separated list of `percentage`, `absolute`, `min2max`, `unaligned`,
408 - `match-ids`, `match-names`. Check the [badges](https://github.com/netdata/netdata/blob/master/src/web/api/badges/README.md) documentation for more info.
409 +- `OPTIONS` is a space separated list of `percentage`, `absolute`, `min`, `max`, `average`, `sum`,
410 + `min2max`, `unaligned`, `match-ids`, `match-names`.
411 +
412 + - `percentage` during time-aggregation, calculate the percentage of the selected dimensions over the total of all dimensions.
413 + - `absolute` during time-aggregation, turns all sample values positive before using them.
414 + - `min` after time-aggregation of each dimension, return the minimum of all dimensions.
415 + - `max` after time-aggregation of each dimension, return the maximum of all dimensions.
416 + - `average` after time-aggregation of each dimension, return the average of all dimensions.
417 + - `sum` after time-aggregation of each dimension, return the sum of all dimensions (this is the default).
418 + - `min2max` after time-aggregation of each dimension, return the delta between the min and the max of the dimensions.
419 + - `unligned` prevents shifting the query window to multiples of the query duration.
420 + - `match-ids` matches the dimensions based on their IDs (the default is enabled, give `match-names` to disable).
421 + - `match-names` matches the dimension based on their names (the default is enabled, give `match-ids` to disable).
422
423 - `of DIMENSIONS` is optional and has to be the last parameter. Dimensions have to be separated
424 by `,` or `|`. The space characters found in dimensions will be kept as-is (a few dimensions
src/health/health.h
+4
@@ -20,6 +20,10 @@ typedef enum __attribute__((packed)) {
20 HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION = 0x80000000,
21 } HEALTH_ENTRY_FLAGS;
22
23 +#define RRDR_OPTIONS_DATA_SOURCES (RRDR_OPTION_PERCENTAGE|RRDR_OPTION_ANOMALY_BIT)
24 +#define RRDR_OPTIONS_DIMS_AGGREGATION (RRDR_OPTION_DIMS_MIN|RRDR_OPTION_DIMS_MAX|RRDR_OPTION_DIMS_AVERAGE|RRDR_OPTION_DIMS_MIN2MAX)
25 +#define RRDR_OPTIONS_REMOVE_OVERLAPPING(options) ((options) &= ~(RRDR_OPTIONS_DIMS_AGGREGATION|RRDR_OPTIONS_DATA_SOURCES))
26 +
27 void health_entry_flags_to_json_array(BUFFER *wb, const char *key, HEALTH_ENTRY_FLAGS flags);
28
29 #ifndef HEALTH_LISTEN_PORT
src/health/health_config.c
+167 -39
@@ -162,25 +162,21 @@ static inline int isvariableterm(const char s) {
162 return 1;
163 }
164
165 -static inline int health_parse_db_lookup(
166 - size_t line, const char *filename, char *string,
167 - RRDR_TIME_GROUPING *group_method, int *after, int *before, int *every,
168 - RRDR_OPTIONS *options, STRING **dimensions
169 -) {
170 - netdata_log_debug(D_HEALTH, "Health configuration parsing database lookup %zu@%s: %s", line, filename, string);
171 -
172 - if(*dimensions) string_freez(*dimensions);
173 - *dimensions = NULL;
174 - *after = 0;
175 - *before = 0;
176 - *every = 0;
177 - *options = 0;
165 +static inline int health_parse_db_lookup(size_t line, const char *filename, char *string, struct rrd_alert_config *ac) {
166 + if(ac->dimensions) string_freez(ac->dimensions);
167 + ac->dimensions = NULL;
168 + ac->after = 0;
169 + ac->before = 0;
170 + ac->update_every = 0;
171 + ac->options = 0;
172 + ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_EQUAL;
173 + ac->time_group_value = NAN;
174
175 char *s = string, *key;
176
177 // first is the group method
178 key = s;
183 - while(*s && !isspace(*s)) s++;
179 + while(*s && !isspace(*s) && *s != '(') s++;
180 while(*s && isspace(*s)) *s++ = '\0';
181 if(!*s) {
182 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(
184 return 0;
185 }
186
191 - if((*group_method = time_grouping_parse(key, RRDR_GROUPING_UNDEFINED)) == RRDR_GROUPING_UNDEFINED) {
187 + bool group_options = false;
188 + if(*s == '(') {
189 + *s++ = '\0';
190 + group_options = true;
191 + }
192 +
193 + if((ac->time_group = time_grouping_parse(key, RRDR_GROUPING_UNDEFINED)) == RRDR_GROUPING_UNDEFINED) {
194 netdata_log_error("Health configuration at line %zu of file '%s': invalid group method '%s'",
195 line, filename, key);
196 return 0;
197 }
198
199 + if(group_options) {
200 + if(*s == '!') {
201 + s++;
202 + if(*s == '=') s++;
203 + ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_NOT_EQUAL;
204 + }
205 + else if(*s == '<') {
206 + s++;
207 + if(*s == '>') {
208 + s++;
209 + ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_NOT_EQUAL;
210 + }
211 + else if(*s == '=') {
212 + s++;
213 + ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER_EQUAL;
214 + }
215 + else
216 + ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER;
217 + }
218 + else if(*s == '>') {
219 + if(*s == '=') {
220 + s++;
221 + ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS_EQUAL;
222 + }
223 + else
224 + ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS;
225 + }
226 +
227 + while(*s && isspace(*s)) s++;
228 +
229 + if(*s) {
230 + if(isdigit(*s) || *s == '.') {
231 + ac->time_group_value = str2ndd(s, &s);
232 + while(s && *s && isspace(*s)) s++;
233 +
234 + if(!s || *s != ')') {
235 + netdata_log_error("Health configuration at line %zu of file '%s': missing closing parenthesis after number in aggregation method on '%s'",
236 + line, filename, key);
237 + return 0;
238 + }
239 + }
240 + }
241 + else if(*s != ')') {
242 + netdata_log_error("Health configuration at line %zu of file '%s': missing closing parenthesis after method on '%s'",
243 + line, filename, key);
244 + return 0;
245 + }
246 +
247 + s++;
248 + }
249 +
250 + switch (ac->time_group) {
251 + default:
252 + break;
253 +
254 + case RRDR_GROUPING_COUNTIF:
255 + if(isnan(ac->time_group_value))
256 + ac->time_group_value = 0;
257 + break;
258 +
259 + case RRDR_GROUPING_TRIMMED_MEAN:
260 + case RRDR_GROUPING_TRIMMED_MEDIAN:
261 + if(isnan(ac->time_group_value))
262 + ac->time_group_value = 5;
263 + break;
264 +
265 + case RRDR_GROUPING_PERCENTILE:
266 + if(isnan(ac->time_group_value))
267 + ac->time_group_value = 95;
268 + break;
269 + }
270 +
271 // then is the 'after' time
272 key = s;
273 while(*s && !isspace(*s)) s++;
274 while(*s && isspace(*s)) *s++ = '\0';
275
202 - if(!config_parse_duration(key, after)) {
276 + if(!config_parse_duration(key, &ac->after)) {
277 netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' after group method",
278 line, filename, key);
279 return 0;
280 }
281
282 // sane defaults
209 - *every = ABS(*after);
283 + ac->update_every = ABS(ac->after);
284
285 // now we may have optional parameters
286 while(*s) {
@@ -220,7 +294,7 @@ static inline int health_parse_db_lookup(
294 while(*s && !isspace(*s)) s++;
295 while(*s && isspace(*s)) *s++ = '\0';
296
223 - if (!config_parse_duration(value, before)) {
297 + if (!config_parse_duration(value, &ac->before)) {
298 netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
299 line, filename, value, key);
300 }
@@ -230,34 +304,46 @@ static inline int health_parse_db_lookup(
304 while(*s && !isspace(*s)) s++;
305 while(*s && isspace(*s)) *s++ = '\0';
306
233 - if (!config_parse_duration(value, every)) {
307 + if (!config_parse_duration(value, &ac->update_every)) {
308 netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
309 line, filename, value, key);
310 }
311 }
312 else if(!strcasecmp(key, "absolute") || !strcasecmp(key, "abs") || !strcasecmp(key, "absolute_sum")) {
239 - *options |= RRDR_OPTION_ABSOLUTE;
313 + ac->options |= RRDR_OPTION_ABSOLUTE;
314 }
315 else if(!strcasecmp(key, "min2max")) {
242 - *options |= RRDR_OPTION_MIN2MAX;
316 + ac->options |= RRDR_OPTION_DIMS_MIN2MAX;
317 + }
318 + else if(!strcasecmp(key, "average")) {
319 + ac->options |= RRDR_OPTION_DIMS_AVERAGE;
320 + }
321 + else if(!strcasecmp(key, "min")) {
322 + ac->options |= RRDR_OPTION_DIMS_MIN;
323 + }
324 + else if(!strcasecmp(key, "max")) {
325 + ac->options |= RRDR_OPTION_DIMS_MAX;
326 + }
327 + else if(!strcasecmp(key, "sum")) {
328 + ;
329 }
330 else if(!strcasecmp(key, "null2zero")) {
245 - *options |= RRDR_OPTION_NULL2ZERO;
331 + ac->options |= RRDR_OPTION_NULL2ZERO;
332 }
333 else if(!strcasecmp(key, "percentage")) {
248 - *options |= RRDR_OPTION_PERCENTAGE;
334 + ac->options |= RRDR_OPTION_PERCENTAGE;
335 }
336 else if(!strcasecmp(key, "unaligned")) {
251 - *options |= RRDR_OPTION_NOT_ALIGNED;
337 + ac->options |= RRDR_OPTION_NOT_ALIGNED;
338 }
339 else if(!strcasecmp(key, "anomaly-bit")) {
254 - *options |= RRDR_OPTION_ANOMALY_BIT;
340 + ac->options |= RRDR_OPTION_ANOMALY_BIT;
341 }
342 else if(!strcasecmp(key, "match-ids") || !strcasecmp(key, "match_ids")) {
257 - *options |= RRDR_OPTION_MATCH_IDS;
343 + ac->options |= RRDR_OPTION_MATCH_IDS;
344 }
345 else if(!strcasecmp(key, "match-names") || !strcasecmp(key, "match_names")) {
260 - *options |= RRDR_OPTION_MATCH_NAMES;
346 + ac->options |= RRDR_OPTION_MATCH_NAMES;
347 }
348 else if(!strcasecmp(key, "of")) {
349 char *find = NULL;
@@ -266,7 +352,7 @@ static inline int health_parse_db_lookup(
352 if(find) {
353 *find = '\0';
354 }
269 - *dimensions = string_strdupz(s);
355 + ac->dimensions = string_strdupz(s);
356 }
357
358 if(!find) {
@@ -340,6 +426,46 @@ static inline void strip_quotes(char *s) {
426 }
427 }
428
429 +static void replace_green_red(RRD_ALERT_PROTOTYPE *ap, NETDATA_DOUBLE green, NETDATA_DOUBLE red) {
430 + if(!isnan(green)) {
431 + STRING *green_str = string_strdupz("green");
432 + expression_hardcode_variable(ap->config.calculation, green_str, green);
433 + expression_hardcode_variable(ap->config.warning, green_str, green);
434 + expression_hardcode_variable(ap->config.critical, green_str, green);
435 + string_freez(green_str);
436 + }
437 +
438 + if(!isnan(red)) {
439 + STRING *red_str = string_strdupz("red");
440 + expression_hardcode_variable(ap->config.calculation, red_str, red);
441 + expression_hardcode_variable(ap->config.warning, red_str, red);
442 + expression_hardcode_variable(ap->config.critical, red_str, red);
443 + string_freez(red_str);
444 + }
445 +}
446 +
447 +static void dims_grouping_from_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
448 + if(ap->config.options & RRDR_OPTION_DIMS_MIN)
449 + ap->config.dims_group = ALERT_LOOKUP_DIMS_MIN;
450 + else if(ap->config.options & RRDR_OPTION_DIMS_MAX)
451 + ap->config.dims_group = ALERT_LOOKUP_DIMS_MAX;
452 + else if(ap->config.options & RRDR_OPTION_DIMS_MIN2MAX)
453 + ap->config.dims_group = ALERT_LOOKUP_DIMS_MIN2MAX;
454 + else if(ap->config.options & RRDR_OPTION_DIMS_AVERAGE)
455 + ap->config.dims_group = ALERT_LOOKUP_DIMS_AVERAGE;
456 + else
457 + ap->config.dims_group = ALERT_LOOKUP_DIMS_SUM;
458 +}
459 +
460 +static void lookup_data_source_from_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
461 + if(ap->config.options & RRDR_OPTION_PERCENTAGE)
462 + ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_PERCENTAGES;
463 + else if(ap->config.options & RRDR_OPTION_ANOMALY_BIT)
464 + ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_ANOMALIES;
465 + else
466 + ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_SAMPLES;
467 +}
468 +
469 #define PARSE_HEALTH_CONFIG_LOG_DUPLICATE_STRING_MSG(ax, member) do { \
470 if(strcmp(string2str(ax->member), value) != 0) \
471 netdata_log_error( \
@@ -391,8 +517,6 @@ static inline void strip_quotes(char *s) {
517 } \
518 } while(0)
519
394 -
395 -
520 int health_readfile(const char *filename, void *data __maybe_unused, bool stock_config) {
521 netdata_log_debug(D_HEALTH, "Health configuration reading file '%s'", filename);
522
@@ -466,6 +590,8 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
590 RRD_ALERT_PROTOTYPE *ap = NULL;
591 struct rrd_alert_config *ac = NULL;
592 struct rrd_alert_match *am = NULL;
593 + NETDATA_DOUBLE green = NAN;
594 + NETDATA_DOUBLE red = NAN;
595
596 size_t line = 0, append = 0;
597 char *s;
@@ -522,6 +648,9 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
648
649 if((hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) || (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY))) {
650 if(ap) {
651 + lookup_data_source_from_rrdr_options(ap);
652 + dims_grouping_from_rrdr_options(ap);
653 + replace_green_red(ap, green, red);
654 health_prototype_add(ap);
655 freez(ap);
656 }
@@ -544,8 +673,8 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
673 ap->match.is_template = (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY));
674 ap->config.source = health_source_file(line, filename);
675 ap->config.source_type = stock_config ? DYNCFG_SOURCE_TYPE_STOCK : DYNCFG_SOURCE_TYPE_USER;
547 - ap->config.green = NAN;
548 - ap->config.red = NAN;
676 + green = NAN;
677 + red = NAN;
678 ap->config.delay_multiplier = 1;
679 ap->config.warn_repeat_every = health_globals.config.default_warn_repeat_every;
680 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_
722 PARSE_HEALTH_CONFIG_LINE_STRING(ac, type);
723 }
724 else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
596 - ac->lookup = string_strdupz(value);
597 - health_parse_db_lookup(line, filename, value,
598 - &ac->group, &ac->after, &ac->before,
599 - &ac->update_every, &ac->options,
600 - &ac->dimensions);
725 + health_parse_db_lookup(line, filename, value, ac);
726 }
727 else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
728 if(!config_parse_duration(value, &ac->update_every))
@@ -608,7 +733,7 @@ int health_readfile(const char *filename, void *data __maybe_unused, bool stock_
733 }
734 else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
735 char *e;
611 - ac->green = str2ndd(value, &e);
736 + green = str2ndd(value, &e);
737 if(e && *e) {
738 netdata_log_error(
739 "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_
743 }
744 else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
745 char *e;
621 - ac->red = str2ndd(value, &e);
746 + red = str2ndd(value, &e);
747 if(e && *e) {
748 netdata_log_error(
749 "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_
830 }
831
832 if(ap) {
833 + lookup_data_source_from_rrdr_options(ap);
834 + dims_grouping_from_rrdr_options(ap);
835 + replace_green_red(ap, green, red);
836 health_prototype_add(ap);
837 freez(ap);
838 }
src/health/health_dyncfg.c
+103 -38
@@ -9,6 +9,50 @@ static void health_dyncfg_register_prototype(RRD_ALERT_PROTOTYPE *ap);
9 // ---------------------------------------------------------------------------------------------------------------------
10 // parse the json object of an alert definition
11
12 +static void dims_grouping_to_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
13 + ap->config.options &= ~(RRDR_OPTIONS_DIMS_AGGREGATION);
14 +
15 + switch(ap->config.dims_group) {
16 + default:
17 + case ALERT_LOOKUP_DIMS_SUM:
18 + break;
19 +
20 + case ALERT_LOOKUP_DIMS_AVERAGE:
21 + ap->config.options |= RRDR_OPTION_DIMS_AVERAGE;
22 + break;
23 +
24 + case ALERT_LOOKUP_DIMS_MIN:
25 + ap->config.options |= RRDR_OPTION_DIMS_MIN;
26 + break;
27 +
28 + case ALERT_LOOKUP_DIMS_MAX:
29 + ap->config.options |= RRDR_OPTION_DIMS_MAX;
30 + break;
31 +
32 + case ALERT_LOOKUP_DIMS_MIN2MAX:
33 + ap->config.options |= RRDR_OPTION_DIMS_MIN2MAX;
34 + break;
35 + }
36 +}
37 +
38 +static void data_source_to_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
39 + ap->config.options &= ~(RRDR_OPTIONS_DATA_SOURCES);
40 +
41 + switch(ap->config.data_source) {
42 + default:
43 + case ALERT_LOOKUP_DATA_SOURCE_SAMPLES:
44 + break;
45 +
46 + case ALERT_LOOKUP_DATA_SOURCE_PERCENTAGES:
47 + ap->config.options |= RRDR_OPTION_PERCENTAGE;
48 + break;
49 +
50 + case ALERT_LOOKUP_DATA_SOURCE_ANOMALIES:
51 + ap->config.options |= RRDR_OPTION_ANOMALY_BIT;
52 + break;
53 + }
54 +}
55 +
56 static bool parse_match(json_object *jobj, const char *path, struct rrd_alert_match *match, BUFFER *error) {
57 STRING *on = NULL;
58 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
70 static bool parse_config_value_database_lookup(json_object *jobj, const char *path, struct rrd_alert_config *config, BUFFER *error) {
71 JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "after", config->after, error);
72 JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "before", config->before, error);
29 - JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "grouping", time_grouping_txt2id, config->group, error);
73 + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "time_group", time_grouping_txt2id, config->time_group, error);
74 + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "dims_group", alerts_dims_grouping2id, config->dims_group, error);
75 + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "data_source", alerts_data_sources2id, config->data_source, error);
76 +
77 + switch(config->time_group) {
78 + default:
79 + break;
80 +
81 + case RRDR_GROUPING_COUNTIF:
82 + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "time_group_condition", alerts_group_condition2id, config->time_group_condition, error);
83 + // fall through
84 +
85 + case RRDR_GROUPING_TRIMMED_MEAN:
86 + case RRDR_GROUPING_TRIMMED_MEDIAN:
87 + case RRDR_GROUPING_PERCENTILE:
88 + JSONC_PARSE_DOUBLE_OR_ERROR_AND_RETURN(jobj, path, "time_group_value", config->time_group_value, error);
89 + break;
90 + }
91 +
92 JSONC_PARSE_ARRAY_OF_TXT2BITMAP_OR_ERROR_AND_RETURN(jobj, path, "options", rrdr_options_parse_one, config->options, error);
93 JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "dimensions", config->dimensions, error, true);
94 return true;
@@ -40,8 +102,6 @@ static bool parse_config_value(json_object *jobj, const char *path, struct rrd_a
102 }
103
104 static bool parse_config_conditions(json_object *jobj, const char *path, struct rrd_alert_config *config, BUFFER *error) {
43 - JSONC_PARSE_DOUBLE_OR_ERROR_AND_RETURN(jobj, path, "green", config->green, error);
44 - JSONC_PARSE_DOUBLE_OR_ERROR_AND_RETURN(jobj, path, "red", config->red, error);
105 JSONC_PARSE_TXT2EXPRESSION_OR_ERROR_AND_RETURN(jobj, path, "warning_condition", config->warning, error);
106 JSONC_PARSE_TXT2EXPRESSION_OR_ERROR_AND_RETURN(jobj, path, "critical_condition", config->critical, error);
107 return true;
@@ -70,20 +130,21 @@ static bool parse_config_action(json_object *jobj, const char *path, struct rrd_
130 return true;
131 }
132
73 -static bool parse_config(json_object *jobj, const char *path, struct rrd_alert_config *config, BUFFER *error) {
133 +static bool parse_config(json_object *jobj, const char *path, RRD_ALERT_PROTOTYPE *ap, BUFFER *error) {
134 // we shouldn't parse these from the payload - they are given to us via the function call
75 - // JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, "source_type", dyncfg_source_type2id, config->source_type);
76 - // JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, "source", config->source);
135 + // JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "source_type", dyncfg_source_type2id, ap->config.source_type, error);
136 + // JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "source", ap->config.source, error, true);
137
78 - JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "summary", config->summary, error, true);
79 - JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "info", config->info, error, true);
80 - JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "type", config->type, error, true);
81 - JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "component", config->component, error, true);
82 - JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "classification", config->classification, error, true);
138 + JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "summary", ap->config.summary, error, true);
139 + JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "info", ap->config.info, error, true);
140 + JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "type", ap->config.type, error, true);
141 + JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "component", ap->config.component, error, true);
142 + JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "classification", ap->config.classification, error, true);
143
84 - JSONC_PARSE_SUBOBJECT(jobj, path, "value", config, parse_config_value, error);
85 - JSONC_PARSE_SUBOBJECT(jobj, path, "conditions", config, parse_config_conditions, error);
86 - JSONC_PARSE_SUBOBJECT(jobj, path, "action", config, parse_config_action, error);
144 + JSONC_PARSE_SUBOBJECT(jobj, path, "value", &ap->config, parse_config_value, error);
145 + JSONC_PARSE_SUBOBJECT(jobj, path, "conditions", &ap->config, parse_config_conditions, error);
146 + JSONC_PARSE_SUBOBJECT(jobj, path, "action", &ap->config, parse_config_action, error);
147 + JSONC_PARSE_SUBOBJECT(jobj, path, "match", &ap->match, parse_match, error);
148
149 return true;
150 }
@@ -126,8 +187,7 @@ static bool parse_prototype(json_object *jobj, const char *path, RRD_ALERT_PROTO
187 return false;
188 }
189
129 - JSONC_PARSE_SUBOBJECT(rule, path, "match", &ap->match, parse_match, error);
130 - JSONC_PARSE_SUBOBJECT(rule, path, "config", &ap->config, parse_config, error);
190 + JSONC_PARSE_SUBOBJECT(rule, path, "config", ap, parse_config, error);
191
192 ap = NULL; // so that we will create another one, if available
193 }
@@ -177,6 +237,9 @@ static RRD_ALERT_PROTOTYPE *health_prototype_payload_parse(const char *payload,
237 goto cleanup;
238 }
239
240 + data_source_to_rrdr_options(ap);
241 + dims_grouping_to_rrdr_options(ap);
242 +
243 if(ap->match.enabled)
244 base->_internal.enabled = true;
245 }
@@ -204,18 +267,6 @@ static inline void health_prototype_rule_to_json_array_member(BUFFER *wb, RRD_AL
267 buffer_json_member_add_boolean(wb, "enabled", ap->match.enabled);
268 buffer_json_member_add_string(wb, "type", ap->match.is_template ? "template" : "instance");
269
207 - buffer_json_member_add_object(wb, "match");
208 - {
209 - if(ap->match.is_template)
210 - buffer_json_member_add_string(wb, "on", string2str(ap->match.on.context));
211 - else
212 - buffer_json_member_add_string(wb, "on", string2str(ap->match.on.chart));
213 -
214 - buffer_json_member_add_string_or_empty(wb, "host_labels", ap->match.host_labels ? string2str(ap->match.host_labels) : "*");
215 - buffer_json_member_add_string_or_empty(wb, "instance_labels", ap->match.chart_labels ? string2str(ap->match.chart_labels) : "*");
216 - }
217 - buffer_json_object_close(wb); // match
218 -
270 buffer_json_member_add_object(wb, "config");
271 {
272 if(!for_hashing) {
@@ -224,6 +275,18 @@ static inline void health_prototype_rule_to_json_array_member(BUFFER *wb, RRD_AL
275 buffer_json_member_add_string(wb, "source", string2str(ap->config.source));
276 }
277
278 + buffer_json_member_add_object(wb, "match");
279 + {
280 + if(ap->match.is_template)
281 + buffer_json_member_add_string(wb, "on", string2str(ap->match.on.context));
282 + else
283 + buffer_json_member_add_string(wb, "on", string2str(ap->match.on.chart));
284 +
285 + buffer_json_member_add_string_or_empty(wb, "host_labels", ap->match.host_labels ? string2str(ap->match.host_labels) : "*");
286 + buffer_json_member_add_string_or_empty(wb, "instance_labels", ap->match.chart_labels ? string2str(ap->match.chart_labels) : "*");
287 + }
288 + buffer_json_object_close(wb); // match
289 +
290 buffer_json_member_add_string(wb, "summary", string2str(ap->config.summary));
291 buffer_json_member_add_string(wb, "info", string2str(ap->config.info));
292
@@ -237,8 +300,12 @@ static inline void health_prototype_rule_to_json_array_member(BUFFER *wb, RRD_AL
300 {
301 buffer_json_member_add_int64(wb, "after", ap->config.after);
302 buffer_json_member_add_int64(wb, "before", ap->config.before);
240 - buffer_json_member_add_string(wb, "grouping", time_grouping_id2txt(ap->config.group));
241 - rrdr_options_to_buffer_json_array(wb, "options", ap->config.options);
303 + buffer_json_member_add_string(wb, "time_group", time_grouping_id2txt(ap->config.time_group));
304 + buffer_json_member_add_string(wb, "time_group_condition", alerts_group_conditions_id2txt(ap->config.time_group_condition));
305 + buffer_json_member_add_double(wb, "time_group_value", ap->config.time_group_value);
306 + buffer_json_member_add_string(wb, "dims_group", alerts_dims_grouping_id2group(ap->config.dims_group));
307 + buffer_json_member_add_string(wb, "data_source", alerts_data_source_id2source(ap->config.data_source));
308 + rrdr_options_to_buffer_json_array(wb, "options", RRDR_OPTIONS_REMOVE_OVERLAPPING(ap->config.options));
309 buffer_json_member_add_string(wb, "dimensions", string2str(ap->config.dimensions));
310 }
311 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
318
319 buffer_json_member_add_object(wb, "conditions");
320 {
254 - buffer_json_member_add_double(wb, "green", ap->config.green);
255 - buffer_json_member_add_double(wb, "red", ap->config.red);
321 buffer_json_member_add_string(wb, "warning_condition", expression_source(ap->config.warning));
322 buffer_json_member_add_string(wb, "critical_condition", expression_source(ap->config.critical));
323 }
@@ -556,10 +621,10 @@ static void health_dyncfg_register_prototype(RRD_ALERT_PROTOTYPE *ap) {
621 ap->_internal.enabled ? DYNCFG_STATUS_ACCEPTED : DYNCFG_STATUS_DISABLED, DYNCFG_TYPE_JOB,
622 ap->config.source_type, string2str(ap->config.source),
623 DYNCFG_CMD_SCHEMA | DYNCFG_CMD_GET | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE |
559 - DYNCFG_CMD_UPDATE | DYNCFG_CMD_TEST |
624 + DYNCFG_CMD_UPDATE |
625 (ap->config.source_type == DYNCFG_SOURCE_TYPE_DYNCFG && !ap->_internal.is_on_disk ? DYNCFG_CMD_REMOVE : 0),
561 - HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG,
562 - HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG,
626 + HTTP_ACCESS_NONE,
627 + HTTP_ACCESS_NONE,
628 dyncfg_health_cb, NULL);
629
630 #ifdef NETDATA_TEST_HEALTH_PROTOTYPES_JSON_AND_PARSING
@@ -589,9 +654,9 @@ void health_dyncfg_register_all_prototypes(void) {
654 DYNCFG_HEALTH_ALERT_PROTOTYPE_PREFIX, "/health/alerts/prototypes",
655 DYNCFG_STATUS_ACCEPTED, DYNCFG_TYPE_TEMPLATE,
656 DYNCFG_SOURCE_TYPE_INTERNAL, "internal",
592 - DYNCFG_CMD_SCHEMA | DYNCFG_CMD_ADD | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE | DYNCFG_CMD_TEST,
593 - HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG,
594 - HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG,
657 + DYNCFG_CMD_SCHEMA | DYNCFG_CMD_ADD | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
658 + HTTP_ACCESS_NONE,
659 + HTTP_ACCESS_NONE,
660 dyncfg_health_cb, NULL);
661
662 dfe_start_read(health_globals.prototypes.dict, ap) {
src/health/health_event_loop.c
+24 -1
@@ -340,8 +340,31 @@ static void health_event_loop(void) {
340 /* time_t old_db_timestamp = rc->db_before; */
341 int value_is_null = 0;
342
343 + char group_options_buf[100];
344 + const char *group_options = group_options_buf;
345 + switch(rc->config.time_group) {
346 + default:
347 + group_options = NULL;
348 + break;
349 +
350 + case RRDR_GROUPING_PERCENTILE:
351 + case RRDR_GROUPING_TRIMMED_MEAN:
352 + case RRDR_GROUPING_TRIMMED_MEDIAN:
353 + snprintfz(group_options_buf, sizeof(group_options_buf),
354 + NETDATA_DOUBLE_FORMAT_AUTO,
355 + rc->config.time_group_value);
356 + break;
357 +
358 + case RRDR_GROUPING_COUNTIF:
359 + snprintfz(group_options_buf, sizeof(group_options_buf),
360 + "%s" NETDATA_DOUBLE_FORMAT_AUTO,
361 + alerts_group_conditions_id2txt(rc->config.time_group_condition),
362 + rc->config.time_group_value);
363 + break;
364 + }
365 +
366 int ret = rrdset2value_api_v1(rc->rrdset, NULL, &rc->value, rrdcalc_dimensions(rc), 1,
344 - rc->config.after, rc->config.before, rc->config.group, NULL,
367 + rc->config.after, rc->config.before, rc->config.time_group, group_options,
368 0, rc->config.options | RRDR_OPTION_SELECTED_TIER,
369 &rc->db_after,&rc->db_before,
370 NULL, NULL, NULL,
src/health/health_json.c
+4 -4
@@ -129,11 +129,11 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
129 "\t\t\t\"lookup_options\": \"",
130 (unsigned long) rc->db_after,
131 (unsigned long) rc->db_before,
132 - time_grouping_id2txt(rc->config.group),
132 + time_grouping_id2txt(rc->config.time_group),
133 rc->config.after,
134 rc->config.before
135 );
136 - buffer_data_options2string(wb, rc->config.options);
136 + rrdr_options_to_buffer(wb, rc->config.options);
137 buffer_strcat(wb, "\",\n");
138 }
139
@@ -153,11 +153,11 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
153 }
154
155 buffer_strcat(wb, "\t\t\t\"green\":");
156 - buffer_print_netdata_double(wb, rc->config.green);
156 + buffer_print_netdata_double(wb, NAN);
157 buffer_strcat(wb, ",\n");
158
159 buffer_strcat(wb, "\t\t\t\"red\":");
160 - buffer_print_netdata_double(wb, rc->config.red);
160 + buffer_print_netdata_double(wb, NAN);
161 buffer_strcat(wb, ",\n");
162
163 buffer_strcat(wb, "\t\t\t\"value\":");
src/health/health_prototypes.c
+117 -4
@@ -4,6 +4,122 @@
4
5 // ---------------------------------------------------------------------------------------------------------------------
6
7 +static struct {
8 + ALERT_LOOKUP_DIMS_GROUPING group;
9 + const char *name;
10 +} dims_grouping[] = {
11 + { .group = ALERT_LOOKUP_DIMS_SUM, .name = "sum" },
12 + { .group = ALERT_LOOKUP_DIMS_MIN, .name = "min" },
13 + { .group = ALERT_LOOKUP_DIMS_MAX, .name = "max" },
14 + { .group = ALERT_LOOKUP_DIMS_AVERAGE, .name = "average" },
15 + { .group = ALERT_LOOKUP_DIMS_MIN2MAX, .name = "min2max" },
16 +
17 + // terminator
18 + { .group = 0, .name = NULL },
19 +};
20 +
21 +ALERT_LOOKUP_DIMS_GROUPING alerts_dims_grouping2id(const char *group) {
22 + if(!group || !*group)
23 + return dims_grouping[0].group;
24 +
25 + for(size_t i = 0; dims_grouping[i].name ;i++) {
26 + if(strcmp(dims_grouping[i].name, group) == 0)
27 + return dims_grouping[i].group;
28 + }
29 +
30 + nd_log(NDLS_DAEMON, NDLP_WARNING, "Alert lookup dimensions grouping '%s' is not valid", group);
31 + return dims_grouping[0].group;
32 +}
33 +
34 +const char *alerts_dims_grouping_id2group(ALERT_LOOKUP_DIMS_GROUPING grouping) {
35 + for(size_t i = 0; dims_grouping[i].name ;i++) {
36 + if(grouping == dims_grouping[i].group)
37 + return dims_grouping[i].name;
38 + }
39 +
40 + nd_log(NDLS_DAEMON, NDLP_WARNING, "Alert lookup dimensions grouping %d is not valid", grouping);
41 + return dims_grouping[0].name;
42 +}
43 +
44 +// ---------------------------------------------------------------------------------------------------------------------
45 +
46 +static struct {
47 + ALERT_LOOKUP_DATA_SOURCE source;
48 + const char *name;
49 +} data_sources[] = {
50 + { .source = ALERT_LOOKUP_DATA_SOURCE_SAMPLES, .name = "samples" },
51 + { .source = ALERT_LOOKUP_DATA_SOURCE_PERCENTAGES, .name = "percentages" },
52 + { .source = ALERT_LOOKUP_DATA_SOURCE_ANOMALIES, .name = "anomalies" },
53 +
54 + // terminator
55 + { .source = 0, .name = NULL },
56 +};
57 +
58 +ALERT_LOOKUP_DATA_SOURCE alerts_data_sources2id(const char *source) {
59 + if(!source || !*source)
60 + return data_sources[0].source;
61 +
62 + for(size_t i = 0; data_sources[i].name ;i++) {
63 + if(strcmp(data_sources[i].name, source) == 0)
64 + return data_sources[i].source;
65 + }
66 +
67 + nd_log(NDLS_DAEMON, NDLP_WARNING, "Alert data source '%s' is not valid", source);
68 + return data_sources[0].source;
69 +}
70 +
71 +const char *alerts_data_source_id2source(ALERT_LOOKUP_DATA_SOURCE source) {
72 + for(size_t i = 0; data_sources[i].name ;i++) {
73 + if(source == data_sources[i].source)
74 + return data_sources[i].name;
75 + }
76 +
77 + nd_log(NDLS_DAEMON, NDLP_WARNING, "Alert data source %d is not valid", source);
78 + return data_sources[0].name;
79 +}
80 +
81 +// ---------------------------------------------------------------------------------------------------------------------
82 +
83 +static struct {
84 + ALERT_LOOKUP_TIME_GROUP_CONDITION condition;
85 + const char *name;
86 +} group_conditions[] = {
87 + { .condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_EQUAL, .name = "=" },
88 + { .condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_NOT_EQUAL, .name = "!=" },
89 + { .condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER, .name = ">" },
90 + { .condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER_EQUAL, .name = ">=" },
91 + { .condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS, .name = "<" },
92 + { .condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS_EQUAL, .name = "<=" },
93 +
94 + // terminator
95 + { .condition = 0, .name = NULL },
96 +};
97 +
98 +ALERT_LOOKUP_TIME_GROUP_CONDITION alerts_group_condition2id(const char *source) {
99 + if(!source || !*source)
100 + return group_conditions[0].condition;
101 +
102 + for(size_t i = 0; group_conditions[i].name ;i++) {
103 + if(strcmp(group_conditions[i].name, source) == 0)
104 + return group_conditions[i].condition;
105 + }
106 +
107 + nd_log(NDLS_DAEMON, NDLP_WARNING, "Alert data source '%s' is not valid", source);
108 + return group_conditions[0].condition;
109 +}
110 +
111 +const char *alerts_group_conditions_id2txt(ALERT_LOOKUP_TIME_GROUP_CONDITION source) {
112 + for(size_t i = 0; group_conditions[i].name ;i++) {
113 + if(source == group_conditions[i].condition)
114 + return group_conditions[i].name;
115 + }
116 +
117 + nd_log(NDLS_DAEMON, NDLP_WARNING, "Alert data source %d is not valid", source);
118 + return group_conditions[0].name;
119 +}
120 +
121 +// ---------------------------------------------------------------------------------------------------------------------
122 +
123 static struct {
124 const char *name;
125 uint32_t hash;
@@ -424,12 +540,9 @@ void health_prototype_copy_config(struct rrd_alert_config *dst, struct rrd_alert
540
541 dst->update_every = src->update_every;
542
427 - dst->green = src->green;
428 - dst->red = src->red;
429 -
543 dst->dimensions = string_dup(src->dimensions);
544
432 - dst->group = src->group;
545 + dst->time_group = src->time_group;
546 dst->before = src->before;
547 dst->after = src->after;
548 dst->options = src->options;
src/health/health_prototypes.h
+34 -6
@@ -10,6 +10,35 @@ typedef enum __attribute__((packed)) {
10 ALERT_ACTION_OPTION_NO_CLEAR_NOTIFICATION = (1 << 0),
11 } ALERT_ACTION_OPTIONS;
12
13 +typedef enum __attribute__((packed)) {
14 + ALERT_LOOKUP_DATA_SOURCE_SAMPLES = 0,
15 + ALERT_LOOKUP_DATA_SOURCE_PERCENTAGES,
16 + ALERT_LOOKUP_DATA_SOURCE_ANOMALIES,
17 +} ALERT_LOOKUP_DATA_SOURCE;
18 +ALERT_LOOKUP_DATA_SOURCE alerts_data_sources2id(const char *source);
19 +const char *alerts_data_source_id2source(ALERT_LOOKUP_DATA_SOURCE source);
20 +
21 +typedef enum __attribute__((packed)) {
22 + ALERT_LOOKUP_DIMS_SUM = 0,
23 + ALERT_LOOKUP_DIMS_MIN,
24 + ALERT_LOOKUP_DIMS_MAX,
25 + ALERT_LOOKUP_DIMS_AVERAGE,
26 + ALERT_LOOKUP_DIMS_MIN2MAX,
27 +} ALERT_LOOKUP_DIMS_GROUPING;
28 +ALERT_LOOKUP_DIMS_GROUPING alerts_dims_grouping2id(const char *group);
29 +const char *alerts_dims_grouping_id2group(ALERT_LOOKUP_DIMS_GROUPING grouping);
30 +
31 +typedef enum __attribute__((packed)) {
32 + ALERT_LOOKUP_TIME_GROUP_CONDITION_EQUAL,
33 + ALERT_LOOKUP_TIME_GROUP_CONDITION_NOT_EQUAL,
34 + ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER,
35 + ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS,
36 + ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER_EQUAL,
37 + ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS_EQUAL,
38 +} ALERT_LOOKUP_TIME_GROUP_CONDITION;
39 +ALERT_LOOKUP_TIME_GROUP_CONDITION alerts_group_condition2id(const char *source);
40 +const char *alerts_group_conditions_id2txt(ALERT_LOOKUP_TIME_GROUP_CONDITION source);
41 +
42 struct rrd_alert_match {
43 bool enabled;
44
@@ -44,21 +73,20 @@ struct rrd_alert_config {
73 STRING *units; // the units of the alarm
74 STRING *summary; // a short alert summary
75 STRING *info; // a description of the alarm
47 - STRING *lookup; // the lookup field
76
77 int update_every; // update frequency for the alarm
78
79 ALERT_ACTION_OPTIONS alert_action_options;
80
53 - // the red and green threshold of this alarm (to be set to the chart)
54 - NETDATA_DOUBLE green;
55 - NETDATA_DOUBLE red;
56 -
81 // ------------------------------------------------------------------------
82 // database lookup settings
83
84 STRING *dimensions; // the chart dimensions
61 - RRDR_TIME_GROUPING group; // grouping method: average, max, etc.
85 + RRDR_TIME_GROUPING time_group; // grouping method: average, max, etc.
86 + ALERT_LOOKUP_TIME_GROUP_CONDITION time_group_condition;
87 + NETDATA_DOUBLE time_group_value;
88 + ALERT_LOOKUP_DIMS_GROUPING dims_group; // grouping method for dimensions
89 + ALERT_LOOKUP_DATA_SOURCE data_source;
90 int before; // ending point in time-series
91 int after; // starting point in time-series
92 RRDR_OPTIONS options; // configuration options
src/health/health_variable.c
-20
@@ -166,8 +166,6 @@ bool alert_variable_lookup_internal(STRING *variable, void *data, NETDATA_DOUBLE
166 *warning_string = NULL,
167 *critical_string = NULL,
168 *last_collected_t_string = NULL,
169 - *green_string = NULL,
170 - *red_string = NULL,
169 *update_every_string = NULL;
170
171
@@ -202,8 +200,6 @@ bool alert_variable_lookup_internal(STRING *variable, void *data, NETDATA_DOUBLE
200 warning_string = string_strdupz("WARNING");
201 critical_string = string_strdupz("CRITICAL");
202 last_collected_t_string = string_strdupz("last_collected_t");
205 - green_string = string_strdupz("green");
206 - red_string = string_strdupz("red");
203 update_every_string = string_strdupz("update_every");
204 }
205
@@ -311,22 +307,6 @@ bool alert_variable_lookup_internal(STRING *variable, void *data, NETDATA_DOUBLE
307 goto log;
308 }
309
314 - if(unlikely(variable == green_string)) {
315 - *result = (NETDATA_DOUBLE)rc->config.green;
316 - source = "current alert green threshold";
317 - source_st = st;
318 - found = true;
319 - goto log;
320 - }
321 -
322 - if(unlikely(variable == red_string)) {
323 - *result = (NETDATA_DOUBLE)rc->config.red;
324 - source = "current alert red threshold";
325 - source_st = st;
326 - found = true;
327 - goto log;
328 - }
329 -
310 // find the dimension id/name
311
312 vbd = (struct variable_lookup_job){
src/health/rrdcalc.c
-7
@@ -339,12 +339,6 @@ static void rrdcalc_rrdhost_insert_callback(const DICTIONARY_ITEM *item __maybe_
339
340 rc->id = rrdcalc_get_unique_id(host, rc->chart, rc->config.name, &rc->next_event_id, &rc->config.hash_id);
341
342 - if(!isnan(rc->config.green) && isnan(st->green))
343 - st->green = rc->config.green;
344 -
345 - if(!isnan(rc->config.red) && isnan(st->red))
346 - st->red = rc->config.red;
347 -
342 expression_set_variable_lookup_callback(rc->config.calculation, alert_variable_lookup, rc);
343 expression_set_variable_lookup_callback(rc->config.warning, alert_variable_lookup, rc);
344 expression_set_variable_lookup_callback(rc->config.critical, alert_variable_lookup, rc);
@@ -496,7 +490,6 @@ void rrd_alert_config_cleanup(struct rrd_alert_config *ac) {
490 string_freez(ac->units);
491 string_freez(ac->summary);
492 string_freez(ac->info);
499 - string_freez(ac->lookup);
493
494 string_freez(ac->dimensions);
495
src/health/schema.d/health:alert:prototype.json
+370 -245
@@ -15,12 +15,12 @@
15 "matchInstance": {
16 "type": "object",
17 "title": "Apply this rule to a single instance",
18 - "description": "This rule will be applied to a specific instance on all nodes",
18 + "description": "This is a single alert rule that will be applied to the specific instance on all nodes hosted on this Netdata.",
19 "properties": {
20 "on": {
21 "type": "string",
22 "default": "",
23 - "title": "The instance this rule should be applied to",
23 + "title": "The instance this rule should be applied to.",
24 "description": "You can find the instance names on all charts at the instances drop down menu. Do not include the host name in this field."
25 },
26 "host_labels": { "$ref": "#/definitions/matchHostLabels" },
@@ -35,12 +35,12 @@
35 "matchTemplate": {
36 "type": "object",
37 "title": "Apply this rule to all instances of a context",
38 - "description": "This rule will applied to all instances on all nodes.",
38 + "description": "This rule defines a template, that will apply this alert to all instances (e.g. disks, network interfaces, nginx servers, etc) on all nodes hosted on this Netdata.",
39 "properties": {
40 "on": {
41 "type": "string",
42 "default": "",
43 - "title": "The context of the instances this rule should be applied to",
43 + "title": "The context of the instances this rule should be applied to.",
44 "description": "The context is the code-name of each chart on the dashboard, that appears at the chart title bar, between the chart title and its unit of measurement, like: system.cpu, disk.io, etc."
45 },
46 "host_labels": { "$ref": "#/definitions/matchHostLabels" },
@@ -52,243 +52,347 @@
52 "instance_labels"
53 ]
54 },
55 - "config": {
55 + "configSummary": {
56 + "type": "string",
57 + "title": "Short description of the alert",
58 + "description": "This field is used in notification as a short description of the alert. Variables, like ${label:key}, are replaced with the value of instance label called 'key'."
59 + },
60 + "configInfo": {
61 + "type": "string",
62 + "title": "Long description of the alert",
63 + "description": "This field is used to provide enough information about the type and nature of the alert. Variables, like ${label:key}, are replaced with the value of instance label called 'key'."
64 + },
65 + "configType": {
66 + "type": "string",
67 + "title": "Alert Type",
68 + "description": "Use categories like: 'System', 'Containers', 'Web Servers', 'Message Brokers', etc."
69 + },
70 + "configComponent": {
71 + "type": "string",
72 + "title": "Alert Component",
73 + "description": "Component is a sub-type of Alert Type. Examples: 'CPU', 'Memory', 'Network', 'Disk', 'Hardware', 'nginx', 'redis', 'postgresql', etc."
74 + },
75 + "configClassification": {
76 + "type": "string",
77 + "title": "Classification",
78 + "description": "Use 'Workload', 'Utilization', 'Latency', 'Availability', 'Errors', etc."
79 + },
80 + "configValue": {
81 "type": "object",
57 - "title": "Alert Configuration",
58 - "description": "The properties that control the value the alert will get, the conditions it will trigger, the back-off for notifications, the auto-repeating of notifications, etc.",
82 + "title": "",
83 + "description": "Each alert has a value. This section defines how this value is calculated.",
84 "properties": {
60 - "summary": {
61 - "type": "string",
62 - "title": "Short description of the alert",
63 - "description": "This field is used in notification as a short description of the alert. Variables, like ${label:key}, are replaced with the value of instance label called 'key'."
64 - },
65 - "info": {
66 - "type": "string",
67 - "title": "Long description of the alert",
68 - "description": "This field is used to provide enough information about the type and nature of the alert. Variables, like ${label:key}, are replaced with the value of instance label called 'key'."
69 - },
70 - "type": {
71 - "type": "string",
72 - "title": "Alert Type",
73 - "description": "Use categories like: 'System', 'Containers', 'Web Servers', 'Message Brokers', etc."
74 - },
75 - "component": {
76 - "type": "string",
77 - "title": "Alert Component",
78 - "description": "Component is a sub-type of Alert Type. Examples: 'CPU', 'Memory', 'Network', 'Disk', 'Hardware', 'nginx', 'redis', 'postgresql', etc."
79 - },
80 - "classification": {
81 - "type": "string",
82 - "title": "Classification",
83 - "description": "Use 'Workload', 'Utilization', 'Latency', 'Availability', 'Errors', etc."
84 - },
85 - "value": {
85 + "database_lookup": {
86 "type": "object",
87 - "title": "",
88 - "description": "Each alert has a value. This section defines how this value is calculated.",
87 + "title": "Database Query to Get Value",
88 + "description": "The database query to be executed to calculate the value of the alert. When set, the query is executed before any other calculations. The result of the query will be available as $this in further calculations.",
89 "properties": {
90 - "database_lookup": {
91 - "type": "object",
92 - "title": "Database Query to Get Value",
93 - "description": "The database query to be executed to calculate the value of the alert. When set, the query is executed before any other calculations. The result of the query will be available as $this in further calculations.",
94 - "properties": {
95 - "after": {
96 - "type": "integer",
97 - "default": 0,
98 - "title": "From",
99 - "description": "Starting timestamp"
100 - },
101 - "before": {
102 - "type": "integer",
103 - "default": 0,
104 - "title": "To",
105 - "description": "Ending timestamp"
106 - },
107 - "dimensions": {
108 - "type": "string",
109 - "title": "Dimensions",
110 - "description": "Simple pattern",
111 - "default": "*"
112 - },
113 - "grouping": {
114 - "type": "string",
115 - "oneOf": [
116 - { "const": "average", "title": "The mean (average) value" },
117 - { "const": "median", "title": "The median value" },
118 - { "const": "min", "title": "The minimum value" },
119 - { "const": "max", "title": "The maximum value" },
120 - { "const": "sum", "title": "The sum of all the values" },
121 - { "const": "incremental_sum", "title": "The delta of the latest and oldest values" },
122 - { "const": "stddev", "title": "The standard deviation of the values" },
123 - { "const": "cv", "title": "The standard deviation expresses as a % of the mean value" },
124 - { "const": "trimmed-mean1", "title": "The mean after trimming 1% of the extreme values" },
125 - { "const": "trimmed-mean2", "title": "The mean after trimming 2% of the extreme values" },
126 - { "const": "trimmed-mean3", "title": "The mean after trimming 3% of the extreme values" },
127 - { "const": "trimmed-mean", "title": "The mean after trimming 5% of the extreme values" },
128 - { "const": "trimmed-mean10", "title": "The mean after trimming 10% of the extreme values" },
129 - { "const": "trimmed-mean15", "title": "The mean after trimming 15% of the extreme values" },
130 - { "const": "trimmed-mean20", "title": "The mean after trimming 20% of the extreme values" },
131 - { "const": "trimmed-mean25", "title": "The mean after trimming 25% of the extreme values" },
132 - { "const": "trimmed-median1", "title": "The median after trimming 1% of the extreme values" },
133 - { "const": "trimmed-median2", "title": "The median after trimming 2% of the extreme values" },
134 - { "const": "trimmed-median3", "title": "The median after trimming 3% of the extreme values" },
135 - { "const": "trimmed-median", "title": "The median after trimming 5% of the extreme values" },
136 - { "const": "trimmed-median10", "title": "The median after trimming 10% of the extreme values" },
137 - { "const": "trimmed-median15", "title": "The median after trimming 15% of the extreme values" },
138 - { "const": "trimmed-median20", "title": "The median after trimming 20% of the extreme values" },
139 - { "const": "trimmed-median25", "title": "The median after trimming 25% of the extreme values" },
140 - { "const": "percentile99", "title": "The 99th percentile of the values" },
141 - { "const": "percentile98", "title": "The 98th percentile of the values" },
142 - { "const": "percentile97", "title": "The 97th percentile of the values" },
143 - { "const": "percentile", "title": "The 95th percentile of the values" },
144 - { "const": "percentile90", "title": "The 90th percentile of the values" },
145 - { "const": "percentile80", "title": "The 80th percentile of the values" },
146 - { "const": "percentile75", "title": "The 75th percentile of the values" },
147 - { "const": "percentile50", "title": "The 50th percentile of the values" },
148 - { "const": "percentile25", "title": "The 25th percentile of the values" },
149 - { "const": "ses", "title": "Single Exponential Smoothing" },
150 - { "const": "des", "title": "Double Exponential Smoothing" },
151 - { "const": "countif", "title": "Count If zero" }
152 - ],
153 - "default": "average",
154 - "title": "Time Aggregation Function",
155 - "description": "When querying time-series data we need to come up with a single value. This function is used to aggregate all the values of the time-series data to a single value."
156 - },
157 - "options": {
158 - "type": "array",
159 - "title": "Time-Series Query options",
160 - "description": "Options affecting the way the value is calculated",
161 - "uniqueItems": true,
162 - "items": {
163 - "oneOf": [
164 - { "const": "unaligned", "title": "Do not shift the time-frame for visual presentation" },
165 - { "const": "abs", "title": "Make all values positive before using them" },
166 - { "const": "min2max", "title": "Use the delta of the minimum to the maximum value" },
167 - { "const": "null2zero", "title": "Treat gaps in the time-series as a zero value" },
168 - { "const": "percentage", "title": "Calculate the percentage of the selected dimensions over the sum of all dimensions" },
169 - { "const": "anomaly-bit", "title": "Query the anomaly rate of the samples collected" },
170 - { "const": "match_ids", "title": "Match only dimension IDs, not Names" },
171 - { "const": "match_names", "title": "Match only dimension Names, not IDs" }
172 - ]
173 - },
174 - "default": [ "unaligned" ]
175 - }
176 - }
177 - },
178 - "calculation": {
90 + "data_source": {
91 "type": "string",
180 - "title": "Calculation",
181 - "description": "An expression to transform the value"
92 + "oneOf": [
93 + { "const": "samples", "title": "Samples", "description": "Use the time-series values for each dimension" },
94 + { "const": "percentages", "title": "Percentages", "description": "Use the percentage of each dimension vs the sum of all dimensions" },
95 + { "const": "anomalies", "title": "Anomalies", "description": "Use the anomaly rate of each dimension" }
96 + ],
97 + "default": "samples",
98 + "title": " ",
99 + "description": ""
100 },
183 - "units": {
101 + "time_group": {
102 "type": "string",
185 - "title": "Unit",
186 - "description": "of measurement"
187 - }
188 - }
189 - },
190 - "conditions": {
191 - "type": "object",
192 - "title": "",
193 - "properties": {
194 - "warning_condition": {
195 - "type": "string",
196 - "title": "Warning Expression",
197 - "description": "The alert value is available as '$this'. If this expression evaluates to a non-zero value, the alert is considered to be in warning level."
103 + "oneOf": [
104 + { "const": "average", "title": "Average" },
105 + { "const": "median", "title": "Median" },
106 + { "const": "min", "title": "Minimum" },
107 + { "const": "max", "title": "Maximum" },
108 + { "const": "sum", "title": "Sum" },
109 + { "const": "incremental_sum", "title": "Incremental Sum" },
110 + { "const": "stddev", "title": "Standard Deviation" },
111 + { "const": "cv", "title": "Coefficient of Variation" },
112 + { "const": "trimmed-mean", "title": "Trimmed Mean" },
113 + { "const": "trimmed-median", "title": "Trimmed Median" },
114 + { "const": "percentile", "title": "Percentile" },
115 + { "const": "ses", "title": "Simple Exponential Smoothing" },
116 + { "const": "des", "title": "Double Exponential Smoothing" },
117 + { "const": "countif", "title": "Count If" }
118 + ],
119 + "default": "average",
120 + "title": "Time Aggregation",
121 + "description": ""
122 },
199 - "critical_condition": {
200 - "type": "string",
201 - "title": "Critical Expression",
202 - "description": "The alert value is available as '$this'. If this expression evaluates to a non-zero value, the alert is considered to be in critical level."
123 + "after": {
124 + "type": "integer",
125 + "default": -600,
126 + "title": "From",
127 + "description": "Relative to 'To'"
128 },
204 - "green": {
205 - "type": [
206 - "integer",
207 - "null"
208 - ],
209 - "title": "Healthy threshold",
210 - "description": "A threshold that indicates a healthy status. This threshold can be used as '$green' in the alert conditions."
129 + "before": {
130 + "type": "integer",
131 + "default": 0,
132 + "title": "To",
133 + "description": "Ending timestamp"
134 },
212 - "red": {
213 - "type": [
214 - "integer",
215 - "null"
216 - ],
217 - "title": "Critical threshold",
218 - "description": "A threshold that indicates a critical status. This threshold can be used as '$red' in the alert conditions."
219 - }
220 - }
221 - },
222 - "action": {
223 - "type": "object",
224 - "title": "",
225 - "description": "The action the alert should take when it transitions states",
226 - "properties": {
227 - "execute": {
135 + "dims_group": {
136 "type": "string",
229 - "title": "Command to execute when the alert transitions states"
137 + "oneOf": [
138 + { "const": "average", "title": "Average", "description": "The average of all dimensions" },
139 + { "const": "min", "title": "Minimum", "description": "The minimum of all dimensions" },
140 + { "const": "max", "title": "Maximum", "description": "The maximum of all dimensions" },
141 + { "const": "sum", "title": "Sum", "description": "The sum of all dimensions" },
142 + { "const": "min2max", "title": "Min-to-Max", "description": "The delta between the minimum of the maximum of the dimensions" }
143 + ],
144 + "default": "sum",
145 + "title": "Dims Aggregation",
146 + "description": "on dimensions"
147 },
231 - "recipient": {
148 + "dimensions": {
149 "type": "string",
233 - "title": "Recipient(s)"
150 + "title": "Dimensions",
151 + "description": "Simple pattern",
152 + "default": "*"
153 },
154 "options": {
155 "type": "array",
237 - "title": "Action Options",
156 + "title": "Time-Series Query options",
157 + "description": "Options affecting the way the value is calculated",
158 "uniqueItems": true,
159 "items": {
160 "oneOf": [
241 - { "const": "no-clear-notification", "title": "Do not perform any action when the alert is cleared"}
161 + { "const": "unaligned", "title": "Do not shift the time-frame for visual presentation" },
162 + { "const": "abs", "title": "Make all values positive before using them" },
163 + { "const": "null2zero", "title": "Treat gaps in the time-series as a zero value" },
164 + { "const": "match_ids", "title": "Match only dimension IDs, not Names" },
165 + { "const": "match_names", "title": "Match only dimension Names, not IDs" }
166 ]
167 },
244 - "default": []
245 - },
246 - "delay": {
247 - "type": "object",
248 - "title": "Delay the action (notification)",
249 - "description": "Rules to postpone the action, to avoid multiple notifications on flapping alerts.",
250 - "properties": {
251 - "up": {
252 - "type": "integer",
253 - "title": "Delay when raising"
254 - },
255 - "down": {
256 - "type": "integer",
257 - "title": "Delay when going Down"
258 - },
259 - "multiplier": {
260 - "type": "number",
261 - "title": "Back-Off"
262 - },
263 - "max": {
264 - "type": "integer",
265 - "title": "Max"
168 + "default": [ "unaligned" ]
169 + }
170 + },
171 + "allOf": [
172 + {
173 + "if": {
174 + "properties": {
175 + "time_group": {
176 + "enum": ["trimmed-mean"]
177 + }
178 }
179 + },
180 + "then": {
181 + "properties": {
182 + "time_group_value": {
183 + "type": "integer",
184 + "default": 1,
185 + "title": "Trim %",
186 + "description": ""
187 + }
188 + },
189 + "required": ["time_group_value"]
190 }
191 },
269 - "repeat": {
270 - "type": "object",
271 - "title": "Auto-Repeat Action",
272 - "description": "Repeat the action while the alert is raised.",
273 - "properties": {
274 - "enabled": {
275 - "type": "boolean"
192 + {
193 + "if": {
194 + "properties": {
195 + "time_group": {
196 + "enum": ["trimmed-median"]
197 + }
198 + }
199 + },
200 + "then": {
201 + "properties": {
202 + "time_group_value": {
203 + "type": "integer",
204 + "default": 1,
205 + "title": "Trim %",
206 + "description": ""
207 + }
208 },
277 - "warning": {
278 - "type": "integer",
279 - "title": "Repeat on Warning"
209 + "required": ["time_group_value"]
210 + }
211 + },
212 + {
213 + "if": {
214 + "properties": {
215 + "time_group": {
216 + "enum": ["percentile"]
217 + }
218 + }
219 + },
220 + "then": {
221 + "properties": {
222 + "time_group_value": {
223 + "type": "integer",
224 + "default": 95,
225 + "title": "Percentage",
226 + "description": ""
227 + }
228 },
281 - "critical": {
282 - "type": "integer",
283 - "title": "Repeat on Critical"
229 + "required": ["time_group_value"]
230 + }
231 + },
232 + {
233 + "if": {
234 + "properties": {
235 + "time_group": {
236 + "const": "countif"
237 + }
238 }
239 + },
240 + "then": {
241 + "properties": {
242 + "time_group_condition": {
243 + "type": "string",
244 + "oneOf": [
245 + { "const": "!=", "title": "!=" },
246 + { "const": "=", "title": "==" },
247 + { "const": ">=", "title": ">=" },
248 + { "const": ">", "title": ">" },
249 + { "const": "<=", "title": "<=" },
250 + { "const": "<", "title": "<" }
251 + ],
252 + "default": "equal",
253 + "title": "Condition",
254 + "description": ""
255 + },
256 + "time_group_value": {
257 + "type": "number",
258 + "default": 1,
259 + "title": "Value to match",
260 + "description": ""
261 + }
262 + },
263 + "required": ["time_group_condition", "time_group_value"]
264 }
265 }
266 + ]
267 + },
268 + "calculation": {
269 + "type": "string",
270 + "title": "Calculation",
271 + "description": "An expression to transform the value"
272 + },
273 + "units": {
274 + "type": "string",
275 + "title": "Unit",
276 + "description": "of measurement"
277 + }
278 + }
279 + },
280 + "configConditions": {
281 + "type": "object",
282 + "title": "",
283 + "properties": {
284 + "warning_condition": {
285 + "type": "string",
286 + "title": "Warning Expression",
287 + "description": "The alert value is available as '$this'. If this expression evaluates to a non-zero value, the alert is considered to be in warning level."
288 + },
289 + "critical_condition": {
290 + "type": "string",
291 + "title": "Critical Expression",
292 + "description": "The alert value is available as '$this'. If this expression evaluates to a non-zero value, the alert is considered to be in critical level."
293 + }
294 + }
295 + },
296 + "configAction": {
297 + "type": "object",
298 + "title": "",
299 + "description": "The action the alert should take when it transitions states",
300 + "properties": {
301 + "execute": {
302 + "type": "string",
303 + "title": "Command to execute when the alert transitions states"
304 + },
305 + "recipient": {
306 + "type": "string",
307 + "title": "Recipient(s)"
308 + },
309 + "options": {
310 + "type": "array",
311 + "title": "Action Options",
312 + "uniqueItems": true,
313 + "items": {
314 + "oneOf": [
315 + { "const": "no-clear-notification", "title": "Do not perform any action when the alert is cleared"}
316 + ]
317 + },
318 + "default": []
319 + },
320 + "delay": {
321 + "type": "object",
322 + "title": "Delay the action (notification)",
323 + "description": "Rules to postpone the action, to avoid multiple notifications on flapping alerts.",
324 + "properties": {
325 + "up": {
326 + "type": "integer",
327 + "title": "Delay when raising"
328 + },
329 + "down": {
330 + "type": "integer",
331 + "title": "Delay when going Down"
332 + },
333 + "multiplier": {
334 + "type": "number",
335 + "title": "Back-Off"
336 + },
337 + "max": {
338 + "type": "integer",
339 + "title": "Max"
340 + }
341 + }
342 + },
343 + "repeat": {
344 + "type": "object",
345 + "title": "Auto-Repeat Action",
346 + "description": "Repeat the action while the alert is raised.",
347 + "properties": {
348 + "enabled": {
349 + "type": "boolean"
350 + },
351 + "warning": {
352 + "type": "integer",
353 + "title": "Repeat on Warning"
354 + },
355 + "critical": {
356 + "type": "integer",
357 + "title": "Repeat on Critical"
358 + }
359 }
360 }
361 + }
362 + },
363 + "configInstance": {
364 + "type": "object",
365 + "title": "Alert Configuration",
366 + "description": "The properties that control the value the alert will get, the conditions it will trigger, the back-off for notifications, the auto-repeating of notifications, etc.",
367 + "properties": {
368 + "match": { "$ref": "#/definitions/matchInstance" },
369 + "summary": { "$ref": "#/definitions/configSummary" },
370 + "info": { "$ref": "#/definitions/configInfo" },
371 + "type": { "$ref": "#/definitions/configType" },
372 + "component": { "$ref": "#/definitions/configComponent" },
373 + "classification": { "$ref": "#/definitions/configClassification" },
374 + "value": { "$ref": "#/definitions/configValue" },
375 + "conditions": { "$ref": "#/definitions/configConditions" },
376 + "actions": { "$ref": "#/definitions/configAction" }
377 },
290 - "required": [
291 - ]
378 + "required": []
379 + },
380 + "configTemplate": {
381 + "type": "object",
382 + "title": "Alert Configuration",
383 + "description": "The properties that control the value the alert will get, the conditions it will trigger, the back-off for notifications, the auto-repeating of notifications, etc.",
384 + "properties": {
385 + "match": { "$ref": "#/definitions/matchTemplate" },
386 + "summary": { "$ref": "#/definitions/configSummary" },
387 + "info": { "$ref": "#/definitions/configInfo" },
388 + "type": { "$ref": "#/definitions/configType" },
389 + "component": { "$ref": "#/definitions/configComponent" },
390 + "classification": { "$ref": "#/definitions/configClassification" },
391 + "value": { "$ref": "#/definitions/configValue" },
392 + "conditions": { "$ref": "#/definitions/configConditions" },
393 + "action": { "$ref": "#/definitions/configAction" }
394 + },
395 + "required": []
396 }
397 },
398 "type": "object",
@@ -309,17 +413,17 @@
413 "type": "boolean",
414 "default": true,
415 "title": "Enabled",
312 - "description": "Enable or disable this rule."
416 + "description": ""
417 },
418 "type": {
419 "type": "string",
420 "oneOf": [
317 - { "const": "instance" , "title": "Apply this rule to a specific instance (deprecated)" },
318 - { "const": "template" , "title": "Apply this rule to all instances" }
421 + { "const": "instance" , "title": "A specific Instance" },
422 + { "const": "template" , "title": "Each of the Instances" }
423 ],
424 "default": "template",
321 - "title": "Type of rule",
322 - "description": "Select the type of this rule."
425 + "title": "Apply this rule to:",
426 + "description": ""
427 }
428 },
429 "required": [ "type", "enabled" ],
@@ -330,14 +434,12 @@
434 },
435 "then": {
436 "properties": {
333 - "match": { "$ref": "#/definitions/matchInstance" },
334 - "config": { "$ref": "#/definitions/config" }
437 + "config": { "$ref": "#/definitions/configInstance" }
438 }
439 },
440 "else": {
441 "properties": {
339 - "match": { "$ref": "#/definitions/matchTemplate" },
340 - "config": { "$ref": "#/definitions/config" }
442 + "config": { "$ref": "#/definitions/configTemplate" }
443 }
444 }
445 }
@@ -365,21 +467,11 @@
467 "ui:widget": "checkbox"
468 },
469 "type": {
368 - "ui:classNames": "dyncfg-grid-col-span-3-4",
369 - "ui:help": "Rules can be configured to match a specific instance (like a specific disk), or match all the instances (like all the disks). All rules are always checked against all nodes streamed to this Netdata, so the matching rules include patterns to match both instances and nodes."
370 - },
371 - "match": {
372 - "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
373 - "on": {
374 - "ui:classNames": "dyncfg-grid-col-span-1-6"
375 - },
376 - "host_labels": {
377 - "ui:help": "A simple pattern to match the node labels of the nodes this rule is to be applied to. A space separated list of label=value pairs is accepted. Asterisks can be placed anywhere, including the label key. The label keys and their values are available at the labels filter of the charts on the dashboard.",
378 - "ui:classNames": "dyncfg-grid-col-span-1-3"
379 - },
380 - "instance_labels": {
381 - "ui:classNames": "dyncfg-grid-col-span-4-3",
382 - "ui:help": "A simple pattern to match the instance labels of the instances this rule is to be applied to. A space separated list of label=value pairs is accepted. Asterisks can be placed anywhere, including the label key. The label keys and their values are available at the labels filter of the charts on the dashboard."
470 + "ui:classNames": "dyncfg-grid-col-span-5-2",
471 + "ui:help": "Rules can be configured to match a specific instance (like a specific disk), or match all the instances (like all the disks). All rules are always checked against all nodes streamed to this Netdata, so the matching rules include patterns to match both instances and nodes.",
472 + "ui:widget": "radio",
473 + "ui:options": {
474 + "flavour": "buttonGroup"
475 }
476 },
477 "config": {
@@ -387,6 +479,12 @@
479 "ui:flavour": "tabs",
480 "ui:options": {
481 "tabs": [
482 + {
483 + "title": "Match",
484 + "fields": [
485 + "match"
486 + ]
487 + },
488 {
489 "title": "Value",
490 "fields": [
@@ -417,6 +515,20 @@
515 }
516 ]
517 },
518 + "match": {
519 + "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
520 + "on": {
521 + "ui:classNames": "dyncfg-grid-col-span-1-6"
522 + },
523 + "host_labels": {
524 + "ui:help": "A simple pattern to match the node labels of the nodes this rule is to be applied to. A space separated list of label=value pairs is accepted. Asterisks can be placed anywhere, including the label key. The label keys and their values are available at the labels filter of the charts on the dashboard.",
525 + "ui:classNames": "dyncfg-grid-col-span-1-3"
526 + },
527 + "instance_labels": {
528 + "ui:classNames": "dyncfg-grid-col-span-4-3",
529 + "ui:help": "A simple pattern to match the instance labels of the instances this rule is to be applied to. A space separated list of label=value pairs is accepted. Asterisks can be placed anywhere, including the label key. The label keys and their values are available at the labels filter of the charts on the dashboard."
530 + }
531 + },
532 "summary": {
533 "ui:classNames": "dyncfg-grid-col-span-1-3"
534 },
@@ -435,9 +547,27 @@
547 "value": {
548 "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
549 "database_lookup": {
550 + "ui:order": ["data_source", "time_group", "time_group_condition", "time_group_value", "after", "before", "dims_group", "dimensions", "options"],
551 "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
552 "ui:collapsible": true,
553 "ui:initiallyExpanded": true,
554 + "data_source": {
555 + "ui:widget": "radio",
556 + "ui:options": {
557 + "flavour": "buttonGroup"
558 + },
559 + "ui:classNames": "dyncfg-grid-col-span-1-6"
560 + },
561 + "time_group": {
562 + "ui:help": "When querying time-series data we need to come up with a single value. This function is used to aggregate all the values of the time-series data to a single value.",
563 + "ui:classNames": "dyncfg-grid-col-span-1-2"
564 + },
565 + "time_group_condition": {
566 + "ui:classNames": "dyncfg-grid-col-span-3-1"
567 + },
568 + "time_group_value": {
569 + "ui:classNames": "dyncfg-grid-col-span-4-1"
570 + },
571 "after": {
572 "ui:help": "The oldest timestamp of the time-series data to be included in the query. Negative values define a duration in seconds in the past of 'To' (so, -60 means a minute ago from 'To').",
573 "ui:classNames": "dyncfg-grid-col-span-1-1"
@@ -446,12 +576,13 @@
576 "ui:help": "The newest timestamp of the time-series data to be included in the query. Negative value define a duration in seconds in the past (so, -60 means a minute ago). Zero means now.",
577 "ui:classNames": "dyncfg-grid-col-span-2-1"
578 },
579 + "dims_group": {
580 + "ui:help": "After each dimension has a single computed value, use this algorithm to derive the final value.",
581 + "ui:classNames": "dyncfg-grid-col-span-3-2"
582 + },
583 "dimensions": {
584 "ui:help": "A simple pattern to match the dimensions that should be included in the query",
451 - "ui:classNames": "dyncfg-grid-col-span-3-4"
452 - },
453 - "grouping": {
454 - "ui:classNames": "dyncfg-grid-col-span-1-6"
585 + "ui:classNames": "dyncfg-grid-col-span-5-2"
586 },
587 "options": {
588 "ui:classNames": "dyncfg-grid-col-span-1-6"
@@ -470,16 +601,10 @@
601 "conditions": {
602 "ui:classNames": "dyncfg-grid dyncfg-grid-col-6 dyncfg-grid-col-span-1-6",
603 "warning_condition": {
473 - "ui:classNames": "dyncfg-grid-col-span-1-3"
604 + "ui:classNames": "dyncfg-grid-col-span-1-6"
605 },
606 "critical_condition": {
476 - "ui:classNames": "dyncfg-grid-col-span-4-3"
477 - },
478 - "green": {
479 - "ui:classNames": "dyncfg-grid-col-span-1-3"
480 - },
481 - "red": {
482 - "ui:classNames": "dyncfg-grid-col-span-4-3"
607 + "ui:classNames": "dyncfg-grid-col-span-1-6"
608 }
609 },
610 "action": {
src/libnetdata/eval/eval.c
+107 -1
@@ -2,6 +2,13 @@
2
3 #include "../libnetdata.h"
4
5 +typedef enum __attribute__((packed)) {
6 + EVAL_VALUE_INVALID = 0,
7 + EVAL_VALUE_NUMBER,
8 + EVAL_VALUE_VARIABLE,
9 + EVAL_VALUE_EXPRESSION
10 +} EVAL_VALUE_TYPE;
11 +
12 // ----------------------------------------------------------------------------
13 // data structures for storing the parsed expression in memory
14
@@ -11,7 +18,7 @@ typedef struct eval_variable {
18 } EVAL_VARIABLE;
19
20 typedef struct eval_value {
14 - int type;
21 + EVAL_VALUE_TYPE type;
22
23 union {
24 NETDATA_DOUBLE number;
@@ -1143,3 +1150,102 @@ void expression_set_variable_lookup_callback(EVAL_EXPRESSION *expression, eval_e
1150 expression->variable_lookup_cb = cb;
1151 expression->variable_lookup_cb_data = data;
1152 }
1153 +
1154 +static size_t expression_hardcode_node_variable(EVAL_NODE *node, STRING *variable, NETDATA_DOUBLE value) {
1155 + size_t matches = 0;
1156 +
1157 + for(int i = 0; i < node->count; i++) {
1158 + switch(node->ops[i].type) {
1159 + case EVAL_VALUE_NUMBER:
1160 + case EVAL_VALUE_INVALID:
1161 + break;
1162 +
1163 + case EVAL_VALUE_VARIABLE:
1164 + if(node->ops[i].variable->name == variable) {
1165 + string_freez(node->ops[i].variable->name);
1166 + freez(node->ops[i].variable);
1167 + node->ops[i].type = EVAL_VALUE_NUMBER;
1168 + node->ops[i].number = value;
1169 + matches++;
1170 + }
1171 + break;
1172 +
1173 + case EVAL_VALUE_EXPRESSION:
1174 + matches += expression_hardcode_node_variable(node->ops[i].expression, variable, value);
1175 + break;
1176 + }
1177 + }
1178 +
1179 + return matches;
1180 +}
1181 +
1182 +void expression_hardcode_variable(EVAL_EXPRESSION *expression, STRING *variable, NETDATA_DOUBLE value) {
1183 + if (!expression || !variable || isnan(value))
1184 + return;
1185 +
1186 + size_t matches = expression_hardcode_node_variable(expression->nodes, variable, value);
1187 + if (matches) {
1188 + char replace[1024];
1189 + snprintfz(replace, sizeof(replace), NETDATA_DOUBLE_FORMAT_AUTO, value);
1190 + size_t replace_len = strlen(replace);
1191 +
1192 + size_t source_len = string_strlen(expression->source);
1193 + const char *source_str = string2str(expression->source);
1194 +
1195 + // Allocate enough space to accommodate all replacements.
1196 + char buf[source_len + 1 + matches * (replace_len + 1)];
1197 +
1198 + char find1[string_strlen(variable) + 1 + 1];
1199 + snprintfz(find1, sizeof(find1), "$%s", string2str(variable));
1200 + size_t find1_len = strlen(find1);
1201 +
1202 + char find2[string_strlen(variable) + 1 + 3];
1203 + snprintfz(find2, sizeof(find2), "${%s}", string2str(variable));
1204 + size_t find2_len = strlen(find2);
1205 +
1206 + size_t found = 0;
1207 + char *buf_ptr = buf;
1208 + const char *source_ptr = source_str;
1209 +
1210 + while (*source_ptr) {
1211 + char *s1 = strstr(source_ptr, find1);
1212 + char *s2 = strstr(source_ptr, find2);
1213 +
1214 + char *s = s1;
1215 + size_t len = find1_len;
1216 + if (s2 && (!s1 || s2 < s1)) {
1217 + s = s2;
1218 + len = find2_len;
1219 + }
1220 +
1221 + if (s) {
1222 + if (s == s1 && (isalnum(s[len]) || s[len] == '_')) {
1223 + // Move past the variable if it's part of a larger word.
1224 + source_ptr = s + len;
1225 + continue;
1226 + }
1227 +
1228 + // Copy the part before the variable.
1229 + memcpy(buf_ptr, source_ptr, s - source_ptr);
1230 + buf_ptr += (s - source_ptr);
1231 +
1232 + // Copy the replacement.
1233 + memcpy(buf_ptr, replace, replace_len);
1234 + buf_ptr += replace_len;
1235 + *buf_ptr = '\0';
1236 +
1237 + // Move the source pointer past the replaced variable.
1238 + source_ptr = s + len;
1239 + found++;
1240 + } else {
1241 + // Copy the rest of the string if no more variables are found.
1242 + strcpy(buf_ptr, source_ptr);
1243 + break;
1244 + }
1245 + }
1246 +
1247 + // Update the expression source with the new string.
1248 + string_freez(expression->source);
1249 + expression->source = string_strdupz(buf);
1250 + }
1251 +}
src/libnetdata/eval/eval.h
+2 -5
@@ -11,11 +11,6 @@ struct eval_expression;
11 typedef struct eval_expression EVAL_EXPRESSION;
12 typedef bool (*eval_expression_variable_lookup_t)(STRING *variable, void *data, NETDATA_DOUBLE *result);
13
14 -#define EVAL_VALUE_INVALID 0
15 -#define EVAL_VALUE_NUMBER 1
16 -#define EVAL_VALUE_VARIABLE 2
17 -#define EVAL_VALUE_EXPRESSION 3
18 -
14 // parsing and evaluation
15 #define EVAL_ERROR_OK 0
16
@@ -56,4 +51,6 @@ const char *expression_error_msg(EVAL_EXPRESSION *expression);
51 NETDATA_DOUBLE expression_result(EVAL_EXPRESSION *expression);
52 void expression_set_variable_lookup_callback(EVAL_EXPRESSION *expression, eval_expression_variable_lookup_t cb, void *data);
53
54 +void expression_hardcode_variable(EVAL_EXPRESSION *expression, STRING *variable, NETDATA_DOUBLE value);
55 +
56 #endif //NETDATA_EVAL_H
src/web/api/formatters/value/value.c
+35 -33
@@ -2,7 +2,6 @@
2
3 #include "value.h"
4
5 -
5 inline NETDATA_DOUBLE rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all_values_are_null, NETDATA_DOUBLE *anomaly_rate) {
6 size_t c;
7
@@ -10,61 +9,64 @@ inline NETDATA_DOUBLE rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all
9 RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ];
10 NETDATA_DOUBLE *ar = &r->ar[ i * r->d ];
11
13 - NETDATA_DOUBLE sum = 0, min = 0, max = 0, v;
14 - int all_null = 1, init = 1;
12 + NETDATA_DOUBLE sum = NAN, min = NAN, max = NAN, v = NAN;
13 + size_t dims = 0;
14
15 NETDATA_DOUBLE total_anomaly_rate = 0;
16
17 // for each dimension
18 for (c = 0; c < r->d ; c++) {
20 - if(!rrdr_dimension_should_be_exposed(r->od[c], options))
19 + if(unlikely(!rrdr_dimension_should_be_exposed(r->od[c], options)))
20 + continue;
21 +
22 + if(unlikely((co[c] & RRDR_VALUE_EMPTY)))
23 continue;
24
25 NETDATA_DOUBLE n = cn[c];
26
25 - if(unlikely(init)) {
26 - if(n > 0) {
27 - min = 0;
28 - max = n;
29 - }
30 - else {
31 - min = n;
32 - max = 0;
33 - }
34 - init = 0;
35 - }
27 + if(unlikely(!dims))
28 + min = max = n;
29
37 - if(likely(!(co[c] & RRDR_VALUE_EMPTY))) {
38 - all_null = 0;
39 - sum += n;
40 - }
30 + sum += n;
31
42 - if(n < min) min = n;
43 - if(n > max) max = n;
32 + if (n < min) min = n;
33 + if (n > max) max = n;
34
35 total_anomaly_rate += ar[c];
46 - }
36
48 - if(anomaly_rate) {
49 - if(!r->d) *anomaly_rate = 0;
50 - else *anomaly_rate = total_anomaly_rate / (NETDATA_DOUBLE)r->d;
37 + dims++;
38 }
39
53 - if(unlikely(all_null)) {
54 - if(likely(all_values_are_null))
40 + if(!dims) {
41 + if(anomaly_rate)
42 + *anomaly_rate = 0;
43 +
44 + if(all_values_are_null)
45 *all_values_are_null = 1;
56 - return 0;
57 - }
58 - else {
59 - if(likely(all_values_are_null))
60 - *all_values_are_null = 0;
46 +
47 + return (options & RRDR_OPTION_NULL2ZERO) ? 0 : NAN;
48 }
49
63 - if(options & RRDR_OPTION_MIN2MAX)
50 + if(anomaly_rate)
51 + *anomaly_rate = total_anomaly_rate / (NETDATA_DOUBLE)dims;
52 +
53 + if(all_values_are_null)
54 + *all_values_are_null = 0;
55 +
56 + if(options & RRDR_OPTION_DIMS_MIN2MAX)
57 v = max - min;
58 + else if(options & RRDR_OPTION_DIMS_AVERAGE)
59 + v = sum / (NETDATA_DOUBLE)dims;
60 + else if(options & RRDR_OPTION_DIMS_MIN)
61 + v = min;
62 + else if(options & RRDR_OPTION_DIMS_MAX)
63 + v = max;
64 else
65 v = sum;
66
67 + if((options & RRDR_OPTION_NULL2ZERO) && (isnan(v) || isinf(v)))
68 + v = 0;
69 +
70 return v;
71 }
72
src/web/api/queries/query.c
+2 -2
@@ -276,7 +276,7 @@ static struct {
276 },
277 {.name = "trimmed-median5",
278 .hash = 0,
279 - .value = RRDR_GROUPING_TRIMMED_MEDIAN5,
279 + .value = RRDR_GROUPING_TRIMMED_MEDIAN,
280 .add_flush = RRDR_GROUPING_MEDIAN,
281 .init = NULL,
282 .create= tg_median_create_trimmed_5,
@@ -336,7 +336,7 @@ static struct {
336 },
337 {.name = "trimmed-median",
338 .hash = 0,
339 - .value = RRDR_GROUPING_TRIMMED_MEDIAN5,
339 + .value = RRDR_GROUPING_TRIMMED_MEDIAN,
340 .add_flush = RRDR_GROUPING_MEDIAN,
341 .init = NULL,
342 .create= tg_median_create_trimmed_5,
src/web/api/queries/query.h
+1 -1
@@ -26,7 +26,7 @@ typedef enum rrdr_time_grouping {
26 RRDR_GROUPING_TRIMMED_MEDIAN1,
27 RRDR_GROUPING_TRIMMED_MEDIAN2,
28 RRDR_GROUPING_TRIMMED_MEDIAN3,
29 - RRDR_GROUPING_TRIMMED_MEDIAN5,
29 + RRDR_GROUPING_TRIMMED_MEDIAN,
30 RRDR_GROUPING_TRIMMED_MEDIAN10,
31 RRDR_GROUPING_TRIMMED_MEDIAN15,
32 RRDR_GROUPING_TRIMMED_MEDIAN20,
src/web/api/queries/rrdr.h
+27 -24
@@ -21,30 +21,33 @@ typedef enum rrdr_options {
21 RRDR_OPTION_NONZERO = (1 << 0), // don't output dimensions with just zero values
22 RRDR_OPTION_REVERSED = (1 << 1), // output the rows in reverse order (oldest to newest)
23 RRDR_OPTION_ABSOLUTE = (1 << 2), // values positive, for DATASOURCE_SSV before summing
24 - RRDR_OPTION_MIN2MAX = (1 << 3), // when adding dimensions, use max - min, instead of sum
25 - RRDR_OPTION_SECONDS = (1 << 4), // output seconds, instead of dates
26 - RRDR_OPTION_MILLISECONDS = (1 << 5), // output milliseconds, instead of dates
27 - RRDR_OPTION_NULL2ZERO = (1 << 6), // do not show nulls, convert them to zeros
28 - RRDR_OPTION_OBJECTSROWS = (1 << 7), // each row of values should be an object, not an array
29 - RRDR_OPTION_GOOGLE_JSON = (1 << 8), // comply with google JSON/JSONP specs
30 - RRDR_OPTION_JSON_WRAP = (1 << 9), // wrap the response in a JSON header with info about the result
31 - RRDR_OPTION_LABEL_QUOTES = (1 << 10), // in CSV output, wrap header labels in double quotes
32 - RRDR_OPTION_PERCENTAGE = (1 << 11), // give values as percentage of total
33 - RRDR_OPTION_NOT_ALIGNED = (1 << 12), // do not align charts for persistent timeframes
34 - RRDR_OPTION_DISPLAY_ABS = (1 << 13), // for badges, display the absolute value, but calculate colors with sign
35 - RRDR_OPTION_MATCH_IDS = (1 << 14), // when filtering dimensions, match only IDs
36 - RRDR_OPTION_MATCH_NAMES = (1 << 15), // when filtering dimensions, match only names
37 - RRDR_OPTION_NATURAL_POINTS = (1 << 16), // return the natural points of the database
38 - RRDR_OPTION_VIRTUAL_POINTS = (1 << 17), // return virtual points
39 - RRDR_OPTION_ANOMALY_BIT = (1 << 18), // Return the anomaly bit stored in each collected_number
40 - RRDR_OPTION_RETURN_RAW = (1 << 19), // Return raw data for aggregating across multiple nodes
41 - RRDR_OPTION_RETURN_JWAR = (1 << 20), // Return anomaly rates in jsonwrap
42 - RRDR_OPTION_SELECTED_TIER = (1 << 21), // Use the selected tier for the query
43 - RRDR_OPTION_ALL_DIMENSIONS = (1 << 22), // Return the full dimensions list
44 - RRDR_OPTION_SHOW_DETAILS = (1 << 23), // v2 returns detailed object tree
45 - RRDR_OPTION_DEBUG = (1 << 24), // v2 returns request description
46 - RRDR_OPTION_MINIFY = (1 << 25), // remove JSON spaces and newlines from JSON output
47 - RRDR_OPTION_GROUP_BY_LABELS = (1 << 26), // v2 returns flattened labels per dimension of the chart
24 + RRDR_OPTION_DIMS_MIN2MAX = (1 << 3), // when adding dimensions, use max - min, instead of sum
25 + RRDR_OPTION_DIMS_AVERAGE = (1 << 4), // when adding dimensions, use average, instead of sum
26 + RRDR_OPTION_DIMS_MIN = (1 << 5), // when adding dimensions, use minimum, instead of sum
27 + RRDR_OPTION_DIMS_MAX = (1 << 6), // when adding dimensions, use maximum, instead of sum
28 + RRDR_OPTION_SECONDS = (1 << 7), // output seconds, instead of dates
29 + RRDR_OPTION_MILLISECONDS = (1 << 8), // output milliseconds, instead of dates
30 + RRDR_OPTION_NULL2ZERO = (1 << 9), // do not show nulls, convert them to zeros
31 + RRDR_OPTION_OBJECTSROWS = (1 << 10), // each row of values should be an object, not an array
32 + RRDR_OPTION_GOOGLE_JSON = (1 << 11), // comply with google JSON/JSONP specs
33 + RRDR_OPTION_JSON_WRAP = (1 << 12), // wrap the response in a JSON header with info about the result
34 + RRDR_OPTION_LABEL_QUOTES = (1 << 13), // in CSV output, wrap header labels in double quotes
35 + RRDR_OPTION_PERCENTAGE = (1 << 14), // give values as percentage of total
36 + RRDR_OPTION_NOT_ALIGNED = (1 << 15), // do not align charts for persistent timeframes
37 + RRDR_OPTION_DISPLAY_ABS = (1 << 16), // for badges, display the absolute value, but calculate colors with sign
38 + RRDR_OPTION_MATCH_IDS = (1 << 17), // when filtering dimensions, match only IDs
39 + RRDR_OPTION_MATCH_NAMES = (1 << 18), // when filtering dimensions, match only names
40 + RRDR_OPTION_NATURAL_POINTS = (1 << 19), // return the natural points of the database
41 + RRDR_OPTION_VIRTUAL_POINTS = (1 << 20), // return virtual points
42 + RRDR_OPTION_ANOMALY_BIT = (1 << 21), // Return the anomaly bit stored in each collected_number
43 + RRDR_OPTION_RETURN_RAW = (1 << 22), // Return raw data for aggregating across multiple nodes
44 + RRDR_OPTION_RETURN_JWAR = (1 << 23), // Return anomaly rates in jsonwrap
45 + RRDR_OPTION_SELECTED_TIER = (1 << 24), // Use the selected tier for the query
46 + RRDR_OPTION_ALL_DIMENSIONS = (1 << 25), // Return the full dimensions list
47 + RRDR_OPTION_SHOW_DETAILS = (1 << 26), // v2 returns detailed object tree
48 + RRDR_OPTION_DEBUG = (1 << 27), // v2 returns request description
49 + RRDR_OPTION_MINIFY = (1 << 28), // remove JSON spaces and newlines from JSON output
50 + RRDR_OPTION_GROUP_BY_LABELS = (1 << 29), // v2 returns flattened labels per dimension of the chart
51
52 // internal ones - not to be exposed to the API
53 RRDR_OPTION_INTERNAL_AR = (1 << 31), // internal use only, to let the formatters know we want to render the anomaly rate
src/web/api/web_api_v1.c
+19 -2
@@ -14,11 +14,14 @@ static struct {
14 , {"reversed" , 0 , RRDR_OPTION_REVERSED}
15 , {"reverse" , 0 , RRDR_OPTION_REVERSED}
16 , {"jsonwrap" , 0 , RRDR_OPTION_JSON_WRAP}
17 - , {"min2max" , 0 , RRDR_OPTION_MIN2MAX}
17 + , {"min2max" , 0 , RRDR_OPTION_DIMS_MIN2MAX} // rrdr2value() only
18 + , {"average" , 0 , RRDR_OPTION_DIMS_AVERAGE} // rrdr2value() only
19 + , {"min" , 0 , RRDR_OPTION_DIMS_MIN} // rrdr2value() only
20 + , {"max" , 0 , RRDR_OPTION_DIMS_MAX} // rrdr2value() only
21 , {"ms" , 0 , RRDR_OPTION_MILLISECONDS}
22 , {"milliseconds" , 0 , RRDR_OPTION_MILLISECONDS}
20 - , {"abs" , 0 , RRDR_OPTION_ABSOLUTE}
23 , {"absolute" , 0 , RRDR_OPTION_ABSOLUTE}
24 + , {"abs" , 0 , RRDR_OPTION_ABSOLUTE}
25 , {"absolute_sum" , 0 , RRDR_OPTION_ABSOLUTE}
26 , {"absolute-sum" , 0 , RRDR_OPTION_ABSOLUTE}
27 , {"display_absolute" , 0 , RRDR_OPTION_DISPLAY_ABS}
@@ -328,6 +331,20 @@ void rrdr_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS
331 buffer_json_array_close(wb);
332 }
333
334 +void rrdr_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options) {
335 + RRDR_OPTIONS used = 0; // to prevent adding duplicates
336 + size_t added = 0;
337 + for(int i = 0; rrdr_options[i].name ; i++) {
338 + if (unlikely((rrdr_options[i].value & options) && !(rrdr_options[i].value & used))) {
339 + const char *name = rrdr_options[i].name;
340 + used |= rrdr_options[i].value;
341 +
342 + if(added++) buffer_strcat(wb, " ");
343 + buffer_strcat(wb, name);
344 + }
345 + }
346 +}
347 +
348 void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options) {
349 char *write = buf;
350 char *end = &buf[size - 1];
src/web/api/web_api_v1.h
+1
@@ -15,6 +15,7 @@ void web_client_api_request_v2_contexts_alerts_status_to_buffer_json_array(BUFFE
15 RRDR_OPTIONS rrdr_options_parse(char *o);
16 RRDR_OPTIONS rrdr_options_parse_one(const char *o);
17
18 +void rrdr_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options);
19 void rrdr_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options);
20 void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options);
21
src/web/server/web_client.c
-69
@@ -555,75 +555,6 @@ static int mysendfile(struct web_client *w, char *filename) {
555 }
556 #endif
557
558 -void buffer_data_options2string(BUFFER *wb, uint32_t options) {
559 - int count = 0;
560 -
561 - if(options & RRDR_OPTION_NONZERO) {
562 - if(count++) buffer_strcat(wb, " ");
563 - buffer_strcat(wb, "nonzero");
564 - }
565 -
566 - if(options & RRDR_OPTION_REVERSED) {
567 - if(count++) buffer_strcat(wb, " ");
568 - buffer_strcat(wb, "flip");
569 - }
570 -
571 - if(options & RRDR_OPTION_JSON_WRAP) {
572 - if(count++) buffer_strcat(wb, " ");
573 - buffer_strcat(wb, "jsonwrap");
574 - }
575 -
576 - if(options & RRDR_OPTION_MIN2MAX) {
577 - if(count++) buffer_strcat(wb, " ");
578 - buffer_strcat(wb, "min2max");
579 - }
580 -
581 - if(options & RRDR_OPTION_MILLISECONDS) {
582 - if(count++) buffer_strcat(wb, " ");
583 - buffer_strcat(wb, "ms");
584 - }
585 -
586 - if(options & RRDR_OPTION_ABSOLUTE) {
587 - if(count++) buffer_strcat(wb, " ");
588 - buffer_strcat(wb, "absolute");
589 - }
590 -
591 - if(options & RRDR_OPTION_SECONDS) {
592 - if(count++) buffer_strcat(wb, " ");
593 - buffer_strcat(wb, "seconds");
594 - }
595 -
596 - if(options & RRDR_OPTION_NULL2ZERO) {
597 - if(count++) buffer_strcat(wb, " ");
598 - buffer_strcat(wb, "null2zero");
599 - }
600 -
601 - if(options & RRDR_OPTION_OBJECTSROWS) {
602 - if(count++) buffer_strcat(wb, " ");
603 - buffer_strcat(wb, "objectrows");
604 - }
605 -
606 - if(options & RRDR_OPTION_GOOGLE_JSON) {
607 - if(count++) buffer_strcat(wb, " ");
608 - buffer_strcat(wb, "google_json");
609 - }
610 -
611 - if(options & RRDR_OPTION_PERCENTAGE) {
612 - if(count++) buffer_strcat(wb, " ");
613 - buffer_strcat(wb, "percentage");
614 - }
615 -
616 - if(options & RRDR_OPTION_NOT_ALIGNED) {
617 - if(count++) buffer_strcat(wb, " ");
618 - buffer_strcat(wb, "unaligned");
619 - }
620 -
621 - if(options & RRDR_OPTION_ANOMALY_BIT) {
622 - if(count++) buffer_strcat(wb, " ");
623 - buffer_strcat(wb, "anomaly-bit");
624 - }
625 -}
626 -
558 static inline int check_host_and_call(RRDHOST *host, struct web_client *w, char *url, int (*func)(RRDHOST *, struct web_client *, char *)) {
559 return func(host, w, url);
560 }
src/web/server/web_client.h
-2
@@ -250,8 +250,6 @@ ssize_t web_client_read_file(struct web_client *w);
250 void web_client_process_request_from_web_server(struct web_client *w);
251 void web_client_request_done(struct web_client *w);
252
253 -void buffer_data_options2string(BUFFER *wb, uint32_t options);
254 -
253 void web_client_build_http_header(struct web_client *w);
254
255 void web_client_reuse_from_cache(struct web_client *w);