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