master
c 502 lines 19.7 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "api_v2_contexts_alerts.h"
4 #include "libnetdata/json/json-keys.h"
5
6 struct alert_transitions_facets alert_transition_facets[] = {
7 [ATF_STATUS] = {
8 .id = "f_status",
9 .name = "Alert Status",
10 .query_param = "f_status",
11 .order = 1,
12 },
13 [ATF_TYPE] = {
14 .id = "f_type",
15 .name = "Alert Type",
16 .query_param = "f_type",
17 .order = 2,
18 },
19 [ATF_ROLE] = {
20 .id = "f_role",
21 .name = "Recipient Role",
22 .query_param = "f_role",
23 .order = 3,
24 },
25 [ATF_CLASS] = {
26 .id = "f_class",
27 .name = "Alert Class",
28 .query_param = "f_class",
29 .order = 4,
30 },
31 [ATF_COMPONENT] = {
32 .id = "f_component",
33 .name = "Alert Component",
34 .query_param = "f_component",
35 .order = 5,
36 },
37 [ATF_NODE] = {
38 .id = "f_node",
39 .name = "Alert Node",
40 .query_param = "f_node",
41 .order = 6,
42 },
43 [ATF_ALERT_NAME] = {
44 .id = "f_alert",
45 .name = "Alert Name",
46 .query_param = "f_alert",
47 .order = 7,
48 },
49 [ATF_CHART_NAME] = {
50 .id = "f_instance",
51 .name = "Instance Name",
52 .query_param = "f_instance",
53 .order = 8,
54 },
55 [ATF_CONTEXT] = {
56 .id = "f_context",
57 .name = "Context",
58 .query_param = "f_context",
59 .order = 9,
60 },
61
62 // terminator
63 [ATF_TOTAL_ENTRIES] = {
64 .id = NULL,
65 .name = NULL,
66 .query_param = NULL,
67 .order = 9999,
68 }
69 };
70
71 #define SQL_TRANSITION_DATA_SMALL_STRING (6 * 8)
72 #define SQL_TRANSITION_DATA_MEDIUM_STRING (12 * 8)
73 #define SQL_TRANSITION_DATA_BIG_STRING 512
74
75 struct sql_alert_transition_fixed_size {
76 usec_t global_id;
77 nd_uuid_t transition_id;
78 nd_uuid_t host_id;
79 nd_uuid_t config_hash_id;
80 uint32_t alarm_id;
81 char alert_name[SQL_TRANSITION_DATA_SMALL_STRING];
82 char chart[RRD_ID_LENGTH_MAX];
83 char chart_name[RRD_ID_LENGTH_MAX];
84 char chart_context[SQL_TRANSITION_DATA_MEDIUM_STRING];
85 char family[SQL_TRANSITION_DATA_SMALL_STRING];
86 char recipient[SQL_TRANSITION_DATA_MEDIUM_STRING];
87 char units[SQL_TRANSITION_DATA_SMALL_STRING];
88 char exec[SQL_TRANSITION_DATA_BIG_STRING];
89 char info[SQL_TRANSITION_DATA_BIG_STRING];
90 char summary[SQL_TRANSITION_DATA_BIG_STRING];
91 char classification[SQL_TRANSITION_DATA_SMALL_STRING];
92 char type[SQL_TRANSITION_DATA_SMALL_STRING];
93 char component[SQL_TRANSITION_DATA_SMALL_STRING];
94 time_t when_key;
95 time_t duration;
96 time_t non_clear_duration;
97 uint64_t flags;
98 time_t delay_up_to_timestamp;
99 time_t exec_run_timestamp;
100 int exec_code;
101 int new_status;
102 int old_status;
103 int delay;
104 time_t last_repeat;
105 NETDATA_DOUBLE new_value;
106 NETDATA_DOUBLE old_value;
107
108 char machine_guid[UUID_STR_LEN];
109 struct sql_alert_transition_fixed_size *next;
110 struct sql_alert_transition_fixed_size *prev;
111 };
112
113 struct facet_entry {
114 uint32_t count;
115 };
116
117 static struct sql_alert_transition_fixed_size *contexts_v2_alert_transition_dup(struct sql_alert_transition_data *t, const char *machine_guid, struct sql_alert_transition_fixed_size *dst) {
118 struct sql_alert_transition_fixed_size *n = dst ? dst : mallocz(sizeof(*n));
119
120 n->global_id = t->global_id;
121 uuid_copy(n->transition_id, *t->transition_id);
122 uuid_copy(n->host_id, *t->host_id);
123 uuid_copy(n->config_hash_id, *t->config_hash_id);
124 n->alarm_id = t->alarm_id;
125 strncpyz(n->alert_name, t->alert_name ? t->alert_name : "", sizeof(n->alert_name) - 1);
126 strncpyz(n->chart, t->chart ? t->chart : "", sizeof(n->chart) - 1);
127 strncpyz(n->chart_name, t->chart_name ? t->chart_name : n->chart, sizeof(n->chart_name) - 1);
128 strncpyz(n->chart_context, t->chart_context ? t->chart_context : "", sizeof(n->chart_context) - 1);
129 strncpyz(n->family, t->family ? t->family : "", sizeof(n->family) - 1);
130 strncpyz(n->recipient, t->recipient ? t->recipient : "", sizeof(n->recipient) - 1);
131 strncpyz(n->units, t->units ? t->units : "", sizeof(n->units) - 1);
132 strncpyz(n->exec, t->exec ? t->exec : "", sizeof(n->exec) - 1);
133 strncpyz(n->info, t->info ? t->info : "", sizeof(n->info) - 1);
134 strncpyz(n->summary, t->summary ? t->summary : "", sizeof(n->summary) - 1);
135 strncpyz(n->classification, t->classification ? t->classification : "", sizeof(n->classification) - 1);
136 strncpyz(n->type, t->type ? t->type : "", sizeof(n->type) - 1);
137 strncpyz(n->component, t->component ? t->component : "", sizeof(n->component) - 1);
138 n->when_key = t->when_key;
139 n->duration = t->duration;
140 n->non_clear_duration = t->non_clear_duration;
141 n->flags = t->flags;
142 n->delay_up_to_timestamp = t->delay_up_to_timestamp;
143 n->exec_run_timestamp = t->exec_run_timestamp;
144 n->exec_code = t->exec_code;
145 n->new_status = t->new_status;
146 n->old_status = t->old_status;
147 n->delay = t->delay;
148 n->last_repeat = t->last_repeat;
149 n->new_value = t->new_value;
150 n->old_value = t->old_value;
151
152 memcpy(n->machine_guid, machine_guid, sizeof(n->machine_guid));
153 n->next = n->prev = NULL;
154
155 return n;
156 }
157
158 static void contexts_v2_alert_transition_free(struct sql_alert_transition_fixed_size *t) {
159 freez(t);
160 }
161
162 static inline void contexts_v2_alert_transition_keep(struct alert_transitions_callback_data *d, struct sql_alert_transition_data *t, const char *machine_guid) {
163 d->items_matched++;
164
165 if(unlikely(t->global_id <= d->ctl->request->alerts.global_id_anchor)) {
166 // this is in our past, we are not interested
167 d->operations.skips_before++;
168 return;
169 }
170
171 if(unlikely(!d->base)) {
172 d->last_added = contexts_v2_alert_transition_dup(t, machine_guid, NULL);
173 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(d->base, d->last_added, prev, next);
174 d->items_to_return++;
175 d->operations.first++;
176 return;
177 }
178
179 struct sql_alert_transition_fixed_size *last = d->last_added;
180 while(last->prev != d->base->prev && t->global_id > last->prev->global_id) {
181 last = last->prev;
182 d->operations.backwards++;
183 }
184
185 while(last->next && t->global_id < last->next->global_id) {
186 last = last->next;
187 d->operations.forwards++;
188 }
189
190 if(d->items_to_return >= d->max_items_to_return) {
191 if(last == d->base->prev && t->global_id < last->global_id) {
192 d->operations.skips_after++;
193 return;
194 }
195 }
196
197 d->items_to_return++;
198
199 if(t->global_id > last->global_id) {
200 if(d->items_to_return > d->max_items_to_return) {
201 d->items_to_return--;
202 d->operations.shifts++;
203 d->last_added = d->base->prev;
204 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(d->base, d->last_added, prev, next);
205 d->last_added = contexts_v2_alert_transition_dup(t, machine_guid, d->last_added);
206 }
207 DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(d->base, d->last_added, prev, next);
208 d->operations.prepend++;
209 }
210 else {
211 d->last_added = contexts_v2_alert_transition_dup(t, machine_guid, NULL);
212 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(d->base, d->last_added, prev, next);
213 d->operations.append++;
214 }
215
216 while(d->items_to_return > d->max_items_to_return) {
217 // we have to remove something
218
219 struct sql_alert_transition_fixed_size *tmp = d->base->prev;
220 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(d->base, tmp, prev, next);
221 d->items_to_return--;
222
223 if(unlikely(d->last_added == tmp))
224 d->last_added = d->base;
225
226 contexts_v2_alert_transition_free(tmp);
227
228 d->operations.shifts++;
229 }
230 }
231
232 static void contexts_v2_alert_transition_callback(struct sql_alert_transition_data *t, void *data) {
233 struct alert_transitions_callback_data *d = data;
234 d->items_evaluated++;
235
236 char machine_guid[UUID_STR_LEN] = "";
237 uuid_unparse_lower(*t->host_id, machine_guid);
238
239 const char *facets[ATF_TOTAL_ENTRIES] = {
240 [ATF_STATUS] = rrdcalc_status2string(t->new_status),
241 [ATF_CLASS] = t->classification,
242 [ATF_TYPE] = t->type,
243 [ATF_COMPONENT] = t->component,
244 [ATF_ROLE] = t->recipient && *t->recipient ? t->recipient : string2str(localhost->health.default_recipient),
245 [ATF_NODE] = machine_guid,
246 [ATF_ALERT_NAME] = t->alert_name,
247 [ATF_CHART_NAME] = t->chart_name,
248 [ATF_CONTEXT] = t->chart_context,
249 };
250
251 for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
252 if (!facets[i] || !*facets[i]) facets[i] = "unknown";
253
254 struct facet_entry tmp = {
255 .count = 0,
256 };
257 dictionary_set(d->facets[i].dict, facets[i], &tmp, sizeof(tmp));
258 }
259
260 bool selected[ATF_TOTAL_ENTRIES] = { 0 };
261
262 uint32_t selected_by = 0;
263 for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
264 selected[i] = !d->facets[i].pattern || simple_pattern_matches(d->facets[i].pattern, facets[i]);
265 if(selected[i])
266 selected_by++;
267 }
268
269 if(selected_by == ATF_TOTAL_ENTRIES) {
270 // this item is selected by all facets
271 // put it in our result (if it fits)
272 contexts_v2_alert_transition_keep(d, t, machine_guid);
273 }
274
275 if(selected_by >= ATF_TOTAL_ENTRIES - 1) {
276 // this item is selected by all, or all except one facet
277 // in both cases we need to add it to our counters
278
279 for (size_t i = 0; i < ATF_TOTAL_ENTRIES; i++) {
280 uint32_t counted_by = selected_by;
281
282 if (counted_by != ATF_TOTAL_ENTRIES) {
283 counted_by = 0;
284 for (size_t j = 0; j < ATF_TOTAL_ENTRIES; j++) {
285 if (i == j || selected[j])
286 counted_by++;
287 }
288 }
289
290 if (counted_by == ATF_TOTAL_ENTRIES) {
291 // we need to count it on this facet
292 struct facet_entry *x = dictionary_get(d->facets[i].dict, facets[i]);
293 internal_fatal(!x, "facet is not found");
294 if(x)
295 x->count++;
296 }
297 }
298 }
299 }
300
301 void contexts_v2_alert_transitions_to_json(BUFFER *wb, struct rrdcontext_to_json_v2_data *ctl, bool debug) {
302 struct alert_transitions_callback_data data = {
303 .wb = wb,
304 .ctl = ctl,
305 .debug = debug,
306 .only_one_config = true,
307 .max_items_to_return = ctl->request->alerts.last,
308 .items_to_return = 0,
309 .base = NULL,
310 };
311
312 for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
313 data.facets[i].dict = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_FIXED_SIZE | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, sizeof(struct facet_entry));
314 if(ctl->request->alerts.facets[i])
315 data.facets[i].pattern = simple_pattern_create(ctl->request->alerts.facets[i], SIMPLE_PATTERN_DEFAULT_WEB_SEPARATORS, SIMPLE_PATTERN_EXACT, false);
316 }
317
318 sql_alert_transitions(
319 ctl->nodes.dict,
320 ctl->window.after,
321 ctl->window.before,
322 ctl->request->contexts,
323 ctl->request->alerts.alert,
324 ctl->request->alerts.transition,
325 contexts_v2_alert_transition_callback,
326 &data,
327 debug);
328
329 if(!(ctl->request->options & CONTEXTS_OPTION_MCP)) {
330 buffer_json_member_add_array(wb, "facets");
331 for (size_t i = 0; i < ATF_TOTAL_ENTRIES; i++) {
332 buffer_json_add_array_item_object(wb);
333 {
334 buffer_json_member_add_string(wb, "id", alert_transition_facets[i].id);
335 buffer_json_member_add_string(wb, "name", alert_transition_facets[i].name);
336 buffer_json_member_add_uint64(wb, "order", alert_transition_facets[i].order);
337 buffer_json_member_add_array(wb, "options");
338 {
339 struct facet_entry *x;
340 dfe_start_read(data.facets[i].dict, x)
341 {
342 buffer_json_add_array_item_object(wb);
343 {
344 buffer_json_member_add_string(wb, "id", x_dfe.name);
345 if (i == ATF_NODE) {
346 RRDHOST *host = rrdhost_find_by_guid(x_dfe.name);
347 if (host)
348 buffer_json_member_add_string(wb, "name", rrdhost_hostname(host));
349 else
350 buffer_json_member_add_string(wb, "name", x_dfe.name);
351 } else
352 buffer_json_member_add_string(wb, "name", x_dfe.name);
353 buffer_json_member_add_uint64(wb, "count", x->count);
354 }
355 buffer_json_object_close(wb);
356 }
357 dfe_done(x);
358 }
359 buffer_json_array_close(wb); // options
360 }
361 buffer_json_object_close(wb); // facet
362 }
363 buffer_json_array_close(wb); // facets
364 }
365
366 buffer_json_member_add_array(wb, "transitions");
367 for(struct sql_alert_transition_fixed_size *t = data.base; t ; t = t->next) {
368 buffer_json_add_array_item_object(wb);
369 {
370 RRDHOST *host = rrdhost_find_by_guid(t->machine_guid);
371
372 buffer_json_member_add_uint64(wb, JSKEY(alert_global_id), t->global_id);
373 buffer_json_member_add_string(wb, "alert", *t->alert_name ? t->alert_name : NULL);
374
375 if(!(ctl->options & CONTEXTS_OPTION_MCP)) {
376 buffer_json_member_add_uuid(wb, "transition_id", t->transition_id);
377 buffer_json_member_add_string(wb, "machine_guid", t->machine_guid);
378
379 if(host && !UUIDiszero(host->node_id))
380 buffer_json_member_add_uuid(wb, "node_id", host->node_id.uuid);
381 }
382
383 buffer_json_member_add_uuid(wb, "config_hash_id", t->config_hash_id);
384
385 if(host) {
386 buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(host));
387 }
388
389 if(!(ctl->options & CONTEXTS_OPTION_MCP)) {
390 buffer_json_member_add_string(wb, "instance", *t->chart ? t->chart : NULL);
391 buffer_json_member_add_string(wb, "instance_n", *t->chart_name ? t->chart_name : NULL);
392 }
393 else
394 buffer_json_member_add_string(wb, "instance", *t->chart_name ? t->chart_name : NULL);
395
396 buffer_json_member_add_string(wb, "context", *t->chart_context ? t->chart_context : NULL);
397 // buffer_json_member_add_string(wb, "family", *t->family ? t->family : NULL);
398 buffer_json_member_add_string(wb, "component", *t->component ? t->component : NULL);
399 buffer_json_member_add_string(wb, "classification", *t->classification ? t->classification : NULL);
400 buffer_json_member_add_string(wb, "type", *t->type ? t->type : NULL);
401
402 buffer_json_member_add_time_t_formatted(wb, "when", t->when_key, ctl->options & CONTEXTS_OPTION_RFC3339);
403 buffer_json_member_add_string(wb, "info", *t->info ? t->info : "");
404 buffer_json_member_add_string(wb, "summary", *t->summary ? t->summary : "");
405 buffer_json_member_add_string(wb, "units", *t->units ? t->units : NULL);
406
407 buffer_json_member_add_object(wb, "new");
408 {
409 buffer_json_member_add_string(wb, "status", rrdcalc_status2string(t->new_status));
410 buffer_json_member_add_double(wb, "value", t->new_value);
411 }
412 buffer_json_object_close(wb); // new
413
414 buffer_json_member_add_object(wb, "old");
415 {
416 buffer_json_member_add_string(wb, "status", rrdcalc_status2string(t->old_status));
417 buffer_json_member_add_double(wb, "value", t->old_value);
418 buffer_json_member_add_time_t(wb, "duration", t->duration);
419 buffer_json_member_add_time_t(wb, "raised_duration", t->non_clear_duration);
420 }
421 buffer_json_object_close(wb); // old
422
423 buffer_json_member_add_object(wb, "notification");
424 {
425 buffer_json_member_add_time_t_formatted(wb, "when", t->exec_run_timestamp, ctl->options & CONTEXTS_OPTION_RFC3339);
426 buffer_json_member_add_time_t(wb, "delay", t->delay);
427 buffer_json_member_add_time_t_formatted(wb, "delay_up_to_time", t->delay_up_to_timestamp, ctl->options & CONTEXTS_OPTION_RFC3339);
428 health_entry_flags_to_json_array(wb, "flags", t->flags);
429 buffer_json_member_add_string(wb, "exec", *t->exec ? t->exec : string2str(localhost->health.default_exec));
430 buffer_json_member_add_uint64(wb, "exec_code", t->exec_code);
431 buffer_json_member_add_string(wb, "to", *t->recipient ? t->recipient : string2str(localhost->health.default_recipient));
432 }
433 buffer_json_object_close(wb); // notification
434 }
435 buffer_json_object_close(wb); // a transition
436 }
437 buffer_json_array_close(wb); // all transitions
438
439 if(ctl->options & CONTEXTS_OPTION_CONFIGURATIONS) {
440 DICTIONARY *configs = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
441
442 for(struct sql_alert_transition_fixed_size *t = data.base; t ; t = t->next) {
443 char guid[UUID_STR_LEN];
444 uuid_unparse_lower(t->config_hash_id, guid);
445 dictionary_set(configs, guid, NULL, 0);
446 }
447
448 buffer_json_member_add_array(wb, "configurations");
449 sql_get_alert_configuration(configs, contexts_v2_alert_config_to_json_from_sql_alert_config_data, &data, debug);
450 buffer_json_array_close(wb);
451
452 dictionary_destroy(configs);
453 }
454
455 while(data.base) {
456 struct sql_alert_transition_fixed_size *t = data.base;
457 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(data.base, t, prev, next);
458 contexts_v2_alert_transition_free(t);
459 }
460
461 for(size_t i = 0; i < ATF_TOTAL_ENTRIES ;i++) {
462 dictionary_destroy(data.facets[i].dict);
463 simple_pattern_free(data.facets[i].pattern);
464 }
465
466 buffer_json_member_add_object(wb, "items");
467 {
468 // all the items in the window, under the scope_nodes, ignoring the facets (filters)
469 buffer_json_member_add_uint64(wb, "evaluated", data.items_evaluated);
470
471 // all the items matching the query (if you didn't put anchor_gi and last, these are all the items you would get back)
472 buffer_json_member_add_uint64(wb, "matched", data.items_matched);
473
474 // the items included in this response
475 buffer_json_member_add_uint64(wb, "returned", data.items_to_return);
476
477 // same as last=X parameter
478 buffer_json_member_add_uint64(wb, "max_to_return", data.max_items_to_return);
479
480 // items before the first returned, this should be 0 if anchor_gi is not set
481 buffer_json_member_add_uint64(wb, "before", data.operations.skips_before);
482
483 // items after the last returned, when this is zero there aren't any items after the current list
484 buffer_json_member_add_uint64(wb, "after", data.operations.skips_after + data.operations.shifts);
485 }
486 buffer_json_object_close(wb); // items
487
488 if(debug) {
489 buffer_json_member_add_object(wb, "stats");
490 {
491 buffer_json_member_add_uint64(wb, "first", data.operations.first);
492 buffer_json_member_add_uint64(wb, "prepend", data.operations.prepend);
493 buffer_json_member_add_uint64(wb, "append", data.operations.append);
494 buffer_json_member_add_uint64(wb, "backwards", data.operations.backwards);
495 buffer_json_member_add_uint64(wb, "forwards", data.operations.forwards);
496 buffer_json_member_add_uint64(wb, "shifts", data.operations.shifts);
497 buffer_json_member_add_uint64(wb, "skips_before", data.operations.skips_before);
498 buffer_json_member_add_uint64(wb, "skips_after", data.operations.skips_after);
499 }
500 buffer_json_object_close(wb);
501 }
502 }