Parse host tags (#7702)
* Fix memory leaks * Check for configuration options * Parse simple tags * Parse JSON tags * Remove an unnecessary check * Parse a JSON object * Parse a JSON array * Update the documentation * Fix host locks
Vladimir Kobal committed
Feb 1, 2020 at 00:05 UTC
177af26ea878e673166ebbeb4518539f624a81f0
12 files changed
+204
-4
backends/README.md
+8
-3
@@ -183,9 +183,14 @@ from your Netdata):
183
are different: disks with device-mapper, interrupts, QoS classes, statsd synthetic charts, etc.
184
185
- `host tags = list of TAG=VALUE` defines tags that should be appended on all metrics for the given host. These are
186
- currently only sent to opentsdb and prometheus. Please use the appropriate format for each time-series db. For
187
- example opentsdb likes them like `TAG1=VALUE1 TAG2=VALUE2`, but prometheus like `tag1="value1",tag2="value2"`. Host
188
- tags are mirrored with database replication (streaming of metrics between Netdata servers).
186
+ currently only sent to graphite, json, opentsdb and prometheus. Please use the appropriate format for each
187
+ time-series db. For example opentsdb likes them like `TAG1=VALUE1 TAG2=VALUE2`, but prometheus like `tag1="value1",
188
+ tag2="value2"`. Host tags are mirrored with database replication (streaming of metrics between Netdata servers).
189
+
190
+ Starting from Netdata v1.20 the host tags are parsed in accordance with a configured backend type and stored as
191
+ host labels so that they can be reused in API responses and exporting connectors. The parsing is supported for
192
+ graphite, json, opentsdb, and prometheus (default) backend types. You can check how the host tags were parsed using
193
+ the /api/v1/info API call.
194
195
## monitoring operation
196
daemon/commands.c
+2
@@ -203,6 +203,7 @@ static cmd_status_t cmd_reload_labels_execute(char *args, char **message)
203
204
BUFFER *wb = buffer_create(10);
205
206
+ rrdhost_rdlock(localhost);
207
netdata_rwlock_rdlock(&localhost->labels_rwlock);
208
struct label *l=localhost->labels;
209
while (l != NULL) {
@@ -210,6 +211,7 @@ static cmd_status_t cmd_reload_labels_execute(char *args, char **message)
211
l = l->next;
212
}
213
netdata_rwlock_unlock(&localhost->labels_rwlock);
214
+ rrdhost_unlock(localhost);
215
216
(*message)=strdupz(buffer_tostring(wb));
217
buffer_free(wb);
database/rrdcalc.c
+1
@@ -662,6 +662,7 @@ static void rrdcalc_labels_unlink_alarm_loop(RRDHOST *host, RRDCALC *alarms) {
662
}
663
664
void rrdcalc_labels_unlink_alarm_from_host(RRDHOST *host) {
665
+ rrdhost_check_rdlock(host);
666
netdata_rwlock_rdlock(&host->labels_rwlock);
667
668
rrdcalc_labels_unlink_alarm_loop(host, host->alarms);
database/rrdcalctemplate.c
+1
@@ -15,6 +15,7 @@ static int rrdcalctemplate_is_there_label_restriction(RRDCALCTEMPLATE *rt, RRDH
15
16
int ret;
17
if(move) {
18
+ rrdhost_check_rdlock(host);
19
netdata_rwlock_rdlock(&host->labels_rwlock);
20
while(move) {
21
snprintfz(cmp, CONFIG_FILE_LINE_MAX, "%s=%s", move->key, move->value);
database/rrdhost.c
+180
@@ -156,6 +156,7 @@ RRDHOST *rrdhost_create(const char *hostname,
156
157
netdata_mutex_init(&host->rrdpush_sender_buffer_mutex);
158
netdata_rwlock_init(&host->rrdhost_rwlock);
159
+ netdata_rwlock_init(&host->labels_rwlock);
160
161
rrdhost_init_hostname(host, hostname);
162
rrdhost_init_machine_guid(host, guid);
@@ -664,6 +665,7 @@ void rrdhost_free(RRDHOST *host) {
665
// free it
666
667
freez((void *)host->tags);
668
+ free_host_labels(host->labels);
669
freez((void *)host->os);
670
freez((void *)host->timezone);
671
freez(host->program_version);
@@ -680,6 +682,7 @@ void rrdhost_free(RRDHOST *host) {
682
freez(host->registry_hostname);
683
simple_pattern_free(host->rrdpush_send_charts_matching);
684
rrdhost_unlock(host);
685
+ netdata_rwlock_destroy(&host->labels_rwlock);
686
netdata_rwlock_destroy(&host->health_log.alarm_log_rwlock);
687
netdata_rwlock_destroy(&host->rrdhost_rwlock);
688
freez(host);
@@ -842,6 +845,178 @@ struct label *load_config_labels()
845
return l;
846
}
847
848
+typedef enum strip_quotes {
849
+ DO_NOT_STRIP_QUOTES,
850
+ STRIP_QUOTES
851
+} STRIP_QUOTES_OPTION;
852
+
853
+typedef enum skip_escaped_characters {
854
+ DO_NOT_SKIP_ESCAPED_CHARACTERS,
855
+ SKIP_ESCAPED_CHARACTERS
856
+} SKIP_ESCAPED_CHARACTERS_OPTION;
857
+
858
+static inline void strip_last_symbol(
859
+ char *str,
860
+ char symbol,
861
+ SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters)
862
+{
863
+ char *end = str;
864
+
865
+ while (*end && *end != symbol) {
866
+ if (unlikely(skip_escaped_characters && *end == '\\')) {
867
+ end++;
868
+ if (unlikely(!*end))
869
+ break;
870
+ }
871
+ end++;
872
+ }
873
+ if (likely(*end == symbol))
874
+ *end = '\0';
875
+}
876
+
877
+static inline char *strip_double_quotes(char *str, SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters)
878
+{
879
+ if (*str == '"') {
880
+ str++;
881
+ strip_last_symbol(str, '"', skip_escaped_characters);
882
+ }
883
+
884
+ return str;
885
+}
886
+
887
+struct label *parse_simple_tags(
888
+ struct label *label_list,
889
+ const char *tags,
890
+ char key_value_separator,
891
+ char label_separator,
892
+ STRIP_QUOTES_OPTION strip_quotes_from_key,
893
+ STRIP_QUOTES_OPTION strip_quotes_from_value,
894
+ SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters)
895
+{
896
+ const char *end = tags;
897
+
898
+ while (*end) {
899
+ const char *start = end;
900
+ char key[CONFIG_MAX_VALUE + 1];
901
+ char value[CONFIG_MAX_VALUE + 1];
902
+
903
+ while (*end && *end != key_value_separator)
904
+ end++;
905
+ strncpyz(key, start, end - start);
906
+
907
+ if (*end)
908
+ start = ++end;
909
+ while (*end && *end != label_separator)
910
+ end++;
911
+ strncpyz(value, start, end - start);
912
+
913
+ label_list = add_label_to_list(
914
+ label_list,
915
+ strip_quotes_from_key ? strip_double_quotes(trim(key), skip_escaped_characters) : trim(key),
916
+ strip_quotes_from_value ? strip_double_quotes(trim(value), skip_escaped_characters) : trim(value),
917
+ LABEL_SOURCE_NETDATA_CONF);
918
+
919
+ if (*end)
920
+ end++;
921
+ }
922
+
923
+ return label_list;
924
+}
925
+
926
+struct label *parse_json_tags(struct label *label_list, const char *tags)
927
+{
928
+ char tags_buf[CONFIG_MAX_VALUE + 1];
929
+ strncpy(tags_buf, tags, CONFIG_MAX_VALUE);
930
+ char *str = tags_buf;
931
+
932
+ switch (*str) {
933
+ case '{':
934
+ str++;
935
+ strip_last_symbol(str, '}', SKIP_ESCAPED_CHARACTERS);
936
+
937
+ label_list = parse_simple_tags(label_list, str, ':', ',', STRIP_QUOTES, STRIP_QUOTES, SKIP_ESCAPED_CHARACTERS);
938
+
939
+ break;
940
+ case '[':
941
+ str++;
942
+ strip_last_symbol(str, ']', SKIP_ESCAPED_CHARACTERS);
943
+
944
+ char *end = str + strlen(str);
945
+ size_t i = 0;
946
+
947
+ while (str < end) {
948
+ char key[CONFIG_MAX_VALUE + 1];
949
+ snprintfz(key, CONFIG_MAX_VALUE, "host_tag%zu", i);
950
+
951
+ str = strip_double_quotes(trim(str), SKIP_ESCAPED_CHARACTERS);
952
+
953
+ label_list = add_label_to_list(label_list, key, str, LABEL_SOURCE_NETDATA_CONF);
954
+
955
+ // skip to the next element in the array
956
+ str += strlen(str) + 1;
957
+ while (*str && *str != ',')
958
+ str++;
959
+ str++;
960
+ i++;
961
+ }
962
+
963
+ break;
964
+ case '"':
965
+ label_list = add_label_to_list(
966
+ label_list, "host_tag", strip_double_quotes(str, SKIP_ESCAPED_CHARACTERS), LABEL_SOURCE_NETDATA_CONF);
967
+ break;
968
+ default:
969
+ label_list = add_label_to_list(label_list, "host_tag", str, LABEL_SOURCE_NETDATA_CONF);
970
+ break;
971
+ }
972
+
973
+ return label_list;
974
+}
975
+
976
+struct label *load_labels_from_tags()
977
+{
978
+ if (!localhost->tags)
979
+ return NULL;
980
+
981
+ struct label *label_list = NULL;
982
+ BACKEND_TYPE type = BACKEND_TYPE_UNKNOWN;
983
+
984
+ if (config_exists(CONFIG_SECTION_BACKEND, "enabled")) {
985
+ if (config_get_boolean(CONFIG_SECTION_BACKEND, "enabled", CONFIG_BOOLEAN_NO) != CONFIG_BOOLEAN_NO) {
986
+ const char *type_name = config_get(CONFIG_SECTION_BACKEND, "type", "graphite");
987
+ type = backend_select_type(type_name);
988
+ }
989
+ }
990
+
991
+ switch (type) {
992
+ case BACKEND_TYPE_GRAPHITE:
993
+ label_list = parse_simple_tags(
994
+ label_list, localhost->tags, '=', ';', DO_NOT_STRIP_QUOTES, DO_NOT_STRIP_QUOTES,
995
+ DO_NOT_SKIP_ESCAPED_CHARACTERS);
996
+ break;
997
+ case BACKEND_TYPE_OPENTSDB_USING_TELNET:
998
+ label_list = parse_simple_tags(
999
+ label_list, localhost->tags, '=', ' ', DO_NOT_STRIP_QUOTES, DO_NOT_STRIP_QUOTES,
1000
+ DO_NOT_SKIP_ESCAPED_CHARACTERS);
1001
+ break;
1002
+ case BACKEND_TYPE_OPENTSDB_USING_HTTP:
1003
+ label_list = parse_simple_tags(
1004
+ label_list, localhost->tags, ':', ',', STRIP_QUOTES, STRIP_QUOTES,
1005
+ DO_NOT_SKIP_ESCAPED_CHARACTERS);
1006
+ break;
1007
+ case BACKEND_TYPE_JSON:
1008
+ label_list = parse_json_tags(label_list, localhost->tags);
1009
+ break;
1010
+ default:
1011
+ label_list = parse_simple_tags(
1012
+ label_list, localhost->tags, '=', ',', DO_NOT_STRIP_QUOTES, STRIP_QUOTES,
1013
+ DO_NOT_SKIP_ESCAPED_CHARACTERS);
1014
+ break;
1015
+ }
1016
+
1017
+ return label_list;
1018
+}
1019
+
1020
struct label *load_kubernetes_labels()
1021
{
1022
struct label *l=NULL;
@@ -931,6 +1106,7 @@ void free_host_labels(struct label *labels)
1106
1107
void replace_label_list(RRDHOST *host, struct label *new_labels)
1108
{
1109
+ rrdhost_check_rdlock(host);
1110
netdata_rwlock_wrlock(&host->labels_rwlock);
1111
struct label *old_labels = host->labels;
1112
host->labels = new_labels;
@@ -982,13 +1158,17 @@ void reload_host_labels()
1158
struct label *from_auto = load_auto_labels();
1159
struct label *from_k8s = load_kubernetes_labels();
1160
struct label *from_config = load_config_labels();
1161
+ struct label *from_tags = load_labels_from_tags();
1162
1163
struct label *new_labels = merge_label_lists(from_auto, from_k8s);
1164
+ new_labels = merge_label_lists(new_labels, from_tags);
1165
new_labels = merge_label_lists(new_labels, from_config);
1166
1167
+ rrdhost_rdlock(localhost);
1168
replace_label_list(localhost, new_labels);
1169
1170
health_label_log_save(localhost);
1171
+ rrdhost_unlock(localhost);
1172
1173
if(localhost->rrdpush_send_enabled && localhost->rrdpush_sender_buffer){
1174
localhost->labels_flag |= LABEL_FLAG_UPDATE_STREAM;
exporting/graphite/graphite.c
+1
@@ -87,6 +87,7 @@ int format_host_labels_graphite_plaintext(struct instance *instance, RRDHOST *ho
87
if (unlikely(!sending_labels_configured(instance)))
88
return 0;
89
90
+ rrdhost_check_rdlock(host);
91
netdata_rwlock_rdlock(&host->labels_rwlock);
92
for (struct label *label = host->labels; label; label = label->next) {
93
if (!should_send_label(instance, label))
exporting/json/json.c
+1
@@ -69,6 +69,7 @@ int format_host_labels_json_plaintext(struct instance *instance, RRDHOST *host)
69
buffer_strcat(instance->labels, "\"labels\":{");
70
71
int count = 0;
72
+ rrdhost_check_rdlock(host);
73
netdata_rwlock_rdlock(&host->labels_rwlock);
74
for (struct label *label = host->labels; label; label = label->next) {
75
if (!should_send_label(instance, label))
exporting/opentsdb/opentsdb.c
+2
@@ -119,6 +119,7 @@ int format_host_labels_opentsdb_telnet(struct instance *instance, RRDHOST *host)
119
if (unlikely(!sending_labels_configured(instance)))
120
return 0;
121
122
+ rrdhost_check_rdlock(localhost);
123
netdata_rwlock_rdlock(&host->labels_rwlock);
124
for (struct label *label = host->labels; label; label = label->next) {
125
if (!should_send_label(instance, label))
@@ -261,6 +262,7 @@ int format_host_labels_opentsdb_http(struct instance *instance, RRDHOST *host)
262
if (unlikely(!sending_labels_configured(instance)))
263
return 0;
264
265
+ rrdhost_check_rdlock(host);
266
netdata_rwlock_rdlock(&host->labels_rwlock);
267
for (struct label *label = host->labels; label; label = label->next) {
268
if (!should_send_label(instance, label))
health/health_log.c
+1
@@ -72,6 +72,7 @@ inline void health_label_log_save(RRDHOST *host) {
72
73
if(likely(host->health_log_fp)) {
74
BUFFER *wb = buffer_create(1024);
75
+ rrdhost_check_rdlock(host);
76
netdata_rwlock_rdlock(&host->labels_rwlock);
77
struct label *l=localhost->labels;
78
while (l != NULL) {
libnetdata/config/appconfig.c
+3
-1
@@ -566,7 +566,9 @@ int appconfig_load(struct config *root, char *filename, int overwrite_used, cons
566
error("INTERNAL ERROR: Cannot remove '%s' from section '%s', it was not inserted before.",
567
cv2->name, co->name);
568
569
- free(cv2);
569
+ freez(cv2->name);
570
+ freez(cv2->value);
571
+ freez(cv2);
572
cv2 = save;
573
}
574
co->values = NULL;
streaming/rrdpush.c
+2
@@ -347,6 +347,7 @@ void rrdpush_send_labels(RRDHOST *host) {
347
return;
348
349
rrdpush_buffer_lock(host);
350
+ rrdhost_rdlock(host);
351
netdata_rwlock_rdlock(&host->labels_rwlock);
352
353
struct label *labels = host->labels;
@@ -364,6 +365,7 @@ void rrdpush_send_labels(RRDHOST *host) {
365
, "OVERWRITE %s\n", "labels");
366
367
netdata_rwlock_unlock(&host->labels_rwlock);
368
+ rrdhost_unlock(host);
369
370
if(host->rrdpush_sender_pipe[PIPE_WRITE] != -1 && write(host->rrdpush_sender_pipe[PIPE_WRITE], " ", 1) == -1)
371
error("STREAM %s [send]: cannot write to internal pipe", host->hostname);
web/api/web_api_v1.c
+2
@@ -776,6 +776,7 @@ inline void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation) {
776
}
777
778
int count = 0;
779
+ rrdhost_rdlock(host);
780
netdata_rwlock_rdlock(&host->labels_rwlock);
781
for (struct label *label = host->labels; label; label = label->next) {
782
if(count > 0) buffer_strcat(wb, ",\n");
@@ -789,6 +790,7 @@ inline void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation) {
790
}
791
buffer_strcat(wb, "\n");
792
netdata_rwlock_unlock(&host->labels_rwlock);
793
+ rrdhost_unlock(host);
794
}
795
796
inline int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url) {