master
c 865 lines 34.7 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "health.h"
4 #include "health_internals.h"
5
6 static inline int health_parse_delay(
7 size_t line, const char *filename, char *string,
8 int *delay_up_duration,
9 int *delay_down_duration,
10 int *delay_max_duration,
11 float *delay_multiplier) {
12
13 char given_up = 0;
14 char given_down = 0;
15 char given_max = 0;
16 char given_multiplier = 0;
17
18 char *s = string;
19 while(*s) {
20 char *key = s;
21
22 while(*s && !isspace((uint8_t)*s)) s++;
23 while(*s && isspace((uint8_t)*s)) *s++ = '\0';
24
25 if(!*key) break;
26
27 char *value = s;
28 while(*s && !isspace((uint8_t)*s)) s++;
29 while(*s && isspace((uint8_t)*s)) *s++ = '\0';
30
31 if(!strcasecmp(key, "up")) {
32 if (!duration_parse_seconds(value, delay_up_duration)) {
33 netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
34 line, filename, value, key);
35 }
36 else given_up = 1;
37 }
38 else if(!strcasecmp(key, "down")) {
39 if (!duration_parse_seconds(value, delay_down_duration)) {
40 netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
41 line, filename, value, key);
42 }
43 else given_down = 1;
44 }
45 else if(!strcasecmp(key, "multiplier")) {
46 *delay_multiplier = strtof(value, NULL);
47 if(isnan(*delay_multiplier) || isinf(*delay_multiplier) || islessequal(*delay_multiplier, 0)) {
48 netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
49 line, filename, value, key);
50 }
51 else given_multiplier = 1;
52 }
53 else if(!strcasecmp(key, "max")) {
54 if (!duration_parse_seconds(value, delay_max_duration)) {
55 netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
56 line, filename, value, key);
57 }
58 else given_max = 1;
59 }
60 else {
61 netdata_log_error("Health configuration at line %zu of file '%s': unknown keyword '%s'",
62 line, filename, key);
63 }
64 }
65
66 if(!given_up)
67 *delay_up_duration = 0;
68
69 if(!given_down)
70 *delay_down_duration = 0;
71
72 if(!given_multiplier)
73 *delay_multiplier = 1.0;
74
75 if(!given_max) {
76 if((*delay_max_duration) < (*delay_up_duration) * (*delay_multiplier))
77 *delay_max_duration = (int)((*delay_up_duration) * (*delay_multiplier));
78
79 if((*delay_max_duration) < (*delay_down_duration) * (*delay_multiplier))
80 *delay_max_duration = (int)((*delay_down_duration) * (*delay_multiplier));
81 }
82
83 return 1;
84 }
85
86 static inline ALERT_ACTION_OPTIONS health_parse_options(const char *s) {
87 ALERT_ACTION_OPTIONS options = ALERT_ACTION_OPTION_NONE;
88 char buf[100+1] = "";
89
90 while(*s) {
91 buf[0] = '\0';
92
93 // skip spaces
94 while(*s && isspace((uint8_t)*s))
95 s++;
96
97 // find the next space
98 size_t count = 0;
99 while(*s && count < 100 && !isspace((uint8_t)*s))
100 buf[count++] = *s++;
101
102 if(buf[0]) {
103 buf[count] = '\0';
104
105 if(!strcasecmp(buf, "no-clear-notification") || !strcasecmp(buf, "no-clear"))
106 options |= ALERT_ACTION_OPTION_NO_CLEAR_NOTIFICATION;
107 else
108 netdata_log_error("Ignoring unknown alarm option '%s'", buf);
109 }
110 }
111
112 return options;
113 }
114
115 static inline int health_parse_repeat(
116 size_t line,
117 const char *file,
118 char *string,
119 uint32_t *warn_repeat_every,
120 uint32_t *crit_repeat_every
121 ) {
122
123 char *s = string;
124 while(*s) {
125 char *key = s;
126
127 while(*s && !isspace((uint8_t)*s)) s++;
128 while(*s && isspace((uint8_t)*s)) *s++ = '\0';
129
130 if(!*key) break;
131
132 char *value = s;
133 while(*s && !isspace((uint8_t)*s)) s++;
134 while(*s && isspace((uint8_t)*s)) *s++ = '\0';
135
136 if(!strcasecmp(key, "off")) {
137 *warn_repeat_every = 0;
138 *crit_repeat_every = 0;
139 return 1;
140 }
141 if(!strcasecmp(key, "warning")) {
142 if (!duration_parse_seconds(value, (int *)warn_repeat_every)) {
143 netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
144 line, file, value, key);
145 }
146 }
147 else if(!strcasecmp(key, "critical")) {
148 if (!duration_parse_seconds(value, (int *)crit_repeat_every)) {
149 netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
150 line, file, value, key);
151 }
152 }
153 }
154
155 return 1;
156 }
157
158 int health_parse_db_lookup(size_t line, const char *filename, char *string, struct rrd_alert_config *ac) {
159 if(ac->dimensions) string_freez(ac->dimensions);
160 ac->dimensions = NULL;
161 ac->after = 0;
162 ac->before = 0;
163 ac->update_every = 0;
164 ac->options = 0;
165 ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_EQUAL;
166 ac->time_group_value = NAN;
167
168 char *s = string, *key;
169
170 // first is the group method
171 key = s;
172 while(*s && !isspace((uint8_t)*s) && *s != '(') s++;
173 while(*s && isspace((uint8_t)*s)) *s++ = '\0';
174 if(!*s) {
175 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'",
176 line, filename, key);
177 return 0;
178 }
179
180 bool group_options = false;
181 if(*s == '(') {
182 *s++ = '\0';
183 group_options = true;
184 }
185
186 if((ac->time_group = time_grouping_parse(key, RRDR_GROUPING_UNDEFINED)) == RRDR_GROUPING_UNDEFINED) {
187 netdata_log_error("Health configuration at line %zu of file '%s': invalid group method '%s'",
188 line, filename, key);
189 return 0;
190 }
191
192 if(group_options) {
193 // skip leading whitespace inside parentheses
194 while(*s && isspace((uint8_t)*s)) s++;
195
196 if(*s == '!') {
197 s++;
198 if(*s == '=') s++;
199 ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_NOT_EQUAL;
200 }
201 else if(*s == '<') {
202 s++;
203 if(*s == '>') {
204 s++;
205 ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_NOT_EQUAL;
206 }
207 else if(*s == '=') {
208 s++;
209 ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS_EQUAL;
210 }
211 else
212 ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS;
213 }
214 else if(*s == '>') {
215 s++;
216 if(*s == '=') {
217 s++;
218 ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER_EQUAL;
219 }
220 else
221 ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER;
222 }
223 else if(*s == '=' || *s == ':') {
224 // explicit equal operator (=, == or :)
225 s++;
226 if(*s == '=') s++; // support == as well
227 ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_EQUAL;
228 }
229
230 while(*s && isspace((uint8_t)*s)) s++;
231
232 if(*s == ')') {
233 // empty options like countif() - allowed, will use defaults
234 }
235 else if((isdigit((uint8_t)*s)) ||
236 (*s == '.' && isdigit((uint8_t)s[1])) ||
237 ((*s == '-' || *s == '+') && s[1] &&
238 ((isdigit((uint8_t)s[1])) || (s[1] == '.' && isdigit((uint8_t)s[2]))))) {
239 // parse numeric value (including negative numbers like >=-3, or >+5, or >-.5)
240 ac->time_group_value = str2ndd(s, &s);
241 while(s && *s && isspace((uint8_t)*s)) s++;
242
243 if(!s || *s != ')') {
244 netdata_log_error("Health configuration at line %zu of file '%s': missing closing parenthesis after number in aggregation method on '%s'",
245 line, filename, key);
246 return 0;
247 }
248 }
249 else if(*s) {
250 // invalid character - not a valid start of a number or ')'
251 netdata_log_error("Health configuration at line %zu of file '%s': invalid character '%c' in aggregation method options on '%s'",
252 line, filename, *s, key);
253 return 0;
254 }
255 else {
256 // end of string without closing parenthesis
257 netdata_log_error("Health configuration at line %zu of file '%s': missing closing parenthesis after aggregation method on '%s'",
258 line, filename, key);
259 return 0;
260 }
261
262 s++;
263 }
264
265 switch (ac->time_group) {
266 default:
267 break;
268
269 case RRDR_GROUPING_COUNTIF:
270 if(isnan(ac->time_group_value))
271 ac->time_group_value = 0;
272 break;
273
274 case RRDR_GROUPING_TRIMMED_MEAN:
275 case RRDR_GROUPING_TRIMMED_MEDIAN:
276 if(isnan(ac->time_group_value))
277 ac->time_group_value = 5;
278 break;
279
280 case RRDR_GROUPING_PERCENTILE:
281 if(isnan(ac->time_group_value))
282 ac->time_group_value = 95;
283 break;
284 }
285
286 // then is the 'after' time
287 while(*s && isspace((uint8_t)*s)) s++; // skip whitespace after group method
288 key = s;
289 while(*s && !isspace((uint8_t)*s)) s++;
290 while(*s && isspace((uint8_t)*s)) *s++ = '\0';
291
292 if(!duration_parse_seconds(key, &ac->after)) {
293 netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' after group method",
294 line, filename, key);
295 return 0;
296 }
297
298 // sane defaults
299 ac->update_every = ABS(ac->after);
300
301 // now we may have optional parameters
302 while(*s) {
303 key = s;
304 while(*s && !isspace((uint8_t)*s)) s++;
305 while(*s && isspace((uint8_t)*s)) *s++ = '\0';
306 if(!*key) break;
307
308 if(!strcasecmp(key, "at")) {
309 char *value = s;
310 while(*s && !isspace((uint8_t)*s)) s++;
311 while(*s && isspace((uint8_t)*s)) *s++ = '\0';
312
313 if (!duration_parse_seconds(value, &ac->before)) {
314 netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
315 line, filename, value, key);
316 return 0;
317 }
318 }
319 else if(!strcasecmp(key, HEALTH_EVERY_KEY)) {
320 char *value = s;
321 while(*s && !isspace((uint8_t)*s)) s++;
322 while(*s && isspace((uint8_t)*s)) *s++ = '\0';
323
324 if (!duration_parse_seconds(value, &ac->update_every)) {
325 netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
326 line, filename, value, key);
327 return 0;
328 }
329 }
330 else if(!strcasecmp(key, "absolute") || !strcasecmp(key, "abs") || !strcasecmp(key, "absolute_sum")) {
331 ac->options |= RRDR_OPTION_ABSOLUTE;
332 }
333 else if(!strcasecmp(key, "min2max")) {
334 ac->options |= RRDR_OPTION_DIMS_MIN2MAX;
335 }
336 else if(!strcasecmp(key, "average")) {
337 ac->options |= RRDR_OPTION_DIMS_AVERAGE;
338 }
339 else if(!strcasecmp(key, "min")) {
340 ac->options |= RRDR_OPTION_DIMS_MIN;
341 }
342 else if(!strcasecmp(key, "max")) {
343 ac->options |= RRDR_OPTION_DIMS_MAX;
344 }
345 else if(!strcasecmp(key, "sum")) {
346 ;
347 }
348 else if(!strcasecmp(key, "null2zero")) {
349 ac->options |= RRDR_OPTION_NULL2ZERO;
350 }
351 else if(!strcasecmp(key, "percentage")) {
352 ac->options |= RRDR_OPTION_PERCENTAGE;
353 }
354 else if(!strcasecmp(key, "unaligned")) {
355 ac->options |= RRDR_OPTION_NOT_ALIGNED;
356 }
357 else if(!strcasecmp(key, "anomaly-bit")) {
358 ac->options |= RRDR_OPTION_ANOMALY_BIT;
359 }
360 else if(!strcasecmp(key, "match-ids") || !strcasecmp(key, "match_ids")) {
361 ac->options |= RRDR_OPTION_MATCH_IDS;
362 }
363 else if(!strcasecmp(key, "match-names") || !strcasecmp(key, "match_names")) {
364 ac->options |= RRDR_OPTION_MATCH_NAMES;
365 }
366 else if(!strcasecmp(key, "of")) {
367 char *find = NULL;
368 if(*s && strcasecmp(s, "all") != 0) {
369 find = strcasestr(s, " foreach");
370 if(find) {
371 *find = '\0';
372 }
373 ac->dimensions = string_strdupz(s);
374 }
375
376 if(!find) {
377 break;
378 }
379 s = ++find;
380 }
381 else {
382 netdata_log_error("Health configuration at line %zu of file '%s': unknown keyword '%s'",
383 line, filename, key);
384 }
385 }
386
387 return 1;
388 }
389
390 static inline STRING *health_source_file(size_t line, const char *file) {
391 char buffer[FILENAME_MAX + 1];
392 snprintfz(buffer, FILENAME_MAX, "line=%zu,file=%s", line, file);
393 return string_strdupz(buffer);
394 }
395
396 char *health_edit_command_from_source(const char *source)
397 {
398 char buffer[FILENAME_MAX + 1];
399 char *temp = strdupz(source);
400 char *line_num = strchr(temp, '@');
401 char *line_p = temp;
402 char *file_no_path = strrchr(temp, '/');
403
404 // Check for the 'line=' format if '@' is not found
405 if (!line_num) {
406 line_num = strstr(temp, "line=");
407 file_no_path = strstr(temp, "file=/");
408 }
409
410 if (likely(file_no_path && line_num)) {
411 if (line_num == strchr(temp, '@')) {
412 *line_num = '\0'; // Handle the old format
413 } else {
414 line_num += strlen("line=");
415 file_no_path = strrchr(file_no_path + strlen("file="), '/');
416 char *line_end = strchr(line_num, ',');
417 if (line_end) {
418 line_p = line_num;
419 *line_end = '\0';
420 }
421 }
422
423 snprintfz(
424 buffer,
425 FILENAME_MAX,
426 "sudo %s/edit-config health.d/%s=%s=%s",
427 netdata_configured_user_config_dir,
428 file_no_path + 1,
429 line_p,
430 rrdhost_registry_hostname(localhost));
431 } else {
432 buffer[0] = '\0';
433 }
434
435 freez(temp);
436 return strdupz(buffer);
437 }
438
439
440 static inline void strip_quotes(char *s) {
441 while(*s) {
442 if(*s == '\'' || *s == '"') *s = ' ';
443 s++;
444 }
445 }
446
447 static void replace_green_red(RRD_ALERT_PROTOTYPE *ap, NETDATA_DOUBLE green, NETDATA_DOUBLE red) {
448 if(!isnan(green)) {
449 STRING *green_str = string_strdupz("green");
450 expression_hardcode_variable(ap->config.calculation, green_str, green);
451 expression_hardcode_variable(ap->config.warning, green_str, green);
452 expression_hardcode_variable(ap->config.critical, green_str, green);
453 string_freez(green_str);
454 }
455
456 if(!isnan(red)) {
457 STRING *red_str = string_strdupz("red");
458 expression_hardcode_variable(ap->config.calculation, red_str, red);
459 expression_hardcode_variable(ap->config.warning, red_str, red);
460 expression_hardcode_variable(ap->config.critical, red_str, red);
461 string_freez(red_str);
462 }
463 }
464
465 static void dims_grouping_from_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
466 if(ap->config.options & RRDR_OPTION_DIMS_MIN)
467 ap->config.dims_group = ALERT_LOOKUP_DIMS_MIN;
468 else if(ap->config.options & RRDR_OPTION_DIMS_MAX)
469 ap->config.dims_group = ALERT_LOOKUP_DIMS_MAX;
470 else if(ap->config.options & RRDR_OPTION_DIMS_MIN2MAX)
471 ap->config.dims_group = ALERT_LOOKUP_DIMS_MIN2MAX;
472 else if(ap->config.options & RRDR_OPTION_DIMS_AVERAGE)
473 ap->config.dims_group = ALERT_LOOKUP_DIMS_AVERAGE;
474 else
475 ap->config.dims_group = ALERT_LOOKUP_DIMS_SUM;
476 }
477
478 static void lookup_data_source_from_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
479 if(ap->config.options & RRDR_OPTION_PERCENTAGE)
480 ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_PERCENTAGES;
481 else if(ap->config.options & RRDR_OPTION_ANOMALY_BIT)
482 ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_ANOMALIES;
483 else
484 ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_SAMPLES;
485 }
486
487 #define PARSE_HEALTH_CONFIG_LOG_DUPLICATE_STRING_MSG(ax, member) do { \
488 if(strcmp(string2str(ax->member), value) != 0) \
489 netdata_log_error( \
490 "Health configuration at line %zu of file '%s' for alarm '%s' has key '%s' twice, " \
491 "once with value '%s' and later with value '%s'. Using ('%s').", \
492 line, filename, string2str(ac->name), key, \
493 string2str(ax->member), value, value); \
494 } while(0)
495
496 #define PARSE_HEALTH_CONFIG_LINE_STRING(ax, member) do { \
497 if(ax->member) { \
498 PARSE_HEALTH_CONFIG_LOG_DUPLICATE_STRING_MSG(ax, member); \
499 string_freez(ax->member); \
500 } \
501 ax->member = string_strdupz(value); \
502 } while(0)
503
504 #define PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(ax, member, label) do { \
505 const char *_label = label; \
506 if(_label && !*_label) \
507 _label = NULL; \
508 \
509 if(value && (!*value || strcmp(value, "*") == 0)) \
510 value = NULL; \
511 else if(value && (strcmp(value, "!* *") == 0 || strcmp(value, "!*") == 0)) { \
512 value = NULL; \
513 ap->match.enabled = false; \
514 } \
515 \
516 if(value && !_label && !strchr(value, '=')) { \
517 netdata_log_error( \
518 "Health configuration at line %zu of file '%s' for alarm '%s' has key '%s' " \
519 "with value '%s' that does not match label=pattern. Ignoring it.", \
520 line, filename, string2str(ac->name), key, value); \
521 value = NULL; \
522 } \
523 \
524 if(value) { \
525 typeof(ax->member) _old = ax->member; \
526 size_t _label_len = _label ? strlen(_label) : 0; \
527 size_t _value_len = strlen(value); \
528 size_t _old_len = _old ? string_strlen(_old) : 0; \
529 size_t _buf_len = _label_len + (_label ? 1 : 0) + _value_len + (_old ? 1 : 0) + _old_len + 1; \
530 char *_buf = mallocz(_buf_len); \
531 snprintfz(_buf, _buf_len, "%s%s%s%s%s", \
532 _label ? _label : "", \
533 _label ? "=" : "", \
534 value, \
535 _old ? " " : "", \
536 _old ? string2str(_old) : ""); \
537 string_freez(_old); \
538 ax->member = string_strdupz(_buf); \
539 freez(_buf); \
540 } \
541 } while(0)
542
543 int health_readfile(const char *filename, void *data __maybe_unused, bool stock_config) {
544 netdata_log_debug(D_HEALTH, "Health configuration reading file '%s'", filename);
545
546 static uint32_t
547 hash_alarm = 0,
548 hash_template = 0,
549 hash_os = 0,
550 hash_on = 0,
551 hash_host = 0,
552 hash_plugin = 0,
553 hash_module = 0,
554 hash_calc = 0,
555 hash_green = 0,
556 hash_red = 0,
557 hash_warn = 0,
558 hash_crit = 0,
559 hash_exec = 0,
560 hash_every = 0,
561 hash_lookup = 0,
562 hash_units = 0,
563 hash_summary = 0,
564 hash_info = 0,
565 hash_class = 0,
566 hash_component = 0,
567 hash_type = 0,
568 hash_recipient = 0,
569 hash_delay = 0,
570 hash_options = 0,
571 hash_repeat = 0,
572 hash_host_label = 0,
573 hash_chart_label = 0;
574
575 char buffer[HEALTH_CONF_MAX_LINE + 1];
576
577 if(unlikely(!hash_alarm)) {
578 hash_alarm = simple_uhash(HEALTH_ALARM_KEY);
579 hash_template = simple_uhash(HEALTH_TEMPLATE_KEY);
580 hash_on = simple_uhash(HEALTH_ON_KEY);
581 hash_os = simple_uhash(HEALTH_OS_KEY);
582 hash_host = simple_uhash(HEALTH_HOST_KEY);
583 hash_plugin = simple_uhash(HEALTH_PLUGIN_KEY);
584 hash_module = simple_uhash(HEALTH_MODULE_KEY);
585 hash_calc = simple_uhash(HEALTH_CALC_KEY);
586 hash_lookup = simple_uhash(HEALTH_LOOKUP_KEY);
587 hash_green = simple_uhash(HEALTH_GREEN_KEY);
588 hash_red = simple_uhash(HEALTH_RED_KEY);
589 hash_warn = simple_uhash(HEALTH_WARN_KEY);
590 hash_crit = simple_uhash(HEALTH_CRIT_KEY);
591 hash_exec = simple_uhash(HEALTH_EXEC_KEY);
592 hash_every = simple_uhash(HEALTH_EVERY_KEY);
593 hash_units = simple_hash(HEALTH_UNITS_KEY);
594 hash_summary = simple_hash(HEALTH_SUMMARY_KEY);
595 hash_info = simple_hash(HEALTH_INFO_KEY);
596 hash_class = simple_uhash(HEALTH_CLASS_KEY);
597 hash_component = simple_uhash(HEALTH_COMPONENT_KEY);
598 hash_type = simple_uhash(HEALTH_TYPE_KEY);
599 hash_recipient = simple_hash(HEALTH_RECIPIENT_KEY);
600 hash_delay = simple_uhash(HEALTH_DELAY_KEY);
601 hash_options = simple_uhash(HEALTH_OPTIONS_KEY);
602 hash_repeat = simple_uhash(HEALTH_REPEAT_KEY);
603 hash_host_label = simple_uhash(HEALTH_HOST_LABEL_KEY);
604 hash_chart_label = simple_uhash(HEALTH_CHART_LABEL_KEY);
605 }
606
607 FILE *fp = fopen(filename, "r");
608 if(!fp) {
609 netdata_log_error("Health configuration cannot read file '%s'.", filename);
610 return 0;
611 }
612
613 RRD_ALERT_PROTOTYPE *ap = NULL;
614 struct rrd_alert_config *ac = NULL;
615 struct rrd_alert_match *am = NULL;
616 NETDATA_DOUBLE green = NAN;
617 NETDATA_DOUBLE red = NAN;
618
619 size_t line = 0, append = 0;
620 char *s;
621 while((s = fgets(&buffer[append], (int)(HEALTH_CONF_MAX_LINE - append), fp)) || append) {
622 int stop_appending = !s;
623 line++;
624 s = trim(buffer);
625 if(!s || *s == '#') continue;
626
627 append = strlen(s);
628 if(!stop_appending && s[append - 1] == '\\') {
629 s[append - 1] = ' ';
630 append = &s[append] - buffer;
631 if(append < HEALTH_CONF_MAX_LINE)
632 continue;
633 else
634 netdata_log_error(
635 "Health configuration has too long multi-line at line %zu of file '%s'.",
636 line, filename);
637 }
638 append = 0;
639
640 char *key = s;
641 while(*s && *s != ':') s++;
642 if(!*s) {
643 netdata_log_error(
644 "Health configuration has invalid line %zu of file '%s'. It does not contain a ':'. Ignoring it.",
645 line, filename);
646 continue;
647 }
648 *s = '\0';
649 s++;
650
651 char *value = s;
652 key = trim_all(key);
653 value = trim_all(value);
654
655 if(!key || !*key) {
656 netdata_log_error(
657 "Health configuration has invalid line %zu of file '%s'. Keyword is empty. Ignoring it.",
658 line, filename);
659
660 continue;
661 }
662
663 if(!value || !*value) {
664 netdata_log_error(
665 "Health configuration has invalid line %zu of file '%s'. value is empty. Ignoring it.",
666 line, filename);
667 continue;
668 }
669
670 uint32_t hash = simple_uhash(key);
671
672 if((hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) || (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY))) {
673 if(ap) {
674 lookup_data_source_from_rrdr_options(ap);
675 dims_grouping_from_rrdr_options(ap);
676 replace_green_red(ap, green, red);
677 health_prototype_add(ap, NULL);
678 freez(ap);
679 }
680
681 ap = callocz(1, sizeof(*ap));
682 am = &ap->match;
683 ac = &ap->config;
684
685 {
686 char *tmp = strdupz(value);
687 if(rrdvar_fix_name(tmp))
688 netdata_log_error("Health configuration renamed alarm '%s' to '%s'", value, tmp);
689
690 ap->config.name = string_strdupz(tmp);
691 freez(tmp);
692 }
693
694 ap->_internal.enabled = true;
695 ap->match.enabled = true;
696 ap->match.is_template = (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY));
697 ap->config.source = health_source_file(line, filename);
698 ap->config.source_type = stock_config ? DYNCFG_SOURCE_TYPE_STOCK : DYNCFG_SOURCE_TYPE_USER;
699 green = NAN;
700 red = NAN;
701 ap->config.delay_multiplier = 1;
702 ap->config.warn_repeat_every = health_globals.config.default_warn_repeat_every;
703 ap->config.crit_repeat_every = health_globals.config.default_crit_repeat_every;
704 }
705 else if(!am || !ac || !ap) {
706 netdata_log_error(
707 "Health configuration at line %zu of file '%s' has unknown key '%s'. "
708 "Expected either '" HEALTH_ALARM_KEY "' or '" HEALTH_TEMPLATE_KEY "'.",
709 line, filename, key);
710 }
711 else if(!am->is_template && hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
712 PARSE_HEALTH_CONFIG_LINE_STRING(am, on.chart);
713 }
714 else if(am->is_template && hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
715 PARSE_HEALTH_CONFIG_LINE_STRING(am, on.context);
716 }
717 else if(hash == hash_os && !strcasecmp(key, HEALTH_OS_KEY)) {
718 PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, host_labels, "_os");
719 }
720 else if(hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) {
721 PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, host_labels, "_hostname");
722 }
723 else if(hash == hash_host_label && !strcasecmp(key, HEALTH_HOST_LABEL_KEY)) {
724 PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, host_labels, NULL);
725 }
726 else if(hash == hash_plugin && !strcasecmp(key, HEALTH_PLUGIN_KEY)) {
727 PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, chart_labels, "_collect_plugin");
728 }
729 else if(hash == hash_module && !strcasecmp(key, HEALTH_MODULE_KEY)) {
730 PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, chart_labels, "_collect_module");
731 }
732 else if(hash == hash_chart_label && !strcasecmp(key, HEALTH_CHART_LABEL_KEY)) {
733 PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, chart_labels, NULL);
734 }
735 else if(hash == hash_class && !strcasecmp(key, HEALTH_CLASS_KEY)) {
736 strip_quotes(value);
737 PARSE_HEALTH_CONFIG_LINE_STRING(ac, classification);
738 }
739 else if(hash == hash_component && !strcasecmp(key, HEALTH_COMPONENT_KEY)) {
740 strip_quotes(value);
741 PARSE_HEALTH_CONFIG_LINE_STRING(ac, component);
742 }
743 else if(hash == hash_type && !strcasecmp(key, HEALTH_TYPE_KEY)) {
744 strip_quotes(value);
745 PARSE_HEALTH_CONFIG_LINE_STRING(ac, type);
746 }
747 else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
748 health_parse_db_lookup(line, filename, value, ac);
749 }
750 else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
751 if(!duration_parse_seconds(value, &ac->update_every))
752 netdata_log_error(
753 "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
754 "cannot parse duration: '%s'.",
755 line, filename, string2str(ac->name), key, value);
756 }
757 else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
758 char *e;
759 green = str2ndd(value, &e);
760 if(e && *e) {
761 netdata_log_error(
762 "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
763 "leaves this string unmatched: '%s'.",
764 line, filename, string2str(ac->name), key, e);
765 }
766 }
767 else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
768 char *e;
769 red = str2ndd(value, &e);
770 if(e && *e) {
771 netdata_log_error(
772 "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
773 "leaves this string unmatched: '%s'.",
774 line, filename, string2str(ac->name), key, e);
775 }
776 }
777 else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) {
778 const char *failed_at = NULL;
779 int error = 0;
780 ac->calculation = expression_parse(value, &failed_at, &error);
781 if(!ac->calculation) {
782 netdata_log_error(
783 "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
784 "has non-parseable expression '%s': %s at '%s'",
785 line, filename, string2str(ac->name), key, value, expression_strerror(error), failed_at);
786 am->enabled = false;
787 }
788 }
789 else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) {
790 const char *failed_at = NULL;
791 int error = 0;
792 ac->warning = expression_parse(value, &failed_at, &error);
793 if(!ac->warning) {
794 netdata_log_error(
795 "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
796 "has non-parseable expression '%s': %s at '%s'",
797 line, filename, string2str(ac->name), key, value, expression_strerror(error), failed_at);
798 am->enabled = false;
799 }
800 }
801 else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) {
802 const char *failed_at = NULL;
803 int error = 0;
804 ac->critical = expression_parse(value, &failed_at, &error);
805 if(!ac->critical) {
806 netdata_log_error(
807 "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
808 "has non-parseable expression '%s': %s at '%s'",
809 line, filename, string2str(ac->name), key, value, expression_strerror(error), failed_at);
810 am->enabled = false;
811 }
812 }
813 else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) {
814 strip_quotes(value);
815 PARSE_HEALTH_CONFIG_LINE_STRING(ac, exec);
816 }
817 else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
818 strip_quotes(value);
819 PARSE_HEALTH_CONFIG_LINE_STRING(ac, recipient);
820 }
821 else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) {
822 strip_quotes(value);
823 PARSE_HEALTH_CONFIG_LINE_STRING(ac, units);
824 }
825 else if(hash == hash_summary && !strcasecmp(key, HEALTH_SUMMARY_KEY)) {
826 strip_quotes(value);
827 PARSE_HEALTH_CONFIG_LINE_STRING(ac, summary);
828 }
829 else if(hash == hash_info && !strcasecmp(key, HEALTH_INFO_KEY)) {
830 strip_quotes(value);
831 PARSE_HEALTH_CONFIG_LINE_STRING(ac, info);
832 }
833 else if(hash == hash_delay && !strcasecmp(key, HEALTH_DELAY_KEY)) {
834 health_parse_delay(line, filename, value,
835 &ac->delay_up_duration, &ac->delay_down_duration,
836 &ac->delay_max_duration, &ac->delay_multiplier);
837 }
838 else if(hash == hash_options && !strcasecmp(key, HEALTH_OPTIONS_KEY)) {
839 ac->alert_action_options |= health_parse_options(value);
840 }
841 else if(hash == hash_repeat && !strcasecmp(key, HEALTH_REPEAT_KEY)){
842 health_parse_repeat(line, filename, value,
843 &ac->warn_repeat_every,
844 &ac->crit_repeat_every);
845 ac->has_custom_repeat_config = true;
846 }
847 else {
848 if (strcmp(key, "families") != 0 && strcmp(key, "charts") != 0)
849 netdata_log_error(
850 "Health configuration at line %zu of file '%s' for alarm/template '%s' has unknown key '%s'.",
851 line, filename, string2str(ac->name), key);
852 }
853 }
854
855 if(ap) {
856 lookup_data_source_from_rrdr_options(ap);
857 dims_grouping_from_rrdr_options(ap);
858 replace_green_red(ap, green, red);
859 health_prototype_add(ap, NULL);
860 freez(ap);
861 }
862
863 fclose(fp);
864 return 1;
865 }