@cryptotaxi247 / netdata-1 / commits / dd73f3e0c

Repeating alarm notifications (#6309)

* Alarm_repeat mergin the original! * Alarm_repeat binary tree! * Alarm_repeat binary tree finished! * Alarm_repeat move function and format string * Alarms bringing a new Binary tree * Alarms fixing the last two * Alarm_repeat useless var! * Alarm fix format and repeat alarm! * Alarm_backend steps! * Alarm_repeat stopping to test cloud! * Alarm_repeat stopping to test cloud 2! * Alarm_repeat fixing when restart!

thiagoftsm committed Jul 1, 2019 at 11:55 UTC dd73f3e0cd0d9a1797f0c82de0fae5ccf97e9464
20 files changed +513 -105
daemon/common.h
+1
@@ -14,6 +14,7 @@
14 #define config_get_float(section, name, value) appconfig_get_float(&netdata_config, section, name, value)
15 #define config_get_boolean(section, name, value) appconfig_get_boolean(&netdata_config, section, name, value)
16 #define config_get_boolean_ondemand(section, name, value) appconfig_get_boolean_ondemand(&netdata_config, section, name, value)
17 +#define config_get_duration(section, name, value) appconfig_get_duration(&netdata_config, section, name, value)
18
19 #define config_set(section, name, default_value) appconfig_set(&netdata_config, section, name, default_value)
20 #define config_set_default(section, name, value) appconfig_set_default(&netdata_config, section, name, value)
daemon/main.c
+1 -1
@@ -1217,7 +1217,7 @@ int main(int argc, char **argv) {
1217 info("netdata initialization completed. Enjoy real-time performance monitoring!");
1218 netdata_ready = 1;
1219
1220 - send_statistics("START","-", "-");
1220 + send_statistics("START", "-", "-");
1221
1222 // ------------------------------------------------------------------------
1223 // unblock signals
database/rrd.c
+1 -1
@@ -132,7 +132,6 @@ const char *rrdset_type_name(RRDSET_TYPE chart_type) {
132 }
133 }
134
135 -
135 // ----------------------------------------------------------------------------
136 // RRD - cache directory
137
@@ -154,3 +153,4 @@ char *rrdset_cache_dir(RRDHOST *host, const char *id, const char *config_section
153
154 return ret;
155 }
156 +
database/rrd.h
+13
@@ -572,6 +572,8 @@ struct alarm_entry {
572 uint32_t updated_by_id;
573 uint32_t updates_id;
574
575 + time_t last_repeat;
576 +
577 struct alarm_entry *next;
578 };
579
@@ -686,11 +688,16 @@ struct rrdhost {
688 char *health_log_filename; // the alarms event log filename
689 size_t health_log_entries_written; // the number of alarm events writtern to the alarms event log
690 FILE *health_log_fp; // the FILE pointer to the open alarms event log file
691 + uint32_t health_default_warn_repeat_every; // the default value for the interval between repeating warning notifications
692 + uint32_t health_default_crit_repeat_every; // the default value for the interval between repeating critical notifications
693 +
694
695 // all RRDCALCs are primarily allocated and linked here
696 // RRDCALCs may be linked to charts at any point
697 // (charts may or may not exist when these are loaded)
698 RRDCALC *alarms;
699 + avl_tree_lock alarms_idx_health_log;
700 + avl_tree_lock alarms_idx_name;
701
702 ALARM_LOG health_log; // alarms historical events (event log)
703 uint32_t health_last_processed_id; // the last processed health id from the log
@@ -1021,6 +1028,12 @@ extern collected_number rrddim_set(RRDSET *st, const char *id, collected_number
1028
1029 extern long align_entries_to_pagesize(RRD_MEMORY_MODE mode, long entries);
1030
1031 +// ----------------------------------------------------------------------------
1032 +// Miscellaneous functions
1033 +
1034 +extern int alarm_compare_id(void *a, void *b);
1035 +extern int alarm_compare_name(void *a, void *b);
1036 +
1037 // ----------------------------------------------------------------------------
1038 // RRD internal functions
1039
database/rrdcalc.c
+91 -11
@@ -81,9 +81,9 @@ static void rrdsetcalc_link(RRDSET *st, RRDCALC *rc) {
81
82 if(!rc->units) rc->units = strdupz(st->units);
83
84 - {
84 + if(!rrdcalc_isrepeating(rc)) {
85 time_t now = now_realtime_sec();
86 - health_alarm_log(
86 + ALARM_ENTRY *ae = health_create_alarm_entry(
87 host,
88 rc->id,
89 rc->next_event_id++,
@@ -104,6 +104,7 @@ static void rrdsetcalc_link(RRDSET *st, RRDCALC *rc) {
104 0,
105 0
106 );
107 + health_alarm_log(host, ae);
108 }
109 }
110
@@ -142,9 +143,9 @@ inline void rrdsetcalc_unlink(RRDCALC *rc) {
143
144 RRDHOST *host = st->rrdhost;
145
145 - {
146 + if(!rrdcalc_isrepeating(rc)) {
147 time_t now = now_realtime_sec();
147 - health_alarm_log(
148 + ALARM_ENTRY *ae = health_create_alarm_entry(
149 host,
150 rc->id,
151 rc->next_event_id++,
@@ -165,6 +166,7 @@ inline void rrdsetcalc_unlink(RRDCALC *rc) {
166 0,
167 0
168 );
169 + health_alarm_log(host, ae);
170 }
171
172 debug(D_HEALTH, "Health unlinking alarm '%s.%s' from chart '%s' of host '%s'", rc->chart?rc->chart:"NOCHART", rc->name, st->id, host->hostname);
@@ -253,7 +255,7 @@ inline uint32_t rrdcalc_get_unique_id(RRDHOST *host, const char *chart, const ch
255 return host->health_log.next_alarm_id++;
256 }
257
256 -inline void rrdcalc_create_part2(RRDHOST *host, RRDCALC *rc) {
258 +inline void rrdcalc_add_to_host(RRDHOST *host, RRDCALC *rc) {
259 rrdhost_check_rdlock(host);
260
261 if(rc->calculation) {
@@ -301,8 +303,7 @@ inline void rrdcalc_create_part2(RRDHOST *host, RRDCALC *rc) {
303 }
304 }
305
304 -inline RRDCALC *rrdcalc_create(RRDHOST *host, RRDCALCTEMPLATE *rt, const char *chart) {
305 -
306 +inline RRDCALC *rrdcalc_create_from_template(RRDHOST *host, RRDCALCTEMPLATE *rt, const char *chart) {
307 debug(D_HEALTH, "Health creating dynamic alarm (from template) '%s.%s'", chart, rt->name);
308
309 if(rrdcalc_exists(host, chart, rt->name, 0, 0))
@@ -328,6 +329,10 @@ inline RRDCALC *rrdcalc_create(RRDHOST *host, RRDCALCTEMPLATE *rt, const char *c
329 rc->delay_max_duration = rt->delay_max_duration;
330 rc->delay_multiplier = rt->delay_multiplier;
331
332 + rc->last_repeat = 0;
333 + rc->warn_repeat_every = rt->warn_repeat_every;
334 + rc->crit_repeat_every = rt->crit_repeat_every;
335 +
336 rc->group = rt->group;
337 rc->after = rt->after;
338 rc->before = rt->before;
@@ -356,7 +361,7 @@ inline RRDCALC *rrdcalc_create(RRDHOST *host, RRDCALCTEMPLATE *rt, const char *c
361 error("Health alarm '%s.%s': failed to re-parse critical expression '%s'", chart, rt->name, rt->critical->source);
362 }
363
359 - debug(D_HEALTH, "Health runtime added alarm '%s.%s': exec '%s', recipient '%s', green " CALCULATED_NUMBER_FORMAT_AUTO ", red " CALCULATED_NUMBER_FORMAT_AUTO ", 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",
364 + debug(D_HEALTH, "Health runtime added alarm '%s.%s': exec '%s', recipient '%s', green " CALCULATED_NUMBER_FORMAT_AUTO ", red " CALCULATED_NUMBER_FORMAT_AUTO ", 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, warn_repeat_every %u, crit_repeat_every %u",
365 (rc->chart)?rc->chart:"NOCHART",
366 rc->name,
367 (rc->exec)?rc->exec:"DEFAULT",
@@ -376,16 +381,24 @@ inline RRDCALC *rrdcalc_create(RRDHOST *host, RRDCALCTEMPLATE *rt, const char *c
381 rc->delay_up_duration,
382 rc->delay_down_duration,
383 rc->delay_max_duration,
379 - rc->delay_multiplier
384 + rc->delay_multiplier,
385 + rc->warn_repeat_every,
386 + rc->crit_repeat_every
387 );
388
382 - rrdcalc_create_part2(host, rc);
389 + rrdcalc_add_to_host(host, rc);
390 + RRDCALC *rdcmp = (RRDCALC *) avl_insert_lock(&(host)->alarms_idx_health_log,(avl *)rc);
391 + if (rdcmp != rc) {
392 + error("Cannot insert the alarm index ID %s",rc->name);
393 + }
394 +
395 return rc;
396 }
397
398 void rrdcalc_free(RRDCALC *rc) {
399 if(unlikely(!rc)) return;
400
401 +
402 expression_free(rc->calculation);
403 expression_free(rc->warning);
404 expression_free(rc->critical);
@@ -413,7 +426,6 @@ void rrdcalc_unlink_and_free(RRDHOST *host, RRDCALC *rc) {
426 // unlink it from RRDHOST
427 if(unlikely(rc == host->alarms))
428 host->alarms = rc->next;
416 -
429 else {
430 RRDCALC *t;
431 for(t = host->alarms; t && t->next != rc; t = t->next) ;
@@ -425,5 +437,73 @@ void rrdcalc_unlink_and_free(RRDHOST *host, RRDCALC *rc) {
437 error("Cannot unlink alarm '%s.%s' from host '%s': not found", rc->chart?rc->chart:"NOCHART", rc->name, host->hostname);
438 }
439
440 + if (rc) {
441 + RRDCALC *rdcmp = (RRDCALC *) avl_remove_lock(&(host)->alarms_idx_health_log, (avl *)rc);
442 + if (!rdcmp) {
443 + error("Cannot remove the health alarm index");
444 + }
445 +
446 + rdcmp = (RRDCALC *) avl_remove_lock(&(host)->alarms_idx_name, (avl *)rc);
447 + if (!rdcmp) {
448 + error("Cannot remove the health alarm index");
449 + }
450 + }
451 +
452 rrdcalc_free(rc);
453 }
454 +
455 +// ----------------------------------------------------------------------------
456 +// Alarm
457 +
458 +
459 +/**
460 + * Alarm is repeating
461 + *
462 + * Is this alarm repeating ?
463 + *
464 + * @param host The structure that has the binary tree
465 + * @param alarm_id the id of the alarm to search
466 + *
467 + * @return It returns 1 case it is repeating and 0 otherwise
468 + */
469 +int alarm_isrepeating(RRDHOST *host, uint32_t alarm_id) {
470 + RRDCALC findme;
471 + findme.id = alarm_id;
472 + RRDCALC *rc = (RRDCALC *)avl_search_lock(&host->alarms_idx_health_log, (avl *)&findme);
473 + if (!rc) {
474 + return 0;
475 + }
476 + return rrdcalc_isrepeating(rc);
477 +}
478 +
479 +/**
480 + * Entry is repeating
481 + *
482 + * Check whether the id of alarm entry is yet present in the host structure
483 + *
484 + * @param host The structure that has the binary tree
485 + * @param ae the alarm entry
486 + *
487 + * @return It returns 1 case it is repeating and 0 otherwise
488 + */
489 +int alarm_entry_isrepeating(RRDHOST *host, ALARM_ENTRY *ae) {
490 + return alarm_isrepeating(host, ae->alarm_id);
491 +}
492 +
493 +/**
494 + * Max last repeat
495 + *
496 + * Check the maximum last_repeat for the alarms associated a host
497 + *
498 + * @param host The structure that has the binary tree
499 + *
500 + * @return It returns 1 case it is repeating and 0 otherwise
501 + */
502 +RRDCALC *alarm_max_last_repeat(RRDHOST *host, char *alarm_name,uint32_t hash) {
503 + RRDCALC findme;
504 + findme.name = alarm_name;
505 + findme.hash = hash;
506 + RRDCALC *rc = (RRDCALC *)avl_search_lock(&host->alarms_idx_name, (avl *)&findme);
507 +
508 + return rc;
509 +}
database/rrdcalc.h
+23 -2
@@ -29,7 +29,9 @@
29 #define RRDCALC_FLAG_SILENCED 0x00000100
30 #define RRDCALC_FLAG_NO_CLEAR_NOTIFICATION 0x80000000
31
32 +
33 struct rrdcalc {
34 + avl avl; // the index, with key the id - this has to be first!
35 uint32_t id; // the unique id of this alarm
36 uint32_t next_event_id; // the next event id that will be used for this alarm
37
@@ -77,9 +79,16 @@ struct rrdcalc {
79 float delay_multiplier; // multiplier for all delays when alarms switch status
80 // while now < delay_up_to
81
82 + // ------------------------------------------------------------------------
83 + // notification repeat settings
84 +
85 + uint32_t warn_repeat_every; // interval between repeating warning notifications
86 + uint32_t crit_repeat_every; // interval between repeating critical notifications
87 +
88 // ------------------------------------------------------------------------
89 // runtime information
90
91 + RRDCALC_STATUS old_status; // the old status of the alarm
92 RRDCALC_STATUS status; // the current status of the alarm
93
94 calculated_number value; // the current value of the alarm
@@ -90,6 +99,7 @@ struct rrdcalc {
99 time_t last_updated; // the last update timestamp of the alarm
100 time_t next_update; // the next update timestamp of the alarm
101 time_t last_status_change; // the timestamp of the last time this alarm changed status
102 + time_t last_repeat; // the last time the alarm got repeated
103
104 time_t db_after; // the first timestamp evaluated by the db lookup
105 time_t db_before; // the last timestamp evaluated by the db lookup
@@ -119,6 +129,10 @@ struct rrdcalc {
129 struct rrdcalc *next;
130 };
131
132 +extern int alarm_isrepeating(RRDHOST *host, uint32_t alarm_id);
133 +extern int alarm_entry_isrepeating(RRDHOST *host, ALARM_ENTRY *ae);
134 +extern RRDCALC *alarm_max_last_repeat(RRDHOST *host, char *alarm_name, uint32_t hash);
135 +
136 #define RRDCALC_HAS_DB_LOOKUP(rc) ((rc)->after)
137
138 extern void rrdsetcalc_link_matching(RRDSET *st);
@@ -132,7 +146,14 @@ extern void rrdcalc_unlink_and_free(RRDHOST *host, RRDCALC *rc);
146
147 extern int rrdcalc_exists(RRDHOST *host, const char *chart, const char *name, uint32_t hash_chart, uint32_t hash_name);
148 extern uint32_t rrdcalc_get_unique_id(RRDHOST *host, const char *chart, const char *name, uint32_t *next_event_id);
135 -extern RRDCALC *rrdcalc_create(RRDHOST *host, RRDCALCTEMPLATE *rt, const char *chart);
136 -extern void rrdcalc_create_part2(RRDHOST *host, RRDCALC *rc);
149 +extern RRDCALC *rrdcalc_create_from_template(RRDHOST *host, RRDCALCTEMPLATE *rt, const char *chart);
150 +extern void rrdcalc_add_to_host(RRDHOST *host, RRDCALC *rc);
151 +
152 +static inline int rrdcalc_isrepeating(RRDCALC *rc) {
153 + if (unlikely(rc->warn_repeat_every > 0 || rc->crit_repeat_every > 0)) {
154 + return 1;
155 + }
156 + return 0;
157 +}
158
159 #endif //NETDATA_RRDCALC_H
database/rrdcalctemplate.c
+1 -1
@@ -13,7 +13,7 @@ void rrdcalctemplate_link_matching(RRDSET *st) {
13 for(rt = host->templates; rt ; rt = rt->next) {
14 if(rt->hash_context == st->hash_context && !strcmp(rt->context, st->context)
15 && (!rt->family_pattern || simple_pattern_matches(rt->family_pattern, st->family))) {
16 - RRDCALC *rc = rrdcalc_create(host, rt, st->id);
16 + RRDCALC *rc = rrdcalc_create_from_template(host, rt, st->id);
17 if(unlikely(!rc))
18 info("Health tried to create alarm from template '%s' on chart '%s' of host '%s', but it failed", rt->name, st->id, host->hostname);
19
database/rrdcalctemplate.h
+6
@@ -48,6 +48,12 @@ struct rrdcalctemplate {
48 int delay_max_duration; // the absolute max delay to apply to this alarm
49 float delay_multiplier; // multiplier for all delays when alarms switch status
50
51 + // ------------------------------------------------------------------------
52 + // notification repeat settings
53 +
54 + uint32_t warn_repeat_every; // interval between repeating warning notifications
55 + uint32_t crit_repeat_every; // interval between repeating critical notifications
56 +
57 // ------------------------------------------------------------------------
58 // expressions related to the alarm
59
database/rrdhost.c
+47 -3
@@ -179,6 +179,10 @@ RRDHOST *rrdhost_create(const char *hostname,
179 if(config_get_boolean(CONFIG_SECTION_GLOBAL, "delete orphan hosts files", 1) && !is_localhost)
180 rrdhost_flag_set(host, RRDHOST_FLAG_DELETE_ORPHAN_HOST);
181
182 + host->health_default_warn_repeat_every = config_get_duration(CONFIG_SECTION_HEALTH, "default repeat warning", "never");
183 + host->health_default_crit_repeat_every = config_get_duration(CONFIG_SECTION_HEALTH, "default repeat critical", "never");
184 + avl_init_lock(&(host->alarms_idx_health_log), alarm_compare_id);
185 + avl_init_lock(&(host->alarms_idx_name), alarm_compare_name);
186
187 // ------------------------------------------------------------------------
188 // initialize health variables
@@ -274,12 +278,12 @@ RRDHOST *rrdhost_create(const char *hostname,
278 // load health configuration
279
280 if(host->health_enabled) {
277 - health_alarm_log_load(host);
278 - health_alarm_log_open(host);
279 -
281 rrdhost_wrlock(host);
282 health_readdir(host, health_user_config_dir(), health_stock_config_dir(), NULL);
283 rrdhost_unlock(host);
284 +
285 + health_alarm_log_load(host);
286 + health_alarm_log_open(host);
287 }
288
289
@@ -876,3 +880,43 @@ int rrdhost_set_system_info_variable(struct rrdhost_system_info *system_info, ch
880
881 return res;
882 }
883 +
884 +/**
885 + * Alarm Compare ID
886 + *
887 + * Callback function used with the binary trees to compare the id of RRDCALC
888 + *
889 + * @param a a pointer to the RRDCAL item to insert,compare or update the binary tree
890 + * @param b the pointer to the binary tree.
891 + *
892 + * @return It returns 0 case the values are equal, 1 case a is bigger than b and -1 case a is smaller than b.
893 + */
894 +int alarm_compare_id(void *a, void *b) {
895 + register uint32_t hash1 = ((RRDCALC *)a)->id;
896 + register uint32_t hash2 = ((RRDCALC *)b)->id;
897 +
898 + if(hash1 < hash2) return -1;
899 + else if(hash1 > hash2) return 1;
900 +
901 + return 0;
902 +}
903 +
904 +/**
905 + * Alarm Compare NAME
906 + *
907 + * Callback function used with the binary trees to compare the name of RRDCALC
908 + *
909 + * @param a a pointer to the RRDCAL item to insert,compare or update the binary tree
910 + * @param b the pointer to the binary tree.
911 + *
912 + * @return It returns 0 case the values are equal, 1 case a is bigger than b and -1 case a is smaller than b.
913 + */
914 +int alarm_compare_name(void *a, void *b) {
915 + RRDCALC *in1 = (RRDCALC *)a;
916 + RRDCALC *in2 = (RRDCALC *)b;
917 +
918 + if(in1->hash < in2->hash) return -1;
919 + else if(in1->hash > in2->hash) return 1;
920 +
921 + return strcmp(in1->name,in2->name);
922 +}
health/README.md
+21 -1
@@ -11,7 +11,6 @@ packet dropped).
11
12 Netdata also supports alarm **templates**, so that an alarm can be attached to all the charts of the same context (i.e. all network interfaces, or all disks, or all mysql servers, etc.).
13
14 -
14 Each alarm can execute a single query to the database using statistical algorithms against past data,
15 but alarms can be combined. So, if you need 2 queries in the database, you can combine
16 2 alarms together (both will run a query to the database, and the results can be combined).
@@ -342,6 +341,24 @@ delay: [[[up U] [down D] multiplier M] max X]
341 their matching one) and a delay is in place.
342 - All are reset to their defaults when the alarm switches state without a delay in place.
343
344 +---
345 +
346 +#### Alarm line `repeat`
347 +
348 +Defines the interval between repeating notifications for the alarms in CRITICAL or WARNING mode. This will override the default interval settings inherited from health settings in `netdata.conf`. The default settings for repeating notifications are `default repeat warning = DURATION` and `default repeat critical = DURATION` which can be found in health stock configuration.
349 +
350 +Format:
351 +
352 +```
353 +repeat: [off] [warning DURATION] [critical DURATION]
354 +```
355 +
356 +* `off`: Turns off the repeating feature for the current alarm. This is effective when the default repeat settings has been enabled in health configuration.
357 +* `warning DURATION`: Defines the interval when the alarm is in WARNING state. Use `0s` to turn off the repeating notification for WARNING mode.
358 +* `critical DURATION`: Defines the interval when the alarm is in CRITICAL state. Use `0s` to turn off the repeating notification for CRITICAL mode.
359 +
360 +---
361 +
362 #### Alarm line `option`
363
364 The only possible value for the `option` line is
@@ -567,12 +584,15 @@ template: disk_full_percent
584 every: 1m
585 warn: $this > 80
586 crit: $this > 95
587 + repeat: warning 120s critical 10s
588 ```
589
590 `$used` and `$avail` are the `used` and `avail` chart dimensions as shown on the dashboard.
591
592 So, the `calc` line finds the percentage of used space. `$this` resolves to this percentage.
593
594 +This is a repeating alarm and if the alarm becomes CRITICAL it repeats the notifications every 10 seconds. It also repeats notifications every 2 minutes if the alarm goes into WARNING mode.
595 +
596 ### Example 3
597
598 Predict if any disk will run out of space in the near future.
health/health.c
+60 -24
@@ -255,17 +255,18 @@ static inline void health_alarm_log_process(RRDHOST *host) {
255 netdata_rwlock_rdlock(&host->health_log.alarm_log_rwlock);
256
257 ALARM_ENTRY *ae;
258 - for(ae = host->health_log.alarms; ae && ae->unique_id >= host->health_last_processed_id ; ae = ae->next) {
259 - if(unlikely(
260 - !(ae->flags & HEALTH_ENTRY_FLAG_PROCESSED) &&
261 - !(ae->flags & HEALTH_ENTRY_FLAG_UPDATED)
258 + for(ae = host->health_log.alarms; ae && ae->unique_id >= host->health_last_processed_id; ae = ae->next) {
259 + if(likely(!alarm_entry_isrepeating(host, ae))) {
260 + if(unlikely(
261 + !(ae->flags & HEALTH_ENTRY_FLAG_PROCESSED) &&
262 + !(ae->flags & HEALTH_ENTRY_FLAG_UPDATED)
263 )) {
264 + if(unlikely(ae->unique_id < first_waiting))
265 + first_waiting = ae->unique_id;
266
264 - if(unlikely(ae->unique_id < first_waiting))
265 - first_waiting = ae->unique_id;
266 -
267 - if(likely(now >= ae->delay_up_to_timestamp))
268 - health_process_notifications(host, ae);
267 + if(likely(now >= ae->delay_up_to_timestamp))
268 + health_process_notifications(host, ae);
269 + }
270 }
271 }
272
@@ -295,6 +296,10 @@ static inline void health_alarm_log_process(RRDHOST *host) {
296 ALARM_ENTRY *t = ae->next;
297
298 health_alarm_log_free_one_nochecks_nounlink(ae);
299 + if(likely(!alarm_entry_isrepeating(host, ae))) {
300 + health_alarm_log_free_one_nochecks_nounlink(ae);
301 + host->health_log.count--;
302 + }
303
304 ae = t;
305 host->health_log.count--;
@@ -411,7 +416,7 @@ SILENCE_TYPE check_silenced(RRDCALC *rc, char* host, SILENCERS *silencers) {
416 debug(D_HEALTH, "Alarm %s matched a silence entry, but no SILENCE or DISABLE command was issued via the command API. The match has no effect.", rc->name);
417 } else {
418 debug(D_HEALTH, "Alarm %s via the command API - name:%s context:%s chart:%s host:%s family:%s"
414 - , (silencers->stype==STYPE_DISABLE_ALARMS)?"Disabled":"Silenced"
419 + , (silencers->stype == STYPE_DISABLE_ALARMS)?"Disabled":"Silenced"
420 , rc->name
421 , (rc->rrdset)?rc->rrdset->context:""
422 , rc->chart
@@ -756,20 +761,22 @@ void *health_main(void *ptr) {
761 rc->delay_last = delay;
762 rc->delay_up_to_timestamp = now + delay;
763
759 - health_alarm_log(
760 - host, rc->id, rc->next_event_id++, now, rc->name, rc->rrdset->id,
761 - rc->rrdset->family, rc->exec, rc->recipient, now - rc->last_status_change,
762 - rc->old_value, rc->value, rc->status, status, rc->source, rc->units, rc->info,
763 - rc->delay_last,
764 - (
765 - ((rc->options & RRDCALC_FLAG_NO_CLEAR_NOTIFICATION)? HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION : 0) |
766 - ((rc->rrdcalc_flags & RRDCALC_FLAG_SILENCED)? HEALTH_ENTRY_FLAG_SILENCED : 0)
767 - )
768 -
769 - );
770 -
771 - rc->last_status_change = now;
772 - rc->status = status;
764 + if(likely(!rrdcalc_isrepeating(rc))) {
765 + ALARM_ENTRY *ae = health_create_alarm_entry(
766 + host, rc->id, rc->next_event_id++, now, rc->name, rc->rrdset->id,
767 + rc->rrdset->family, rc->exec, rc->recipient, now - rc->last_status_change,
768 + rc->old_value, rc->value, rc->status, status, rc->source, rc->units, rc->info,
769 + rc->delay_last,
770 + (
771 + ((rc->options & RRDCALC_FLAG_NO_CLEAR_NOTIFICATION)? HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION : 0) |
772 + ((rc->rrdcalc_flags & RRDCALC_FLAG_SILENCED)? HEALTH_ENTRY_FLAG_SILENCED : 0)
773 + )
774 + );
775 + health_alarm_log(host, ae);
776 + }
777 + rc->last_status_change = now;
778 + rc->old_status = rc->status;
779 + rc->status = status;
780 }
781
782 rc->last_updated = now;
@@ -779,6 +786,35 @@ void *health_main(void *ptr) {
786 next_run = rc->next_update;
787 }
788
789 + // process repeating alarms
790 + RRDCALC *rc;
791 + for(rc = host->alarms; rc ; rc = rc->next) {
792 + int repeat_every = 0;
793 + if(unlikely(rrdcalc_isrepeating(rc))) {
794 + if(unlikely(rc->status == RRDCALC_STATUS_WARNING))
795 + repeat_every = rc->warn_repeat_every;
796 + else if(unlikely(rc->status == RRDCALC_STATUS_CRITICAL))
797 + repeat_every = rc->crit_repeat_every;
798 + }
799 + if(unlikely(repeat_every > 0 && (rc->last_repeat + repeat_every) <= now)) {
800 + rc->last_repeat = now;
801 + ALARM_ENTRY *ae = health_create_alarm_entry(
802 + host, rc->id, rc->next_event_id++, now, rc->name, rc->rrdset->id,
803 + rc->rrdset->family, rc->exec, rc->recipient, now - rc->last_status_change,
804 + rc->old_value, rc->value, rc->old_status, rc->status, rc->source, rc->units, rc->info,
805 + rc->delay_last,
806 + (
807 + ((rc->options & RRDCALC_FLAG_NO_CLEAR_NOTIFICATION)? HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION : 0) |
808 + ((rc->rrdcalc_flags & RRDCALC_FLAG_SILENCED)? HEALTH_ENTRY_FLAG_SILENCED : 0)
809 + )
810 + );
811 + ae->last_repeat = rc->last_repeat;
812 + health_process_notifications(host, ae);
813 + debug(D_HEALTH, "Notification sent for the repeating alarm %u.", ae->alarm_id);
814 + health_alarm_log_free_one_nochecks_nounlink(ae);
815 + }
816 + }
817 +
818 rrdhost_unlock(host);
819 }
820
health/health.h
+3 -1
@@ -108,7 +108,7 @@ extern void health_alarm_log_save(RRDHOST *host, ALARM_ENTRY *ae);
108 extern ssize_t health_alarm_log_read(RRDHOST *host, FILE *fp, const char *filename);
109 extern void health_alarm_log_load(RRDHOST *host);
110
111 -extern void health_alarm_log(
111 +extern ALARM_ENTRY* health_create_alarm_entry(
112 RRDHOST *host,
113 uint32_t alarm_id,
114 uint32_t alarm_event_id,
@@ -129,6 +129,8 @@ extern void health_alarm_log(
129 int delay,
130 uint32_t flags);
131
132 +extern void health_alarm_log(RRDHOST *host, ALARM_ENTRY *ae);
133 +
134 extern void health_readdir(RRDHOST *host, const char *user_path, const char *stock_path, const char *subpath);
135 extern char *health_user_config_dir(void);
136 extern char *health_stock_config_dir(void);
health/health_config.c
+80 -56
@@ -23,6 +23,7 @@
23 #define HEALTH_INFO_KEY "info"
24 #define HEALTH_DELAY_KEY "delay"
25 #define HEALTH_OPTIONS_KEY "options"
26 +#define HEALTH_REPEAT_KEY "repeat"
27
28 static inline int rrdcalc_add_alarm_from_config(RRDHOST *host, RRDCALC *rc) {
29 if(!rc->chart) {
@@ -45,7 +46,7 @@ static inline int rrdcalc_add_alarm_from_config(RRDHOST *host, RRDCALC *rc) {
46
47 rc->id = rrdcalc_get_unique_id(host, rc->chart, rc->name, &rc->next_event_id);
48
48 - debug(D_HEALTH, "Health configuration adding alarm '%s.%s' (%u): exec '%s', recipient '%s', green " CALCULATED_NUMBER_FORMAT_AUTO ", red " CALCULATED_NUMBER_FORMAT_AUTO ", 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",
49 + debug(D_HEALTH, "Health configuration adding alarm '%s.%s' (%u): exec '%s', recipient '%s', green " CALCULATED_NUMBER_FORMAT_AUTO ", red " CALCULATED_NUMBER_FORMAT_AUTO ", 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, warn_repeat_every %u, crit_repeat_every %u",
50 rc->chart?rc->chart:"NOCHART",
51 rc->name,
52 rc->id,
@@ -66,10 +67,12 @@ static inline int rrdcalc_add_alarm_from_config(RRDHOST *host, RRDCALC *rc) {
67 rc->delay_up_duration,
68 rc->delay_down_duration,
69 rc->delay_max_duration,
69 - rc->delay_multiplier
70 + rc->delay_multiplier,
71 + rc->warn_repeat_every,
72 + rc->crit_repeat_every
73 );
74
72 - rrdcalc_create_part2(host, rc);
75 + rrdcalc_add_to_host(host, rc);
76 return 1;
77 }
78
@@ -100,7 +103,7 @@ static inline int rrdcalctemplate_add_template_from_config(RRDHOST *host, RRDCAL
103 }
104 }
105
103 - debug(D_HEALTH, "Health configuration adding template '%s': context '%s', exec '%s', recipient '%s', green " CALCULATED_NUMBER_FORMAT_AUTO ", red " CALCULATED_NUMBER_FORMAT_AUTO ", 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",
106 + debug(D_HEALTH, "Health configuration adding template '%s': context '%s', exec '%s', recipient '%s', green " CALCULATED_NUMBER_FORMAT_AUTO ", red " CALCULATED_NUMBER_FORMAT_AUTO ", 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, warn_repeat_every %u, crit_repeat_every %u",
107 rt->name,
108 (rt->context)?rt->context:"NONE",
109 (rt->exec)?rt->exec:"DEFAULT",
@@ -120,7 +123,9 @@ static inline int rrdcalctemplate_add_template_from_config(RRDHOST *host, RRDCAL
123 rt->delay_up_duration,
124 rt->delay_down_duration,
125 rt->delay_max_duration,
123 - rt->delay_multiplier
126 + rt->delay_multiplier,
127 + rt->warn_repeat_every,
128 + rt->crit_repeat_every
129 );
130
131 if(likely(last)) {
@@ -134,48 +139,6 @@ static inline int rrdcalctemplate_add_template_from_config(RRDHOST *host, RRDCAL
139 return 1;
140 }
141
137 -static inline int health_parse_duration(char *string, int *result) {
138 - // make sure it is a number
139 - if(!*string || !(isdigit(*string) || *string == '+' || *string == '-')) {
140 - *result = 0;
141 - return 0;
142 - }
143 -
144 - char *e = NULL;
145 - calculated_number n = str2ld(string, &e);
146 - if(e && *e) {
147 - switch (*e) {
148 - case 'Y':
149 - *result = (int) (n * 86400 * 365);
150 - break;
151 - case 'M':
152 - *result = (int) (n * 86400 * 30);
153 - break;
154 - case 'w':
155 - *result = (int) (n * 86400 * 7);
156 - break;
157 - case 'd':
158 - *result = (int) (n * 86400);
159 - break;
160 - case 'h':
161 - *result = (int) (n * 3600);
162 - break;
163 - case 'm':
164 - *result = (int) (n * 60);
165 - break;
166 -
167 - default:
168 - case 's':
169 - *result = (int) (n);
170 - break;
171 - }
172 - }
173 - else
174 - *result = (int)(n);
175 -
176 - return 1;
177 -}
178 -
142 static inline int health_parse_delay(
143 size_t line, const char *filename, char *string,
144 int *delay_up_duration,
@@ -202,14 +165,14 @@ static inline int health_parse_delay(
165 while(*s && isspace(*s)) *s++ = '\0';
166
167 if(!strcasecmp(key, "up")) {
205 - if (!health_parse_duration(value, delay_up_duration)) {
168 + if (!config_parse_duration(value, delay_up_duration)) {
169 error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
170 line, filename, value, key);
171 }
172 else given_up = 1;
173 }
174 else if(!strcasecmp(key, "down")) {
212 - if (!health_parse_duration(value, delay_down_duration)) {
175 + if (!config_parse_duration(value, delay_down_duration)) {
176 error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
177 line, filename, value, key);
178 }
@@ -224,7 +187,7 @@ static inline int health_parse_delay(
187 else given_multiplier = 1;
188 }
189 else if(!strcasecmp(key, "max")) {
227 - if (!health_parse_duration(value, delay_max_duration)) {
190 + if (!config_parse_duration(value, delay_max_duration)) {
191 error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
192 line, filename, value, key);
193 }
@@ -285,6 +248,50 @@ static inline uint32_t health_parse_options(const char *s) {
248 return options;
249 }
250
251 +static inline int health_parse_repeat(
252 + size_t line,
253 + const char *file,
254 + char *string,
255 + uint32_t *warn_repeat_every,
256 + uint32_t *crit_repeat_every
257 +) {
258 +
259 + char *s = string;
260 + while(*s) {
261 + char *key = s;
262 +
263 + while(*s && !isspace(*s)) s++;
264 + while(*s && isspace(*s)) *s++ = '\0';
265 +
266 + if(!*key) break;
267 +
268 + char *value = s;
269 + while(*s && !isspace(*s)) s++;
270 + while(*s && isspace(*s)) *s++ = '\0';
271 +
272 + if(!strcasecmp(key, "off")) {
273 + *warn_repeat_every = 0;
274 + *crit_repeat_every = 0;
275 + return 1;
276 + }
277 + if(!strcasecmp(key, "warning")) {
278 + if (!config_parse_duration(value, (int*)warn_repeat_every)) {
279 + error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
280 + line, file, value, key);
281 + }
282 + }
283 + else if(!strcasecmp(key, "critical")) {
284 + if (!config_parse_duration(value, (int*)crit_repeat_every)) {
285 + error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
286 + line, file, value, key);
287 + }
288 + }
289 + }
290 +
291 + return 1;
292 +}
293 +
294 +
295 static inline int health_parse_db_lookup(
296 size_t line, const char *filename, char *string,
297 RRDR_GROUPING *group_method, int *after, int *before, int *every,
@@ -322,7 +329,7 @@ static inline int health_parse_db_lookup(
329 while(*s && !isspace(*s)) s++;
330 while(*s && isspace(*s)) *s++ = '\0';
331
325 - if(!health_parse_duration(key, after)) {
332 + if(!config_parse_duration(key, after)) {
333 error("Health configuration at line %zu of file '%s': invalid duration '%s' after group method",
334 line, filename, key);
335 return 0;
@@ -343,7 +350,7 @@ static inline int health_parse_db_lookup(
350 while(*s && !isspace(*s)) s++;
351 while(*s && isspace(*s)) *s++ = '\0';
352
346 - if (!health_parse_duration(value, before)) {
353 + if (!config_parse_duration(value, before)) {
354 error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
355 line, filename, value, key);
356 }
@@ -353,7 +360,7 @@ static inline int health_parse_db_lookup(
360 while(*s && !isspace(*s)) s++;
361 while(*s && isspace(*s)) *s++ = '\0';
362
356 - if (!health_parse_duration(value, every)) {
363 + if (!config_parse_duration(value, every)) {
364 error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
365 line, filename, value, key);
366 }
@@ -430,7 +437,8 @@ static int health_readfile(const char *filename, void *data) {
437 hash_info = 0,
438 hash_recipient = 0,
439 hash_delay = 0,
433 - hash_options = 0;
440 + hash_options = 0,
441 + hash_repeat = 0;
442
443 char buffer[HEALTH_CONF_MAX_LINE + 1];
444
@@ -454,6 +462,7 @@ static int health_readfile(const char *filename, void *data) {
462 hash_recipient = simple_hash(HEALTH_RECIPIENT_KEY);
463 hash_delay = simple_uhash(HEALTH_DELAY_KEY);
464 hash_options = simple_uhash(HEALTH_OPTIONS_KEY);
465 + hash_repeat = simple_uhash(HEALTH_REPEAT_KEY);
466 }
467
468 FILE *fp = fopen(filename, "r");
@@ -532,6 +541,9 @@ static int health_readfile(const char *filename, void *data) {
541 rc->value = NAN;
542 rc->old_value = NAN;
543 rc->delay_multiplier = 1.0;
544 + rc->old_status = RRDCALC_STATUS_UNINITIALIZED;
545 + rc->warn_repeat_every = host->health_default_warn_repeat_every;
546 + rc->crit_repeat_every = host->health_default_crit_repeat_every;
547
548 if(rrdvar_fix_name(rc->name))
549 error("Health configuration renamed alarm '%s' to '%s'", value, rc->name);
@@ -556,6 +568,8 @@ static int health_readfile(const char *filename, void *data) {
568 rt->green = NAN;
569 rt->red = NAN;
570 rt->delay_multiplier = 1.0;
571 + rt->warn_repeat_every = host->health_default_warn_repeat_every;
572 + rt->crit_repeat_every = host->health_default_crit_repeat_every;
573
574 if(rrdvar_fix_name(rt->name))
575 error("Health configuration renamed template '%s' to '%s'", value, rt->name);
@@ -612,7 +626,7 @@ static int health_readfile(const char *filename, void *data) {
626 &rc->options, &rc->dimensions);
627 }
628 else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
615 - if(!health_parse_duration(value, &rc->update_every))
629 + if(!config_parse_duration(value, &rc->update_every))
630 error("Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' cannot parse duration: '%s'.",
631 line, filename, rc->name, key, value);
632 }
@@ -707,6 +721,11 @@ static int health_readfile(const char *filename, void *data) {
721 else if(hash == hash_options && !strcasecmp(key, HEALTH_OPTIONS_KEY)) {
722 rc->options |= health_parse_options(value);
723 }
724 + else if(hash == hash_repeat && !strcasecmp(key, HEALTH_REPEAT_KEY)){
725 + health_parse_repeat(line, filename, value,
726 + &rc->warn_repeat_every,
727 + &rc->crit_repeat_every);
728 + }
729 else {
730 error("Health configuration at line %zu of file '%s' for alarm '%s' has unknown key '%s'.",
731 line, filename, rc->name, key);
@@ -736,7 +755,7 @@ static int health_readfile(const char *filename, void *data) {
755 &rt->update_every, &rt->options, &rt->dimensions);
756 }
757 else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
739 - if(!health_parse_duration(value, &rt->update_every))
758 + if(!config_parse_duration(value, &rt->update_every))
759 error("Health configuration at line %zu of file '%s' for template '%s' at key '%s' cannot parse duration: '%s'.",
760 line, filename, rt->name, key, value);
761 }
@@ -831,6 +850,11 @@ static int health_readfile(const char *filename, void *data) {
850 else if(hash == hash_options && !strcasecmp(key, HEALTH_OPTIONS_KEY)) {
851 rt->options |= health_parse_options(value);
852 }
853 + else if(hash == hash_repeat && !strcasecmp(key, HEALTH_REPEAT_KEY)){
854 + health_parse_repeat(line, filename, value,
855 + &rt->warn_repeat_every,
856 + &rt->crit_repeat_every);
857 + }
858 else {
859 error("Health configuration at line %zu of file '%s' for template '%s' has unknown key '%s'.",
860 line, filename, rt->name, key);
health/health_json.c
+4
@@ -140,6 +140,8 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
140 "\t\t\t\"delay_multiplier\": %f,\n"
141 "\t\t\t\"delay\": %d,\n"
142 "\t\t\t\"delay_up_to_timestamp\": %lu,\n"
143 + "\t\t\t\"warn_repeat_every\": \"%u\",\n"
144 + "\t\t\t\"crit_repeat_every\": \"%u\",\n"
145 "\t\t\t\"value_string\": \"%s\",\n"
146 , rc->chart, rc->name
147 , (unsigned long)rc->id
@@ -165,6 +167,8 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
167 , rc->delay_multiplier
168 , rc->delay_last
169 , (unsigned long)rc->delay_up_to_timestamp
170 + , rc->warn_repeat_every
171 + , rc->crit_repeat_every
172 , value_string
173 );
174
health/health_log.c
+53 -4
@@ -79,6 +79,7 @@ inline void health_alarm_log_save(RRDHOST *host, ALARM_ENTRY *ae) {
79 "\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s"
80 "\t%d\t%d\t%d\t%d"
81 "\t" CALCULATED_NUMBER_FORMAT_AUTO "\t" CALCULATED_NUMBER_FORMAT_AUTO
82 + "\t%016lx"
83 "\n"
84 , (ae->flags & HEALTH_ENTRY_FLAG_SAVED)?'U':'A'
85 , host->hostname
@@ -112,6 +113,7 @@ inline void health_alarm_log_save(RRDHOST *host, ALARM_ENTRY *ae) {
113
114 , ae->new_value
115 , ae->old_value
116 + , (uint64_t)ae->last_repeat
117 ) < 0))
118 error("HEALTH [%s]: failed to save alarm log entry to '%s'. Health data may be lost in case of abnormal restart.", host->hostname, host->health_log_filename);
119 else {
@@ -174,10 +176,40 @@ inline ssize_t health_alarm_log_read(RRDHOST *host, FILE *fp, const char *filena
176 continue;
177 }
178
179 + // Check if we got last_repeat field
180 + time_t last_repeat = 0;
181 + if(entries > 27) {
182 + char* alarm_name = pointers[13];
183 + last_repeat = (time_t)strtoul(pointers[27], NULL, 16);
184 +
185 + RRDCALC *rc = alarm_max_last_repeat(host, alarm_name,simple_hash(alarm_name));
186 + if (!rc) {
187 + for(rc = host->alarms; rc ; rc = rc->next) {
188 + RRDCALC *rdcmp = (RRDCALC *) avl_insert_lock(&(host)->alarms_idx_name, (avl *)rc);
189 + if(rdcmp != rc) {
190 + error("Cannot insert the alarm index ID using log %s", rc->name);
191 + }
192 + }
193 +
194 + rc = alarm_max_last_repeat(host, alarm_name,simple_hash(alarm_name));
195 + }
196 +
197 + if(unlikely(rc)) {
198 + if (rrdcalc_isrepeating(rc)) {
199 + rc->last_repeat = last_repeat;
200 + // We iterate through repeating alarm entries only to
201 + // find the latest last_repeat timestamp. Otherwise,
202 + // there is no need to keep them in memory.
203 + continue;
204 + }
205 + }
206 + }
207 +
208 if(unlikely(*pointers[0] == 'A')) {
209 // make sure it is properly numbered
210 if(unlikely(host->health_log.alarms && unique_id < host->health_log.alarms->unique_id)) {
180 - error("HEALTH [%s]: line %zu of file '%s' has alarm log entry %u in wrong order. Ignoring it.", host->hostname, line, filename, unique_id);
211 + error( "HEALTH [%s]: line %zu of file '%s' has alarm log entry %u in wrong order. Ignoring it."
212 + , host->hostname, line, filename, unique_id);
213 errored++;
214 continue;
215 }
@@ -186,11 +218,11 @@ inline ssize_t health_alarm_log_read(RRDHOST *host, FILE *fp, const char *filena
218 }
219 else if(unlikely(*pointers[0] == 'U')) {
220 // find the original
189 - for(ae = host->health_log.alarms; ae; ae = ae->next) {
221 + for(ae = host->health_log.alarms; ae ; ae = ae->next) {
222 if(unlikely(unique_id == ae->unique_id)) {
223 if(unlikely(*pointers[0] == 'A')) {
224 error("HEALTH [%s]: line %zu of file '%s' adds duplicate alarm log entry %u. Using the later."
193 - , host->hostname, line, filename, unique_id);
225 + , host->hostname, line, filename, unique_id);
226 *pointers[0] = 'U';
227 duplicate++;
228 }
@@ -270,6 +302,8 @@ inline ssize_t health_alarm_log_read(RRDHOST *host, FILE *fp, const char *filena
302 ae->new_value = str2l(pointers[25]);
303 ae->old_value = str2l(pointers[26]);
304
305 + ae->last_repeat = last_repeat;
306 +
307 char value_string[100 + 1];
308 freez(ae->old_value_string);
309 freez(ae->new_value_string);
@@ -339,7 +373,7 @@ inline void health_alarm_log_load(RRDHOST *host) {
373 // ----------------------------------------------------------------------------
374 // health alarm log management
375
342 -inline void health_alarm_log(
376 +inline ALARM_ENTRY* health_create_alarm_entry(
377 RRDHOST *host,
378 uint32_t alarm_id,
379 uint32_t alarm_event_id,
@@ -398,9 +432,24 @@ inline void health_alarm_log(
432 ae->delay_up_to_timestamp = when + delay;
433 ae->flags |= flags;
434
435 + ae->last_repeat = 0;
436 +
437 if(ae->old_status == RRDCALC_STATUS_WARNING || ae->old_status == RRDCALC_STATUS_CRITICAL)
438 ae->non_clear_duration += ae->duration;
439
440 + return ae;
441 +}
442 +
443 +inline void health_alarm_log(
444 + RRDHOST *host,
445 + ALARM_ENTRY *ae
446 +) {
447 + debug(D_HEALTH, "Health adding alarm log entry with id: %u", ae->unique_id);
448 +
449 + if(unlikely(alarm_entry_isrepeating(host, ae))) {
450 + error("Repeating alarms cannot be added to host's alarm log entries. It seems somewhere in the logic, API is being misused. Alarm id: %u", ae->alarm_id);
451 + return;
452 + }
453 // link it
454 netdata_rwlock_wrlock(&host->health_log.alarm_log_rwlock);
455 ae->next = host->health_log.alarms;
libnetdata/config/appconfig.c
+83
@@ -411,6 +411,27 @@ int appconfig_set_boolean(struct config *root, const char *section, const char *
411 return value;
412 }
413
414 +int appconfig_get_duration(struct config *root, const char *section, const char *name, const char *value)
415 +{
416 + int result = 0;
417 + const char *s;
418 +
419 + s = appconfig_get(root, section, name, value);
420 + if(!s) goto fallback;
421 +
422 + if(!config_parse_duration(s, &result)) {
423 + error("config option '[%s].%s = %s' is configured with an valid duration", section, name, s);
424 + goto fallback;
425 + }
426 +
427 + return result;
428 +
429 + fallback:
430 + if(!config_parse_duration(value, &result))
431 + error("INTERNAL ERROR: default duration supplied for option '[%s].%s = %s' is not a valid duration", section, name, value);
432 +
433 + return result;
434 +}
435
436 // ----------------------------------------------------------------------------
437 // config load/save
@@ -586,3 +607,65 @@ void appconfig_generate(struct config *root, BUFFER *wb, int only_changed)
607 appconfig_unlock(root);
608 }
609 }
610 +
611 +/**
612 + * Parse Duration
613 + *
614 + * Parse the string setting the result
615 + *
616 + * @param string the timestamp string
617 + * @param result the output variable
618 + *
619 + * @return It returns 1 on success and 0 otherwise
620 + */
621 +int config_parse_duration(const char* string, int* result) {
622 + while(*string && isspace(*string)) string++;
623 +
624 + if(unlikely(!*string)) goto fallback;
625 +
626 + if(*string == 'n' && !strcmp(string, "never")) {
627 + // this is a valid option
628 + *result = 0;
629 + return 1;
630 + }
631 +
632 + // make sure it is a number
633 + if(!(isdigit(*string) || *string == '+' || *string == '-')) goto fallback;
634 +
635 + char *e = NULL;
636 + calculated_number n = str2ld(string, &e);
637 + if(e && *e) {
638 + switch (*e) {
639 + case 'Y':
640 + *result = (int) (n * 31536000);
641 + break;
642 + case 'M':
643 + *result = (int) (n * 2592000);
644 + break;
645 + case 'w':
646 + *result = (int) (n * 604800);
647 + break;
648 + case 'd':
649 + *result = (int) (n * 86400);
650 + break;
651 + case 'h':
652 + *result = (int) (n * 3600);
653 + break;
654 + case 'm':
655 + *result = (int) (n * 60);
656 + break;
657 + case 's':
658 + default:
659 + *result = (int) (n);
660 + break;
661 + }
662 + }
663 + else
664 + *result = (int)(n);
665 +
666 + return 1;
667 +
668 + fallback:
669 + *result = 0;
670 + return 0;
671 +}
libnetdata/config/appconfig.h
+3
@@ -119,6 +119,7 @@ extern long long appconfig_get_number(struct config *root, const char *section,
119 extern LONG_DOUBLE appconfig_get_float(struct config *root, const char *section, const char *name, LONG_DOUBLE value);
120 extern int appconfig_get_boolean(struct config *root, const char *section, const char *name, int value);
121 extern int appconfig_get_boolean_ondemand(struct config *root, const char *section, const char *name, int value);
122 +extern int appconfig_get_duration(struct config *root, const char *section, const char *name, const char *value);
123
124 extern const char *appconfig_set(struct config *root, const char *section, const char *name, const char *value);
125 extern const char *appconfig_set_default(struct config *root, const char *section, const char *name, const char *value);
@@ -133,4 +134,6 @@ extern void appconfig_generate(struct config *root, BUFFER *wb, int only_changed
134
135 extern int appconfig_section_compare(void *a, void *b);
136
137 +extern int config_parse_duration(const char* string, int* result);
138 +
139 #endif /* NETDATA_CONFIG_H */
web/api/netdata-swagger.json
+8
@@ -1305,6 +1305,14 @@
1305 "crit_parsed": {
1306 "type": "string"
1307 },
1308 + "warn_repeat_every": {
1309 + "type": "integer",
1310 + "format": "int32"
1311 + },
1312 + "crit_repeat_every": {
1313 + "type": "integer",
1314 + "format": "int32"
1315 + },
1316 "green": {
1317 "type": "string",
1318 "format": "nullable"
web/api/netdata-swagger.yaml
+6
@@ -892,6 +892,12 @@ definitions:
892 type: string
893 crit_parsed:
894 type: string
895 + warn_repeat_every:
896 + type: integer
897 + format: int32
898 + crit_repeat_every:
899 + type: integer
900 + format: int32
901 green:
902 type: string
903 format: nullable
web/gui/main.js
+8
@@ -2100,6 +2100,14 @@ function alarmsUpdateModal() {
2100 + ((chart.red !== null) ? ('<tr><td width="10%" style="text-align:right">red&nbsp;threshold</td><td><code>' + chart.red + ' ' + units + '</code></td></tr>') : '');
2101 }
2102
2103 + if (alarm.warn_repeat_every > 0) {
2104 + html += '<tr><td width="10%" style="text-align:right">repeat&nbsp;warning</td><td>' + NETDATA.seconds4human(alarm.warn_repeat_every) + '</td></tr>';
2105 + }
2106 +
2107 + if (alarm.crit_repeat_every > 0) {
2108 + html += '<tr><td width="10%" style="text-align:right">repeat&nbsp;critical</td><td>' + NETDATA.seconds4human(alarm.crit_repeat_every) + '</td></tr>';
2109 + }
2110 +
2111 var delay = '';
2112 if ((alarm.delay_up_duration > 0 || alarm.delay_down_duration > 0) && alarm.delay_multiplier !== 0 && alarm.delay_max_duration > 0) {
2113 if (alarm.delay_up_duration === alarm.delay_down_duration) {