Speed up alerts API filtering with host status snapshots (#21984)
* Refactor health alert handling and snapshot management: - Introduced `health_alert_status_counts` for better organization of alert status data. - Implemented host-level alert status snapshot for efficient prefiltering. - Optimized alert summary handling with conditional dictionary initialization based on the SUMMARY flag. - Added atomic operations for consistent alert status tracking and updates. - Enhanced health event loop to dynamically adjust alert status counts. * Fix seqlock window: defer begin_update to right before publish
Stelios Fragkakis committed
Mar 20, 2026 at 14:05 UTC
64e0d811d6d622ad01bbfa2bbed8ff8f4295b97f
4 files changed
+270
-87
src/database/contexts/api_v2_contexts.c
+63
-1
@@ -563,6 +563,56 @@ static void rrdcontext_to_json_v2_rrdhost(BUFFER *wb, RRDHOST *host, struct rrdc
563
buffer_json_object_close(wb); // this node
564
}
565
566
+static bool rrdhost_alert_status_snapshot_read(RRDHOST *host, struct health_alert_status_counts *snapshot) {
567
+ for(size_t retries = 0; retries < 3; retries++) {
568
+ uint64_t g1 = __atomic_load_n(&host->health.alert_status_snapshot.generation, __ATOMIC_ACQUIRE);
569
+ if(unlikely(g1 & 1))
570
+ continue;
571
+
572
+ snapshot->clear = __atomic_load_n(&host->health.alert_status_snapshot.counts.clear, __ATOMIC_RELAXED);
573
+ snapshot->warning = __atomic_load_n(&host->health.alert_status_snapshot.counts.warning, __ATOMIC_RELAXED);
574
+ snapshot->critical = __atomic_load_n(&host->health.alert_status_snapshot.counts.critical, __ATOMIC_RELAXED);
575
+ snapshot->undefined = __atomic_load_n(&host->health.alert_status_snapshot.counts.undefined, __ATOMIC_RELAXED);
576
+ snapshot->uninitialized = __atomic_load_n(&host->health.alert_status_snapshot.counts.uninitialized, __ATOMIC_RELAXED);
577
+
578
+ uint8_t valid = __atomic_load_n(&host->health.alert_status_snapshot.valid, __ATOMIC_ACQUIRE);
579
+ uint64_t g2 = __atomic_load_n(&host->health.alert_status_snapshot.generation, __ATOMIC_ACQUIRE);
580
+ if(likely(g1 == g2 && !(g2 & 1)))
581
+ return valid;
582
+ }
583
+
584
+ return false;
585
+}
586
+
587
+static inline bool rrdhost_alert_status_snapshot_matches_filter(
588
+ CONTEXTS_ALERT_STATUS filter,
589
+ const struct health_alert_status_counts *snapshot) {
590
+
591
+ if(!(filter & CONTEXTS_ALERT_STATUSES))
592
+ return true;
593
+
594
+ if((filter & CONTEXT_ALERT_UNINITIALIZED) && snapshot->uninitialized)
595
+ return true;
596
+
597
+ if((filter & CONTEXT_ALERT_UNDEFINED) && snapshot->undefined)
598
+ return true;
599
+
600
+ if((filter & CONTEXT_ALERT_CLEAR) && snapshot->clear)
601
+ return true;
602
+
603
+ if((filter & CONTEXT_ALERT_WARNING) && snapshot->warning)
604
+ return true;
605
+
606
+ if((filter & CONTEXT_ALERT_CRITICAL) && snapshot->critical)
607
+ return true;
608
+
609
+ if((filter & CONTEXT_ALERT_RAISED) &&
610
+ (snapshot->warning || snapshot->critical))
611
+ return true;
612
+
613
+ return false;
614
+}
615
+
616
static ssize_t rrdcontext_to_json_v2_add_host(void *data, RRDHOST *host, bool queryable_host) {
617
if(!queryable_host || !host->rrdctx.contexts)
618
// the host matches the 'scope_host' but does not match the 'host' patterns
@@ -583,8 +633,20 @@ static ssize_t rrdcontext_to_json_v2_add_host(void *data, RRDHOST *host, bool qu
633
// interrupted
634
return -1; // stop the query
635
586
- bool host_matched = (ctl->mode & (CONTEXTS_V2_NODES | CONTEXTS_V2_FUNCTIONS | CONTEXTS_V2_ALERTS)) && !ctl->contexts.pattern && !ctl->contexts.scope_pattern && !ctl->window.enabled;
636
+ bool host_may_have_matching_alerts = true;
637
+ if((ctl->mode & CONTEXTS_V2_ALERTS) && (ctl->request->alerts.status & CONTEXTS_ALERT_STATUSES)) {
638
+ struct health_alert_status_counts snapshot = { 0 };
639
+ if(rrdhost_alert_status_snapshot_read(host, &snapshot) &&
640
+ !rrdhost_alert_status_snapshot_matches_filter(ctl->request->alerts.status, &snapshot))
641
+ host_may_have_matching_alerts = false;
642
+ }
643
+
644
+ CONTEXTS_V2_MODE host_matched_modes = CONTEXTS_V2_NODES | CONTEXTS_V2_FUNCTIONS |
645
+ (host_may_have_matching_alerts ? CONTEXTS_V2_ALERTS : 0);
646
+ bool host_matched = (ctl->mode & host_matched_modes) && !ctl->contexts.pattern && !ctl->contexts.scope_pattern && !ctl->window.enabled;
647
bool do_contexts = (ctl->mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS)) || ctl->contexts.pattern || ctl->contexts.scope_pattern;
648
+ if((ctl->mode & CONTEXTS_V2_ALERTS) && !host_may_have_matching_alerts)
649
+ do_contexts = false;
650
651
if(do_contexts) {
652
ssize_t added = query_scope_foreach_context(
src/database/contexts/api_v2_contexts_alerts.c
+93
-84
@@ -43,6 +43,7 @@ struct alert_by_x_entry {
43
};
44
45
bool rrdcontext_matches_alert(struct rrdcontext_to_json_v2_data *ctl, RRDCONTEXT *rc) {
46
+ const bool summary_requested = (ctl->request->options & CONTEXTS_OPTION_SUMMARY);
47
size_t matches = 0;
48
RRDINSTANCE *ri;
49
dfe_start_read(rc->rrdinstances, ri) {
@@ -87,54 +88,58 @@ bool rrdcontext_matches_alert(struct rrdcontext_to_json_v2_data *ctl, RRDCONTEXT
88
continue;
89
}
90
90
- struct alert_v2_entry t = {
91
- .tmp = rcl,
92
- };
93
- struct alert_v2_entry *a2e =
94
- dictionary_set(ctl->alerts.summary, string2str(rcl->config.name),
95
- &t, sizeof(struct alert_v2_entry));
96
- size_t ati = a2e->ati;
97
- matches++;
91
+ size_t ati = 0;
92
+ if(summary_requested) {
93
+ struct alert_v2_entry t = {
94
+ .tmp = rcl,
95
+ };
96
+ struct alert_v2_entry *a2e =
97
+ dictionary_set(ctl->alerts.summary, string2str(rcl->config.name),
98
+ &t, sizeof(struct alert_v2_entry));
99
+ ati = a2e->ati;
100
+
101
+ dictionary_set_advanced(ctl->alerts.by_type,
102
+ string2str(rcl->config.type),
103
+ (ssize_t)string_strlen(rcl->config.type),
104
+ NULL,
105
+ sizeof(struct alert_by_x_entry),
106
+ rcl);
107
+
108
+ dictionary_set_advanced(ctl->alerts.by_component,
109
+ string2str(rcl->config.component),
110
+ (ssize_t)string_strlen(rcl->config.component),
111
+ NULL,
112
+ sizeof(struct alert_by_x_entry),
113
+ rcl);
114
+
115
+ dictionary_set_advanced(ctl->alerts.by_classification,
116
+ string2str(rcl->config.classification),
117
+ (ssize_t)string_strlen(rcl->config.classification),
118
+ NULL,
119
+ sizeof(struct alert_by_x_entry),
120
+ rcl);
121
+
122
+ dictionary_set_advanced(ctl->alerts.by_recipient,
123
+ string2str(rcl->config.recipient),
124
+ (ssize_t)string_strlen(rcl->config.recipient),
125
+ NULL,
126
+ sizeof(struct alert_by_x_entry),
127
+ rcl);
128
+
129
+ char module[128];
130
+ rrdlabels_get_value_strcpyz(st->rrdlabels, module, sizeof(module), "_collect_module");
131
+ if(!*module)
132
+ strncpyz(module, "[unset]", sizeof(module) - 1);
133
+
134
+ dictionary_set_advanced(ctl->alerts.by_module,
135
+ module,
136
+ -1,
137
+ NULL,
138
+ sizeof(struct alert_by_x_entry),
139
+ rcl);
140
+ }
141
99
- dictionary_set_advanced(ctl->alerts.by_type,
100
- string2str(rcl->config.type),
101
- (ssize_t)string_strlen(rcl->config.type),
102
- NULL,
103
- sizeof(struct alert_by_x_entry),
104
- rcl);
105
-
106
- dictionary_set_advanced(ctl->alerts.by_component,
107
- string2str(rcl->config.component),
108
- (ssize_t)string_strlen(rcl->config.component),
109
- NULL,
110
- sizeof(struct alert_by_x_entry),
111
- rcl);
112
-
113
- dictionary_set_advanced(ctl->alerts.by_classification,
114
- string2str(rcl->config.classification),
115
- (ssize_t)string_strlen(rcl->config.classification),
116
- NULL,
117
- sizeof(struct alert_by_x_entry),
118
- rcl);
119
-
120
- dictionary_set_advanced(ctl->alerts.by_recipient,
121
- string2str(rcl->config.recipient),
122
- (ssize_t)string_strlen(rcl->config.recipient),
123
- NULL,
124
- sizeof(struct alert_by_x_entry),
125
- rcl);
126
-
127
- char module[128];
128
- rrdlabels_get_value_strcpyz(st->rrdlabels, module, sizeof(module), "_collect_module");
129
- if(!*module)
130
- strncpyz(module, "[unset]", sizeof(module) - 1);
131
-
132
- dictionary_set_advanced(ctl->alerts.by_module,
133
- module,
134
- -1,
135
- NULL,
136
- sizeof(struct alert_by_x_entry),
137
- rcl);
142
+ matches++;
143
144
if (ctl->options & (CONTEXTS_OPTION_INSTANCES | CONTEXTS_OPTION_VALUES)) {
145
char key[20 + 1];
@@ -737,60 +742,64 @@ static void rrdcontext_v2_set_transition_filter(const char *machine_guid, const
742
}
743
744
bool rrdcontexts_v2_init_alert_dictionaries(struct rrdcontext_to_json_v2_data *ctl, struct api_v2_contexts_request *req) {
745
+ const bool summary_requested = (req->options & CONTEXTS_OPTION_SUMMARY);
746
+
747
if(req->alerts.transition) {
748
ctl->options |= CONTEXTS_OPTION_INSTANCES | CONTEXTS_OPTION_VALUES;
749
if(!sql_find_alert_transition(req->alerts.transition, rrdcontext_v2_set_transition_filter, ctl))
750
return false;
751
}
752
746
- ctl->alerts.summary = dictionary_create_advanced(
747
- DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
748
- NULL,
749
- sizeof(struct alert_v2_entry));
753
+ if(summary_requested) {
754
+ ctl->alerts.summary = dictionary_create_advanced(
755
+ DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
756
+ NULL,
757
+ sizeof(struct alert_v2_entry));
758
751
- dictionary_register_insert_callback(ctl->alerts.summary, alerts_v2_insert_callback, ctl);
752
- dictionary_register_conflict_callback(ctl->alerts.summary, alerts_v2_conflict_callback, ctl);
753
- dictionary_register_delete_callback(ctl->alerts.summary, alerts_v2_delete_callback, ctl);
759
+ dictionary_register_insert_callback(ctl->alerts.summary, alerts_v2_insert_callback, ctl);
760
+ dictionary_register_conflict_callback(ctl->alerts.summary, alerts_v2_conflict_callback, ctl);
761
+ dictionary_register_delete_callback(ctl->alerts.summary, alerts_v2_delete_callback, ctl);
762
755
- ctl->alerts.by_type = dictionary_create_advanced(
756
- DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
757
- NULL,
758
- sizeof(struct alert_by_x_entry));
763
+ ctl->alerts.by_type = dictionary_create_advanced(
764
+ DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
765
+ NULL,
766
+ sizeof(struct alert_by_x_entry));
767
760
- dictionary_register_insert_callback(ctl->alerts.by_type, alerts_by_x_insert_callback, NULL);
761
- dictionary_register_conflict_callback(ctl->alerts.by_type, alerts_by_x_conflict_callback, NULL);
768
+ dictionary_register_insert_callback(ctl->alerts.by_type, alerts_by_x_insert_callback, NULL);
769
+ dictionary_register_conflict_callback(ctl->alerts.by_type, alerts_by_x_conflict_callback, NULL);
770
763
- ctl->alerts.by_component = dictionary_create_advanced(
764
- DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
765
- NULL,
766
- sizeof(struct alert_by_x_entry));
771
+ ctl->alerts.by_component = dictionary_create_advanced(
772
+ DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
773
+ NULL,
774
+ sizeof(struct alert_by_x_entry));
775
768
- dictionary_register_insert_callback(ctl->alerts.by_component, alerts_by_x_insert_callback, NULL);
769
- dictionary_register_conflict_callback(ctl->alerts.by_component, alerts_by_x_conflict_callback, NULL);
776
+ dictionary_register_insert_callback(ctl->alerts.by_component, alerts_by_x_insert_callback, NULL);
777
+ dictionary_register_conflict_callback(ctl->alerts.by_component, alerts_by_x_conflict_callback, NULL);
778
771
- ctl->alerts.by_classification = dictionary_create_advanced(
772
- DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
773
- NULL,
774
- sizeof(struct alert_by_x_entry));
779
+ ctl->alerts.by_classification = dictionary_create_advanced(
780
+ DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
781
+ NULL,
782
+ sizeof(struct alert_by_x_entry));
783
776
- dictionary_register_insert_callback(ctl->alerts.by_classification, alerts_by_x_insert_callback, NULL);
777
- dictionary_register_conflict_callback(ctl->alerts.by_classification, alerts_by_x_conflict_callback, NULL);
784
+ dictionary_register_insert_callback(ctl->alerts.by_classification, alerts_by_x_insert_callback, NULL);
785
+ dictionary_register_conflict_callback(ctl->alerts.by_classification, alerts_by_x_conflict_callback, NULL);
786
779
- ctl->alerts.by_recipient = dictionary_create_advanced(
780
- DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
781
- NULL,
782
- sizeof(struct alert_by_x_entry));
787
+ ctl->alerts.by_recipient = dictionary_create_advanced(
788
+ DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
789
+ NULL,
790
+ sizeof(struct alert_by_x_entry));
791
784
- dictionary_register_insert_callback(ctl->alerts.by_recipient, alerts_by_x_insert_callback, NULL);
785
- dictionary_register_conflict_callback(ctl->alerts.by_recipient, alerts_by_x_conflict_callback, NULL);
792
+ dictionary_register_insert_callback(ctl->alerts.by_recipient, alerts_by_x_insert_callback, NULL);
793
+ dictionary_register_conflict_callback(ctl->alerts.by_recipient, alerts_by_x_conflict_callback, NULL);
794
787
- ctl->alerts.by_module = dictionary_create_advanced(
788
- DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
789
- NULL,
790
- sizeof(struct alert_by_x_entry));
795
+ ctl->alerts.by_module = dictionary_create_advanced(
796
+ DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
797
+ NULL,
798
+ sizeof(struct alert_by_x_entry));
799
792
- dictionary_register_insert_callback(ctl->alerts.by_module, alerts_by_x_insert_callback, NULL);
793
- dictionary_register_conflict_callback(ctl->alerts.by_module, alerts_by_x_conflict_callback, NULL);
800
+ dictionary_register_insert_callback(ctl->alerts.by_module, alerts_by_x_insert_callback, NULL);
801
+ dictionary_register_conflict_callback(ctl->alerts.by_module, alerts_by_x_conflict_callback, NULL);
802
+ }
803
804
if(ctl->options & (CONTEXTS_OPTION_INSTANCES | CONTEXTS_OPTION_VALUES)) {
805
ctl->alerts.alert_instances = dictionary_create_advanced(
src/health/health-alert-log.h
+15
@@ -14,7 +14,22 @@ typedef struct alarm_log {
14
RW_SPINLOCK spinlock;
15
} ALARM_LOG;
16
17
+struct health_alert_status_counts {
18
+ uint32_t clear;
19
+ uint32_t warning;
20
+ uint32_t critical;
21
+ uint32_t undefined;
22
+ uint32_t uninitialized;
23
+};
24
+
25
typedef struct health {
26
+ // Recomputed on each host health evaluation and read by alerts APIs as a host-level prefilter.
27
+ struct {
28
+ uint64_t generation; // seqlock-style generation for consistent readers
29
+ struct health_alert_status_counts counts;
30
+ uint8_t valid; // 1 when snapshot is fully published
31
+ } alert_status_snapshot;
32
+
33
time_t delay_up_to; // a timestamp to delay alarms processing up to
34
STRING *default_exec; // the full path of the alarms notifications program
35
STRING *default_recipient; // the default recipient for all alarms
src/health/health_event_loop.c
+99
-2
@@ -36,6 +36,85 @@ void rrdhost_set_health_evloop_iteration(RRDHOST *host) {
36
health_evloop_current_iteration(), __ATOMIC_RELAXED);
37
}
38
39
+static inline void health_alert_status_counts_add(struct health_alert_status_counts *c, RRDCALC_STATUS status) {
40
+ switch(status) {
41
+ case RRDCALC_STATUS_CLEAR:
42
+ c->clear++;
43
+ break;
44
+
45
+ case RRDCALC_STATUS_WARNING:
46
+ c->warning++;
47
+ break;
48
+
49
+ case RRDCALC_STATUS_CRITICAL:
50
+ c->critical++;
51
+ break;
52
+
53
+ case RRDCALC_STATUS_UNDEFINED:
54
+ c->undefined++;
55
+ break;
56
+
57
+ case RRDCALC_STATUS_UNINITIALIZED:
58
+ c->uninitialized++;
59
+ break;
60
+
61
+ default:
62
+ break;
63
+ }
64
+}
65
+
66
+static inline void health_alert_status_counts_sub(struct health_alert_status_counts *c, RRDCALC_STATUS status) {
67
+ switch(status) {
68
+ case RRDCALC_STATUS_CLEAR:
69
+ if(c->clear) c->clear--;
70
+ break;
71
+
72
+ case RRDCALC_STATUS_WARNING:
73
+ if(c->warning) c->warning--;
74
+ break;
75
+
76
+ case RRDCALC_STATUS_CRITICAL:
77
+ if(c->critical) c->critical--;
78
+ break;
79
+
80
+ case RRDCALC_STATUS_UNDEFINED:
81
+ if(c->undefined) c->undefined--;
82
+ break;
83
+
84
+ case RRDCALC_STATUS_UNINITIALIZED:
85
+ if(c->uninitialized) c->uninitialized--;
86
+ break;
87
+
88
+ default:
89
+ break;
90
+ }
91
+}
92
+
93
+static inline uint64_t health_alert_status_snapshot_begin_update(RRDHOST *host) {
94
+ // Make generation odd (writer in progress) so readers can safely retry/fallback.
95
+ uint64_t generation = __atomic_add_fetch(&host->health.alert_status_snapshot.generation, 1, __ATOMIC_ACQ_REL);
96
+ if(!(generation & 1))
97
+ generation = __atomic_add_fetch(&host->health.alert_status_snapshot.generation, 1, __ATOMIC_ACQ_REL);
98
+
99
+ __atomic_store_n(&host->health.alert_status_snapshot.valid, 0, __ATOMIC_RELEASE);
100
+ return generation;
101
+}
102
+
103
+static inline void health_alert_status_snapshot_finish_update(
104
+ RRDHOST *host,
105
+ const struct health_alert_status_counts *counts,
106
+ uint64_t odd_generation) {
107
+
108
+ __atomic_store_n(&host->health.alert_status_snapshot.counts.clear, counts->clear, __ATOMIC_RELAXED);
109
+ __atomic_store_n(&host->health.alert_status_snapshot.counts.warning, counts->warning, __ATOMIC_RELAXED);
110
+ __atomic_store_n(&host->health.alert_status_snapshot.counts.critical, counts->critical, __ATOMIC_RELAXED);
111
+ __atomic_store_n(&host->health.alert_status_snapshot.counts.undefined, counts->undefined, __ATOMIC_RELAXED);
112
+ __atomic_store_n(&host->health.alert_status_snapshot.counts.uninitialized, counts->uninitialized, __ATOMIC_RELAXED);
113
+
114
+ __atomic_store_n(&host->health.alert_status_snapshot.valid, 1, __ATOMIC_RELEASE);
115
+ __atomic_store_n(&host->health.alert_status_snapshot.generation, odd_generation + 1, __ATOMIC_RELEASE);
116
+}
117
+
118
// ----------------------------------------------------------------------------
119
// health main thread and friends
120
@@ -240,6 +319,8 @@ static void do_eval_expression(
319
// returns the number of runnable alerts
320
static void health_event_loop_for_host(RRDHOST *host, bool apply_hibernation_delay, time_t now, time_t *next_run) {
321
size_t runnable = 0;
322
+ struct health_alert_status_counts status_counts = { 0 };
323
+ bool snapshot_complete = true;
324
325
if(unlikely(!rrdhost_should_run_health(host)))
326
return;
@@ -291,8 +372,13 @@ static void health_event_loop_for_host(RRDHOST *host, bool apply_hibernation_del
372
// the first loop is to lookup values from the db
373
RRDCALC *rc;
374
foreach_rrdcalc_in_rrdhost_read(host, rc) {
294
- if(unlikely(!service_running(SERVICE_HEALTH) || !rrdhost_should_run_health(host)))
375
+ if(unlikely(!service_running(SERVICE_HEALTH) || !rrdhost_should_run_health(host))) {
376
+ snapshot_complete = false;
377
break;
378
+ }
379
+
380
+ if(likely(rc->rrdset))
381
+ health_alert_status_counts_add(&status_counts, rc->status);
382
383
rrdcalc_update_info_using_rrdset_labels(rc);
384
@@ -325,6 +411,7 @@ static void health_event_loop_for_host(RRDHOST *host, bool apply_hibernation_del
411
if (ae) {
412
health_log_alert(host, ae);
413
health_alarm_log_add_entry(host, ae, false);
414
+ health_alert_status_counts_sub(&status_counts, rc->status);
415
rc->old_status = rc->status;
416
rc->status = RRDCALC_STATUS_REMOVED;
417
rc->last_status_change = now_tmp;
@@ -424,8 +511,10 @@ static void health_event_loop_for_host(RRDHOST *host, bool apply_hibernation_del
511
512
if (unlikely(runnable && service_running(SERVICE_HEALTH))) {
513
foreach_rrdcalc_in_rrdhost_read(host, rc) {
427
- if(unlikely(!service_running(SERVICE_HEALTH) || !rrdhost_should_run_health(host)))
514
+ if(unlikely(!service_running(SERVICE_HEALTH) || !rrdhost_should_run_health(host))) {
515
+ snapshot_complete = false;
516
break;
517
+ }
518
519
if (unlikely(!(rc->run_flags & RRDCALC_FLAG_RUNNABLE)))
520
continue;
@@ -534,6 +623,9 @@ static void health_event_loop_for_host(RRDHOST *host, bool apply_hibernation_del
623
rrdhost_hostname(host), ae_chart_id(ae), ae_name(ae), ae_new_value_string(ae),
624
rrdcalc_status2string(ae->new_status));
625
626
+ health_alert_status_counts_sub(&status_counts, rc->status);
627
+ health_alert_status_counts_add(&status_counts, status);
628
+
629
rc->last_status_change_value = rc->value;
630
rc->last_status_change = now;
631
rc->old_status = rc->status;
@@ -617,6 +709,11 @@ static void health_event_loop_for_host(RRDHOST *host, bool apply_hibernation_del
709
foreach_rrdcalc_in_rrdhost_done(rc);
710
}
711
712
+ if(likely(snapshot_complete)) {
713
+ uint64_t snapshot_generation = health_alert_status_snapshot_begin_update(host);
714
+ health_alert_status_snapshot_finish_update(host, &status_counts, snapshot_generation);
715
+ }
716
+
717
if(unlikely(!service_running(SERVICE_HEALTH) || !rrdhost_should_run_health(host))) {
718
alerts_raised_summary_free(hrm);
719
return;