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);