1 #define NETDATA_HEALTH_INTERNALS
4 #define HEALTH_CONF_MAX_LINE 4096
6 #define HEALTH_ALARM_KEY "alarm"
7 #define HEALTH_TEMPLATE_KEY "template"
8 #define HEALTH_ON_KEY "on"
9 #define HEALTH_FAMILIES_KEY "families"
10 #define HEALTH_LOOKUP_KEY "lookup"
11 #define HEALTH_CALC_KEY "calc"
12 #define HEALTH_EVERY_KEY "every"
13 #define HEALTH_GREEN_KEY "green"
14 #define HEALTH_RED_KEY "red"
15 #define HEALTH_WARN_KEY "warn"
16 #define HEALTH_CRIT_KEY "crit"
17 #define HEALTH_EXEC_KEY "exec"
18 #define HEALTH_RECIPIENT_KEY "to"
19 #define HEALTH_UNITS_KEY "units"
20 #define HEALTH_INFO_KEY "info"
21 #define HEALTH_DELAY_KEY "delay"
22 #define HEALTH_OPTIONS_KEY "options"
24 static inline int rrdcalc_add_alarm_from_config(RRDHOST *host, RRDCALC *rc) {
26 error("Health configuration for alarm '%s' does not have a chart", rc->name);
30 if(!rc->update_every) {
31 error("Health configuration for alarm '%s.%s' has no frequency (parameter 'every'). Ignoring it.", rc->chart?rc->chart:"NOCHART", rc->name);
35 if(!RRDCALC_HAS_DB_LOOKUP(rc) && !rc->warning && !rc->critical) {
36 error("Health configuration for alarm '%s.%s' is useless (no calculation, no warning and no critical evaluation)", rc->chart?rc->chart:"NOCHART", rc->name);
40 if (rrdcalc_exists(host, rc->chart, rc->name, rc->hash_chart, rc->hash))
43 rc->id = rrdcalc_get_unique_id(host, rc->chart, rc->name, &rc->next_event_id);
45 debug(D_HEALTH, "Health configuration adding alarm '%s.%s' (%u): exec '%s', recipient '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s', delay up %d, delay down %d, delay max %d, delay_multiplier %f",
46 rc->chart?rc->chart:"NOCHART",
49 (rc->exec)?rc->exec:"DEFAULT",
50 (rc->recipient)?rc->recipient:"DEFAULT",
57 (rc->dimensions)?rc->dimensions:"NONE",
59 (rc->calculation)?rc->calculation->parsed_as:"NONE",
60 (rc->warning)?rc->warning->parsed_as:"NONE",
61 (rc->critical)?rc->critical->parsed_as:"NONE",
63 rc->delay_up_duration,
64 rc->delay_down_duration,
65 rc->delay_max_duration,
69 rrdcalc_create_part2(host, rc);
73 static inline int rrdcalctemplate_add_template_from_config(RRDHOST *host, RRDCALCTEMPLATE *rt) {
74 if(unlikely(!rt->context)) {
75 error("Health configuration for template '%s' does not have a context", rt->name);
79 if(unlikely(!rt->update_every)) {
80 error("Health configuration for template '%s' has no frequency (parameter 'every'). Ignoring it.", rt->name);
84 if(unlikely(!RRDCALCTEMPLATE_HAS_CALCULATION(rt) && !rt->warning && !rt->critical)) {
85 error("Health configuration for template '%s' is useless (no calculation, no warning and no critical evaluation)", rt->name);
89 RRDCALCTEMPLATE *t, *last = NULL;
90 for (t = host->templates; t ; last = t, t = t->next) {
91 if(unlikely(t->hash_name == rt->hash_name && !strcmp(t->name, rt->name))) {
92 error("Health configuration template '%s' already exists for host '%s'.", rt->name, host->hostname);
97 debug(D_HEALTH, "Health configuration adding template '%s': context '%s', exec '%s', recipient '%s', green %Lf, red %Lf, lookup: group %d, after %d, before %d, options %u, dimensions '%s', update every %d, calculation '%s', warning '%s', critical '%s', source '%s', delay up %d, delay down %d, delay max %d, delay_multiplier %f",
99 (rt->context)?rt->context:"NONE",
100 (rt->exec)?rt->exec:"DEFAULT",
101 (rt->recipient)?rt->recipient:"DEFAULT",
108 (rt->dimensions)?rt->dimensions:"NONE",
110 (rt->calculation)?rt->calculation->parsed_as:"NONE",
111 (rt->warning)?rt->warning->parsed_as:"NONE",
112 (rt->critical)?rt->critical->parsed_as:"NONE",
114 rt->delay_up_duration,
115 rt->delay_down_duration,
116 rt->delay_max_duration,
124 rt->next = host->templates;
125 host->templates = rt;
131 static inline int health_parse_duration(char *string, int *result) {
132 // make sure it is a number
133 if(!*string || !(isdigit(*string) || *string == '+' || *string == '-')) {
139 calculated_number n = strtold(string, &e);
143 *result = (int) (n * 86400 * 365);
146 *result = (int) (n * 86400 * 30);
149 *result = (int) (n * 86400 * 7);
152 *result = (int) (n * 86400);
155 *result = (int) (n * 3600);
158 *result = (int) (n * 60);
173 static inline int health_parse_delay(
174 size_t line, const char *path, const char *file, char *string,
175 int *delay_up_duration,
176 int *delay_down_duration,
177 int *delay_max_duration,
178 float *delay_multiplier) {
183 char given_multiplier = 0;
189 while(*s && !isspace(*s)) s++;
190 while(*s && isspace(*s)) *s++ = '\0';
195 while(*s && !isspace(*s)) s++;
196 while(*s && isspace(*s)) *s++ = '\0';
198 if(!strcasecmp(key, "up")) {
199 if (!health_parse_duration(value, delay_up_duration)) {
200 error("Health configuration at line %zu of file '%s/%s': invalid value '%s' for '%s' keyword",
201 line, path, file, value, key);
205 else if(!strcasecmp(key, "down")) {
206 if (!health_parse_duration(value, delay_down_duration)) {
207 error("Health configuration at line %zu of file '%s/%s': invalid value '%s' for '%s' keyword",
208 line, path, file, value, key);
212 else if(!strcasecmp(key, "multiplier")) {
213 *delay_multiplier = strtof(value, NULL);
214 if(isnan(*delay_multiplier) || isinf(*delay_multiplier) || islessequal(*delay_multiplier, 0)) {
215 error("Health configuration at line %zu of file '%s/%s': invalid value '%s' for '%s' keyword",
216 line, path, file, value, key);
218 else given_multiplier = 1;
220 else if(!strcasecmp(key, "max")) {
221 if (!health_parse_duration(value, delay_max_duration)) {
222 error("Health configuration at line %zu of file '%s/%s': invalid value '%s' for '%s' keyword",
223 line, path, file, value, key);
228 error("Health configuration at line %zu of file '%s/%s': unknown keyword '%s'",
229 line, path, file, key);
234 *delay_up_duration = 0;
237 *delay_down_duration = 0;
239 if(!given_multiplier)
240 *delay_multiplier = 1.0;
243 if((*delay_max_duration) < (*delay_up_duration) * (*delay_multiplier))
244 *delay_max_duration = (*delay_up_duration) * (*delay_multiplier);
246 if((*delay_max_duration) < (*delay_down_duration) * (*delay_multiplier))
247 *delay_max_duration = (*delay_down_duration) * (*delay_multiplier);
253 static inline uint32_t health_parse_options(const char *s) {
254 uint32_t options = 0;
255 char buf[100+1] = "";
261 while(*s && isspace(*s))
264 // find the next space
266 while(*s && count < 100 && !isspace(*s))
272 if(!strcasecmp(buf, "no-clear-notification") || !strcasecmp(buf, "no-clear"))
273 options |= RRDCALC_FLAG_NO_CLEAR_NOTIFICATION;
275 error("Ignoring unknown alarm option '%s'", buf);
282 static inline int health_parse_db_lookup(
283 size_t line, const char *path, const char *file, char *string,
284 int *group_method, int *after, int *before, int *every,
285 uint32_t *options, char **dimensions
287 debug(D_HEALTH, "Health configuration parsing database lookup %zu@%s/%s: %s", line, path, file, string);
289 if(*dimensions) freez(*dimensions);
296 char *s = string, *key;
298 // first is the group method
300 while(*s && !isspace(*s)) s++;
301 while(*s && isspace(*s)) *s++ = '\0';
303 error("Health configuration invalid chart calculation at line %zu of file '%s/%s': expected group method followed by the 'after' time, but got '%s'",
304 line, path, file, key);
308 if((*group_method = web_client_api_request_v1_data_group(key, -1)) == -1) {
309 error("Health configuration at line %zu of file '%s/%s': invalid group method '%s'",
310 line, path, file, key);
314 // then is the 'after' time
316 while(*s && !isspace(*s)) s++;
317 while(*s && isspace(*s)) *s++ = '\0';
319 if(!health_parse_duration(key, after)) {
320 error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' after group method",
321 line, path, file, key);
326 *every = abs(*after);
328 // now we may have optional parameters
331 while(*s && !isspace(*s)) s++;
332 while(*s && isspace(*s)) *s++ = '\0';
335 if(!strcasecmp(key, "at")) {
337 while(*s && !isspace(*s)) s++;
338 while(*s && isspace(*s)) *s++ = '\0';
340 if (!health_parse_duration(value, before)) {
341 error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' for '%s' keyword",
342 line, path, file, value, key);
345 else if(!strcasecmp(key, HEALTH_EVERY_KEY)) {
347 while(*s && !isspace(*s)) s++;
348 while(*s && isspace(*s)) *s++ = '\0';
350 if (!health_parse_duration(value, every)) {
351 error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' for '%s' keyword",
352 line, path, file, value, key);
355 else if(!strcasecmp(key, "absolute") || !strcasecmp(key, "abs") || !strcasecmp(key, "absolute_sum")) {
356 *options |= RRDR_OPTION_ABSOLUTE;
358 else if(!strcasecmp(key, "min2max")) {
359 *options |= RRDR_OPTION_MIN2MAX;
361 else if(!strcasecmp(key, "null2zero")) {
362 *options |= RRDR_OPTION_NULL2ZERO;
364 else if(!strcasecmp(key, "percentage")) {
365 *options |= RRDR_OPTION_PERCENTAGE;
367 else if(!strcasecmp(key, "unaligned")) {
368 *options |= RRDR_OPTION_NOT_ALIGNED;
370 else if(!strcasecmp(key, "of")) {
371 if(*s && strcasecmp(s, "all"))
372 *dimensions = strdupz(s);
376 error("Health configuration at line %zu of file '%s/%s': unknown keyword '%s'",
377 line, path, file, key);
384 static inline char *trim_all_spaces(char *buffer) {
385 char *d = buffer, *s = buffer;
388 while(isspace(*s)) s++;
391 // copy the non-space part
392 while(*s && !isspace(*s)) *d++ = *s++;
394 // add a space if we have to
395 if(*s && isspace(*s)) {
401 while(isspace(*s)) s++;
408 if(isspace(*d)) *d = '\0';
411 if(!buffer[0]) return NULL;
415 static inline char *health_source_file(size_t line, const char *path, const char *filename) {
416 char buffer[FILENAME_MAX + 1];
417 snprintfz(buffer, FILENAME_MAX, "%zu@%s/%s", line, path, filename);
418 return strdupz(buffer);
421 static inline void strip_quotes(char *s) {
423 if(*s == '\'' || *s == '"') *s = ' ';
428 int health_readfile(RRDHOST *host, const char *path, const char *filename) {
429 debug(D_HEALTH, "Health configuration reading file '%s/%s'", path, filename);
450 char buffer[HEALTH_CONF_MAX_LINE + 1];
452 if(unlikely(!hash_alarm)) {
453 hash_alarm = simple_uhash(HEALTH_ALARM_KEY);
454 hash_template = simple_uhash(HEALTH_TEMPLATE_KEY);
455 hash_on = simple_uhash(HEALTH_ON_KEY);
456 hash_families = simple_uhash(HEALTH_FAMILIES_KEY);
457 hash_calc = simple_uhash(HEALTH_CALC_KEY);
458 hash_lookup = simple_uhash(HEALTH_LOOKUP_KEY);
459 hash_green = simple_uhash(HEALTH_GREEN_KEY);
460 hash_red = simple_uhash(HEALTH_RED_KEY);
461 hash_warn = simple_uhash(HEALTH_WARN_KEY);
462 hash_crit = simple_uhash(HEALTH_CRIT_KEY);
463 hash_exec = simple_uhash(HEALTH_EXEC_KEY);
464 hash_every = simple_uhash(HEALTH_EVERY_KEY);
465 hash_units = simple_hash(HEALTH_UNITS_KEY);
466 hash_info = simple_hash(HEALTH_INFO_KEY);
467 hash_recipient = simple_hash(HEALTH_RECIPIENT_KEY);
468 hash_delay = simple_uhash(HEALTH_DELAY_KEY);
469 hash_options = simple_uhash(HEALTH_OPTIONS_KEY);
472 snprintfz(buffer, HEALTH_CONF_MAX_LINE, "%s/%s", path, filename);
473 FILE *fp = fopen(buffer, "r");
475 error("Health configuration cannot read file '%s'.", buffer);
480 RRDCALCTEMPLATE *rt = NULL;
482 size_t line = 0, append = 0;
484 while((s = fgets(&buffer[append], (int)(HEALTH_CONF_MAX_LINE - append), fp)) || append) {
485 int stop_appending = !s;
491 if(!stop_appending && s[append - 1] == '\\') {
493 append = &s[append] - buffer;
494 if(append < HEALTH_CONF_MAX_LINE)
497 error("Health configuration has too long muli-line at line %zu of file '%s/%s'.", line, path, filename);
503 while(*s && *s != ':') s++;
505 error("Health configuration has invalid line %zu of file '%s/%s'. It does not contain a ':'. Ignoring it.", line, path, filename);
512 key = trim_all_spaces(key);
513 value = trim_all_spaces(value);
516 error("Health configuration has invalid line %zu of file '%s/%s'. Keyword is empty. Ignoring it.", line, path, filename);
521 error("Health configuration has invalid line %zu of file '%s/%s'. value is empty. Ignoring it.", line, path, filename);
525 uint32_t hash = simple_uhash(key);
527 if(hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) {
528 if(rc && !rrdcalc_add_alarm_from_config(host, rc))
529 rrdcalc_free(host, rc);
532 if (!rrdcalctemplate_add_template_from_config(host, rt))
533 rrdcalctemplate_free(host, rt);
537 rc = callocz(1, sizeof(RRDCALC));
538 rc->next_event_id = 1;
539 rc->name = strdupz(value);
540 rc->hash = simple_hash(rc->name);
541 rc->source = health_source_file(line, path, filename);
546 rc->delay_multiplier = 1.0;
548 if(rrdvar_fix_name(rc->name))
549 error("Health configuration renamed alarm '%s' to '%s'", value, rc->name);
551 else if(hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY)) {
553 if(!rrdcalc_add_alarm_from_config(host, rc))
554 rrdcalc_free(host, rc);
558 if(rt && !rrdcalctemplate_add_template_from_config(host, rt))
559 rrdcalctemplate_free(host, rt);
561 rt = callocz(1, sizeof(RRDCALCTEMPLATE));
562 rt->name = strdupz(value);
563 rt->hash_name = simple_hash(rt->name);
564 rt->source = health_source_file(line, path, filename);
567 rt->delay_multiplier = 1.0;
569 if(rrdvar_fix_name(rt->name))
570 error("Health configuration renamed template '%s' to '%s'", value, rt->name);
573 if(hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
575 if(strcmp(rc->chart, value))
576 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
577 line, path, filename, rc->name, key, rc->chart, value, value);
581 rc->chart = strdupz(value);
582 rc->hash_chart = simple_hash(rc->chart);
584 else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
585 health_parse_db_lookup(line, path, filename, value, &rc->group, &rc->after, &rc->before,
587 &rc->options, &rc->dimensions);
589 else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
590 if(!health_parse_duration(value, &rc->update_every))
591 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' cannot parse duration: '%s'.",
592 line, path, filename, rc->name, key, value);
594 else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
596 rc->green = strtold(value, &e);
598 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' leaves this string unmatched: '%s'.",
599 line, path, filename, rc->name, key, e);
602 else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
604 rc->red = strtold(value, &e);
606 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' leaves this string unmatched: '%s'.",
607 line, path, filename, rc->name, key, e);
610 else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) {
611 const char *failed_at = NULL;
613 rc->calculation = expression_parse(value, &failed_at, &error);
614 if(!rc->calculation) {
615 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
616 line, path, filename, rc->name, key, value, expression_strerror(error), failed_at);
619 else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) {
620 const char *failed_at = NULL;
622 rc->warning = expression_parse(value, &failed_at, &error);
624 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
625 line, path, filename, rc->name, key, value, expression_strerror(error), failed_at);
628 else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) {
629 const char *failed_at = NULL;
631 rc->critical = expression_parse(value, &failed_at, &error);
633 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
634 line, path, filename, rc->name, key, value, expression_strerror(error), failed_at);
637 else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) {
639 if(strcmp(rc->exec, value))
640 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
641 line, path, filename, rc->name, key, rc->exec, value, value);
645 rc->exec = strdupz(value);
647 else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
649 if(strcmp(rc->recipient, value))
650 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
651 line, path, filename, rc->name, key, rc->recipient, value, value);
653 freez(rc->recipient);
655 rc->recipient = strdupz(value);
657 else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) {
659 if(strcmp(rc->units, value))
660 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
661 line, path, filename, rc->name, key, rc->units, value, value);
665 rc->units = strdupz(value);
666 strip_quotes(rc->units);
668 else if(hash == hash_info && !strcasecmp(key, HEALTH_INFO_KEY)) {
670 if(strcmp(rc->info, value))
671 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
672 line, path, filename, rc->name, key, rc->info, value, value);
676 rc->info = strdupz(value);
677 strip_quotes(rc->info);
679 else if(hash == hash_delay && !strcasecmp(key, HEALTH_DELAY_KEY)) {
680 health_parse_delay(line, path, filename, value, &rc->delay_up_duration, &rc->delay_down_duration, &rc->delay_max_duration, &rc->delay_multiplier);
682 else if(hash == hash_options && !strcasecmp(key, HEALTH_OPTIONS_KEY)) {
683 rc->options |= health_parse_options(value);
686 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has unknown key '%s'.",
687 line, path, filename, rc->name, key);
691 if(hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
693 if(strcmp(rt->context, value))
694 error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
695 line, path, filename, rt->name, key, rt->context, value, value);
699 rt->context = strdupz(value);
700 rt->hash_context = simple_hash(rt->context);
702 else if(hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY)) {
703 freez(rt->family_match);
704 simple_pattern_free(rt->family_pattern);
706 rt->family_match = strdupz(value);
707 rt->family_pattern = simple_pattern_create(rt->family_match, SIMPLE_PATTERN_EXACT);
709 else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
710 health_parse_db_lookup(line, path, filename, value, &rt->group, &rt->after, &rt->before,
711 &rt->update_every, &rt->options, &rt->dimensions);
713 else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
714 if(!health_parse_duration(value, &rt->update_every))
715 error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' cannot parse duration: '%s'.",
716 line, path, filename, rt->name, key, value);
718 else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
720 rt->green = strtold(value, &e);
722 error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' leaves this string unmatched: '%s'.",
723 line, path, filename, rt->name, key, e);
726 else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
728 rt->red = strtold(value, &e);
730 error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' leaves this string unmatched: '%s'.",
731 line, path, filename, rt->name, key, e);
734 else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) {
735 const char *failed_at = NULL;
737 rt->calculation = expression_parse(value, &failed_at, &error);
738 if(!rt->calculation) {
739 error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
740 line, path, filename, rt->name, key, value, expression_strerror(error), failed_at);
743 else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) {
744 const char *failed_at = NULL;
746 rt->warning = expression_parse(value, &failed_at, &error);
748 error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
749 line, path, filename, rt->name, key, value, expression_strerror(error), failed_at);
752 else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) {
753 const char *failed_at = NULL;
755 rt->critical = expression_parse(value, &failed_at, &error);
757 error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
758 line, path, filename, rt->name, key, value, expression_strerror(error), failed_at);
761 else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) {
763 if(strcmp(rt->exec, value))
764 error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
765 line, path, filename, rt->name, key, rt->exec, value, value);
769 rt->exec = strdupz(value);
771 else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
773 if(strcmp(rt->recipient, value))
774 error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
775 line, path, filename, rt->name, key, rt->recipient, value, value);
777 freez(rt->recipient);
779 rt->recipient = strdupz(value);
781 else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) {
783 if(strcmp(rt->units, value))
784 error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
785 line, path, filename, rt->name, key, rt->units, value, value);
789 rt->units = strdupz(value);
790 strip_quotes(rt->units);
792 else if(hash == hash_info && !strcasecmp(key, HEALTH_INFO_KEY)) {
794 if(strcmp(rt->info, value))
795 error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
796 line, path, filename, rt->name, key, rt->info, value, value);
800 rt->info = strdupz(value);
801 strip_quotes(rt->info);
803 else if(hash == hash_delay && !strcasecmp(key, HEALTH_DELAY_KEY)) {
804 health_parse_delay(line, path, filename, value, &rt->delay_up_duration, &rt->delay_down_duration, &rt->delay_max_duration, &rt->delay_multiplier);
806 else if(hash == hash_options && !strcasecmp(key, HEALTH_OPTIONS_KEY)) {
807 rt->options |= health_parse_options(value);
810 error("Health configuration at line %zu of file '%s/%s' for template '%s' has unknown key '%s'.",
811 line, path, filename, rt->name, key);
815 error("Health configuration at line %zu of file '%s/%s' has unknown key '%s'. Expected either '" HEALTH_ALARM_KEY "' or '" HEALTH_TEMPLATE_KEY "'.",
816 line, path, filename, key);
820 if(rc && !rrdcalc_add_alarm_from_config(host, rc))
821 rrdcalc_free(host, rc);
823 if(rt && !rrdcalctemplate_add_template_from_config(host, rt))
824 rrdcalctemplate_free(host, rt);
830 void health_readdir(RRDHOST *host, const char *path) {
831 if(!host->health_enabled) return;
833 size_t pathlen = strlen(path);
835 debug(D_HEALTH, "Health configuration reading directory '%s'", path);
837 DIR *dir = opendir(path);
839 error("Health configuration cannot open directory '%s'.", path);
843 struct dirent *de = NULL;
844 while ((de = readdir(dir))) {
845 size_t len = strlen(de->d_name);
847 if(de->d_type == DT_DIR
849 (de->d_name[0] == '.' && de->d_name[1] == '\0')
850 || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
852 debug(D_HEALTH, "Ignoring directory '%s'", de->d_name);
856 else if(de->d_type == DT_DIR) {
857 char *s = mallocz(pathlen + strlen(de->d_name) + 2);
860 strcat(s, de->d_name);
861 health_readdir(host, s);
866 else if((de->d_type == DT_LNK || de->d_type == DT_REG || de->d_type == DT_UNKNOWN) &&
867 len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
868 health_readfile(host, path, de->d_name);
871 else debug(D_HEALTH, "Ignoring file '%s'", de->d_name);