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},