@cryptotaxi247 / netdata-1 / commits / f3efdba1a

New alerts endpoint (#15232)

* alerts / alerts_log v2 * Add global_id to ae Populate entries with global id * Remove transition id from template Change history to instances * Link ae to rc in all cases Code cleanup

Stelios Fragkakis committed Jun 22, 2023 at 01:16 UTC f3efdba1a0cff9b8d5a2df7149824dba2b59b653
15 files changed +756 -5
database/contexts/api_v2.c
+375 -1
@@ -98,7 +98,11 @@ struct rrdcontext_to_json_v2_data {
98 time_t now;
99
100 BUFFER *wb;
101 - struct api_v2_contexts_request *request;
101 + union {
102 + struct api_v2_contexts_request *request;
103 + struct api_v2_alerts_request *alerts_request;
104 + };
105 +
106 DICTIONARY *ctx;
107
108 CONTEXTS_V2_OPTIONS options;
@@ -110,6 +114,16 @@ struct rrdcontext_to_json_v2_data {
114 size_t ni;
115 } nodes;
116
117 + struct {
118 + Pvoid_t JudyHS;
119 +// ALERT_OPTIONS alert_options;
120 + SIMPLE_PATTERN *scope_pattern;
121 + SIMPLE_PATTERN *pattern;
122 +// time_t after;
123 +// time_t before;
124 + size_t li;
125 + } alerts;
126 +
127 struct {
128 SIMPLE_PATTERN *scope_pattern;
129 SIMPLE_PATTERN *pattern;
@@ -136,6 +150,22 @@ struct rrdcontext_to_json_v2_data {
150 struct query_timings timings;
151 };
152
153 +static void add_alert_index(Pvoid_t *JudyHS, uuid_t *uuid, ssize_t idx)
154 +{
155 + Pvoid_t *PValue = JudyHSIns(JudyHS, uuid, sizeof(*uuid), PJE0);
156 + if (!PValue)
157 + return;
158 + *((Word_t *) PValue) = (Word_t) idx;
159 +}
160 +
161 +ssize_t get_alert_index(Pvoid_t JudyHS, uuid_t *uuid)
162 +{
163 + Pvoid_t *PValue = JudyHSGet(JudyHS, uuid, sizeof(*uuid));
164 + if (!PValue)
165 + return -1;
166 + return (ssize_t) *((Word_t *) PValue);
167 +}
168 +
169 static FTS_MATCH rrdcontext_to_json_v2_full_text_search(struct rrdcontext_to_json_v2_data *ctl, RRDCONTEXT *rc, SIMPLE_PATTERN *q) {
170 if(unlikely(full_text_search_string(&ctl->q.fts, q, rc->id) ||
171 full_text_search_string(&ctl->q.fts, q, rc->family)))
@@ -250,6 +280,11 @@ void buffer_json_agent_status_id(BUFFER *wb, size_t ai, usec_t duration_ut) {
280 buffer_json_object_close(wb);
281 }
282
283 +static ssize_t alert_to_json_v2_add_context(void *data, RRDCONTEXT_ACQUIRED *rca, bool queryable_context __maybe_unused)
284 +{
285 + return rrdcontext_to_json_v2_add_context(data, rca, queryable_context);
286 +}
287 +
288 void buffer_json_node_add_v2(BUFFER *wb, RRDHOST *host, size_t ni, usec_t duration_ut, bool status) {
289 buffer_json_member_add_string(wb, "mg", host->machine_guid);
290
@@ -595,6 +630,229 @@ static ssize_t rrdcontext_to_json_v2_add_host(void *data, RRDHOST *host, bool qu
630 return 1;
631 }
632
633 +static ssize_t alert_to_json_v2_add_host(void *data, RRDHOST *host, bool queryable_host) {
634 + if(!queryable_host || !host->rrdctx.contexts)
635 + // the host matches the 'scope_host' but does not match the 'host' patterns
636 + // or the host does not have any contexts
637 + return 0;
638 +
639 + struct rrdcontext_to_json_v2_data *ctl = data;
640 + BUFFER *wb = ctl->wb;
641 +
642 + bool host_matched = (ctl->options & CONTEXTS_V2_NODES);
643 + bool do_contexts = (ctl->options & (CONTEXTS_V2_CONTEXTS));
644 +
645 + ctl->q.host_match = FTS_MATCHED_NONE;
646 + if((ctl->options & CONTEXTS_V2_SEARCH)) {
647 + // check if we match the host itself
648 + if(ctl->q.pattern && (
649 + full_text_search_string(&ctl->q.fts, ctl->q.pattern, host->hostname) ||
650 + full_text_search_char(&ctl->q.fts, ctl->q.pattern, host->machine_guid) ||
651 + (ctl->q.pattern && full_text_search_char(&ctl->q.fts, ctl->q.pattern, ctl->q.host_node_id_str)))) {
652 + ctl->q.host_match = FTS_MATCHED_HOST;
653 + do_contexts = true;
654 + }
655 + }
656 +
657 + if(do_contexts) {
658 + // save it
659 + SIMPLE_PATTERN *old_q = ctl->q.pattern;
660 +
661 + if(ctl->q.host_match == FTS_MATCHED_HOST)
662 + // do not do pattern matching on contexts - we matched the host itself
663 + ctl->q.pattern = NULL;
664 +
665 + ssize_t added = query_scope_foreach_context(
666 + host, ctl->alerts_request->scope_contexts,
667 + ctl->contexts.scope_pattern, ctl->contexts.pattern,
668 + alert_to_json_v2_add_context, queryable_host, ctl);
669 +
670 + // restore it
671 + ctl->q.pattern = old_q;
672 +
673 + if(added == -1)
674 + return -1;
675 +
676 + if(added)
677 + host_matched = true;
678 + }
679 +
680 + if(host_matched && (ctl->options & (CONTEXTS_V2_NODES))) {
681 + buffer_json_add_array_item_object(wb);
682 + buffer_json_node_add_v2(wb, host, ctl->nodes.ni++, 0, false);
683 +
684 + if (ctl->alerts_request->options & ALERT_OPTION_TRANSITIONS) {
685 + if (rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH)) {
686 + buffer_json_member_add_array(wb, "instances");
687 + health_alert2json(host, wb, ctl->alerts_request->options, ctl->alerts.JudyHS, ctl->alerts_request->after, ctl->alerts_request->before, ctl->alerts_request->last);
688 + buffer_json_array_close(wb);
689 + }
690 + }
691 +
692 + buffer_json_object_close(wb);
693 + }
694 +
695 + return host_matched ? 1 : 0;
696 +}
697 +
698 +static inline bool alert_is_matched( struct api_v2_alerts_request *alerts_request, RRDCALC *rc)
699 +{
700 + char hash_id[UUID_STR_LEN];
701 + uuid_unparse_lower(rc->config_hash_id, hash_id);
702 +
703 + if (alerts_request->alert_id)
704 + return (rc->id == alerts_request->alert_id);
705 +
706 + SIMPLE_PATTERN_RESULT match = SP_MATCHED_POSITIVE;
707 + SIMPLE_PATTERN *match_pattern = alerts_request->config_hash_pattern;
708 + if(match_pattern) {
709 + match = simple_pattern_matches_extract(match_pattern, hash_id, NULL, 0);
710 + if(match == SP_NOT_MATCHED)
711 + return false;;
712 + }
713 +
714 + match = SP_MATCHED_POSITIVE;
715 + match_pattern = alerts_request->alert_name_pattern;
716 + if(match_pattern) {
717 + match = simple_pattern_matches_string_extract(match_pattern, rc->name, NULL, 0);
718 + if(match == SP_NOT_MATCHED)
719 + return false;
720 + }
721 +
722 + return true;
723 +}
724 +
725 +static ssize_t alert_to_json_v2_add_alert(void *data, RRDHOST *host, bool queryable_host) {
726 + if(!queryable_host || !host->rrdctx.contexts)
727 + // the host matches the 'scope_host' but does not match the 'host' patterns
728 + // or the host does not have any contexts
729 + return 0;
730 +
731 + struct rrdcontext_to_json_v2_data *ctl = data;
732 + BUFFER *wb = ctl->wb;
733 +
734 + bool host_matched = (ctl->options & CONTEXTS_V2_NODES);
735 + bool do_contexts = (ctl->options & (CONTEXTS_V2_CONTEXTS));
736 +
737 + if(do_contexts) {
738 + ssize_t added = query_scope_foreach_context(
739 + host, ctl->request->scope_contexts,
740 + ctl->contexts.scope_pattern, ctl->contexts.pattern,
741 + alert_to_json_v2_add_context, queryable_host, ctl);
742 +
743 + if(added == -1)
744 + return -1;
745 +
746 + if(added)
747 + host_matched = true;
748 + }
749 +
750 + if(host_matched && (ctl->options & (CONTEXTS_V2_NODES))) {
751 + if (rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH)) {
752 +
753 + RRDCALC *rc;
754 + foreach_rrdcalc_in_rrdhost_read(host, rc) {
755 + if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec))
756 + continue;
757 +
758 + if (unlikely(!rrdset_is_available_for_exporting_and_alarms(rc->rrdset)))
759 + continue;
760 +
761 + if ((ctl->alerts_request->options & ALERT_OPTION_ACTIVE) &&
762 + !(rc->status == RRDCALC_STATUS_WARNING || rc->status == RRDCALC_STATUS_CRITICAL))
763 + continue;
764 +
765 + char hash_id[GUID_LEN + 1];
766 + uuid_unparse_lower(rc->config_hash_id, hash_id);
767 +
768 + if (!alert_is_matched(ctl->alerts_request, rc))
769 + continue;
770 +
771 + ssize_t idx = get_alert_index(ctl->alerts.JudyHS, &rc->config_hash_id);
772 + if (idx >= 0)
773 + continue;
774 +
775 + buffer_json_add_array_item_object(wb);
776 + add_alert_index(&ctl->alerts.JudyHS, &rc->config_hash_id, (ssize_t)ctl->alerts.li++);
777 +
778 + buffer_json_member_add_string(wb, "config_hash_id", hash_id);
779 + buffer_json_member_add_string(wb, "name", rrdcalc_name(rc));
780 + buffer_json_member_add_string(wb, "chart", rrdcalc_chart_name(rc));
781 + buffer_json_member_add_string(wb, "family", (rc->rrdset) ? rrdset_family(rc->rrdset) : "");
782 + buffer_json_member_add_string(wb, "class", rc->classification ? rrdcalc_classification(rc) : "Unknown");
783 + buffer_json_member_add_string(wb, "component", rc->component ? rrdcalc_component(rc) : "Unknown");
784 + buffer_json_member_add_string(wb, "type", rc->type ? rrdcalc_type(rc) : "Unknown");
785 + buffer_json_member_add_string(wb, "units", rrdcalc_units(rc));
786 + buffer_json_member_add_boolean(wb, "enabled", host->health.health_enabled);
787 +
788 + if (ctl->alerts_request->options & ALERT_OPTION_CONFIG) {
789 + buffer_json_member_add_object(wb, "config");
790 + {
791 + buffer_json_member_add_boolean(wb, "active", (rc->rrdset));
792 + buffer_json_member_add_boolean(wb, "disabled", (rc->run_flags & RRDCALC_FLAG_DISABLED));
793 + buffer_json_member_add_boolean(wb, "silenced", (rc->run_flags & RRDCALC_FLAG_SILENCED));
794 + buffer_json_member_add_string(
795 + wb, "exec", rc->exec ? rrdcalc_exec(rc) : string2str(host->health.health_default_exec));
796 + buffer_json_member_add_string(
797 + wb,
798 + "recipient",
799 + rc->recipient ? rrdcalc_recipient(rc) : string2str(host->health.health_default_recipient));
800 + buffer_json_member_add_string(wb, "info", rrdcalc_info(rc));
801 + buffer_json_member_add_string(wb, "source", rrdcalc_source(rc));
802 + buffer_json_member_add_time_t(wb, "update_every", rc->update_every);
803 + buffer_json_member_add_time_t(wb, "delay_up_duration", rc->delay_up_duration);
804 + buffer_json_member_add_time_t(wb, "delay_down_duration", rc->delay_down_duration);
805 + buffer_json_member_add_time_t(wb, "delay_max_duration", rc->delay_max_duration);
806 + buffer_json_member_add_double(wb, "delay_multiplier", rc->delay_multiplier);
807 + buffer_json_member_add_time_t(wb, "delay", rc->delay_last);
808 + buffer_json_member_add_time_t(wb, "warn_repeat_every", rc->warn_repeat_every);
809 + buffer_json_member_add_time_t(wb, "crit_repeat_every", rc->crit_repeat_every);
810 + if (unlikely(rc->options & RRDCALC_OPTION_NO_CLEAR_NOTIFICATION)) {
811 + buffer_json_member_add_boolean(wb, "no_clear_notification", true);
812 + }
813 +
814 + if (rc->calculation) {
815 + buffer_json_member_add_string(wb, "calc", rc->calculation->source);
816 + buffer_json_member_add_string(wb, "calc_parsed", rc->calculation->parsed_as);
817 + }
818 +
819 + if (rc->warning) {
820 + buffer_json_member_add_string(wb, "warn", rc->warning->source);
821 + buffer_json_member_add_string(wb, "warn_parsed", rc->warning->parsed_as);
822 + }
823 +
824 + if (rc->critical) {
825 + buffer_json_member_add_string(wb, "crit", rc->critical->source);
826 + buffer_json_member_add_string(wb, "crit_parsed", rc->critical->parsed_as);
827 + }
828 +
829 + if (RRDCALC_HAS_DB_LOOKUP(rc)) {
830 + if (rc->dimensions)
831 + buffer_json_member_add_string(wb, "lookup_dimensions", rrdcalc_dimensions(rc));
832 +
833 + buffer_json_member_add_string(wb, "lookup_method", time_grouping_method2string(rc->group));
834 + buffer_json_member_add_time_t(wb, "lookup_after", rc->after);
835 + buffer_json_member_add_time_t(wb, "lookup_before", rc->before);
836 +
837 + BUFFER *temp_id = buffer_create(1, NULL);
838 + buffer_data_options2string(temp_id, rc->options);
839 +
840 + buffer_json_member_add_string(wb, "lookup_options", buffer_tostring(temp_id));
841 +
842 + buffer_free(temp_id);
843 + }
844 + }
845 + buffer_json_object_close(wb); // config
846 + }
847 + buffer_json_object_close(wb); // Alert
848 + }
849 + foreach_rrdcalc_in_rrdhost_done(rc);
850 + }
851 + }
852 +
853 + return host_matched ? 1 : 0;
854 +}
855 +
856 static void buffer_json_contexts_v2_options_to_array(BUFFER *wb, CONTEXTS_V2_OPTIONS options) {
857 if(options & CONTEXTS_V2_DEBUG)
858 buffer_json_add_array_item_string(wb, "debug");
@@ -991,3 +1249,119 @@ cleanup:
1249 return resp;
1250 }
1251
1252 +int alerts_to_json_v2(BUFFER *wb, struct api_v2_alerts_request *req, CONTEXTS_V2_OPTIONS options)
1253 +{
1254 + int resp = HTTP_RESP_OK;
1255 +
1256 +// ALERT_OPTIONS alert_options = req->options;
1257 + struct rrdcontext_to_json_v2_data ctl = {
1258 + .wb = wb,
1259 + .alerts_request = req,
1260 + .ctx = NULL,
1261 + .options = options,
1262 + .versions = { 0 },
1263 + .nodes.scope_pattern = string_to_simple_pattern(req->scope_nodes),
1264 + .nodes.pattern = string_to_simple_pattern(req->nodes),
1265 + .contexts.pattern = string_to_simple_pattern(req->contexts),
1266 + .contexts.scope_pattern = string_to_simple_pattern(req->scope_contexts),
1267 + .timings = {
1268 + .received_ut = now_monotonic_usec(),
1269 + }
1270 + };
1271 +
1272 + if(options & CONTEXTS_V2_CONTEXTS)
1273 + {
1274 + ctl.ctx = dictionary_create_advanced(
1275 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL,
1276 + sizeof(struct rrdcontext_to_json_v2_entry));
1277 +
1278 + dictionary_register_delete_callback(ctl.ctx, contexts_delete_callback, NULL);
1279 + }
1280 +
1281 + time_t now_s = now_realtime_sec();
1282 + buffer_json_member_add_uint64(wb, "api", 2);
1283 +
1284 + {
1285 + buffer_json_member_add_object(wb, "request");
1286 +
1287 + buffer_json_member_add_object(wb, "scope");
1288 + buffer_json_member_add_string(wb, "scope_nodes", req->scope_nodes);
1289 + buffer_json_member_add_string(wb, "scope_contexts", req->scope_contexts);
1290 + buffer_json_object_close(wb);
1291 +
1292 + buffer_json_member_add_object(wb, "selectors");
1293 + buffer_json_member_add_string(wb, "nodes", req->nodes);
1294 + buffer_json_member_add_string(wb, "contexts", req->contexts);
1295 + buffer_json_object_close(wb);
1296 +
1297 + buffer_json_member_add_array(wb, "options");
1298 + buffer_json_contexts_v2_options_to_array(wb, options);
1299 + buffer_json_array_close(wb);
1300 +
1301 + buffer_json_object_close(wb);
1302 + }
1303 +
1304 + // Alert configuration
1305 + buffer_json_member_add_array(wb, "alerts");
1306 +
1307 + ssize_t ret = query_scope_foreach_host(ctl.nodes.scope_pattern, ctl.nodes.pattern,
1308 + alert_to_json_v2_add_alert, &ctl, &ctl.versions, NULL);
1309 +
1310 + if(unlikely(ret < 0)) {
1311 + buffer_flush(wb);
1312 +
1313 + if(ret == -2) {
1314 + buffer_strcat(wb, "query timeout");
1315 + resp = HTTP_RESP_GATEWAY_TIMEOUT;
1316 + }
1317 + else {
1318 + buffer_strcat(wb, "query interrupted");
1319 + resp = HTTP_RESP_BACKEND_FETCH_FAILED;
1320 + }
1321 + goto cleanup;
1322 + }
1323 +
1324 + buffer_json_array_close(wb); // alerts
1325 +
1326 + buffer_json_member_add_array(wb, "nodes");
1327 +
1328 + ret = query_scope_foreach_host(ctl.nodes.scope_pattern, ctl.nodes.pattern,
1329 + alert_to_json_v2_add_host, &ctl,
1330 + &ctl.versions, NULL);
1331 +
1332 + if(unlikely(ret < 0)) {
1333 + buffer_flush(wb);
1334 +
1335 + if(ret == -2) {
1336 + buffer_strcat(wb, "query timeout");
1337 + resp = HTTP_RESP_GATEWAY_TIMEOUT;
1338 + }
1339 + else {
1340 + buffer_strcat(wb, "query interrupted");
1341 + resp = HTTP_RESP_BACKEND_FETCH_FAILED;
1342 + }
1343 + goto cleanup;
1344 + }
1345 +
1346 + buffer_json_array_close(wb); // Nodes
1347 +
1348 + ctl.timings.executed_ut = now_monotonic_usec();
1349 + version_hashes_api_v2(wb, &ctl.versions);
1350 +
1351 + buffer_json_agents_array_v2(wb, &ctl.timings, now_s, false);
1352 + buffer_json_cloud_timings(wb, "timings", &ctl.timings);
1353 + buffer_json_finalize(wb);
1354 +
1355 + JudyHSFreeArray(&ctl.alerts.JudyHS, PJE0);
1356 +
1357 +cleanup:
1358 +// dictionary_destroy(ctl.ctx);
1359 + simple_pattern_free(ctl.nodes.scope_pattern);
1360 + simple_pattern_free(ctl.nodes.pattern);
1361 + simple_pattern_free(ctl.contexts.pattern);
1362 + simple_pattern_free(ctl.contexts.scope_pattern);
1363 + simple_pattern_free(req->config_hash_pattern);
1364 + simple_pattern_free(req->alert_name_pattern);
1365 +
1366 + return resp;
1367 +}
database/contexts/rrdcontext.h
+22
@@ -475,6 +475,27 @@ struct api_v2_contexts_request {
475 void *interrupt_callback_data;
476 };
477
478 +struct api_v2_alerts_request {
479 + char *scope_nodes;
480 + char *scope_contexts;
481 + char *nodes;
482 + char *contexts;
483 + char *config_hash;
484 + char *state;
485 + SIMPLE_PATTERN *config_hash_pattern;
486 + char *transition_id;
487 + time_t alert_id;
488 + uint32_t last;
489 + char *alert_name;
490 + SIMPLE_PATTERN *alert_name_pattern;
491 + ALERT_OPTIONS options;
492 + time_t after;
493 + time_t before;
494 + char *q;
495 +};
496 +
497 +ssize_t get_alert_index(Pvoid_t JudyHS, uuid_t *uuid);
498 +
499 typedef enum __attribute__ ((__packed__)) {
500 CONTEXTS_V2_DEBUG = (1 << 0),
501 CONTEXTS_V2_MINIFY = (1 << 1),
@@ -490,6 +511,7 @@ typedef enum __attribute__ ((__packed__)) {
511 } CONTEXTS_V2_OPTIONS;
512
513 int rrdcontext_to_json_v2(BUFFER *wb, struct api_v2_contexts_request *req, CONTEXTS_V2_OPTIONS options);
514 +int alerts_to_json_v2(BUFFER *wb, struct api_v2_alerts_request *req, CONTEXTS_V2_OPTIONS options);
515
516 RRDCONTEXT_TO_JSON_OPTIONS rrdcontext_to_json_parse_options(char *o);
517 void buffer_json_agents_array_v2(BUFFER *wb, struct query_timings *timings, time_t now_s, bool info);
database/rrd.h
+1
@@ -1014,6 +1014,7 @@ struct alarm_entry {
1014 uint32_t unique_id;
1015 uint32_t alarm_id;
1016 uint32_t alarm_event_id;
1017 + usec_t global_id;
1018 uuid_t config_hash_id;
1019 uuid_t transition_id;
1020
database/rrdcalc.c
+2
@@ -281,6 +281,7 @@ static void rrdcalc_link_to_rrdset(RRDSET *st, RRDCALC *rc) {
281 0,
282 rrdcalc_isrepeating(rc)?HEALTH_ENTRY_FLAG_IS_REPEATING:0);
283
284 + rc->ae = ae;
285 health_alarm_log_add_entry(host, ae);
286 }
287
@@ -324,6 +325,7 @@ static void rrdcalc_unlink_from_rrdset(RRDCALC *rc, bool having_ll_wrlock) {
325 0,
326 0);
327
328 + rc->ae = ae;
329 health_alarm_log_add_entry(host, ae);
330 }
331
database/rrdcalc.h
+1
@@ -136,6 +136,7 @@ struct rrdcalc {
136 int delay_up_current; // the current up notification delay duration
137 int delay_down_current; // the current down notification delay duration
138 int delay_last; // the last delay we used
139 + ALARM_ENTRY *ae; // last alarm entry
140
141 // ------------------------------------------------------------------------
142 // variables this alarm exposes to the rest of the alarms
database/sqlite/sqlite_health.c
+135 -4
@@ -90,7 +90,7 @@ failed:
90 #define SQL_INSERT_HEALTH_LOG_DETAIL "INSERT INTO health_log_detail (health_log_id, unique_id, alarm_id, alarm_event_id, " \
91 "updated_by_id, updates_id, when_key, duration, non_clear_duration, flags, exec_run_timestamp, delay_up_to_timestamp, " \
92 "info, exec_code, new_status, old_status, delay, new_value, old_value, last_repeat, transition_id, global_id) " \
93 - "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,now_usec(0)); "
93 + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,@global_id); "
94 void sql_health_alarm_log_insert(RRDHOST *host, ALARM_ENTRY *ae) {
95 sqlite3_stmt *res = NULL;
96 int rc;
@@ -318,6 +318,12 @@ void sql_health_alarm_log_insert(RRDHOST *host, ALARM_ENTRY *ae) {
318 goto failed;
319 }
320
321 + rc = sqlite3_bind_int64(res, 22, (sqlite3_int64) ae->global_id);
322 + if (unlikely(rc != SQLITE_OK)) {
323 + error_report("Failed to bind global_id parameter for SQL_INSERT_HEALTH_LOG_DETAIL");
324 + goto failed;
325 + }
326 +
327 rc = execute_insert(res);
328 if (unlikely(rc != SQLITE_DONE)) {
329 error_report("HEALTH [%s]: Failed to execute SQL_INSERT_HEALTH_LOG_DETAIL, rc = %d", rrdhost_hostname(host), rc);
@@ -740,7 +746,13 @@ void sql_check_removed_alerts_state(RRDHOST *host)
746 /* Health related SQL queries
747 Load from the health log table
748 */
743 -#define SQL_LOAD_HEALTH_LOG "SELECT hld.unique_id, hld.alarm_id, hld.alarm_event_id, hl.config_hash_id, hld.updated_by_id, hld.updates_id, hld.when_key, hld.duration, hld.non_clear_duration, hld.flags, hld.exec_run_timestamp, hld.delay_up_to_timestamp, hl.name, hl.chart, hl.family, hl.exec, hl.recipient, ah.source, hl.units, hld.info, hld.exec_code, hld.new_status, hld.old_status, hld.delay, hld.new_value, hld.old_value, hld.last_repeat, ah.class, ah.component, ah.type, hl.chart_context, hld.transition_id FROM health_log hl, alert_hash ah, health_log_detail hld where hl.config_hash_id = ah.hash_id and hl.host_id = @host_id and hl.last_transition_id = hld.transition_id;"
749 +#define SQL_LOAD_HEALTH_LOG "SELECT hld.unique_id, hld.alarm_id, hld.alarm_event_id, hl.config_hash_id, hld.updated_by_id, " \
750 + "hld.updates_id, hld.when_key, hld.duration, hld.non_clear_duration, hld.flags, hld.exec_run_timestamp, " \
751 + "hld.delay_up_to_timestamp, hl.name, hl.chart, hl.family, hl.exec, hl.recipient, ah.source, hl.units, " \
752 + "hld.info, hld.exec_code, hld.new_status, hld.old_status, hld.delay, hld.new_value, hld.old_value, " \
753 + "hld.last_repeat, ah.class, ah.component, ah.type, hl.chart_context, hld.transition_id, hld.global_id " \
754 + "FROM health_log hl, alert_hash ah, health_log_detail hld " \
755 + "WHERE hl.config_hash_id = ah.hash_id and hl.host_id = @host_id and hl.last_transition_id = hld.transition_id;"
756 void sql_health_alarm_log_load(RRDHOST *host) {
757 sqlite3_stmt *res = NULL;
758 int ret;
@@ -911,8 +923,11 @@ void sql_health_alarm_log_load(RRDHOST *host) {
923 else
924 ae->chart_context = NULL;
925
914 - if (sqlite3_column_type(res, 31) != SQLITE_NULL)
915 - uuid_copy(ae->transition_id, *((uuid_t *) sqlite3_column_blob(res, 31)));
926 + if (sqlite3_column_type(res, 31) != SQLITE_NULL)
927 + uuid_copy(ae->transition_id, *((uuid_t *)sqlite3_column_blob(res, 31)));
928 +
929 + if (sqlite3_column_type(res, 32) != SQLITE_NULL)
930 + ae->global_id = sqlite3_column_int64(res, 32);
931
932 char value_string[100 + 1];
933 string_freez(ae->old_value_string);
@@ -1674,3 +1689,119 @@ uint32_t sql_get_alarm_id(RRDHOST *host, STRING *chart, STRING *name, uint32_t *
1689
1690 return alarm_id;
1691 }
1692 +
1693 +#define SQL_SELECT_HEALTH_LOG_V2 "SELECT unique_id, alarm_event_id, updated_by_id, " \
1694 + "updates_id, when_key, duration, non_clear_duration, flags, exec_run_timestamp, delay_up_to_timestamp, " \
1695 + "exec, recipient, info, exec_code, new_status, old_status, delay, new_value, " \
1696 + "old_value, last_repeat, transition_id, units, d.global_id FROM health_log_detail d, health_log h " \
1697 + "WHERE h.host_id = @host_id AND h.health_log_id = d.health_log_id "
1698 +
1699 +void sql_health_alarm_log2json_v2(RRDHOST *host, BUFFER *wb, uint32_t alert_id, char *chart, time_t after, time_t before, uint32_t max)
1700 +{
1701 + sqlite3_stmt *res = NULL;
1702 + int rc;
1703 +
1704 + BUFFER *command = buffer_create(MAX_HEALTH_SQL_SIZE, NULL);
1705 +
1706 + buffer_sprintf(command, SQL_SELECT_HEALTH_LOG_V2);
1707 +
1708 + if (alert_id)
1709 + buffer_sprintf(command, "AND d.alarm_id = %u ", alert_id);
1710 +
1711 + if (chart)
1712 + buffer_sprintf(command, "AND h.chart = '%s' ", chart);
1713 +
1714 + if (after)
1715 + buffer_sprintf(command, "AND d.when_key >= %ld ", after);
1716 +
1717 + if (before)
1718 + buffer_sprintf(command, "AND d.when_key < %ld ", before);
1719 +
1720 + buffer_sprintf(command, " ORDER BY d.alarm_event_id DESC LIMIT %u", max);
1721 +
1722 + rc = sqlite3_prepare_v2(db_meta, buffer_tostring(command), -1, &res, 0);
1723 + if (unlikely(rc != SQLITE_OK)) {
1724 + error_report("Failed to prepare statement SQL_SELECT_HEALTH_LOG");
1725 + buffer_free(command);
1726 + return;
1727 + }
1728 +
1729 + rc = sqlite3_bind_blob(res, 1, &host->host_uuid, sizeof(host->host_uuid), SQLITE_STATIC);
1730 + if (unlikely(rc != SQLITE_OK))
1731 + error_report("Failed to bind host_id parameter for SQL_GET_ALARM_ID.");
1732 +
1733 + while (sqlite3_step(res) == SQLITE_ROW) {
1734 +
1735 + char old_value_string[100 + 1];
1736 + char new_value_string[100 + 1];
1737 +
1738 + // One alarm
1739 + buffer_json_add_array_item_object(wb);
1740 +
1741 + if (sqlite3_column_type(res, 20) != SQLITE_NULL) {
1742 + char transition_id[UUID_STR_LEN];
1743 + uuid_unparse_lower(*((uuid_t *)sqlite3_column_blob(res, 20)), transition_id);
1744 + buffer_json_member_add_string(wb, "transition_id", transition_id);
1745 + }
1746 + else
1747 + buffer_json_member_add_quoted_string(wb, "transition_id", "null");
1748 +
1749 + buffer_json_member_add_uint64(wb, "gi", (uint64_t) sqlite3_column_int64(res, 22));
1750 +
1751 + uint64_t flags = sqlite3_column_int64(res, 8);
1752 + buffer_json_member_add_uint64 (wb, "utc_offset", host->utc_offset);
1753 + buffer_json_member_add_string (wb, "timezone", rrdhost_abbrev_timezone(host));
1754 + buffer_json_member_add_uint64 (wb, "unique_id", (unsigned int) sqlite3_column_int64(res, 0));
1755 + buffer_json_member_add_uint64 (wb, "alarm_event_id", (unsigned int) sqlite3_column_int64(res, 1));
1756 + buffer_json_member_add_object(wb, "notif_status");
1757 + {
1758 + const char *exec = (const char *) sqlite3_column_text(res, 10);
1759 + const char *recipient = (const char *) sqlite3_column_text(res, 11);
1760 +
1761 + buffer_json_member_add_boolean (wb, "processed", flags & HEALTH_ENTRY_FLAG_PROCESSED);
1762 + buffer_json_member_add_boolean(wb, "updated", flags & HEALTH_ENTRY_FLAG_UPDATED);
1763 + buffer_json_member_add_uint64 (wb, "exec_run", (long unsigned int)sqlite3_column_int64(res, 9));
1764 + buffer_json_member_add_boolean (wb, "exec_failed", flags & HEALTH_ENTRY_FLAG_EXEC_FAILED);
1765 + buffer_json_member_add_string (wb, "exec", exec ? (const char *) exec : string2str(host->health.health_default_exec));
1766 + buffer_json_member_add_string (wb, "recipient", recipient ? recipient : string2str(host->health.health_default_recipient));
1767 + buffer_json_member_add_uint64 (wb, "exec_code", sqlite3_column_int(res, 13));
1768 + }
1769 + buffer_json_object_close(wb); // notif_status
1770 + buffer_json_member_add_uint64 (wb, "when", (long unsigned int)sqlite3_column_int64(res, 4));
1771 + buffer_json_member_add_uint64 (wb, "duration", (long unsigned int)sqlite3_column_int64(res, 5));
1772 + buffer_json_member_add_uint64 (wb, "non_clear_duration", (long unsigned int)sqlite3_column_int64(res, 6));
1773 +
1774 + buffer_json_member_add_string (wb, "status", rrdcalc_status2string(sqlite3_column_int(res, 14)));
1775 + buffer_json_member_add_string (wb, "old_status", rrdcalc_status2string(sqlite3_column_int(res, 15)));
1776 + buffer_json_member_add_uint64 (wb, "delay", sqlite3_column_int(res, 16));
1777 + buffer_json_member_add_uint64 (wb, "delay_up_to_timestamp", (long unsigned int)sqlite3_column_int64(res, 10));
1778 + buffer_json_member_add_uint64 (wb, "updated_by_id", (unsigned int)sqlite3_column_int64(res, 3));
1779 + buffer_json_member_add_uint64 (wb, "updates_id", (unsigned int)sqlite3_column_int64(res, 4));
1780 + buffer_json_member_add_uint64(wb, "last_repeat", (long unsigned int)sqlite3_column_int64(res, 19));
1781 + buffer_json_member_add_boolean (wb, "silenced", flags & HEALTH_ENTRY_FLAG_SILENCED);
1782 + buffer_json_member_add_string (wb, "info", (const char *)sqlite3_column_text(res, 12));
1783 +
1784 + if(unlikely(flags & HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION))
1785 + buffer_json_member_add_boolean (wb, "no_clear_notification", true);
1786 +
1787 + buffer_json_member_add_string (wb, "value_string", sqlite3_column_type(res, 17) == SQLITE_NULL ? "-" : format_value_and_unit(new_value_string, 100, sqlite3_column_double(res, 17), (char *) sqlite3_column_text(res, 21), -1));
1788 + if (sqlite3_column_type(res, 17) == SQLITE_NULL) {
1789 + buffer_json_member_add_quoted_string(wb, "value", "null");
1790 + } else {
1791 + buffer_json_member_add_double(wb, "value", sqlite3_column_double(res, 17));
1792 + }
1793 +
1794 + buffer_json_member_add_string (wb, "old_value_string", sqlite3_column_type(res, 18) == SQLITE_NULL ? "-" : format_value_and_unit(old_value_string, 100, sqlite3_column_double(res, 18), (char *) sqlite3_column_text(res, 21), -1));
1795 + if (sqlite3_column_type(res, 18) == SQLITE_NULL) {
1796 + buffer_json_member_add_quoted_string(wb, "old_value", "null");
1797 + } else
1798 + buffer_json_member_add_double(wb, "old_value", sqlite3_column_double(res, 18));
1799 + buffer_json_object_close(wb);
1800 + }
1801 +
1802 + rc = sqlite3_finalize(res);
1803 + if (unlikely(rc != SQLITE_OK))
1804 + error_report("Failed to finalize statement for SQL_SELECT_HEALTH_LOG");
1805 +
1806 + buffer_free(command);
1807 +}
database/sqlite/sqlite_health.h
+1
@@ -18,4 +18,5 @@ int sql_health_get_last_executed_event(RRDHOST *host, ALARM_ENTRY *ae, RRDCALC_S
18 void sql_health_alarm_log2json(RRDHOST *host, BUFFER *wb, uint32_t after, char *chart);
19 int health_migrate_old_health_log_table(char *table);
20 uint32_t sql_get_alarm_id(RRDHOST *host, STRING *chart, STRING *name, uint32_t *next_event_id);
21 +void sql_health_alarm_log2json_v2(RRDHOST *host, BUFFER *wb, uint32_t alert_id, char *chart, time_t after, time_t before, uint32_t top);
22 #endif //NETDATA_SQLITE_HEALTH_H
health/health.c
+3
@@ -1128,6 +1128,7 @@ void *health_main(void *ptr) {
1128 rc->last_status_change = now;
1129 rc->last_updated = now;
1130 rc->value = NAN;
1131 + rc->ae = ae;
1132
1133 #ifdef ENABLE_ACLK
1134 if (netdata_cloud_enabled)
@@ -1396,6 +1397,7 @@ void *health_main(void *ptr) {
1397 rc->last_status_change = now;
1398 rc->old_status = rc->status;
1399 rc->status = status;
1400 + rc->ae = ae;
1401 }
1402
1403 rc->last_updated = now;
@@ -1473,6 +1475,7 @@ void *health_main(void *ptr) {
1475 ae->flags |= HEALTH_ENTRY_RUN_ONCE;
1476 }
1477 rc->run_flags |= RRDCALC_FLAG_RUN_ONCE;
1478 + rc->ae = ae;
1479 health_process_notifications(host, ae);
1480 debug(D_HEALTH, "Notification sent for the repeating alarm %u.", ae->alarm_id);
1481 health_alarm_wait_for_execution(ae);
health/health.h
+2
@@ -40,6 +40,8 @@ void health_reload(void);
40
41 void health_aggregate_alarms(RRDHOST *host, BUFFER *wb, BUFFER* context, RRDCALC_STATUS status);
42 void health_alarms2json(RRDHOST *host, BUFFER *wb, int all);
43 +void health_alert2json(RRDHOST *host, BUFFER *wb, ALERT_OPTIONS all, Pvoid_t JudyHS, time_t after, time_t before, uint32_t top);
44 +void health_alert2json_conf(RRDHOST *host, BUFFER *wb, ALERT_OPTIONS all);
45 void health_alarms_values2json(RRDHOST *host, BUFFER *wb, int all);
46
47 void health_api_v1_chart_variables2json(RRDSET *st, BUFFER *buf);
health/health_json.c
+89
@@ -167,6 +167,64 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
167 buffer_strcat(wb, "\t\t}");
168 }
169
170 +
171 +static inline void health_alerts_rrdcalc2json_nolock(RRDHOST *host __maybe_unused, BUFFER *wb,
172 + RRDCALC *rc, ALERT_OPTIONS options __maybe_unused,
173 + Pvoid_t JudyHS, time_t after, time_t before, uint32_t top)
174 +{
175 + ssize_t idx= get_alert_index(JudyHS, &rc->config_hash_id);
176 + // If not in index then skip it
177 + if (idx < 0)
178 + return;
179 +
180 + char value_string[100 + 1];
181 + format_value_and_unit(value_string, 100, rc->value, rrdcalc_units(rc), -1);
182 +
183 +
184 + char hash_id[UUID_STR_LEN];
185 + uuid_unparse_lower(rc->config_hash_id, hash_id);
186 +
187 + buffer_json_add_array_item_object(wb);
188 + if ((!after || after <= rc->last_updated) && (!before || before >= rc->last_updated)) {
189 +
190 + buffer_json_member_add_uint64(wb, "li", (size_t) idx);
191 +
192 + char trans_uuid_str[UUID_STR_LEN];
193 + if (rc->ae) {
194 + uuid_unparse_lower(rc->ae->transition_id, trans_uuid_str);
195 + buffer_json_member_add_string(wb, "transition_id", trans_uuid_str);
196 + buffer_json_member_add_uint64(wb, "gi", rc->ae->global_id);
197 + }
198 + else {
199 + buffer_json_member_add_quoted_string(wb, "transition_id", "NULL");
200 + buffer_json_member_add_quoted_string(wb, "gi", "NULL");
201 + }
202 +
203 + buffer_json_member_add_string(wb, "status", rrdcalc_status2string(rc->status));
204 + buffer_json_member_add_uint64(wb, "last_status_change", (unsigned long)rc->last_status_change);
205 + buffer_json_member_add_uint64(wb, "last_updated", (unsigned long)rc->last_updated);
206 + buffer_json_member_add_uint64(wb, "next_update", (unsigned long)rc->next_update);
207 + buffer_json_member_add_uint64(wb, "delay_up_to_timestamp", (unsigned long)rc->delay_up_to_timestamp);
208 +
209 + buffer_json_member_add_string(wb, "value_string", value_string);
210 + buffer_json_member_add_uint64(wb, "last_repeat", (unsigned long)rc->last_repeat);
211 + buffer_json_member_add_uint64(wb, "times_repeat", (unsigned long)rc->times_repeat);
212 + buffer_json_member_add_uint64(wb, "db_after", (unsigned long)rc->db_after);
213 + buffer_json_member_add_uint64(wb, "db_before", (unsigned long)rc->db_before);
214 +
215 + buffer_json_member_add_double(wb, "green", rc->green);
216 + buffer_json_member_add_double(wb, "red", rc->red);
217 + buffer_json_member_add_double(wb, "value", rc->value);
218 +
219 + if (options & ALERT_OPTION_INSTANCES) {
220 + buffer_json_member_add_array(wb, "transitions");
221 + sql_health_alarm_log2json_v2(host, wb, rc->id, NULL, after, before, top);
222 + buffer_json_array_close(wb);
223 + }
224 + }
225 + buffer_json_object_close(wb); // array entry
226 +}
227 +
228 //void health_rrdcalctemplate2json_nolock(BUFFER *wb, RRDCALCTEMPLATE *rt) {
229 //
230 //}
@@ -234,6 +292,37 @@ static void health_alarms2json_fill_alarms(RRDHOST *host, BUFFER *wb, int all, v
292 foreach_rrdcalc_in_rrdhost_done(rc);
293 }
294
295 +static void health_alerts2json_fill_alarms(
296 + RRDHOST *host,
297 + BUFFER *wb,
298 + ALERT_OPTIONS all,
299 + Pvoid_t JudyHS,
300 + time_t after,
301 + time_t before,
302 + uint32_t top,
303 + void (*fp)(RRDHOST *, BUFFER *, RRDCALC *, ALERT_OPTIONS, Pvoid_t , time_t, time_t, uint32_t))
304 +{
305 + RRDCALC *rc;
306 + foreach_rrdcalc_in_rrdhost_read(host, rc) {
307 + if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec))
308 + continue;
309 +
310 + if (unlikely(!rrdset_is_available_for_exporting_and_alarms(rc->rrdset)))
311 + continue;
312 +
313 + if(likely((all & ALERT_OPTION_ACTIVE) && !(rc->status == RRDCALC_STATUS_WARNING || rc->status == RRDCALC_STATUS_CRITICAL)))
314 + continue;
315 +
316 + fp(host, wb, rc, all, JudyHS, after, before, top);
317 + }
318 + foreach_rrdcalc_in_rrdhost_done(rc);
319 +}
320 +
321 +void health_alert2json(RRDHOST *host, BUFFER *wb, ALERT_OPTIONS options, Pvoid_t JudyHS, time_t after, time_t before, uint32_t top)
322 +{
323 + health_alerts2json_fill_alarms(host, wb, options, JudyHS, after, before, top, health_alerts_rrdcalc2json_nolock);
324 +}
325 +
326 void health_alarms2json(RRDHOST *host, BUFFER *wb, int all) {
327 buffer_sprintf(wb, "{\n\t\"hostname\": \"%s\","
328 "\n\t\"latest_alarm_log_unique_id\": %u,"
health/health_log.c
+1
@@ -47,6 +47,7 @@ inline ALARM_ENTRY* health_create_alarm_entry(
47 uuid_copy(ae->config_hash_id, *((uuid_t *) config_hash_id));
48
49 uuid_generate_random(ae->transition_id);
50 + ae->global_id = now_realtime_usec();
51
52 ae->family = string_dup(family);
53 ae->classification = string_dup(class);
web/api/queries/rrdr.h
+8
@@ -51,6 +51,14 @@ typedef enum rrdr_options {
51 RRDR_OPTION_INTERNAL_AR = (1 << 31), // internal use only, to let the formatters know we want to render the anomaly rate
52 } RRDR_OPTIONS;
53
54 +typedef enum alert_options {
55 + ALERT_OPTION_MINIFY = (1 << 0), // remove JSON spaces and newlines from JSON output
56 + ALERT_OPTION_ACTIVE = (1 << 1), // Only active alerts
57 + ALERT_OPTION_CONFIG = (1 << 2), // Include config
58 + ALERT_OPTION_TRANSITIONS = (1 << 3), // Include transitions
59 + ALERT_OPTION_INSTANCES = (1 << 4), // Include alert instances
60 +} ALERT_OPTIONS;
61 +
62 typedef enum __attribute__ ((__packed__)) rrdr_value_flag {
63
64 // IMPORTANT:
web/api/web_api_v1.c
+36
@@ -50,6 +50,19 @@ static struct {
50 , {NULL , 0 , 0}
51 };
52
53 +static struct {
54 + const char *name;
55 + uint32_t hash;
56 + ALERT_OPTIONS value;
57 +} api_v2_alert_options[] = {
58 + {"minify" , 0 , ALERT_OPTION_MINIFY}
59 + , {"active" , 0 , ALERT_OPTION_ACTIVE}
60 + , {"config" , 0 , ALERT_OPTION_CONFIG}
61 + , {"transitions" , 0 , ALERT_OPTION_TRANSITIONS}
62 + , {"instances" , 0 , ALERT_OPTION_INSTANCES}
63 + , {NULL , 0 , 0}
64 +};
65 +
66 static struct {
67 const char *name;
68 uint32_t hash;
@@ -94,6 +107,9 @@ void web_client_api_v1_init(void) {
107 for(i = 0; api_v1_data_options[i].name ; i++)
108 api_v1_data_options[i].hash = simple_hash(api_v1_data_options[i].name);
109
110 + for(i = 0; api_v2_alert_options[i].name ; i++)
111 + api_v2_alert_options[i].hash = simple_hash(api_v2_alert_options[i].name);
112 +
113 for(i = 0; api_v1_data_formats[i].name ; i++)
114 api_v1_data_formats[i].hash = simple_hash(api_v1_data_formats[i].name);
115
@@ -194,6 +210,26 @@ inline RRDR_OPTIONS web_client_api_request_v1_data_options(char *o) {
210 return ret;
211 }
212
213 +inline ALERT_OPTIONS web_client_api_request_v2_alert_options(char *o) {
214 + ALERT_OPTIONS ret = 0x00000000;
215 + char *tok;
216 +
217 + while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) {
218 + if(!*tok) continue;
219 +
220 + uint32_t hash = simple_hash(tok);
221 + int i;
222 + for(i = 0; api_v2_alert_options[i].name ; i++) {
223 + if (unlikely(hash == api_v2_alert_options[i].hash && !strcmp(tok, api_v2_alert_options[i].name))) {
224 + ret |= api_v2_alert_options[i].value;
225 + break;
226 + }
227 + }
228 + }
229 +
230 + return ret;
231 +}
232 +
233 void web_client_api_request_v1_data_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options) {
234 buffer_json_member_add_array(wb, key);
235
web/api/web_api_v1.h
+1
@@ -8,6 +8,7 @@
8 struct web_client;
9
10 RRDR_OPTIONS web_client_api_request_v1_data_options(char *o);
11 +ALERT_OPTIONS web_client_api_request_v2_alert_options(char *o);
12 void web_client_api_request_v1_data_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options);
13 void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options);
14
web/api/web_api_v2.c
+79
@@ -358,6 +358,84 @@ cleanup:
358 return ret;
359 }
360
361 +static int web_client_api_request_v2_alerts(RRDHOST *host __maybe_unused, struct web_client *w, char *url)
362 +{
363 + time_t after = 0;
364 + time_t before = 0;
365 + struct api_v2_alerts_request req = { 0 };
366 +
367 + CONTEXTS_V2_OPTIONS options = CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES;
368 + ALERT_OPTIONS alert_options = 0;
369 +
370 + while(url) {
371 + char *value = strsep_skip_consecutive_separators(&url, "&");
372 + if(!value || !*value) continue;
373 +
374 + char *name = strsep_skip_consecutive_separators(&value, "=");
375 + if(!name || !*name) continue;
376 + if(!value || !*value) continue;
377 +
378 + // name and value are now the parameters
379 + // they are not null and not empty
380 +
381 + if (!strcmp(name, "scope_nodes")) {
382 + req.scope_nodes = value;
383 + options |= CONTEXTS_V2_NODES;
384 + }
385 + else if (!strcmp(name, "nodes")) {
386 + req.nodes = value;
387 + options |= CONTEXTS_V2_NODES;
388 + }
389 + else if (!strcmp(name, "scope_contexts")) {
390 + req.scope_contexts = value;
391 + options |= CONTEXTS_V2_CONTEXTS;
392 + }
393 + else if (!strcmp(name, "instance_id"))
394 + req.alert_id = (time_t)strtoul(value, NULL, 0);
395 + else if (!strcmp(name, "last"))
396 + req.last = strtoul(value, NULL, 0);
397 + else if (!strcmp(name, "transition_id")) {
398 + req.transition_id = value;
399 + } else if (!strcmp(name, "alert_name")) {
400 + req.alert_name = value;
401 + req.alert_name_pattern = string_to_simple_pattern(value);
402 + } else if (!strcmp(name, "config_hash")) {
403 + req.config_hash = value;
404 + req.config_hash_pattern = string_to_simple_pattern(value);
405 + } else if (!strcmp(name, "contexts")) {
406 + req.contexts = value;
407 + options |= CONTEXTS_V2_CONTEXTS;
408 + } else if (!strcmp(name, "state")) {
409 + req.state = value; // all | warning | critical, clear, undefined, uninitialiazed
410 + } else if (!strcmp(name, "q"))
411 + req.q = value;
412 + else if (!strcmp(name, "after"))
413 + after = (time_t)strtoul(value, NULL, 0);
414 + else if (!strcmp(name, "before"))
415 + before = (time_t)strtoul(value, NULL, 0);
416 + else if (!strcmp(name, "output")) {
417 + // config, instances, transitions
418 + alert_options |= web_client_api_request_v2_alert_options(value);
419 + }
420 +
421 + // TODO: All states and special "ACTIVE"
422 + }
423 +
424 + buffer_flush(w->response.data);
425 + buffer_no_cacheable(w->response.data);
426 + w->response.data->content_type = CT_APPLICATION_JSON;
427 + buffer_json_initialize( w->response.data, "\"", "\"", 0, true, alert_options & ALERT_OPTION_MINIFY);
428 +
429 + if (alert_options & ALERT_OPTION_INSTANCES && !req.last)
430 + req.last = 1;
431 +
432 + req.options = alert_options;
433 + req.after = after;
434 + req.before = before;
435 +
436 + return alerts_to_json_v2(w->response.data, &req, options);
437 +}
438 +
439 static int web_client_api_request_v2_webrtc(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) {
440 return webrtc_new_connection(w->post_payload, w->response.data);
441 }
@@ -373,6 +451,7 @@ static struct web_api_command api_commands_v2[] = {
451 {"q", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q},
452
453 {"rtc_offer", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v2_webrtc},
454 + {"alerts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alerts},
455
456 // terminator
457 {NULL, 0, WEB_CLIENT_ACL_NONE, NULL},