summaryrefslogtreecommitdiffstats
path: root/src/libnetdata/facets
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-11-09 08:36:11 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-11-09 08:37:11 +0000
commit910c794ec6d0a364b4aabccf22b715cb45780e83 (patch)
tree561a9ef6b6a4668102674e1a52b3e7563c57ac61 /src/libnetdata/facets
parentReleasing debian version 1.47.5-1. (diff)
downloadnetdata-debian.tar.xz
netdata-debian.zip
Merging upstream version 2.0.0 (Closes: #923993, #1042533, #1045145).HEADdebian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libnetdata/facets')
-rw-r--r--src/libnetdata/facets/facets.c277
-rw-r--r--src/libnetdata/facets/facets.h35
-rw-r--r--src/libnetdata/facets/logs_query_status.h868
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