@cryptotaxi247 / netdata-1 / commits / c8c72f18a

Labels issues (#7515)

Initial work on host labels from the dedicated branch. Includes work for issues #7096, #7400, #7411, #7369, #7410, #7458, #7459, #7412 and #7408 by @vlvkobal, @thiagoftsm, @cakrit and @amoss.

Andrew Moss committed Dec 16, 2019 at 15:12 UTC c8c72f18a6a8fd09d3b6284e49525396b24e8395
36 files changed +906 -76
.gitignore
+1
@@ -113,6 +113,7 @@ system/netdata-freebsd
113 system/edit-config
114
115 daemon/anonymous-statistics.sh
116 +daemon/get-kubernetes-labels.sh
117
118 health/notifications/alarm-notify.sh
119 collectors/cgroups.plugin/cgroup-name.sh
BUILD.md
+1
@@ -1,3 +1,4 @@
1 +
2 # The build system
3
4 We are currently migrating from `autotools` to `CMake` as a build-system. This document
daemon/Makefile.am
+2
@@ -13,9 +13,11 @@ dist_noinst_DATA = \
13 README.md \
14 config/README.md \
15 anonymous-statistics.sh.in \
16 + get-kubernetes-labels.sh.in \
17 $(NULL)
18
19 dist_plugins_SCRIPTS = \
20 anonymous-statistics.sh \
21 system-info.sh \
22 + get-kubernetes-labels.sh \
23 $(NULL)
daemon/commands.c
+26
@@ -40,6 +40,7 @@ static cmd_status_t cmd_save_database_execute(char *args, char **message);
40 static cmd_status_t cmd_reopen_logs_execute(char *args, char **message);
41 static cmd_status_t cmd_exit_execute(char *args, char **message);
42 static cmd_status_t cmd_fatal_execute(char *args, char **message);
43 +static cmd_status_t cmd_reload_labels_execute(char *args, char **message);
44
45 static command_info_t command_info_array[] = {
46 {"help", cmd_help_execute, CMD_TYPE_HIGH_PRIORITY}, // show help menu
@@ -48,6 +49,7 @@ static command_info_t command_info_array[] = {
49 {"reopen-logs", cmd_reopen_logs_execute, CMD_TYPE_ORTHOGONAL}, // Close and reopen log files
50 {"shutdown-agent", cmd_exit_execute, CMD_TYPE_EXCLUSIVE}, // exit cleanly
51 {"fatal-agent", cmd_fatal_execute, CMD_TYPE_HIGH_PRIORITY}, // exit with fatal error
52 + {"reload-labels", cmd_reload_labels_execute, CMD_TYPE_ORTHOGONAL}, // reload the labels
53 };
54
55 /* Mutexes for commands of type CMD_TYPE_ORTHOGONAL */
@@ -97,6 +99,8 @@ static cmd_status_t cmd_help_execute(char *args, char **message)
99 " Show this help menu.\n"
100 "reload-health\n"
101 " Reload health configuration.\n"
102 + "reload-labels\n"
103 + " Reload all labels.\n"
104 "save-database\n"
105 " Save internal DB to disk for memory mode save.\n"
106 "reopen-logs\n"
@@ -172,6 +176,28 @@ static cmd_status_t cmd_fatal_execute(char *args, char **message)
176 return CMD_STATUS_SUCCESS;
177 }
178
179 +static cmd_status_t cmd_reload_labels_execute(char *args, char **message)
180 +{
181 + (void)args;
182 + info("COMMAND: reloading host labels.");
183 + reload_host_labels();
184 +
185 + BUFFER *wb = buffer_create(10);
186 +
187 + netdata_rwlock_rdlock(&localhost->labels_rwlock);
188 + struct label *l=localhost->labels;
189 + while (l != NULL) {
190 + buffer_sprintf(wb,"Label [source id=%s]: \"%s\" -> \"%s\"\n", translate_label_source(l->label_source), l->key, l->value);
191 + l = l->next;
192 + }
193 + netdata_rwlock_unlock(&localhost->labels_rwlock);
194 +
195 + (*message)=strdupz(buffer_tostring(wb));
196 + buffer_free(wb);
197 +
198 + return CMD_STATUS_SUCCESS;
199 +}
200 +
201 static void cmd_lock_exclusive(unsigned index)
202 {
203 (void)index;
daemon/commands.h
+1
@@ -19,6 +19,7 @@ typedef enum cmd {
19 CMD_REOPEN_LOGS,
20 CMD_EXIT,
21 CMD_FATAL,
22 + CMD_RELOAD_LABELS,
23 CMD_TOTAL_COMMANDS
24 } cmd_t;
25
daemon/common.h
+1 -1
@@ -8,7 +8,7 @@
8 // ----------------------------------------------------------------------------
9 // shortcuts for the default netdata configuration
10
11 -#define config_load(filename, overwrite_used) appconfig_load(&netdata_config, filename, overwrite_used)
11 +#define config_load(filename, overwrite_used, section) appconfig_load(&netdata_config, filename, overwrite_used, section)
12 #define config_get(section, name, default_value) appconfig_get(&netdata_config, section, name, default_value)
13 #define config_get_number(section, name, value) appconfig_get_number(&netdata_config, section, name, value)
14 #define config_get_float(section, name, value) appconfig_get_float(&netdata_config, section, name, value)
daemon/get-kubernetes-labels.sh.in new
+18
@@ -0,0 +1,18 @@
1 +#!/usr/bin/env bash
2 +
3 +# Checks if netdata is running in a kubernetes pod and fetches that pod's labels
4 +
5 +if [ -n "${KUBERNETES_SERVICE_HOST}" ] && [ -n "${KUBERNETES_PORT_443_TCP_PORT}" ] && [ -n "${MY_POD_NAMESPACE}" ] && [ -n "${MY_POD_NAME}" ]; then
6 + if command -v jq >/dev/null 2>&1; then
7 + KUBE_TOKEN="$(</var/run/secrets/kubernetes.io/serviceaccount/token)"
8 + URL="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/$MY_POD_NAMESPACE/pods/$MY_POD_NAME"
9 + curl -sSk -H "Authorization: Bearer $KUBE_TOKEN" "$URL" |
10 + jq -r '.metadata.labels' | grep ':' | tr -d '," '
11 + exit 0
12 + else
13 + echo "jq command not available. Please install jq to get host labels for kubernetes pods."
14 + exit 1
15 + fi
16 +else
17 + exit 0
18 +fi
daemon/main.c
+6 -3
@@ -703,20 +703,20 @@ static int load_netdata_conf(char *filename, char overwrite_used) {
703 int ret = 0;
704
705 if(filename && *filename) {
706 - ret = config_load(filename, overwrite_used);
706 + ret = config_load(filename, overwrite_used, NULL);
707 if(!ret)
708 error("CONFIG: cannot load config file '%s'.", filename);
709 }
710 else {
711 filename = strdupz_path_subpath(netdata_configured_user_config_dir, "netdata.conf");
712
713 - ret = config_load(filename, overwrite_used);
713 + ret = config_load(filename, overwrite_used, NULL);
714 if(!ret) {
715 info("CONFIG: cannot load user config '%s'. Will try the stock version.", filename);
716 freez(filename);
717
718 filename = strdupz_path_subpath(netdata_configured_stock_config_dir, "netdata.conf");
719 - ret = config_load(filename, overwrite_used);
719 + ret = config_load(filename, overwrite_used, NULL);
720 if(!ret)
721 info("CONFIG: cannot load stock config '%s'. Running with internal defaults.", filename);
722 }
@@ -1271,6 +1271,9 @@ int main(int argc, char **argv) {
1271
1272 error_log_limit_reset();
1273
1274 + // Load host labels
1275 + reload_host_labels();
1276 +
1277 // ------------------------------------------------------------------------
1278 // spawn the threads
1279
daemon/system-info.sh
+1 -1
@@ -107,7 +107,7 @@ if [ "${CONTAINER}" = "unknown" ]; then
107 fi
108 fi
109
110 -echo "NETDATA_SYSTEM_OS_NAME=\"${NAME}\""
110 +echo "NETDATA_SYSTEM_OS_NAME=${NAME}"
111 echo "NETDATA_SYSTEM_OS_ID=${ID}"
112 echo "NETDATA_SYSTEM_OS_ID_LIKE=${ID_LIKE}"
113 echo "NETDATA_SYSTEM_OS_VERSION=${VERSION}"
database/rrd.h
+24
@@ -148,6 +148,25 @@ typedef enum rrddim_flags {
148 #define rrddim_flag_clear(rd, flag) (rd)->flags &= ~(flag)
149 #endif
150
151 +typedef enum label_source {
152 + LABEL_SOURCE_AUTO = 0,
153 + LABEL_SOURCE_NETDATA_CONF = 1,
154 + LABEL_SOURCE_DOCKER = 2,
155 + LABEL_SOURCE_ENVIRONMENT = 3,
156 + LABEL_SOURCE_KUBERNETES = 4
157 +} LABEL_SOURCE;
158 +
159 +struct label {
160 + char *key, *value;
161 + uint32_t key_hash;
162 + LABEL_SOURCE label_source;
163 + struct label *next;
164 +};
165 +
166 +char *translate_label_source(LABEL_SOURCE l);
167 +struct label *create_label(char *key, char *value, LABEL_SOURCE label_source);
168 +struct label *add_label_to_list(struct label *l, char *key, char *value, LABEL_SOURCE label_source);
169 +void reload_host_labels();
170
171 // ----------------------------------------------------------------------------
172 // RRD DIMENSION - this is a metric
@@ -726,6 +745,11 @@ struct rrdhost {
745
746 netdata_rwlock_t rrdhost_rwlock; // lock for this RRDHOST (protects rrdset_root linked list)
747
748 + // ------------------------------------------------------------------------
749 + // Support for host-level labels
750 + struct label *labels;
751 + netdata_rwlock_t labels_rwlock; // lock for the label list
752 +
753 // ------------------------------------------------------------------------
754 // indexes
755
database/rrdcalc.c
+87
@@ -559,6 +559,8 @@ void rrdcalc_free(RRDCALC *rc) {
559 freez(rc->units);
560 freez(rc->info);
561 simple_pattern_free(rc->spdim);
562 + freez(rc->labels);
563 + simple_pattern_free(rc->splabels);
564 freez(rc);
565 }
566
@@ -603,6 +605,91 @@ void rrdcalc_unlink_and_free(RRDHOST *host, RRDCALC *rc) {
605 rrdcalc_free(rc);
606 }
607
608 +void rrdcalc_foreach_unlink_and_free(RRDHOST *host, RRDCALC *rc) {
609 +
610 + if(unlikely(rc == host->alarms_with_foreach))
611 + host->alarms_with_foreach = rc->next;
612 + else {
613 + RRDCALC *t;
614 + for(t = host->alarms_with_foreach; t && t->next != rc; t = t->next) ;
615 + if(t) {
616 + t->next = rc->next;
617 + rc->next = NULL;
618 + }
619 + else
620 + error("Cannot unlink alarm '%s.%s' from host '%s': not found", rc->chart?rc->chart:"NOCHART", rc->name, host->hostname);
621 + }
622 +
623 + rrdcalc_free(rc);
624 +}
625 +
626 +static void rrdcalc_labels_unlink_alarm_loop(RRDHOST *host, RRDCALC *alarms) {
627 + RRDCALC *rc = alarms;
628 + while (rc) {
629 + if (!rc->labels) {
630 + rc = rc->next;
631 + continue;
632 + }
633 +
634 + char cmp[CONFIG_FILE_LINE_MAX+1];
635 + struct label *move = host->labels;
636 + while(move) {
637 + snprintf(cmp, CONFIG_FILE_LINE_MAX, "%s=%s", move->key, move->value);
638 + if (simple_pattern_matches(rc->splabels, move->key) ||
639 + simple_pattern_matches(rc->splabels, cmp)) {
640 + break;
641 + }
642 +
643 + move = move->next;
644 + }
645 +
646 + RRDCALC *next = rc->next;
647 + if(!move) {
648 + info("Health configuration for alarm '%s' cannot be applied, because the host %s does not have the label(s) '%s'",
649 + rc->name,
650 + host->hostname,
651 + rc->labels);
652 +
653 + if(host->alarms == alarms) {
654 + rrdcalc_unlink_and_free(host, rc);
655 + } else
656 + rrdcalc_foreach_unlink_and_free(host, rc);
657 +
658 + }
659 +
660 + rc = next;
661 + }
662 +}
663 +
664 +void rrdcalc_labels_unlink_alarm_from_host(RRDHOST *host) {
665 + netdata_rwlock_rdlock(&host->labels_rwlock);
666 +
667 + rrdcalc_labels_unlink_alarm_loop(host, host->alarms);
668 + rrdcalc_labels_unlink_alarm_loop(host, host->alarms_with_foreach);
669 +
670 + netdata_rwlock_unlock(&host->labels_rwlock);
671 +}
672 +
673 +void rrdcalc_labels_unlink() {
674 + rrd_rdlock();
675 +
676 + RRDHOST *host;
677 + rrdhost_foreach_read(host) {
678 + if (unlikely(!host->health_enabled))
679 + continue;
680 +
681 + if (host->labels) {
682 + rrdhost_wrlock(host);
683 +
684 + rrdcalc_labels_unlink_alarm_from_host(host);
685 +
686 + rrdhost_unlock(host);
687 + }
688 + }
689 +
690 + rrd_unlock();
691 +}
692 +
693 // ----------------------------------------------------------------------------
694 // Alarm
695
database/rrdcalc.h
+8
@@ -90,6 +90,11 @@ struct rrdcalc {
90 uint32_t warn_repeat_every; // interval between repeating warning notifications
91 uint32_t crit_repeat_every; // interval between repeating critical notifications
92
93 + // ------------------------------------------------------------------------
94 + // Labels settings
95 + char *labels; // the label read from an alarm file
96 + SIMPLE_PATTERN *splabels; // the simple pattern of labels
97 +
98 // ------------------------------------------------------------------------
99 // runtime information
100
@@ -157,6 +162,9 @@ extern void rrdcalc_add_to_host(RRDHOST *host, RRDCALC *rc);
162 extern void dimension_remove_pipe_comma(char *str);
163 extern char *alarm_name_with_dim(char *name, size_t namelen, const char *dim, size_t dimlen);
164
165 +extern void rrdcalc_labels_unlink();
166 +extern void rrdcalc_labels_unlink_alarm_from_host(RRDHOST *host);
167 +
168 static inline int rrdcalc_isrepeating(RRDCALC *rc) {
169 if (unlikely(rc->warn_repeat_every > 0 || rc->crit_repeat_every > 0)) {
170 return 1;
database/rrdcalctemplate.c
+51 -5
@@ -4,6 +4,45 @@
4 #include "rrd.h"
5
6 // ----------------------------------------------------------------------------
7 +
8 +static int rrdcalctemplate_is_there_label_restriction(RRDCALCTEMPLATE *rt, RRDHOST *host) {
9 + if(!rt->labels)
10 + return 0;
11 +
12 + errno = 0;
13 + struct label *move = host->labels;
14 + char cmp[CONFIG_FILE_LINE_MAX+1];
15 +
16 + int ret;
17 + if(move) {
18 + netdata_rwlock_rdlock(&host->labels_rwlock);
19 + while(move) {
20 + snprintfz(cmp, CONFIG_FILE_LINE_MAX, "%s=%s", move->key, move->value);
21 + if (simple_pattern_matches(rt->splabels, move->key) ||
22 + simple_pattern_matches(rt->splabels, cmp)) {
23 + break;
24 + }
25 + move = move->next;
26 + }
27 + netdata_rwlock_unlock(&host->labels_rwlock);
28 +
29 + if(!move) {
30 + error("Health template '%s' cannot be applied, because the host %s does not have the label(s) '%s'",
31 + rt->name,
32 + host->hostname,
33 + rt->labels
34 + );
35 + ret = 1;
36 + } else {
37 + ret = 0;
38 + }
39 + } else {
40 + ret =0;
41 + }
42 +
43 + return ret;
44 +}
45 +
46 // RRDCALCTEMPLATE management
47 /**
48 * RRDCALC TEMPLATE LINK MATCHING
@@ -14,13 +53,18 @@
53 void rrdcalctemplate_link_matching_test(RRDCALCTEMPLATE *rt, RRDSET *st, RRDHOST *host ) {
54 if(rt->hash_context == st->hash_context && !strcmp(rt->context, st->context)
55 && (!rt->family_pattern || simple_pattern_matches(rt->family_pattern, st->family))) {
17 - RRDCALC *rc = rrdcalc_create_from_template(host, rt, st->id);
18 - if(unlikely(!rc))
19 - info("Health tried to create alarm from template '%s' on chart '%s' of host '%s', but it failed", rt->name, st->id, host->hostname);
56 + if (!rrdcalctemplate_is_there_label_restriction(rt, host)) {
57 + RRDCALC *rc = rrdcalc_create_from_template(host, rt, st->id);
58 + if (unlikely(!rc))
59 + info("Health tried to create alarm from template '%s' on chart '%s' of host '%s', but it failed",
60 + rt->name, st->id, host->hostname);
61 #ifdef NETDATA_INTERNAL_CHECKS
21 - else if(rc->rrdset != st && !rc->foreachdim) //When we have a template with foreadhdim, the child will be added to the index late
22 - error("Health alarm '%s.%s' should be linked to chart '%s', but it is not", rc->chart?rc->chart:"NOCHART", rc->name, st->id);
62 + else if (rc->rrdset != st &&
63 + !rc->foreachdim) //When we have a template with foreadhdim, the child will be added to the index late
64 + error("Health alarm '%s.%s' should be linked to chart '%s', but it is not",
65 + rc->chart ? rc->chart : "NOCHART", rc->name, st->id);
66 #endif
67 + }
68 }
69 }
70
@@ -56,7 +100,9 @@ inline void rrdcalctemplate_free(RRDCALCTEMPLATE *rt) {
100 freez(rt->info);
101 freez(rt->dimensions);
102 freez(rt->foreachdim);
103 + freez(rt->labels);
104 simple_pattern_free(rt->spdim);
105 + simple_pattern_free(rt->splabels);
106 freez(rt);
107 }
108
database/rrdcalctemplate.h
+5
@@ -58,6 +58,11 @@ struct rrdcalctemplate {
58 uint32_t warn_repeat_every; // interval between repeating warning notifications
59 uint32_t crit_repeat_every; // interval between repeating critical notifications
60
61 + // ------------------------------------------------------------------------
62 + // Labels settings
63 + char *labels; // the label read from an alarm file
64 + SIMPLE_PATTERN *splabels; // the simple pattern of labels
65 +
66 // ------------------------------------------------------------------------
67 // expressions related to the alarm
68
database/rrdhost.c
+242
@@ -709,6 +709,248 @@ void rrdhost_save_charts(RRDHOST *host) {
709 rrdhost_unlock(host);
710 }
711
712 +static int is_valid_label_key(char *key) {
713 + //Prometheus exporter
714 + if(!strcmp(key, "chart") || !strcmp(key, "family") || !strcmp(key, "dimension"))
715 + return 0;
716 +
717 + //Netdata and Prometheus internal
718 + if (*key == '_')
719 + return 0;
720 +
721 + while(*key) {
722 + if(!(isdigit(*key) || isalpha(*key) || *key == '.' || *key == '_' || *key == '-'))
723 + return 0;
724 +
725 + key++;
726 + }
727 +
728 + return 1;
729 +}
730 +
731 +char *translate_label_source(LABEL_SOURCE l) {
732 + switch (l) {
733 + case LABEL_SOURCE_AUTO:
734 + return "AUTO";
735 + case LABEL_SOURCE_NETDATA_CONF:
736 + return "NETDATA.CONF";
737 + case LABEL_SOURCE_DOCKER :
738 + return "DOCKER";
739 + case LABEL_SOURCE_ENVIRONMENT :
740 + return "ENVIRONMENT";
741 + case LABEL_SOURCE_KUBERNETES :
742 + return "KUBERNETES";
743 + default:
744 + return "Invalid label source";
745 + }
746 +}
747 +
748 +struct label *load_auto_labels()
749 +{
750 + struct label *label_list = NULL;
751 +
752 + if (localhost->system_info->os_name)
753 + label_list =
754 + add_label_to_list(label_list, "_os_name", localhost->system_info->os_name, LABEL_SOURCE_AUTO);
755 +
756 + if (localhost->system_info->os_version)
757 + label_list =
758 + add_label_to_list(label_list, "_os_version", localhost->system_info->os_version, LABEL_SOURCE_AUTO);
759 +
760 + if (localhost->system_info->kernel_version)
761 + label_list =
762 + add_label_to_list(label_list, "_kernel_version", localhost->system_info->kernel_version, LABEL_SOURCE_AUTO);
763 +
764 + if (localhost->system_info->architecture)
765 + label_list =
766 + add_label_to_list(label_list, "_architecture", localhost->system_info->architecture, LABEL_SOURCE_AUTO);
767 +
768 + if (localhost->system_info->virtualization)
769 + label_list =
770 + add_label_to_list(label_list, "_virtualization", localhost->system_info->virtualization, LABEL_SOURCE_AUTO);
771 +
772 + if (localhost->system_info->virt_detection)
773 + label_list =
774 + add_label_to_list(label_list, "_container", localhost->system_info->virt_detection, LABEL_SOURCE_AUTO);
775 +
776 + label_list = add_label_to_list(
777 + label_list, "_is_master", (localhost->next || configured_as_master()) ? "true" : "false", LABEL_SOURCE_AUTO);
778 +
779 + if (localhost->rrdpush_send_destination)
780 + label_list =
781 + add_label_to_list(label_list, "_streams_to", localhost->rrdpush_send_destination, LABEL_SOURCE_AUTO);
782 +
783 + return label_list;
784 +}
785 +
786 +struct label *load_config_labels()
787 +{
788 + int status = config_load(NULL, 1, CONFIG_SECTION_HOST_LABEL);
789 + if(!status) {
790 + char *filename = CONFIG_DIR "/" CONFIG_FILENAME;
791 + error("LABEL: Cannot reload the configuration file '%s', using labels in memory", filename);
792 + }
793 +
794 + struct label *l = NULL;
795 + struct section *co = appconfig_get_section(&netdata_config, CONFIG_SECTION_HOST_LABEL);
796 + if(co) {
797 + config_section_wrlock(co);
798 + struct config_option *cv;
799 + for(cv = co->values; cv ; cv = cv->next) {
800 + char *name = cv->name;
801 + if(is_valid_label_key(name) && strcmp(name, "from environment") && strcmp(name, "from kubernetes pods") ) {
802 + l = add_label_to_list(l, name, cv->value, LABEL_SOURCE_NETDATA_CONF);
803 + cv->flags |= CONFIG_VALUE_USED;
804 + } else {
805 + error("LABELS: It was not possible to create the label '%s' because it contains invalid character(s) or values."
806 + , name);
807 + }
808 + }
809 + config_section_unlock(co);
810 + }
811 +
812 + return l;
813 +}
814 +
815 +struct label *load_kubernetes_labels()
816 +{
817 + struct label *l=NULL;
818 + char *label_script = mallocz(sizeof(char) * (strlen(netdata_configured_primary_plugins_dir) + strlen("get-kubernetes-labels.sh") + 2));
819 + sprintf(label_script, "%s/%s", netdata_configured_primary_plugins_dir, "get-kubernetes-labels.sh");
820 + if (unlikely(access(label_script, R_OK) != 0)) {
821 + error("Kubernetes pod label fetching script %s not found.",label_script);
822 + freez(label_script);
823 + } else {
824 + pid_t command_pid;
825 +
826 + debug(D_RRDHOST, "Attempting to fetch external labels via %s", label_script);
827 +
828 + FILE *fp = mypopen(label_script, &command_pid);
829 + if(fp) {
830 + int MAX_LINE_SIZE=300;
831 + char buffer[MAX_LINE_SIZE + 1];
832 + while (fgets(buffer, MAX_LINE_SIZE, fp) != NULL) {
833 + char *name=buffer;
834 + char *value=buffer;
835 + while (*value && *value != ':') value++;
836 + if (*value == ':') {
837 + *value = '\0';
838 + value++;
839 + }
840 + char *eos=value;
841 + while (*eos && *eos != '\n') eos++;
842 + if (*eos == '\n') *eos = '\0';
843 + if (strlen(value)>0) {
844 + if (is_valid_label_key(name)){
845 + l = add_label_to_list(l, name, value, LABEL_SOURCE_KUBERNETES);
846 + } else {
847 + info("Ignoring invalid label name '%s'", name);
848 + }
849 + } else {
850 + error("%s outputted unexpected result: '%s'", label_script, name);
851 + }
852 + };
853 + // Non-zero exit code means that all the script output is error messages. We've shown already any message that didn't include a ':'
854 + // Here we'll inform with an ERROR that the script failed, show whatever (if anything) was added to the list of labels, free the memory and set the return to null
855 + int retcode=mypclose(fp, command_pid);
856 + if (retcode) {
857 + error("%s exited abnormally. No kubernetes labels will be added to the host.", label_script);
858 + struct label *ll=l;
859 + while (ll != NULL) {
860 + info("Ignoring Label [source id=%s]: \"%s\" -> \"%s\"\n", translate_label_source(ll->label_source), ll->key, ll->value);
861 + ll = ll->next;
862 + freez(l);
863 + l=ll;
864 + }
865 + }
866 + }
867 + freez(label_script);
868 + }
869 +
870 + return l;
871 +}
872 +
873 +struct label *create_label(char *key, char *value, LABEL_SOURCE label_source)
874 +{
875 + size_t key_len = strlen(key), value_len = strlen(value);
876 + size_t n = sizeof(struct label) + key_len + 1 + value_len + 1;
877 + struct label *result = callocz(1,n);
878 + if (result != NULL) {
879 + char *c = (char *)result;
880 + c += sizeof(struct label);
881 + strcpy(c, key);
882 + result->key = c;
883 + c += key_len + 1;
884 + strcpy(c, value);
885 + result->value = c;
886 + result->label_source = label_source;
887 + result->key_hash = simple_hash(result->key);
888 + }
889 + return result;
890 +}
891 +
892 +struct label *add_label_to_list(struct label *l, char *key, char *value, LABEL_SOURCE label_source)
893 +{
894 + struct label *lab = create_label(key, value, label_source);
895 + lab->next = l;
896 + return lab;
897 +}
898 +
899 +int label_list_contains(struct label *head, struct label *check)
900 +{
901 + while (head != NULL)
902 + {
903 + if (head->key_hash == check->key_hash && !strcmp(head->key, check->key))
904 + return 1;
905 + head = head->next;
906 + }
907 + return 0;
908 +}
909 +
910 +/* Create a list with entries from both lists.
911 + If any entry in the low priority list is masked by an entry in the high priorty list then delete it.
912 +*/
913 +struct label *merge_label_lists(struct label *lo_pri, struct label *hi_pri)
914 +{
915 + struct label *result = hi_pri;
916 + while (lo_pri != NULL)
917 + {
918 + struct label *current = lo_pri;
919 + lo_pri = lo_pri->next;
920 + if (!label_list_contains(result, current)) {
921 + current->next = result;
922 + result = current;
923 + }
924 + else
925 + freez(current);
926 + }
927 + return result;
928 +}
929 +
930 +void reload_host_labels()
931 +{
932 + struct label *from_auto = load_auto_labels();
933 + struct label *from_k8s = load_kubernetes_labels();
934 + struct label *from_config = load_config_labels();
935 +
936 + struct label *new_labels = merge_label_lists(from_auto, from_k8s);
937 + new_labels = merge_label_lists(new_labels, from_config);
938 +
939 + netdata_rwlock_wrlock(&localhost->labels_rwlock);
940 + struct label *old_labels = localhost->labels;
941 + localhost->labels = new_labels;
942 + netdata_rwlock_unlock(&localhost->labels_rwlock);
943 +
944 + while (old_labels != NULL)
945 + {
946 + struct label *current = old_labels;
947 + old_labels = old_labels->next;
948 + freez(current);
949 + }
950 +
951 + health_reload();
952 +}
953 +
954 // ----------------------------------------------------------------------------
955 // RRDHOST - delete host files
956
database/rrdvar.c
+7 -1
@@ -291,12 +291,18 @@ void health_api_v1_chart_variables2json(RRDSET *st, BUFFER *buf) {
291
292 buffer_sprintf(buf, "{\n\t\"chart\": \"%s\",\n\t\"chart_name\": \"%s\",\n\t\"chart_context\": \"%s\",\n\t\"chart_variables\": {", st->id, st->name, st->context);
293 avl_traverse_lock(&st->rrdvar_root_index, single_variable2json, (void *)&helper);
294 +
295 buffer_sprintf(buf, "\n\t},\n\t\"family\": \"%s\",\n\t\"family_variables\": {", st->family);
296 helper.counter = 0;
297 avl_traverse_lock(&st->rrdfamily->rrdvar_root_index, single_variable2json, (void *)&helper);
297 - buffer_sprintf(buf, "\n\t},\n\t\"host\": \"%s\",\n\t\"host_variables\": {", host->hostname);
298 +
299 + buffer_sprintf(buf, "\n\t},\n\t\"host\": \"%s\",", host->hostname);
300 + buffer_strcat(buf, "\n\t\"labels\": {\n");
301 + host_labels2json(st->rrdhost, buf, 2);
302 + buffer_strcat(buf, "\t},\n\t\"host_variables\": {");
303 helper.counter = 0;
304 avl_traverse_lock(&host->rrdvar_root_index, single_variable2json, (void *)&helper);
305 +
306 buffer_strcat(buf, "\n\t}\n}\n");
307 }
308
docs/configuration-guide.md
+12
@@ -134,4 +134,16 @@ Unix prefers regular expressions. But they are just too hard, too cryptic to use
134
135 So, Netdata supports [simple patterns](../libnetdata/simple_pattern/).
136
137 +## Netdata labels
138 +
139 +Since version 1.20, Netdata accepts user defined labels for host. The labels are defined in the section `[host labels]`.
140 +To define a label inside this section, some rules needs to be followed, or Netdata will reject the label. The following
141 +restrictions are applied for label names:
142 +
143 +- Names cannot start with `_`, but it can be present in other parts of the name.
144 +- Names only accept alphabet letters, numbers, dots, and dashes.
145 +
146 +The policy for values are more flexible, but you can not use exclamation (`!`), whitespaces (` `) and asterisk (`*`),
147 +because they are used to compare label values in health alarms and templates.
148 +
149 [![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fdocs%2Fconfiguration-guide&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
exporting/read_config.c
+2 -2
@@ -205,13 +205,13 @@ struct engine *read_exporting_config()
205
206 char *filename = strdupz_path_subpath(netdata_configured_user_config_dir, EXPORTING_CONF);
207
208 - exporting_config_exists = appconfig_load(&exporting_config, filename, 0);
208 + exporting_config_exists = appconfig_load(&exporting_config, filename, 0, NULL);
209 if (!exporting_config_exists) {
210 info("CONFIG: cannot load user exporting config '%s'. Will try the stock version.", filename);
211 freez(filename);
212
213 filename = strdupz_path_subpath(netdata_configured_stock_config_dir, EXPORTING_CONF);
214 - exporting_config_exists = appconfig_load(&exporting_config, filename, 0);
214 + exporting_config_exists = appconfig_load(&exporting_config, filename, 0, NULL);
215 if (!exporting_config_exists)
216 info("CONFIG: cannot load stock exporting config '%s'. Running with internal defaults.", filename);
217 }
health/REFERENCE.md
+43
@@ -68,6 +68,7 @@ Netdata parses the following lines. Beneath the table is an in-depth explanation
68 | [`delay`](#alarm-line-delay) | no | Optional hysteresis settings to prevent floods of notifications. |
69 | [`repeat`](#alarm-line-repeat) | no | The interval for sending notifications when an alarm is in WARNING or CRITICAL mode. |
70 | [`option`](#alarm-line-option) | no | Add an option to not clear alarms. |
71 +| [`label`](#alarm-line-label) | no | List of labels present on a host. |
72
73 The `alarm` or `template` line must be the first line of any entity.
74
@@ -370,6 +371,48 @@ increasing. Eventually, the comparison will find the averages in the two time-fr
371 However, the issue was not resolved, it's just a matter of the newer data "polluting" the old. For such alarms, it's a
372 good idea to tell Netdata to not clear the notification, by using the `no-clear-notification` option.
373
374 +#### Alarm line `label`
375 +
376 +Defines the list of labels expected on a host. For example, let's suppose that `netdata.conf` is configured with the
377 +following labels:
378 +
379 +```yaml
380 +[host labels]
381 + installed = 20191211
382 + room = server
383 +```
384 +
385 +And more labels in `netdata.conf` for workstations:
386 +
387 +```yaml
388 +[host labels]
389 + installed = 201705
390 + room = workstation
391 +```
392 +
393 +By defining labels inside of `netdata.conf`, you can now apply labels to alarms. For example, you can add the following
394 +line to any alarms you'd like to apply to hosts that have the label `room = server`.
395 +
396 +```yaml
397 +label: room = server
398 +```
399 +
400 +You can also combine labels when applying them to alarms. For example, if you want to raise a specific alarm only for hosts
401 +inside a room that were installed at a specific time, you can write the following label line:
402 +
403 +```yaml
404 +label: room = workstation AND installed = 201705
405 +```
406 +
407 +The `label` is a space-separated list that accepts simple patterns. For example, you can create an alarm
408 +that will be applied to all hosts installed in the last decade with the following line:
409 +
410 +```yaml
411 +label: installed = 201*
412 +```
413 +
414 +See our [simple patterns docs](../libnetdata/simple_pattern/) for more examples.
415 +
416 ## Expressions
417
418 Netdata has an internal [infix expression parser](../libnetdata/eval). This parses expressions and creates an internal
health/health.c
+5
@@ -152,6 +152,9 @@ void health_reload_host(RRDHOST *host) {
152 rrdhost_wrlock(host);
153 health_readdir(host, user_path, stock_path, NULL);
154
155 + //Discard alarms with labels that do not apply to host
156 + rrdcalc_labels_unlink_alarm_from_host(host);
157 +
158 // link the loaded alarms to their charts
159 RRDDIM *rd;
160 rrdset_foreach_write(st, host) {
@@ -577,6 +580,8 @@ void *health_main(void *ptr) {
580 time_t now = now_realtime_sec();
581 time_t hibernation_delay = config_get_number(CONFIG_SECTION_HEALTH, "postpone alarms during hibernation for seconds", 60);
582
583 + rrdcalc_labels_unlink();
584 +
585 unsigned int loop = 0;
586 while(!netdata_exit) {
587 loop++;
health/health_config.c
+30 -1
@@ -24,6 +24,7 @@
24 #define HEALTH_DELAY_KEY "delay"
25 #define HEALTH_OPTIONS_KEY "options"
26 #define HEALTH_REPEAT_KEY "repeat"
27 +#define HEALTH_LABEL_KEY "label"
28
29 static inline int rrdcalc_add_alarm_from_config(RRDHOST *host, RRDCALC *rc) {
30 if(!rc->chart) {
@@ -497,7 +498,8 @@ static int health_readfile(const char *filename, void *data) {
498 hash_recipient = 0,
499 hash_delay = 0,
500 hash_options = 0,
500 - hash_repeat = 0;
501 + hash_repeat = 0,
502 + hash_label = 0;
503
504 char buffer[HEALTH_CONF_MAX_LINE + 1];
505
@@ -522,6 +524,7 @@ static int health_readfile(const char *filename, void *data) {
524 hash_delay = simple_uhash(HEALTH_DELAY_KEY);
525 hash_options = simple_uhash(HEALTH_OPTIONS_KEY);
526 hash_repeat = simple_uhash(HEALTH_REPEAT_KEY);
527 + hash_label = simple_uhash(HEALTH_LABEL_KEY);
528 }
529
530 FILE *fp = fopen(filename, "r");
@@ -795,6 +798,19 @@ static int health_readfile(const char *filename, void *data) {
798 &rc->warn_repeat_every,
799 &rc->crit_repeat_every);
800 }
801 + else if(hash == hash_label && !strcasecmp(key, HEALTH_LABEL_KEY)) {
802 + if(rc->labels) {
803 + if(strcmp(rc->labels, value) != 0)
804 + error("Health configuration at line %zu of file '%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'.",
805 + line, filename, rc->name, key, value, value);
806 +
807 + freez(rc->labels);
808 + simple_pattern_free(rc->splabels);
809 + }
810 +
811 + rc->labels = simple_pattern_trim_around_equal(value);
812 + rc->splabels = simple_pattern_create(rc->labels, NULL, SIMPLE_PATTERN_EXACT);
813 + }
814 else {
815 error("Health configuration at line %zu of file '%s' for alarm '%s' has unknown key '%s'.",
816 line, filename, rc->name, key);
@@ -927,6 +943,19 @@ static int health_readfile(const char *filename, void *data) {
943 &rt->warn_repeat_every,
944 &rt->crit_repeat_every);
945 }
946 + else if(hash == hash_label && !strcasecmp(key, HEALTH_LABEL_KEY)) {
947 + if(rt->labels) {
948 + if(strcmp(rt->labels, value) != 0)
949 + error("Health configuration at line %zu of file '%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
950 + line, filename, rt->name, key, rt->labels, value, value);
951 +
952 + freez(rt->labels);
953 + simple_pattern_free(rt->splabels);
954 + }
955 +
956 + rt->labels = simple_pattern_trim_around_equal(value);
957 + rt->splabels = simple_pattern_create(rt->labels, NULL, SIMPLE_PATTERN_EXACT);
958 + }
959 else {
960 error("Health configuration at line %zu of file '%s' for template '%s' has unknown key '%s'.",
961 line, filename, rt->name, key);
health/health_json.c
+5 -1
@@ -304,8 +304,12 @@ void health_alarms2json(RRDHOST *host, BUFFER *wb, int all) {
304 // for(rt = host->templates; rt ; rt = rt->next)
305 // health_rrdcalctemplate2json_nolock(wb, rt);
306
307 - buffer_strcat(wb, "\n\t}\n}\n");
307 + buffer_strcat(wb, "\n\t},");
308 rrdhost_unlock(host);
309 +
310 + buffer_strcat(wb, "\n\t\"labels\": {\n");
311 + host_labels2json(host, wb, 2);
312 + buffer_strcat(wb, "\t}\n}\n");
313 }
314
315
libnetdata/config/appconfig.c
+36 -45
@@ -2,46 +2,6 @@
2
3 #include "../libnetdata.h"
4
5 -#define CONFIG_FILE_LINE_MAX ((CONFIG_MAX_NAME + CONFIG_MAX_VALUE + 1024) * 2)
6 -
7 -// ----------------------------------------------------------------------------
8 -// definitions
9 -
10 -#define CONFIG_VALUE_LOADED 0x01 // has been loaded from the config
11 -#define CONFIG_VALUE_USED 0x02 // has been accessed from the program
12 -#define CONFIG_VALUE_CHANGED 0x04 // has been changed from the loaded value or the internal default value
13 -#define CONFIG_VALUE_CHECKED 0x08 // has been checked if the value is different from the default
14 -
15 -struct config_option {
16 - avl avl; // the index entry of this entry - this has to be first!
17 -
18 - uint8_t flags;
19 - uint32_t hash; // a simple hash to speed up searching
20 - // we first compare hashes, and only if the hashes are equal we do string comparisons
21 -
22 - char *name;
23 - char *value;
24 -
25 - struct config_option *next; // config->mutex protects just this
26 -};
27 -
28 -struct section {
29 - avl avl; // the index entry of this section - this has to be first!
30 -
31 - uint32_t hash; // a simple hash to speed up searching
32 - // we first compare hashes, and only if the hashes are equal we do string comparisons
33 -
34 - char *name;
35 -
36 - struct section *next; // gloabl config_mutex protects just this
37 -
38 - struct config_option *values;
39 - avl_tree_lock values_index;
40 -
41 - netdata_mutex_t mutex; // this locks only the writers, to ensure atomic updates
42 - // readers are protected using the rwlock in avl_tree_lock
43 -};
44 -
5 /*
6 * @Input:
7 * Connector / instance to add to an internal structure
@@ -129,19 +89,19 @@ int is_valid_connector(char *type, int check_reserved)
89 // ----------------------------------------------------------------------------
90 // locking
91
132 -static inline void appconfig_wrlock(struct config *root) {
92 +inline void appconfig_wrlock(struct config *root) {
93 netdata_mutex_lock(&root->mutex);
94 }
95
136 -static inline void appconfig_unlock(struct config *root) {
96 +inline void appconfig_unlock(struct config *root) {
97 netdata_mutex_unlock(&root->mutex);
98 }
99
140 -static inline void config_section_wrlock(struct section *co) {
100 +inline void config_section_wrlock(struct section *co) {
101 netdata_mutex_lock(&co->mutex);
102 }
103
144 -static inline void config_section_unlock(struct section *co) {
104 +inline void config_section_unlock(struct section *co) {
105 netdata_mutex_unlock(&co->mutex);
106 }
107
@@ -520,7 +480,7 @@ int appconfig_get_duration(struct config *root, const char *section, const char
480 // ----------------------------------------------------------------------------
481 // config load/save
482
523 -int appconfig_load(struct config *root, char *filename, int overwrite_used)
483 +int appconfig_load(struct config *root, char *filename, int overwrite_used, const char *section_name)
484 {
485 int line = 0;
486 struct section *co = NULL;
@@ -543,6 +503,10 @@ int appconfig_load(struct config *root, char *filename, int overwrite_used)
503 return 0;
504 }
505
506 + uint32_t section_hash = 0;
507 + if(section_name) {
508 + section_hash = simple_hash(section_name);
509 + }
510 is_exporter_config = (strstr(filename, EXPORTING_CONF) != NULL);
511
512 while(fgets(buffer, CONFIG_FILE_LINE_MAX, fp) != NULL) {
@@ -592,6 +556,23 @@ int appconfig_load(struct config *root, char *filename, int overwrite_used)
556 co = appconfig_section_find(root, s);
557 if(!co) co = appconfig_section_create(root, s);
558
559 + if(co && section_name && overwrite_used && section_hash == co->hash && !strcmp(section_name, co->name)) {
560 + config_section_wrlock(co);
561 + struct config_option *cv2 = co->values;
562 + while (cv2) {
563 + struct config_option *save = cv2->next;
564 + struct config_option *found = appconfig_option_index_del(co, cv2);
565 + if(found != cv2)
566 + error("INTERNAL ERROR: Cannot remove '%s' from section '%s', it was not inserted before.",
567 + cv2->name, co->name);
568 +
569 + free(cv2);
570 + cv2 = save;
571 + }
572 + co->values = NULL;
573 + config_section_unlock(co);
574 + }
575 +
576 continue;
577 }
578
@@ -601,6 +582,10 @@ int appconfig_load(struct config *root, char *filename, int overwrite_used)
582 continue;
583 }
584
585 + if(section_name && overwrite_used && section_hash != co->hash && strcmp(section_name, co->name)) {
586 + continue;
587 + }
588 +
589 char *name = s;
590 char *value = strchr(s, '=');
591 if(!value) {
@@ -701,6 +686,7 @@ void appconfig_generate(struct config *root, BUFFER *wb, int only_changed)
686 || !strcmp(co->name, CONFIG_SECTION_HEALTH)
687 || !strcmp(co->name, CONFIG_SECTION_BACKEND)
688 || !strcmp(co->name, CONFIG_SECTION_STREAM)
689 + || !strcmp(co->name, CONFIG_SECTION_HOST_LABEL)
690 )
691 pri = 0;
692 else if(!strncmp(co->name, "plugin:", 7)) pri = 1;
@@ -806,3 +792,8 @@ int config_parse_duration(const char* string, int* result) {
792 *result = 0;
793 return 0;
794 }
795 +
796 +struct section *appconfig_get_section(struct config *root, const char *name)
797 +{
798 + return appconfig_section_find(root, name);
799 +}
libnetdata/config/appconfig.h
+47 -6
@@ -92,6 +92,7 @@
92 #define CONFIG_SECTION_BACKEND "backend"
93 #define CONFIG_SECTION_STREAM "stream"
94 #define CONFIG_SECTION_EXPORTING "exporting:global"
95 +#define CONFIG_SECTION_HOST_LABEL "host labels"
96 #define EXPORTING_CONF "exporting.conf"
97
98 // these are used to limit the configuration names and values lengths
@@ -99,17 +100,51 @@
100 #define CONFIG_MAX_NAME 1024
101 #define CONFIG_MAX_VALUE 2048
102
103 +// ----------------------------------------------------------------------------
104 +// Config definitions
105 +#define CONFIG_FILE_LINE_MAX ((CONFIG_MAX_NAME + CONFIG_MAX_VALUE + 1024) * 2)
106 +
107 +#define CONFIG_VALUE_LOADED 0x01 // has been loaded from the config
108 +#define CONFIG_VALUE_USED 0x02 // has been accessed from the program
109 +#define CONFIG_VALUE_CHANGED 0x04 // has been changed from the loaded value or the internal default value
110 +#define CONFIG_VALUE_CHECKED 0x08 // has been checked if the value is different from the default
111 +
112 +struct config_option {
113 + avl avl; // the index entry of this entry - this has to be first!
114 +
115 + uint8_t flags;
116 + uint32_t hash; // a simple hash to speed up searching
117 + // we first compare hashes, and only if the hashes are equal we do string comparisons
118 +
119 + char *name;
120 + char *value;
121 +
122 + struct config_option *next; // config->mutex protects just this
123 +};
124 +
125 +struct section {
126 + avl avl; // the index entry of this section - this has to be first!
127 +
128 + uint32_t hash; // a simple hash to speed up searching
129 + // we first compare hashes, and only if the hashes are equal we do string comparisons
130 +
131 + char *name;
132 +
133 + struct section *next; // gloabl config_mutex protects just this
134 +
135 + struct config_option *values;
136 + avl_tree_lock values_index;
137 +
138 + netdata_mutex_t mutex; // this locks only the writers, to ensure atomic updates
139 + // readers are protected using the rwlock in avl_tree_lock
140 +};
141 +
142 struct config {
143 struct section *sections;
144 netdata_mutex_t mutex;
145 avl_tree_lock index;
146 };
147
108 -//struct connector_instance {
109 -// char instance_name[CONFIG_MAX_NAME + 1];
110 -// char connector_name[CONFIG_MAX_NAME + 1];
111 -//};
112 -
148 #define CONFIG_BOOLEAN_INVALID 100 // an invalid value to check for validity (used as default initialization when needed)
149
150 #define CONFIG_BOOLEAN_NO 0 // disabled
@@ -119,7 +154,9 @@ struct config {
154 #define CONFIG_BOOLEAN_AUTO 2 // enabled if it has useful info when enabled
155 #endif
156
122 -extern int appconfig_load(struct config *root, char *filename, int overwrite_used);
157 +extern int appconfig_load(struct config *root, char *filename, int overwrite_used, const char *section_name);
158 +extern void config_section_wrlock(struct section *co);
159 +extern void config_section_unlock(struct section *co);
160
161 extern char *appconfig_get(struct config *root, const char *section, const char *name, const char *default_value);
162 extern long long appconfig_get_number(struct config *root, const char *section, const char *name, long long value);
@@ -143,6 +180,10 @@ extern int appconfig_section_compare(void *a, void *b);
180
181 extern int config_parse_duration(const char* string, int* result);
182
183 +extern struct section *appconfig_get_section(struct config *root, const char *name);
184 +
185 +extern void appconfig_wrlock(struct config *root);
186 +extern void appconfig_unlock(struct config *root);
187
188 struct connector_instance {
189 char instance_name[CONFIG_MAX_NAME + 1];
libnetdata/inlined.h
+14
@@ -246,6 +246,20 @@ static inline char *strncpyz(char *dst, const char *src, size_t n) {
246 return p;
247 }
248
249 +static inline void escape_json_string(char *dst, char *src, size_t len) {
250 + while (*src != '\0' && len > 1) {
251 + if (*src == '\\' || *src == '\"' || *src < 0x1F) {
252 + *dst++ = '\\';
253 + *dst++ = *src++;
254 + len -= 2;
255 + } else {
256 + *dst++ = *src++;
257 + len--;
258 + }
259 + }
260 + *dst = '\0';
261 +}
262 +
263 static inline int read_file(const char *filename, char *buffer, size_t size) {
264 if(unlikely(!size)) return 3;
265
libnetdata/simple_pattern/simple_pattern.c
+23
@@ -331,3 +331,26 @@ extern int simple_pattern_is_potential_name(SIMPLE_PATTERN *p)
331 }
332 return (alpha || wildcards) && !colon;
333 }
334 +
335 +char *simple_pattern_trim_around_equal(char *src) {
336 + char *store = mallocz(strlen(src) +1);
337 + if(!store)
338 + return NULL;
339 +
340 + char *dst = store;
341 + while (*src) {
342 + if (*src == '=') {
343 + if (*(dst -1) == ' ')
344 + dst--;
345 +
346 + *dst++ = *src++;
347 + if (*src == ' ')
348 + src++;
349 + }
350 +
351 + *dst++ = *src++;
352 + }
353 + *dst = 0x00;
354 +
355 + return store;
356 +}
libnetdata/simple_pattern/simple_pattern.h
+3
@@ -33,4 +33,7 @@ extern void simple_pattern_free(SIMPLE_PATTERN *list);
33 extern void simple_pattern_dump(uint64_t debug_type, SIMPLE_PATTERN *p) ;
34 extern int simple_pattern_is_potential_name(SIMPLE_PATTERN *p) ;
35
36 +//Auxiliary function to create a pattern
37 +char *simple_pattern_trim_around_equal(char *src);
38 +
39 #endif //NETDATA_SIMPLE_PATTERN_H
streaming/rrdpush.c
+21 -2
@@ -57,12 +57,12 @@ char *netdata_ssl_ca_file = NULL;
57 static void load_stream_conf() {
58 errno = 0;
59 char *filename = strdupz_path_subpath(netdata_configured_user_config_dir, "stream.conf");
60 - if(!appconfig_load(&stream_config, filename, 0)) {
60 + if(!appconfig_load(&stream_config, filename, 0, NULL)) {
61 info("CONFIG: cannot load user config '%s'. Will try stock config.", filename);
62 freez(filename);
63
64 filename = strdupz_path_subpath(netdata_configured_stock_config_dir, "stream.conf");
65 - if(!appconfig_load(&stream_config, filename, 0))
65 + if(!appconfig_load(&stream_config, filename, 0, NULL))
66 info("CONFIG: cannot load stock config '%s'. Running with internal defaults.", filename);
67 }
68 freez(filename);
@@ -151,6 +151,25 @@ static inline int should_send_chart_matching(RRDSET *st) {
151 return(rrdset_flag_check(st, RRDSET_FLAG_UPSTREAM_SEND));
152 }
153
154 +int configured_as_master() {
155 + struct section *section = NULL;
156 + int is_master = 0;
157 +
158 + appconfig_wrlock(&stream_config);
159 + for (section = stream_config.sections; section; section = section->next) {
160 + uuid_t uuid;
161 +
162 + if (uuid_parse(section->name, uuid) != -1 &&
163 + appconfig_get_boolean(&stream_config, section->name, "enabled", 0)) {
164 + is_master = 1;
165 + break;
166 + }
167 + }
168 + appconfig_unlock(&stream_config);
169 +
170 + return is_master;
171 +}
172 +
173 // checks if the current chart definition has been sent
174 static inline int need_to_send_chart_definition(RRDSET *st) {
175 rrdset_check_rdlock(st);
streaming/rrdpush.h
+1
@@ -13,6 +13,7 @@ extern char *default_rrdpush_send_charts_matching;
13 extern unsigned int remote_clock_resync_iterations;
14
15 extern int rrdpush_init();
16 +extern int configured_as_master();
17 extern void rrdset_done_push(RRDSET *st);
18 extern void rrdset_push_chart_definition_now(RRDSET *st);
19 extern void *rrdpush_sender_thread(void *ptr);
web/api/formatters/charts2json.c
+6 -1
@@ -56,7 +56,6 @@ void charts2json(RRDHOST *host, BUFFER *wb) {
56 ",\n\t\"history\": %ld"
57 ",\n\t\"memory_mode\": \"%s\""
58 ",\n\t\"custom_info\": \"%s\""
59 - ",\n\t\"charts\": {"
59 , host->hostname
60 , host->program_version
61 , get_release_channel()
@@ -68,6 +67,12 @@ void charts2json(RRDHOST *host, BUFFER *wb) {
67 , custom_dashboard_info_js_filename
68 );
69
70 + buffer_strcat(wb, ",\n\t\"labels\": {\n");
71 + host_labels2json(host, wb, 2);
72 + buffer_strcat(wb, "\t}");
73 +
74 + buffer_strcat(wb, ",\n\t\"charts\": {");
75 +
76 c = 0;
77 rrdhost_rdlock(host);
78 rrdset_foreach_read(st, host) {
web/api/formatters/json_wrapper.c
+23 -4
@@ -26,16 +26,35 @@ void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS
26 " %sapi%s: 1,\n"
27 " %sid%s: %s%s%s,\n"
28 " %sname%s: %s%s%s,\n"
29 - " %sview_update_every%s: %d,\n"
29 + , kq, kq
30 + , kq, kq, sq, r->st->id, sq
31 + , kq, kq, sq, r->st->name, sq);
32 +
33 + RRDHOST *host = r->st->rrdhost;
34 + buffer_sprintf(wb, " %slabels%s: {\n", kq, kq);
35 +
36 + int count = 0;
37 + netdata_rwlock_rdlock(&host->labels_rwlock);
38 + for (struct label *label = host->labels; label; label = label->next) {
39 + if(count > 0) buffer_strcat(wb, ",\n");
40 +
41 + char value[CONFIG_MAX_VALUE * 2 + 1];
42 + escape_json_string(value, label->value, CONFIG_MAX_VALUE * 2);
43 + buffer_sprintf(wb, " %s%s%s: %s%s%s", kq, label->key, kq, sq, value, sq);
44 +
45 + count++;
46 + }
47 + netdata_rwlock_unlock(&host->labels_rwlock);
48 +
49 + buffer_strcat(wb, "\n },\n");
50 +
51 + buffer_sprintf(wb, " %sview_update_every%s: %d,\n"
52 " %supdate_every%s: %d,\n"
53 " %sfirst_entry%s: %u,\n"
54 " %slast_entry%s: %u,\n"
55 " %sbefore%s: %u,\n"
56 " %safter%s: %u,\n"
57 " %sdimension_names%s: ["
36 - , kq, kq
37 - , kq, kq, sq, r->st->id, sq
38 - , kq, kq, sq, r->st->name, sq
58 , kq, kq, r->update_every
59 , kq, kq, r->st->update_every
60 , kq, kq, (uint32_t)rrdset_first_entry_t(r->st)
web/api/formatters/rrdset2json.c
+5 -1
@@ -73,7 +73,11 @@ void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memor
73 if(dimensions_count) *dimensions_count += dimensions;
74 if(memory_used) *memory_used += memory;
75
76 - buffer_sprintf(wb, "\n\t\t\t},\n\t\t\t\"chart_variables\": ");
76 + buffer_strcat(wb, "\n\t\t\t},\n\t\t\t\"labels\": {\n");
77 + host_labels2json(st->rrdhost, wb, 4);
78 + buffer_strcat(wb, "\t\t\t}");
79 +
80 + buffer_sprintf(wb, ",\n\t\t\t\"chart_variables\": ");
81 health_api_v1_chart_custom_variables2json(st, wb);
82
83 buffer_strcat(wb, ",\n\t\t\t\"green\": ");
web/api/netdata-swagger.json
+66
@@ -988,6 +988,17 @@
988 "description": "Container technology detection method.",
989 "example": "dockerenv"
990 },
991 + "labels": {
992 + "type": "object",
993 + "description": "List of host labels.",
994 + "properties": {
995 + "app": {
996 + "type": "string",
997 + "description": "Host label.",
998 + "example": "netdata"
999 + }
1000 + }
1001 + },
1002 "collectors": {
1003 "type": "array",
1004 "items": {
@@ -1068,6 +1079,17 @@
1079 "type": "number",
1080 "description": "The default update frequency of the netdata server. All charts have an update frequency equal or bigger than this."
1081 },
1082 + "labels": {
1083 + "type": "object",
1084 + "description": "List of host labels.",
1085 + "properties": {
1086 + "app": {
1087 + "type": "string",
1088 + "description": "Host label.",
1089 + "example": "netdata"
1090 + }
1091 + }
1092 + },
1093 "charts": {
1094 "type": "object",
1095 "description": "An object containing all the chart objects available at the netdata server. This is used as an indexed array. The key of each chart object is the id of the chart.",
@@ -1170,6 +1192,17 @@
1192 }
1193 }
1194 },
1195 + "labels": {
1196 + "type": "object",
1197 + "description": "List of host labels.",
1198 + "properties": {
1199 + "app": {
1200 + "type": "string",
1201 + "description": "Host label.",
1202 + "example": "netdata"
1203 + }
1204 + }
1205 + },
1206 "chart_variables": {
1207 "type": "object",
1208 "additionalProperties": {
@@ -1211,6 +1244,17 @@
1244 "type": "string",
1245 "description": "The host containing the chart."
1246 },
1247 + "labels": {
1248 + "type": "object",
1249 + "description": "List of host labels.",
1250 + "properties": {
1251 + "app": {
1252 + "type": "string",
1253 + "description": "Host label.",
1254 + "example": "netdata"
1255 + }
1256 + }
1257 + },
1258 "chart_variables": {
1259 "type": "object",
1260 "additionalProperties": {
@@ -1277,6 +1321,17 @@
1321 "type": "string",
1322 "description": "The name of the chart."
1323 },
1324 + "labels": {
1325 + "type": "object",
1326 + "description": "List of host labels.",
1327 + "properties": {
1328 + "app": {
1329 + "type": "string",
1330 + "description": "Host label.",
1331 + "example": "netdata"
1332 + }
1333 + }
1334 + },
1335 "update_every": {
1336 "type": "number",
1337 "description": "The update frequency of this chart, in seconds. One value every this amount of time is kept in the round robin database (indepedently of the current view)."
@@ -1684,6 +1739,17 @@
1739 }
1740 }
1741 }
1742 + },
1743 + "labels": {
1744 + "type": "object",
1745 + "description": "List of host labels.",
1746 + "properties": {
1747 + "app": {
1748 + "type": "string",
1749 + "description": "Host label.",
1750 + "example": "netdata"
1751 + }
1752 + }
1753 }
1754 }
1755 },
web/api/netdata-swagger.yaml
+48
@@ -809,6 +809,14 @@ components:
809 type: string
810 description: Container technology detection method.
811 example: dockerenv
812 + labels:
813 + type: object
814 + description: List of host labels.
815 + properties:
816 + app:
817 + type: string
818 + description: Host label.
819 + example: netdata
820 collectors:
821 type: array
822 items:
@@ -870,6 +878,14 @@ components:
878 type: number
879 description: The default update frequency of the netdata server. All charts have
880 an update frequency equal or bigger than this.
881 + labels:
882 + type: object
883 + description: List of host labels.
884 + properties:
885 + app:
886 + type: string
887 + description: Host label.
888 + example: netdata
889 charts:
890 type: object
891 description: An object containing all the chart objects available at the netdata
@@ -963,6 +979,14 @@ components:
979 name:
980 type: string
981 description: The name of the dimension
982 + labels:
983 + type: object
984 + description: List of host labels.
985 + properties:
986 + app:
987 + type: string
988 + description: Host label.
989 + example: netdata
990 chart_variables:
991 type: object
992 additionalProperties:
@@ -994,6 +1018,14 @@ components:
1018 host:
1019 type: string
1020 description: The host containing the chart.
1021 + labels:
1022 + type: object
1023 + description: List of host labels.
1024 + properties:
1025 + app:
1026 + type: string
1027 + description: Host label.
1028 + example: netdata
1029 chart_variables:
1030 type: object
1031 additionalProperties:
@@ -1041,6 +1073,14 @@ components:
1073 name:
1074 type: string
1075 description: The name of the chart.
1076 + labels:
1077 + type: object
1078 + description: List of host labels.
1079 + properties:
1080 + app:
1081 + type: string
1082 + description: Host label.
1083 + example: netdata
1084 update_every:
1085 type: number
1086 description: The update frequency of this chart, in seconds. One value every this
@@ -1333,6 +1373,14 @@ components:
1373 format: nullable
1374 value:
1375 type: number
1376 + labels:
1377 + type: object
1378 + description: List of host labels.
1379 + properties:
1380 + app:
1381 + type: string
1382 + description: Host label.
1383 + example: netdata
1384 alarm_log_entry:
1385 type: object
1386 properties:
web/api/web_api_v1.c
+33 -2
@@ -761,10 +761,37 @@ static inline void web_client_api_request_v1_info_mirrored_hosts(BUFFER *wb) {
761 rrd_unlock();
762 }
763
764 +inline void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation) {
765 + char tabs[11];
766 +
767 + if (indentation > 10)
768 + indentation = 10;
769 +
770 + tabs[0] = '\0';
771 + while (indentation) {
772 + strcat(tabs, "\t");
773 + indentation--;
774 + }
775 +
776 + int count = 0;
777 + netdata_rwlock_rdlock(&host->labels_rwlock);
778 + for (struct label *label = host->labels; label; label = label->next) {
779 + if(count > 0) buffer_strcat(wb, ",\n");
780 + buffer_strcat(wb, tabs);
781 +
782 + char value[CONFIG_MAX_VALUE * 2 + 1];
783 + escape_json_string(value, label->value, CONFIG_MAX_VALUE * 2);
784 + buffer_sprintf(wb, "\"%s\": \"%s\"", label->key, value);
785 +
786 + count++;
787 + }
788 + buffer_strcat(wb, "\n");
789 + netdata_rwlock_unlock(&host->labels_rwlock);
790 +}
791 +
792 inline int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url) {
793 (void)url;
794 if (!netdata_ready) return HTTP_RESP_BACKEND_FETCH_FAILED;
767 -
795 BUFFER *wb = w->response.data;
796 buffer_flush(wb);
797 wb->contenttype = CT_APPLICATION_JSON;
@@ -781,7 +808,7 @@ inline int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, c
808 web_client_api_request_v1_info_summary_alarm_statuses(host, wb);
809 buffer_strcat(wb, "\t},\n");
810
784 - buffer_sprintf(wb, "\t\"os_name\": %s,\n", (host->system_info->os_name) ? host->system_info->os_name : "\"\"");
811 + buffer_sprintf(wb, "\t\"os_name\": \"%s\",\n", (host->system_info->os_name) ? host->system_info->os_name : "\"\"");
812 buffer_sprintf(wb, "\t\"os_id\": \"%s\",\n", (host->system_info->os_id) ? host->system_info->os_id : "");
813 buffer_sprintf(wb, "\t\"os_id_like\": \"%s\",\n", (host->system_info->os_id_like) ? host->system_info->os_id_like : "");
814 buffer_sprintf(wb, "\t\"os_version\": \"%s\",\n", (host->system_info->os_version) ? host->system_info->os_version : "");
@@ -795,6 +822,10 @@ inline int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, c
822 buffer_sprintf(wb, "\t\"container\": \"%s\",\n", (host->system_info->container) ? host->system_info->container : "");
823 buffer_sprintf(wb, "\t\"container_detection\": \"%s\",\n", (host->system_info->container_detection) ? host->system_info->container_detection : "");
824
825 + buffer_strcat(wb, "\t\"labels\": {\n");
826 + host_labels2json(host, wb, 2);
827 + buffer_strcat(wb, "\t},\n");
828 +
829 buffer_strcat(wb, "\t\"collectors\": [");
830 chartcollectors2json(host, wb);
831 buffer_strcat(wb, "\n\t]\n");
web/api/web_api_v1.h
+2
@@ -24,6 +24,8 @@ extern int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
24 extern int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url);
25 extern int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url);
26
27 +extern void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation);
28 +
29 extern void web_client_api_v1_init(void);
30 extern void web_client_api_v1_management_init(void);
31