master
c 494 lines 15 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "health.h"
4 #include "health_internals.h"
5
6 struct variable_lookup_score {
7 RRDSET *st;
8 const char *source;
9 NETDATA_DOUBLE value;
10 size_t score;
11 };
12
13 struct variable_lookup_job {
14 RRDCALC *rc;
15 RRDHOST *host;
16 STRING *variable;
17 STRING *dim;
18 const char *dimension;
19 size_t dimension_length;
20 enum {
21 DIM_SELECT_NORMAL,
22 DIM_SELECT_RAW,
23 DIM_SELECT_LAST_COLLECTED,
24 } dimension_selection;
25
26 struct {
27 size_t size;
28 size_t used;
29 struct variable_lookup_score *array;
30 } result;
31
32 struct {
33 RRDSET *last_rrdset;
34 size_t last_score;
35 } score;
36 };
37
38 static void variable_lookup_add_result_with_score(struct variable_lookup_job *vbd, NETDATA_DOUBLE n, RRDSET *st, const char *source __maybe_unused) {
39 if(vbd->score.last_rrdset != st && vbd->rc->rrdset) {
40 vbd->score.last_rrdset = st;
41 vbd->score.last_score = rrdlabels_common_count(vbd->rc->rrdset->rrdlabels, st->rrdlabels);
42 }
43
44 if(vbd->result.used >= vbd->result.size) {
45 if(!vbd->result.size)
46 vbd->result.size = 1;
47
48 vbd->result.size *= 2;
49 vbd->result.array = reallocz(vbd->result.array, sizeof(struct variable_lookup_score) * vbd->result.size);
50 }
51
52 vbd->result.array[vbd->result.used++] = (struct variable_lookup_score) {
53 .value = n,
54 .score = vbd->score.last_score,
55 .st = st,
56 .source = source,
57 };
58 }
59
60 static bool variable_lookup_in_chart(struct variable_lookup_job *vbd, RRDSET *st, bool stop_on_match) {
61 bool found = false;
62 const DICTIONARY_ITEM *item = NULL;
63 RRDDIM *rd = NULL;
64 dfe_start_read(st->rrddim_root_index, rd) {
65 if(rd->id == vbd->dim || rd->name == vbd->dim) {
66 item = dictionary_acquired_item_dup(st->rrddim_root_index, rd_dfe.item);
67 break;
68 }
69 }
70 dfe_done(rd);
71
72 if (item) {
73 switch (vbd->dimension_selection) {
74 case DIM_SELECT_NORMAL:
75 variable_lookup_add_result_with_score(vbd, (NETDATA_DOUBLE)rd->collector.last_stored_value, st, "last stored value of dimension");
76 break;
77 case DIM_SELECT_RAW:
78 variable_lookup_add_result_with_score(vbd, rrddim_last_collected_as_double(rd), st, "last collected value of dimension");
79 break;
80 case DIM_SELECT_LAST_COLLECTED:
81 variable_lookup_add_result_with_score(vbd, (NETDATA_DOUBLE)rd->collector.last_collected_time.tv_sec, st, "last collected time of dimension");
82 break;
83 }
84
85 dictionary_acquired_item_release(st->rrddim_root_index, item);
86 found = true;
87 }
88 if(found && stop_on_match) goto cleanup;
89
90 // chart variable
91 {
92 NETDATA_DOUBLE n;
93 if(rrdvar_get_custom_chart_variable_value(st, vbd->variable, &n)) {
94 variable_lookup_add_result_with_score(vbd, n, st, "chart variable");
95 found = true;
96 }
97 }
98 if(found && stop_on_match) goto cleanup;
99
100 cleanup:
101 return found;
102 }
103
104 static int foreach_instance_in_context_cb(RRDSET *st, void *data) {
105 struct variable_lookup_job *vbd = data;
106 return variable_lookup_in_chart(vbd, st, false) ? 1 : 0;
107 }
108
109 static bool variable_lookup_context(struct variable_lookup_job *vbd, const char *chart_or_context, const char *dim_id_or_name) {
110 struct variable_lookup_job vbd_back = *vbd;
111
112 vbd->dimension = dim_id_or_name;
113 vbd->dim = string_strdupz(vbd->dimension);
114 vbd->dimension_length = string_strlen(vbd->dim);
115 // vbd->dimension_selection = DIM_SELECT_NORMAL;
116
117 bool found = false;
118
119 // lookup chart in host
120
121 RRDSET_ACQUIRED *rsa = rrdset_find_and_acquire(vbd->host, chart_or_context, false);
122 if(rsa) {
123 if(variable_lookup_in_chart(vbd, rrdset_acquired_to_rrdset(rsa), false))
124 found = true;
125 rrdset_acquired_release(rsa);
126 }
127
128 // lookup context in contexts, then foreach chart
129
130 if(rrdcontext_foreach_instance_with_rrdset_in_context(vbd->host, chart_or_context, foreach_instance_in_context_cb, vbd) > 0)
131 found = true;
132
133 string_freez(vbd->dim);
134
135 vbd->dimension = vbd_back.dimension;
136 vbd->dim = vbd_back.dim;
137 vbd->dimension_length = vbd_back.dimension_length;
138 // vbd->dimension_selection = vbd_back.dimension_selection;
139
140 return found;
141 }
142
143 bool alert_variable_from_running_alerts(struct variable_lookup_job *vbd) {
144 bool found = false;
145 RRDCALC *rc;
146 foreach_rrdcalc_in_rrdhost_read(vbd->host, rc) {
147 if(rc->config.name == vbd->variable && rc->rrdset) {
148 variable_lookup_add_result_with_score(vbd, (NETDATA_DOUBLE)rc->value, rc->rrdset, "alarm value");
149 found = true;
150 }
151 }
152 foreach_rrdcalc_in_rrdhost_done(rc);
153 return found;
154 }
155
156 static STRING *this_string = NULL,
157 *now_string = NULL,
158 *after_string = NULL,
159 *before_string = NULL,
160 *status_string = NULL,
161 *removed_string = NULL,
162 *uninitialized_string = NULL,
163 *undefined_string = NULL,
164 *clear_string = NULL,
165 *warning_string = NULL,
166 *critical_string = NULL,
167 *last_collected_t_string = NULL,
168 *update_every_string = NULL;
169
170 void alert_variable_lookup_cleanup(void) {
171 string_freez(this_string);
172 string_freez(now_string);
173 string_freez(after_string);
174 string_freez(before_string);
175 string_freez(status_string);
176 string_freez(removed_string);
177 string_freez(uninitialized_string);
178 string_freez(undefined_string);
179 string_freez(clear_string);
180 string_freez(warning_string);
181 string_freez(critical_string);
182 string_freez(last_collected_t_string);
183 string_freez(update_every_string);
184 this_string = NULL;
185 now_string = NULL;
186 after_string = NULL;
187 before_string = NULL;
188 status_string = NULL;
189 removed_string = NULL;
190 uninitialized_string = NULL;
191 undefined_string = NULL;
192 clear_string = NULL;
193 warning_string = NULL;
194 critical_string = NULL;
195 last_collected_t_string = NULL;
196 update_every_string = NULL;
197 }
198
199 bool alert_variable_lookup_internal(STRING *variable, void *data, NETDATA_DOUBLE *result, BUFFER *wb) {
200 struct variable_lookup_job vbd = { 0 };
201
202 // const char *v_name = string2str(variable);
203 // bool trace_this = false;
204 // if(strcmp(v_name, "btrfs_allocated") == 0)
205 // trace_this = true;
206
207 bool found = false;
208
209 const char *source = NULL;
210 RRDSET *source_st = NULL;
211
212 RRDCALC *rc = data;
213 RRDSET *st = rc->rrdset;
214
215 if(!st)
216 return false;
217
218 if(unlikely(!last_collected_t_string)) {
219 this_string = string_strdupz("this");
220 now_string = string_strdupz("now");
221 after_string = string_strdupz("after");
222 before_string = string_strdupz("before");
223 status_string = string_strdupz("status");
224 removed_string = string_strdupz("REMOVED");
225 undefined_string = string_strdupz("UNDEFINED");
226 uninitialized_string = string_strdupz("UNINITIALIZED");
227 clear_string = string_strdupz("CLEAR");
228 warning_string = string_strdupz("WARNING");
229 critical_string = string_strdupz("CRITICAL");
230 last_collected_t_string = string_strdupz("last_collected_t");
231 update_every_string = string_strdupz("update_every");
232 }
233
234 if(unlikely(variable == this_string)) {
235 *result = (NETDATA_DOUBLE)rc->value;
236 source = "current alert value";
237 source_st = st;
238 found = true;
239 goto log;
240 }
241
242 if(unlikely(variable == after_string)) {
243 *result = (NETDATA_DOUBLE)rc->db_after;
244 source = "current alert query start time";
245 source_st = st;
246 found = true;
247 goto log;
248 }
249
250 if(unlikely(variable == before_string)) {
251 *result = (NETDATA_DOUBLE)rc->db_before;
252 source = "current alert query end time";
253 source_st = st;
254 found = true;
255 goto log;
256 }
257
258 if(unlikely(variable == now_string)) {
259 *result = (NETDATA_DOUBLE)now_realtime_sec();
260 source = "current wall-time clock timestamp";
261 source_st = st;
262 found = true;
263 goto log;
264 }
265
266 if(unlikely(variable == status_string)) {
267 *result = (NETDATA_DOUBLE)rc->status;
268 source = "current alert status";
269 source_st = st;
270 found = true;
271 goto log;
272 }
273
274 if(unlikely(variable == removed_string)) {
275 *result = (NETDATA_DOUBLE)RRDCALC_STATUS_REMOVED;
276 source = "removed status constant";
277 source_st = st;
278 found = true;
279 goto log;
280 }
281
282 if(unlikely(variable == uninitialized_string)) {
283 *result = (NETDATA_DOUBLE)RRDCALC_STATUS_UNINITIALIZED;
284 source = "uninitialized status constant";
285 source_st = st;
286 found = true;
287 goto log;
288 }
289
290 if(unlikely(variable == undefined_string)) {
291 *result = (NETDATA_DOUBLE)RRDCALC_STATUS_UNDEFINED;
292 source = "undefined status constant";
293 source_st = st;
294 found = true;
295 goto log;
296 }
297
298 if(unlikely(variable == clear_string)) {
299 *result = (NETDATA_DOUBLE)RRDCALC_STATUS_CLEAR;
300 source = "clear status constant";
301 source_st = st;
302 found = true;
303 goto log;
304 }
305
306 if(unlikely(variable == warning_string)) {
307 *result = (NETDATA_DOUBLE)RRDCALC_STATUS_WARNING;
308 source = "warning status constant";
309 source_st = st;
310 found = true;
311 goto log;
312 }
313
314 if(unlikely(variable == critical_string)) {
315 *result = (NETDATA_DOUBLE)RRDCALC_STATUS_CRITICAL;
316 source = "critical status constant";
317 source_st = st;
318 found = true;
319 goto log;
320 }
321
322 if(unlikely(variable == last_collected_t_string)) {
323 *result = (NETDATA_DOUBLE)st->last_collected_time.tv_sec;
324 source = "current instance last_collected_t";
325 source_st = st;
326 found = true;
327 goto log;
328 }
329
330 if(unlikely(variable == update_every_string)) {
331 *result = (NETDATA_DOUBLE)st->update_every;
332 source = "current instance update_every";
333 source_st = st;
334 found = true;
335 goto log;
336 }
337
338 // find the dimension id/name
339
340 vbd = (struct variable_lookup_job){
341 .rc = rc,
342 .host = st->rrdhost,
343 .variable = variable,
344 .dimension = string2str(variable),
345 .dimension_length = string_strlen(variable),
346 .dimension_selection = DIM_SELECT_NORMAL,
347 .dim = string_dup(variable),
348 .result = { 0 },
349 };
350 if (strendswith_lengths(vbd.dimension, vbd.dimension_length, "_raw", 4)) {
351 vbd.dimension_length -= 4;
352 vbd.dimension_selection = DIM_SELECT_RAW;
353 vbd.dim = string_strndupz(vbd.dimension, vbd.dimension_length);
354 } else if (strendswith_lengths(vbd.dimension, vbd.dimension_length, "_last_collected_t", 17)) {
355 vbd.dimension_length -= 17;
356 vbd.dimension_selection = DIM_SELECT_LAST_COLLECTED;
357 vbd.dim = string_strndupz(vbd.dimension, vbd.dimension_length);
358 }
359
360 if(variable_lookup_in_chart(&vbd, st, true)) {
361 found = true;
362 goto find_best_scored;
363 }
364
365 // host variables
366 {
367 NETDATA_DOUBLE n;
368 found = rrdvar_get_custom_host_variable_value(vbd.host, vbd.variable, &n);
369 if(found) {
370 variable_lookup_add_result_with_score(&vbd, n, st, "host variable");
371 goto find_best_scored;
372 }
373 }
374
375 // alert names
376 if(alert_variable_from_running_alerts(&vbd)) {
377 found = true;
378 goto find_best_scored;
379 }
380
381 // find the components of the variable
382 {
383 char *id = strdupz(string2str(vbd.dim));
384
385 char *dot = strrchr(id, '.');
386 while(dot) {
387 *dot = '\0';
388
389 if(strchr(id, '.') == NULL) break;
390
391 if(variable_lookup_context(&vbd, id, dot + 1))
392 found = true;
393
394 char *dot2 = strrchr(id, '.');
395 *dot = '.';
396 dot = dot2;
397 }
398
399 freez(id);
400 }
401
402 find_best_scored:
403 if(found && vbd.result.array) {
404 struct variable_lookup_score *best = &vbd.result.array[0];
405 for (size_t i = 1; i < vbd.result.used; i++)
406 if (vbd.result.array[i].score > best->score)
407 best = &vbd.result.array[i];
408
409 source = best->source;
410 source_st = best->st;
411 *result = best->value;
412 freez(vbd.result.array);
413 }
414 else {
415 found = false;
416 *result = NAN;
417 }
418
419 log:
420 #ifdef NETDATA_LOG_HEALTH_VARIABLES_LOOKUP
421 if(found) {
422 nd_log(NDLS_DAEMON, NDLP_INFO,
423 "HEALTH_VARIABLE_LOOKUP: variable '%s' of alert '%s' of chart '%s', context '%s', host '%s' "
424 "resolved with %s of chart '%s' and context '%s'",
425 string2str(variable),
426 string2str(rc->config.name),
427 string2str(rc->rrdset->id),
428 string2str(rc->rrdset->context),
429 string2str(rc->rrdset->rrdhost->hostname),
430 source,
431 string2str(source_st->id),
432 string2str(source_st->context)
433 );
434 }
435 else {
436 nd_log(NDLS_DAEMON, NDLP_INFO,
437 "HEALTH_VARIABLE_LOOKUP: variable '%s' of alert '%s' of chart '%s', context '%s', host '%s' "
438 "could not be resolved",
439 string2str(variable),
440 string2str(rc->config.name),
441 string2str(rc->rrdset->id),
442 string2str(rc->rrdset->context),
443 string2str(rc->rrdset->rrdhost->hostname)
444 );
445 }
446 #endif
447
448 if(unlikely(wb)) {
449 buffer_json_member_add_string(wb, "variable", string2str(variable));
450 buffer_json_member_add_string(wb, "instance", string2str(st->id));
451 buffer_json_member_add_string(wb, "context", string2str(st->context));
452 buffer_json_member_add_boolean(wb, "found", found);
453
454 if (found) {
455 buffer_json_member_add_double(wb, "value", *result);
456 buffer_json_member_add_object(wb, "source");
457 {
458 buffer_json_member_add_string(wb, "description", source);
459 buffer_json_member_add_string(wb, "instance", string2str(source_st->id));
460 buffer_json_member_add_string(wb, "context", string2str(source_st->context));
461 buffer_json_member_add_uint64(wb, "candidates", vbd.result.used ? vbd.result.used : 1);
462 }
463 buffer_json_object_close(wb); // source
464 }
465 }
466
467 string_freez(vbd.dim);
468
469 return found;
470 }
471
472 bool alert_variable_lookup(STRING *variable, void *data, NETDATA_DOUBLE *result) {
473 return alert_variable_lookup_internal(variable, data, result, NULL);
474 }
475
476 int alert_variable_lookup_trace(RRDHOST *host __maybe_unused, RRDSET *st, const char *variable, BUFFER *wb) {
477 int code = HTTP_RESP_INTERNAL_SERVER_ERROR;
478
479 buffer_flush(wb);
480 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
481
482 STRING *v = string_strdupz(variable);
483 RRDCALC rc = {
484 .rrdset = st,
485 };
486
487 NETDATA_DOUBLE n;
488 alert_variable_lookup_internal(v, &rc, &n, wb);
489
490 string_freez(v);
491
492 buffer_json_finalize(wb);
493 return code;
494 }