diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d4ca19873..9d749691b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -590,6 +590,7 @@ set(RRD_PLUGIN_FILES database/rrddimvar.h database/rrdfamily.c database/rrdhost.c + database/rrdlabels.c database/rrd.c database/rrd.h database/rrdset.c @@ -1332,12 +1333,29 @@ endif() target_link_libraries(valid_urls_testdriver libnetdata ${NETDATA_COMMON_LIBRARIES} ${CMOCKA_LIBRARIES}) # add_test(NAME test_valid_urls COMMAND valid_urls_testdriver) + set(CGROUPS_TEST_FILES + collectors/cgroups.plugin/tests/test_cgroups_plugin.c + collectors/cgroups.plugin/tests/test_cgroups_plugin.h + collectors/cgroups.plugin/tests/test_doubles.c + database/rrdlabels.c + database/rrd.h + ) + add_executable(cgroups_testdriver ${CGROUPS_TEST_FILES} ${CGROUPS_PLUGIN_FILES}) + target_link_options( + cgroups_testdriver + PRIVATE + -Wl,--wrap=add_label_to_list + ) + target_link_libraries(cgroups_testdriver libnetdata ${NETDATA_COMMON_LIBRARIES} ${CMOCKA_LIBRARIES}) + add_test(NAME test_cgroups COMMAND cgroups_testdriver) + set_target_properties( str2ld_testdriver storage_number_testdriver exporting_engine_testdriver web_api_testdriver valid_urls_testdriver + cgroups_testdriver PROPERTIES RUNTIME_OUTPUT_DIRECTORY tests ) diff --git a/Makefile.am b/Makefile.am index c7c2f8ab1c..333909e251 100644 --- a/Makefile.am +++ b/Makefile.am @@ -366,6 +366,7 @@ RRD_PLUGIN_FILES = \ database/rrddimvar.h \ database/rrdfamily.c \ database/rrdhost.c \ + database/rrdlabels.c \ database/rrd.c \ database/rrd.h \ database/rrdset.c \ @@ -854,6 +855,7 @@ if ENABLE_UNITTESTS exporting/tests/exporting_engine_testdriver \ web/api/tests/web_api_testdriver \ web/api/tests/valid_urls_testdriver \ + collectors/cgroups_plugin/tests/cgroups_testdriver \ $(NULL) TESTS = $(check_PROGRAMS) @@ -1006,4 +1008,19 @@ if ENABLE_BACKEND_MONGODB -Wl,--wrap=mongoc_collection_insert_many \ $(NULL) endif + + collectors_cgroups_plugin_tests_cgroups_testdriver_SOURCES = \ + collectors/cgroups.plugin/tests/test_cgroups_plugin.c \ + collectors/cgroups.plugin/tests/test_cgroups_plugin.h \ + collectors/cgroups.plugin/tests/test_doubles.c \ + $(CGROUPS_PLUGIN_FILES) \ + database/rrdlabels.c \ + database/rrd.h \ + $(LIBNETDATA_FILES) \ + $(NULL) + collectors_cgroups_plugin_tests_cgroups_testdriver_LDADD = $(NETDATA_COMMON_LIBS) $(TEST_LIBS) + collectors_cgroups_plugin_tests_cgroups_testdriver_LDFLAGS = \ + -Wl,--wrap=add_label_to_list \ + $(NULL) + endif diff --git a/collectors/cgroups.plugin/cgroup-name.sh.in b/collectors/cgroups.plugin/cgroup-name.sh.in index cd1b8a5880..19fbf3989d 100755 --- a/collectors/cgroups.plugin/cgroup-name.sh.in +++ b/collectors/cgroups.plugin/cgroup-name.sh.in @@ -17,63 +17,63 @@ export LC_ALL=C PROGRAM_NAME="$(basename "${0}")" logdate() { - date "+%Y-%m-%d %H:%M:%S" + date "+%Y-%m-%d %H:%M:%S" } log() { - local status="${1}" - shift + local status="${1}" + shift - echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${*}" + echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${*}" } warning() { - log WARNING "${@}" + log WARNING "${@}" } error() { - log ERROR "${@}" + log ERROR "${@}" } info() { - log INFO "${@}" + log INFO "${@}" } fatal() { - log FATAL "${@}" - exit 1 + log FATAL "${@}" + exit 1 } function docker_like_get_name_command() { - local command="${1}" - local id="${2}" - info "Running command: ${command} ps --filter=id=\"${id}\" --format=\"{{.Names}}\"" - NAME="$(${command} ps --filter=id="${id}" --format="{{.Names}}")" - return 0 + local command="${1}" + local id="${2}" + info "Running command: ${command} ps --filter=id=\"${id}\" --format=\"{{.Names}}\"" + NAME="$(${command} ps --filter=id="${id}" --format="{{.Names}}")" + return 0 } function docker_like_get_name_api() { - local host_var="${1}" - local host="${!host_var}" - local path="/containers/${2}/json" - if [ -z "${host}" ]; then - warning "No ${host_var} is set" - return 1 - fi - if ! command -v jq >/dev/null 2>&1; then - warning "Can't find jq command line tool. jq is required for netdata to retrieve container name using ${host} API, falling back to docker ps" - return 1 - fi - if [ -S "${host}" ]; then - info "Running API command: curl --unix-socket \"${host}\" http://localhost${path}" - JSON=$(curl -sS --unix-socket "${host}" "http://localhost${path}") - else - info "Running API command: curl \"${host}${path}\"" - JSON=$(curl -sS "${host}${path}") - fi - NAME=$(echo "${JSON}" | jq -r .Name,.Config.Hostname | grep -v null | head -n1 | sed 's|^/||') - return 0 + local host_var="${1}" + local host="${!host_var}" + local path="/containers/${2}/json" + if [ -z "${host}" ]; then + warning "No ${host_var} is set" + return 1 + fi + if ! command -v jq > /dev/null 2>&1; then + warning "Can't find jq command line tool. jq is required for netdata to retrieve container name using ${host} API, falling back to docker ps" + return 1 + fi + if [ -S "${host}" ]; then + info "Running API command: curl --unix-socket \"${host}\" http://localhost${path}" + JSON=$(curl -sS --unix-socket "${host}" "http://localhost${path}") + else + info "Running API command: curl \"${host}${path}\"" + JSON=$(curl -sS "${host}${path}") + fi + NAME=$(echo "${JSON}" | jq -r .Name,.Config.Hostname | grep -v null | head -n1 | sed 's|^/||') + return 0 } # get_lbl_val returns the value for the label with the given name. @@ -99,6 +99,21 @@ function get_lbl_val() { return 1 } +function add_lbl_prefix() { + local orig_labels prefix + orig_labels="${1}" + prefix="${2}" + + IFS=, read -ra labels <<< "$orig_labels" + + local new_labels + for l in "${labels[@]}"; do + new_labels+="${prefix}${l}," + done + + echo "${new_labels:0:-1}" # trim last ',' +} + # k8s_get_kubepod_name resolves */kubepods/* cgroup name. # pod level cgroup name format: 'pod_<namespace>_<pod_name>' # container level cgroup name format: 'cntr_<namespace>_<pod_name>_<container_name>' @@ -122,11 +137,11 @@ function k8s_get_kubepod_name() { # # NOTE: cgroups plugin uses '_' to join dir names, so it is <parent>_<child>_<child>_... - local funcname="${FUNCNAME[0]}" + local fn="${FUNCNAME[0]}" local id="${1}" if [[ ! $id =~ ^kubepods ]]; then - warning "${funcname}: '${id}' is not kubepod cgroup." + warning "${fn}: '${id}' is not kubepod cgroup." return 1 fi @@ -160,72 +175,121 @@ function k8s_get_kubepod_name() { fi if [ -z "$pod_uid" ] && [ -z "$cntr_id" ]; then - warning "${funcname}: can't extract pod_uid or container_id from the cgroup '$id'." + warning "${fn}: can't extract pod_uid or container_id from the cgroup '$id'." return 1 fi - [ -n "$pod_uid" ] && info "${funcname}: cgroup '$id' is a pod(uid:$pod_uid)" - [ -n "$cntr_id" ] && info "${funcname}: cgroup '$id' is a container(id:$cntr_id)" + [ -n "$pod_uid" ] && info "${fn}: cgroup '$id' is a pod(uid:$pod_uid)" + [ -n "$cntr_id" ] && info "${fn}: cgroup '$id' is a container(id:$cntr_id)" if ! command -v jq > /dev/null 2>&1; then - warning "${funcname}: 'jq' command not available." + warning "${fn}: 'jq' command not available." return 1 fi + local kube_system_ns + local tmp_kube_system_ns_file="${TMPDIR:-"/tmp/"}netdata-cgroups-kube-system-ns" + [ -f "$tmp_kube_system_ns_file" ] && kube_system_ns=$(cat "$tmp_kube_system_ns_file" 2> /dev/null) + local pods if [ -n "${KUBERNETES_SERVICE_HOST}" ] && [ -n "${KUBERNETES_PORT_443_TCP_PORT}" ]; then - local token header url + local token header host url token="$(< /var/run/secrets/kubernetes.io/serviceaccount/token)" header="Authorization: Bearer $token" - url="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/pods" + host="$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT" + + if [ -z "$kube_system_ns" ]; then + url="https://$host/api/v1/namespaces/kube-system" + # FIX: check HTTP response code + if ! kube_system_ns=$(curl -sSk -H "$header" "$url" 2>&1); then + warning "${fn}: error on curl '${url}': ${kube_system_ns}." + else + echo "$kube_system_ns" > "$tmp_kube_system_ns_file" 2> /dev/null + fi + fi + + url="https://$host/api/v1/pods" + [ -n "$MY_NODE_NAME" ] && url+="?fieldSelector=spec.nodeName==$MY_NODE_NAME" + # FIX: check HTTP response code if ! pods=$(curl -sSk -H "$header" "$url" 2>&1); then - warning "${funcname}: error on curl '${url}': ${pods}." + warning "${fn}: error on curl '${url}': ${pods}." return 1 fi elif ps -C kubelet > /dev/null 2>&1 && command -v kubectl > /dev/null 2>&1; then + if [ -z "$kube_system_ns" ]; then + if ! kube_system_ns=$(kubectl get namespaces kube-system -o json 2>&1); then + warning "${fn}: error on 'kubectl': ${kube_system_ns}." + else + echo "$kube_system_ns" > "$tmp_kube_system_ns_file" 2> /dev/null + fi + fi + [[ -z ${KUBE_CONFIG+x} ]] && KUBE_CONFIG="/etc/kubernetes/admin.conf" if ! pods=$(kubectl --kubeconfig="$KUBE_CONFIG" get pods --all-namespaces -o json 2>&1); then - warning "${funcname}: error on 'kubectl': ${pods}." + warning "${fn}: error on 'kubectl': ${pods}." return 1 fi else - warning "${funcname}: not inside the k8s cluster and 'kubectl' command not available." + warning "${fn}: not inside the k8s cluster and 'kubectl' command not available." return 1 fi + local kube_system_uid + if [ -n "$kube_system_ns" ] && ! kube_system_uid=$(jq -r '.metadata.uid' <<< "$kube_system_ns" 2>&1); then + warning "${fn}: error on 'jq' parse kube_system_ns: ${kube_system_uid}." + fi + local jq_filter - # 'namespace="<NAMESPACE>",pod_name="<NAME>",pod_uid="<UID>",container_name="<NAME>",container_ID="<ID>"' - # wrong field value is 'null' - jq_filter+='.items[] | ' - jq_filter+='"namespace=\"\(.metadata.namespace)\",pod_name=\"\(.metadata.name)\",pod_uid=\"\(.metadata.uid)\"" + ' - jq_filter+='(.status.containerStatuses[]? | ' - jq_filter+='",container_name=\"\(.name)\",container_id=\"\(.containerID)\""' - jq_filter+=') | ' - jq_filter+='sub("docker://";"")' + jq_filter+='.items[] | "' + jq_filter+='namespace=\"\(.metadata.namespace)\",' + jq_filter+='pod_name=\"\(.metadata.name)\",' + jq_filter+='pod_uid=\"\(.metadata.uid)\",' + #jq_filter+='\(.metadata.labels | to_entries | map("pod_label_"+.key+"=\""+.value+"\"") | join(",") | if length > 0 then .+"," else . end)' + jq_filter+='\((.metadata.ownerReferences[]? | select(.controller==true) | "controller_kind=\""+.kind+"\",controller_name=\""+.name+"\",") // "")' + jq_filter+='node_name=\"\(.spec.nodeName)\",' + jq_filter+='" + ' + jq_filter+='(.status.containerStatuses[]? | "' + jq_filter+='container_name=\"\(.name)\",' + jq_filter+='container_id=\"\(.containerID)\"' + jq_filter+='") | ' + jq_filter+='sub("docker://";"")' # containerID: docker://a346da9bc0e3eaba6b295f64ac16e02f2190db2cef570835706a9e7a36e2c722 local containers if ! containers=$(jq -r "${jq_filter}" <<< "$pods" 2>&1); then - warning "${funcname}: error on 'jq' parse: ${containers}." + warning "${fn}: error on 'jq' parse pods: ${containers}." return 1 fi # available labels: - # namespace, pod_name, pod_uid, container_name, container_id + # namespace, pod_name, pod_uid, container_name, container_id, node_name local labels if [ -n "$cntr_id" ]; then if labels=$(grep "$cntr_id" <<< "$containers" 2> /dev/null); then - name="cntr_$(get_lbl_val "$labels" namespace)_$(get_lbl_val "$labels" pod_name)_$(get_lbl_val "$labels" container_name)" + labels+=',kind="container"' + [ -n "$kube_system_uid" ] && [ "$kube_system_uid" != "null" ] && labels+=",cluster_id=\"$kube_system_uid\"" + name="cntr" + name+="_$(get_lbl_val "$labels" namespace)" + name+="_$(get_lbl_val "$labels" pod_name)" + name+="_$(get_lbl_val "$labels" container_name)" + labels=$(add_lbl_prefix "$labels" "k8s_") + name+=" $labels" fi elif [ -n "$pod_uid" ]; then if labels=$(grep "$pod_uid" -m 1 <<< "$containers" 2> /dev/null); then - labels="${labels%%,cont*}" - name="pod_$(get_lbl_val "$labels" namespace)_$(get_lbl_val "$labels" pod_name)" + labels="${labels%%,container_*}" + labels+=',kind="pod"' + [ -n "$kube_system_uid" ] && [ "$kube_system_uid" != "null" ] && labels+=",cluster_id=\"$kube_system_uid\"" + name="pod" + name+="_$(get_lbl_val "$labels" namespace)" + name+="_$(get_lbl_val "$labels" pod_name)" + labels=$(add_lbl_prefix "$labels" "k8s_") + name+=" $labels" fi fi # jq filter nonexistent field and nonexistent label value is 'null' if [[ $name =~ _null(_|$) ]]; then - warning "${funcname}: invalid name: $name (cgroup '$id')" + warning "${fn}: invalid name: $name (cgroup '$id')" name="" fi @@ -235,69 +299,77 @@ function k8s_get_kubepod_name() { } function k8s_get_name() { - local funcname="${FUNCNAME[0]}" + local fn="${FUNCNAME[0]}" local id="${1}" NAME=$(k8s_get_kubepod_name "$id") if [ -z "${NAME}" ]; then - warning "${funcname}: cannot find the name of cgroup with id '${id}'. Setting name to ${id} and disabling it." + warning "${fn}: cannot find the name of cgroup with id '${id}'. Setting name to ${id} and disabling it." NAME="${id}" NAME_NOT_FOUND=3 else NAME="k8s_${NAME}" - info "${funcname}: cgroup '${id}' has chart name '${NAME}'" + + local name labels + name=${NAME%% *} + labels=${NAME#* } + if [ "$name" != "$labels" ]; then + info "${fn}: cgroup '${id}' has chart name '${name}', labels '${labels}" + else + info "${fn}: cgroup '${id}' has chart name '${NAME}'" + fi fi } function docker_get_name() { - local id="${1}" - if hash docker 2>/dev/null; then - docker_like_get_name_command docker "${id}" - else - docker_like_get_name_api DOCKER_HOST "${id}" || docker_like_get_name_command podman "${id}" - fi - if [ -z "${NAME}" ]; then - warning "cannot find the name of docker container '${id}'" - NAME_NOT_FOUND=2 - NAME="${id:0:12}" - else - info "docker container '${id}' is named '${NAME}'" - fi + local id="${1}" + if hash docker 2> /dev/null; then + docker_like_get_name_command docker "${id}" + else + docker_like_get_name_api DOCKER_HOST "${id}" || docker_like_get_name_command podman "${id}" + fi + if [ -z "${NAME}" ]; then + warning "cannot find the name of docker container '${id}'" + NAME_NOT_FOUND=2 + NAME="${id:0:12}" + else + info "docker container '${id}' is named '${NAME}'" + fi } function docker_validate_id() { - local id="${1}" - if [ -n "${id}" ] && { [ ${#id} -eq 64 ] || [ ${#id} -eq 12 ]; }; then - docker_get_name "${id}" - else - error "a docker id cannot be extracted from docker cgroup '${CGROUP}'." - fi + local id="${1}" + if [ -n "${id}" ] && { [ ${#id} -eq 64 ] || [ ${#id} -eq 12 ]; }; then + docker_get_name "${id}" + else + error "a docker id cannot be extracted from docker cgroup '${CGROUP}'." + fi } function podman_get_name() { - local id="${1}" + local id="${1}" - # for Podman, prefer using the API if we can, as netdata will not normally have access - # to other users' containers, so they will not be visible when running `podman ps` - docker_like_get_name_api PODMAN_HOST "${id}" || docker_like_get_name_command podman "${id}" + # for Podman, prefer using the API if we can, as netdata will not normally have access + # to other users' containers, so they will not be visible when running `podman ps` + docker_like_get_name_api PODMAN_HOST "${id}" || docker_like_get_name_command podman "${id}" - if [ -z "${NAME}" ]; then - warning "cannot find the name of podman container '${id}'" - NAME_NOT_FOUND=2 - NAME="${id:0:12}" - else - info "podman container '${id}' is named '${NAME}'" - fi + if [ -z "${NAME}" ]; then + warning "cannot find the name of podman container '${id}'" + NAME_NOT_FOUND=2 + NAME="${id:0:12}" + else + info "podman container '${id}' is named '${NAME}'" + fi } function podman_validate_id() { - local id="${1}" - if [ -n "${id}" ] && [ ${#id} -eq 64 ]; then - podman_get_name "${id}" - else - error "a podman id cannot be extracted from docker cgroup '${CGROUP}'." - fi + local id="${1}" + if [ -n "${id}" ] && [ ${#id} -eq 64 ]; then + podman_get_name "${id}" + else + error "a podman id cannot be extracted from docker cgroup '${CGROUP}'." + fi } # ----------------------------------------------------------------------------- @@ -314,86 +386,85 @@ NAME= # ----------------------------------------------------------------------------- if [ -z "${CGROUP}" ]; then - fatal "called without a cgroup name. Nothing to do." + fatal "called without a cgroup name. Nothing to do." fi for CONFIG in "${NETDATA_USER_CONFIG_DIR}/cgroups-names.conf" "${NETDATA_STOCK_CONFIG_DIR}/cgroups-names.conf"; do - if [ -f "${CONFIG}" ]; then - NAME="$(grep "^${CGROUP} " "${CONFIG}" | sed 's/[[:space:]]\+/ /g' | cut -d ' ' -f 2)" - if [ -z "${NAME}" ]; then - info "cannot find cgroup '${CGROUP}' in '${CONFIG}'." - else - break - fi - #else - # info "configuration file '${CONFIG}' is not available." - fi + if [ -f "${CONFIG}" ]; then + NAME="$(grep "^${CGROUP} " "${CONFIG}" | sed 's/[[:space:]]\+/ /g' | cut -d ' ' -f 2)" + if [ -z "${NAME}" ]; then + info "cannot find cgroup '${CGROUP}' in '${CONFIG}'." + else + break + fi + #else + # info "configuration file '${CONFIG}' is not available." + fi done if [ -z "${NAME}" ]; then - if [[ ${CGROUP} =~ ^.*kubepods.* ]]; then - k8s_get_name "${CGROUP}" - fi + if [[ ${CGROUP} =~ ^.*kubepods.* ]]; then + k8s_get_name "${CGROUP}" + fi fi if [ -z "${NAME}" ]; then - if [[ ${CGROUP} =~ ^.*docker[-_/\.][a-fA-F0-9]+[-_\.]?.*$ ]]; then - # docker containers - #shellcheck disable=SC1117 - DOCKERID="$(echo "${CGROUP}" | sed "s|^.*docker[-_/]\([a-fA-F0-9]\+\)[-_\.]\?.*$|\1|")" - docker_validate_id "${DOCKERID}" - elif [[ ${CGROUP} =~ ^.*ecs[-_/\.][a-fA-F0-9]+[-_\.]?.*$ ]]; then - # ECS - #shellcheck disable=SC1117 - DOCKERID="$(echo "${CGROUP}" | sed "s|^.*ecs[-_/].*[-_/]\([a-fA-F0-9]\+\)[-_\.]\?.*$|\1|")" - docker_validate_id "${DOCKERID}" - elif [[ ${CGROUP} =~ ^.*libpod-[a-fA-F0-9]+.*$ ]]; then - # Podman - PODMANID="$(echo "${CGROUP}" | sed "s|^.*libpod-\([a-fA-F0-9]\+\).*$|\1|")" - podman_validate_id "${PODMANID}" + if [[ ${CGROUP} =~ ^.*docker[-_/\.][a-fA-F0-9]+[-_\.]?.*$ ]]; then + # docker containers + #shellcheck disable=SC1117 + DOCKERID="$(echo "${CGROUP}" | sed "s|^.*docker[-_/]\([a-fA-F0-9]\+\)[-_\.]\?.*$|\1|")" + docker_validate_id "${DOCKERID}" + elif [[ ${CGROUP} =~ ^.*ecs[-_/\.][a-fA-F0-9]+[-_\.]?.*$ ]]; then + # ECS + #shellcheck disable=SC1117 + DOCKERID="$(echo "${CGROUP}" | sed "s|^.*ecs[-_/].*[-_/]\([a-fA-F0-9]\+\)[-_\.]\?.*$|\1|")" + docker_validate_id "${DOCKERID}" + elif [[ ${CGROUP} =~ ^.*libpod-[a-fA-F0-9]+.*$ ]]; then + # Podman + PODMANID="$(echo "${CGROUP}" | sed "s|^.*libpod-\([a-fA-F0-9]\+\).*$|\1|")" + podman_validate_id "${PODMANID}" - elif [[ ${CGROUP} =~ machine.slice[_/].*\.service ]]; then - # systemd-nspawn - NAME="$(echo "${CGROUP}" | sed 's/.*machine.slice[_\/]\(.*\)\.service/\1/g')" + elif [[ ${CGROUP} =~ machine.slice[_/].*\.service ]]; then + # systemd-nspawn + NAME="$(echo "${CGROUP}" | sed 's/.*machine.slice[_\/]\(.*\)\.service/\1/g')" - elif [[ ${CGROUP} =~ machine.slice_machine.*-qemu ]]; then - # libvirtd / qemu virtual machines - # NAME="$(echo ${CGROUP} | sed 's/machine.slice_machine.*-qemu//; s/\/x2d//; s/\/x2d/\-/g; s/\.scope//g')" - NAME="qemu_$(echo "${CGROUP}" | sed 's/machine.slice_machine.*-qemu//; s/\/x2d[[:digit:]]*//; s/\/x2d//g; s/\.scope//g')" + elif [[ ${CGROUP} =~ machine.slice_machine.*-qemu ]]; then + # libvirtd / qemu virtual machines + # NAME="$(echo ${CGROUP} | sed 's/machine.slice_machine.*-qemu//; s/\/x2d//; s/\/x2d/\-/g; s/\.scope//g')" + NAME="qemu_$(echo "${CGROUP}" | sed 's/machine.slice_machine.*-qemu//; s/\/x2d[[:digit:]]*//; s/\/x2d//g; s/\.scope//g')" - elif [[ ${CGROUP} =~ machine_.*\.libvirt-qemu ]]; then - # libvirtd / qemu virtual machines - NAME="qemu_$(echo "${CGROUP}" | sed 's/^machine_//; s/\.libvirt-qemu$//; s/-/_/;')" + elif [[ ${CGROUP} =~ machine_.*\.libvirt-qemu ]]; then + # libvirtd / qemu virtual machines + NAME="qemu_$(echo "${CGROUP}" | sed 's/^machine_//; s/\.libvirt-qemu$//; s/-/_/;')" - elif [[ ${CGROUP} =~ qemu.slice_([0-9]+).scope && -d /etc/pve ]]; then - # Proxmox VMs + elif [[ ${CGROUP} =~ qemu.slice_([0-9]+).scope && -d /etc/pve ]]; then + # Proxmox VMs - FILENAME="/etc/pve/qemu-server/${BASH_REMATCH[1]}.conf" - if [[ -f $FILENAME && -r $FILENAME ]]; then - NAME="qemu_$(grep -e '^name: ' "/etc/pve/qemu-server/${BASH_REMATCH[1]}.conf" | head -1 | sed -rn 's|\s*name\s*:\s*(.*)?$|\1|p')" - else - error "proxmox config file missing ${FILENAME} or netdata does not have read access. Please ensure netdata is a member of www-data group." - fi - elif [[ ${CGROUP} =~ lxc_([0-9]+) && -d /etc/pve ]]; then - # Proxmox Containers (LXC) + FILENAME="/etc/pve/qemu-server/${BASH_REMATCH[1]}.conf" + if [[ -f $FILENAME && -r $FILENAME ]]; then + NAME="qemu_$(grep -e '^name: ' "/etc/pve/qemu-server/${BASH_REMATCH[1]}.conf" | head -1 | sed -rn 's|\s*name\s*:\s*(.*)?$|\1|p')" + else + error "proxmox config file missing ${FILENAME} or netdata does not have read access. Please ensure netdata is a member of www-data group." + fi + elif [[ ${CGROUP} =~ lxc_([0-9]+) && -d /etc/pve ]]; then + # Proxmox Containers (LXC) - FILENAME="/etc/pve/lxc/${BASH_REMATCH[1]}.conf" - if [[ -f ${FILENAME} && -r ${FILENAME} ]]; then - NAME=$(grep -e '^hostname: ' "/etc/pve/lxc/${BASH_REMATCH[1]}.conf" | head -1 | sed -rn 's|\s*hostname\s*:\s*(.*)?$|\1|p') - else - error "proxmox config file missing ${FILENAME} or netdata does not have read access. Please ensure netdata is a member of www-data group." - fi - elif [[ ${CGROUP} =~ lxc.payload.* ]]; then - # LXC 4.0 - NAME="$(echo "${CGROUP}" | sed 's/lxc\.payload\.\(.*\)/\1/g')" - fi + FILENAME="/etc/pve/lxc/${BASH_REMATCH[1]}.conf" + if [[ -f ${FILENAME} && -r ${FILENAME} ]]; then + NAME=$(grep -e '^hostname: ' "/etc/pve/lxc/${BASH_REMATCH[1]}.conf" | head -1 | sed -rn 's|\s*hostname\s*:\s*(.*)?$|\1|p') + else + error "proxmox config file missing ${FILENAME} or netdata does not have read access. Please ensure netdata is a member of www-data group." + fi + elif [[ ${CGROUP} =~ lxc.payload.* ]]; then + # LXC 4.0 + NAME="$(echo "${CGROUP}" | sed 's/lxc\.payload\.\(.*\)/\1/g')" + fi - [ -z "${NAME}" ] && NAME="${CGROUP}" - [ ${#NAME} -gt 100 ] && NAME="${NAME:0:100}" + [ -z "${NAME}" ] && NAME="${CGROUP}" + [ ${#NAME} -gt 100 ] && NAME="${NAME:0:100}" fi info "cgroup '${CGROUP}' is called '${NAME}'" echo "${NAME}" exit ${NAME_NOT_FOUND} - diff --git a/collectors/cgroups.plugin/sys_fs_cgroup.c b/collectors/cgroups.plugin/sys_fs_cgroup.c index 75df8c6382..499124cb46 100644 --- a/collectors/cgroups.plugin/sys_fs_cgroup.c +++ b/collectors/cgroups.plugin/sys_fs_cgroup.c @@ -600,6 +600,8 @@ struct cgroup { char *chart_title; + struct label *chart_labels; + struct cpuacct_stat cpuacct_stat; struct cpuacct_usage cpuacct_usage; @@ -1226,7 +1228,7 @@ static inline void read_cgroup_network_interfaces(struct cgroup *cg) { info("CGROUP: cgroup '%s' has network interface '%s' as '%s'", cg->id, i->host_device, i->container_device); // register a device rename to proc_net_dev.c - netdev_rename_device_add(i->host_device, i->container_device, cg->chart_id); + netdev_rename_device_add(i->host_device, i->container_device, cg->chart_id, cg->chart_labels); } } @@ -1275,6 +1277,35 @@ static inline char *cgroup_chart_id_strdupz(const char *s) { return r; } +char *parse_k8s_data(struct label **labels, char *data) +{ + char *name = mystrsep(&data, " "); + + if (!data) { + return name; + } + + while (data) { + char *key = mystrsep(&data, "="); + + char *value; + if (data && *data == ',') { + value = ""; + *data++ = '\0'; + } else { + value = mystrsep(&data, ","); + } + value = strip_double_quotes(value, 1); + + if (!key || *key == '\0' || !value || *value == '\0') + continue; + + *labels = add_label_to_list(*labels, key, value, LABEL_SOURCE_KUBERNETES); + } + + return name; +} + static inline void cgroup_get_chart_name(struct cgroup *cg) { debug(D_CGROUP, "looking for the name of cgroup '%s' with chart id '%s' and title '%s'", cg->id, cg->chart_id, cg->chart_title); @@ -1305,12 +1336,19 @@ static inline void cgroup_get_chart_name(struct cgroup *cg) { cg->enabled = 0; } - if(likely(cg->pending_renames < 2)) { + if (likely(cg->pending_renames < 2)) { + char *name = s; + + if (!strncmp(s, "k8s_", 4)) { + free_label_list(cg->chart_labels); + name = parse_k8s_data(&cg->chart_labels, s); + } + freez(cg->chart_title); - cg->chart_title = cgroup_title_strdupz(s); + cg->chart_title = cgroup_title_strdupz(name); freez(cg->chart_id); - cg->chart_id = cgroup_chart_id_strdupz(s); + cg->chart_id = cgroup_chart_id_strdupz(name); cg->hash_chart = simple_hash(cg->chart_id); } } @@ -1508,6 +1546,8 @@ static inline void cgroup_free(struct cgroup *cg) { freez(cg->chart_id); freez(cg->chart_title); + free_label_list(cg->chart_labels); + freez(cg); cgroup_root_count--; @@ -3022,6 +3062,9 @@ void update_cgroup_charts(int update_every) { , update_every , RRDSET_TYPE_STACKED ); + + rrdset_update_labels(cg->st_cpu, cg->chart_labels); + if(!(cg->options & CGROUP_OPTIONS_IS_UNIFIED)) { rrddim_add(cg->st_cpu, "user", NULL, 100, system_hz, RRD_ALGORITHM_INCREMENTAL); rrddim_add(cg->st_cpu, "system", NULL, 100, system_hz, RRD_ALGORITHM_INCREMENTAL); @@ -3093,6 +3136,8 @@ void update_cgroup_charts(int update_every) { , RRDSET_TYPE_LINE ); + rrdset_update_labels(cg->st_cpu_limit, cg->chart_labels); + if(!(cg->options & CGROUP_OPTIONS_IS_UNIFIED)) rrddim_add(cg->st_cpu_limit, "used", NULL, 1, system_hz, RRD_ALGORITHM_ABSOLUTE); else @@ -3146,6 +3191,8 @@ void update_cgroup_charts(int update_every) { , RRDSET_TYPE_STACKED ); + rrdset_update_labels(cg->st_cpu_per_core, cg->chart_labels); + for(i = 0; i < cg->cpuacct_usage.cpus; i++) { snprintfz(id, RRD_ID_LENGTH_MAX, "cpu%u", i); rrddim_add(cg->st_cpu_per_core, id, NULL, 100, 1000000000, RRD_ALGORITHM_INCREMENTAL); @@ -3179,6 +3226,9 @@ void update_cgroup_charts(int update_every) { , update_every , RRDSET_TYPE_STACKED ); + + rrdset_update_labels(cg->st_mem, cg->chart_labels); + if(!(cg->options & CGROUP_OPTIONS_IS_UNIFIED)) { rrddim_add(cg->st_mem, "cache", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); rrddim_add(cg->st_mem, "rss", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); @@ -3237,6 +3287,8 @@ void update_cgroup_charts(int update_every) { , RRDSET_TYPE_AREA ); + rrdset_update_labels(cg->st_writeback, cg->chart_labels); + if(cg->memory.detailed_has_dirty) rrddim_add(cg->st_writeback, "dirty", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); @@ -3270,6 +3322,8 @@ void update_cgroup_charts(int update_every) { , RRDSET_TYPE_LINE ); + rrdset_update_labels(cg->st_mem_activity, cg->chart_labels); + rrddim_add(cg->st_mem_activity, "pgpgin", "in", system_page_size, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL); rrddim_add(cg->st_mem_activity, "pgpgout", "out", -system_page_size, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL); } @@ -3299,6 +3353,8 @@ void update_cgroup_charts(int update_every) { , RRDSET_TYPE_LINE ); + rrdset_update_labels(cg->st_pgfaults, cg->chart_labels); + rrddim_add(cg->st_pgfaults, "pgfault", NULL, system_page_size, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL); rrddim_add(cg->st_pgfaults, "pgmajfault", "swap", -system_page_size, 1024 * 1024, RRD_ALGORITHM_INCREMENTAL); } @@ -3329,6 +3385,8 @@ void update_cgroup_charts(int update_every) { , RRDSET_TYPE_STACKED ); + rrdset_update_labels(cg->st_mem_usage, cg->chart_labels); + rrddim_add(cg->st_mem_usage, "ram", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); rrddim_add(cg->st_mem_usage, "swap", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); } @@ -3390,6 +3448,8 @@ void update_cgroup_charts(int update_every) { , RRDSET_TYPE_STACKED ); + rrdset_update_labels(cg->st_mem_usage_limit, cg->chart_labels); + rrddim_add(cg->st_mem_usage_limit, "available", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); rrddim_add(cg->st_mem_usage_limit, "used", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); } @@ -3431,6 +3491,8 @@ void update_cgroup_charts(int update_every) { , update_every , RRDSET_TYPE_LINE ); + + rrdset_update_labels(cg->st_mem_failcnt, cg->chart_labels); rrddim_add(cg->st_mem_failcnt, "failures", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); } @@ -3460,6 +3522,8 @@ void update_cgroup_charts(int update_every) { , RRDSET_TYPE_AREA ); + rrdset_update_labels(cg->st_io, cg->chart_labels); + rrddim_add(cg->st_io, "read", NULL, 1, 1024, RRD_ALGORITHM_INCREMENTAL); rrddim_add(cg->st_io, "write", NULL, -1, 1024, RRD_ALGORITHM_INCREMENTAL); } @@ -3490,6 +3554,8 @@ void update_cgroup_charts(int update_every) { , RRDSET_TYPE_LINE ); + rrdset_update_labels(cg->st_serviced_ops, cg->chart_labels); + rrddim_add(cg->st_serviced_ops, "read", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); rrddim_add(cg->st_serviced_ops, "write", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); } @@ -3519,6 +3585,8 @@ void update_cgroup_charts(int update_every) { , update_every , RRDSET_TYPE_AREA ); + + rrdset_update_labels(cg->st_throttle_io, cg->chart_labels); rrddim_add(cg->st_throttle_io, "read", NULL, 1, 1024, RRD_ALGORITHM_INCREMENTAL); rrddim_add(cg->st_throttle_io, "write", NULL, -1, 1024, RRD_ALGORITHM_INCREMENTAL); @@ -3549,6 +3617,8 @@ void update_cgroup_charts(int update_every) { , update_every , RRDSET_TYPE_LINE ); + + rrdset_update_labels(cg->st_throttle_serviced_ops, cg->chart_labels); rrddim_add(cg->st_throttle_serviced_ops, "read", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); rrddim_add(cg->st_throttle_serviced_ops, "write", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); @@ -3579,6 +3649,8 @@ void update_cgroup_charts(int update_every) { , update_every , RRDSET_TYPE_LINE ); + + rrdset_update_labels(cg->st_queued_ops, cg->chart_labels); rrddim_add(cg->st_queued_ops, "read", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); rrddim_add(cg->st_queued_ops, "write", NULL, -1, 1, RRD_ALGORITHM_ABSOLUTE); @@ -3609,6 +3681,8 @@ void update_cgroup_charts(int update_every) { , update_every , RRDSET_TYPE_LINE ); + + rrdset_update_labels(cg->st_merged_ops, cg->chart_labels); rrddim_add(cg->st_merged_ops, "read", NULL, 1, 1024, RRD_ALGORITHM_INCREMENTAL); rrddim_add(cg->st_merged_ops, "write", NULL, -1, 1024, RRD_ALGORITHM_INCREMENTAL); @@ -3639,8 +3713,11 @@ void update_cgroup_charts(int update_every) { , PLUGIN_CGROUPS_NAME , PLUGIN_CGROUPS_MODULE_CGROUPS_NAME , cgroup_containers_chart_priority + 2200 - , update_every, - RRDSET_TYPE_LINE); + , update_every + , RRDSET_TYPE_LINE + ); + + rrdset_update_labels(chart = res->some.st, cg->chart_labels); res->some.rd10 = rrddim_add(chart, "some 10", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); res->some.rd60 = rrddim_add(chart, "some 60", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); @@ -3669,8 +3746,11 @@ void update_cgroup_charts(int update_every) { , PLUGIN_CGROUPS_NAME , PLUGIN_CGROUPS_MODULE_CGROUPS_NAME , cgroup_containers_chart_priority + 2300 - , update_every, - RRDSET_TYPE_LINE); + , update_every + , RRDSET_TYPE_LINE + ); + + rrdset_update_labels(chart = res->some.st, cg->chart_labels); res->some.rd10 = rrddim_add(chart, "some 10", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); res->some.rd60 = rrddim_add(chart, "some 60", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); @@ -3698,8 +3778,11 @@ void update_cgroup_charts(int update_every) { , PLUGIN_CGROUPS_NAME , PLUGIN_CGROUPS_MODULE_CGROUPS_NAME , cgroup_containers_chart_priority + 2350 - , update_every, - RRDSET_TYPE_LINE); + , update_every + , RRDSET_TYPE_LINE + ); + + rrdset_update_labels(chart = res->full.st, cg->chart_labels); res->full.rd10 = rrddim_add(chart, "full 10", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); res->full.rd60 = rrddim_add(chart, "full 60", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); @@ -3728,8 +3811,11 @@ void update_cgroup_charts(int update_every) { , PLUGIN_CGROUPS_NAME , PLUGIN_CGROUPS_MODULE_CGROUPS_NAME , cgroup_containers_chart_priority + 2400 - , update_every, - RRDSET_TYPE_LINE); + , update_every + , RRDSET_TYPE_LINE + ); + + rrdset_update_labels(chart = res->some.st, cg->chart_labels); res->some.rd10 = rrddim_add(chart, "some 10", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); res->some.rd60 = rrddim_add(chart, "some 60", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); @@ -3757,8 +3843,11 @@ void update_cgroup_charts(int update_every) { , PLUGIN_CGROUPS_NAME , PLUGIN_CGROUPS_MODULE_CGROUPS_NAME , cgroup_containers_chart_priority + 2450 - , update_every, - RRDSET_TYPE_LINE); + , update_every + , RRDSET_TYPE_LINE + ); + + rrdset_update_labels(chart = res->full.st, cg->chart_labels); res->full.rd10 = rrddim_add(chart, "full 10", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); res->full.rd60 = rrddim_add(chart, "full 60", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); diff --git a/collectors/cgroups.plugin/sys_fs_cgroup.h b/collectors/cgroups.plugin/sys_fs_cgroup.h index 09ce5e3fb3..155330ff1b 100644 --- a/collectors/cgroups.plugin/sys_fs_cgroup.h +++ b/collectors/cgroups.plugin/sys_fs_cgroup.h @@ -28,4 +28,6 @@ extern void *cgroups_main(void *ptr); #endif // (TARGET_OS == OS_LINUX) +extern char *parse_k8s_data(struct label **labels, char *data); + #endif //NETDATA_SYS_FS_CGROUP_H diff --git a/collectors/cgroups.plugin/tests/test_cgroups_plugin.c b/collectors/cgroups.plugin/tests/test_cgroups_plugin.c new file mode 100644 index 0000000000..be8ea2c488 --- /dev/null +++ b/collectors/cgroups.plugin/tests/test_cgroups_plugin.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "test_cgroups_plugin.h" +#include "libnetdata/required_dummies.h" + +RRDHOST *localhost; +int netdata_zero_metrics_enabled = 1; +struct config netdata_config; +char *netdata_configured_primary_plugins_dir = NULL; + +static void test_parse_k8s_data(void **state) +{ + UNUSED(state); + + struct label *labels = (struct label *)0xff; + + struct k8s_test_data { + char *data; + char *name; + char *key[3]; + char *value[3]; + }; + + struct k8s_test_data test_data[] = { + // One label + { .data = "name label1=\"value1\"", + .name = "name", + .key[0] = "label1", .value[0] = "value1" }, + + // Three labels + { .data = "name label1=\"value1\",label2=\"value2\",label3=\"value3\"", + .name = "name", + .key[0] = "label1", .value[0] = "value1", + .key[1] = "label2", .value[1] = "value2", + .key[2] = "label3", .value[2] = "value3" }, + + // Comma at the end of the data string + { .data = "name label1=\"value1\",", + .name = "name", + .key[0] = "label1", .value[0] = "value1" }, + + // Equals sign in the value + { .data = "name label1=\"value=1\"", + .name = "name", + .key[0] = "label1", .value[0] = "value=1" }, + + // Double quotation mark in the value + { .data = "name label1=\"value\"1\"", + .name = "name", + .key[0] = "label1", .value[0] = "value" }, + + // Escaped double quotation mark in the value + { .data = "name label1=\"value\\\"1\"", + .name = "name", + .key[0] = "label1", .value[0] = "value\\\"1" }, + + // Equals sign in the key + { .data = "name label=1=\"value1\"", + .name = "name", + .key[0] = "label", .value[0] = "1=\"value1\"" }, + + // Skipped value + { .data = "name label1=,label2=\"value2\"", + .name = "name", + .key[0] = "label2", .value[0] = "value2" }, + + // A pair of equals signs + { .data = "name= =", + .name = "name=" }, + + // A pair of commas + { .data = "name, ,", + .name = "name," }, + + { .data = NULL } + }; + + for (int i = 0; test_data[i].data != NULL; i++) { + char *data = strdup(test_data[i].data); + + for (int l = 0; l < 3 && test_data[i].key[l] != NULL; l++) { + char *key = test_data[i].key[l]; + char *value = test_data[i].value[l]; + + expect_function_call(__wrap_add_label_to_list); + expect_value(__wrap_add_label_to_list, l, 0xff); + expect_string(__wrap_add_label_to_list, key, key); + expect_string(__wrap_add_label_to_list, value, value); + expect_value(__wrap_add_label_to_list, label_source, LABEL_SOURCE_KUBERNETES); + } + + char *name = parse_k8s_data(&labels, data); + + assert_string_equal(name, test_data[i].name); + assert_ptr_equal(labels, 0xff); + + free(data); + } +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_parse_k8s_data), + }; + + int test_res = cmocka_run_group_tests_name("test_parse_k8s_data", tests, NULL, NULL); + + return test_res; +} diff --git a/collectors/cgroups.plugin/tests/test_cgroups_plugin.h b/collectors/cgroups.plugin/tests/test_cgroups_plugin.h new file mode 100644 index 0000000000..3d68e92304 --- /dev/null +++ b/collectors/cgroups.plugin/tests/test_cgroups_plugin.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef TEST_CGROUPS_PLUGIN_H +#define TEST_CGROUPS_PLUGIN_H 1 + +#include "libnetdata/libnetdata.h" + +#include "../sys_fs_cgroup.h" + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <stdint.h> +#include <cmocka.h> + +#endif /* TEST_CGROUPS_PLUGIN_H */ diff --git a/collectors/cgroups.plugin/tests/test_doubles.c b/collectors/cgroups.plugin/tests/test_doubles.c new file mode 100644 index 0000000000..5572fb2f50 --- /dev/null +++ b/collectors/cgroups.plugin/tests/test_doubles.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "test_cgroups_plugin.h" + +void rrdset_is_obsolete(RRDSET *st) +{ + UNUSED(st); +} + +void rrdset_isnot_obsolete(RRDSET *st) +{ + UNUSED(st); +} + +struct mountinfo *mountinfo_read(int do_statvfs) +{ + UNUSED(do_statvfs); + + return NULL; +} + +struct mountinfo * +mountinfo_find_by_filesystem_mount_source(struct mountinfo *root, const char *filesystem, const char *mount_source) +{ + UNUSED(root); + UNUSED(filesystem); + UNUSED(mount_source); + + return NULL; +} + +struct mountinfo * +mountinfo_find_by_filesystem_super_option(struct mountinfo *root, const char *filesystem, const char *super_options) +{ + UNUSED(root); + UNUSED(filesystem); + UNUSED(super_options); + + return NULL; +} + +void mountinfo_free_all(struct mountinfo *mi) +{ + UNUSED(mi); +} + +struct label *__wrap_add_label_to_list(struct label *l, char *key, char *value, LABEL_SOURCE label_source) +{ + function_called(); + check_expected_ptr(l); + check_expected_ptr(key); + check_expected_ptr(value); + check_expected(label_source); + return l; +} + +void rrdset_update_labels(RRDSET *st, struct label *labels) +{ + UNUSED(st); + UNUSED(labels); +} + +RRDSET *rrdset_create_custom( + RRDHOST *host, const char *type, const char *id, const char *name, const char *family, const char *context, + const char *title, const char *units, const char *plugin, const char *module, long priority, int update_every, + RRDSET_TYPE chart_type, RRD_MEMORY_MODE memory_mode, long history_entries) +{ + UNUSED(host); + UNUSED(type); + UNUSED(id); + UNUSED(name); + UNUSED(family); + UNUSED(context); + UNUSED(title); + UNUSED(units); + UNUSED(plugin); + UNUSED(module); + UNUSED(priority); + UNUSED(update_every); + UNUSED(chart_type); + UNUSED(memory_mode); + UNUSED(history_entries); + + return NULL; +} + +RRDDIM *rrddim_add_custom( + RRDSET *st, const char *id, const char *name, collected_number multiplier, collected_number divisor, + RRD_ALGORITHM algorithm, RRD_MEMORY_MODE memory_mode) +{ + UNUSED(st); + UNUSED(id); + UNUSED(name); + UNUSED(multiplier); + UNUSED(divisor); + UNUSED(algorithm); + UNUSED(memory_mode); + + return NULL; +} + +collected_number rrddim_set(RRDSET *st, const char *id, collected_number value) +{ + UNUSED(st); + UNUSED(id); + UNUSED(value); + + return 0; +} + +collected_number rrddim_set_by_pointer(RRDSET *st, RRDDIM *rd, collected_number value) +{ + UNUSED(st); + UNUSED(rd); + UNUSED(value); + + return 0; +} + +RRDSETVAR *rrdsetvar_custom_chart_variable_create(RRDSET *st, const char *name) +{ + UNUSED(st); + UNUSED(name); + + return NULL; +} + +void rrdsetvar_custom_chart_variable_set(RRDSETVAR *rs, calculated_number value) +{ + UNUSED(rs); + UNUSED(value); +} + +void rrdset_next_usec(RRDSET *st, usec_t microseconds) +{ + UNUSED(st); + UNUSED(microseconds); +} + +void rrdset_done(RRDSET *st) +{ + UNUSED(st); +} + +void update_pressure_chart(struct pressure_chart *chart) +{ + UNUSED(chart); +} + +void netdev_rename_device_add( + const char *host_device, const char *container_device, const char *container_name, struct label *labels) +{ + UNUSED(host_device); + UNUSED(container_device); + UNUSED(container_name); + UNUSED(labels); +} + +void netdev_rename_device_del(const char *host_device) +{ + UNUSED(host_device); +} diff --git a/collectors/plugins.d/pluginsd_parser.c b/collectors/plugins.d/pluginsd_parser.c index aee8d33434..4a97c5535a 100644 --- a/collectors/plugins.d/pluginsd_parser.c +++ b/collectors/plugins.d/pluginsd_parser.c @@ -157,11 +157,11 @@ PARSER_RC pluginsd_overwrite_action(void *user, RRDHOST *host, struct label *new { UNUSED(user); - if (!host->labels) { - host->labels = new_labels; + if (!host->labels.head) { + host->labels.head = new_labels; } else { rrdhost_rdlock(host); - replace_label_list(host, new_labels); + replace_label_list(&host->labels, new_labels); rrdhost_unlock(host); } return PARSER_RC_OK; diff --git a/collectors/proc.plugin/plugin_proc.h b/collectors/proc.plugin/plugin_proc.h index 50ffb184ed..108c026ab2 100644 --- a/collectors/proc.plugin/plugin_proc.h +++ b/collectors/proc.plugin/plugin_proc.h @@ -65,7 +65,8 @@ extern int get_numa_node_count(void); extern unsigned long long tcpext_TCPSynRetrans; // netdev renames -extern void netdev_rename_device_add(const char *host_device, const char *container_device, const char *container_name); +extern void netdev_rename_device_add( + const char *host_device, const char *container_device, const char *container_name, struct label *labels); extern void netdev_rename_device_del(const char *host_device); #include "proc_self_mountinfo.h" diff --git a/collectors/proc.plugin/proc_net_dev.c b/collectors/proc.plugin/proc_net_dev.c index 9bfb0597b8..a90e3c3ee3 100644 --- a/collectors/proc.plugin/proc_net_dev.c +++ b/collectors/proc.plugin/proc_net_dev.c @@ -60,6 +60,8 @@ static struct netdev { const char *chart_family; + struct label *chart_labels; + int flipped; unsigned long priority; @@ -192,6 +194,7 @@ static void netdev_free_chart_strings(struct netdev *d) { static void netdev_free(struct netdev *d) { netdev_charts_release(d); netdev_free_chart_strings(d); + free_label_list(d->chart_labels); freez((void *)d->name); freez((void *)d->filename_speed); @@ -211,6 +214,8 @@ static struct netdev_rename { const char *container_device; const char *container_name; + struct label *chart_labels; + int processed; struct netdev_rename *next; @@ -230,7 +235,9 @@ static struct netdev_rename *netdev_rename_find(const char *host_device, uint32_ } // other threads can call this function to register a rename to a netdev -void netdev_rename_device_add(const char *host_device, const char *container_device, const char *container_name) { +void netdev_rename_device_add( + const char *host_device, const char *container_device, const char *container_name, struct label *labels) +{ netdata_mutex_lock(&netdev_rename_mutex); uint32_t hash = simple_hash(host_device); @@ -240,6 +247,7 @@ void netdev_rename_device_add(const char *host_device, const char *container_dev r->host_device = strdupz(host_device); r->container_device = strdupz(container_device); r->container_name = strdupz(container_name); + update_label_list(&r->chart_labels, labels); r->hash = hash; r->next = netdev_rename_root; r->processed = 0; @@ -254,6 +262,9 @@ void netdev_rename_device_add(const char *host_device, const char *container_dev r->container_device = strdupz(container_device); r->container_name = strdupz(container_name); + + update_label_list(&r->chart_labels, labels); + r->processed = 0; netdev_pending_renames++; info("CGROUP: altered network interface rename for '%s' as '%s' under '%s'", r->host_device, r->container_device, r->container_name); @@ -285,6 +296,7 @@ void netdev_rename_device_del(const char *host_device) { freez((void *) r->host_device); freez((void *) r->container_name); freez((void *) r->container_device); + free_label_list(r->chart_labels); freez((void *) r); break; } @@ -334,6 +346,8 @@ static inline void netdev_rename_cgroup(struct netdev *d, struct netdev_rename * snprintfz(buffer, RRD_ID_LENGTH_MAX, "net %s", r->container_device); d->chart_family = strdupz(buffer); + update_label_list(&d->chart_labels, r->chart_labels); + d->priority = NETDATA_CHART_PRIO_CGROUP_NET_IFACE; d->flipped = 1; } @@ -677,6 +691,8 @@ int do_proc_net_dev(int update_every, usec_t dt) { , RRDSET_TYPE_AREA ); + rrdset_update_labels(d->st_bandwidth, d->chart_labels); + d->rd_rbytes = rrddim_add(d->st_bandwidth, "received", NULL, 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL); d->rd_tbytes = rrddim_add(d->st_bandwidth, "sent", NULL, -8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL); @@ -820,6 +836,8 @@ int do_proc_net_dev(int update_every, usec_t dt) { rrdset_flag_set(d->st_packets, RRDSET_FLAG_DETAIL); + rrdset_update_labels(d->st_packets, d->chart_labels); + d->rd_rpackets = rrddim_add(d->st_packets, "received", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); d->rd_tpackets = rrddim_add(d->st_packets, "sent", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); d->rd_rmulticast = rrddim_add(d->st_packets, "multicast", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); @@ -866,6 +884,8 @@ int do_proc_net_dev(int update_every, usec_t dt) { rrdset_flag_set(d->st_errors, RRDSET_FLAG_DETAIL); + rrdset_update_labels(d->st_errors, d->chart_labels); + d->rd_rerrors = rrddim_add(d->st_errors, "inbound", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); d->rd_terrors = rrddim_add(d->st_errors, "outbound", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); @@ -910,6 +930,8 @@ int do_proc_net_dev(int update_every, usec_t dt) { rrdset_flag_set(d->st_drops, RRDSET_FLAG_DETAIL); + rrdset_update_labels(d->st_drops, d->chart_labels); + d->rd_rdrops = rrddim_add(d->st_drops, "inbound", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); d->rd_tdrops = rrddim_add(d->st_drops, "outbound", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); @@ -954,6 +976,8 @@ int do_proc_net_dev(int update_every, usec_t dt) { rrdset_flag_set(d->st_fifo, RRDSET_FLAG_DETAIL); + rrdset_update_labels(d->st_fifo, d->chart_labels); + d->rd_rfifo = rrddim_add(d->st_fifo, "receive", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); d->rd_tfifo = rrddim_add(d->st_fifo, "transmit", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); @@ -998,6 +1022,8 @@ int do_proc_net_dev(int update_every, usec_t dt) { rrdset_flag_set(d->st_compressed, RRDSET_FLAG_DETAIL); + rrdset_update_labels(d->st_compressed, d->chart_labels); + d->rd_rcompressed = rrddim_add(d->st_compressed, "received", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); d->rd_tcompressed = rrddim_add(d->st_compressed, "sent", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); @@ -1042,6 +1068,8 @@ int do_proc_net_dev(int update_every, usec_t dt) { rrdset_flag_set(d->st_events, RRDSET_FLAG_DETAIL); + rrdset_update_labels(d->st_events, d->chart_labels); + d->rd_rframe = rrddim_add(d->st_events, "frames", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); d->rd_tcollisions = rrddim_add(d->st_events, "collisions", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); d->rd_tcarrier = rrddim_add(d->st_events, "carrier", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); diff --git a/daemon/commands.c b/daemon/commands.c index d6364e89a8..eac392e33a 100644 --- a/daemon/commands.c +++ b/daemon/commands.c @@ -216,13 +216,13 @@ static cmd_status_t cmd_reload_labels_execute(char *args, char **message) BUFFER *wb = buffer_create(10); rrdhost_rdlock(localhost); - netdata_rwlock_rdlock(&localhost->labels_rwlock); - struct label *l=localhost->labels; + netdata_rwlock_rdlock(&localhost->labels.labels_rwlock); + struct label *l = localhost->labels.head; while (l != NULL) { buffer_sprintf(wb,"Label [source id=%s]: \"%s\" -> \"%s\"\n", translate_label_source(l->label_source), l->key, l->value); l = l->next; } - netdata_rwlock_unlock(&localhost->labels_rwlock); + netdata_rwlock_unlock(&localhost->labels.labels_rwlock); rrdhost_unlock(localhost); (*message)=strdupz(buffer_tostring(wb)); diff --git a/database/rrd.h b/database/rrd.h index 49da6ba5a3..6fafbf5710 100644 --- a/database/rrd.h +++ b/database/rrd.h @@ -19,6 +19,7 @@ typedef struct context_param CONTEXT_PARAM; struct rrddim_volatile; struct rrdset_volatile; struct context_param; +struct label; #ifdef ENABLE_DBENGINE struct rrdeng_page_descr; struct rrdengine_instance; @@ -185,12 +186,45 @@ struct label { struct label *next; }; +struct label_index { + struct label *head; // Label list + netdata_rwlock_t labels_rwlock; // lock for the label list + uint32_t labels_flag; // Flags for labels +}; + +typedef enum strip_quotes { + DO_NOT_STRIP_QUOTES, + STRIP_QUOTES +} STRIP_QUOTES_OPTION; + +typedef enum skip_escaped_characters { + DO_NOT_SKIP_ESCAPED_CHARACTERS, + SKIP_ESCAPED_CHARACTERS +} SKIP_ESCAPED_CHARACTERS_OPTION; + char *translate_label_source(LABEL_SOURCE l); struct label *create_label(char *key, char *value, LABEL_SOURCE label_source); -struct label *add_label_to_list(struct label *l, char *key, char *value, LABEL_SOURCE label_source); -extern void replace_label_list(RRDHOST *host, struct label *new_labels); -extern void free_host_labels(struct label *labels); -void reload_host_labels(); +extern struct label *add_label_to_list(struct label *l, char *key, char *value, LABEL_SOURCE label_source); +extern void update_label_list(struct label **labels, struct label *new_labels); +extern void replace_label_list(struct label_index *labels, struct label *new_labels); +extern int is_valid_label_value(char *value); +extern int is_valid_label_key(char *key); +extern void free_label_list(struct label *labels); +extern struct label *label_list_lookup_key(struct label *head, char *key, uint32_t key_hash); +extern int label_list_contains_key(struct label *head, char *key, uint32_t key_hash); +extern int label_list_contains(struct label *head, struct label *check); +extern struct label *merge_label_lists(struct label *lo_pri, struct label *hi_pri); +extern void strip_last_symbol( + char *str, + char symbol, + SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters); +extern char *strip_double_quotes(char *str, SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters); +void reload_host_labels(void); +extern void rrdset_add_label_to_new_list(RRDSET *st, char *key, char *value, LABEL_SOURCE source); +extern void rrdset_finalize_labels(RRDSET *st); +extern void rrdset_update_labels(RRDSET *st, struct label *labels); +extern int rrdset_contains_label_key(RRDSET *st, char *key, uint32_t key_hash); +extern struct label *rrdset_lookup_label_key(RRDSET *st, char *key, uint32_t key_hash); // ---------------------------------------------------------------------------- // RRD DIMENSION - this is a metric @@ -376,6 +410,8 @@ struct rrdset_volatile { char *old_title; char *old_family; char *old_context; + struct label *new_labels; + struct label_index labels; }; // ---------------------------------------------------------------------------- @@ -801,9 +837,7 @@ struct rrdhost { // ------------------------------------------------------------------------ // Support for host-level labels - struct label *labels; - netdata_rwlock_t labels_rwlock; // lock for the label list - uint32_t labels_flag; //Flags for labels + struct label_index labels; // ------------------------------------------------------------------------ // indexes diff --git a/database/rrdcalc.c b/database/rrdcalc.c index a057841373..935ee9c055 100644 --- a/database/rrdcalc.c +++ b/database/rrdcalc.c @@ -651,7 +651,7 @@ static void rrdcalc_labels_unlink_alarm_loop(RRDHOST *host, RRDCALC *alarms) { } char cmp[CONFIG_FILE_LINE_MAX+1]; - struct label *move = host->labels; + struct label *move = host->labels.head; while(move) { snprintf(cmp, CONFIG_FILE_LINE_MAX, "%s=%s", move->key, move->value); if (simple_pattern_matches(rc->splabels, move->key) || @@ -682,12 +682,12 @@ static void rrdcalc_labels_unlink_alarm_loop(RRDHOST *host, RRDCALC *alarms) { void rrdcalc_labels_unlink_alarm_from_host(RRDHOST *host) { rrdhost_check_rdlock(host); - netdata_rwlock_rdlock(&host->labels_rwlock); + netdata_rwlock_rdlock(&host->labels.labels_rwlock); rrdcalc_labels_unlink_alarm_loop(host, host->alarms); rrdcalc_labels_unlink_alarm_loop(host, host->alarms_with_foreach); - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); } void rrdcalc_labels_unlink() { @@ -698,7 +698,7 @@ void rrdcalc_labels_unlink() { if (unlikely(!host->health_enabled)) continue; - if (host->labels) { + if (host->labels.head) { rrdhost_wrlock(host); rrdcalc_labels_unlink_alarm_from_host(host); diff --git a/database/rrdcalctemplate.c b/database/rrdcalctemplate.c index 4928ccb7bc..007b8c5d6a 100644 --- a/database/rrdcalctemplate.c +++ b/database/rrdcalctemplate.c @@ -10,13 +10,13 @@ static int rrdcalctemplate_is_there_label_restriction(RRDCALCTEMPLATE *rt, RRDH return 0; errno = 0; - struct label *move = host->labels; + struct label *move = host->labels.head; char cmp[CONFIG_FILE_LINE_MAX+1]; int ret; if(move) { rrdhost_check_rdlock(host); - netdata_rwlock_rdlock(&host->labels_rwlock); + netdata_rwlock_rdlock(&host->labels.labels_rwlock); while(move) { snprintfz(cmp, CONFIG_FILE_LINE_MAX, "%s=%s", move->key, move->value); if (simple_pattern_matches(rt->splabels, move->key) || @@ -25,7 +25,7 @@ static int rrdcalctemplate_is_there_label_restriction(RRDCALCTEMPLATE *rt, RRDH } move = move->next; } - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); if(!move) { error("Health template '%s' cannot be applied, because the host %s does not have the label(s) '%s'", diff --git a/database/rrdhost.c b/database/rrdhost.c index c3940c29b2..e120e9d445 100644 --- a/database/rrdhost.c +++ b/database/rrdhost.c @@ -187,7 +187,7 @@ RRDHOST *rrdhost_create(const char *hostname, #endif netdata_rwlock_init(&host->rrdhost_rwlock); - netdata_rwlock_init(&host->labels_rwlock); + netdata_rwlock_init(&host->labels.labels_rwlock); netdata_mutex_init(&host->aclk_state_lock); @@ -873,7 +873,7 @@ void rrdhost_free(RRDHOST *host) { pthread_mutex_destroy(&host->aclk_state_lock); freez(host->aclk_state.claimed_id); freez((void *)host->tags); - free_host_labels(host->labels); + free_label_list(host->labels.head); freez((void *)host->os); freez((void *)host->timezone); freez(host->program_version); @@ -890,7 +890,7 @@ void rrdhost_free(RRDHOST *host) { freez(host->registry_hostname); simple_pattern_free(host->rrdpush_send_charts_matching); rrdhost_unlock(host); - netdata_rwlock_destroy(&host->labels_rwlock); + netdata_rwlock_destroy(&host->labels.labels_rwlock); netdata_rwlock_destroy(&host->health_log.alarm_log_rwlock); netdata_rwlock_destroy(&host->rrdhost_rwlock); @@ -930,55 +930,7 @@ void rrdhost_save_charts(RRDHOST *host) { rrdhost_unlock(host); } -static int is_valid_label_value(char *value) { - while(*value) { - if(*value == '"' || *value == '\'' || *value == '*' || *value == '!') { - return 0; - } - - value++; - } - - return 1; -} - -static int is_valid_label_key(char *key) { - //Prometheus exporter - if(!strcmp(key, "chart") || !strcmp(key, "family") || !strcmp(key, "dimension")) - return 0; - - //Netdata and Prometheus internal - if (*key == '_') - return 0; - - while(*key) { - if(!(isdigit(*key) || isalpha(*key) || *key == '.' || *key == '_' || *key == '-')) - return 0; - - key++; - } - - return 1; -} - -char *translate_label_source(LABEL_SOURCE l) { - switch (l) { - case LABEL_SOURCE_AUTO: - return "AUTO"; - case LABEL_SOURCE_NETDATA_CONF: - return "NETDATA.CONF"; - case LABEL_SOURCE_DOCKER : - return "DOCKER"; - case LABEL_SOURCE_ENVIRONMENT : - return "ENVIRONMENT"; - case LABEL_SOURCE_KUBERNETES : - return "KUBERNETES"; - default: - return "Invalid label source"; - } -} - -struct label *load_auto_labels() +static struct label *rrdhost_load_auto_labels(void) { struct label *label_list = NULL; @@ -1040,11 +992,13 @@ struct label *load_auto_labels() return label_list; } -static inline int is_valid_label_config_option(char *name, char *value) { - return (is_valid_label_key(name) && is_valid_label_value(value) && strcmp(name, "from environment") && strcmp(name, "from kubernetes pods") ); - } +static inline int rrdhost_is_valid_label_config_option(char *name, char *value) +{ + return (is_valid_label_key(name) && is_valid_label_value(value) && strcmp(name, "from environment") && + strcmp(name, "from kubernetes pods")); +} -struct label *load_config_labels() +static struct label *rrdhost_load_config_labels() { int status = config_load(NULL, 1, CONFIG_SECTION_HOST_LABEL); if(!status) { @@ -1058,7 +1012,7 @@ struct label *load_config_labels() config_section_wrlock(co); struct config_option *cv; for(cv = co->values; cv ; cv = cv->next) { - if( is_valid_label_config_option(cv->name, cv->value)) { + if(rrdhost_is_valid_label_config_option(cv->name, cv->value)) { l = add_label_to_list(l, cv->name, cv->value, LABEL_SOURCE_NETDATA_CONF); cv->flags |= CONFIG_VALUE_USED; } else { @@ -1072,45 +1026,6 @@ struct label *load_config_labels() return l; } -typedef enum strip_quotes { - DO_NOT_STRIP_QUOTES, - STRIP_QUOTES -} STRIP_QUOTES_OPTION; - -typedef enum skip_escaped_characters { - DO_NOT_SKIP_ESCAPED_CHARACTERS, - SKIP_ESCAPED_CHARACTERS -} SKIP_ESCAPED_CHARACTERS_OPTION; - -static inline void strip_last_symbol( - char *str, - char symbol, - SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters) -{ - char *end = str; - - while (*end && *end != symbol) { - if (unlikely(skip_escaped_characters && *end == '\\')) { - end++; - if (unlikely(!*end)) - break; - } - end++; - } - if (likely(*end == symbol)) - *end = '\0'; -} - -static inline char *strip_double_quotes(char *str, SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters) -{ - if (*str == '"') { - str++; - strip_last_symbol(str, '"', skip_escaped_characters); - } - - return str; -} - struct label *parse_simple_tags( struct label *label_list, const char *tags, @@ -1200,7 +1115,7 @@ struct label *parse_json_tags(struct label *label_list, const char *tags) return label_list; } -struct label *load_labels_from_tags() +static struct label *rrdhost_load_labels_from_tags(void) { if (!localhost->tags) return NULL; @@ -1244,7 +1159,7 @@ struct label *load_labels_from_tags() return label_list; } -struct label *load_kubernetes_labels() +static struct label *rrdhost_load_kubernetes_labels(void) { struct label *l=NULL; char *label_script = mallocz(sizeof(char) * (strlen(netdata_configured_primary_plugins_dir) + strlen("get-kubernetes-labels.sh") + 2)); @@ -1302,104 +1217,26 @@ struct label *load_kubernetes_labels() return l; } -struct label *create_label(char *key, char *value, LABEL_SOURCE label_source) +void reload_host_labels(void) { - size_t key_len = strlen(key), value_len = strlen(value); - size_t n = sizeof(struct label) + key_len + 1 + value_len + 1; - struct label *result = callocz(1,n); - if (result != NULL) { - char *c = (char *)result; - c += sizeof(struct label); - strcpy(c, key); - result->key = c; - c += key_len + 1; - strcpy(c, value); - result->value = c; - result->label_source = label_source; - result->key_hash = simple_hash(result->key); - } - return result; -} - -void free_host_labels(struct label *labels) -{ - while (labels != NULL) - { - struct label *current = labels; - labels = labels->next; - freez(current); - } -} - -void replace_label_list(RRDHOST *host, struct label *new_labels) -{ - rrdhost_check_rdlock(host); - netdata_rwlock_wrlock(&host->labels_rwlock); - struct label *old_labels = host->labels; - host->labels = new_labels; - netdata_rwlock_unlock(&host->labels_rwlock); - - free_host_labels(old_labels); -} - -struct label *add_label_to_list(struct label *l, char *key, char *value, LABEL_SOURCE label_source) -{ - struct label *lab = create_label(key, value, label_source); - lab->next = l; - return lab; -} - -int label_list_contains(struct label *head, struct label *check) -{ - while (head != NULL) - { - if (head->key_hash == check->key_hash && !strcmp(head->key, check->key)) - return 1; - head = head->next; - } - return 0; -} - -/* Create a list with entries from both lists. - If any entry in the low priority list is masked by an entry in the high priorty list then delete it. -*/ -struct label *merge_label_lists(struct label *lo_pri, struct label *hi_pri) -{ - struct label *result = hi_pri; - while (lo_pri != NULL) - { - struct label *current = lo_pri; - lo_pri = lo_pri->next; - if (!label_list_contains(result, current)) { - current->next = result; - result = current; - } - else - freez(current); - } - return result; -} - -void reload_host_labels() -{ - struct label *from_auto = load_auto_labels(); - struct label *from_k8s = load_kubernetes_labels(); - struct label *from_config = load_config_labels(); - struct label *from_tags = load_labels_from_tags(); + struct label *from_auto = rrdhost_load_auto_labels(); + struct label *from_k8s = rrdhost_load_kubernetes_labels(); + struct label *from_config = rrdhost_load_config_labels(); + struct label *from_tags = rrdhost_load_labels_from_tags(); struct label *new_labels = merge_label_lists(from_auto, from_k8s); new_labels = merge_label_lists(new_labels, from_tags); new_labels = merge_label_lists(new_labels, from_config); rrdhost_rdlock(localhost); - replace_label_list(localhost, new_labels); + replace_label_list(&localhost->labels, new_labels); health_label_log_save(localhost); rrdhost_unlock(localhost); /* TODO-GAPS - fix this so that it looks properly at the state and version of the sender if(localhost->rrdpush_send_enabled && localhost->rrdpush_sender_buffer){ - localhost->labels_flag |= LABEL_FLAG_UPDATE_STREAM; + localhost->labels.labels_flag |= LABEL_FLAG_UPDATE_STREAM; rrdpush_send_labels(localhost); } */ diff --git a/database/rrdlabels.c b/database/rrdlabels.c new file mode 100644 index 0000000000..b423f422a4 --- /dev/null +++ b/database/rrdlabels.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#define NETDATA_RRD_INTERNALS +#include "rrd.h" + +char *translate_label_source(LABEL_SOURCE l) { + switch (l) { + case LABEL_SOURCE_AUTO: + return "AUTO"; + case LABEL_SOURCE_NETDATA_CONF: + return "NETDATA.CONF"; + case LABEL_SOURCE_DOCKER : + return "DOCKER"; + case LABEL_SOURCE_ENVIRONMENT : + return "ENVIRONMENT"; + case LABEL_SOURCE_KUBERNETES : + return "KUBERNETES"; + default: + return "Invalid label source"; + } +} + +int is_valid_label_value(char *value) { + while(*value) { + if(*value == '"' || *value == '\'' || *value == '*' || *value == '!') { + return 0; + } + + value++; + } + + return 1; +} + +int is_valid_label_key(char *key) { + //Prometheus exporter + if(!strcmp(key, "chart") || !strcmp(key, "family") || !strcmp(key, "dimension")) + return 0; + + //Netdata and Prometheus internal + if (*key == '_') + return 0; + + while(*key) { + if(!(isdigit(*key) || isalpha(*key) || *key == '.' || *key == '_' || *key == '-')) + return 0; + + key++; + } + + return 1; +} + +void strip_last_symbol( + char *str, + char symbol, + SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters) +{ + char *end = str; + + while (*end && *end != symbol) { + if (unlikely(skip_escaped_characters && *end == '\\')) { + end++; + if (unlikely(!*end)) + break; + } + end++; + } + if (likely(*end == symbol)) + *end = '\0'; +} + +char *strip_double_quotes(char *str, SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters) +{ + if (*str == '"') { + str++; + strip_last_symbol(str, '"', skip_escaped_characters); + } + + return str; +} + +struct label *create_label(char *key, char *value, LABEL_SOURCE label_source) +{ + size_t key_len = strlen(key), value_len = strlen(value); + size_t n = sizeof(struct label) + key_len + 1 + value_len + 1; + struct label *result = callocz(1,n); + if (result != NULL) { + char *c = (char *)result; + c += sizeof(struct label); + strcpy(c, key); + result->key = c; + c += key_len + 1; + strcpy(c, value); + result->value = c; + result->label_source = label_source; + result->key_hash = simple_hash(result->key); + } + return result; +} + +void free_label_list(struct label *labels) +{ + while (labels != NULL) + { + struct label *current = labels; + labels = labels->next; + freez(current); + } +} + +void replace_label_list(struct label_index *labels, struct label *new_labels) +{ + netdata_rwlock_wrlock(&labels->labels_rwlock); + struct label *old_labels = labels->head; + labels->head = new_labels; + netdata_rwlock_unlock(&labels->labels_rwlock); + + free_label_list(old_labels); +} + +struct label *add_label_to_list(struct label *l, char *key, char *value, LABEL_SOURCE label_source) +{ + struct label *lab = create_label(key, value, label_source); + lab->next = l; + return lab; +} + +void update_label_list(struct label **labels, struct label *new_labels) +{ + free_label_list(*labels); + *labels = NULL; + + while (new_labels != NULL) + { + *labels = add_label_to_list(*labels, new_labels->key, new_labels->value, new_labels->label_source); + new_labels = new_labels->next; + } +} + +struct label *label_list_lookup_key(struct label *head, char *key, uint32_t key_hash) +{ + while (head != NULL) + { + if (head->key_hash == key_hash && !strcmp(head->key, key)) + return head; + head = head->next; + } + return NULL; +} + +int label_list_contains_key(struct label *head, char *key, uint32_t key_hash) +{ + return (label_list_lookup_key(head, key, key_hash) != NULL); +} + +int label_list_contains(struct label *head, struct label *check) +{ + return label_list_contains_key(head, check->key, check->key_hash); +} + +/* Create a list with entries from both lists. + If any entry in the low priority list is masked by an entry in the high priority list then delete it. +*/ +struct label *merge_label_lists(struct label *lo_pri, struct label *hi_pri) +{ + struct label *result = hi_pri; + while (lo_pri != NULL) + { + struct label *current = lo_pri; + lo_pri = lo_pri->next; + if (!label_list_contains(result, current)) { + current->next = result; + result = current; + } + else + freez(current); + } + return result; +} + diff --git a/database/rrdset.c b/database/rrdset.c index 72f216f2ee..f2932798f4 100644 --- a/database/rrdset.c +++ b/database/rrdset.c @@ -330,13 +330,22 @@ void rrdset_free(RRDSET *st) { rrdset_index_del_name(host, st); + // ------------------------------------------------------------------------ + // remove it from the configuration + + appconfig_section_destroy_non_loaded(&netdata_config, st->config_section); + // ------------------------------------------------------------------------ // free its children structures freez(st->exporting_flags); while(st->variables) rrdsetvar_free(st->variables); - while(st->alarms) rrdsetcalc_unlink(st->alarms); +// while(st->alarms) rrdsetcalc_unlink(st->alarms); + /* We must free all connected alarms here in case this has been an ephemeral chart whose alarm was + * created by a template. This leads to an effective memory leak, which cannot be detected since the + * alarms will still be connected to the host, and freed during shutdown. */ + while(st->alarms) rrdcalc_unlink_and_free(st->rrdhost, st->alarms); while(st->dimensions) rrddim_free(st, st->dimensions); rrdfamily_free(host, st->rrdfamily); @@ -366,6 +375,7 @@ void rrdset_free(RRDSET *st) { // free it netdata_rwlock_destroy(&st->rrdset_rwlock); + netdata_rwlock_destroy(&st->state->labels.labels_rwlock); // free directly allocated members freez(st->config_section); @@ -374,6 +384,7 @@ void rrdset_free(RRDSET *st) { freez(st->state->old_title); freez(st->state->old_family); freez(st->state->old_context); + free_label_list(st->state->labels.head); freez(st->state); switch(st->rrd_memory_mode) { @@ -835,7 +846,7 @@ RRDSET *rrdset_create_custom( st->chart_type = rrdset_type_id(config_get(st->config_section, "chart type", rrdset_type_name(chart_type))); st->type = config_get(st->config_section, "type", type); - st->state = mallocz(sizeof(*st->state)); + st->state = callocz(1, sizeof(*st->state)); st->family = config_get(st->config_section, "family", family?family:st->type); st->state->old_family = strdupz(st->family); json_fix_string(st->family); @@ -887,6 +898,7 @@ RRDSET *rrdset_create_custom( avl_init_lock(&st->rrdvar_root_index, rrdvar_compare); netdata_rwlock_init(&st->rrdset_rwlock); + netdata_rwlock_init(&st->state->labels.labels_rwlock); if(name && *name && rrdset_set_name(st, name)) // we did set the name @@ -1903,3 +1915,58 @@ after_second_database_work: netdata_thread_enable_cancelability(); } + +void rrdset_add_label_to_new_list(RRDSET *st, char *key, char *value, LABEL_SOURCE source) +{ + st->state->new_labels = add_label_to_list(st->state->new_labels, key, value, source); +} + +void rrdset_finalize_labels(RRDSET *st) +{ + struct label *new_labels = st->state->new_labels; + struct label_index *labels = &st->state->labels; + + if (!labels->head) { + labels->head = new_labels; + } else { + replace_label_list(labels, new_labels); + } + st->state->new_labels = NULL; +} + +void rrdset_update_labels(RRDSET *st, struct label *labels) +{ + if (!labels) + return; + + update_label_list(&st->state->new_labels, labels); + rrdset_finalize_labels(st); +} + +int rrdset_contains_label_key(RRDSET *st, char *key, uint32_t key_hash) +{ + struct label_index *labels = &st->state->labels; + int ret; + + if (!labels->head) + return 0; + + netdata_rwlock_rdlock(&labels->labels_rwlock); + ret = label_list_contains_key(labels->head, key, key_hash); + netdata_rwlock_unlock(&labels->labels_rwlock); + + return ret; +} + +struct label *rrdset_lookup_label_key(RRDSET *st, char *key, uint32_t key_hash) +{ + struct label_index *labels = &st->state->labels; + struct label *ret = NULL; + + if (labels->head) { + netdata_rwlock_rdlock(&labels->labels_rwlock); + ret = label_list_lookup_key(labels->head, key, key_hash); + netdata_rwlock_unlock(&labels->labels_rwlock); + } + return ret; +} diff --git a/exporting/graphite/graphite.c b/exporting/graphite/graphite.c index dac1d7682e..9c09631f19 100644 --- a/exporting/graphite/graphite.c +++ b/exporting/graphite/graphite.c @@ -100,8 +100,8 @@ int format_host_labels_graphite_plaintext(struct instance *instance, RRDHOST *ho return 0; rrdhost_check_rdlock(host); - netdata_rwlock_rdlock(&host->labels_rwlock); - for (struct label *label = host->labels; label; label = label->next) { + netdata_rwlock_rdlock(&host->labels.labels_rwlock); + for (struct label *label = host->labels.head; label; label = label->next) { if (!should_send_label(instance, label)) continue; @@ -113,7 +113,7 @@ int format_host_labels_graphite_plaintext(struct instance *instance, RRDHOST *ho buffer_sprintf(instance->labels, "%s=%s", label->key, value); } } - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); return 0; } diff --git a/exporting/json/json.c b/exporting/json/json.c index e746d64ae0..f2396bafa1 100644 --- a/exporting/json/json.c +++ b/exporting/json/json.c @@ -125,8 +125,8 @@ int format_host_labels_json_plaintext(struct instance *instance, RRDHOST *host) int count = 0; rrdhost_check_rdlock(host); - netdata_rwlock_rdlock(&host->labels_rwlock); - for (struct label *label = host->labels; label; label = label->next) { + netdata_rwlock_rdlock(&host->labels.labels_rwlock); + for (struct label *label = host->labels.head; label; label = label->next) { if (!should_send_label(instance, label)) continue; @@ -138,7 +138,7 @@ int format_host_labels_json_plaintext(struct instance *instance, RRDHOST *host) count++; } - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); buffer_strcat(instance->labels, "},"); diff --git a/exporting/opentsdb/opentsdb.c b/exporting/opentsdb/opentsdb.c index 4d00f893e3..9f65926641 100644 --- a/exporting/opentsdb/opentsdb.c +++ b/exporting/opentsdb/opentsdb.c @@ -153,8 +153,8 @@ int format_host_labels_opentsdb_telnet(struct instance *instance, RRDHOST *host) return 0; rrdhost_check_rdlock(localhost); - netdata_rwlock_rdlock(&host->labels_rwlock); - for (struct label *label = host->labels; label; label = label->next) { + netdata_rwlock_rdlock(&host->labels.labels_rwlock); + for (struct label *label = host->labels.head; label; label = label->next) { if (!should_send_label(instance, label)) continue; @@ -164,7 +164,7 @@ int format_host_labels_opentsdb_telnet(struct instance *instance, RRDHOST *host) if (*value) buffer_sprintf(instance->labels, " %s=%s", label->key, value); } - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); return 0; } @@ -294,8 +294,8 @@ int format_host_labels_opentsdb_http(struct instance *instance, RRDHOST *host) return 0; rrdhost_check_rdlock(host); - netdata_rwlock_rdlock(&host->labels_rwlock); - for (struct label *label = host->labels; label; label = label->next) { + netdata_rwlock_rdlock(&host->labels.labels_rwlock); + for (struct label *label = host->labels.head; label; label = label->next) { if (!should_send_label(instance, label)) continue; @@ -310,7 +310,7 @@ int format_host_labels_opentsdb_http(struct instance *instance, RRDHOST *host) buffer_sprintf(instance->labels, "\"%s\":\"%s\"", label->key, value); } } - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); return 0; } diff --git a/exporting/prometheus/prometheus.c b/exporting/prometheus/prometheus.c index 6b44488808..81a397aa34 100644 --- a/exporting/prometheus/prometheus.c +++ b/exporting/prometheus/prometheus.c @@ -283,8 +283,8 @@ void format_host_labels_prometheus(struct instance *instance, RRDHOST *host) int count = 0; rrdhost_check_rdlock(host); - netdata_rwlock_rdlock(&host->labels_rwlock); - for (struct label *label = host->labels; label; label = label->next) { + netdata_rwlock_rdlock(&host->labels.labels_rwlock); + for (struct label *label = host->labels.head; label; label = label->next) { if (!should_send_label(instance, label)) continue; @@ -301,7 +301,7 @@ void format_host_labels_prometheus(struct instance *instance, RRDHOST *host) count++; } } - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); } struct host_variables_callback_options { diff --git a/exporting/prometheus/remote_write/remote_write.c b/exporting/prometheus/remote_write/remote_write.c index bd7ff48f20..8f757fc223 100644 --- a/exporting/prometheus/remote_write/remote_write.c +++ b/exporting/prometheus/remote_write/remote_write.c @@ -156,8 +156,8 @@ int format_host_prometheus_remote_write(struct instance *instance, RRDHOST *host if (unlikely(sending_labels_configured(instance))) { rrdhost_check_rdlock(host); - netdata_rwlock_rdlock(&host->labels_rwlock); - for (struct label *label = host->labels; label; label = label->next) { + netdata_rwlock_rdlock(&host->labels.labels_rwlock); + for (struct label *label = host->labels.head; label; label = label->next) { if (!should_send_label(instance, label)) continue; @@ -169,7 +169,7 @@ int format_host_prometheus_remote_write(struct instance *instance, RRDHOST *host add_label(connector_specific_data->write_request, key, value); } - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); } return 0; diff --git a/exporting/tests/exporting_fixtures.c b/exporting/tests/exporting_fixtures.c index 42a8e5a749..00bb0ed0f2 100644 --- a/exporting/tests/exporting_fixtures.c +++ b/exporting/tests/exporting_fixtures.c @@ -43,13 +43,13 @@ int setup_rrdhost() label->key = strdupz("key1"); label->value = strdupz("value1"); label->label_source = LABEL_SOURCE_NETDATA_CONF; - localhost->labels = label; + localhost->labels.head = label; label = calloc(1, sizeof(struct label)); label->key = strdupz("key2"); label->value = strdupz("value2"); label->label_source = LABEL_SOURCE_AUTO; - localhost->labels->next = label; + localhost->labels.head->next = label; localhost->rrdset_root = calloc(1, sizeof(RRDSET)); RRDSET *st = localhost->rrdset_root; @@ -93,12 +93,12 @@ int teardown_rrdhost() free((void *)st->name); free(st); - free(localhost->labels->next->key); - free(localhost->labels->next->value); - free(localhost->labels->next); - free(localhost->labels->key); - free(localhost->labels->value); - free(localhost->labels); + free(localhost->labels.head->next->key); + free(localhost->labels.head->next->value); + free(localhost->labels.head->next); + free(localhost->labels.head->key); + free(localhost->labels.head->value); + free(localhost->labels.head); free((void *)localhost->tags); free(localhost); diff --git a/health/health_log.c b/health/health_log.c index b4806267fb..1ac63b594e 100644 --- a/health/health_log.c +++ b/health/health_log.c @@ -73,13 +73,13 @@ inline void health_label_log_save(RRDHOST *host) { if(likely(host->health_log_fp)) { BUFFER *wb = buffer_create(1024); rrdhost_check_rdlock(host); - netdata_rwlock_rdlock(&host->labels_rwlock); - struct label *l=localhost->labels; + netdata_rwlock_rdlock(&host->labels.labels_rwlock); + struct label *l=localhost->labels.head; while (l != NULL) { buffer_sprintf(wb,"%s=%s\t ", l->key, l->value); l = l->next; } - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); char *write = (char *) buffer_tostring(wb) ; diff --git a/libnetdata/avl/avl.c b/libnetdata/avl/avl.c index 15293740d4..5219851896 100644 --- a/libnetdata/avl/avl.c +++ b/libnetdata/avl/avl.c @@ -367,6 +367,22 @@ void avl_init_lock(avl_tree_lock *tree, int (*compar)(void * /*a*/, void * /*b*/ #endif /* AVL_WITHOUT_PTHREADS */ } +void avl_destroy_lock(avl_tree_lock *tree) { +#ifndef AVL_WITHOUT_PTHREADS + int lock; + +#ifdef AVL_LOCK_WITH_MUTEX + lock = pthread_mutex_destroy(&tree->mutex); +#else + lock = pthread_rwlock_destroy(&tree->rwlock); +#endif + + if(lock != 0) + fatal("Failed to destroy AVL mutex/rwlock, error: %d", lock); + +#endif /* AVL_WITHOUT_PTHREADS */ +} + avl *avl_search_lock(avl_tree_lock *tree, avl *item) { avl_read_lock(tree); avl *ret = avl_search(&tree->avl_tree, item); diff --git a/libnetdata/avl/avl.h b/libnetdata/avl/avl.h index a3e0f65667..32e3f27a8b 100644 --- a/libnetdata/avl/avl.h +++ b/libnetdata/avl/avl.h @@ -82,6 +82,9 @@ avl *avl_search(avl_tree_type *tree, avl *item); void avl_init_lock(avl_tree_lock *tree, int (*compar)(void *a, void *b)); void avl_init(avl_tree_type *tree, int (*compar)(void *a, void *b)); +/* Destroy the avl_tree_lock locks + */ +void avl_destroy_lock(avl_tree_lock *tree); int avl_traverse_lock(avl_tree_lock *tree, int (*callback)(void *entry, void *data), void *data); int avl_traverse(avl_tree_type *tree, int (*callback)(void *entry, void *data), void *data); diff --git a/libnetdata/config/appconfig.c b/libnetdata/config/appconfig.c index 70f9e4cda5..d9dcde71a5 100644 --- a/libnetdata/config/appconfig.c +++ b/libnetdata/config/appconfig.c @@ -189,6 +189,49 @@ static inline struct section *appconfig_section_create(struct config *root, cons return co; } +void appconfig_section_destroy_non_loaded(struct config *root, const char *section) +{ + struct section *co; + struct config_option *cv, *cv_next; + + debug(D_CONFIG, "Destroying section '%s'.", section); + + co = appconfig_section_find(root, section); + if(!co) { + error("Could not destroy section '%s'. Not found.", section); + return; + } + + config_section_wrlock(co); + for(cv = co->values; cv ; cv = cv->next) { + if (cv->flags & CONFIG_VALUE_LOADED) { + /* Do not destroy values that were loaded from the configuration files. */ + config_section_unlock(co); + return; + } + } + for(cv = co->values ; cv ; cv = cv_next) { + cv_next = cv->next; + if(unlikely(!appconfig_option_index_del(co, cv))) + error("Cannot remove config option '%s' from section '%s'.", cv->name, co->name); + freez(cv->value); + freez(cv->name); + freez(cv); + } + co->values = NULL; + config_section_unlock(co); + + if (unlikely(!appconfig_index_del(root, co))) { + error("Cannot remove section '%s' from config.", section); + return; + } + + avl_destroy_lock(&co->values_index); + freez(co->name); + pthread_mutex_destroy(&co->mutex); + freez(co); +} + // ---------------------------------------------------------------------------- // config name-value methods diff --git a/libnetdata/config/appconfig.h b/libnetdata/config/appconfig.h index df4adb41f1..9d02e4ada6 100644 --- a/libnetdata/config/appconfig.h +++ b/libnetdata/config/appconfig.h @@ -182,6 +182,8 @@ extern void appconfig_generate(struct config *root, BUFFER *wb, int only_changed extern int appconfig_section_compare(void *a, void *b); +extern void appconfig_section_destroy_non_loaded(struct config *root, const char *section); + extern int config_parse_duration(const char* string, int* result); extern struct section *appconfig_get_section(struct config *root, const char *name); diff --git a/streaming/receiver.c b/streaming/receiver.c index 495a40c017..3ea15806d8 100644 --- a/streaming/receiver.c +++ b/streaming/receiver.c @@ -421,7 +421,7 @@ static int rrdpush_receive(struct receiver_state *rpt) */ // rpt->host->connected_senders++; - rpt->host->labels_flag = (rpt->stream_version > 0)?LABEL_FLAG_UPDATE_STREAM:LABEL_FLAG_STOP_STREAM; + rpt->host->labels.labels_flag = (rpt->stream_version > 0)?LABEL_FLAG_UPDATE_STREAM:LABEL_FLAG_STOP_STREAM; if(health_enabled != CONFIG_BOOLEAN_NO) { if(alarms_delay > 0) { diff --git a/streaming/rrdpush.c b/streaming/rrdpush.c index 3b813e01fe..f54fc609ee 100644 --- a/streaming/rrdpush.c +++ b/streaming/rrdpush.c @@ -334,35 +334,35 @@ void rrdset_done_push(RRDSET *st) { // labels void rrdpush_send_labels(RRDHOST *host) { - if (!host->labels || !(host->labels_flag & LABEL_FLAG_UPDATE_STREAM) || (host->labels_flag & LABEL_FLAG_STOP_STREAM)) + if (!host->labels.head || !(host->labels.labels_flag & LABEL_FLAG_UPDATE_STREAM) || (host->labels.labels_flag & LABEL_FLAG_STOP_STREAM)) return; sender_start(host->sender); rrdhost_rdlock(host); - netdata_rwlock_rdlock(&host->labels_rwlock); + netdata_rwlock_rdlock(&host->labels.labels_rwlock); - struct label *labels = host->labels; - while(labels) { + struct label *label_i = host->labels.head; + while(label_i) { buffer_sprintf(host->sender->build , "LABEL \"%s\" = %d %s\n" - , labels->key - , (int)labels->label_source - , labels->value); + , label_i->key + , (int)label_i->label_source + , label_i->value); - labels = labels->next; + label_i = label_i->next; } buffer_sprintf(host->sender->build , "OVERWRITE %s\n", "labels"); - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); rrdhost_unlock(host); sender_commit(host->sender); if(host->rrdpush_sender_pipe[PIPE_WRITE] != -1 && write(host->rrdpush_sender_pipe[PIPE_WRITE], " ", 1) == -1) error("STREAM %s [send]: cannot write to internal pipe", host->hostname); - host->labels_flag &= ~LABEL_FLAG_UPDATE_STREAM; + host->labels.labels_flag &= ~LABEL_FLAG_UPDATE_STREAM; } void rrdpush_claimed_id(RRDHOST *host) diff --git a/streaming/sender.c b/streaming/sender.c index a6191b0a84..c112609808 100644 --- a/streaming/sender.c +++ b/streaming/sender.c @@ -116,8 +116,8 @@ static inline void rrdpush_sender_thread_data_flush(RRDHOST *host) { } static inline void rrdpush_set_flags_to_newest_stream(RRDHOST *host) { - host->labels_flag |= LABEL_FLAG_UPDATE_STREAM; - host->labels_flag &= ~LABEL_FLAG_STOP_STREAM; + host->labels.labels_flag |= LABEL_FLAG_UPDATE_STREAM; + host->labels.labels_flag &= ~LABEL_FLAG_STOP_STREAM; } void rrdpush_encode_variable(stream_encoded_t *se, RRDHOST *host) @@ -354,8 +354,8 @@ static int rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_po answer = memcmp(http, START_STREAMING_PROMPT, strlen(START_STREAMING_PROMPT)); if(!answer) { version = 0; - host->labels_flag |= LABEL_FLAG_STOP_STREAM; - host->labels_flag &= ~LABEL_FLAG_UPDATE_STREAM; + host->labels.labels_flag |= LABEL_FLAG_STOP_STREAM; + host->labels.labels_flag &= ~LABEL_FLAG_UPDATE_STREAM; } } } diff --git a/web/api/formatters/json_wrapper.c b/web/api/formatters/json_wrapper.c index bfe172cfce..72967375d7 100644 --- a/web/api/formatters/json_wrapper.c +++ b/web/api/formatters/json_wrapper.c @@ -2,7 +2,7 @@ #include "json_wrapper.h" -void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value, RRDDIM *temp_rd) { +void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value, RRDDIM *temp_rd, char *chart_label_key) { rrdset_check_rdlock(r->st); long rows = rrdr_rows(r); @@ -86,12 +86,12 @@ void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS buffer_strcat(wb, "no data"); buffer_strcat(wb, sq); } + buffer_strcat(wb, "],\n"); // Composite charts if (temp_rd) { buffer_sprintf( wb, - "],\n" " %schart_ids%s: [", kq, kq); @@ -114,11 +114,46 @@ void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS buffer_strcat(wb, "no data"); buffer_strcat(wb, sq); } + buffer_strcat(wb, "],\n"); + if (chart_label_key) { + uint32_t key_hash = simple_hash(chart_label_key); + struct label *current_label; + + buffer_sprintf( + wb, + " %schart_labels%s: { %s%s%s : [", + kq, kq, kq, chart_label_key, kq); + + for (c = 0, i = 0, rd = temp_rd; rd && c < r->d; c++, rd = rd->next) { + if (unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) + continue; + if (unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) + continue; + + if (i) + buffer_strcat(wb, ", "); + + current_label = rrdset_lookup_label_key(rd->rrdset, chart_label_key, key_hash); + if (current_label) { + buffer_strcat(wb, sq); + buffer_strcat(wb, current_label->value); + buffer_strcat(wb, sq); + } else + buffer_strcat(wb, "null"); + i++; + } + if (!i) { + rows = 0; + buffer_strcat(wb, sq); + buffer_strcat(wb, "no data"); + buffer_strcat(wb, sq); + } + buffer_strcat(wb, "] },\n"); + } } - buffer_sprintf(wb, "],\n" - " %slatest_values%s: [" + buffer_sprintf(wb, " %slatest_values%s: [" , kq, kq); for(c = 0, i = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) { diff --git a/web/api/formatters/json_wrapper.h b/web/api/formatters/json_wrapper.h index b502f02862..d48d5d1ae8 100644 --- a/web/api/formatters/json_wrapper.h +++ b/web/api/formatters/json_wrapper.h @@ -5,7 +5,7 @@ #include "rrd2json.h" -extern void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value, RRDDIM *temp_rd); +extern void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value, RRDDIM *temp_rd, char *chart_key); extern void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value); #endif //NETDATA_API_FORMATTER_JSON_WRAPPER_H diff --git a/web/api/formatters/rrd2json.c b/web/api/formatters/rrd2json.c index 390659061c..9168f76eb1 100644 --- a/web/api/formatters/rrd2json.c +++ b/web/api/formatters/rrd2json.c @@ -188,6 +188,7 @@ int rrdset2anything_api_v1( , uint32_t options , time_t *latest_timestamp , struct context_param *context_param_list + , char *chart_label_key ) { time_t last_accessed_time = now_realtime_sec(); st->last_accessed_time = last_accessed_time; @@ -212,7 +213,7 @@ int rrdset2anything_api_v1( case DATASOURCE_SSV: if(options & RRDR_OPTION_JSON_WRAP) { wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); rrdr2ssv(r, wb, options, "", " ", ""); rrdr_json_wrapper_end(r, wb, format, options, 1); } @@ -225,7 +226,7 @@ int rrdset2anything_api_v1( case DATASOURCE_SSV_COMMA: if(options & RRDR_OPTION_JSON_WRAP) { wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); rrdr2ssv(r, wb, options, "", ",", ""); rrdr_json_wrapper_end(r, wb, format, options, 1); } @@ -238,7 +239,7 @@ int rrdset2anything_api_v1( case DATASOURCE_JS_ARRAY: if(options & RRDR_OPTION_JSON_WRAP) { wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); rrdr2ssv(r, wb, options, "[", ",", "]"); rrdr_json_wrapper_end(r, wb, format, options, 0); } @@ -251,7 +252,7 @@ int rrdset2anything_api_v1( case DATASOURCE_CSV: if(options & RRDR_OPTION_JSON_WRAP) { wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); rrdr2csv(r, wb, format, options, "", ",", "\\n", "", temp_rd); rrdr_json_wrapper_end(r, wb, format, options, 1); } @@ -264,7 +265,7 @@ int rrdset2anything_api_v1( case DATASOURCE_CSV_MARKDOWN: if(options & RRDR_OPTION_JSON_WRAP) { wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); rrdr2csv(r, wb, format, options, "", "|", "\\n", "", temp_rd); rrdr_json_wrapper_end(r, wb, format, options, 1); } @@ -277,7 +278,7 @@ int rrdset2anything_api_v1( case DATASOURCE_CSV_JSON_ARRAY: wb->contenttype = CT_APPLICATION_JSON; if(options & RRDR_OPTION_JSON_WRAP) { - rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); buffer_strcat(wb, "[\n"); rrdr2csv(r, wb, format, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n", temp_rd); buffer_strcat(wb, "\n]"); @@ -294,7 +295,7 @@ int rrdset2anything_api_v1( case DATASOURCE_TSV: if(options & RRDR_OPTION_JSON_WRAP) { wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); rrdr2csv(r, wb, format, options, "", "\t", "\\n", "", temp_rd); rrdr_json_wrapper_end(r, wb, format, options, 1); } @@ -307,7 +308,7 @@ int rrdset2anything_api_v1( case DATASOURCE_HTML: if(options & RRDR_OPTION_JSON_WRAP) { wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 1, temp_rd, chart_label_key); buffer_strcat(wb, "<html>\\n<center>\\n<table border=\\\"0\\\" cellpadding=\\\"5\\\" cellspacing=\\\"5\\\">\\n"); rrdr2csv(r, wb, format, options, "<tr><td>", "</td><td>", "</td></tr>\\n", "", temp_rd); buffer_strcat(wb, "</table>\\n</center>\\n</html>\\n"); @@ -325,7 +326,7 @@ int rrdset2anything_api_v1( wb->contenttype = CT_APPLICATION_X_JAVASCRIPT; if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); rrdr2json(r, wb, options, 1, temp_rd); @@ -337,7 +338,7 @@ int rrdset2anything_api_v1( wb->contenttype = CT_APPLICATION_JSON; if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); rrdr2json(r, wb, options, 1, temp_rd); @@ -348,7 +349,7 @@ int rrdset2anything_api_v1( case DATASOURCE_JSONP: wb->contenttype = CT_APPLICATION_X_JAVASCRIPT; if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); rrdr2json(r, wb, options, 0, temp_rd); @@ -361,7 +362,7 @@ int rrdset2anything_api_v1( wb->contenttype = CT_APPLICATION_JSON; if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd); + rrdr_json_wrapper_begin(r, wb, format, options, 0, temp_rd, chart_label_key); rrdr2json(r, wb, options, 0, temp_rd); diff --git a/web/api/formatters/rrd2json.h b/web/api/formatters/rrd2json.h index fcae720f4f..1f929c4943 100644 --- a/web/api/formatters/rrd2json.h +++ b/web/api/formatters/rrd2json.h @@ -66,6 +66,7 @@ extern int rrdset2anything_api_v1( , uint32_t options , time_t *latest_timestamp , struct context_param *context_param_list + , char *chart_label_key ); extern int rrdset2value_api_v1( diff --git a/web/api/formatters/rrdset2json.c b/web/api/formatters/rrdset2json.c index 251395eff5..5482881e0c 100644 --- a/web/api/formatters/rrdset2json.c +++ b/web/api/formatters/rrdset2json.c @@ -2,6 +2,36 @@ #include "rrdset2json.h" +void chart_labels2json(RRDSET *st, BUFFER *wb, size_t indentation) +{ + char tabs[11]; + struct label_index *labels = &st->state->labels; + + if (indentation > 10) + indentation = 10; + + tabs[0] = '\0'; + while (indentation) { + strcat(tabs, "\t"); + indentation--; + } + + int count = 0; + netdata_rwlock_rdlock(&labels->labels_rwlock); + for (struct label *label = labels->head; label; label = label->next) { + if(count > 0) buffer_strcat(wb, ",\n"); + buffer_strcat(wb, tabs); + + char value[CONFIG_MAX_VALUE * 2 + 1]; + sanitize_json_string(value, label->value, CONFIG_MAX_VALUE * 2); + buffer_sprintf(wb, "\"%s\": \"%s\"", label->key, value); + + count++; + } + buffer_strcat(wb, "\n"); + netdata_rwlock_unlock(&labels->labels_rwlock); +} + // generate JSON for the /api/v1/chart API call void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memory_used, int skip_volatile) { @@ -118,6 +148,10 @@ void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memor "\n\t\t\t}" ); } + buffer_strcat(wb, ",\n\t\t\t\"chart_labels\": {\n"); + chart_labels2json(st, wb, 2); + buffer_strcat(wb, "\t\t\t}\n"); + buffer_sprintf(wb, "\n\t\t}" diff --git a/web/api/web_api_v1.c b/web/api/web_api_v1.c index 71027552f2..cbfeee9a5c 100644 --- a/web/api/web_api_v1.c +++ b/web/api/web_api_v1.c @@ -402,7 +402,8 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c , *after_str = NULL , *group_time_str = NULL , *points_str = NULL - , *context = NULL; + , *context = NULL + , *chart_label_key = NULL; int group = RRDR_GROUPING_AVERAGE; uint32_t format = DATASOURCE_JSON; @@ -422,6 +423,7 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c // they are not null and not empty if(!strcmp(name, "context")) context = value; + else if(!strcmp(name, "chart_label_key")) chart_label_key = value; else if(!strcmp(name, "chart")) chart = value; else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) { if(!dimensions) dimensions = buffer_create(100); @@ -499,9 +501,15 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c if (context && !chart) { RRDSET *st1; uint32_t context_hash = simple_hash(context); + uint32_t key_hash; + + if (chart_label_key) + key_hash = simple_hash(chart_label_key); + rrdhost_rdlock(host); rrdset_foreach_read(st1, host) { - if (st1->hash_context == context_hash && !strcmp(st1->context, context)) + if (st1->hash_context == context_hash && !strcmp(st1->context, context) && + (!chart_label_key || rrdset_contains_label_key(st1, chart_label_key, key_hash))) build_context_param_list(&context_param_list, st1); } rrdhost_unlock(host); @@ -518,8 +526,16 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c if (!st && !context_param_list) { if (context && !chart) { - buffer_strcat(w->response.data, "Context is not found: "); - buffer_strcat_htmlescape(w->response.data, context); + if (!chart_label_key) { + buffer_strcat(w->response.data, "Context is not found: "); + buffer_strcat_htmlescape(w->response.data, context); + } else { + buffer_strcat(w->response.data, "Context: "); + buffer_strcat_htmlescape(w->response.data, context); + buffer_strcat(w->response.data, " or chart label key: "); + buffer_strcat_htmlescape(w->response.data, chart_label_key); + buffer_strcat(w->response.data, " not found"); + } } else { buffer_strcat(w->response.data, "Chart is not found: "); @@ -572,7 +588,7 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c } ret = rrdset2anything_api_v1(st, w->response.data, dimensions, format, points, after, before, group, group_time - , options, &last_timestamp_in_data, context_param_list); + , options, &last_timestamp_in_data, context_param_list, chart_label_key); free_context_param_list(&context_param_list); @@ -869,8 +885,8 @@ inline void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation) { int count = 0; rrdhost_rdlock(host); - netdata_rwlock_rdlock(&host->labels_rwlock); - for (struct label *label = host->labels; label; label = label->next) { + netdata_rwlock_rdlock(&host->labels.labels_rwlock); + for (struct label *label = host->labels.head; label; label = label->next) { if(count > 0) buffer_strcat(wb, ",\n"); buffer_strcat(wb, tabs); @@ -881,7 +897,7 @@ inline void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation) { count++; } buffer_strcat(wb, "\n"); - netdata_rwlock_unlock(&host->labels_rwlock); + netdata_rwlock_unlock(&host->labels.labels_rwlock); rrdhost_unlock(host); }