summaryrefslogtreecommitdiffstats
path: root/src/health/health_variable.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/health/health_variable.c')
-rw-r--r--src/health/health_variable.c466
1 files changed, 466 insertions, 0 deletions
diff --git a/src/health/health_variable.c b/src/health/health_variable.c
new file mode 100644
index 000000000..69637de64
--- /dev/null
+++ b/src/health/health_variable.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "health.h"
+#include "health_internals.h"
+
+struct variable_lookup_score {
+ RRDSET *st;
+ const char *source;
+ NETDATA_DOUBLE value;
+ size_t score;
+};
+
+struct variable_lookup_job {
+ RRDCALC *rc;
+ RRDHOST *host;
+ STRING *variable;
+ STRING *dim;
+ const char *dimension;
+ size_t dimension_length;
+ enum {
+ DIM_SELECT_NORMAL,
+ DIM_SELECT_RAW,
+ DIM_SELECT_LAST_COLLECTED,
+ } dimension_selection;
+
+ struct {
+ size_t size;
+ size_t used;
+ struct variable_lookup_score *array;
+ } result;
+
+ struct {
+ RRDSET *last_rrdset;
+ size_t last_score;
+ } score;
+};
+
+static void variable_lookup_add_result_with_score(struct variable_lookup_job *vbd, NETDATA_DOUBLE n, RRDSET *st, const char *source __maybe_unused) {
+ if(vbd->score.last_rrdset != st) {
+ vbd->score.last_rrdset = st;
+ vbd->score.last_score = rrdlabels_common_count(vbd->rc->rrdset->rrdlabels, st->rrdlabels);
+ }
+
+ if(vbd->result.used >= vbd->result.size) {
+ if(!vbd->result.size)
+ vbd->result.size = 1;
+
+ vbd->result.size *= 2;
+ vbd->result.array = reallocz(vbd->result.array, sizeof(struct variable_lookup_score) * vbd->result.size);
+ }
+
+ vbd->result.array[vbd->result.used++] = (struct variable_lookup_score) {
+ .value = n,
+ .score = vbd->score.last_score,
+ .st = st,
+ .source = source,
+ };
+}
+
+static bool variable_lookup_in_chart(struct variable_lookup_job *vbd, RRDSET *st, bool stop_on_match) {
+ bool found = false;
+ const DICTIONARY_ITEM *item = NULL;
+ RRDDIM *rd = NULL;
+ dfe_start_read(st->rrddim_root_index, rd) {
+ if(rd->id == vbd->dim || rd->name == vbd->dim) {
+ item = dictionary_acquired_item_dup(st->rrddim_root_index, rd_dfe.item);
+ break;
+ }
+ }
+ dfe_done(rd);
+
+ if (item) {
+ switch (vbd->dimension_selection) {
+ case DIM_SELECT_NORMAL:
+ variable_lookup_add_result_with_score(vbd, (NETDATA_DOUBLE)rd->collector.last_stored_value, st, "last stored value of dimension");
+ break;
+ case DIM_SELECT_RAW:
+ variable_lookup_add_result_with_score(vbd, (NETDATA_DOUBLE)rd->collector.last_collected_value, st, "last collected value of dimension");
+ break;
+ case DIM_SELECT_LAST_COLLECTED:
+ variable_lookup_add_result_with_score(vbd, (NETDATA_DOUBLE)rd->collector.last_collected_time.tv_sec, st, "last collected time of dimension");
+ break;
+ }
+
+ dictionary_acquired_item_release(st->rrddim_root_index, item);
+ found = true;
+ }
+ if(found && stop_on_match) goto cleanup;
+
+ // chart variable
+ {
+ NETDATA_DOUBLE n;
+ if(rrdvar_get_custom_chart_variable_value(st, vbd->variable, &n)) {
+ variable_lookup_add_result_with_score(vbd, n, st, "chart variable");
+ found = true;
+ }
+ }
+ if(found && stop_on_match) goto cleanup;
+
+cleanup:
+ return found;
+}
+
+static int foreach_instance_in_context_cb(RRDSET *st, void *data) {
+ struct variable_lookup_job *vbd = data;
+ return variable_lookup_in_chart(vbd, st, false) ? 1 : 0;
+}
+
+static bool variable_lookup_context(struct variable_lookup_job *vbd, const char *chart_or_context, const char *dim_id_or_name) {
+ struct variable_lookup_job vbd_back = *vbd;
+
+ vbd->dimension = dim_id_or_name;
+ vbd->dim = string_strdupz(vbd->dimension);
+ vbd->dimension_length = string_strlen(vbd->dim);
+ // vbd->dimension_selection = DIM_SELECT_NORMAL;
+
+ bool found = false;
+
+ // lookup chart in host
+
+ RRDSET_ACQUIRED *rsa = rrdset_find_and_acquire(vbd->host, chart_or_context);
+ if(rsa) {
+ if(variable_lookup_in_chart(vbd, rrdset_acquired_to_rrdset(rsa), false))
+ found = true;
+ rrdset_acquired_release(rsa);
+ }
+
+ // lookup context in contexts, then foreach chart
+
+ if(rrdcontext_foreach_instance_with_rrdset_in_context(vbd->host, chart_or_context, foreach_instance_in_context_cb, vbd) > 0)
+ found = true;
+
+ string_freez(vbd->dim);
+
+ vbd->dimension = vbd_back.dimension;
+ vbd->dim = vbd_back.dim;
+ vbd->dimension_length = vbd_back.dimension_length;
+ // vbd->dimension_selection = vbd_back.dimension_selection;
+
+ return found;
+}
+
+bool alert_variable_from_running_alerts(struct variable_lookup_job *vbd) {
+ bool found = false;
+ RRDCALC *rc;
+ foreach_rrdcalc_in_rrdhost_read(vbd->host, rc) {
+ if(rc->config.name == vbd->variable) {
+ variable_lookup_add_result_with_score(vbd, (NETDATA_DOUBLE)rc->value, rc->rrdset, "alarm value");
+ found = true;
+ }
+ }
+ foreach_rrdcalc_in_rrdhost_done(rc);
+ return found;
+}
+
+bool alert_variable_lookup_internal(STRING *variable, void *data, NETDATA_DOUBLE *result, BUFFER *wb) {
+ static STRING *this_string = NULL,
+ *now_string = NULL,
+ *after_string = NULL,
+ *before_string = NULL,
+ *status_string = NULL,
+ *removed_string = NULL,
+ *uninitialized_string = NULL,
+ *undefined_string = NULL,
+ *clear_string = NULL,
+ *warning_string = NULL,
+ *critical_string = NULL,
+ *last_collected_t_string = NULL,
+ *update_every_string = NULL;
+
+
+ struct variable_lookup_job vbd = { 0 };
+
+// const char *v_name = string2str(variable);
+// bool trace_this = false;
+// if(strcmp(v_name, "btrfs_allocated") == 0)
+// trace_this = true;
+
+ bool found = false;
+
+ const char *source = NULL;
+ RRDSET *source_st = NULL;
+
+ RRDCALC *rc = data;
+ RRDSET *st = rc->rrdset;
+
+ if(!st)
+ return false;
+
+ if(unlikely(!last_collected_t_string)) {
+ this_string = string_strdupz("this");
+ now_string = string_strdupz("now");
+ after_string = string_strdupz("after");
+ before_string = string_strdupz("before");
+ status_string = string_strdupz("status");
+ removed_string = string_strdupz("REMOVED");
+ undefined_string = string_strdupz("UNDEFINED");
+ uninitialized_string = string_strdupz("UNINITIALIZED");
+ clear_string = string_strdupz("CLEAR");
+ warning_string = string_strdupz("WARNING");
+ critical_string = string_strdupz("CRITICAL");
+ last_collected_t_string = string_strdupz("last_collected_t");
+ update_every_string = string_strdupz("update_every");
+ }
+
+ if(unlikely(variable == this_string)) {
+ *result = (NETDATA_DOUBLE)rc->value;
+ source = "current alert value";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == after_string)) {
+ *result = (NETDATA_DOUBLE)rc->db_after;
+ source = "current alert query start time";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == before_string)) {
+ *result = (NETDATA_DOUBLE)rc->db_before;
+ source = "current alert query end time";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == now_string)) {
+ *result = (NETDATA_DOUBLE)now_realtime_sec();
+ source = "current wall-time clock timestamp";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == status_string)) {
+ *result = (NETDATA_DOUBLE)rc->status;
+ source = "current alert status";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == removed_string)) {
+ *result = (NETDATA_DOUBLE)RRDCALC_STATUS_REMOVED;
+ source = "removed status constant";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == uninitialized_string)) {
+ *result = (NETDATA_DOUBLE)RRDCALC_STATUS_UNINITIALIZED;
+ source = "uninitialized status constant";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == undefined_string)) {
+ *result = (NETDATA_DOUBLE)RRDCALC_STATUS_UNDEFINED;
+ source = "undefined status constant";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == clear_string)) {
+ *result = (NETDATA_DOUBLE)RRDCALC_STATUS_CLEAR;
+ source = "clear status constant";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == warning_string)) {
+ *result = (NETDATA_DOUBLE)RRDCALC_STATUS_WARNING;
+ source = "warning status constant";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == critical_string)) {
+ *result = (NETDATA_DOUBLE)RRDCALC_STATUS_CRITICAL;
+ source = "critical status constant";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == last_collected_t_string)) {
+ *result = (NETDATA_DOUBLE)st->last_collected_time.tv_sec;
+ source = "current instance last_collected_t";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ if(unlikely(variable == update_every_string)) {
+ *result = (NETDATA_DOUBLE)st->update_every;
+ source = "current instance update_every";
+ source_st = st;
+ found = true;
+ goto log;
+ }
+
+ // find the dimension id/name
+
+ vbd = (struct variable_lookup_job){
+ .rc = rc,
+ .host = st->rrdhost,
+ .variable = variable,
+ .dimension = string2str(variable),
+ .dimension_length = string_strlen(variable),
+ .dimension_selection = DIM_SELECT_NORMAL,
+ .dim = string_dup(variable),
+ .result = { 0 },
+ };
+ if (strendswith_lengths(vbd.dimension, vbd.dimension_length, "_raw", 4)) {
+ vbd.dimension_length -= 4;
+ vbd.dimension_selection = DIM_SELECT_RAW;
+ vbd.dim = string_strndupz(vbd.dimension, vbd.dimension_length);
+ } else if (strendswith_lengths(vbd.dimension, vbd.dimension_length, "_last_collected_t", 17)) {
+ vbd.dimension_length -= 17;
+ vbd.dimension_selection = DIM_SELECT_LAST_COLLECTED;
+ vbd.dim = string_strndupz(vbd.dimension, vbd.dimension_length);
+ }
+
+ if(variable_lookup_in_chart(&vbd, st, true)) {
+ found = true;
+ goto find_best_scored;
+ }
+
+ // host variables
+ {
+ NETDATA_DOUBLE n;
+ found = rrdvar_get_custom_host_variable_value(vbd.host, vbd.variable, &n);
+ if(found) {
+ variable_lookup_add_result_with_score(&vbd, n, st, "host variable");
+ goto find_best_scored;
+ }
+ }
+
+ // alert names
+ if(alert_variable_from_running_alerts(&vbd)) {
+ found = true;
+ goto find_best_scored;
+ }
+
+ // find the components of the variable
+ {
+ char id[string_strlen(vbd.dim) + 1];
+ memcpy(id, string2str(vbd.dim), string_strlen(vbd.dim));
+ id[string_strlen(vbd.dim)] = '\0';
+
+ char *dot = strrchr(id, '.');
+ while(dot) {
+ *dot = '\0';
+
+ if(strchr(id, '.') == NULL) break;
+
+ if(variable_lookup_context(&vbd, id, dot + 1))
+ found = true;
+
+ char *dot2 = strrchr(id, '.');
+ *dot = '.';
+ dot = dot2;
+ }
+ }
+
+find_best_scored:
+ if(found && vbd.result.array) {
+ struct variable_lookup_score *best = &vbd.result.array[0];
+ for (size_t i = 1; i < vbd.result.used; i++)
+ if (vbd.result.array[i].score > best->score)
+ best = &vbd.result.array[i];
+
+ source = best->source;
+ source_st = best->st;
+ *result = best->value;
+ freez(vbd.result.array);
+ }
+ else {
+ found = false;
+ *result = NAN;
+ }
+
+log:
+#ifdef NETDATA_LOG_HEALTH_VARIABLES_LOOKUP
+ if(found) {
+ nd_log(NDLS_DAEMON, NDLP_INFO,
+ "HEALTH_VARIABLE_LOOKUP: variable '%s' of alert '%s' of chart '%s', context '%s', host '%s' "
+ "resolved with %s of chart '%s' and context '%s'",
+ string2str(variable),
+ string2str(rc->config.name),
+ string2str(rc->rrdset->id),
+ string2str(rc->rrdset->context),
+ string2str(rc->rrdset->rrdhost->hostname),
+ source,
+ string2str(source_st->id),
+ string2str(source_st->context)
+ );
+ }
+ else {
+ nd_log(NDLS_DAEMON, NDLP_INFO,
+ "HEALTH_VARIABLE_LOOKUP: variable '%s' of alert '%s' of chart '%s', context '%s', host '%s' "
+ "could not be resolved",
+ string2str(variable),
+ string2str(rc->config.name),
+ string2str(rc->rrdset->id),
+ string2str(rc->rrdset->context),
+ string2str(rc->rrdset->rrdhost->hostname)
+ );
+ }
+#endif
+
+ if(unlikely(wb)) {
+ buffer_json_member_add_string(wb, "variable", string2str(variable));
+ buffer_json_member_add_string(wb, "instance", string2str(st->id));
+ buffer_json_member_add_string(wb, "context", string2str(st->context));
+ buffer_json_member_add_boolean(wb, "found", found);
+
+ if (found) {
+ buffer_json_member_add_double(wb, "value", *result);
+ buffer_json_member_add_object(wb, "source");
+ {
+ buffer_json_member_add_string(wb, "description", source);
+ buffer_json_member_add_string(wb, "instance", string2str(source_st->id));
+ buffer_json_member_add_string(wb, "context", string2str(source_st->context));
+ buffer_json_member_add_uint64(wb, "candidates", vbd.result.used ? vbd.result.used : 1);
+ }
+ buffer_json_object_close(wb); // source
+ }
+ }
+
+ string_freez(vbd.dim);
+
+ return found;
+}
+
+bool alert_variable_lookup(STRING *variable, void *data, NETDATA_DOUBLE *result) {
+ return alert_variable_lookup_internal(variable, data, result, NULL);
+}
+
+int alert_variable_lookup_trace(RRDHOST *host __maybe_unused, RRDSET *st, const char *variable, BUFFER *wb) {
+ int code = HTTP_RESP_INTERNAL_SERVER_ERROR;
+
+ buffer_flush(wb);
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
+
+ STRING *v = string_strdupz(variable);
+ RRDCALC rc = {
+ .rrdset = st,
+ };
+
+ NETDATA_DOUBLE n;
+ alert_variable_lookup_internal(v, &rc, &n, wb);
+
+ string_freez(v);
+
+ buffer_json_finalize(wb);
+ return code;
+}