Costa Tsaousis 1 год назад
Родитель
Сommit
41bd902426

+ 1 - 0
Makefile.am

@@ -201,6 +201,7 @@ LIBNETDATA_FILES = \
     libnetdata/string/utf8.h \
     libnetdata/worker_utilization/worker_utilization.c \
     libnetdata/worker_utilization/worker_utilization.h \
+    libnetdata/xxhash.h \
     libnetdata/http/http_defs.h \
     libnetdata/dyn_conf/dyn_conf.c \
     libnetdata/dyn_conf/dyn_conf.h \

+ 5 - 0
REDISTRIBUTED.md

@@ -190,4 +190,9 @@ connectivity is not available.
     Copyright March 2010 by Université de Montréal, Richard Simard and Pierre L'Ecuyer
     [GPL 3.0](https://www.gnu.org/licenses/gpl-3.0.en.html)
 
+-   [xxHash](https://github.com/Cyan4973/xxHash)
+
+    Copyright (c) 2012-2021 Yann Collet
+    [BSD](https://github.com/Cyan4973/xxHash/blob/dev/LICENSE)
+
 

+ 1 - 1
aclk/aclk_query.c

@@ -112,7 +112,7 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query)
     if(web_client_timeout_checkpoint_and_check(w, &t)) {
         netdata_log_access("QUERY CANCELED: QUEUE TIME EXCEEDED %llu ms (LIMIT %d ms)", t / USEC_PER_MS, query->timeout);
         retval = 1;
-        w->response.code = HTTP_RESP_BACKEND_FETCH_FAILED;
+        w->response.code = HTTP_RESP_SERVICE_UNAVAILABLE;
         aclk_http_msg_v2_err(query_thr->client, query->callback_topic, query->msg_id, w->response.code, CLOUD_EC_SND_TIMEOUT, CLOUD_EMSG_SND_TIMEOUT, NULL, 0);
         goto cleanup;
     }

+ 10 - 9
aclk/aclk_tx_msgs.c

@@ -194,15 +194,16 @@ int aclk_http_msg_v2(mqtt_wss_client client, const char *topic, const char *msg_
     int rc = aclk_send_message_with_bin_payload(client, msg, topic, payload, payload_len);
 
     switch (rc) {
-    case HTTP_RESP_FORBIDDEN:
-        aclk_http_msg_v2_err(client, topic, msg_id, rc, CLOUD_EC_REQ_REPLY_TOO_BIG, CLOUD_EMSG_REQ_REPLY_TOO_BIG, NULL, 0);
-        break;
-    case HTTP_RESP_INTERNAL_SERVER_ERROR:
-        aclk_http_msg_v2_err(client, topic, msg_id, rc, CLOUD_EC_FAIL_TOPIC, CLOUD_EMSG_FAIL_TOPIC, payload, payload_len);
-        break;
-    case HTTP_RESP_BACKEND_FETCH_FAILED:
-        aclk_http_msg_v2_err(client, topic, msg_id, rc, CLOUD_EC_SND_TIMEOUT, CLOUD_EMSG_SND_TIMEOUT, payload, payload_len);
-        break;
+        case HTTP_RESP_FORBIDDEN:
+            aclk_http_msg_v2_err(client, topic, msg_id, rc, CLOUD_EC_REQ_REPLY_TOO_BIG, CLOUD_EMSG_REQ_REPLY_TOO_BIG, NULL, 0);
+            break;
+        case HTTP_RESP_INTERNAL_SERVER_ERROR:
+            aclk_http_msg_v2_err(client, topic, msg_id, rc, CLOUD_EC_FAIL_TOPIC, CLOUD_EMSG_FAIL_TOPIC, payload, payload_len);
+            break;
+        case HTTP_RESP_GATEWAY_TIMEOUT:
+        case HTTP_RESP_SERVICE_UNAVAILABLE:
+            aclk_http_msg_v2_err(client, topic, msg_id, rc, CLOUD_EC_SND_TIMEOUT, CLOUD_EMSG_SND_TIMEOUT, payload, payload_len);
+            break;
     }
     return rc ? rc : http_code;
 }

+ 5 - 5
collectors/plugins.d/pluginsd_parser.c

@@ -718,13 +718,13 @@ static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void
                       string2str(pf->function));
 
     // send the command to the plugin
-    int ret = send_to_plugin(buffer, parser);
+    ssize_t ret = send_to_plugin(buffer, parser);
 
     pf->sent_ut = now_realtime_usec();
 
     if(ret < 0) {
-        netdata_log_error("FUNCTION: failed to send function to plugin, error %d", ret);
-        rrd_call_function_error(pf->destination_wb, "Failed to communicate with collector", HTTP_RESP_BACKEND_FETCH_FAILED);
+        netdata_log_error("FUNCTION '%s': failed to send it to the plugin, error %d", string2str(pf->function), ret);
+        rrd_call_function_error(pf->destination_wb, "Failed to communicate with collector", HTTP_RESP_SERVICE_UNAVAILABLE);
     }
     else {
         internal_error(LOG_FUNCTIONS,
@@ -740,8 +740,8 @@ static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void
     ret = send_to_plugin(pf->payload, parser);
 
     if(ret < 0) {
-        netdata_log_error("FUNCTION_PAYLOAD: failed to send function to plugin, error %d", ret);
-        rrd_call_function_error(pf->destination_wb, "Failed to communicate with collector", HTTP_RESP_BACKEND_FETCH_FAILED);
+        netdata_log_error("FUNCTION_PAYLOAD '%s': failed to send function to plugin, error %d", string2str(pf->function), ret);
+        rrd_call_function_error(pf->destination_wb, "Failed to communicate with collector", HTTP_RESP_SERVICE_UNAVAILABLE);
     }
     else {
         internal_error(LOG_FUNCTIONS,

+ 12 - 1
collectors/systemd-journal.plugin/systemd-journal.c

@@ -29,6 +29,7 @@
 #define JOURNAL_PARAMETER_ANCHOR                "anchor"
 #define JOURNAL_PARAMETER_LAST                  "last"
 #define JOURNAL_PARAMETER_QUERY                 "query"
+#define JOURNAL_PARAMETER_HISTOGRAM             "histogram"
 
 #define SYSTEMD_ALWAYS_VISIBLE_KEYS             NULL
 #define SYSTEMD_KEYS_EXCLUDED_FROM_FACETS       NULL
@@ -80,8 +81,10 @@ int systemd_journal_query(BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t be
         r = sd_journal_open(&j, 0);
     }
 
-    if (r < 0)
+    if (r < 0) {
+        netdata_log_error("SYSTEMD-JOURNAL: Failed to open SystemD Journal, with error %d", r);
         return HTTP_RESP_INTERNAL_SERVER_ERROR;
+    }
 
     facets_rows_begin(facets);
 
@@ -350,6 +353,7 @@ static void function_systemd_journal(const char *transaction, char *function, ch
     facets_accepted_param(facets, JOURNAL_PARAMETER_ANCHOR);
     facets_accepted_param(facets, JOURNAL_PARAMETER_LAST);
     facets_accepted_param(facets, JOURNAL_PARAMETER_QUERY);
+    facets_accepted_param(facets, JOURNAL_PARAMETER_HISTOGRAM);
 
     // register the fields in the order you want them on the dashboard
 
@@ -379,6 +383,7 @@ static void function_systemd_journal(const char *transaction, char *function, ch
     usec_t anchor = 0;
     size_t last = 0;
     const char *query = NULL;
+    const char *chart = NULL;
 
     buffer_json_member_add_object(wb, "request");
     buffer_json_member_add_object(wb, "filters");
@@ -406,6 +411,9 @@ static void function_systemd_journal(const char *transaction, char *function, ch
         else if(strncmp(keyword, JOURNAL_PARAMETER_QUERY ":", strlen(JOURNAL_PARAMETER_QUERY ":")) == 0) {
             query= &keyword[strlen(JOURNAL_PARAMETER_QUERY ":")];
         }
+        else if(strncmp(keyword, JOURNAL_PARAMETER_HISTOGRAM ":", strlen(JOURNAL_PARAMETER_HISTOGRAM ":")) == 0) {
+            chart = &keyword[strlen(JOURNAL_PARAMETER_HISTOGRAM ":")];
+        }
         else {
             char *value = strchr(keyword, ':');
             if(value) {
@@ -459,12 +467,15 @@ static void function_systemd_journal(const char *transaction, char *function, ch
     buffer_json_member_add_uint64(wb, "anchor", anchor);
     buffer_json_member_add_uint64(wb, "last", last);
     buffer_json_member_add_string(wb, "query", query);
+    buffer_json_member_add_string(wb, "chart", chart);
     buffer_json_member_add_time_t(wb, "timeout", timeout);
     buffer_json_object_close(wb); // request
 
     facets_set_items(facets, last);
     facets_set_anchor(facets, anchor);
     facets_set_query(facets, query);
+    facets_set_histogram(facets, chart ? chart : "PRIORITY", after_s * USEC_PER_SEC, before_s * USEC_PER_SEC);
+
     int response = systemd_journal_query(wb, facets, after_s * USEC_PER_SEC, before_s * USEC_PER_SEC,
                                        now_monotonic_usec() + (timeout - 1) * USEC_PER_SEC);
 

+ 1 - 1
database/contexts/api_v2.c

@@ -2023,7 +2023,7 @@ int rrdcontext_to_json_v2(BUFFER *wb, struct api_v2_contexts_request *req, CONTE
         }
         else {
             buffer_strcat(wb, "query interrupted");
-            resp = HTTP_RESP_BACKEND_FETCH_FAILED;
+            resp = HTTP_RESP_CLIENT_CLOSED_REQUEST;
         }
         goto cleanup;
     }

+ 1 - 1
database/rrdfunctions.c

@@ -587,7 +587,7 @@ static int rrd_call_function_find(RRDHOST *host, BUFFER *wb, const char *name, s
         return rrd_call_function_error(wb, "No collector is supplying this function on this host at this time.", HTTP_RESP_NOT_FOUND);
 
     if(!(*rdcf)->collector->running)
-        return rrd_call_function_error(wb, "The collector that registered this function, is not currently running.", HTTP_RESP_BACKEND_FETCH_FAILED);
+        return rrd_call_function_error(wb, "The collector that registered this function, is not currently running.", HTTP_RESP_SERVICE_UNAVAILABLE);
 
     return HTTP_RESP_OK;
 }

+ 2 - 8
libnetdata/buffer/buffer.h

@@ -252,17 +252,11 @@ static inline void buffer_strcat(BUFFER *wb, const char *txt) {
 static inline void buffer_strncat(BUFFER *wb, const char *txt, size_t len) {
     if(unlikely(!txt || !*txt)) return;
 
-    const char *t = txt;
     buffer_need_bytes(wb, len + 1);
-    char *s = &wb->buffer[wb->len];
-    char *d = s;
-    const char *e = &wb->buffer[wb->len + len];
 
-    while(*t && d < e)
-        *d++ = *t++;
-
-    wb->len += d - s;
+    memcpy(&wb->buffer[wb->len], txt, len);
 
+    wb->len += len;
     wb->buffer[wb->len] = '\0';
 
     buffer_overflow_check(wb);

+ 585 - 45
libnetdata/facets/facets.c

@@ -6,55 +6,23 @@ static void facets_row_free(FACETS *facets __maybe_unused, FACET_ROW *row);
 
 // ----------------------------------------------------------------------------
 
-time_t calculate_bar_width(time_t before, time_t after) {
-    // Array of valid durations in seconds
-    static time_t valid_durations[] = {
-            1,
-            15,
-            30,
-            1 * 60, 2 * 60, 3 * 60, 5 * 60, 10 * 60, 15 * 60, 30 * 60,          // minutes
-            1 * 3600, 2 * 3600, 6 * 3600, 8 * 3600, 12 * 3600,                  // hours
-            1 * 86400, 2 * 86400, 3 * 86400, 5 * 86400, 7 * 86400, 14 * 86400,  // days
-            1 * (30*86400)                                                      // months
-    };
-    static int array_size = sizeof(valid_durations) / sizeof(valid_durations[0]);
-
-    time_t duration = before - after;
-    time_t bar_width = 1;
-
-    for (int i = array_size - 1; i >= 0; --i) {
-        if (duration / valid_durations[i] >= HISTOGRAM_COLUMNS) {
-            bar_width = valid_durations[i];
-            break;
-        }
-    }
-
-    return bar_width;
-}
-
-// ----------------------------------------------------------------------------
-
-static inline void uint32_to_char(uint32_t num, char *out) {
-    static char id_encoding_characters[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz_0123456789";
+static inline void uint64_to_char(uint64_t num, char *out) {
+    static const char id_encoding_characters[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz_0123456789";
 
     int i;
-    for(i = 5; i >= 0; --i) {
+    for(i = 10; i >= 0; --i) {
         out[i] = id_encoding_characters[num & 63];
         num >>= 6;
     }
-    out[6] = '\0';
 }
 
-inline void facets_string_hash(const char *src, char *out) {
-    uint32_t hash1 = fnv1a_hash32(src);
-    uint32_t hash2 = djb2_hash32(src);
-    uint32_t hash3 = larson_hash32(src);
+inline void facets_string_hash(const char *src, size_t len, char *out) {
+    XXH128_hash_t hash = XXH128(src, len, 0);
 
-    uint32_to_char(hash1, out);
-    uint32_to_char(hash2, &out[6]);
-    uint32_to_char(hash3, &out[12]);
+    uint64_to_char(hash.high64, out);
+    uint64_to_char(hash.low64, &out[11]);  // Starts right after the first 64-bit encoded string
 
-    out[18] = '\0';
+    out[FACET_STRING_HASH_SIZE - 1] = '\0';
 }
 
 // ----------------------------------------------------------------------------
@@ -66,6 +34,9 @@ typedef struct facet_value {
 
     uint32_t rows_matching_facet_value;
     uint32_t final_facet_value_counter;
+
+    uint32_t *histogram;
+    uint32_t min, max, sum;
 } FACET_VALUE;
 
 struct facet_key {
@@ -124,6 +95,15 @@ struct facets {
     uint32_t max_items_to_return;
     uint32_t order;
 
+    struct {
+        char *chart;
+        bool enabled;
+        uint32_t slots;
+        usec_t slot_width;
+        usec_t after_ut;
+        usec_t before_ut;
+    } histogram;
+
     struct {
         FACET_ROW *last_added;
 
@@ -143,6 +123,515 @@ struct facets {
 
 // ----------------------------------------------------------------------------
 
+static usec_t calculate_histogram_bar_width(usec_t after_ut, usec_t before_ut) {
+    // Array of valid durations in seconds
+    static time_t valid_durations[] = {
+            1,
+            15,
+            30,
+            1 * 60, 2 * 60, 3 * 60, 5 * 60, 10 * 60, 15 * 60, 30 * 60,          // minutes
+            1 * 3600, 2 * 3600, 6 * 3600, 8 * 3600, 12 * 3600,                  // hours
+            1 * 86400, 2 * 86400, 3 * 86400, 5 * 86400, 7 * 86400, 14 * 86400,  // days
+            1 * (30*86400)                                                      // months
+    };
+    static int array_size = sizeof(valid_durations) / sizeof(valid_durations[0]);
+
+    usec_t duration = before_ut - after_ut;
+    usec_t bar_width = 1 * 60;
+
+    for (int i = array_size - 1; i >= 0; --i) {
+        if (duration / (valid_durations[i] * 60) >= HISTOGRAM_COLUMNS) {
+            bar_width = valid_durations[i] * 60;
+            break;
+        }
+    }
+
+    return bar_width;
+}
+
+static inline usec_t facets_histogram_slot_baseline_ut(FACETS *facets, usec_t ut) {
+    usec_t delta = ut % facets->histogram.slot_width;
+    return ut - delta;
+}
+
+void facets_set_histogram(FACETS *facets, const char *chart, usec_t after_ut, usec_t before_ut) {
+    facets->histogram.enabled = true;
+    facets->histogram.chart = chart ? strdupz(chart) : NULL;
+    facets->histogram.slot_width = calculate_histogram_bar_width(after_ut, before_ut);
+    facets->histogram.after_ut = facets_histogram_slot_baseline_ut(facets, after_ut);
+    facets->histogram.before_ut = facets_histogram_slot_baseline_ut(facets, before_ut) + facets->histogram.slot_width;
+    facets->histogram.slots = (facets->histogram.before_ut - facets->histogram.after_ut) / facets->histogram.slot_width + 1;
+}
+
+static inline void facets_histogram_update_value(FACETS *facets, FACET_KEY *k, FACET_VALUE *v, usec_t usec) {
+    if(!facets->histogram.enabled)
+        return;
+
+    if(unlikely(!v->histogram))
+        v->histogram = callocz(facets->histogram.slots, sizeof(*v->histogram));
+
+    usec_t base_ut = facets_histogram_slot_baseline_ut(facets, usec);
+
+    if(base_ut < facets->histogram.after_ut)
+        base_ut = facets->histogram.after_ut;
+
+    if(base_ut > facets->histogram.before_ut)
+        base_ut = facets->histogram.before_ut;
+
+    uint32_t slot = (base_ut - facets->histogram.after_ut) / facets->histogram.slot_width;
+
+    if(unlikely(slot >= facets->histogram.slots))
+        slot = facets->histogram.slots - 1;
+
+    v->histogram[slot]++;
+}
+
+static inline void facets_histogram_value_names(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
+    buffer_json_member_add_array(wb, key);
+    {
+        FACET_VALUE *v;
+        dfe_start_read(k->values, v) {
+            if(unlikely(!v->histogram))
+                continue;
+
+            buffer_json_add_array_item_string(wb, v->name);
+        }
+        dfe_done(v);
+    }
+    buffer_json_array_close(wb); // key
+}
+
+static inline void facets_histogram_value_units(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
+    buffer_json_member_add_array(wb, key);
+    {
+        FACET_VALUE *v;
+        dfe_start_read(k->values, v) {
+            if(unlikely(!v->histogram))
+                continue;
+
+            buffer_json_add_array_item_string(wb, "events");
+        }
+        dfe_done(v);
+    }
+    buffer_json_array_close(wb); // key
+}
+
+static inline void facets_histogram_value_min(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
+    buffer_json_member_add_array(wb, key);
+    {
+        FACET_VALUE *v;
+        dfe_start_read(k->values, v) {
+            if(unlikely(!v->histogram))
+                continue;
+
+            buffer_json_add_array_item_uint64(wb, v->min);
+        }
+        dfe_done(v);
+    }
+    buffer_json_array_close(wb); // key
+}
+
+static inline void facets_histogram_value_max(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
+    buffer_json_member_add_array(wb, key);
+    {
+        FACET_VALUE *v;
+        dfe_start_read(k->values, v) {
+                    if(unlikely(!v->histogram))
+                        continue;
+
+                    buffer_json_add_array_item_uint64(wb, v->max);
+                }
+        dfe_done(v);
+    }
+    buffer_json_array_close(wb); // key
+}
+
+static inline void facets_histogram_value_avg(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
+    buffer_json_member_add_array(wb, key);
+    {
+        FACET_VALUE *v;
+        dfe_start_read(k->values, v) {
+            if(unlikely(!v->histogram))
+                continue;
+
+            buffer_json_add_array_item_double(wb, (double)v->sum / (double)facets->histogram.slots);
+        }
+        dfe_done(v);
+    }
+    buffer_json_array_close(wb); // key
+}
+
+static inline void facets_histogram_value_arp(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
+    buffer_json_member_add_array(wb, key);
+    {
+        FACET_VALUE *v;
+        dfe_start_read(k->values, v) {
+            if(unlikely(!v->histogram))
+                continue;
+
+            buffer_json_add_array_item_uint64(wb, 0);
+        }
+        dfe_done(v);
+    }
+    buffer_json_array_close(wb); // key
+}
+
+static inline void facets_histogram_value_con(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key, uint32_t sum) {
+    buffer_json_member_add_array(wb, key);
+    {
+        FACET_VALUE *v;
+        dfe_start_read(k->values, v) {
+            if(unlikely(!v->histogram))
+                continue;
+
+            buffer_json_add_array_item_double(wb, (double)v->sum * 100.0 / (double)sum);
+        }
+        dfe_done(v);
+    }
+    buffer_json_array_close(wb); // key
+}
+
+static void facets_histogram_generate(FACETS *facets, FACET_KEY *k, BUFFER *wb) {
+    size_t dimensions = 0;
+    uint32_t min = UINT32_MAX, max = 0, sum = 0, count = 0;
+
+    {
+        FACET_VALUE *v;
+        dfe_start_read(k->values, v){
+            if (unlikely(!v->histogram))
+                continue;
+
+            dimensions++;
+
+            v->min = UINT32_MAX;
+            v->max = 0;
+            v->sum = 0;
+
+            for(uint32_t i = 0; i < facets->histogram.slots ;i++) {
+                uint32_t n = v->histogram[i];
+
+                if(n < min)
+                    min = n;
+
+                if(n > max)
+                    max = n;
+
+                sum += n;
+                count++;
+
+                if(n < v->min)
+                    v->min = n;
+
+                if(n > v->max)
+                    v->max = n;
+
+                v->sum += n;
+            }
+        }
+        dfe_done(v);
+    }
+
+    if(!dimensions)
+        return;
+
+    buffer_json_member_add_object(wb, "summary");
+    {
+        buffer_json_member_add_array(wb, "nodes");
+        {
+            buffer_json_add_array_item_object(wb); // node
+            {
+                buffer_json_member_add_string(wb, "mg", "default");
+                buffer_json_member_add_string(wb, "nm", "facets.histogram");
+                buffer_json_member_add_uint64(wb, "ni", 0);
+                buffer_json_member_add_object(wb, "st");
+                {
+                    buffer_json_member_add_uint64(wb, "ai", 0);
+                    buffer_json_member_add_uint64(wb, "code", 200);
+                    buffer_json_member_add_string(wb, "msg", "");
+                }
+                buffer_json_object_close(wb); // st
+                buffer_json_member_add_object(wb, "is");
+                {
+                    buffer_json_member_add_uint64(wb, "sl", 1);
+                    buffer_json_member_add_uint64(wb, "qr", 1);
+                }
+                buffer_json_object_close(wb); // is
+                buffer_json_member_add_object(wb, "ds");
+                {
+                    buffer_json_member_add_uint64(wb, "sl", dimensions);
+                    buffer_json_member_add_uint64(wb, "qr", dimensions);
+                }
+                buffer_json_object_close(wb); // ds
+                buffer_json_member_add_object(wb, "sts");
+                {
+                    buffer_json_member_add_uint64(wb, "min", min);
+                    buffer_json_member_add_uint64(wb, "max", max);
+                    buffer_json_member_add_double(wb, "avg", (double)sum / (double)count);
+                    buffer_json_member_add_double(wb, "con", 100.0);
+                }
+                buffer_json_object_close(wb); // sts
+            }
+            buffer_json_object_close(wb); // node
+        }
+        buffer_json_array_close(wb); // nodes
+
+        buffer_json_member_add_array(wb, "contexts");
+        {
+            buffer_json_add_array_item_object(wb); // context
+            {
+                buffer_json_member_add_string(wb, "id", "facets.histogram");
+                buffer_json_member_add_object(wb, "is");
+                {
+                    buffer_json_member_add_uint64(wb, "sl", 1);
+                    buffer_json_member_add_uint64(wb, "qr", 1);
+                }
+                buffer_json_object_close(wb); // is
+                buffer_json_member_add_object(wb, "ds");
+                {
+                    buffer_json_member_add_uint64(wb, "sl", dimensions);
+                    buffer_json_member_add_uint64(wb, "qr", dimensions);
+                }
+                buffer_json_object_close(wb); // ds
+                buffer_json_member_add_object(wb, "sts");
+                {
+                    buffer_json_member_add_uint64(wb, "min", min);
+                    buffer_json_member_add_uint64(wb, "max", max);
+                    buffer_json_member_add_double(wb, "avg", (double)sum / (double)count);
+                    buffer_json_member_add_double(wb, "con", 100.0);
+                }
+                buffer_json_object_close(wb); // sts
+            }
+            buffer_json_object_close(wb); // context
+        }
+        buffer_json_array_close(wb); // contexts
+
+        buffer_json_member_add_array(wb, "instances");
+        {
+            buffer_json_add_array_item_object(wb); // instance
+            {
+                buffer_json_member_add_string(wb, "id", "facets.histogram");
+                buffer_json_member_add_uint64(wb, "ni", 0);
+                buffer_json_member_add_object(wb, "ds");
+                {
+                    buffer_json_member_add_uint64(wb, "sl", dimensions);
+                    buffer_json_member_add_uint64(wb, "qr", dimensions);
+                }
+                buffer_json_object_close(wb); // ds
+                buffer_json_member_add_object(wb, "sts");
+                {
+                    buffer_json_member_add_uint64(wb, "min", min);
+                    buffer_json_member_add_uint64(wb, "max", max);
+                    buffer_json_member_add_double(wb, "avg", (double)sum / (double)count);
+                    buffer_json_member_add_double(wb, "con", 100.0);
+                }
+                buffer_json_object_close(wb); // sts
+            }
+            buffer_json_object_close(wb); // instance
+        }
+        buffer_json_array_close(wb); // instances
+
+        buffer_json_member_add_array(wb, "dimensions");
+        {
+            size_t pri = 0;
+            FACET_VALUE *v;
+            dfe_start_read(k->values, v) {
+                if(unlikely(!v->histogram))
+                    continue;
+
+                buffer_json_add_array_item_object(wb); // dimension
+                {
+                    buffer_json_member_add_string(wb, "id", v->name);
+                    buffer_json_member_add_object(wb, "ds");
+                    {
+                        buffer_json_member_add_uint64(wb, "sl", 1);
+                        buffer_json_member_add_uint64(wb, "qr", 1);
+                    }
+                    buffer_json_object_close(wb); // ds
+                    buffer_json_member_add_object(wb, "sts");
+                    {
+                        buffer_json_member_add_uint64(wb, "min", v->min);
+                        buffer_json_member_add_uint64(wb, "max", v->max);
+                        buffer_json_member_add_double(wb, "avg", (double)v->sum / (double)facets->histogram.slots);
+                        buffer_json_member_add_double(wb, "con", (double)v->sum * 100.0 / (double)sum);
+                    }
+                    buffer_json_object_close(wb); // sts
+                    buffer_json_member_add_uint64(wb, "pri", pri++);
+                }
+                buffer_json_object_close(wb); // dimension
+            }
+            dfe_done(v);
+        }
+        buffer_json_array_close(wb); // dimensions
+
+        buffer_json_member_add_array(wb, "labels");
+        buffer_json_array_close(wb); // labels
+
+        buffer_json_member_add_array(wb, "alerts");
+        buffer_json_array_close(wb); // alerts
+    }
+    buffer_json_object_close(wb); // summary
+
+    buffer_json_member_add_object(wb, "totals");
+    {
+        buffer_json_member_add_object(wb, "nodes");
+        {
+            buffer_json_member_add_uint64(wb, "sl", 1);
+            buffer_json_member_add_uint64(wb, "qr", 1);
+        }
+        buffer_json_object_close(wb); // nodes;
+        buffer_json_member_add_object(wb, "contexts");
+        {
+            buffer_json_member_add_uint64(wb, "sl", 1);
+            buffer_json_member_add_uint64(wb, "qr", 1);
+        }
+        buffer_json_object_close(wb); // contexts;
+        buffer_json_member_add_object(wb, "dimensions");
+        {
+            buffer_json_member_add_uint64(wb, "sl", dimensions);
+            buffer_json_member_add_uint64(wb, "qr", dimensions);
+        }
+        buffer_json_object_close(wb); // contexts;
+    }
+    buffer_json_object_close(wb); // totals
+
+    buffer_json_member_add_object(wb, "result");
+    {
+        facets_histogram_value_names(wb, facets, k, "labels");
+
+        buffer_json_member_add_object(wb, "point");
+        {
+            buffer_json_member_add_uint64(wb, "value", 0);
+            buffer_json_member_add_uint64(wb, "arp", 1);
+            buffer_json_member_add_uint64(wb, "pa", 2);
+        }
+        buffer_json_object_close(wb); // point
+
+        buffer_json_member_add_array(wb, "data");
+        {
+            usec_t t = facets->histogram.after_ut;
+            for(uint32_t i = 0; i < facets->histogram.slots ;i++) {
+                buffer_json_add_array_item_array(wb); // row
+                {
+                    buffer_json_add_array_item_time_ms(wb, t / USEC_PER_SEC);
+
+                    FACET_VALUE *v;
+                    dfe_start_read(k->values, v) {
+                        if(unlikely(!v->histogram))
+                            continue;
+
+                        buffer_json_add_array_item_array(wb); // point
+
+                        buffer_json_add_array_item_uint64(wb, v->histogram[i]);
+                        buffer_json_add_array_item_uint64(wb, 0);
+                        buffer_json_add_array_item_uint64(wb, 1);
+
+                        buffer_json_array_close(wb); // point
+                    }
+                    dfe_done(v);
+                }
+                buffer_json_array_close(wb); // row
+
+                t += facets->histogram.slot_width;
+            }
+        }
+        buffer_json_array_close(wb); //data
+    }
+    buffer_json_object_close(wb); // result
+
+    buffer_json_member_add_object(wb, "db");
+    {
+        buffer_json_member_add_uint64(wb, "tiers", 1);
+        buffer_json_member_add_uint64(wb, "update_every", 1);
+        buffer_json_member_add_time_t(wb, "first_entry", facets->histogram.after_ut / USEC_PER_SEC);
+        buffer_json_member_add_time_t(wb, "last_entry", facets->histogram.before_ut / USEC_PER_SEC);
+        buffer_json_member_add_string(wb, "units", "events");
+        buffer_json_member_add_object(wb, "dimensions");
+        {
+            facets_histogram_value_names(wb, facets, k, "ids");
+            facets_histogram_value_units(wb, facets, k, "units");
+
+            buffer_json_member_add_object(wb, "sts");
+            {
+                facets_histogram_value_min(wb, facets, k, "min");
+                facets_histogram_value_max(wb, facets, k, "max");
+                facets_histogram_value_avg(wb, facets, k, "avg");
+                facets_histogram_value_arp(wb, facets, k, "arp");
+                facets_histogram_value_con(wb, facets, k, "con", sum);
+            }
+            buffer_json_object_close(wb); // sts
+        }
+        buffer_json_object_close(wb); // dimensions
+
+        buffer_json_member_add_array(wb, "per_tier");
+        {
+            buffer_json_add_array_item_object(wb); // tier0
+            {
+                buffer_json_member_add_uint64(wb, "tier", 0);
+                buffer_json_member_add_uint64(wb, "queries", 1);
+                buffer_json_member_add_uint64(wb, "points", count);
+                buffer_json_member_add_time_t(wb, "update_every", 1);
+                buffer_json_member_add_time_t(wb, "first_entry", facets->histogram.after_ut / USEC_PER_SEC);
+                buffer_json_member_add_time_t(wb, "last_entry", facets->histogram.before_ut / USEC_PER_SEC);
+            }
+            buffer_json_object_close(wb); // tier0
+        }
+        buffer_json_array_close(wb); // per_tier
+    }
+    buffer_json_object_close(wb); // db
+
+    buffer_json_member_add_object(wb, "view");
+    {
+        buffer_json_member_add_string(wb, "title", "Events Distribution");
+        buffer_json_member_add_time_t(wb, "update_every", 1);
+        buffer_json_member_add_time_t(wb, "after", facets->histogram.after_ut / USEC_PER_SEC);
+        buffer_json_member_add_time_t(wb, "before", facets->histogram.before_ut / USEC_PER_SEC);
+        buffer_json_member_add_string(wb, "units", "events");
+        buffer_json_member_add_string(wb, "chart_type", "stacked");
+        buffer_json_member_add_object(wb, "dimensions");
+        {
+            buffer_json_member_add_array(wb, "grouped_by");
+            {
+                buffer_json_add_array_item_string(wb, "dimension");
+            }
+            buffer_json_array_close(wb); // grouped_by
+
+            facets_histogram_value_names(wb, facets, k, "ids");
+            facets_histogram_value_names(wb, facets, k, "names");
+            facets_histogram_value_units(wb, facets, k, "units");
+
+            buffer_json_member_add_object(wb, "sts");
+            {
+                facets_histogram_value_min(wb, facets, k, "min");
+                facets_histogram_value_max(wb, facets, k, "max");
+                facets_histogram_value_avg(wb, facets, k, "avg");
+                facets_histogram_value_arp(wb, facets, k, "arp");
+                facets_histogram_value_con(wb, facets, k, "con", sum);
+            }
+            buffer_json_object_close(wb); // sts
+        }
+        buffer_json_object_close(wb); // dimensions
+
+        buffer_json_member_add_uint64(wb, "min", min);
+        buffer_json_member_add_uint64(wb, "max", max);
+    }
+    buffer_json_object_close(wb); // view
+
+    buffer_json_member_add_array(wb, "agents");
+    {
+        buffer_json_add_array_item_object(wb); // agent
+        {
+            buffer_json_member_add_string(wb, "mg", "default");
+            buffer_json_member_add_string(wb, "nm", "facets.histogram");
+            buffer_json_member_add_time_t(wb, "now", now_realtime_sec());
+            buffer_json_member_add_uint64(wb, "ai", 0);
+        }
+        buffer_json_object_close(wb); // agent
+    }
+    buffer_json_array_close(wb); // agents
+}
+
+// ----------------------------------------------------------------------------
+
 static inline void facet_value_is_used(FACET_KEY *k, FACET_VALUE *v) {
     if(!k->key_found_in_row)
         v->rows_matching_facet_value++;
@@ -218,7 +707,7 @@ static bool facet_value_conflict_callback(const DICTIONARY_ITEM *item __maybe_un
     if(v->name)
         facet_value_is_used(k, v);
 
-    internal_fatal(v->name && strcmp(v->name, nv->name) != 0, "hash conflict: '%s' and '%s' have the same hash '%s'", v->name, nv->name,
+    internal_fatal(v->name && nv->name && strcmp(v->name, nv->name) != 0, "value hash conflict: '%s' and '%s' have the same hash '%s'", v->name, nv->name,
                    dictionary_acquired_item_name(item));
 
     return false;
@@ -226,6 +715,7 @@ static bool facet_value_conflict_callback(const DICTIONARY_ITEM *item __maybe_un
 
 static void facet_value_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
     FACET_VALUE *v = value;
+    freez(v->histogram);
     freez((char *)v->name);
 }
 
@@ -278,6 +768,9 @@ static bool facet_key_conflict_callback(const DICTIONARY_ITEM *item __maybe_unus
         facet_key_late_init(facets, k);
     }
 
+    internal_fatal(k->name && nk->name && strcmp(k->name, nk->name) != 0, "key hash conflict: '%s' and '%s' have the same hash '%s'", k->name, nk->name,
+                   dictionary_acquired_item_name(item));
+
     if(k->options & FACET_KEY_OPTION_REORDER) {
         k->order = facets->order++;
         k->options &= ~FACET_KEY_OPTION_REORDER;
@@ -337,6 +830,7 @@ void facets_destroy(FACETS *facets) {
         facets_row_free(facets, r);
     }
 
+    freez(facets->histogram.chart);
     freez(facets);
 }
 
@@ -354,7 +848,7 @@ inline FACET_KEY *facets_register_key(FACETS *facets, const char *key, FACET_KEY
             .default_selected_for_values = true,
     };
     char hash[FACET_STRING_HASH_SIZE];
-    facets_string_hash(tk.name, hash);
+    facets_string_hash(tk.name, strlen(key), hash);
     return dictionary_set(facets->keys, hash, &tk, sizeof(tk));
 }
 
@@ -414,7 +908,7 @@ static inline void facets_check_value(FACETS *facets __maybe_unused, FACET_KEY *
         k->transform.cb(facets, k->current_value.b, k->transform.data);
 
     if(!k->current_value.updated) {
-        buffer_strcat(k->current_value.b, FACET_VALUE_UNSET);
+        buffer_fast_strcat(k->current_value.b, FACET_VALUE_UNSET, sizeof(FACET_VALUE_UNSET) - 1);
         k->current_value.updated = true;
     }
 
@@ -431,7 +925,7 @@ static inline void facets_check_value(FACETS *facets __maybe_unused, FACET_KEY *
         FACET_VALUE tk = {
             .name = buffer_tostring(k->current_value.b),
         };
-        facets_string_hash(tk.name, k->current_value.hash);
+        facets_string_hash(tk.name, buffer_strlen(k->current_value.b), k->current_value.hash);
         dictionary_set(k->values, k->current_value.hash, &tk, sizeof(tk));
     }
     else {
@@ -652,10 +1146,13 @@ void facets_row_finished(FACETS *facets, usec_t usec) {
             if(counted_by == total_keys) {
                 if(k->values) {
                     if(!k->current_value.hash[0])
-                        facets_string_hash(buffer_tostring(k->current_value.b), k->current_value.hash);
+                        facets_string_hash(buffer_tostring(k->current_value.b), buffer_strlen(k->current_value.b), k->current_value.hash);
 
                     FACET_VALUE *v = dictionary_get(k->values, k->current_value.hash);
                     v->final_facet_value_counter++;
+
+                    if(selected_by == total_keys)
+                        facets_histogram_update_value(facets, k, v, usec);
                 }
 
                 found++;
@@ -826,6 +1323,49 @@ void facets_report(FACETS *facets, BUFFER *wb) {
     buffer_json_member_add_array(wb, "default_charts");
     buffer_json_array_close(wb);
 
+    if(facets->histogram.enabled) {
+        const char *first_histogram = NULL;
+        buffer_json_member_add_array(wb, "available_histograms");
+        {
+            FACET_KEY *k;
+            dfe_start_read(facets->keys, k) {
+                if (!k->values)
+                    continue;
+
+                if(unlikely(!first_histogram))
+                    first_histogram = k_dfe.name;
+
+                buffer_json_add_array_item_object(wb);
+                buffer_json_member_add_string(wb, "id", k_dfe.name);
+                buffer_json_member_add_string(wb, "name", k->name);
+                buffer_json_object_close(wb);
+            }
+            dfe_done(k);
+        }
+        buffer_json_array_close(wb);
+
+        {
+            const char *id = facets->histogram.chart;
+            FACET_KEY *k = dictionary_get(facets->keys, id);
+            if(!k || !k->values) {
+                id = first_histogram;
+                k = dictionary_get(facets->keys, id);
+            }
+
+            if(k && k->values) {
+                buffer_json_member_add_object(wb, "histogram");
+                {
+                    buffer_json_member_add_string(wb, "id", id);
+                    buffer_json_member_add_string(wb, "name", k->name);
+                    buffer_json_member_add_object(wb, "chart");
+                    facets_histogram_generate(facets, k, wb);
+                    buffer_json_object_close(wb);
+                }
+                buffer_json_object_close(wb); // histogram
+            }
+        }
+    }
+
     buffer_json_member_add_object(wb, "items");
     {
         buffer_json_member_add_uint64(wb, "evaluated", facets->operations.evaluated);

Некоторые файлы не были показаны из-за большого количества измененных файлов