@cryptotaxi247 / netdata-1 / commits / 708efb41b

Support chart labels in alerts (#13290)

* chart labels for alerts * proper termination * use strchr * change if statement * change label variable. add docs * change doc * assign buf to temp * use new dictionary functions * reduce variable scope * reduce line length * make sure rrdcalc updates labels after inserted * reduce var scope * add rrdcalc.c for cmocka tests * Revert "add rrdcalc.c for cmocka tests" This reverts commit 5fe122adcf7abcbe6d67fa2ebd7c4ff8620cf9c8. * Fix cmocka unit tests * valgrind errors Co-authored-by: Vladimir Kobal <vlad@prokk.net>

Emmanuel Vasilakis committed Aug 16, 2022 at 10:33 UTC 708efb41bdf952c84b60326d40c07cc69e58d19c
15 files changed +147 -55
collectors/cgroups.plugin/tests/test_doubles.c
+4
@@ -153,3 +153,7 @@ void sql_store_chart_label(uuid_t *chart_uuid, int source_type, char *label, cha
153 UNUSED(label);
154 UNUSED(value);
155 }
156 +
157 +void rrdcalc_update_rrdlabels(RRDSET *st) {
158 + (void)st;
159 +}
collectors/proc.plugin/proc_diskstats.c
+2
@@ -852,6 +852,8 @@ static void add_labels_to_disk(struct disk *d, RRDSET *st) {
852 rrdlabels_add(st->state->chart_labels, "device_type", "virtual", RRDLABEL_SRC_AUTO);
853 break;
854 }
855 +
856 + rrdcalc_update_rrdlabels(st);
857 }
858
859 int do_proc_diskstats(int update_every, usec_t dt) {
collectors/proc.plugin/proc_mdstat.c
+1
@@ -80,6 +80,7 @@ static inline void make_chart_obsolete(char *name, const char *id_modifier)
80 static void add_labels_to_mdstat(struct raid *raid, RRDSET *st) {
81 rrdlabels_add(st->state->chart_labels, "device", raid->name, RRDLABEL_SRC_AUTO);
82 rrdlabels_add(st->state->chart_labels, "raid_level", raid->level, RRDLABEL_SRC_AUTO);
83 + rrdcalc_update_rrdlabels(st);
84 }
85
86 int do_proc_mdstat(int update_every, usec_t dt)
collectors/proc.plugin/proc_net_wireless.c
+1
@@ -200,6 +200,7 @@ static void configure_device(int do_status, int do_quality, int do_discarded_pac
200
201 static void add_labels_to_wireless(struct netwireless *w, RRDSET *st) {
202 rrdlabels_add(st->state->chart_labels, "device", w->name, RRDLABEL_SRC_AUTO);
203 + rrdcalc_update_rrdlabels(st);
204 }
205
206 int do_proc_net_wireless(int update_every, usec_t dt)
collectors/proc.plugin/sys_class_power_supply.c
+1
@@ -114,6 +114,7 @@ void power_supply_free(struct power_supply *ps) {
114
115 static void add_labels_to_power_supply(struct power_supply *ps, RRDSET *st) {
116 rrdlabels_add(st->state->chart_labels, "device", ps->name, RRDLABEL_SRC_AUTO);
117 + rrdcalc_update_rrdlabels(st);
118 }
119
120 int do_sys_class_power_supply(int update_every, usec_t dt) {
collectors/proc.plugin/sys_fs_btrfs.c
+1
@@ -451,6 +451,7 @@ static inline int find_all_btrfs_pools(const char *path) {
451 static void add_labels_to_btrfs(BTRFS_NODE *n, RRDSET *st) {
452 rrdlabels_add(st->state->chart_labels, "device", n->id, RRDLABEL_SRC_AUTO);
453 rrdlabels_add(st->state->chart_labels, "device_label", n->label, RRDLABEL_SRC_AUTO);
454 + rrdcalc_update_rrdlabels(st);
455 }
456
457 int do_sys_fs_btrfs(int update_every, usec_t dt) {
database/rrd.h
+1
@@ -216,6 +216,7 @@ extern void rrdlabels_destroy(DICTIONARY *labels_dict);
216 extern void rrdlabels_add(DICTIONARY *dict, const char *name, const char *value, RRDLABEL_SRC ls);
217 extern void rrdlabels_add_pair(DICTIONARY *dict, const char *string, RRDLABEL_SRC ls);
218 extern void rrdlabels_get_value_to_buffer_or_null(DICTIONARY *labels, BUFFER *wb, const char *key, const char *quote, const char *null);
219 +extern void rrdlabels_get_value_to_char_or_null(DICTIONARY *labels, char **value, const char *key);
220
221 extern void rrdlabels_unmark_all(DICTIONARY *labels);
222 extern void rrdlabels_remove_all_unmarked(DICTIONARY *labels);
database/rrdcalc.c
+68 -1
@@ -35,6 +35,62 @@ inline const char *rrdcalc_status2string(RRDCALC_STATUS status) {
35 }
36 }
37
38 +char *rrdcalc_replace_variables(const char *line, RRDCALC *rc)
39 +{
40 + if (!line)
41 + return NULL;
42 +
43 + size_t pos = 0;
44 + char *temp = strdupz(line);
45 + char var[RRDCALC_VAR_MAX];
46 + char *m, *lbl_value = NULL;
47 +
48 + while ((m = strchr(temp + pos, '$'))) {
49 + int i=0;
50 + char *e = m;
51 + while (*e) {
52 + if (*e == ' ' || i == RRDCALC_VAR_MAX - 1) {
53 + break;
54 + }
55 + else
56 + var[i]=*e;
57 + e++;
58 + i++;
59 + }
60 + var[i]='\0';
61 + pos = m - temp + 1;
62 + if (!strcmp(var, RRDCALC_VAR_FAMILY)) {
63 + char *buf = find_and_replace(temp, var, (rc->rrdset && rc->rrdset->family) ? rc->rrdset->family : "", m);
64 + freez(temp);
65 + temp = buf;
66 + } else if (!strncmp(var, RRDCALC_VAR_LABEL, RRDCALC_VAR_LABEL_LEN)) {
67 + if(likely(rc->rrdset->state && rc->rrdset->state->chart_labels)) {
68 + rrdlabels_get_value_to_char_or_null(rc->rrdset->state->chart_labels, &lbl_value, var+RRDCALC_VAR_LABEL_LEN);
69 + if (lbl_value) {
70 + char *buf = find_and_replace(temp, var, lbl_value, m);
71 + freez(temp);
72 + temp = buf;
73 + freez(lbl_value);
74 + }
75 + }
76 + }
77 + }
78 +
79 + return temp;
80 +}
81 +
82 +void rrdcalc_update_rrdlabels(RRDSET *st) {
83 + RRDCALC *rc;
84 + for( rc = st->alarms; rc ; rc = rc->rrdset_next ) {
85 + if (rc->original_info) {
86 + if (rc->info)
87 + freez(rc->info);
88 +
89 + rc->info = rrdcalc_replace_variables(rc->original_info, rc);
90 + }
91 + }
92 +}
93 +
94 static void rrdsetcalc_link(RRDSET *st, RRDCALC *rc) {
95 RRDHOST *host = st->rrdhost;
96
@@ -83,6 +139,12 @@ static void rrdsetcalc_link(RRDSET *st, RRDCALC *rc) {
139
140 if(!rc->units) rc->units = strdupz(st->units);
141
142 + if (rc->original_info) {
143 + if (rc->info)
144 + freez(rc->info);
145 + rc->info = rrdcalc_replace_variables(rc->original_info, rc);
146 + }
147 +
148 time_t now = now_realtime_sec();
149 ALARM_ENTRY *ae = health_create_alarm_entry(
150 host,
@@ -427,7 +489,10 @@ inline RRDCALC *rrdcalc_create_from_template(RRDHOST *host, RRDCALCTEMPLATE *rt,
489 if(rt->recipient) rc->recipient = strdupz(rt->recipient);
490 if(rt->source) rc->source = strdupz(rt->source);
491 if(rt->units) rc->units = strdupz(rt->units);
430 - if(rt->info) rc->info = strdupz(rt->info);
492 + if(rt->info) {
493 + rc->info = strdupz(rt->info);
494 + rc->original_info = strdupz(rt->info);
495 + }
496
497 if (rt->classification) rc->classification = strdupz(rt->classification);
498 if (rt->component) rc->component = strdupz(rt->component);
@@ -543,6 +608,7 @@ inline RRDCALC *rrdcalc_create_from_rrdcalc(RRDCALC *rc, RRDHOST *host, const ch
608 if(rc->source) newrc->source = strdupz(rc->source);
609 if(rc->units) newrc->units = strdupz(rc->units);
610 if(rc->info) newrc->info = strdupz(rc->info);
611 + if(rc->original_info) newrc->original_info = strdupz(rc->original_info);
612
613 if (rc->classification) newrc->classification = strdupz(rc->classification);
614 if (rc->component) newrc->component = strdupz(rc->component);
@@ -586,6 +652,7 @@ void rrdcalc_free(RRDCALC *rc) {
652 freez(rc->source);
653 freez(rc->units);
654 freez(rc->info);
655 + freez(rc->original_info);
656 freez(rc->classification);
657 freez(rc->component);
658 freez(rc->type);
database/rrdcalc.h
+7
@@ -58,6 +58,7 @@ struct rrdcalc {
58
59 char *source; // the source of this alarm
60 char *units; // the units of the alarm
61 + char *original_info; // the original info field before any variable replacement
62 char *info; // a short description of the alarm
63
64 int update_every; // update frequency for the alarm
@@ -210,6 +211,7 @@ extern RRDCALC *rrdcalc_create_from_rrdcalc(RRDCALC *rc, RRDHOST *host, const ch
211 extern void rrdcalc_add_to_host(RRDHOST *host, RRDCALC *rc);
212 extern void dimension_remove_pipe_comma(char *str);
213 extern char *alarm_name_with_dim(char *name, size_t namelen, const char *dim, size_t dimlen);
214 +extern void rrdcalc_update_rrdlabels(RRDSET *st);
215
216 extern void rrdcalc_labels_unlink();
217 extern void rrdcalc_labels_unlink_alarm_from_host(RRDHOST *host);
@@ -221,4 +223,9 @@ static inline int rrdcalc_isrepeating(RRDCALC *rc) {
223 return 0;
224 }
225
226 +#define RRDCALC_VAR_MAX 100
227 +#define RRDCALC_VAR_FAMILY "$family"
228 +#define RRDCALC_VAR_LABEL "$label:"
229 +#define RRDCALC_VAR_LABEL_LEN (sizeof(RRDCALC_VAR_LABEL)-1)
230 +
231 #endif //NETDATA_RRDCALC_H
database/rrdlabels.c
+14 -1
@@ -620,7 +620,7 @@ void rrdlabels_add_pair(DICTIONARY *dict, const char *string, RRDLABEL_SRC ls) {
620 }
621
622 // ----------------------------------------------------------------------------
623 -// rrdlabels_get_to_buffer_or_null()
623 +// rrdlabels_get_value_to_buffer_or_null()
624
625 void rrdlabels_get_value_to_buffer_or_null(DICTIONARY *labels, BUFFER *wb, const char *key, const char *quote, const char *null) {
626 DICTIONARY_ITEM *acquired_item = dictionary_get_and_acquire_item(labels, key);
@@ -634,6 +634,17 @@ void rrdlabels_get_value_to_buffer_or_null(DICTIONARY *labels, BUFFER *wb, const
634 dictionary_acquired_item_release(labels, acquired_item);
635 }
636
637 +// ----------------------------------------------------------------------------
638 +// rrdlabels_get_value_to_char_or_null()
639 +
640 +void rrdlabels_get_value_to_char_or_null(DICTIONARY *labels, char **value, const char *key) {
641 + DICTIONARY_ITEM *acquired_item = dictionary_get_and_acquire_item(labels, key);
642 + RRDLABEL *lb = dictionary_acquired_item_value(acquired_item);
643 +
644 + *value = (lb && lb->label_value) ? strdupz(string2str(lb->label_value)) : NULL;
645 +
646 + dictionary_acquired_item_release(labels, acquired_item);
647 +}
648
649 // ----------------------------------------------------------------------------
650 // rrdlabels_unmark_all()
@@ -939,6 +950,8 @@ void rrdset_update_rrdlabels(RRDSET *st, DICTIONARY *new_rrdlabels) {
950 if (new_rrdlabels)
951 rrdlabels_migrate_to_these(st->state->chart_labels, new_rrdlabels);
952
953 + rrdcalc_update_rrdlabels(st);
954 +
955 // TODO - we should also cleanup sqlite from old new_rrdlabels that have been removed
956 rrdlabels_walkthrough_read(st->state->chart_labels, chart_label_store_to_sql_callback, st);
957 }
exporting/tests/netdata_doubles.c
+4
@@ -255,3 +255,7 @@ void sql_store_chart_label(uuid_t *chart_uuid, int source_type, char *label, cha
255 (void)label;
256 (void)value;
257 }
258 +
259 +void rrdcalc_update_rrdlabels(RRDSET *st) {
260 + (void)st;
261 +}
health/REFERENCE.md
+37 -1
@@ -536,12 +536,48 @@ See our [simple patterns docs](/libnetdata/simple_pattern/README.md) for more ex
536
537 #### Alarm line `info`
538
539 -The info field can contain a small piece of text describing the alarm or template. This will be rendered in notifications and UI elements whenever the specific alarm is in focus. An example for the `ram_available` alarm is:
539 +The info field can contain a small piece of text describing the alarm or template. This will be rendered in
540 +notifications and UI elements whenever the specific alarm is in focus. An example for the `ram_available` alarm is:
541
542 ```yaml
543 info: percentage of estimated amount of RAM available for userspace processes, without causing swapping
544 ```
545
546 +info fields can contain special variables in their text that will be replaced during run-time to provide more specific
547 +alert information. Current variables supported are:
548 +
549 +| variable | description |
550 +| ---------| ----------- |
551 +| $family | Will be replaced by the family instance for the alert (e.g. eth0) |
552 +| $label: | Followed by a chart label name, this will replace the variable with the chart label's value |
553 +
554 +For example, an info field like the following:
555 +
556 +```yaml
557 +info: average inbound utilization for the network interface $family over the last minute
558 +```
559 +
560 +Will be rendered on the alert acting on interface `eth0` as:
561 +
562 +```yaml
563 +info: average inbound utilization for the network interface eth0 over the last minute
564 +```
565 +
566 +An alert acting on a chart that has a chart label named e.g. `target`, with a value of `https://netdata.cloud/`,
567 +can be enriched as follows:
568 +
569 +```yaml
570 +info: average ratio of HTTP responses with unexpected status over the last 5 minutes for the site $label:target
571 +```
572 +
573 +Will become:
574 +
575 +```yaml
576 +info: average ratio of HTTP responses with unexpected status over the last 5 minutes for the site https://netdata.cloud/
577 +```
578 +
579 +> Please note that variable names are case sensitive.
580 +
581 ## Expressions
582
583 Netdata has an internal [infix expression parser](/libnetdata/eval). This parses expressions and creates an internal
health/health_config.c
+2
@@ -942,6 +942,8 @@ static int health_readfile(const char *filename, void *data) {
942 }
943 rc->info = strdupz(value);
944 strip_quotes(rc->info);
945 + rc->original_info = strdupz(value);
946 + strip_quotes(rc->original_info);
947 }
948 else if(hash == hash_delay && !strcasecmp(key, HEALTH_DELAY_KEY)) {
949 alert_cfg->delay = strdupz(value);
health/health_json.c
+2 -35
@@ -96,22 +96,7 @@ void health_alarm_entry2json_nolock(BUFFER *wb, ALARM_ENTRY *ae, RRDHOST *host)
96 , (ae->flags & HEALTH_ENTRY_FLAG_SILENCED)?"true":"false"
97 );
98
99 - char *replaced_info = NULL;
100 - if (likely(ae->info)) {
101 - char *m = NULL;
102 - replaced_info = strdupz(ae->info);
103 - size_t pos = 0;
104 - while ((m = strstr(replaced_info + pos, "$family"))) {
105 - char *buf = NULL;
106 - pos = m - replaced_info;
107 - buf = find_and_replace(replaced_info, "$family", ae->family ? ae->family : "", m);
108 - freez(replaced_info);
109 - replaced_info = strdupz(buf);
110 - freez(buf);
111 - }
112 - }
113 -
114 - health_string2json(wb, "\t\t", "info", replaced_info?replaced_info:"", ",\n");
99 + health_string2json(wb, "\t\t", "info", ae->info ? ae->info : "", ",\n");
100
101 if(unlikely(ae->flags & HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION)) {
102 buffer_strcat(wb, "\t\t\"no_clear_notification\": true,\n");
@@ -127,7 +112,6 @@ void health_alarm_entry2json_nolock(BUFFER *wb, ALARM_ENTRY *ae, RRDHOST *host)
112
113 buffer_strcat(wb, "\t}");
114
130 - freez(replaced_info);
115 freez(edit_command);
116 }
117
@@ -182,21 +166,6 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
166 char value_string[100 + 1];
167 format_value_and_unit(value_string, 100, rc->value, rc->units, -1);
168
185 - char *replaced_info = NULL;
186 - if (likely(rc->info)) {
187 - char *m;
188 - replaced_info = strdupz(rc->info);
189 - size_t pos = 0;
190 - while ((m = strstr(replaced_info + pos, "$family"))) {
191 - char *buf = NULL;
192 - pos = m - replaced_info;
193 - buf = find_and_replace(replaced_info, "$family", (rc->rrdset && rc->rrdset->family) ? rc->rrdset->family : "", m);
194 - freez(replaced_info);
195 - replaced_info = strdupz(buf);
196 - freez(buf);
197 - }
198 - }
199 -
169 char hash_id[GUID_LEN + 1];
170 uuid_unparse_lower(rc->config_hash_id, hash_id);
171
@@ -250,7 +219,7 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
219 , rc->recipient?rc->recipient:host->health_default_recipient
220 , rc->source
221 , rc->units?rc->units:""
253 - , replaced_info?replaced_info:""
222 + , rc->info?rc->info:""
223 , rrdcalc_status2string(rc->status)
224 , (unsigned long)rc->last_status_change
225 , (unsigned long)rc->last_updated
@@ -322,8 +291,6 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC
291 buffer_strcat(wb, "\n");
292
293 buffer_strcat(wb, "\t\t}");
325 -
326 - freez(replaced_info);
294 }
295
296 //void health_rrdcalctemplate2json_nolock(BUFFER *wb, RRDCALCTEMPLATE *rt) {
health/health_log.c
+2 -17
@@ -512,23 +512,8 @@ inline ALARM_ENTRY* health_create_alarm_entry(
512 ae->old_value_string = strdupz(format_value_and_unit(value_string, 100, ae->old_value, ae->units, -1));
513 ae->new_value_string = strdupz(format_value_and_unit(value_string, 100, ae->new_value, ae->units, -1));
514
515 - char *replaced_info = NULL;
516 - if (likely(info)) {
517 - char *m;
518 - replaced_info = strdupz(info);
519 - size_t pos = 0;
520 - while ((m = strstr(replaced_info + pos, "$family"))) {
521 - char *buf = NULL;
522 - pos = m - replaced_info;
523 - buf = find_and_replace(replaced_info, "$family", (ae->family) ? ae->family : "", m);
524 - freez(replaced_info);
525 - replaced_info = strdupz(buf);
526 - freez(buf);
527 - }
528 - }
529 -
530 - if(replaced_info) ae->info = strdupz(replaced_info);
531 - freez(replaced_info);
515 + if (info)
516 + ae->info = strdupz(info);
517
518 ae->old_status = old_status;
519 ae->new_status = new_status;