]> arthur.barton.de Git - netdata.git/blob - src/health_config.c
Merge pull request #1998 from ktsaou/master
[netdata.git] / src / health_config.c
1 #define NETDATA_HEALTH_INTERNALS
2 #include "common.h"
3
4 #define HEALTH_CONF_MAX_LINE 4096
5
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"
23
24 static inline int rrdcalc_add_alarm_from_config(RRDHOST *host, RRDCALC *rc) {
25     if(!rc->chart) {
26         error("Health configuration for alarm '%s' does not have a chart", rc->name);
27         return 0;
28     }
29
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);
32         return 0;
33     }
34
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);
37         return 0;
38     }
39
40     if (rrdcalc_exists(host, rc->chart, rc->name, rc->hash_chart, rc->hash))
41         return 0;
42
43     rc->id = rrdcalc_get_unique_id(host, rc->chart, rc->name, &rc->next_event_id);
44
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",
47             rc->name,
48             rc->id,
49             (rc->exec)?rc->exec:"DEFAULT",
50             (rc->recipient)?rc->recipient:"DEFAULT",
51             rc->green,
52             rc->red,
53             rc->group,
54             rc->after,
55             rc->before,
56             rc->options,
57             (rc->dimensions)?rc->dimensions:"NONE",
58             rc->update_every,
59             (rc->calculation)?rc->calculation->parsed_as:"NONE",
60             (rc->warning)?rc->warning->parsed_as:"NONE",
61             (rc->critical)?rc->critical->parsed_as:"NONE",
62             rc->source,
63             rc->delay_up_duration,
64             rc->delay_down_duration,
65             rc->delay_max_duration,
66             rc->delay_multiplier
67     );
68
69     rrdcalc_create_part2(host, rc);
70     return 1;
71 }
72
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);
76         return 0;
77     }
78
79     if(unlikely(!rt->update_every)) {
80         error("Health configuration for template '%s' has no frequency (parameter 'every'). Ignoring it.", rt->name);
81         return 0;
82     }
83
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);
86         return 0;
87     }
88
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);
93             return 0;
94         }
95     }
96
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",
98             rt->name,
99             (rt->context)?rt->context:"NONE",
100             (rt->exec)?rt->exec:"DEFAULT",
101             (rt->recipient)?rt->recipient:"DEFAULT",
102             rt->green,
103             rt->red,
104             rt->group,
105             rt->after,
106             rt->before,
107             rt->options,
108             (rt->dimensions)?rt->dimensions:"NONE",
109             rt->update_every,
110             (rt->calculation)?rt->calculation->parsed_as:"NONE",
111             (rt->warning)?rt->warning->parsed_as:"NONE",
112             (rt->critical)?rt->critical->parsed_as:"NONE",
113             rt->source,
114             rt->delay_up_duration,
115             rt->delay_down_duration,
116             rt->delay_max_duration,
117             rt->delay_multiplier
118     );
119
120     if(likely(last)) {
121         last->next = rt;
122     }
123     else {
124         rt->next = host->templates;
125         host->templates = rt;
126     }
127
128     return 1;
129 }
130
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 == '-')) {
134         *result = 0;
135         return 0;
136     }
137
138     char *e = NULL;
139     calculated_number n = strtold(string, &e);
140     if(e && *e) {
141         switch (*e) {
142             case 'Y':
143                 *result = (int) (n * 86400 * 365);
144                 break;
145             case 'M':
146                 *result = (int) (n * 86400 * 30);
147                 break;
148             case 'w':
149                 *result = (int) (n * 86400 * 7);
150                 break;
151             case 'd':
152                 *result = (int) (n * 86400);
153                 break;
154             case 'h':
155                 *result = (int) (n * 3600);
156                 break;
157             case 'm':
158                 *result = (int) (n * 60);
159                 break;
160
161             default:
162             case 's':
163                 *result = (int) (n);
164                 break;
165         }
166     }
167     else
168         *result = (int)(n);
169
170     return 1;
171 }
172
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) {
179
180     char given_up = 0;
181     char given_down = 0;
182     char given_max = 0;
183     char given_multiplier = 0;
184
185     char *s = string;
186     while(*s) {
187         char *key = s;
188
189         while(*s && !isspace(*s)) s++;
190         while(*s && isspace(*s)) *s++ = '\0';
191
192         if(!*key) break;
193
194         char *value = s;
195         while(*s && !isspace(*s)) s++;
196         while(*s && isspace(*s)) *s++ = '\0';
197
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);
202             }
203             else given_up = 1;
204         }
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);
209             }
210             else given_down = 1;
211         }
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);
217             }
218             else given_multiplier = 1;
219         }
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);
224             }
225             else given_max = 1;
226         }
227         else {
228             error("Health configuration at line %zu of file '%s/%s': unknown keyword '%s'",
229                     line, path, file, key);
230         }
231     }
232
233     if(!given_up)
234         *delay_up_duration = 0;
235
236     if(!given_down)
237         *delay_down_duration = 0;
238
239     if(!given_multiplier)
240         *delay_multiplier = 1.0;
241
242     if(!given_max) {
243         if((*delay_max_duration) < (*delay_up_duration) * (*delay_multiplier))
244             *delay_max_duration = (*delay_up_duration) * (*delay_multiplier);
245
246         if((*delay_max_duration) < (*delay_down_duration) * (*delay_multiplier))
247             *delay_max_duration = (*delay_down_duration) * (*delay_multiplier);
248     }
249
250     return 1;
251 }
252
253 static inline uint32_t health_parse_options(const char *s) {
254     uint32_t options = 0;
255     char buf[100+1] = "";
256
257     while(*s) {
258         buf[0] = '\0';
259
260         // skip spaces
261         while(*s && isspace(*s))
262             s++;
263
264         // find the next space
265         size_t count = 0;
266         while(*s && count < 100 && !isspace(*s))
267             buf[count++] = *s++;
268
269         if(buf[0]) {
270             buf[count] = '\0';
271
272             if(!strcasecmp(buf, "no-clear-notification") || !strcasecmp(buf, "no-clear"))
273                 options |= RRDCALC_FLAG_NO_CLEAR_NOTIFICATION;
274             else
275                 error("Ignoring unknown alarm option '%s'", buf);
276         }
277     }
278
279     return options;
280 }
281
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
286 ) {
287     debug(D_HEALTH, "Health configuration parsing database lookup %zu@%s/%s: %s", line, path, file, string);
288
289     if(*dimensions) freez(*dimensions);
290     *dimensions = NULL;
291     *after = 0;
292     *before = 0;
293     *every = 0;
294     *options = 0;
295
296     char *s = string, *key;
297
298     // first is the group method
299     key = s;
300     while(*s && !isspace(*s)) s++;
301     while(*s && isspace(*s)) *s++ = '\0';
302     if(!*s) {
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);
305         return 0;
306     }
307
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);
311         return 0;
312     }
313
314     // then is the 'after' time
315     key = s;
316     while(*s && !isspace(*s)) s++;
317     while(*s && isspace(*s)) *s++ = '\0';
318
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);
322         return 0;
323     }
324
325     // sane defaults
326     *every = abs(*after);
327
328     // now we may have optional parameters
329     while(*s) {
330         key = s;
331         while(*s && !isspace(*s)) s++;
332         while(*s && isspace(*s)) *s++ = '\0';
333         if(!*key) break;
334
335         if(!strcasecmp(key, "at")) {
336             char *value = s;
337             while(*s && !isspace(*s)) s++;
338             while(*s && isspace(*s)) *s++ = '\0';
339
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);
343             }
344         }
345         else if(!strcasecmp(key, HEALTH_EVERY_KEY)) {
346             char *value = s;
347             while(*s && !isspace(*s)) s++;
348             while(*s && isspace(*s)) *s++ = '\0';
349
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);
353             }
354         }
355         else if(!strcasecmp(key, "absolute") || !strcasecmp(key, "abs") || !strcasecmp(key, "absolute_sum")) {
356             *options |= RRDR_OPTION_ABSOLUTE;
357         }
358         else if(!strcasecmp(key, "min2max")) {
359             *options |= RRDR_OPTION_MIN2MAX;
360         }
361         else if(!strcasecmp(key, "null2zero")) {
362             *options |= RRDR_OPTION_NULL2ZERO;
363         }
364         else if(!strcasecmp(key, "percentage")) {
365             *options |= RRDR_OPTION_PERCENTAGE;
366         }
367         else if(!strcasecmp(key, "unaligned")) {
368             *options |= RRDR_OPTION_NOT_ALIGNED;
369         }
370         else if(!strcasecmp(key, "of")) {
371             if(*s && strcasecmp(s, "all"))
372                 *dimensions = strdupz(s);
373             break;
374         }
375         else {
376             error("Health configuration at line %zu of file '%s/%s': unknown keyword '%s'",
377                     line, path, file, key);
378         }
379     }
380
381     return 1;
382 }
383
384 static inline char *trim_all_spaces(char *buffer) {
385     char *d = buffer, *s = buffer;
386
387     // skip spaces
388     while(isspace(*s)) s++;
389
390     while(*s) {
391         // copy the non-space part
392         while(*s && !isspace(*s)) *d++ = *s++;
393
394         // add a space if we have to
395         if(*s && isspace(*s)) {
396             *d++ = ' ';
397             s++;
398         }
399
400         // skip spaces
401         while(isspace(*s)) s++;
402     }
403
404     *d = '\0';
405
406     if(d > buffer) {
407         d--;
408         if(isspace(*d)) *d = '\0';
409     }
410
411     if(!buffer[0]) return NULL;
412     return buffer;
413 }
414
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);
419 }
420
421 static inline void strip_quotes(char *s) {
422     while(*s) {
423         if(*s == '\'' || *s == '"') *s = ' ';
424         s++;
425     }
426 }
427
428 int health_readfile(RRDHOST *host, const char *path, const char *filename) {
429     debug(D_HEALTH, "Health configuration reading file '%s/%s'", path, filename);
430
431     static uint32_t
432             hash_alarm = 0,
433             hash_template = 0,
434             hash_on = 0,
435             hash_families = 0,
436             hash_calc = 0,
437             hash_green = 0,
438             hash_red = 0,
439             hash_warn = 0,
440             hash_crit = 0,
441             hash_exec = 0,
442             hash_every = 0,
443             hash_lookup = 0,
444             hash_units = 0,
445             hash_info = 0,
446             hash_recipient = 0,
447             hash_delay = 0,
448             hash_options = 0;
449
450     char buffer[HEALTH_CONF_MAX_LINE + 1];
451
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);
470     }
471
472     snprintfz(buffer, HEALTH_CONF_MAX_LINE, "%s/%s", path, filename);
473     FILE *fp = fopen(buffer, "r");
474     if(!fp) {
475         error("Health configuration cannot read file '%s'.", buffer);
476         return 0;
477     }
478
479     RRDCALC *rc = NULL;
480     RRDCALCTEMPLATE *rt = NULL;
481
482     size_t line = 0, append = 0;
483     char *s;
484     while((s = fgets(&buffer[append], (int)(HEALTH_CONF_MAX_LINE - append), fp)) || append) {
485         int stop_appending = !s;
486         line++;
487         s = trim(buffer);
488         if(!s) continue;
489
490         append = strlen(s);
491         if(!stop_appending && s[append - 1] == '\\') {
492             s[append - 1] = ' ';
493             append = &s[append] - buffer;
494             if(append < HEALTH_CONF_MAX_LINE)
495                 continue;
496             else {
497                 error("Health configuration has too long muli-line at line %zu of file '%s/%s'.", line, path, filename);
498             }
499         }
500         append = 0;
501
502         char *key = s;
503         while(*s && *s != ':') s++;
504         if(!*s) {
505             error("Health configuration has invalid line %zu of file '%s/%s'. It does not contain a ':'. Ignoring it.", line, path, filename);
506             continue;
507         }
508         *s = '\0';
509         s++;
510
511         char *value = s;
512         key = trim_all_spaces(key);
513         value = trim_all_spaces(value);
514
515         if(!key) {
516             error("Health configuration has invalid line %zu of file '%s/%s'. Keyword is empty. Ignoring it.", line, path, filename);
517             continue;
518         }
519
520         if(!value) {
521             error("Health configuration has invalid line %zu of file '%s/%s'. value is empty. Ignoring it.", line, path, filename);
522             continue;
523         }
524
525         uint32_t hash = simple_uhash(key);
526
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);
530
531             if(rt) {
532                 if (!rrdcalctemplate_add_template_from_config(host, rt))
533                     rrdcalctemplate_free(host, rt);
534                 rt = NULL;
535             }
536
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);
542             rc->green = NAN;
543             rc->red = NAN;
544             rc->value = NAN;
545             rc->old_value = NAN;
546             rc->delay_multiplier = 1.0;
547
548             if(rrdvar_fix_name(rc->name))
549                 error("Health configuration renamed alarm '%s' to '%s'", value, rc->name);
550         }
551         else if(hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY)) {
552             if(rc) {
553                 if(!rrdcalc_add_alarm_from_config(host, rc))
554                     rrdcalc_free(host, rc);
555                 rc = NULL;
556             }
557
558             if(rt && !rrdcalctemplate_add_template_from_config(host, rt))
559                 rrdcalctemplate_free(host, rt);
560
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);
565             rt->green = NAN;
566             rt->red = NAN;
567             rt->delay_multiplier = 1.0;
568
569             if(rrdvar_fix_name(rt->name))
570                 error("Health configuration renamed template '%s' to '%s'", value, rt->name);
571         }
572         else if(rc) {
573             if(hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
574                 if(rc->chart) {
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);
578
579                     freez(rc->chart);
580                 }
581                 rc->chart = strdupz(value);
582                 rc->hash_chart = simple_hash(rc->chart);
583             }
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,
586                         &rc->update_every,
587                         &rc->options, &rc->dimensions);
588             }
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);
593             }
594             else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
595                 char *e;
596                 rc->green = strtold(value, &e);
597                 if(e && *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);
600                 }
601             }
602             else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
603                 char *e;
604                 rc->red = strtold(value, &e);
605                 if(e && *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);
608                 }
609             }
610             else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) {
611                 const char *failed_at = NULL;
612                 int error = 0;
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);
617                 }
618             }
619             else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) {
620                 const char *failed_at = NULL;
621                 int error = 0;
622                 rc->warning = expression_parse(value, &failed_at, &error);
623                 if(!rc->warning) {
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);
626                 }
627             }
628             else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) {
629                 const char *failed_at = NULL;
630                 int error = 0;
631                 rc->critical = expression_parse(value, &failed_at, &error);
632                 if(!rc->critical) {
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);
635                 }
636             }
637             else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) {
638                 if(rc->exec) {
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);
642
643                     freez(rc->exec);
644                 }
645                 rc->exec = strdupz(value);
646             }
647             else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
648                 if(rc->recipient) {
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);
652
653                     freez(rc->recipient);
654                 }
655                 rc->recipient = strdupz(value);
656             }
657             else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) {
658                 if(rc->units) {
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);
662
663                     freez(rc->units);
664                 }
665                 rc->units = strdupz(value);
666                 strip_quotes(rc->units);
667             }
668             else if(hash == hash_info && !strcasecmp(key, HEALTH_INFO_KEY)) {
669                 if(rc->info) {
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);
673
674                     freez(rc->info);
675                 }
676                 rc->info = strdupz(value);
677                 strip_quotes(rc->info);
678             }
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);
681             }
682             else if(hash == hash_options && !strcasecmp(key, HEALTH_OPTIONS_KEY)) {
683                 rc->options |= health_parse_options(value);
684             }
685             else {
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);
688             }
689         }
690         else if(rt) {
691             if(hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
692                 if(rt->context) {
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);
696
697                     freez(rt->context);
698                 }
699                 rt->context = strdupz(value);
700                 rt->hash_context = simple_hash(rt->context);
701             }
702             else if(hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY)) {
703                 freez(rt->family_match);
704                 simple_pattern_free(rt->family_pattern);
705
706                 rt->family_match = strdupz(value);
707                 rt->family_pattern = simple_pattern_create(rt->family_match, SIMPLE_PATTERN_EXACT);
708             }
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);
712             }
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);
717             }
718             else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
719                 char *e;
720                 rt->green = strtold(value, &e);
721                 if(e && *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);
724                 }
725             }
726             else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
727                 char *e;
728                 rt->red = strtold(value, &e);
729                 if(e && *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);
732                 }
733             }
734             else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) {
735                 const char *failed_at = NULL;
736                 int error = 0;
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);
741                 }
742             }
743             else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) {
744                 const char *failed_at = NULL;
745                 int error = 0;
746                 rt->warning = expression_parse(value, &failed_at, &error);
747                 if(!rt->warning) {
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);
750                 }
751             }
752             else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) {
753                 const char *failed_at = NULL;
754                 int error = 0;
755                 rt->critical = expression_parse(value, &failed_at, &error);
756                 if(!rt->critical) {
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);
759                 }
760             }
761             else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) {
762                 if(rt->exec) {
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);
766
767                     freez(rt->exec);
768                 }
769                 rt->exec = strdupz(value);
770             }
771             else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
772                 if(rt->recipient) {
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);
776
777                     freez(rt->recipient);
778                 }
779                 rt->recipient = strdupz(value);
780             }
781             else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) {
782                 if(rt->units) {
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);
786
787                     freez(rt->units);
788                 }
789                 rt->units = strdupz(value);
790                 strip_quotes(rt->units);
791             }
792             else if(hash == hash_info && !strcasecmp(key, HEALTH_INFO_KEY)) {
793                 if(rt->info) {
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);
797
798                     freez(rt->info);
799                 }
800                 rt->info = strdupz(value);
801                 strip_quotes(rt->info);
802             }
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);
805             }
806             else if(hash == hash_options && !strcasecmp(key, HEALTH_OPTIONS_KEY)) {
807                 rt->options |= health_parse_options(value);
808             }
809             else {
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);
812             }
813         }
814         else {
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);
817         }
818     }
819
820     if(rc && !rrdcalc_add_alarm_from_config(host, rc))
821         rrdcalc_free(host, rc);
822
823     if(rt && !rrdcalctemplate_add_template_from_config(host, rt))
824         rrdcalctemplate_free(host, rt);
825
826     fclose(fp);
827     return 1;
828 }
829
830 void health_readdir(RRDHOST *host, const char *path) {
831     if(!host->health_enabled) return;
832
833     size_t pathlen = strlen(path);
834
835     debug(D_HEALTH, "Health configuration reading directory '%s'", path);
836
837     DIR *dir = opendir(path);
838     if (!dir) {
839         error("Health configuration cannot open directory '%s'.", path);
840         return;
841     }
842
843     struct dirent *de = NULL;
844     while ((de = readdir(dir))) {
845         size_t len = strlen(de->d_name);
846
847         if(de->d_type == DT_DIR
848            && (
849                    (de->d_name[0] == '.' && de->d_name[1] == '\0')
850                    || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
851            )) {
852             debug(D_HEALTH, "Ignoring directory '%s'", de->d_name);
853             continue;
854         }
855
856         else if(de->d_type == DT_DIR) {
857             char *s = mallocz(pathlen + strlen(de->d_name) + 2);
858             strcpy(s, path);
859             strcat(s, "/");
860             strcat(s, de->d_name);
861             health_readdir(host, s);
862             freez(s);
863             continue;
864         }
865
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);
869         }
870
871         else debug(D_HEALTH, "Ignoring file '%s'", de->d_name);
872     }
873
874     closedir(dir);
875 }
876
877