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);
 }