diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-11-09 08:36:11 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-11-09 08:37:11 +0000 |
commit | 910c794ec6d0a364b4aabccf22b715cb45780e83 (patch) | |
tree | 561a9ef6b6a4668102674e1a52b3e7563c57ac61 /src/libnetdata/facets | |
parent | Releasing debian version 1.47.5-1. (diff) | |
download | netdata-debian.tar.xz netdata-debian.zip |
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libnetdata/facets')
-rw-r--r-- | src/libnetdata/facets/facets.c | 277 | ||||
-rw-r--r-- | src/libnetdata/facets/facets.h | 35 | ||||
-rw-r--r-- | src/libnetdata/facets/logs_query_status.h | 868 |
3 files changed, 1120 insertions, 60 deletions
diff --git a/src/libnetdata/facets/facets.c b/src/libnetdata/facets/facets.c index 3c746cbc..230e03de 100644 --- a/src/libnetdata/facets/facets.c +++ b/src/libnetdata/facets/facets.c @@ -102,25 +102,25 @@ static inline bool is_valid_string_hash(const char *s) { // hashtable for FACET_VALUE // cleanup hashtable defines -#include "../../libnetdata/simple_hashtable_undef.h" +#include "../simple_hashtable/simple_hashtable_undef.h" struct facet_value; // #define SIMPLE_HASHTABLE_SORT_FUNCTION compare_facet_value #define SIMPLE_HASHTABLE_VALUE_TYPE struct facet_value #define SIMPLE_HASHTABLE_NAME _VALUE -#include "../simple_hashtable.h" +#include "../simple_hashtable/simple_hashtable.h" // ---------------------------------------------------------------------------- // hashtable for FACET_KEY // cleanup hashtable defines -#include "../../libnetdata/simple_hashtable_undef.h" +#include "../simple_hashtable/simple_hashtable_undef.h" struct facet_key; // #define SIMPLE_HASHTABLE_SORT_FUNCTION compare_facet_key #define SIMPLE_HASHTABLE_VALUE_TYPE struct facet_key #define SIMPLE_HASHTABLE_NAME _KEY -#include "../simple_hashtable.h" +#include "../simple_hashtable/simple_hashtable.h" // ---------------------------------------------------------------------------- @@ -222,6 +222,7 @@ struct facets { SIMPLE_PATTERN *visible_keys; SIMPLE_PATTERN *excluded_keys; SIMPLE_PATTERN *included_keys; + bool all_keys_included_by_default; FACETS_OPTIONS options; @@ -255,6 +256,7 @@ struct facets { } keys_in_row; FACET_ROW *base; // double linked list of the selected facets rows + FACET_ROW_BIN_DATA bin_data; uint32_t items_to_return; uint32_t max_items_to_return; @@ -328,6 +330,10 @@ struct facets { struct { size_t searches; } fts; + + struct { + size_t bin_data_inflight; + }; } operations; struct { @@ -353,6 +359,27 @@ uint32_t facets_rows(FACETS *facets) { return facets->items_to_return; } +static const char *facets_key_id(FACET_KEY *k) { + if(k->facets->options & FACETS_OPTION_HASH_IDS) + return hash_to_static_string(k->hash); + else + return k->name ? k->name : hash_to_static_string(k->hash); +} + +static const char *facets_key_value_id(FACET_KEY *k, FACET_VALUE *v) { + if(k->facets->options & FACETS_OPTION_HASH_IDS) + return hash_to_static_string(v->hash); + else + return v->name ? v->name : hash_to_static_string(v->hash); +} + +void facets_use_hashes_for_ids(FACETS *facets, bool set) { + if(set) + facets->options |= FACETS_OPTION_HASH_IDS; + else + facets->options &= ~(FACETS_OPTION_HASH_IDS); +} + // ---------------------------------------------------------------------------- static void facets_row_free(FACETS *facets __maybe_unused, FACET_ROW *row); @@ -570,12 +597,12 @@ static inline void FACET_VALUE_ADD_CURRENT_VALUE_TO_INDEX(FACET_KEY *k) { k->facets->operations.values.indexed++; } -static inline void FACET_VALUE_ADD_OR_UPDATE_SELECTED(FACET_KEY *k, FACETS_HASH hash) { +static inline void FACET_VALUE_ADD_OR_UPDATE_SELECTED(FACET_KEY *k, const char *name, FACETS_HASH hash) { FACET_VALUE tv = { .hash = hash, .selected = true, - .name = NULL, - .name_len = 0, + .name = name, + .name_len = name ? strlen(name) : 0, }; FACET_VALUE_ADD_TO_INDEX(k, &tv); } @@ -643,6 +670,35 @@ bool facets_key_name_value_length_is_selected(FACETS *facets, const char *key, s return (v && v->selected) ? true : false; } +bool facets_foreach_selected_value_in_key(FACETS *facets, const char *key, size_t key_length, DICTIONARY *used_hashes_registry, facets_foreach_selected_value_in_key_t cb, void *data) { + FACETS_HASH hash = FACETS_HASH_FUNCTION(key, key_length); + FACET_KEY *k = FACETS_KEY_GET_FROM_INDEX(facets, hash); + if(!k || k->default_selected_for_values) + return false; + + size_t selected = 0; + for(FACET_VALUE *v = k->values.ll; v ;v = v->next) { + if(!v->selected) continue; + + const char *value = v->name; + if(!value) { + if(used_hashes_registry) { + char hash_str[FACET_STRING_HASH_SIZE]; + facets_hash_to_str(v->hash, hash_str); + value = dictionary_get(used_hashes_registry, hash_str); + } + + if(!value) + return false; + } + + if(!cb(facets, selected++, k->name, value, data)) + return false; + } + + return selected > 0; +} + void facets_add_possible_value_name_to_key(FACETS *facets, const char *key, size_t key_length, const char *value, size_t value_length) { FACETS_HASH hash = FACETS_HASH_FUNCTION(key, key_length); FACET_KEY *k = FACETS_KEY_GET_FROM_INDEX(facets, hash); @@ -691,7 +747,7 @@ static inline FACET_KEY *FACETS_KEY_CREATE(FACETS *facets, FACETS_HASH hash, con k->current_value.b = buffer_create(sizeof(FACET_VALUE_UNSET), NULL); k->default_selected_for_values = true; - if(!(k->options & FACET_KEY_OPTION_REORDER)) + if(unlikely((k->options & (FACET_KEY_OPTION_REORDER | FACET_KEY_OPTION_REORDER_DONE)) == 0)) k->order = facets->order++; if((k->options & FACET_KEY_OPTION_FTS) || (facets->options & FACETS_OPTION_ALL_KEYS_FTS)) @@ -724,10 +780,11 @@ static inline FACET_KEY *FACETS_KEY_ADD_TO_INDEX(FACETS *facets, FACETS_HASH has FACET_KEY *k = SIMPLE_HASHTABLE_SLOT_DATA(slot); facet_key_set_name(k, name, name_length); + k->options |= options; - if(unlikely(k->options & FACET_KEY_OPTION_REORDER)) { + if(unlikely((k->options & (FACET_KEY_OPTION_REORDER | FACET_KEY_OPTION_REORDER_DONE)) == FACET_KEY_OPTION_REORDER)) { k->order = facets->order++; - k->options &= ~FACET_KEY_OPTION_REORDER; + k->options |= FACET_KEY_OPTION_REORDER_DONE; } return k; @@ -1547,7 +1604,7 @@ static inline void facet_value_is_used(FACET_KEY *k, FACET_VALUE *v) { } static inline bool facets_key_is_facet(FACETS *facets, FACET_KEY *k) { - bool included = true, excluded = false, never = false; + bool included = facets->all_keys_included_by_default, excluded = false, never = false; if(k->options & (FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_NO_FACET | FACET_KEY_OPTION_NEVER_FACET)) { if(k->options & FACET_KEY_OPTION_FACET) { @@ -1591,9 +1648,39 @@ static inline bool facets_key_is_facet(FACETS *facets, FACET_KEY *k) { } // ---------------------------------------------------------------------------- +// bin_data management + +static inline void facets_row_bin_data_cleanup(FACETS *facets, FACET_ROW_BIN_DATA *bin_data) { + if(!bin_data->data) + return; + + bin_data->cleanup_cb(bin_data->data); + *bin_data = FACET_ROW_BIN_DATA_EMPTY; + + fatal_assert(facets->operations.bin_data_inflight > 0); + facets->operations.bin_data_inflight--; +} + +void facets_row_bin_data_set(FACETS *facets, void (*cleanup_cb)(void *data), void *data) { + // in case the caller tries to register bin_data multiple times + // for the same row. + facets_row_bin_data_cleanup(facets, &facets->bin_data); + + // set the new values + facets->bin_data.cleanup_cb = cleanup_cb; + facets->bin_data.data = data; + facets->operations.bin_data_inflight++; +} + +void *facets_row_bin_data_get(FACETS *facets __maybe_unused, FACET_ROW *row) { + return row->bin_data.data; +} + +// ---------------------------------------------------------------------------- FACETS *facets_create(uint32_t items_to_return, FACETS_OPTIONS options, const char *visible_keys, const char *facet_keys, const char *non_facet_keys) { FACETS *facets = callocz(1, sizeof(FACETS)); + facets->all_keys_included_by_default = true; facets->options = options; FACETS_KEYS_INDEX_CREATE(facets); @@ -1616,6 +1703,8 @@ FACETS *facets_create(uint32_t items_to_return, FACETS_OPTIONS options, const ch } void facets_destroy(FACETS *facets) { + if(!facets) return; + dictionary_destroy(facets->accepted_params); FACETS_KEYS_INDEX_DESTROY(facets); simple_pattern_free(facets->visible_keys); @@ -1629,6 +1718,13 @@ void facets_destroy(FACETS *facets) { facets_row_free(facets, r); } + // in case the caller did not call facets_row_finished() + // on the last row. + facets_row_bin_data_cleanup(facets, &facets->bin_data); + + // make sure we didn't lose any data + fatal_assert(facets->operations.bin_data_inflight == 0); + freez(facets->histogram.chart); freez(facets); } @@ -1691,6 +1787,40 @@ void facets_enable_slice_mode(FACETS *facets) { facets->options |= FACETS_OPTION_DONT_SEND_EMPTY_VALUE_FACETS | FACETS_OPTION_SORT_FACETS_ALPHABETICALLY; } +void facets_reset_and_disable_all_facets(FACETS *facets) { + facets->all_keys_included_by_default = false; + + simple_pattern_free(facets->included_keys); + facets->included_keys = NULL; + +// We need this, because the exclusions are good for controlling which key can become a facet. +// The excluded ones are not offered for facets at all. +// simple_pattern_free(facets->excluded_keys); +// facets->excluded_keys = NULL; + + simple_pattern_free(facets->visible_keys); + facets->visible_keys = NULL; + + FACET_KEY *k; + foreach_key_in_facets(facets, k) { + k->options |= FACET_KEY_OPTION_NO_FACET; + k->options &= ~FACET_KEY_OPTION_FACET; + } + foreach_key_in_facets_done(k); +} + +inline FACET_KEY *facets_register_facet(FACETS *facets, const char *name, FACET_KEY_OPTIONS options) { + size_t name_length = strlen(name); + FACETS_HASH hash = FACETS_HASH_FUNCTION(name, name_length); + + FACET_KEY *k = FACETS_KEY_ADD_TO_INDEX(facets, hash, name, name_length, options); + k->options |= FACET_KEY_OPTION_FACET; + k->options &= ~FACET_KEY_OPTION_NO_FACET; + facet_key_late_init(facets, k); + + return k; +} + inline FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, FACET_KEY_OPTIONS options) { if(!is_valid_string_hash(key_id)) return NULL; @@ -1708,16 +1838,25 @@ inline FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, F return k; } -void facets_register_facet_id_filter(FACETS *facets, const char *key_id, char *value_id, FACET_KEY_OPTIONS options) { +void facets_register_facet_filter_id(FACETS *facets, const char *key_id, const char *value_id, FACET_KEY_OPTIONS options) { FACET_KEY *k = facets_register_facet_id(facets, key_id, options); if(k) { if(is_valid_string_hash(value_id)) { k->default_selected_for_values = false; - FACET_VALUE_ADD_OR_UPDATE_SELECTED(k, str_to_facets_hash(value_id)); + FACET_VALUE_ADD_OR_UPDATE_SELECTED(k, NULL, str_to_facets_hash(value_id)); } } } +void facets_register_facet_filter(FACETS *facets, const char *key, const char *value, FACET_KEY_OPTIONS options) { + FACET_KEY *k = facets_register_facet(facets, key, options); + if(k) { + FACETS_HASH hash = FACETS_HASH_FUNCTION(value, strlen(value)); + k->default_selected_for_values = false; + FACET_VALUE_ADD_OR_UPDATE_SELECTED(k, value, hash); + } +} + void facets_set_current_row_severity(FACETS *facets, FACET_ROW_SEVERITY severity) { facets->current_row.severity = severity; } @@ -1834,6 +1973,10 @@ void facets_add_key_value(FACETS *facets, const char *key, const char *value) { } void facets_add_key_value_length(FACETS *facets, const char *key, size_t key_len, const char *value, size_t value_len) { + if(!key || !*key || !key_len || !value || !*value || !value_len) + // adding empty values, makes the rows unmatched + return; + FACET_KEY *k = facets_register_key_name_length(facets, key, key_len, 0); k->current_value.raw = value; k->current_value.raw_len = value_len; @@ -1879,7 +2022,9 @@ static void facet_row_key_value_delete_callback(const DICTIONARY_ITEM *item __ma // FACET_ROW management static void facets_row_free(FACETS *facets __maybe_unused, FACET_ROW *row) { + facets_row_bin_data_cleanup(facets, &row->bin_data); dictionary_destroy(row->dict); + row->dict = NULL; freez(row); } @@ -1889,6 +2034,7 @@ static FACET_ROW *facets_row_create(FACETS *facets, usec_t usec, FACET_ROW *into if(into) { row = into; facets->operations.rows.reused++; + facets_row_bin_data_cleanup(facets, &row->bin_data); } else { row = callocz(1, sizeof(FACET_ROW)); @@ -1899,6 +2045,11 @@ static FACET_ROW *facets_row_create(FACETS *facets, usec_t usec, FACET_ROW *into facets->operations.rows.created++; } + // copy the bin_data to the row + // and forget about them in facets + row->bin_data = facets->bin_data; + facets->bin_data = FACET_ROW_BIN_DATA_EMPTY; + row->severity = facets->current_row.severity; row->usec = usec; @@ -2083,6 +2234,8 @@ static void facets_reset_keys_with_value_and_row(FACETS *facets) { facets->current_row.keys_matched_by_query_positive = 0; facets->current_row.keys_matched_by_query_negative = 0; facets->keys_in_row.used = 0; + + facets_row_bin_data_cleanup(facets, &facets->bin_data); } void facets_rows_begin(FACETS *facets) { @@ -2097,6 +2250,9 @@ void facets_rows_begin(FACETS *facets) { } bool facets_row_finished(FACETS *facets, usec_t usec) { +// char buf[RFC3339_MAX_LENGTH]; +// rfc3339_datetime_ut(buf, sizeof(buf), usec, 3, false); + facets->operations.rows.evaluated++; if(unlikely((facets->query && facets->keys_filtered_by_query && @@ -2207,11 +2363,11 @@ void facets_accepted_parameters_to_json_array(FACETS *facets, BUFFER *wb, bool w if(with_keys) { FACET_KEY *k; - foreach_key_in_facets(facets, k){ - if (!k->values.enabled) + foreach_key_in_facets(facets, k) { + if (!k->values.enabled || k->options & FACET_KEY_OPTION_HIDDEN) continue; - buffer_json_add_array_item_string(wb, hash_to_static_string(k->hash)); + buffer_json_add_array_item_string(wb, facets_key_id(k)); } foreach_key_in_facets_done(k); } @@ -2391,8 +2547,8 @@ static uint32_t facets_sort_and_reorder_values(FACET_KEY *k) { return ret; } -void facets_table_config(BUFFER *wb) { - buffer_json_member_add_boolean(wb, "show_ids", false); // do not show the column ids to the user +void facets_table_config(FACETS *facets, BUFFER *wb) { + buffer_json_member_add_boolean(wb, "show_ids", (facets->options & FACETS_OPTION_HASH_IDS) ? false : true); buffer_json_member_add_boolean(wb, "has_history", true); // enable date-time picker with after-before buffer_json_member_add_object(wb, "pagination"); @@ -2408,8 +2564,9 @@ void facets_table_config(BUFFER *wb) { void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry) { facets->report.used_hashes_registry = used_hashes_registry; + facets_table_config(facets, wb); + if(!(facets->options & FACETS_OPTION_DATA_ONLY)) { - facets_table_config(wb); facets_accepted_parameters_to_json_array(facets, wb, true); } @@ -2434,19 +2591,21 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry) CLEAN_BUFFER *tb = buffer_create(0, NULL); FACET_KEY *k; foreach_key_in_facets(facets, k) { - if(!k->values.enabled) + if(!k->values.enabled || k->options & FACET_KEY_OPTION_HIDDEN) continue; - if(!facets_sort_and_reorder_values(k)) - // no values for this key - continue; + facets_sort_and_reorder_values(k); buffer_json_add_array_item_object(wb); // key { - buffer_json_member_add_string(wb, "id", hash_to_static_string(k->hash)); - buffer_json_member_add_string(wb, "name", facets_key_name_cached(k - , facets->report.used_hashes_registry - )); + buffer_json_member_add_string( + wb, "id", facets_key_id(k)); + + buffer_json_member_add_string( + wb, "name", + facets_key_name_cached(k, facets->report.used_hashes_registry)); + + // buffer_json_member_add_string(wb, "raw", k->name); if(!k->order) k->order = facets->order++; buffer_json_member_add_uint64(wb, "order", k->order); @@ -2463,10 +2622,11 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry) buffer_json_add_array_item_object(wb); { - buffer_json_member_add_string(wb, "id", hash_to_static_string(v->hash)); + buffer_json_member_add_string(wb, "id", facets_key_value_id(k, v)); facets_key_value_transformed(facets, k, v, tb, FACETS_TRANSFORM_FACET); buffer_json_member_add_string(wb, "name", buffer_tostring(tb)); + // buffer_json_member_add_string(wb, "raw", v->name); buffer_json_member_add_uint64(wb, "count", v->final_facet_value_counter); buffer_json_member_add_uint64(wb, "order", v->order); } @@ -2517,38 +2677,40 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry) FACET_KEY *k; foreach_key_in_facets(facets, k) { - RRDF_FIELD_OPTIONS options = RRDF_FIELD_OPTS_WRAP; - bool visible = k->options & (FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_STICKY); + if(k->options & FACET_KEY_OPTION_HIDDEN) + continue; - if ((facets->options & FACETS_OPTION_ALL_FACETS_VISIBLE && k->values.enabled)) - visible = true; + RRDF_FIELD_OPTIONS options = RRDF_FIELD_OPTS_WRAP; + RRDF_FIELD_VISUAL visual = (k->options & FACET_KEY_OPTION_RICH_TEXT) ? RRDF_FIELD_VISUAL_RICH : RRDF_FIELD_VISUAL_VALUE; + RRDF_FIELD_TRANSFORM transform = RRDF_FIELD_TRANSFORM_NONE; - if (!visible) - visible = simple_pattern_matches(facets->visible_keys, k->name); + if (k->options & (FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_STICKY) || + ((facets->options & FACETS_OPTION_ALL_FACETS_VISIBLE) && k->values.enabled) || + simple_pattern_matches(facets->visible_keys, k->name)) + options |= RRDF_FIELD_OPTS_VISIBLE; - if (visible) - options |= RRDF_FIELD_OPTS_VISIBLE; + if (k->options & FACET_KEY_OPTION_MAIN_TEXT) + options |= RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_WRAP; - if (k->options & FACET_KEY_OPTION_MAIN_TEXT) - options |= RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_WRAP; + if (k->options & FACET_KEY_OPTION_EXPANDED_FILTER) + options |= RRDF_FIELD_OPTS_EXPANDED_FILTER; - if (k->options & FACET_KEY_OPTION_EXPANDED_FILTER) - options |= RRDF_FIELD_OPTS_EXPANDED_FILTER; + if (k->options & FACET_KEY_OPTION_PRETTY_XML) + transform = RRDF_FIELD_TRANSFORM_XML; - const char *hash_str = hash_to_static_string(k->hash); + const char *key_id = facets_key_id(k); - buffer_rrdf_table_add_field( - wb, field_id++, - hash_str, k->name ? k->name : hash_str, - RRDF_FIELD_TYPE_STRING, - (k->options & FACET_KEY_OPTION_RICH_TEXT) ? RRDF_FIELD_VISUAL_RICH : RRDF_FIELD_VISUAL_VALUE, - RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, - RRDF_FIELD_SORT_FIXED, - NULL, - RRDF_FIELD_SUMMARY_COUNT, - (k->options & FACET_KEY_OPTION_NEVER_FACET) ? RRDF_FIELD_FILTER_NONE : RRDF_FIELD_FILTER_FACET, - options, FACET_VALUE_UNSET); - } + buffer_rrdf_table_add_field( + wb, field_id++, + key_id, k->name ? k->name : key_id, + RRDF_FIELD_TYPE_STRING, + visual, transform, 0, NULL, NAN, + RRDF_FIELD_SORT_FIXED, + NULL, + RRDF_FIELD_SUMMARY_COUNT, + (k->options & FACET_KEY_OPTION_NEVER_FACET) ? RRDF_FIELD_FILTER_NONE : RRDF_FIELD_FILTER_FACET, + options, FACET_VALUE_UNSET); + } foreach_key_in_facets_done(k); } buffer_json_object_close(wb); // columns @@ -2585,6 +2747,9 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry) FACET_KEY *k; foreach_key_in_facets(facets, k) { + if(k->options & FACET_KEY_OPTION_HIDDEN) + continue; + FACET_ROW_KEY_VALUE *rkv = dictionary_get(row->dict, k->name); if(unlikely(k->dynamic.cb)) { @@ -2627,14 +2792,14 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry) { FACET_KEY *k; foreach_key_in_facets(facets, k) { - if (!k->values.enabled) + if (!k->values.enabled || k->options & FACET_KEY_OPTION_HIDDEN) continue; if(unlikely(!first_histogram_hash)) first_histogram_hash = k->hash; buffer_json_add_array_item_object(wb); - buffer_json_member_add_string(wb, "id", hash_to_static_string(k->hash)); + buffer_json_member_add_string(wb, "id", facets_key_id(k)); buffer_json_member_add_string(wb, "name", k->name); buffer_json_member_add_uint64(wb, "order", k->order); buffer_json_object_close(wb); @@ -2662,7 +2827,7 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry) } if(show_histogram) { - buffer_json_member_add_string(wb, "id", k ? hash_to_static_string(k->hash) : ""); + buffer_json_member_add_string(wb, "id", k ? facets_key_id(k) : ""); buffer_json_member_add_string(wb, "name", k ? k->name : ""); buffer_json_member_add_object(wb, "chart"); { diff --git a/src/libnetdata/facets/facets.h b/src/libnetdata/facets/facets.h index 8364d861..1d2b89c2 100644 --- a/src/libnetdata/facets/facets.h +++ b/src/libnetdata/facets/facets.h @@ -23,6 +23,7 @@ typedef enum __attribute__((packed)) { } FACETS_TRANSFORMATION_SCOPE; typedef enum __attribute__((packed)) { + FACET_KEY_OPTION_NONE = 0, FACET_KEY_OPTION_FACET = (1 << 0), // filterable values FACET_KEY_OPTION_NO_FACET = (1 << 1), // non-filterable value FACET_KEY_OPTION_NEVER_FACET = (1 << 2), // never enable this field as facet @@ -32,8 +33,11 @@ typedef enum __attribute__((packed)) { FACET_KEY_OPTION_MAIN_TEXT = (1 << 6), // full width and wrap FACET_KEY_OPTION_RICH_TEXT = (1 << 7), FACET_KEY_OPTION_REORDER = (1 << 8), // give the key a new order id on first encounter - FACET_KEY_OPTION_TRANSFORM_VIEW = (1 << 9), // when registering the transformation, do it only at the view, not on all data - FACET_KEY_OPTION_EXPANDED_FILTER = (1 << 10), // the presentation should have this filter expanded by default + FACET_KEY_OPTION_REORDER_DONE = (1 << 9), // done re-ordering for this field + FACET_KEY_OPTION_TRANSFORM_VIEW = (1 << 10), // when registering the transformation, do it only at the view, not on all data + FACET_KEY_OPTION_EXPANDED_FILTER = (1 << 11), // the presentation should have this filter expanded by default + FACET_KEY_OPTION_PRETTY_XML = (1 << 12), // instruct the UI to parse this as an XML document + FACET_KEY_OPTION_HIDDEN = (1 << 13), // do not include this field in the response } FACET_KEY_OPTIONS; typedef enum __attribute__((packed)) { @@ -51,10 +55,18 @@ typedef struct facet_row_key_value { BUFFER *wb; } FACET_ROW_KEY_VALUE; +typedef struct facet_row_bin_data { + void (*cleanup_cb)(void *data); + void *data; +} FACET_ROW_BIN_DATA; + +#define FACET_ROW_BIN_DATA_EMPTY (FACET_ROW_BIN_DATA){.data = NULL, .cleanup_cb = NULL} + typedef struct facet_row { usec_t usec; DICTIONARY *dict; FACET_ROW_SEVERITY severity; + FACET_ROW_BIN_DATA bin_data; struct facet_row *prev, *next; } FACET_ROW; @@ -77,6 +89,7 @@ typedef enum __attribute__((packed)) { FACETS_OPTION_DONT_SEND_EMPTY_VALUE_FACETS = (1 << 5), // empty facet values will not be included in the report FACETS_OPTION_SORT_FACETS_ALPHABETICALLY = (1 << 6), FACETS_OPTION_SHOW_DELTAS = (1 << 7), + FACETS_OPTION_HASH_IDS = (1 << 8), // when set, the id of the facets, keys and values will be their hash } FACETS_OPTIONS; FACETS *facets_create(uint32_t items_to_return, FACETS_OPTIONS options, const char *visible_keys, const char *facet_keys, const char *non_facet_keys); @@ -98,8 +111,14 @@ void facets_set_anchor(FACETS *facets, usec_t start_ut, usec_t stop_ut, FACETS_A void facets_enable_slice_mode(FACETS *facets); bool facets_row_candidate_to_keep(FACETS *facets, usec_t usec); +void facets_reset_and_disable_all_facets(FACETS *facets); + +FACET_KEY *facets_register_facet(FACETS *facets, const char *name, FACET_KEY_OPTIONS options); FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, FACET_KEY_OPTIONS options); -void facets_register_facet_id_filter(FACETS *facets, const char *key_id, char *value_id, FACET_KEY_OPTIONS options); + +void facets_register_facet_filter(FACETS *facets, const char *key, const char *value, FACET_KEY_OPTIONS options); +void facets_register_facet_filter_id(FACETS *facets, const char *key_id, const char *value_id, FACET_KEY_OPTIONS options); + void facets_set_timeframe_and_histogram_by_id(FACETS *facets, const char *key_id, usec_t after_ut, usec_t before_ut); void facets_set_timeframe_and_histogram_by_name(FACETS *facets, const char *key_name, usec_t after_ut, usec_t before_ut); @@ -121,8 +140,16 @@ usec_t facets_row_oldest_ut(FACETS *facets); usec_t facets_row_newest_ut(FACETS *facets); uint32_t facets_rows(FACETS *facets); -void facets_table_config(BUFFER *wb); +void facets_table_config(FACETS *facets, BUFFER *wb); const char *facets_severity_to_string(FACET_ROW_SEVERITY severity); +typedef bool (*facets_foreach_selected_value_in_key_t)(FACETS *facets, size_t id, const char *key, const char *value, void *data); +bool facets_foreach_selected_value_in_key(FACETS *facets, const char *key, size_t key_length, DICTIONARY *used_hashes_registry, facets_foreach_selected_value_in_key_t cb, void *data); + +void facets_row_bin_data_set(FACETS *facets, void (*cleanup_cb)(void *data), void *data); +void *facets_row_bin_data_get(FACETS *facets __maybe_unused, FACET_ROW *row); + +void facets_use_hashes_for_ids(FACETS *facets, bool set); + #endif diff --git a/src/libnetdata/facets/logs_query_status.h b/src/libnetdata/facets/logs_query_status.h new file mode 100644 index 00000000..4fde2499 --- /dev/null +++ b/src/libnetdata/facets/logs_query_status.h @@ -0,0 +1,868 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_LOGS_QUERY_STATUS_H +#define NETDATA_LOGS_QUERY_STATUS_H + +#include "../libnetdata.h" + +#define LQS_PARAMETER_HELP "help" +#define LQS_PARAMETER_AFTER "after" +#define LQS_PARAMETER_BEFORE "before" +#define LQS_PARAMETER_ANCHOR "anchor" +#define LQS_PARAMETER_LAST "last" +#define LQS_PARAMETER_QUERY "query" +#define LQS_PARAMETER_FACETS "facets" +#define LQS_PARAMETER_HISTOGRAM "histogram" +#define LQS_PARAMETER_DIRECTION "direction" +#define LQS_PARAMETER_IF_MODIFIED_SINCE "if_modified_since" +#define LQS_PARAMETER_DATA_ONLY "data_only" +#define LQS_PARAMETER_SOURCE "__logs_sources" // this must never conflict with user fields +#define LQS_PARAMETER_INFO "info" +#define LQS_PARAMETER_SLICE "slice" +#define LQS_PARAMETER_DELTA "delta" +#define LQS_PARAMETER_TAIL "tail" +#define LQS_PARAMETER_SAMPLING "sampling" + +#define LQS_MAX_PARAMS 1000 +#define LQS_DEFAULT_QUERY_DURATION (1 * 3600) + +#undef LQS_SLICE_PARAMETER +#if LQS_DEFAULT_SLICE_MODE == 1 +#define LQS_SLICE_PARAMETER 1 +#endif + +typedef struct { + const char *transaction; + + FACET_KEY_OPTIONS default_facet; // the option to be used for internal fields. + // when the requests set facets, we disable all default facets, + // so that the UI has full control over them. + + bool fields_are_ids; // POST works with field names, GET works with field hashes (IDs) + bool info; // the request is an INFO request, do not execute a query. + + bool data_only; // return as fast as possible, with the requested amount of data, + // without scanning the entire duration. + + bool slice; // apply native backend filters to slice the events database. + bool delta; // return incremental data for the histogram (used with data_only) + bool tail; // return NOT MODIFIED if no more data are available after the anchor given. + + time_t after_s; // the starting timestamp of the query + time_t before_s; // the ending timestamp of the query + usec_t after_ut; // in microseconds + usec_t before_ut; // in microseconds + + usec_t anchor; // the anchor to seek to + FACETS_ANCHOR_DIRECTION direction; // the direction based on the anchor (or the query timeframe) + + usec_t if_modified_since; // the timestamp to check with tail == true + + size_t entries; // the number of log events to return in a single response + + const char *query; // full text search query string + const char *histogram; // the field to use for the histogram + + SIMPLE_PATTERN *sources; // custom log sources to query + LQS_SOURCE_TYPE source_type; // pre-defined log sources to query + + size_t filters; // the number of filters (facets selected) in the query + size_t sampling; // the number of log events to sample, when the query is too big + + time_t now_s; // the timestamp the query was received + time_t expires_s; // the timestamp the response expires +} LOGS_QUERY_REQUEST; + +#define LOGS_QUERY_REQUEST_DEFAULTS(function_transaction, default_slice, default_direction) \ + (LOGS_QUERY_REQUEST) { \ + .transaction = (function_transaction), \ + .default_facet = FACET_KEY_OPTION_FACET, \ + .info = false, \ + .data_only = false, \ + .slice = (default_slice), \ + .delta = false, \ + .tail = false, \ + .after_s = 0, \ + .before_s = 0, \ + .anchor = 0, \ + .if_modified_since = 0, \ + .entries = 0, \ + .direction = (default_direction), \ + .query = NULL, \ + .histogram = NULL, \ + .sources = NULL, \ + .source_type = LQS_SOURCE_TYPE_ALL, \ + .filters = 0, \ + .sampling = LQS_DEFAULT_ITEMS_SAMPLING, \ +} + +typedef struct { + FACETS *facets; + + LOGS_QUERY_REQUEST rq; + + bool *cancelled; // a pointer to the cancelling boolean + usec_t *stop_monotonic_ut; + + struct { + usec_t start_ut; + usec_t stop_ut; + usec_t delta_ut; + } anchor; + + struct { + usec_t start_ut; + usec_t stop_ut; + bool stop_when_full; + } query; + + usec_t last_modified; + + struct lqs_extension c; +} LOGS_QUERY_STATUS; + +struct logs_query_data { + const char *transaction; + FACETS *facets; + LOGS_QUERY_REQUEST *rq; + BUFFER *wb; +}; + +static inline FACETS_ANCHOR_DIRECTION lgs_get_direction(const char *value) { + return strcasecmp(value, "forward") == 0 ? FACETS_ANCHOR_DIRECTION_FORWARD : FACETS_ANCHOR_DIRECTION_BACKWARD; +} + +static inline void lqs_log_error(LOGS_QUERY_STATUS *lqs, const char *msg) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "LOGS QUERY ERROR: %s, on query " + "timeframe [%"PRIu64" - %"PRIu64"], " + "anchor [%"PRIu64" - %"PRIu64"], " + "if_modified_since %"PRIu64", " + "data_only:%s, delta:%s, tail:%s, direction:%s" + , msg + , lqs->rq.after_ut + , lqs->rq.before_ut + , lqs->anchor.start_ut + , lqs->anchor.stop_ut + , lqs->rq.if_modified_since + , lqs->rq.data_only ? "true" : "false" + , lqs->rq.delta ? "true" : "false" + , lqs->rq.tail ? "tail" : "false" + , lqs->rq.direction == FACETS_ANCHOR_DIRECTION_FORWARD ? "forward" : "backward"); +} + +static inline void lqs_query_timeframe(LOGS_QUERY_STATUS *lqs, usec_t anchor_delta_ut) { + lqs->anchor.delta_ut = anchor_delta_ut; + + if(lqs->rq.direction == FACETS_ANCHOR_DIRECTION_FORWARD) { + lqs->query.start_ut = (lqs->rq.data_only && lqs->anchor.start_ut) ? lqs->anchor.start_ut : lqs->rq.after_ut; + lqs->query.stop_ut = ((lqs->rq.data_only && lqs->anchor.stop_ut) ? lqs->anchor.stop_ut : lqs->rq.before_ut) + lqs->anchor.delta_ut; + } + else { + lqs->query.start_ut = ((lqs->rq.data_only && lqs->anchor.start_ut) ? lqs->anchor.start_ut : lqs->rq.before_ut) + lqs->anchor.delta_ut; + lqs->query.stop_ut = (lqs->rq.data_only && lqs->anchor.stop_ut) ? lqs->anchor.stop_ut : lqs->rq.after_ut; + } + + lqs->query.stop_when_full = (lqs->rq.data_only && !lqs->anchor.stop_ut); +} + +static inline void lqs_function_help(LOGS_QUERY_STATUS *lqs, BUFFER *wb) { + buffer_reset(wb); + wb->content_type = CT_TEXT_PLAIN; + wb->response_code = HTTP_RESP_OK; + + buffer_sprintf(wb, + "%s / %s\n" + "\n" + "%s\n" + "\n" + "The following parameters are supported:\n" + "\n" + , program_name + , LQS_FUNCTION_NAME + , LQS_FUNCTION_DESCRIPTION + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_HELP "\n" + " Shows this help message.\n" + "\n" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_INFO "\n" + " Request initial configuration information about the plugin.\n" + " The key entity returned is the required_params array, which includes\n" + " all the available log sources.\n" + " When `" LQS_PARAMETER_INFO "` is requested, all other parameters are ignored.\n" + "\n" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_DATA_ONLY ":true or " LQS_PARAMETER_DATA_ONLY ":false\n" + " Quickly respond with data requested, without generating a\n" + " `histogram`, `facets` counters and `items`.\n" + "\n" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_DELTA ":true or " LQS_PARAMETER_DELTA ":false\n" + " When doing data only queries, include deltas for histogram, facets and items.\n" + "\n" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_TAIL ":true or " LQS_PARAMETER_TAIL ":false\n" + " When doing data only queries, respond with the newest messages,\n" + " and up to the anchor, but calculate deltas (if requested) for\n" + " the duration [anchor - before].\n" + "\n" + ); + +#ifdef LQS_SLICE_PARAMETER + buffer_sprintf(wb, + " " LQS_PARAMETER_SLICE ":true or " LQS_PARAMETER_SLICE ":false\n" + " When it is turned on, the plugin is is slicing the logs database,\n" + " utilizing the underlying available indexes.\n" + " When it is off, all filtering is done by the plugin.\n" + " The default is: %s\n" + "\n" + , lqs->rq.slice ? "true" : "false" + ); +#endif + buffer_sprintf(wb, + " " LQS_PARAMETER_SOURCE ":SOURCE\n" + " Query only the specified log sources.\n" + " Do an `" LQS_PARAMETER_INFO "` query to find the sources.\n" + "\n" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_BEFORE ":TIMESTAMP_IN_SECONDS\n" + " Absolute or relative (to now) timestamp in seconds, to start the query.\n" + " The query is always executed from the most recent to the oldest log entry.\n" + " If not given the default is: now.\n" + "\n" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_AFTER ":TIMESTAMP_IN_SECONDS\n" + " Absolute or relative (to `before`) timestamp in seconds, to end the query.\n" + " If not given, the default is %d.\n" + "\n" + , -LQS_DEFAULT_QUERY_DURATION + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_LAST ":ITEMS\n" + " The number of items to return.\n" + " The default is %zu.\n" + "\n" + , lqs->rq.entries + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_SAMPLING ":ITEMS\n" + " The number of log entries to sample to estimate facets counters and histogram.\n" + " The default is %zu.\n" + "\n" + , lqs->rq.sampling + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_ANCHOR ":TIMESTAMP_IN_MICROSECONDS\n" + " Return items relative to this timestamp.\n" + " The exact items to be returned depend on the query `" LQS_PARAMETER_DIRECTION "`.\n" + "\n" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_DIRECTION ":forward or " LQS_PARAMETER_DIRECTION ":backward\n" + " When set to `backward` (default) the items returned are the newest before the\n" + " `" LQS_PARAMETER_ANCHOR "`, (or `" LQS_PARAMETER_BEFORE "` if `" LQS_PARAMETER_ANCHOR "` is not set)\n" + " When set to `forward` the items returned are the oldest after the\n" + " `" LQS_PARAMETER_ANCHOR "`, (or `" LQS_PARAMETER_AFTER "` if `" LQS_PARAMETER_ANCHOR "` is not set)\n" + " The default is: %s\n" + "\n" + , lqs->rq.direction == FACETS_ANCHOR_DIRECTION_FORWARD ? "forward" : "backward" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_QUERY ":SIMPLE_PATTERN\n" + " Do a full text search to find the log entries matching the pattern given.\n" + " The plugin is searching for matches on all fields of the database.\n" + "\n" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_IF_MODIFIED_SINCE ":TIMESTAMP_IN_MICROSECONDS\n" + " Each successful response, includes a `last_modified` field.\n" + " By providing the timestamp to the `" LQS_PARAMETER_IF_MODIFIED_SINCE "` parameter,\n" + " the plugin will return 200 with a successful response, or 304 if the source has not\n" + " been modified since that timestamp.\n" + "\n" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_HISTOGRAM ":facet_id\n" + " Use the given `facet_id` for the histogram.\n" + " This parameter is ignored in `" LQS_PARAMETER_DATA_ONLY "` mode.\n" + "\n" + ); + + buffer_sprintf(wb, + " " LQS_PARAMETER_FACETS ":facet_id1,facet_id2,facet_id3,...\n" + " Add the given facets to the list of fields for which analysis is required.\n" + " The plugin will offer both a histogram and facet value counters for its values.\n" + " This parameter is ignored in `" LQS_PARAMETER_DATA_ONLY "` mode.\n" + "\n" + ); + + buffer_sprintf(wb, + " facet_id:value_id1,value_id2,value_id3,...\n" + " Apply filters to the query, based on the facet IDs returned.\n" + " Each `facet_id` can be given once, but multiple `facet_ids` can be given.\n" + "\n" + ); +} + +static inline bool lqs_request_parse_json_payload(json_object *jobj, const char *path, void *data, BUFFER *error) { + struct logs_query_data *qd = data; + LOGS_QUERY_REQUEST *rq = qd->rq; + BUFFER *wb = qd->wb; + FACETS *facets = qd->facets; + // const char *transaction = qd->transaction; + + buffer_flush(error); + + JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_INFO, rq->info, error, false); + JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_DELTA, rq->delta, error, false); + JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_TAIL, rq->tail, error, false); + JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_SLICE, rq->slice, error, false); + JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_DATA_ONLY, rq->data_only, error, false); + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_SAMPLING, rq->sampling, error, false); + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_AFTER, rq->after_s, error, false); + JSONC_PARSE_INT64_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_BEFORE, rq->before_s, error, false); + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_IF_MODIFIED_SINCE, rq->if_modified_since, error, false); + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_ANCHOR, rq->anchor, error, false); + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_LAST, rq->entries, error, false); + JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_DIRECTION, lgs_get_direction, rq->direction, error, false); + JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_QUERY, rq->query, error, false); + JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, LQS_PARAMETER_HISTOGRAM, rq->histogram, error, false); + + json_object *fcts; + if (json_object_object_get_ex(jobj, LQS_PARAMETER_FACETS, &fcts)) { + if (json_object_get_type(fcts) != json_type_array) { + buffer_sprintf(error, "member '%s' is not an array.", LQS_PARAMETER_FACETS); + // nd_log(NDLS_COLLECTORS, NDLP_ERR, "POST payload: '%s' is not an array", LQS_PARAMETER_FACETS); + return false; + } + + rq->default_facet = FACET_KEY_OPTION_NONE; + facets_reset_and_disable_all_facets(facets); + + buffer_json_member_add_array(wb, LQS_PARAMETER_FACETS); + + size_t facets_len = json_object_array_length(fcts); + for (size_t i = 0; i < facets_len; i++) { + json_object *fct = json_object_array_get_idx(fcts, i); + + if (json_object_get_type(fct) != json_type_string) { + buffer_sprintf(error, "facets array item %zu is not a string", i); + // nd_log(NDLS_COLLECTORS, NDLP_ERR, "POST payload: facets array item %zu is not a string", i); + return false; + } + + const char *value = json_object_get_string(fct); + facets_register_facet(facets, value, FACET_KEY_OPTION_FACET|FACET_KEY_OPTION_FTS|FACET_KEY_OPTION_REORDER); + buffer_json_add_array_item_string(wb, value); + } + + buffer_json_array_close(wb); // facets + } + + json_object *selections; + if (json_object_object_get_ex(jobj, "selections", &selections)) { + if (json_object_get_type(selections) != json_type_object) { + buffer_sprintf(error, "member 'selections' is not an object"); + // nd_log(NDLS_COLLECTORS, NDLP_ERR, "POST payload: '%s' is not an object", "selections"); + return false; + } + + buffer_json_member_add_object(wb, "selections"); + + CLEAN_BUFFER *sources_list = buffer_create(0, NULL); + + json_object_object_foreach(selections, key, val) { + if(strcmp(key, "query") == 0) continue; + + if (json_object_get_type(val) != json_type_array) { + buffer_sprintf(error, "selection '%s' is not an array", key); + // nd_log(NDLS_COLLECTORS, NDLP_ERR, "POST payload: selection '%s' is not an array", key); + return false; + } + + bool is_source = false; + if(strcmp(key, LQS_PARAMETER_SOURCE) == 0) { + // reset the sources, so that only what the user selects will be shown + is_source = true; + rq->source_type = LQS_SOURCE_TYPE_NONE; + } + + buffer_json_member_add_array(wb, key); + + size_t values_len = json_object_array_length(val); + for (size_t i = 0; i < values_len; i++) { + json_object *value_obj = json_object_array_get_idx(val, i); + + if (json_object_get_type(value_obj) != json_type_string) { + buffer_sprintf(error, "selection '%s' array item %zu is not a string", key, i); + // nd_log(NDLS_COLLECTORS, NDLP_ERR, "POST payload: selection '%s' array item %zu is not a string", key, i); + return false; + } + + const char *value = json_object_get_string(value_obj); + + if(is_source) { + // processing sources + LQS_SOURCE_TYPE t = LQS_FUNCTION_GET_INTERNAL_SOURCE_TYPE(value); + if(t != LQS_SOURCE_TYPE_NONE) { + rq->source_type |= t; + value = NULL; + } + else { + // else, match the source, whatever it is + if(buffer_strlen(sources_list)) + buffer_putc(sources_list, '|'); + + buffer_strcat(sources_list, value); + } + } + else { + // Call facets_register_facet_id_filter for each value + facets_register_facet_filter( + facets, key, value, FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_REORDER); + + rq->filters++; + } + + buffer_json_add_array_item_string(wb, value); + } + + buffer_json_array_close(wb); // key + } + + if(buffer_strlen(sources_list)) { + simple_pattern_free(rq->sources); + rq->sources = simple_pattern_create(buffer_tostring(sources_list), "|", SIMPLE_PATTERN_EXACT, false); + } + + buffer_json_object_close(wb); // selections + } + + facets_use_hashes_for_ids(facets, false); + rq->fields_are_ids = false; + return true; +} + +static inline bool lqs_request_parse_POST(LOGS_QUERY_STATUS *lqs, BUFFER *wb, BUFFER *payload, const char *transaction) { + FACETS *facets = lqs->facets; + LOGS_QUERY_REQUEST *rq = &lqs->rq; + + buffer_json_member_add_object(wb, "_request"); + + struct logs_query_data qd = { + .transaction = transaction, + .facets = facets, + .rq = rq, + .wb = wb, + }; + + int code; + CLEAN_JSON_OBJECT *jobj = + json_parse_function_payload_or_error(wb, payload, &code, lqs_request_parse_json_payload, &qd); + wb->response_code = code; + + return (jobj && code == HTTP_RESP_OK); +} + +static inline bool lqs_request_parse_GET(LOGS_QUERY_STATUS *lqs, BUFFER *wb, char *function) { + FACETS *facets = lqs->facets; + LOGS_QUERY_REQUEST *rq = &lqs->rq; + + buffer_json_member_add_object(wb, "_request"); + + char func_copy[strlen(function) + 1]; + memcpy(func_copy, function, sizeof(func_copy)); + + char *words[LQS_MAX_PARAMS] = { NULL }; + size_t num_words = quoted_strings_splitter_whitespace(func_copy, words, LQS_MAX_PARAMS); + for(int i = 1; i < LQS_MAX_PARAMS;i++) { + char *keyword = get_word(words, num_words, i); + if(!keyword) break; + + if(strcmp(keyword, LQS_PARAMETER_HELP) == 0) { + lqs_function_help(lqs, wb); + return false; + } + else if(strcmp(keyword, LQS_PARAMETER_INFO) == 0) { + rq->info = true; + } + else if(strncmp(keyword, LQS_PARAMETER_DELTA ":", sizeof(LQS_PARAMETER_DELTA ":") - 1) == 0) { + char *v = &keyword[sizeof(LQS_PARAMETER_DELTA ":") - 1]; + + if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0) + rq->delta = false; + else + rq->delta = true; + } + else if(strncmp(keyword, LQS_PARAMETER_TAIL ":", sizeof(LQS_PARAMETER_TAIL ":") - 1) == 0) { + char *v = &keyword[sizeof(LQS_PARAMETER_TAIL ":") - 1]; + + if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0) + rq->tail = false; + else + rq->tail = true; + } + else if(strncmp(keyword, LQS_PARAMETER_SAMPLING ":", sizeof(LQS_PARAMETER_SAMPLING ":") - 1) == 0) { + rq->sampling = str2ul(&keyword[sizeof(LQS_PARAMETER_SAMPLING ":") - 1]); + } + else if(strncmp(keyword, LQS_PARAMETER_DATA_ONLY ":", sizeof(LQS_PARAMETER_DATA_ONLY ":") - 1) == 0) { + char *v = &keyword[sizeof(LQS_PARAMETER_DATA_ONLY ":") - 1]; + + if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0) + rq->data_only = false; + else + rq->data_only = true; + } + else if(strncmp(keyword, LQS_PARAMETER_SLICE ":", sizeof(LQS_PARAMETER_SLICE ":") - 1) == 0) { + char *v = &keyword[sizeof(LQS_PARAMETER_SLICE ":") - 1]; + + if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0) + rq->slice = false; + else + rq->slice = true; + } + else if(strncmp(keyword, LQS_PARAMETER_SOURCE ":", sizeof(LQS_PARAMETER_SOURCE ":") - 1) == 0) { + const char *value = &keyword[sizeof(LQS_PARAMETER_SOURCE ":") - 1]; + + buffer_json_member_add_array(wb, LQS_PARAMETER_SOURCE); + + CLEAN_BUFFER *sources_list = buffer_create(0, NULL); + + rq->source_type = LQS_SOURCE_TYPE_NONE; + while(value) { + char *sep = strchr(value, ','); + if(sep) + *sep++ = '\0'; + + buffer_json_add_array_item_string(wb, value); + + LQS_SOURCE_TYPE t = LQS_FUNCTION_GET_INTERNAL_SOURCE_TYPE(value); + if(t != LQS_SOURCE_TYPE_NONE) { + rq->source_type |= t; + value = NULL; + } + else { + // else, match the source, whatever it is + if(buffer_strlen(sources_list)) + buffer_putc(sources_list, '|'); + + buffer_strcat(sources_list, value); + } + + value = sep; + } + + if(buffer_strlen(sources_list)) { + simple_pattern_free(rq->sources); + rq->sources = simple_pattern_create(buffer_tostring(sources_list), "|", SIMPLE_PATTERN_EXACT, false); + } + + buffer_json_array_close(wb); // source + } + else if(strncmp(keyword, LQS_PARAMETER_AFTER ":", sizeof(LQS_PARAMETER_AFTER ":") - 1) == 0) { + rq->after_s = str2l(&keyword[sizeof(LQS_PARAMETER_AFTER ":") - 1]); + } + else if(strncmp(keyword, LQS_PARAMETER_BEFORE ":", sizeof(LQS_PARAMETER_BEFORE ":") - 1) == 0) { + rq->before_s = str2l(&keyword[sizeof(LQS_PARAMETER_BEFORE ":") - 1]); + } + else if(strncmp(keyword, LQS_PARAMETER_IF_MODIFIED_SINCE ":", sizeof(LQS_PARAMETER_IF_MODIFIED_SINCE ":") - 1) == 0) { + rq->if_modified_since = str2ull(&keyword[sizeof(LQS_PARAMETER_IF_MODIFIED_SINCE ":") - 1], NULL); + } + else if(strncmp(keyword, LQS_PARAMETER_ANCHOR ":", sizeof(LQS_PARAMETER_ANCHOR ":") - 1) == 0) { + rq->anchor = str2ull(&keyword[sizeof(LQS_PARAMETER_ANCHOR ":") - 1], NULL); + } + else if(strncmp(keyword, LQS_PARAMETER_DIRECTION ":", sizeof(LQS_PARAMETER_DIRECTION ":") - 1) == 0) { + rq->direction = lgs_get_direction(&keyword[sizeof(LQS_PARAMETER_DIRECTION ":") - 1]); + } + else if(strncmp(keyword, LQS_PARAMETER_LAST ":", sizeof(LQS_PARAMETER_LAST ":") - 1) == 0) { + rq->entries = str2ul(&keyword[sizeof(LQS_PARAMETER_LAST ":") - 1]); + } + else if(strncmp(keyword, LQS_PARAMETER_QUERY ":", sizeof(LQS_PARAMETER_QUERY ":") - 1) == 0) { + freez((void *)rq->query); + rq->query= strdupz(&keyword[sizeof(LQS_PARAMETER_QUERY ":") - 1]); + } + else if(strncmp(keyword, LQS_PARAMETER_HISTOGRAM ":", sizeof(LQS_PARAMETER_HISTOGRAM ":") - 1) == 0) { + freez((void *)rq->histogram); + rq->histogram = strdupz(&keyword[sizeof(LQS_PARAMETER_HISTOGRAM ":") - 1]); + } + else if(strncmp(keyword, LQS_PARAMETER_FACETS ":", sizeof(LQS_PARAMETER_FACETS ":") - 1) == 0) { + rq->default_facet = FACET_KEY_OPTION_NONE; + facets_reset_and_disable_all_facets(facets); + + char *value = &keyword[sizeof(LQS_PARAMETER_FACETS ":") - 1]; + if(*value) { + buffer_json_member_add_array(wb, LQS_PARAMETER_FACETS); + + while(value) { + char *sep = strchr(value, ','); + if(sep) + *sep++ = '\0'; + + facets_register_facet_id(facets, value, FACET_KEY_OPTION_FACET|FACET_KEY_OPTION_FTS|FACET_KEY_OPTION_REORDER); + buffer_json_add_array_item_string(wb, value); + + value = sep; + } + + buffer_json_array_close(wb); // facets + } + } + else { + char *value = strchr(keyword, ':'); + if(value) { + *value++ = '\0'; + + buffer_json_member_add_array(wb, keyword); + + while(value) { + char *sep = strchr(value, ','); + if(sep) + *sep++ = '\0'; + + facets_register_facet_filter_id( + facets, keyword, value, + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_REORDER); + + buffer_json_add_array_item_string(wb, value); + rq->filters++; + + value = sep; + } + + buffer_json_array_close(wb); // keyword + } + } + } + + facets_use_hashes_for_ids(facets, true); + rq->fields_are_ids = true; + return true; +} + +static inline void lqs_info_response(BUFFER *wb, FACETS *facets) { + // the buffer already has the request in it + // DO NOT FLUSH IT + + buffer_json_member_add_uint64(wb, "v", 3); + facets_accepted_parameters_to_json_array(facets, wb, false); + buffer_json_member_add_array(wb, "required_params"); + { + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "id", LQS_PARAMETER_SOURCE); + buffer_json_member_add_string(wb, "name", LQS_PARAMETER_SOURCE_NAME); + buffer_json_member_add_string(wb, "help", "Select the logs source to query"); + buffer_json_member_add_string(wb, "type", "multiselect"); + buffer_json_member_add_array(wb, "options"); + { + LQS_FUNCTION_SOURCE_TO_JSON_ARRAY(wb); + } + buffer_json_array_close(wb); // options array + } + buffer_json_object_close(wb); // required params object + } + buffer_json_array_close(wb); // required_params array + + facets_table_config(facets, wb); + + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK); + buffer_json_member_add_string(wb, "type", "table"); + buffer_json_member_add_string(wb, "help", LQS_FUNCTION_DESCRIPTION); + buffer_json_finalize(wb); + + wb->content_type = CT_APPLICATION_JSON; + wb->response_code = HTTP_RESP_OK; +} + +static inline BUFFER *lqs_create_output_buffer(void) { + BUFFER *wb = buffer_create(0, NULL); + buffer_reset(wb); + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY); + return wb; +} + +static inline FACETS *lqs_facets_create(uint32_t items_to_return, FACETS_OPTIONS options, const char *visible_keys, const char *facet_keys, const char *non_facet_keys, bool have_slice) { + FACETS *facets = facets_create(items_to_return, options, + visible_keys, facet_keys, non_facet_keys); + + facets_accepted_param(facets, LQS_PARAMETER_INFO); + facets_accepted_param(facets, LQS_PARAMETER_SOURCE); + facets_accepted_param(facets, LQS_PARAMETER_AFTER); + facets_accepted_param(facets, LQS_PARAMETER_BEFORE); + facets_accepted_param(facets, LQS_PARAMETER_ANCHOR); + facets_accepted_param(facets, LQS_PARAMETER_DIRECTION); + facets_accepted_param(facets, LQS_PARAMETER_LAST); + facets_accepted_param(facets, LQS_PARAMETER_QUERY); + facets_accepted_param(facets, LQS_PARAMETER_FACETS); + facets_accepted_param(facets, LQS_PARAMETER_HISTOGRAM); + facets_accepted_param(facets, LQS_PARAMETER_IF_MODIFIED_SINCE); + facets_accepted_param(facets, LQS_PARAMETER_DATA_ONLY); + facets_accepted_param(facets, LQS_PARAMETER_DELTA); + facets_accepted_param(facets, LQS_PARAMETER_TAIL); + facets_accepted_param(facets, LQS_PARAMETER_SAMPLING); + + if(have_slice) + facets_accepted_param(facets, LQS_PARAMETER_SLICE); + + return facets; +} + +static inline bool lqs_request_parse_and_validate(LOGS_QUERY_STATUS *lqs, BUFFER *wb, char *function, BUFFER *payload, bool have_slice, const char *default_histogram) { + LOGS_QUERY_REQUEST *rq = &lqs->rq; + FACETS *facets = lqs->facets; + + if( (payload && !lqs_request_parse_POST(lqs, wb, payload, rq->transaction)) || + (!payload && !lqs_request_parse_GET(lqs, wb, function)) ) + return false; + + // ---------------------------------------------------------------------------------------------------------------- + // validate parameters + + if(rq->query && !*rq->query) { + freez((void *)rq->query); + rq->query = NULL; + } + + if(rq->histogram && !*rq->histogram) { + freez((void *)rq->histogram); + rq->histogram = NULL; + } + + if(!rq->data_only) + rq->delta = false; + + if(!rq->data_only || !rq->if_modified_since) + rq->tail = false; + + rq->now_s = now_realtime_sec(); + rq->expires_s = rq->now_s + 1; + wb->expires = rq->expires_s; + + if(!rq->after_s && !rq->before_s) { + rq->before_s = rq->now_s; + rq->after_s = rq->before_s - LQS_DEFAULT_QUERY_DURATION; + } + else + rrdr_relative_window_to_absolute(&rq->after_s, &rq->before_s, rq->now_s); + + if(rq->after_s > rq->before_s) { + time_t tmp = rq->after_s; + rq->after_s = rq->before_s; + rq->before_s = tmp; + } + + if(rq->after_s == rq->before_s) + rq->after_s = rq->before_s - LQS_DEFAULT_QUERY_DURATION; + + rq->after_ut = rq->after_s * USEC_PER_SEC; + rq->before_ut = (rq->before_s * USEC_PER_SEC) + USEC_PER_SEC - 1; + + if(!rq->entries) + rq->entries = LQS_DEFAULT_ITEMS_PER_QUERY; + + // ---------------------------------------------------------------------------------------------------------------- + // validate the anchor + + lqs->last_modified = 0; + lqs->anchor.start_ut = lqs->rq.anchor; + lqs->anchor.stop_ut = 0; + + if(lqs->anchor.start_ut && lqs->rq.tail) { + // a tail request + // we need the top X entries from BEFORE + // but, we need to calculate the facets and the + // histogram up to the anchor + lqs->rq.direction = FACETS_ANCHOR_DIRECTION_BACKWARD; + lqs->anchor.start_ut = 0; + lqs->anchor.stop_ut = lqs->rq.anchor; + } + + if(lqs->rq.anchor && lqs->rq.anchor < lqs->rq.after_ut) { + lqs_log_error(lqs, "received anchor is too small for query timeframe, ignoring anchor"); + lqs->rq.anchor = 0; + lqs->anchor.start_ut = 0; + lqs->anchor.stop_ut = 0; + lqs->rq.direction = FACETS_ANCHOR_DIRECTION_BACKWARD; + } + else if(lqs->rq.anchor > lqs->rq.before_ut) { + lqs_log_error(lqs, "received anchor is too big for query timeframe, ignoring anchor"); + lqs->rq.anchor = 0; + lqs->anchor.start_ut = 0; + lqs->anchor.stop_ut = 0; + lqs->rq.direction = FACETS_ANCHOR_DIRECTION_BACKWARD; + } + + facets_set_anchor(facets, lqs->anchor.start_ut, lqs->anchor.stop_ut, lqs->rq.direction); + + facets_set_additional_options(facets, + ((lqs->rq.data_only) ? FACETS_OPTION_DATA_ONLY : 0) | + ((lqs->rq.delta) ? FACETS_OPTION_SHOW_DELTAS : 0)); + + facets_set_items(facets, lqs->rq.entries); + facets_set_query(facets, lqs->rq.query); + + if(lqs->rq.slice && have_slice) + facets_enable_slice_mode(facets); + else + lqs->rq.slice = false; + + if(lqs->rq.histogram) { + if(lqs->rq.fields_are_ids) + facets_set_timeframe_and_histogram_by_id(facets, lqs->rq.histogram, lqs->rq.after_ut, lqs->rq.before_ut); + else + facets_set_timeframe_and_histogram_by_name(facets, lqs->rq.histogram, lqs->rq.after_ut, lqs->rq.before_ut); + } + else if(default_histogram) + facets_set_timeframe_and_histogram_by_name(facets, default_histogram, lqs->rq.after_ut, lqs->rq.before_ut); + + // complete the request object + buffer_json_member_add_boolean(wb, LQS_PARAMETER_INFO, lqs->rq.info); + buffer_json_member_add_boolean(wb, LQS_PARAMETER_SLICE, lqs->rq.slice); + buffer_json_member_add_boolean(wb, LQS_PARAMETER_DATA_ONLY, lqs->rq.data_only); + buffer_json_member_add_boolean(wb, LQS_PARAMETER_DELTA, lqs->rq.delta); + buffer_json_member_add_boolean(wb, LQS_PARAMETER_TAIL, lqs->rq.tail); + buffer_json_member_add_uint64(wb, LQS_PARAMETER_SAMPLING, lqs->rq.sampling); + buffer_json_member_add_uint64(wb, "source_type", lqs->rq.source_type); + buffer_json_member_add_uint64(wb, LQS_PARAMETER_AFTER, lqs->rq.after_ut / USEC_PER_SEC); + buffer_json_member_add_uint64(wb, LQS_PARAMETER_BEFORE, lqs->rq.before_ut / USEC_PER_SEC); + buffer_json_member_add_uint64(wb, "if_modified_since", lqs->rq.if_modified_since); + buffer_json_member_add_uint64(wb, LQS_PARAMETER_ANCHOR, lqs->rq.anchor); + buffer_json_member_add_string(wb, LQS_PARAMETER_DIRECTION, lqs->rq.direction == FACETS_ANCHOR_DIRECTION_FORWARD ? "forward" : "backward"); + buffer_json_member_add_uint64(wb, LQS_PARAMETER_LAST, lqs->rq.entries); + buffer_json_member_add_string(wb, LQS_PARAMETER_QUERY, lqs->rq.query); + buffer_json_member_add_string(wb, LQS_PARAMETER_HISTOGRAM, lqs->rq.histogram); + buffer_json_object_close(wb); // request + + return true; +} + +static inline void lqs_cleanup(LOGS_QUERY_STATUS *lqs) { + freez((void *)lqs->rq.query); + freez((void *)lqs->rq.histogram); + simple_pattern_free(lqs->rq.sources); + facets_destroy(lqs->facets); +} + +#endif //NETDATA_LOGS_QUERY_STATUS_H |