]> arthur.barton.de Git - netdata.git/blob - src/health.c
health configuration now parses templates too
[netdata.git] / src / health.c
1 #include "common.h"
2
3 static const char *health_default_exec = PLUGINS_DIR "/alarm.sh";
4
5 // ----------------------------------------------------------------------------
6 // RRDVAR management
7
8 int rrdvar_compare(void* a, void* b) {
9     if(((RRDVAR *)a)->hash < ((RRDVAR *)b)->hash) return -1;
10     else if(((RRDVAR *)a)->hash > ((RRDVAR *)b)->hash) return 1;
11     else return strcmp(((RRDVAR *)a)->name, ((RRDVAR *)b)->name);
12 }
13
14 static inline RRDVAR *rrdvar_index_add(avl_tree_lock *tree, RRDVAR *rv) {
15     RRDVAR *ret = (RRDVAR *)avl_insert_lock(tree, (avl *)(rv));
16     if(ret != rv)
17         debug(D_VARIABLES, "Request to insert RRDVAR '%s' into index failed. Already exists.", rv->name);
18
19     return ret;
20 }
21
22 static inline RRDVAR *rrdvar_index_del(avl_tree_lock *tree, RRDVAR *rv) {
23     RRDVAR *ret = (RRDVAR *)avl_remove_lock(tree, (avl *)(rv));
24     if(!ret)
25         fatal("Request to remove RRDVAR '%s' from index failed. Not Found.", rv->name);
26
27     return ret;
28 }
29
30 static inline RRDVAR *rrdvar_index_find(avl_tree_lock *tree, const char *name, uint32_t hash) {
31     RRDVAR tmp;
32     tmp.name = (char *)name;
33     tmp.hash = (hash)?hash:simple_hash(tmp.name);
34
35     return (RRDVAR *)avl_search_lock(tree, (avl *)&tmp);
36 }
37
38 static inline RRDVAR *rrdvar_create(const char *name, uint32_t hash, int type, calculated_number *value) {
39     RRDVAR *rv = callocz(1, sizeof(RRDVAR));
40
41     rv->name = (char *)name;
42     rv->hash = (hash)?hash:simple_hash((rv->name));
43     rv->type = type;
44     rv->value = value;
45
46     return rv;
47 }
48
49 static inline void rrdvar_free(RRDHOST *host, RRDVAR *rv) {
50     if(host) {
51         // FIXME: we may need some kind of locking here
52         EVAL_VARIABLE *rf;
53         for (rf = host->references; rf; rf = rf->next)
54             if (rf->rrdvar == rv) rf->rrdvar = NULL;
55     }
56
57     freez(rv);
58 }
59
60 static inline RRDVAR *rrdvar_create_and_index(const char *scope, avl_tree_lock *tree, const char *name, uint32_t hash, int type, calculated_number *value) {
61     RRDVAR *rv = rrdvar_index_find(tree, name, hash);
62     if(unlikely(!rv)) {
63         debug(D_VARIABLES, "Variable '%s' not found in scope '%s'. Creating a new one.", name, scope);
64
65         rv = rrdvar_create(name, hash, type, value);
66         RRDVAR *ret = rrdvar_index_add(tree, rv);
67         if(unlikely(ret != rv)) {
68             debug(D_VARIABLES, "Variable '%s' in scope '%s' already exists", name, scope);
69             rrdvar_free(NULL, rv);
70             rv = NULL;
71         }
72         else
73             debug(D_VARIABLES, "Variable '%s' created in scope '%s'", name, scope);
74     }
75     else {
76         // already exists
77         rv = NULL;
78     }
79
80     /*
81      * check
82     if(rv) {
83         RRDVAR *ret = rrdvar_index_find(tree, name, hash);
84         if(ret != rv) fatal("oops! 1");
85
86         ret = rrdvar_index_del(tree, rv);
87         if(ret != rv) fatal("oops! 2");
88
89         ret = rrdvar_index_add(tree, rv);
90         if(ret != rv) fatal("oops! 3");
91     }
92     */
93
94     return rv;
95 }
96
97 // ----------------------------------------------------------------------------
98 // RRDSETVAR management
99
100 #define RRDSETVAR_ID_MAX 1024
101
102 RRDSETVAR *rrdsetvar_create(RRDSET *st, const char *variable, int type, void *value, uint32_t options) {
103     debug(D_VARIABLES, "RRDVARSET create for chart id '%s' name '%s' with variable name '%s'", st->id, st->name, variable);
104     RRDSETVAR *rs = (RRDSETVAR *)callocz(1, sizeof(RRDSETVAR));
105
106     char buffer[RRDSETVAR_ID_MAX + 1];
107     snprintfz(buffer, RRDSETVAR_ID_MAX, "%s.%s", st->id, variable);
108     rs->fullid = strdupz(buffer);
109     rs->hash_fullid = simple_hash(rs->fullid);
110
111     snprintfz(buffer, RRDSETVAR_ID_MAX, "%s.%s", st->name, variable);
112     rs->fullname = strdupz(buffer);
113     rs->hash_fullname = simple_hash(rs->fullname);
114
115     rs->variable = strdupz(variable);
116     rs->hash_variable = simple_hash(rs->variable);
117
118     rs->type = type;
119     rs->value = value;
120     rs->options = options;
121     rs->rrdset = st;
122
123     rs->local        = rrdvar_create_and_index("local",   &st->variables_root_index, rs->variable, rs->hash_variable, rs->type, rs->value);
124     rs->context      = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index, rs->fullid, rs->hash_fullid, rs->type, rs->value);
125     rs->host         = rrdvar_create_and_index("host",    &st->rrdhost->variables_root_index, rs->fullid, rs->hash_fullid, rs->type, rs->value);
126     rs->context_name = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index, rs->fullname, rs->hash_fullname, rs->type, rs->value);
127     rs->host_name    = rrdvar_create_and_index("host",    &st->rrdhost->variables_root_index, rs->fullname, rs->hash_fullname, rs->type, rs->value);
128
129     rs->next = st->variables;
130     st->variables = rs;
131
132     return rs;
133 }
134
135 void rrdsetvar_rename_all(RRDSET *st) {
136     debug(D_VARIABLES, "RRDSETVAR rename for chart id '%s' name '%s'", st->id, st->name);
137
138     // only these 2 can change name
139     // rs->context_name
140     // rs->host_name
141
142     char buffer[RRDSETVAR_ID_MAX + 1];
143     RRDSETVAR *rs, *next = st->variables;
144     while((rs = next)) {
145         next = rs->next;
146
147         snprintfz(buffer, RRDSETVAR_ID_MAX, "%s.%s", st->name, rs->variable);
148
149         if (strcmp(buffer, rs->fullname)) {
150             // name changed
151             if (rs->context_name) {
152                 rrdvar_index_del(&st->rrdcontext->variables_root_index, rs->context_name);
153                 rrdvar_free(st->rrdhost, rs->context_name);
154             }
155
156             if (rs->host_name) {
157                 rrdvar_index_del(&st->rrdhost->variables_root_index, rs->host_name);
158                 rrdvar_free(st->rrdhost, rs->host_name);
159             }
160
161             freez(rs->fullname);
162             rs->fullname = strdupz(st->name);
163             rs->hash_fullname = simple_hash(rs->fullname);
164             rs->context_name = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index, rs->fullname, rs->hash_fullname, rs->type, rs->value);
165             rs->host_name    = rrdvar_create_and_index("host",    &st->rrdhost->variables_root_index, rs->fullname, rs->hash_fullname, rs->type, rs->value);
166         }
167     }
168
169     rrdsetcalc_link_matching(st);
170 }
171
172 void rrdsetvar_free(RRDSETVAR *rs) {
173     RRDSET *st = rs->rrdset;
174     debug(D_VARIABLES, "RRDSETVAR free for chart id '%s' name '%s', variable '%s'", st->id, st->name, rs->variable);
175
176     if(rs->local) {
177         rrdvar_index_del(&st->variables_root_index, rs->local);
178         rrdvar_free(st->rrdhost, rs->local);
179     }
180
181     if(rs->context) {
182         rrdvar_index_del(&st->rrdcontext->variables_root_index, rs->context);
183         rrdvar_free(st->rrdhost, rs->context);
184     }
185
186     if(rs->host) {
187         rrdvar_index_del(&st->rrdhost->variables_root_index, rs->host);
188         rrdvar_free(st->rrdhost, rs->host);
189     }
190
191     if(rs->context_name) {
192         rrdvar_index_del(&st->rrdcontext->variables_root_index, rs->context_name);
193         rrdvar_free(st->rrdhost, rs->context_name);
194     }
195
196     if(rs->host_name) {
197         rrdvar_index_del(&st->rrdhost->variables_root_index, rs->host_name);
198         rrdvar_free(st->rrdhost, rs->host_name);
199     }
200
201     if(st->variables == rs) {
202         st->variables = rs->next;
203     }
204     else {
205         RRDSETVAR *t;
206         for (t = st->variables; t && t->next != rs; t = t->next);
207         if(!t) error("RRDSETVAR '%s' not found in chart '%s' variables linked list", rs->fullname, st->id);
208         else t->next = rs->next;
209     }
210
211     freez(rs->fullid);
212     freez(rs->fullname);
213     freez(rs->variable);
214     freez(rs);
215 }
216
217 // ----------------------------------------------------------------------------
218 // RRDDIMVAR management
219
220 #define RRDDIMVAR_ID_MAX 1024
221
222 RRDDIMVAR *rrddimvar_create(RRDDIM *rd, int type, const char *prefix, const char *suffix, void *value, uint32_t options) {
223     RRDSET *st = rd->rrdset;
224
225     debug(D_VARIABLES, "RRDDIMSET create for chart id '%s' name '%s', dimension id '%s', name '%s%s%s'", st->id, st->name, rd->id, (prefix)?prefix:"", rd->name, (suffix)?suffix:"");
226
227     if(!prefix) prefix = "";
228     if(!suffix) suffix = "";
229
230     char buffer[RRDDIMVAR_ID_MAX + 1];
231     RRDDIMVAR *rs = (RRDDIMVAR *)callocz(1, sizeof(RRDDIMVAR));
232
233     rs->prefix = strdupz(prefix);
234     rs->suffix = strdupz(suffix);
235
236     snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s%s%s", rs->prefix, rd->id, rs->suffix);
237     rs->id = strdupz(buffer);
238     rs->hash = simple_hash(rs->id);
239
240     snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s%s%s", rs->prefix, rd->name, rs->suffix);
241     rs->name = strdupz(buffer);
242     rs->hash_name = simple_hash(rs->name);
243
244     snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", rd->rrdset->id, rs->id);
245     rs->fullidid = strdupz(buffer);
246     rs->hash_fullidid = simple_hash(rs->fullidid);
247
248     snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", rd->rrdset->id, rs->name);
249     rs->fullidname = strdupz(buffer);
250     rs->hash_fullidname = simple_hash(rs->fullidname);
251
252     snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", rd->rrdset->name, rs->id);
253     rs->fullnameid = strdupz(buffer);
254     rs->hash_fullnameid = simple_hash(rs->fullnameid);
255
256     snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", rd->rrdset->name, rs->name);
257     rs->fullnamename = strdupz(buffer);
258     rs->hash_fullnamename = simple_hash(rs->fullnamename);
259
260     rs->type = type;
261     rs->value = value;
262     rs->options = options;
263     rs->rrddim = rd;
264
265     rs->local_id     = rrdvar_create_and_index("local",   &st->variables_root_index, rs->id, rs->hash, rs->type, rs->value);
266     rs->local_name   = rrdvar_create_and_index("local",   &st->variables_root_index, rs->name, rs->hash_name, rs->type, rs->value);
267
268     rs->context_fullidid     = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index, rs->fullidid, rs->hash_fullidid, rs->type, rs->value);
269     rs->context_fullidname   = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index, rs->fullidname, rs->hash_fullidname, rs->type, rs->value);
270     rs->context_fullnameid   = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index, rs->fullnameid, rs->hash_fullnameid, rs->type, rs->value);
271     rs->context_fullnamename = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index, rs->fullnamename, rs->hash_fullnamename, rs->type, rs->value);
272
273     rs->host_fullidid     = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullidid, rs->hash_fullidid, rs->type, rs->value);
274     rs->host_fullidname   = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullidname, rs->hash_fullidname, rs->type, rs->value);
275     rs->host_fullnameid   = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullnameid, rs->hash_fullnameid, rs->type, rs->value);
276     rs->host_fullnamename = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rs->fullnamename, rs->hash_fullnamename, rs->type, rs->value);
277
278     rs->next = rd->variables;
279     rd->variables = rs;
280
281     return rs;
282 }
283
284 void rrddimvar_rename_all(RRDDIM *rd) {
285     RRDSET *st = rd->rrdset;
286     debug(D_VARIABLES, "RRDDIMSET rename for chart id '%s' name '%s', dimension id '%s', name '%s'", st->id, st->name, rd->id, rd->name);
287
288     RRDDIMVAR *rs, *next = rd->variables;
289     while((rs = next)) {
290         next = rs->next;
291
292         if (strcmp(rd->name, rs->name)) {
293             char buffer[RRDDIMVAR_ID_MAX + 1];
294             // name changed
295
296             // name
297             if (rs->local_name) {
298                 rrdvar_index_del(&st->variables_root_index, rs->local_name);
299                 rrdvar_free(st->rrdhost, rs->local_name);
300             }
301             freez(rs->name);
302             snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s%s%s", rs->prefix, rd->name, rs->suffix);
303             rs->name = strdupz(buffer);
304             rs->hash_name = simple_hash(rs->name);
305             rs->local_name = rrdvar_create_and_index("local", &st->variables_root_index, rs->name, rs->hash_name, rs->type, rs->value);
306
307             // fullidname
308             if (rs->context_fullidname) {
309                 rrdvar_index_del(&st->rrdcontext->variables_root_index, rs->context_fullidname);
310                 rrdvar_free(st->rrdhost, rs->context_fullidname);
311             }
312             if (rs->host_fullidname) {
313                 rrdvar_index_del(&st->rrdhost->variables_root_index, rs->context_fullidname);
314                 rrdvar_free(st->rrdhost, rs->host_fullidname);
315             }
316             freez(rs->fullidname);
317             snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", st->id, rs->name);
318             rs->fullidname = strdupz(buffer);
319             rs->hash_fullidname = simple_hash(rs->fullidname);
320             rs->context_fullidname = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index,
321                                                              rs->fullidname, rs->hash_fullidname, rs->type, rs->value);
322             rs->host_fullidname = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index,
323                                                              rs->fullidname, rs->hash_fullidname, rs->type, rs->value);
324
325             // fullnameid
326             if (rs->context_fullnameid) {
327                 rrdvar_index_del(&st->rrdcontext->variables_root_index, rs->context_fullnameid);
328                 rrdvar_free(st->rrdhost, rs->context_fullnameid);
329             }
330             if (rs->host_fullnameid) {
331                 rrdvar_index_del(&st->rrdhost->variables_root_index, rs->context_fullnameid);
332                 rrdvar_free(st->rrdhost, rs->host_fullnameid);
333             }
334             freez(rs->fullnameid);
335             snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", st->name, rs->id);
336             rs->fullnameid = strdupz(buffer);
337             rs->hash_fullnameid = simple_hash(rs->fullnameid);
338             rs->context_fullnameid = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index,
339                                                              rs->fullnameid, rs->hash_fullnameid, rs->type, rs->value);
340             rs->host_fullnameid = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index,
341                                                           rs->fullnameid, rs->hash_fullnameid, rs->type, rs->value);
342
343             // fullnamename
344             if (rs->context_fullnamename) {
345                 rrdvar_index_del(&st->rrdcontext->variables_root_index, rs->context_fullnamename);
346                 rrdvar_free(st->rrdhost, rs->context_fullnamename);
347             }
348             if (rs->host_fullnamename) {
349                 rrdvar_index_del(&st->rrdhost->variables_root_index, rs->context_fullnamename);
350                 rrdvar_free(st->rrdhost, rs->host_fullnamename);
351             }
352             freez(rs->fullnamename);
353             snprintfz(buffer, RRDDIMVAR_ID_MAX, "%s.%s", st->name, rs->name);
354             rs->fullnamename = strdupz(buffer);
355             rs->hash_fullnamename = simple_hash(rs->fullnamename);
356             rs->context_fullnamename = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index,
357                                                              rs->fullnamename, rs->hash_fullnamename, rs->type, rs->value);
358             rs->host_fullnamename = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index,
359                                                           rs->fullnamename, rs->hash_fullnamename, rs->type, rs->value);
360         }
361     }
362 }
363
364 void rrddimvar_free(RRDDIMVAR *rs) {
365     RRDDIM *rd = rs->rrddim;
366     RRDSET *st = rd->rrdset;
367     debug(D_VARIABLES, "RRDDIMSET free for chart id '%s' name '%s', dimension id '%s', name '%s', prefix='%s', suffix='%s'", st->id, st->name, rd->id, rd->name, rs->prefix, rs->suffix);
368
369     if(rs->local_id) {
370         rrdvar_index_del(&st->variables_root_index, rs->local_id);
371         rrdvar_free(st->rrdhost, rs->local_id);
372     }
373     if(rs->local_name) {
374         rrdvar_index_del(&st->variables_root_index, rs->local_name);
375         rrdvar_free(st->rrdhost, rs->local_name);
376     }
377
378     if(rs->context_fullidid) {
379         rrdvar_index_del(&st->rrdcontext->variables_root_index, rs->context_fullidid);
380         rrdvar_free(st->rrdhost, rs->context_fullidid);
381     }
382     if(rs->context_fullidname) {
383         rrdvar_index_del(&st->rrdcontext->variables_root_index, rs->context_fullidname);
384         rrdvar_free(st->rrdhost, rs->context_fullidname);
385     }
386     if(rs->context_fullnameid) {
387         rrdvar_index_del(&st->rrdcontext->variables_root_index, rs->context_fullnameid);
388         rrdvar_free(st->rrdhost, rs->context_fullnameid);
389     }
390     if(rs->context_fullnamename) {
391         rrdvar_index_del(&st->rrdcontext->variables_root_index, rs->context_fullnamename);
392         rrdvar_free(st->rrdhost, rs->context_fullnamename);
393     }
394
395     if(rs->host_fullidid) {
396         rrdvar_index_del(&st->rrdhost->variables_root_index, rs->host_fullidid);
397         rrdvar_free(st->rrdhost, rs->host_fullidid);
398     }
399     if(rs->host_fullidname) {
400         rrdvar_index_del(&st->rrdhost->variables_root_index, rs->host_fullidname);
401         rrdvar_free(st->rrdhost, rs->host_fullidname);
402     }
403     if(rs->host_fullnameid) {
404         rrdvar_index_del(&st->rrdhost->variables_root_index, rs->host_fullnameid);
405         rrdvar_free(st->rrdhost, rs->host_fullnameid);
406     }
407     if(rs->host_fullnamename) {
408         rrdvar_index_del(&st->rrdhost->variables_root_index, rs->host_fullnamename);
409         rrdvar_free(st->rrdhost, rs->host_fullnamename);
410     }
411
412     if(rd->variables == rs) {
413         debug(D_VARIABLES, "RRDDIMSET removing first entry for chart id '%s' name '%s', dimension id '%s', name '%s'", st->id, st->name, rd->id, rd->name);
414         rd->variables = rs->next;
415     }
416     else {
417         debug(D_VARIABLES, "RRDDIMSET removing non-first entry for chart id '%s' name '%s', dimension id '%s', name '%s'", st->id, st->name, rd->id, rd->name);
418         RRDDIMVAR *t;
419         for (t = rd->variables; t && t->next != rs; t = t->next) ;
420         if(!t) error("RRDDIMVAR '%s' not found in dimension '%s/%s' variables linked list", rs->name, st->id, rd->id);
421         else t->next = rs->next;
422     }
423
424     freez(rs->prefix);
425     freez(rs->suffix);
426     freez(rs->id);
427     freez(rs->name);
428     freez(rs->fullidid);
429     freez(rs->fullidname);
430     freez(rs->fullnameid);
431     freez(rs->fullnamename);
432     freez(rs);
433 }
434
435 // ----------------------------------------------------------------------------
436 // RRDCALC management
437
438 // this has to be called while the caller has locked
439 // the RRDHOST
440 static inline void rrdset_linked_optimize_rrdhost(RRDHOST *host, RRDCALC *rc) {
441     rrdhost_check_wrlock(host);
442
443     // move it to be last
444
445     if(!rc->next)
446         // we are last already
447         return;
448
449     RRDCALC *t, *last = NULL, *prev = NULL;
450     for (t = host->calculations; t ; t = t->next) {
451         if(t->next == rc)
452             prev = t;
453
454         if(!t->next)
455             last = t;
456     }
457
458     if(!last) {
459         error("RRDCALC '%s' cannot be linked to the end of host '%s' list", rc->name, host->hostname);
460         return;
461     }
462
463     if(prev)
464         prev->next = rc->next;
465     else {
466         if(host->calculations == rc)
467             host->calculations = rc->next;
468         else {
469             error("RRDCALC '%s' is not found in host '%s' list", rc->name, host->hostname);
470             return;
471         }
472     }
473
474     last->next = rc;
475     rc->next = NULL;
476 }
477
478 // this has to be called while the caller has locked
479 // the RRDHOST
480 static inline void rrdcalc_unlinked_optimize_rrdhost(RRDHOST *host, RRDCALC *rc) {
481     rrdhost_check_wrlock(host);
482     
483     // move it to be first
484
485     if(host->calculations == rc) {
486         // ok, we are the first
487         return;
488     }
489     else {
490         // find the previous one
491         RRDCALC *t;
492         for (t = host->calculations; t && t->next != rc; rc = rc->next) ;
493         if(unlikely(!t)) {
494             error("RRDCALC '%s' is not linked to host '%s'.", rc->name, host->hostname);
495             return;
496         }
497         t->next = rc->next;
498         rc->next = host->calculations;
499         host->calculations = rc;
500     }
501 }
502
503 static void rrdsetcalc_link(RRDSET *st, RRDCALC *rc) {
504     rc->rrdset = st;
505
506     if(rc->green)
507         st->green = rc->green;
508
509     if(rc->red)
510         st->red = rc->red;
511
512     rc->local   = rrdvar_create_and_index("local", &st->variables_root_index, rc->name, rc->hash, RRDVAR_TYPE_CALCULATED, &rc->value);
513     rc->context = rrdvar_create_and_index("context", &st->rrdcontext->variables_root_index, rc->name, rc->hash, RRDVAR_TYPE_CALCULATED, &rc->value);
514     rc->host    = rrdvar_create_and_index("host", &st->rrdhost->variables_root_index, rc->name, rc->hash, RRDVAR_TYPE_CALCULATED, &rc->value);
515
516     rrdset_linked_optimize_rrdhost(st->rrdhost, rc);
517 }
518
519 static inline int rrdcalc_is_matching_this_rrdset(RRDCALC *rc, RRDSET *st) {
520     if((rc->hash_chart == st->hash && !strcmp(rc->name, st->id)) ||
521             (rc->hash_chart == st->hash_name && !strcmp(rc->name, st->name)))
522         return 1;
523
524     return 0;
525 }
526
527 // this has to be called while the RRDHOST is locked
528 void rrdsetcalc_link_matching(RRDSET *st) {
529     RRDCALC *rc;
530
531     for(rc = st->rrdhost->calculations; rc ; rc = rc->next) {
532         // since unlinked ones are in front and linked at the end
533         // we stop on the first linked RRDCALC
534         if(rc->rrdset != NULL) break;
535
536         if(rrdcalc_is_matching_this_rrdset(rc, st))
537             rrdsetcalc_link(st, rc);
538     }
539 }
540
541 // this has to be called while the RRDHOST is locked
542 void rrdsetcalc_unlink(RRDCALC *rc) {
543     RRDSET *st = rc->rrdset;
544
545     if(!st) {
546         error("Requested to unlink RRDCALC '%s' which is not linked to any RRDSET", rc->name);
547         return;
548     }
549
550     RRDHOST *host = st->rrdhost;
551
552     // unlink it
553     if(rc->rrdset_prev)
554         rc->rrdset_prev->rrdset_next = rc->rrdset_next;
555
556     if(rc->rrdset_next)
557         rc->rrdset_next->rrdset_prev = rc->rrdset_prev;
558
559     if(st->calculations == rc)
560         st->calculations = rc->rrdset_next;
561
562     rc->rrdset_prev = rc->rrdset_next = NULL;
563
564     if(rc->local) {
565         rrdvar_index_del(&st->variables_root_index, rc->local);
566         rrdvar_free(st->rrdhost, rc->local);
567         rc->local = NULL;
568     }
569
570     if(rc->context) {
571         rrdvar_index_del(&st->rrdcontext->variables_root_index, rc->context);
572         rc->context = NULL;
573     }
574
575     if(rc->host) {
576         rrdvar_index_del(&st->rrdhost->variables_root_index, rc->host);
577         rc->host = NULL;
578     }
579
580     rc->rrdset = NULL;
581
582     // RRDCALC will remain in RRDHOST
583     // so that if the matching chart is found in the future
584     // it will be applied automatically
585
586     rrdcalc_unlinked_optimize_rrdhost(host, rc);
587 }
588
589 static inline int rrdcalc_exists(RRDHOST *host, const char *name, uint32_t hash) {
590     RRDCALC *rc;
591
592     // make sure it does not already exist
593     for(rc = host->calculations; rc ; rc = rc->next) {
594         if (rc->hash == hash && !strcmp(name, rc->name)) {
595             error("Attempted to create RRDCAL '%s' in host '%s', but it already exists.", name, host->hostname);
596             return 1;
597         }
598     }
599
600     return 0;
601 }
602
603 void rrdcalc_create_part2(RRDHOST *host, RRDCALC *rc) {
604     // link it to the host
605     rc->next = host->calculations;
606     host->calculations = rc;
607
608     // link it to its chart
609     RRDSET *st;
610     for(st = host->rrdset_root; st ; st = st->next) {
611         if(rrdcalc_is_matching_this_rrdset(rc, st)) {
612             rrdsetcalc_link(st, rc);
613             break;
614         }
615     }
616 }
617
618 RRDCALC *rrdcalc_create(RRDHOST *host, const char *name, const char *chart, const char *dimensions, int group_method, uint32_t after, uint32_t before, int update_every, uint32_t options) {
619     uint32_t hash = simple_hash(name);
620
621     if(rrdcalc_exists(host, name, hash))
622         return NULL;
623
624     RRDCALC *rc = callocz(1, sizeof(RRDCALC));
625
626     rc->name = strdupz(name);
627     rc->hash = simple_hash(rc->name);
628
629     rc->chart = strdupz(chart);
630     rc->hash_chart = simple_hash(rc->chart);
631
632     if(dimensions) rc->dimensions = strdupz(dimensions);
633
634     rc->group = group_method;
635     rc->after = after;
636     rc->before = before;
637     rc->update_every = update_every;
638     rc->options = options;
639
640     rrdcalc_create_part2(host, rc);
641     return rc;
642 }
643
644 void rrdcalc_free(RRDHOST *host, RRDCALC *rc) {
645     if(!rc) return;
646
647     // unlink it from RRDSET
648     if(rc->rrdset) rrdsetcalc_unlink(rc);
649
650     // unlink it from RRDHOST
651     if(rc == host->calculations)
652         host->calculations = rc->next;
653
654     else if(host->calculations) {
655         RRDCALC *t, *last = host->calculations;
656
657         for(t = last->next; t && t != rc; last = t, t = t->next) ;
658         if(last && last->next == rc)
659             last->next = rc->next;
660         else
661             error("Cannot unlink RRDCALC '%s' from RRDHOST '%s': not found", rc->name, host->hostname);
662     }
663     else
664         error("Cannot unlink RRDCALC '%s' from RRDHOST '%s': RRDHOST does not have any calculations", rc->name, host->hostname);
665
666     if(rc->warning) expression_free(rc->warning);
667     if(rc->critical) expression_free(rc->critical);
668
669     freez(rc->source);
670     freez(rc->name);
671     freez(rc->chart);
672     freez(rc->dimensions);
673     freez(rc->exec);
674
675     freez(rc);
676 }
677
678 // ----------------------------------------------------------------------------
679 // RRDCALCTEMPLATE management
680
681 static inline void rrdcalctemplate_free(RRDHOST *host, RRDCALCTEMPLATE *rt) {
682     if(host->templates) {
683         if(host->templates == rt) {
684             host->templates = rt->next;
685         }
686         else {
687             RRDCALCTEMPLATE *t, *last = host->templates;
688             for (t = last->next; t && t != rt; last = t, t = t->next ) ;
689             if(last && last->next == rt) {
690                 last->next = rt->next;
691                 rt->next = NULL;
692             }
693             else
694                 error("Cannot find RRDCALCTEMPLATE '%s' linked in host '%s'", rt->name, host->hostname);
695         }
696     }
697
698     if(rt->warning) expression_free(rt->warning);
699     if(rt->critical) expression_free(rt->critical);
700
701     freez(rt->dimensions);
702     freez(rt->context);
703     freez(rt->name);
704     freez(rt->exec);
705     freez(rt->source);
706     freez(rt);
707 }
708
709 // ----------------------------------------------------------------------------
710 // load health configuration
711
712 #define HEALTH_CONF_MAX_LINE 4096
713
714 #define HEALTH_ALARM_KEY "alarm"
715 #define HEALTH_TEMPLATE_KEY "template"
716 #define HEALTH_ON_KEY "on"
717 #define HEALTH_CALC_KEY "calc"
718 #define HEALTH_GREEN_KEY "green"
719 #define HEALTH_RED_KEY "red"
720 #define HEALTH_WARN_KEY "warn"
721 #define HEALTH_CRIT_KEY "crit"
722 #define HEALTH_EXEC_KEY "exec"
723
724 static inline int rrdcalc_add(RRDHOST *host, RRDCALC *rc) {
725     info("Health configuration examining alarm '%s': chart '%s', exec '%s', green %Lf, red %Lf, calculation: group %d, after %d, before %d, options %u, update every %d, dimensions '%s', warning '%s', critical '%s', source '%s",
726          rc->name,
727          (rc->chart)?rc->chart:"NONE",
728          (rc->exec)?rc->exec:"DEFAULT",
729          rc->green,
730          rc->red,
731          rc->group,
732          rc->after,
733          rc->before,
734          rc->options,
735          rc->update_every,
736          (rc->dimensions)?rc->dimensions:"NONE",
737          (rc->warning)?rc->warning->parsed_as:"NONE",
738          (rc->critical)?rc->critical->parsed_as:"NONE",
739          rc->source
740     );
741
742     if(rrdcalc_exists(host, rc->name, rc->hash))
743         return 0;
744
745     if(!rc->chart) {
746         error("Health configuration for alarm '%s' does not have a chart", rc->name);
747         return 0;
748     }
749
750     if(!RRDCALC_HAS_CALCULATION(rc) && !rc->warning && !rc->critical) {
751         error("Health configuration for alarm '%s' is useless (no calculation, no warning and no critical evaluation)", rc->name);
752         return 0;
753     }
754
755     rrdcalc_create_part2(host, rc);
756     return 1;
757 }
758
759 static inline int rrdcalctemplate_add(RRDHOST *host, RRDCALCTEMPLATE *rt) {
760     info("Health configuration examining template '%s': context '%s', exec '%s', green %Lf, red %Lf, calculation: group %d, after %d, before %d, options %u, update every %d, dimensions '%s', warning '%s', critical '%s', source '%s'",
761          rt->name,
762          (rt->context)?rt->context:"NONE",
763          (rt->exec)?rt->exec:"DEFAULT",
764          rt->green,
765          rt->red,
766          rt->group,
767          rt->after,
768          rt->before,
769          rt->options,
770          rt->update_every,
771          (rt->dimensions)?rt->dimensions:"NONE",
772          (rt->warning)?rt->warning->parsed_as:"NONE",
773          (rt->critical)?rt->critical->parsed_as:"NONE",
774          rt->source
775     );
776
777     if(!RRDCALCTEMPLATE_HAS_CALCULATION(rt) && !rt->warning && !rt->critical) {
778         error("Health configuration for template '%s' is useless (no calculation, no warning and no critical evaluation)", rt->name);
779         return 0;
780     }
781
782     RRDCALCTEMPLATE *t;
783     for (t = host->templates; t ; t = t->next) {
784         if(t->hash_name == rt->hash_name && !strcmp(t->name, rt->name)) {
785             error("Health configuration template '%s' already exists for host '%s'.", rt->name, host->hostname);
786             return 0;
787         }
788     }
789
790     rt->next = host->templates;
791     host->templates = rt;
792     return 1;
793 }
794
795 static inline int health_parse_time(char *string, int *result) {
796     // make sure it is a number
797     if(!*string || !(isdigit(*string) || *string == '+' || *string == '-')) {
798         *result = 0;
799         return 0;
800     }
801
802     char *e = NULL;
803     calculated_number n = strtold(string, &e);
804     if(e && *e) {
805         switch (*e) {
806             case 'Y':
807                 *result = (int) (n * 86400 * 365);
808                 break;
809             case 'M':
810                 *result = (int) (n * 86400 * 30);
811                 break;
812             case 'w':
813                 *result = (int) (n * 86400 * 7);
814                 break;
815             case 'd':
816                 *result = (int) (n * 86400);
817                 break;
818             case 'h':
819                 *result = (int) (n * 3600);
820                 break;
821             case 'm':
822                 *result = (int) (n * 60);
823                 break;
824
825             default:
826             case 's':
827                 *result = (int) (n);
828                 break;
829         }
830     }
831     else
832        *result = (int)(n);
833
834     return 1;
835 }
836
837 static inline int health_parse_chart_calc(
838         size_t line, const char *path, const char *file, char *string,
839         int *group_method, int *after, int *before, int *every,
840         uint32_t *options, char **dimensions
841 ) {
842     if(*dimensions) freez(*dimensions);
843     *dimensions = NULL;
844     *after = 0;
845     *before = 0;
846     *every = 0;
847     *options = 0;
848
849     char *s = string, *key;
850
851     // first is the group method
852     key = s;
853     while(*s && !isspace(*s)) s++;
854     while(*s && isspace(*s)) *s++ = '\0';
855     if(!*s) {
856         error("Health configuration invalid chart calculation at line %zu of file '%s/%s': expected group method followed by the 'after' time, but got '%s'",
857               line, path, file, key);
858         return 0;
859     }
860
861     if((*group_method = web_client_api_request_v1_data_group(key, -1)) == -1) {
862         error("Health configuration at line %zu of file '%s/%s': invalid group method '%s'",
863               line, path, file, key);
864         return 0;
865     }
866
867     // then is the 'after' time
868     key = s;
869     while(*s && !isspace(*s)) s++;
870     while(*s && isspace(*s)) *s++ = '\0';
871
872     if(!health_parse_time(key, after)) {
873         error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' after group method",
874               line, path, file, key);
875         return 0;
876     }
877
878     // sane defaults
879     *every = abs(*after);
880
881     // now we may have optional parameters
882     while(*s) {
883         key = s;
884         while(*s && !isspace(*s)) s++;
885         while(*s && isspace(*s)) *s++ = '\0';
886         if(!*key) break;
887
888         if(!strcasecmp(key, "at")) {
889             char *value = s;
890             while(*s && !isspace(*s)) s++;
891             while(*s && isspace(*s)) *s++ = '\0';
892
893             if (!health_parse_time(value, before)) {
894                 error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' for '%s' keyword",
895                       line, path, file, value, key);
896             }
897         }
898         else if(!strcasecmp(key, "every")) {
899             char *value = s;
900             while(*s && !isspace(*s)) s++;
901             while(*s && isspace(*s)) *s++ = '\0';
902
903             if (!health_parse_time(value, every)) {
904                 error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' for '%s' keyword",
905                       line, path, file, value, key);
906             }
907         }
908         else if(!strcasecmp(key, "absolute") || !strcasecmp(key, "abs") || !strcasecmp(key, "absolute_sum")) {
909             *options |= RRDR_OPTION_ABSOLUTE;
910         }
911         else if(!strcasecmp(key, "min2max")) {
912             *options |= RRDR_OPTION_MIN2MAX;
913         }
914         else if(!strcasecmp(key, "null2zero")) {
915             *options |= RRDR_OPTION_NULL2ZERO;
916         }
917         else if(!strcasecmp(key, "percentage")) {
918             *options |= RRDR_OPTION_PERCENTAGE;
919         }
920         else if(!strcasecmp(key, "unaligned")) {
921             *options |= RRDR_OPTION_NOT_ALIGNED;
922         }
923         else if(!strcasecmp(key, "of")) {
924             if(*s && strcasecmp(s, "all"))
925                *dimensions = strdupz(s);
926             break;
927         }
928     }
929
930     return 1;
931 }
932
933 static inline char *health_source_file(int line, const char *path, const char *filename) {
934     char buffer[FILENAME_MAX + 1];
935     snprintfz(buffer, FILENAME_MAX, "%d@%s/%s", line, path, filename);
936     return strdupz(buffer);
937 }
938
939 int health_readfile(const char *path, const char *filename) {
940     static uint32_t hash_alarm = 0, hash_template = 0, hash_on = 0, hash_calc = 0, hash_green = 0, hash_red = 0, hash_warn = 0, hash_crit = 0, hash_exec = 0;
941     char buffer[HEALTH_CONF_MAX_LINE + 1];
942
943     if(unlikely(!hash_alarm)) {
944         hash_alarm = simple_uhash(HEALTH_ALARM_KEY);
945         hash_template = simple_uhash(HEALTH_TEMPLATE_KEY);
946         hash_on = simple_uhash(HEALTH_ON_KEY);
947         hash_calc = simple_uhash(HEALTH_CALC_KEY);
948         hash_green = simple_uhash(HEALTH_GREEN_KEY);
949         hash_red = simple_uhash(HEALTH_RED_KEY);
950         hash_warn = simple_uhash(HEALTH_WARN_KEY);
951         hash_crit = simple_uhash(HEALTH_CRIT_KEY);
952         hash_exec = simple_uhash(HEALTH_EXEC_KEY);
953     }
954
955     // info("Reading file '%s/%s'", path, filename);
956
957     snprintfz(buffer, HEALTH_CONF_MAX_LINE, "%s/%s", path, filename);
958     FILE *fp = fopen(buffer, "r");
959     if(!fp) {
960         error("Health configuration cannot read file '%s'.", buffer);
961         return 0;
962     }
963
964     RRDCALC *rc = NULL;
965     RRDCALCTEMPLATE *rt = NULL;
966
967     size_t line = 0, append = 0;
968     char *s;
969     while((s = fgets(&buffer[append], (int)(HEALTH_CONF_MAX_LINE - append), fp)) || append) {
970         int stop_appending = !s;
971         line++;
972         // info("Line %zu of file '%s/%s': '%s'", line, path, filename, s);
973         s = trim(buffer);
974         if(!s) continue;
975         // info("Trimmed line %zu of file '%s/%s': '%s'", line, path, filename, s);
976
977         append = strlen(s);
978         if(!stop_appending && s[append - 1] == '\\') {
979             s[append - 1] = ' ';
980             append = &s[append] - buffer;
981             if(append < HEALTH_CONF_MAX_LINE)
982                 continue;
983             continue;
984         }
985         append = 0;
986
987         char *key = s;
988         while(*s && *s != ':') s++;
989         if(!*s) {
990             error("Health configuration has invalid line %zu of file '%s/%s'. It does not contain a ':'. Ignoring it.", line, path, filename);
991             continue;
992         }
993         *s = '\0';
994         s++;
995
996         char *value = s;
997         key = trim(key);
998         value = trim(value);
999
1000         if(!key) {
1001             error("Health configuration has invalid line %zu of file '%s/%s'. Keyword is empty. Ignoring it.", line, path, filename);
1002             continue;
1003         }
1004
1005         if(!value) {
1006             error("Health configuration has invalid line %zu of file '%s/%s'. value is empty. Ignoring it.", line, path, filename);
1007             continue;
1008         }
1009
1010         // info("Health file '%s/%s', key '%s', value '%s'", path, filename, key, value);
1011         uint32_t hash = simple_uhash(key);
1012
1013         if(hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) {
1014             if(rc && !rrdcalc_add(&localhost, rc))
1015                 rrdcalc_free(&localhost, rc);
1016
1017             if(rt) {
1018                 if (!rrdcalctemplate_add(&localhost, rt))
1019                     rrdcalctemplate_free(&localhost, rt);
1020                 rt = NULL;
1021             }
1022
1023             rc = callocz(1, sizeof(RRDCALC));
1024             rc->name = strdupz(value);
1025             rc->hash = simple_hash(rc->name);
1026             rc->source = health_source_file(line, path, filename);
1027         }
1028         else if(hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY)) {
1029             if(rc) {
1030                 if(!rrdcalc_add(&localhost, rc))
1031                     rrdcalc_free(&localhost, rc);
1032                 rc = NULL;
1033             }
1034
1035             if(rt && !rrdcalctemplate_add(&localhost, rt))
1036                 rrdcalctemplate_free(&localhost, rt);
1037
1038             rt = callocz(1, sizeof(RRDCALCTEMPLATE));
1039             rt->name = strdupz(value);
1040             rt->hash_name = simple_hash(rt->name);
1041             rt->source = health_source_file(line, path, filename);
1042         }
1043         else if(rc) {
1044             if(hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
1045                 if(rc->chart) {
1046                     if(strcmp(rc->chart, value))
1047                         info("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').",
1048                              line, path, filename, rc->name, key, rc->chart, value, value);
1049
1050                     freez(rc->chart);
1051                 }
1052                 rc->chart = strdupz(value);
1053                 rc->hash_chart = simple_hash(rc->chart);
1054             }
1055             else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) {
1056                 health_parse_chart_calc(line, path, filename, value, &rc->group, &rc->after, &rc->before, &rc->update_every, &rc->options, &rc->dimensions);
1057             }
1058             else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
1059                 char *e;
1060                 rc->green = strtold(value, &e);
1061                 if(e && *e) {
1062                     info("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' leaves this string unmatched: '%s'.",
1063                          line, path, filename, rc->name, key, e);
1064                 }
1065             }
1066             else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
1067                 char *e;
1068                 rc->red = strtold(value, &e);
1069                 if(e && *e) {
1070                     info("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' leaves this string unmatched: '%s'.",
1071                          line, path, filename, rc->name, key, e);
1072                 }
1073             }
1074             else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) {
1075                 const char *failed_at = NULL;
1076                 int error = 0;
1077                 rc->warning = expression_parse(value, &failed_at, &error);
1078                 if(!rc->warning) {
1079                     error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
1080                          line, path, filename, rc->name, key, value, expression_strerror(error), failed_at);
1081                 }
1082             }
1083             else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) {
1084                 const char *failed_at = NULL;
1085                 int error = 0;
1086                 rc->critical = expression_parse(value, &failed_at, &error);
1087                 if(!rc->critical) {
1088                     error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
1089                           line, path, filename, rc->name, key, value, expression_strerror(error), failed_at);
1090                 }
1091             }
1092             else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) {
1093                 if(rc->exec) {
1094                     if(strcmp(rc->exec, value))
1095                         info("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').",
1096                              line, path, filename, rc->name, key, rc->exec, value, value);
1097
1098                     freez(rc->exec);
1099                 }
1100                 rc->exec = strdupz(value);
1101             }
1102             else {
1103                 error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has unknown key '%s'.",
1104                      line, path, filename, rc->name, key);
1105             }
1106         }
1107         else if(rt) {
1108             if(hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
1109                 if(rt->context) {
1110                     if(strcmp(rt->context, value))
1111                         info("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').",
1112                              line, path, filename, rt->name, key, rt->context, value, value);
1113
1114                     freez(rt->context);
1115                 }
1116                 rt->context = strdupz(value);
1117                 rt->hash_context = simple_hash(rt->context);
1118             }
1119             else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) {
1120                 health_parse_chart_calc(line, path, filename, value, &rt->group, &rt->after, &rt->before, &rt->update_every, &rt->options, &rt->dimensions);
1121             }
1122             else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
1123                 char *e;
1124                 rt->green = strtold(value, &e);
1125                 if(e && *e) {
1126                     info("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' leaves this string unmatched: '%s'.",
1127                          line, path, filename, rt->name, key, e);
1128                 }
1129             }
1130             else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
1131                 char *e;
1132                 rt->red = strtold(value, &e);
1133                 if(e && *e) {
1134                     info("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' leaves this string unmatched: '%s'.",
1135                          line, path, filename, rt->name, key, e);
1136                 }
1137             }
1138             else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) {
1139                 const char *failed_at = NULL;
1140                 int error = 0;
1141                 rt->warning = expression_parse(value, &failed_at, &error);
1142                 if(!rt->warning) {
1143                     error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
1144                           line, path, filename, rt->name, key, value, expression_strerror(error), failed_at);
1145                 }
1146             }
1147             else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) {
1148                 const char *failed_at = NULL;
1149                 int error = 0;
1150                 rt->critical = expression_parse(value, &failed_at, &error);
1151                 if(!rt->critical) {
1152                     error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
1153                           line, path, filename, rt->name, key, value, expression_strerror(error), failed_at);
1154                 }
1155             }
1156             else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) {
1157                 if(rt->exec) {
1158                     if(strcmp(rt->exec, value))
1159                         info("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').",
1160                              line, path, filename, rt->name, key, rt->exec, value, value);
1161
1162                     freez(rt->exec);
1163                 }
1164                 rt->exec = strdupz(value);
1165             }
1166             else {
1167                 error("Health configuration at line %zu of file '%s/%s' for template '%s' has unknown key '%s'.",
1168                       line, path, filename, rt->name, key);
1169             }
1170         }
1171         else {
1172             error("Health configuration at line %zu of file '%s/%s' has unknown key '%s'. Expected either '" HEALTH_ALARM_KEY "' or '" HEALTH_TEMPLATE_KEY "'.",
1173                   line, path, filename, key);
1174         }
1175     }
1176
1177     if(rc && !rrdcalc_add(&localhost, rc))
1178         rrdcalc_free(&localhost, rc);
1179
1180     if(rt && !rrdcalctemplate_add(&localhost, rt))
1181         rrdcalctemplate_free(&localhost, rt);
1182
1183     fclose(fp);
1184     return 1;
1185 }
1186
1187 void health_readdir(const char *path) {
1188     size_t pathlen = strlen(path);
1189
1190     info("Reading directory '%s'", path);
1191
1192     DIR *dir = opendir(path);
1193     if (!dir) {
1194         error("Health configuration cannot open directory '%s'.", path);
1195         return;
1196     }
1197
1198     struct dirent *de = NULL;
1199     while ((de = readdir(dir))) {
1200         size_t len = strlen(de->d_name);
1201
1202         if(de->d_type == DT_DIR
1203            && (
1204                    (de->d_name[0] == '.' && de->d_name[1] == '\0')
1205                    || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
1206            ))
1207             continue;
1208
1209         else if(de->d_type == DT_DIR) {
1210             char *s = mallocz(pathlen + strlen(de->d_name) + 2);
1211             strcpy(s, path);
1212             strcat(s, "/");
1213             strcat(s, de->d_name);
1214             health_readdir(s);
1215             freez(s);
1216             continue;
1217         }
1218
1219         else if((de->d_type == DT_LNK || de->d_type == DT_REG) &&
1220                 len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
1221             health_readfile(path, de->d_name);
1222         }
1223     }
1224 }
1225
1226 void health_init(void) {
1227     char *path;
1228
1229     {
1230         char buffer[FILENAME_MAX + 1];
1231         snprintfz(buffer, FILENAME_MAX, "%s/health.d", config_get("global", "config directory", CONFIG_DIR));
1232         path = config_get("health", "configuration files in directory", buffer);
1233
1234         snprintfz(buffer, FILENAME_MAX, "%s/alarm.sh", config_get("global", "plugins directory", PLUGINS_DIR));
1235         health_default_exec = config_get("health", "script to execute on alarm", buffer);
1236     }
1237
1238     health_readdir(path);
1239 }