@cryptotaxi247 / netdata-1 / commits / 73608f86b

stock configs in /usr/lib/netdata (#4283)

* makefiles install configs in /usr/lib/netdata/conf.d; #4182 * stock health config in /usr/lib/netdata/conf.d/health.d * unit test path concatenation * simplified health file management * use stream.conf from stock config if it does not exist in /etc/netdata * indicate loading of user config in function call * load netdata.conf from stock dir if not found in /etc/netdata * added NETDATA_USER_CONFIG_DIR * provide defaults before loading config * charts.d uses stock files * fping now uses the stock config files * tc-qos-helper.sh now uses stock configs * cgroup-name.sh now uses stock configs too * simplified cgroup-name.sh for user and stock config * alarm-notify.sh uses stock configs too * simplified fping plugin configs loading * simplified tc-qos-helper.sh configs loading * added error handling to charts.d.plugin * apps.plugin used stock configs * generalized recursive double-directory configs loading * statsd uses stock configs * node.d.plugin uses stock configs * compile-time decision of netdata default paths for all files * makeself cleans up old stock config files from user configuration directories * fixed makeself typo * netdata-installer.sh removes stock files from user configuration directories * python.d.plugin user/stock configs update * cleanup stock config files from /etc/netdata, only once * python.d.plugin log loaded files * fix permissions of stock config files and provide an "orig" link for quick access * create help link on stock configs migration for static installations * create user config directories * example statsd synthetic charts now state they are examples * updated configs.signatures * spec file * fixes in spec file * fix typo * install netdata after cleaning up stock configs from /etc/netdata * python.d: add cpuidle stock conf

Costa Tsaousis committed Sep 27, 2018 at 20:37 UTC 73608f86b4c10f72fff991fbf9bfa1a802c2d95c
32 files changed +956 -581
.gitignore
+8
@@ -75,6 +75,14 @@ system/netdata.service
75 system/netdata.plist
76 system/netdata-freebsd
77
78 +plugins.d/alarm-notify.sh
79 +plugins.d/cgroup-name.sh
80 +plugins.d/charts.d.plugin
81 +plugins.d/fping.plugin
82 +plugins.d/node.d.plugin
83 +plugins.d/python.d.plugin
84 +plugins.d/tc-qos-helper.sh
85 +
86 # installer generated files
87 netdata-uninstaller.sh
88 netdata-updater.sh
CMakeLists.txt
+1 -1
@@ -250,7 +250,7 @@ set(CGROUP_NETWORK_SOURCE_FILES
250
251 include_directories(AFTER .)
252
253 -add_definitions(-DHAVE_CONFIG_H -DCACHE_DIR="/var/cache/netdata" -DCONFIG_DIR="/etc/netdata" -DLOG_DIR="/var/log/netdata" -DPLUGINS_DIR="/usr/libexec/netdata" -DWEB_DIR="/usr/share/netdata" -DVARLIB_DIR="/var/lib/netdata")
253 +add_definitions(-DHAVE_CONFIG_H -DCACHE_DIR="/var/cache/netdata" -DCONFIG_DIR="/etc/netdata" -DLIBCONFIG_DIR="/usr/lib/netdata/conf.d" -DLOG_DIR="/var/log/netdata" -DPLUGINS_DIR="/usr/libexec/netdata" -DWEB_DIR="/usr/share/netdata" -DVARLIB_DIR="/var/lib/netdata")
254
255 add_executable(netdata ${NETDATA_COMMON_FILES} ${NETDATA_LINUX_FILES})
256 target_link_libraries (netdata m z uuid mnl netfilter_acct ${CMAKE_THREAD_LIBS_INIT})
build/subst.inc
+3
@@ -4,6 +4,9 @@
4 -e 's#[@]sbindir_POST@#$(sbindir)#g' \
5 -e 's#[@]sysconfdir_POST@#$(sysconfdir)#g' \
6 -e 's#[@]pythondir_POST@#$(pythondir)#g' \
7 + -e 's#[@]configdir_POST@#$(configdir)#g' \
8 + -e 's#[@]libconfigdir_POST@#$(libconfigdir)#g' \
9 + -e 's#[@]cachedir_POST@#$(cachedir)#g' \
10 $< > $@.tmp; then \
11 mv "$@.tmp" "$@"; \
12 else \
conf.d/Makefile.am
+7 -7
@@ -4,7 +4,7 @@
4 #
5 MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
6
7 -dist_config_DATA = \
7 +dist_libconfig_DATA = \
8 apps_groups.conf \
9 charts.d.conf \
10 fping.conf \
@@ -15,7 +15,7 @@ dist_config_DATA = \
15 stream.conf \
16 $(NULL)
17
18 -nodeconfigdir=$(configdir)/node.d
18 +nodeconfigdir=$(libconfigdir)/node.d
19 dist_nodeconfig_DATA = \
20 node.d/README.md \
21 node.d/fronius.conf.md \
@@ -25,7 +25,7 @@ dist_nodeconfig_DATA = \
25 node.d/stiebeleltron.conf.md \
26 $(NULL)
27
28 -pythonconfigdir=$(configdir)/python.d
28 +pythonconfigdir=$(libconfigdir)/python.d
29 dist_pythonconfig_DATA = \
30 python.d/apache.conf \
31 python.d/beanstalk.conf \
@@ -34,6 +34,7 @@ dist_pythonconfig_DATA = \
34 python.d/ceph.conf \
35 python.d/chrony.conf \
36 python.d/couchdb.conf \
37 + python.d/cpuidle.conf \
38 python.d/cpufreq.conf \
39 python.d/dns_query_time.conf \
40 python.d/dnsdist.conf \
@@ -88,8 +89,7 @@ dist_pythonconfig_DATA = \
89 python.d/web_log.conf \
90 $(NULL)
91
91 -healthconfigdir=$(configdir)/health.d
92 -
92 +healthconfigdir=$(libconfigdir)/health.d
93 dist_healthconfig_DATA = \
94 health.d/apache.conf \
95 health.d/apcupsd.conf \
@@ -148,7 +148,7 @@ dist_healthconfig_DATA = \
148 health.d/zfs.conf \
149 $(NULL)
150
151 -chartsconfigdir=$(configdir)/charts.d
151 +chartsconfigdir=$(libconfigdir)/charts.d
152 dist_chartsconfig_DATA = \
153 charts.d/apache.conf \
154 charts.d/apcupsd.conf \
@@ -172,7 +172,7 @@ dist_chartsconfig_DATA = \
172 charts.d/squid.conf \
173 $(NULL)
174
175 -statsdconfigdir=$(configdir)/statsd.d
175 +statsdconfigdir=$(libconfigdir)/statsd.d
176 dist_statsdconfig_DATA = \
177 statsd.d/example.conf \
178 $(NULL)
conf.d/python.d/cpuidle.conf new
+40
@@ -0,0 +1,40 @@
1 +# netdata python.d.plugin configuration for cpuidle
2 +#
3 +# This file is in YaML format. Generally the format is:
4 +#
5 +# name: value
6 +#
7 +# There are 2 sections:
8 +# - global variables
9 +# - one or more JOBS
10 +#
11 +# JOBS allow you to collect values from multiple sources.
12 +# Each source will have its own set of charts.
13 +#
14 +# JOB parameters have to be indented (using spaces only, example below).
15 +
16 +# ----------------------------------------------------------------------
17 +# Global Variables
18 +# These variables set the defaults for all JOBs, however each JOB
19 +# may define its own, overriding the defaults.
20 +
21 +# update_every sets the default data collection frequency.
22 +# If unset, the python.d.plugin default is used.
23 +# update_every: 1
24 +
25 +# priority controls the order of charts at the netdata dashboard.
26 +# Lower numbers move the charts towards the top of the page.
27 +# If unset, the default for python.d.plugin is used.
28 +# priority: 60000
29 +
30 +# retries sets the number of retries to be made in case of failures.
31 +# If unset, the default for python.d.plugin is used.
32 +# Attempts to restore the service are made once every update_every
33 +# and only if the module has collected values in the past.
34 +# retries: 60
35 +
36 +# autodetection_retry sets the job re-check interval in seconds.
37 +# The job is not deleted if check fails.
38 +# Attempts to start the job are made once every autodetection_retry.
39 +# This feature is disabled by default.
40 +# autodetection_retry: 0
conf.d/statsd.d/example.conf
+9 -9
@@ -7,10 +7,10 @@
7 # give a name for this app
8 # this controls the main menu on the dashboard
9 # and will be the prefix for all charts of the app
10 - name = myapp
10 + name = myexampleapp
11
12 # match all the metrics of the app
13 - metrics = myapp.*
13 + metrics = myexampleapp.*
14
15 # shall private charts of these metrics be created?
16 private charts = no
@@ -29,10 +29,10 @@
29
30
31 # create a chart
32 -# this is its id - the chart will be named myapp.mychart
33 -[mychart]
32 +# this is its id - the chart will be named myexampleapp.myexamplechart
33 +[myexamplechart]
34 # a name for the chart, similar to the id (2 names for each chart)
35 - name = mychart
35 + name = myexamplechart
36
37 # the chart title
38 title = my chart title
@@ -57,9 +57,9 @@
57 # events = the number of events for this metric
58 # last = the last value collected
59 # all the others are only valid for histograms and timers
60 - dimension = myapp.metric1 avg average 1 1
61 - dimension = myapp.metric1 lower min 1 1
62 - dimension = myapp.metric1 upper max 1 1
63 - dimension = myapp.metric2 other last 1 1
60 + dimension = myexampleapp.metric1 avg average 1 1
61 + dimension = myexampleapp.metric1 lower min 1 1
62 + dimension = myexampleapp.metric1 upper max 1 1
63 + dimension = myexampleapp.metric2 other last 1 1
64
65 # You can add as many charts as needed
configs.signatures
+1
@@ -65,6 +65,7 @@ declare -A configs_signatures=(
65 ['111ead4b350593dd69b6f7ac0307b49b']='python.d/httpcheck.conf'
66 ['12a4c7803ae79506a14ea784fea60dce']='health.d/net.conf'
67 ['12d27b9f4d1696c2d49a77ed71d68e6f']='python.d/w1sensor.conf'
68 + ['12e57bea1127933a4fe49ce2e9674f4d']='statsd.d/example.conf'
69 ['13141998a5d71308d9c119834c27bfd3']='python.d.conf'
70 ['13ccf65fd879795f0fcea89ade27c2d0']='health.d/swap.conf'
71 ['13e861a3d2f3075de883994ab54df658']='health.d/megacli.conf'
configure.ac
+1
@@ -493,6 +493,7 @@ AC_SUBST([chartsdir], ["\$(libexecdir)/netdata/charts.d"])
493 AC_SUBST([nodedir], ["\$(libexecdir)/netdata/node.d"])
494 AC_SUBST([pythondir], ["\$(libexecdir)/netdata/python.d"])
495 AC_SUBST([configdir], ["\$(sysconfdir)/netdata"])
496 +AC_SUBST([libconfigdir], ["\$(libdir)/netdata/conf.d"])
497 AC_SUBST([logdir], ["\$(localstatedir)/log/netdata"])
498 AC_SUBST([pluginsdir], ["\$(libexecdir)/netdata/plugins.d"])
499 AC_SUBST([webdir])
makeself/install-or-update.sh
+52 -45
@@ -24,62 +24,49 @@ do
24 shift
25 done
26
27 +deleted_stock_configs=0
28 +if [ ! -f "etc/netdata/.installer-cleanup-of-stock-configs-done" ]
29 +then
30
28 -# -----------------------------------------------------------------------------
29 -progress "Checking new configuration files"
30 -
31 -declare -A configs_signatures=()
32 -. system/configs.signatures
33 -
34 -if [ ! -d etc/netdata ]
35 - then
36 - run mkdir -p etc/netdata
37 -fi
38 -
39 -md5sum="$(which md5sum 2>/dev/null || command -v md5sum 2>/dev/null || command -v md5 2>/dev/null)"
40 -for x in $(find etc.new -type f)
41 -do
42 - # find it relative filename
43 - f="${x/etc.new\/netdata\//}"
44 - t="${x/etc.new\//etc\/}"
45 - d=$(dirname "${t}")
31 + # -----------------------------------------------------------------------------
32 + progress "Deleting stock configuration files from user configuration directory"
33
47 - #echo >&2 "x: ${x}"
48 - #echo >&2 "t: ${t}"
49 - #echo >&2 "d: ${d}"
34 + declare -A configs_signatures=()
35 + source "system/configs.signatures"
36
51 - if [ ! -d "${d}" ]
37 + if [ ! -d etc/netdata ]
38 then
53 - run mkdir -p "${d}"
39 + run mkdir -p etc/netdata
40 fi
41
56 - if [ ! -f "${t}" ]
57 - then
58 - run cp "${x}" "${t}"
59 - continue
60 - fi
42 + md5sum="$(which md5sum 2>/dev/null || command -v md5sum 2>/dev/null || command -v md5 2>/dev/null)"
43 + for x in $(find etc -type f)
44 + do
45 + # find it relative filename
46 + f="${x/etc\/netdata\//}"
47
62 - if [ ! -z "${md5sum}" ]
63 - then
64 - # find the checksum of the existing file
65 - md5="$(cat "${t}" | ${md5sum} | cut -d ' ' -f 1)"
66 - #echo >&2 "md5: ${md5}"
48 + # find the stock filename
49 + t="${f/.conf.old/.conf}"
50 + t="${t/.conf.orig/.conf}"
51
68 - # check if it matches
69 - if [ "${configs_signatures[${md5}]}" = "${f}" ]
52 + if [ ! -z "${md5sum}" ]
53 then
71 - run cp "${x}" "${t}"
54 + # find the checksum of the existing file
55 + md5="$( ${md5sum} <"${x}" | cut -d ' ' -f 1)"
56 + #echo >&2 "md5: ${md5}"
57 +
58 + # check if it matches
59 + if [ "${configs_signatures[${md5}]}" = "${t}" ]
60 + then
61 + # it matches the default
62 + run rm -f "${x}"
63 + deleted_stock_configs=$(( deleted_stock_configs + 1 ))
64 + fi
65 fi
73 - fi
74 -
75 - if ! [[ "${x}" =~ .*\.orig ]]
76 - then
77 - run mv "${x}" "${t}.orig"
78 - fi
79 -done
80 -
81 -run rm -rf etc.new
66 + done
67
68 + touch "etc/netdata/.installer-cleanup-of-stock-configs-done"
69 +fi
70
71 # -----------------------------------------------------------------------------
72 progress "Add user netdata to required user groups"
@@ -166,6 +153,26 @@ dir_should_be_link . var/lib/netdata netdata-dbs
153 dir_should_be_link . var/cache/netdata netdata-metrics
154 dir_should_be_link . var/log/netdata netdata-logs
155
156 +dir_should_be_link etc/netdata ../../usr/lib/netdata/conf.d orig
157 +
158 +if [ ${deleted_stock_configs} -gt 0 ]
159 +then
160 + dir_should_be_link etc/netdata ../../usr/lib/netdata/conf.d "000.-.USE.THE.orig.LINK.TO.COPY.AND.EDIT.STOCK.CONFIG.FILES"
161 +fi
162 +
163 +
164 +# -----------------------------------------------------------------------------
165 +
166 +progress "create user config directories"
167 +
168 +for x in "python.d" "charts.d" "node.d" "health.d" "statsd.d"
169 +do
170 + if [ ! -d "etc/netdata/${x}" ]
171 + then
172 + run mkdir -p "etc/netdata/${x}" || exit 1
173 + fi
174 +done
175 +
176
177 # -----------------------------------------------------------------------------
178 progress "fix permissions"
makeself/jobs/70-netdata-git.install.sh
-7
@@ -13,13 +13,6 @@ else
13 # export CFLAGS="-static -O1 -ggdb -Wall -Wextra -Wformat-signedness"
14 fi
15
16 -if [ ! -z "${NETDATA_INSTALL_PATH}" -a -d "${NETDATA_INSTALL_PATH}/etc" ]
17 - then
18 - # make sure we don't have an old etc path, so that the installer
19 - # will install all files without examining changes
20 - run mv "${NETDATA_INSTALL_PATH}/etc" "${NETDATA_INSTALL_PATH}/etc.new"
21 -fi
22 -
16 run ./netdata-installer.sh --install "${NETDATA_INSTALL_PARENT}" \
17 --dont-wait \
18 --dont-start-it \
makeself/jobs/99-makeself.install.sh
-21
@@ -72,27 +72,6 @@ EOF
72 run chmod 755 "${NETDATA_INSTALL_PATH}/bin/netdata"
73
74
75 -# -----------------------------------------------------------------------------
76 -# move etc to protect the destination when unpacked
77 -
78 -if [ ! -z "${NETDATA_INSTALL_PATH}" -a -d "${NETDATA_INSTALL_PATH}/etc" ]
79 - then
80 - if [ -d "${NETDATA_INSTALL_PATH}/etc.new" ]
81 - then
82 - run rm -rf "${NETDATA_INSTALL_PATH}/etc.new" || exit 1
83 - fi
84 -
85 - run mv "${NETDATA_INSTALL_PATH}/etc" \
86 - "${NETDATA_INSTALL_PATH}/etc.new" || exit 1
87 -
88 - if [ -f "${NETDATA_INSTALL_PATH}/etc.new/netdata/netdata.conf" ]
89 - then
90 - # delete the generated netdata.conf, so that the static installer will generate a new one
91 - run rm "${NETDATA_INSTALL_PATH}/etc.new/netdata/netdata.conf"
92 - fi
93 -fi
94 -
95 -
75 # -----------------------------------------------------------------------------
76 # remove the links to allow untaring the archive
77
netdata-installer.sh
+93 -88
@@ -492,6 +492,7 @@ progress "Cleanup compilation directory"
492
493 [ -f src/netdata ] && run make clean
494
495 +
496 # -----------------------------------------------------------------------------
497 progress "Compile netdata"
498
@@ -543,79 +544,76 @@ if [ -d "${NETDATA_PREFIX}/etc/netdata" ]
544 fi
545
546 # -----------------------------------------------------------------------------
546 -progress "Backup existing netdata configuration before installing it"
547 -
548 -if [ "${BASH_VERSINFO[0]}" -ge "4" ]
547 +deleted_stock_configs=0
548 +if [ ! -f "${NETDATA_PREFIX}/etc/netdata/.installer-cleanup-of-stock-configs-done" ]
549 then
550 - declare -A configs_signatures=()
551 - if [ -f "configs.signatures" ]
552 - then
553 - source "configs.signatures" || echo >&2 "ERROR: Failed to load configs.signatures !"
554 - fi
555 -fi
550
557 -config_signature_matches() {
558 - local md5="${1}" file="${2}"
551 + progress "Backup existing netdata configuration before installing it"
552
553 if [ "${BASH_VERSINFO[0]}" -ge "4" ]
561 - then
562 - [ "${configs_signatures[${md5}]}" = "${file}" ] && return 0
563 - return 1
554 + then
555 + declare -A configs_signatures=()
556 + if [ -f "configs.signatures" ]
557 + then
558 + source "configs.signatures" || echo >&2 "ERROR: Failed to load configs.signatures !"
559 + fi
560 fi
561
566 - if [ -f "configs.signatures" ]
567 - then
568 - grep "\['${md5}'\]='${file}'" "configs.signatures" >/dev/null
569 - return $?
570 - fi
562 + config_signature_matches() {
563 + local md5="${1}" file="${2}"
564
572 - return 1
573 -}
565 + if [ "${BASH_VERSINFO[0]}" -ge "4" ]
566 + then
567 + [ "${configs_signatures[${md5}]}" = "${file}" ] && return 0
568 + return 1
569 + fi
570
575 -# backup user configurations
576 -installer_backup_suffix="${PID}.${RANDOM}"
577 -for x in $(find -L "${NETDATA_PREFIX}/etc/netdata" -name '*.conf' -type f)
578 -do
579 - if [ -f "${x}" ]
580 - then
581 - # make a backup of the configuration file
582 - cp -p "${x}" "${x}.old"
571 + if [ -f "configs.signatures" ]
572 + then
573 + grep "\['${md5}'\]='${file}'" "configs.signatures" >/dev/null
574 + return $?
575 + fi
576
584 - if [ -z "${md5sum}" -o ! -x "${md5sum}" ]
577 + return 1
578 + }
579 +
580 + # clean up stock config files from the user configuration directory
581 + for x in $(find -L "${NETDATA_PREFIX}/etc/netdata" -type f)
582 + do
583 + if [ -f "${x}" ]
584 then
586 - # we don't have md5sum - keep it
587 - echo >&2 "File '${TPUT_CYAN}${x}${TPUT_RESET}' ${TPUT_RET}is not known to distribution${TPUT_RESET}. Keeping it."
588 - run cp -a "${x}" "${x}.installer_backup.${installer_backup_suffix}"
589 - else
585 # find it relative filename
591 - f="${x/*\/etc\/netdata\//}"
592 -
593 - # find its checksum
594 - md5="$(cat "${x}" | ${md5sum} | cut -d ' ' -f 1)"
586 + f="${x/${NETDATA_PREFIX}\/etc\/netdata\//}"
587
596 - # copy the original
597 - if [ -f "conf.d/${f}" ]
598 - then
599 - cp "conf.d/${f}" "${x}.orig"
600 - fi
588 + # find the stock filename
589 + t="${f/.conf.installer_backup.*/.conf}"
590 + t="${t/.conf.old/.conf}"
591 + t="${t/.conf.orig/.conf}"
592
602 - if config_signature_matches "${md5}" "${f}"
593 + if [ -z "${md5sum}" -o ! -x "${md5sum}" ]
594 then
604 - # it is a stock version - don't keep it
605 - echo >&2 "File '${TPUT_CYAN}${x}${TPUT_RESET}' is stock version."
595 + # we don't have md5sum - keep it
596 + echo >&2 "File '${TPUT_CYAN}${x}${TPUT_RESET}' ${TPUT_RET}is not known to distribution${TPUT_RESET}. Keeping it."
597 else
607 - # edited by user - keep it
608 - echo >&2 "File '${TPUT_CYAN}${x}${TPUT_RESET}' ${TPUT_RED} has been edited by user${TPUT_RESET}. Keeping it."
609 - run cp -a "${x}" "${x}.installer_backup.${installer_backup_suffix}"
598 + # find its checksum
599 + md5="$(${md5sum} <"${x}" | cut -d ' ' -f 1)"
600 +
601 + if config_signature_matches "${md5}" "${t}"
602 + then
603 + # it is a stock version - remove it
604 + echo >&2 "File '${TPUT_CYAN}${x}${TPUT_RESET}' is stock version of '${t}'."
605 + run rm -f "${x}"
606 + deleted_stock_configs=$(( deleted_stock_configs + 1 ))
607 + else
608 + # edited by user - keep it
609 + echo >&2 "File '${TPUT_CYAN}${x}${TPUT_RESET}' ${TPUT_RED} does not match stock of '${t}'${TPUT_RESET}. Keeping it."
610 + fi
611 fi
612 fi
613 + done
614
613 - elif [ -f "${x}.installer_backup.${installer_backup_suffix}" ]
614 - then
615 - rm -f "${x}.installer_backup.${installer_backup_suffix}"
616 - fi
617 -done
618 -
615 + touch "${NETDATA_PREFIX}/etc/netdata/.installer-cleanup-of-stock-configs-done"
616 +fi
617
618 # -----------------------------------------------------------------------------
619 progress "Install netdata"
@@ -623,19 +621,6 @@ progress "Install netdata"
621 run make install || exit 1
622
623
626 -# -----------------------------------------------------------------------------
627 -progress "Restore user edited netdata configuration files"
628 -
629 -for x in $(find -L "${NETDATA_PREFIX}/etc/netdata/" -name '*.conf' -type f)
630 -do
631 - if [ -f "${x}.installer_backup.${installer_backup_suffix}" ]
632 - then
633 - run cp -a "${x}.installer_backup.${installer_backup_suffix}" "${x}" && \
634 - run rm -f "${x}.installer_backup.${installer_backup_suffix}"
635 - fi
636 -done
637 -
638 -
624 # -----------------------------------------------------------------------------
625 progress "Fix generated files permissions"
626
@@ -709,28 +694,30 @@ NETDATA_LIB_DIR="$( config_option "global" "lib directory" "${NETDATA_PREFIX}/va
694 NETDATA_CACHE_DIR="$( config_option "global" "cache directory" "${NETDATA_PREFIX}/var/cache/netdata" )"
695 NETDATA_WEB_DIR="$( config_option "global" "web files directory" "${NETDATA_PREFIX}/usr/share/netdata/web" )"
696 NETDATA_LOG_DIR="$( config_option "global" "log directory" "${NETDATA_PREFIX}/var/log/netdata" )"
712 -NETDATA_CONF_DIR="$( config_option "global" "config directory" "${NETDATA_PREFIX}/etc/netdata" )"
697 +NETDATA_USER_CONFIG_DIR="$( config_option "global" "config directory" "${NETDATA_PREFIX}/etc/netdata" )"
698 +NETDATA_STOCK_CONFIG_DIR="$( config_option "global" "stock config directory" "${NETDATA_PREFIX}/usr/lib/netdata/conf.d" )"
699 NETDATA_RUN_DIR="${NETDATA_PREFIX}/var/run"
700
701 cat <<OPTIONSEOF
702
703 Permissions
718 - - netdata user : ${NETDATA_USER}
719 - - netdata group : ${NETDATA_GROUP}
720 - - web files user : ${NETDATA_WEB_USER}
721 - - web files group : ${NETDATA_WEB_GROUP}
722 - - root user : ${ROOT_USER}
704 + - netdata user : ${NETDATA_USER}
705 + - netdata group : ${NETDATA_GROUP}
706 + - web files user : ${NETDATA_WEB_USER}
707 + - web files group : ${NETDATA_WEB_GROUP}
708 + - root user : ${ROOT_USER}
709
710 Directories
725 - - netdata conf dir : ${NETDATA_CONF_DIR}
726 - - netdata log dir : ${NETDATA_LOG_DIR}
727 - - netdata run dir : ${NETDATA_RUN_DIR}
728 - - netdata lib dir : ${NETDATA_LIB_DIR}
729 - - netdata web dir : ${NETDATA_WEB_DIR}
730 - - netdata cache dir: ${NETDATA_CACHE_DIR}
711 + - netdata user config dir : ${NETDATA_USER_CONFIG_DIR}
712 + - netdata stock config dir : ${NETDATA_STOCK_CONFIG_DIR}
713 + - netdata log dir : ${NETDATA_LOG_DIR}
714 + - netdata run dir : ${NETDATA_RUN_DIR}
715 + - netdata lib dir : ${NETDATA_LIB_DIR}
716 + - netdata web dir : ${NETDATA_WEB_DIR}
717 + - netdata cache dir : ${NETDATA_CACHE_DIR}
718
719 Other
733 - - netdata port : ${NETDATA_PORT}
720 + - netdata port : ${NETDATA_PORT}
721
722 OPTIONSEOF
723
@@ -745,17 +732,35 @@ fi
732
733 # --- conf dir ----
734
748 -for x in "python.d" "charts.d" "node.d"
735 +for x in "python.d" "charts.d" "node.d" "health.d" "statsd.d"
736 do
750 - if [ ! -d "${NETDATA_CONF_DIR}/${x}" ]
737 + if [ ! -d "${NETDATA_USER_CONFIG_DIR}/${x}" ]
738 then
752 - echo >&2 "Creating directory '${NETDATA_CONF_DIR}/${x}'"
753 - run mkdir -p "${NETDATA_CONF_DIR}/${x}" || exit 1
739 + echo >&2 "Creating directory '${NETDATA_USER_CONFIG_DIR}/${x}'"
740 + run mkdir -p "${NETDATA_USER_CONFIG_DIR}/${x}" || exit 1
741 + fi
742 +done
743 +run chown -R "${ROOT_USER}:${NETDATA_GROUP}" "${NETDATA_USER_CONFIG_DIR}"
744 +run find "${NETDATA_USER_CONFIG_DIR}" -type f -exec chmod 0640 {} \;
745 +run find "${NETDATA_USER_CONFIG_DIR}" -type d -exec chmod 0755 {} \;
746 +
747 +# --- stock conf dir ----
748 +
749 +[ ! -d "${NETDATA_STOCK_CONFIG_DIR}" ] && mkdir -p "${NETDATA_STOCK_CONFIG_DIR}"
750 +
751 +helplink="000.-.USE.THE.orig.LINK.TO.COPY.AND.EDIT.STOCK.CONFIG.FILES"
752 +[ ${deleted_stock_configs} -eq 0 ] && helplink=""
753 +for link in "orig" "${helplink}"
754 +do
755 + if [ ! -z "${link}" ]
756 + then
757 + [ -L "${NETDATA_USER_CONFIG_DIR}/${link}" ] && run rm -f "${NETDATA_USER_CONFIG_DIR}/${link}"
758 + run ln -s "${NETDATA_STOCK_CONFIG_DIR}" "${NETDATA_USER_CONFIG_DIR}/${link}"
759 fi
760 done
756 -run chown -R "${ROOT_USER}:${NETDATA_GROUP}" "${NETDATA_CONF_DIR}"
757 -run find "${NETDATA_CONF_DIR}" -type f -exec chmod 0640 {} \;
758 -run find "${NETDATA_CONF_DIR}" -type d -exec chmod 0755 {} \;
761 +run chown -R "${ROOT_USER}:${NETDATA_GROUP}" "${NETDATA_STOCK_CONFIG_DIR}"
762 +run find "${NETDATA_STOCK_CONFIG_DIR}" -type f -exec chmod 0640 {} \;
763 +run find "${NETDATA_STOCK_CONFIG_DIR}" -type d -exec chmod 0755 {} \;
764
765 # --- web dir ----
766
netdata.spec.in
+16 -8
@@ -181,17 +181,18 @@ rm -rf "${RPM_BUILD_ROOT}"
181 %defattr(-,root,root)
182
183 %dir %{_sysconfdir}/%{name}
184 +%dir %{_libdir}/%{name}
185
185 -%config(noreplace) %{_sysconfdir}/%{name}/*.conf
186 -%config(noreplace) %{_sysconfdir}/%{name}/charts.d/*.conf
187 -%config(noreplace) %{_sysconfdir}/%{name}/health.d/*.conf
188 -#%%config(noreplace) %{_sysconfdir}/%{name}/node.d/*.conf
189 -%config(noreplace) %{_sysconfdir}/%{name}/python.d/*.conf
190 -%config(noreplace) %{_sysconfdir}/%{name}/statsd.d/*.conf
191 -%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
186 +%config %{_libdir}/%{name}/*.conf
187 +%config %{_libdir}/%{name}/charts.d/*.conf
188 +%config %{_libdir}/%{name}/health.d/*.conf
189 +%config %{_libdir}/%{name}/node.d/*.conf
190 +%config %{_libdir}/%{name}/python.d/*.conf
191 +%config %{_libdir}/%{name}/statsd.d/*.conf
192 +%config %{_sysconfdir}/logrotate.d/%{name}
193
194 # To be eventually moved to %%_defaultdocdir
194 -%{_sysconfdir}/%{name}/node.d/*.md
195 +%{_libdir}/%{name}/node.d/*.md
196 %{_libexecdir}/%{name}
197 %{_sbindir}/%{name}
198
@@ -215,9 +216,16 @@ rm -rf "${RPM_BUILD_ROOT}"
216
217 %dir %{_datadir}/%{name}
218 %dir %{_sysconfdir}/%{name}/health.d
219 +%dir %{_libdir}/%{name}/health.d
220 %dir %{_sysconfdir}/%{name}/python.d
221 +%dir %{_libdir}/%{name}/python.d
222 %dir %{_sysconfdir}/%{name}/charts.d
223 +%dir %{_libdir}/%{name}/charts.d
224 %dir %{_sysconfdir}/%{name}/node.d
225 +%dir %{_libdir}/%{name}/node.d
226 +%dir %{_sysconfdir}/%{name}/statsd.d
227 +%dir %{_libdir}/%{name}/statsd.d
228 +
229
230 %if %{with systemd}
231 %{_unitdir}/netdata.service
plugins.d/Makefile.am
+23
@@ -3,6 +3,19 @@
3 # SPDX-License-Identifier: GPL-3.0+
4 #
5 MAINTAINERCLEANFILES= $(srcdir)/Makefile.in
6 +CLEANFILES = \
7 + alarm-notify.sh \
8 + cgroup-name.sh \
9 + charts.d.plugin \
10 + fping.plugin \
11 + node.d.plugin \
12 + python.d.plugin \
13 + tc-qos-helper.sh \
14 + $(NULL)
15 +
16 +include $(top_srcdir)/build/subst.inc
17 +
18 +SUFFIXES = .in
19
20 dist_plugins_DATA = \
21 README.md \
@@ -22,3 +35,13 @@ dist_plugins_SCRIPTS = \
35 tc-qos-helper.sh \
36 loopsleepms.sh.inc \
37 $(NULL)
38 +
39 +dist_noinst_DATA = \
40 + alarm-notify.sh.in \
41 + cgroup-name.sh.in \
42 + charts.d.plugin.in \
43 + fping.plugin.in \
44 + node.d.plugin.in \
45 + python.d.plugin.in \
46 + tc-qos-helper.sh.in \
47 + $(NULL)
plugins.d/alarm-notify.sh.in renamed
+18 -11
@@ -159,9 +159,10 @@ custom_sender() {
159 # -----------------------------------------------------------------------------
160 # defaults to allow running this script by hand
161
162 -[ -z "${NETDATA_CONFIG_DIR}" ] && NETDATA_CONFIG_DIR="$(dirname "${0}")/../../../../etc/netdata"
163 -[ -z "${NETDATA_CACHE_DIR}" ] && NETDATA_CACHE_DIR="$(dirname "${0}")/../../../../var/cache/netdata"
164 -[ -z "${NETDATA_REGISTRY_URL}" ] && NETDATA_REGISTRY_URL="https://registry.my-netdata.io"
162 +[ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
163 +[ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
164 +[ -z "${NETDATA_CACHE_DIR}" ] && NETDATA_CACHE_DIR="@cachedir_POST@"
165 +[ -z "${NETDATA_REGISTRY_URL}" ] && NETDATA_REGISTRY_URL="https://registry.my-netdata.io"
166
167 # -----------------------------------------------------------------------------
168 # parse command line parameters
@@ -362,14 +363,20 @@ DEFAULT_RECIPIENT_IRC=
363 IRC_NETWORK=
364 declare -A role_recipients_irc=()
365
365 -# load the user configuration
366 -# this will overwrite the variables above
367 -if [ -f "${NETDATA_CONFIG_DIR}/health_alarm_notify.conf" ]
368 - then
369 - source "${NETDATA_CONFIG_DIR}/health_alarm_notify.conf"
370 -else
371 - error "Cannot find file ${NETDATA_CONFIG_DIR}/health_alarm_notify.conf. Using internal defaults."
372 -fi
366 +# load the stock and user configuration files
367 +# these will overwrite the variables above
368 +
369 +for CONFIG in "${NETDATA_STOCK_CONFIG_DIR}/health_alarm_notify.conf" "${NETDATA_USER_CONFIG_DIR}/health_alarm_notify.conf"
370 +do
371 + if [ -f "${CONFIG}" ]
372 + then
373 + debug "Loading config file '${CONFIG}'..."
374 + source "${CONFIG}"
375 + [ $? -ne 0 ] && error "Failed to load config file '${CONFIG}'."
376 + else
377 + warning "Cannot find file '${CONFIG}'."
378 + fi
379 +done
380
381 # If we didn't autodetect the character set for e-mail and it wasn't
382 # set by the user, we need to set it to a reasonable default. UTF-8
plugins.d/cgroup-name.sh.in renamed
+16 -10
@@ -51,9 +51,10 @@ debug() {
51
52 # -----------------------------------------------------------------------------
53
54 -[ -z "${NETDATA_CONFIG_DIR}" ] && NETDATA_CONFIG_DIR="$(dirname "${0}")/../../../../etc/netdata"
54 +[ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
55 +[ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
56 +
57 DOCKER_HOST="${DOCKER_HOST:=/var/run/docker.sock}"
56 -CONFIG="${NETDATA_CONFIG_DIR}/cgroups-names.conf"
58 CGROUP="${1}"
59 NAME=
60
@@ -64,16 +65,21 @@ if [ -z "${CGROUP}" ]
65 fatal "called without a cgroup name. Nothing to do."
66 fi
67
67 -if [ -f "${CONFIG}" ]
68 +for CONFIG in "${NETDATA_USER_CONFIG_DIR}/cgroups-names.conf" "${NETDATA_STOCK_CONFIG_DIR}/cgroups-names.conf"
69 +do
70 + if [ -f "${CONFIG}" ]
71 then
69 - NAME="$(grep "^${CGROUP} " "${CONFIG}" | sed "s/[[:space:]]\+/ /g" | cut -d ' ' -f 2)"
70 - if [ -z "${NAME}" ]
71 - then
72 - info "cannot find cgroup '${CGROUP}' in '${CONFIG}'."
72 + NAME="$(grep "^${CGROUP} " "${CONFIG}" | sed "s/[[:space:]]\+/ /g" | cut -d ' ' -f 2)"
73 + if [ -z "${NAME}" ]
74 + then
75 + info "cannot find cgroup '${CGROUP}' in '${CONFIG}'."
76 + else
77 + break
78 + fi
79 + #else
80 + # info "configuration file '${CONFIG}' is not available."
81 fi
74 -#else
75 -# info "configuration file '${CONFIG}' is not available."
76 -fi
82 +done
83
84 function docker_get_name_classic {
85 local id="${1}"
plugins.d/charts.d.plugin.in renamed
+56 -27
@@ -117,14 +117,15 @@ info "started from '$PROGRAM_FILE' with options: $*"
117 # netdata exposes a few environment variables for us
118
119 [ -z "${NETDATA_PLUGINS_DIR}" ] && NETDATA_PLUGINS_DIR="$(dirname "${0}")"
120 -[ -z "${NETDATA_CONFIG_DIR}" ] && NETDATA_CONFIG_DIR="$(dirname "${0}")/../../../../etc/netdata"
120 +[ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
121 +[ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
122
123 pluginsd="${NETDATA_PLUGINS_DIR}"
123 -confd="${NETDATA_CONFIG_DIR}"
124 +stockconfd="${NETDATA_STOCK_CONFIG_DIR}/${PROGRAM_NAME}"
125 +userconfd="${NETDATA_USER_CONFIG_DIR}/${PROGRAM_NAME}"
126 +olduserconfd="${NETDATA_USER_CONFIG_DIR}"
127 chartsd="$pluginsd/../charts.d"
128
126 -myconfig="$confd/$PROGRAM_NAME.conf"
127 -
129 minimum_update_frequency="${NETDATA_UPDATE_EVERY-1}"
130 update_every=${minimum_update_frequency} # this will be overwritten by the command line
131
@@ -231,22 +232,33 @@ mysleep="sleep"
232 # if found and included, this file overwrites loopsleepms()
233 # and current_time_ms() with a high resolution timer function
234 # for precise looping.
234 -. "$pluginsd/loopsleepms.sh.inc"
235 +source "$pluginsd/loopsleepms.sh.inc"
236 +[ $? -ne 0 ] && error "Failed to load '$pluginsd/loopsleepms.sh.inc'."
237
238 # -----------------------------------------------------------------------------
239 # load my configuration
240
239 -if [ -f "$myconfig" ]
241 +for myconfig in "${NETDATA_STOCK_CONFIG_DIR}/${PROGRAM_NAME}.conf" "${NETDATA_USER_CONFIG_DIR}/${PROGRAM_NAME}.conf"
242 +do
243 + if [ -f "$myconfig" ]
244 then
241 - . "$myconfig"
242 - [ $? -ne 0 ] && fatal "cannot load $myconfig"
245 + source "$myconfig"
246 + if [ $? -ne 0 ]
247 + then
248 + error "Config file '$myconfig' loaded with errors."
249 + else
250 + info "Configuration file '$myconfig' loaded."
251 + fi
252 + else
253 + warning "Configuration file '$myconfig' not found."
254 + fi
255 +done
256 +
257 +# make sure time_divisor is right
258 +time_divisor=$((time_divisor))
259 +[ $time_divisor -lt 10 ] && time_divisor=10
260 +[ $time_divisor -gt 100 ] && time_divisor=100
261
244 - time_divisor=$((time_divisor))
245 - [ $time_divisor -lt 10 ] && time_divisor=10
246 - [ $time_divisor -gt 100 ] && time_divisor=100
247 -else
248 - info "configuration file '$myconfig' not found. Using defaults."
249 -fi
262
263 # we check for the timeout command, after we load our
264 # configuration, so that the user may overwrite the
@@ -412,7 +424,7 @@ all_enabled_charts() {
424
425 if [ ! "${enabled}" = "${required}" ]
426 then
415 - info "is disabled. Add a line with $chart=$required in $myconfig to enable it (or remove the line that disables it)."
427 + info "is disabled. Add a line with $chart=$required in '${NETDATA_USER_CONFIG_DIR}/${PROGRAM_NAME}.conf' to enable it (or remove the line that disables it)."
428 else
429 debug "is enabled for auto-detection."
430 local charts="$charts $chart"
@@ -448,18 +460,18 @@ all_enabled_charts() {
460 fi
461
462 # check its config
451 - #if [ -f "$confd/$chart.conf" ]
463 + #if [ -f "$userconfd/$chart.conf" ]
464 #then
453 - # if [ ! -z "$( cat "$confd/$chart.conf" | sed "s/^ \+//g" | grep -v "^$" | grep -v "^#" | grep -v "^$chart$charts_undescore" )" ]
465 + # if [ ! -z "$( cat "$userconfd/$chart.conf" | sed "s/^ \+//g" | grep -v "^$" | grep -v "^#" | grep -v "^$chart$charts_undescore" )" ]
466 # then
455 - # error "module's $chart config $confd/$chart.conf should only have lines starting with $chart$charts_undescore . Disabling it."
467 + # error "module's $chart config $userconfd/$chart.conf should only have lines starting with $chart$charts_undescore . Disabling it."
468 # continue
469 # fi
470 #fi
471
472 #if [ $dryrunner -eq 1 ]
473 # then
462 - # "$pluginsd/charts.d.dryrun-helper.sh" "$chart" "$chartsd/$chart.chart.sh" "$confd/$chart.conf" >/dev/null
474 + # "$pluginsd/charts.d.dryrun-helper.sh" "$chart" "$chartsd/$chart.chart.sh" "$userconfd/$chart.conf" >/dev/null
475 # if [ $? -ne 0 ]
476 # then
477 # error "module's $chart did not pass the dry run check. This means it uses global variables not starting with $chart. Disabling it."
@@ -487,18 +499,35 @@ do
499
500 debug "loading module: '$chartsd/$chart.chart.sh'"
501
490 - . "$chartsd/$chart.chart.sh"
502 + source "$chartsd/$chart.chart.sh"
503 + [ $? -ne 0 ] && warning "Module '$chartsd/$chart.chart.sh' loaded with errors."
504
492 - if [ -f "$confd/$PROGRAM_NAME/$chart.conf" ]
505 + # first load the stock config
506 + if [ -f "$stockconfd/$chart.conf" ]
507 then
494 - debug "loading module configuration: '$confd/$PROGRAM_NAME/$chart.conf'"
495 - . "$confd/$PROGRAM_NAME/$chart.conf"
496 - elif [ -f "$confd/$chart.conf" ]
508 + debug "loading module configuration: '$stockconfd/$chart.conf'"
509 + source "$stockconfd/$chart.conf"
510 + [ $? -ne 0 ] && warning "Config file '$stockconfd/$chart.conf' loaded with errors."
511 + else
512 + debug "not found module configuration: '$stockconfd/$chart.conf'"
513 + fi
514 +
515 + # then load the user config (it overwrites the stock)
516 + if [ -f "$userconfd/$chart.conf" ]
517 then
498 - debug "loading module configuration: '$confd/$chart.conf'"
499 - . "$confd/$chart.conf"
518 + debug "loading module configuration: '$userconfd/$chart.conf'"
519 + source "$userconfd/$chart.conf"
520 + [ $? -ne 0 ] && warning "Config file '$userconfd/$chart.conf' loaded with errors."
521 else
501 - warning "configuration file '$confd/$PROGRAM_NAME/$chart.conf' not found. Using defaults."
522 + debug "not found module configuration: '$userconfd/$chart.conf'"
523 +
524 + if [ -f "$olduserconfd/$chart.conf" ]
525 + then
526 + # support for very old netdata that had the charts.d module configs in /etc/netdata
527 + info "loading module configuration from obsolete location: '$olduserconfd/$chart.conf'"
528 + source "$olduserconfd/$chart.conf"
529 + [ $? -ne 0 ] && warning "Config file '$olduserconfd/$chart.conf' loaded with errors."
530 + fi
531 fi
532
533 eval "dt=\$$chart$suffix_update_every"
plugins.d/fping.plugin.in renamed
+22 -11
@@ -130,7 +130,8 @@ update_every="${1-1}"
130
131 # the netdata configuration directory
132 # passed by netdata as an environment variable
133 -[ -z "${NETDATA_CONFIG_DIR}" ] && NETDATA_CONFIG_DIR="$(dirname "${0}")/../../../../etc/netdata"
133 +[ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
134 +[ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
135
136 # -----------------------------------------------------------------------------
137 # configuration options
@@ -153,23 +154,33 @@ ping_every="$((update_every * 1000 / 5))"
154 fping_opts="-R -b 56 -i 1 -r 0 -t 5000"
155
156 # -----------------------------------------------------------------------------
156 -# load the configuration file
157 +# load the configuration files
158
158 -if [ ! -f "${NETDATA_CONFIG_DIR}/${plugin}.conf" ]
159 +for CONFIG in "${NETDATA_STOCK_CONFIG_DIR}/${plugin}.conf" "${NETDATA_USER_CONFIG_DIR}/${plugin}.conf"
160 +do
161 + if [ -f "${CONFIG}" ]
162 + then
163 + info "Loading config file '${CONFIG}'..."
164 + source "${CONFIG}"
165 + [ $? -ne 0 ] && error "Failed to load config file '${CONFIG}'."
166 + else
167 + warning "Cannot find file '${CONFIG}'."
168 + fi
169 +done
170 +
171 +if [ -z "${hosts}" ]
172 then
160 - fatal "configuration file '${NETDATA_CONFIG_DIR}/${plugin}.conf' not found - nothing to do."
173 + fatal "no hosts configured - nothing to do."
174 fi
175
163 -source "${NETDATA_CONFIG_DIR}/${plugin}.conf"
164 -
165 -if [ -z "${hosts}" ]
176 +if [ -z "${fping}" ]
177 then
167 - fatal "no hosts configured in '${NETDATA_CONFIG_DIR}/${plugin}.conf' - nothing to do."
178 + fatal "fping command is not found. Please set its full path in '${NETDATA_USER_CONFIG_DIR}/${plugin}.conf'"
179 fi
180
170 -if [ -z "${fping}" -o ! -x "${fping}" ]
181 +if [ ! -x "${fping}" ]
182 then
172 - fatal "command '${fping}' is not found or is not executable - cannot proceed."
183 + fatal "fping command '${fping}' is not executable - cannot proceed."
184 fi
185
186 if [ ${ping_every} -lt 20 ]
@@ -186,4 +197,4 @@ info "starting fping: ${fping} ${options[*]}"
197 exec "${fping}" "${options[@]}"
198
199 # if we cannot execute fping, stop
189 -fatal "command '${fping} ${options[@]}' failed to be executed."
200 +fatal "command '${fping} ${options[*]}' failed to be executed (returned code $?)."
plugins.d/node.d.plugin.in renamed
+51 -42
@@ -21,7 +21,8 @@
21 // get NETDATA environment variables
22
23 var NETDATA_PLUGINS_DIR = process.env.NETDATA_PLUGINS_DIR || __dirname;
24 -var NETDATA_CONFIG_DIR = process.env.NETDATA_CONFIG_DIR || __dirname + '/../../../../etc/netdata';
24 +var NETDATA_USER_CONFIG_DIR = process.env.NETDATA_USER_CONFIG_DIR || '@configdir_POST@';
25 +var NETDATA_STOCK_CONFIG_DIR = process.env.NETDATA_STOCK_CONFIG_DIR || '@libconfigdir_POST@';
26 var NETDATA_UPDATE_EVERY = process.env.NETDATA_UPDATE_EVERY || 1;
27 var NODE_D_DIR = NETDATA_PLUGINS_DIR + '/../node.d';
28
@@ -45,20 +46,42 @@ var netdata = require('netdata');
46 // --------------------------------------------------------------------------------------------------------------------
47 // configuration
48
48 -function pluginConfig(filename) {
49 - var f = path.basename(filename);
49 +function netdata_read_json_config_file(module_filename) {
50 + var f = path.basename(module_filename);
51 +
52 + var ufilename, sfilename;
53
51 - // node.d.plugin configuration
54 var m = f.match('.plugin' + '$');
53 - if(m !== null)
54 - return netdata.options.paths.config + '/' + f.substring(0, m.index) + '.conf';
55 + if(m !== null) {
56 + ufilename = netdata.options.paths.config + '/' + f.substring(0, m.index) + '.conf';
57 + sfilename = netdata.options.paths.stock_config + '/' + f.substring(0, m.index) + '.conf';
58 + }
59
56 - // node.d modules configuration
60 m = f.match('.node.js' + '$');
58 - if(m !== null)
59 - return netdata.options.paths.config + '/node.d/' + f.substring(0, m.index) + '.conf';
61 + if(m !== null) {
62 + ufilename = netdata.options.paths.config + '/node.d/' + f.substring(0, m.index) + '.conf';
63 + sfilename = netdata.options.paths.stock_config + '/node.d/' + f.substring(0, m.index) + '.conf';
64 + }
65 +
66 + try {
67 + netdata.debug('loading module\'s ' + module_filename + ' user-config ' + ufilename);
68 + return JSON.parse(fs.readFileSync(ufilename, 'utf8'));
69 + }
70 + catch(e) {
71 + netdata.error('Cannot read user-configuration file ' + ufilename + ': ' + e.message + '.');
72 + dumpError(e);
73 + }
74
61 - return netdata.options.paths.config + '/node.d/' + f + '.conf';
75 + try {
76 + netdata.debug('loading module\'s ' + module_filename + ' stock-config ' + sfilename);
77 + return JSON.parse(fs.readFileSync(sfilename, 'utf8'));
78 + }
79 + catch(e) {
80 + netdata.error('Cannot read stock-configuration file ' + sfilename + ': ' + e.message + ', using internal defaults.');
81 + dumpError(e);
82 + }
83 +
84 + return {};
85 }
86
87 // internal defaults
@@ -69,35 +92,31 @@ extend(true, netdata.options, {
92
93 paths: {
94 plugins: NETDATA_PLUGINS_DIR,
72 - config: NETDATA_CONFIG_DIR,
73 - modules: [],
95 + config: NETDATA_USER_CONFIG_DIR,
96 + stock_config: NETDATA_STOCK_CONFIG_DIR,
97 + modules: []
98 },
99
100 modules_enable_autodetect: true,
101 modules_enable_all: true,
78 - modules: {},
102 + modules: {}
103 });
80 -netdata.options.config_filename = pluginConfig(__filename);
104
105 // load configuration file
83 -try {
84 - netdata.options_loaded = JSON.parse(fs.readFileSync(netdata.options.config_filename, 'utf8'));
85 - extend(true, netdata.options, netdata.options_loaded);
106 +netdata.options_loaded = netdata_read_json_config_file(__filename);
107 +extend(true, netdata.options, netdata.options_loaded);
108
87 - if(!netdata.options.paths.plugins)
88 - netdata.options.paths.plugins = NETDATA_PLUGINS_DIR;
109 +if(!netdata.options.paths.plugins)
110 + netdata.options.paths.plugins = NETDATA_PLUGINS_DIR;
111
90 - if(!netdata.options.paths.config)
91 - netdata.options.paths.config = NETDATA_CONFIG_DIR;
112 +if(!netdata.options.paths.config)
113 + netdata.options.paths.config = NETDATA_USER_CONFIG_DIR;
114
93 - // console.error('merged netdata object:');
94 - // console.error(util.inspect(netdata, {depth: 10}));
95 -}
96 -catch(e) {
97 - netdata.error('Cannot read configuration file ' + netdata.options.config_filename + ': ' + e.message + ', using internal defaults.');
98 - netdata.options_loaded = undefined;
99 - dumpError(e);
100 -}
115 +if(!netdata.options.paths.stock_config)
116 + netdata.options.paths.stock_config = NETDATA_STOCK_CONFIG_DIR;
117 +
118 +// console.error('merged netdata object:');
119 +// console.error(util.inspect(netdata, {depth: 10}));
120
121
122 // apply module paths to node.js process
@@ -206,9 +225,6 @@ function findModules() {
225 netdata.options.modules[n].filename = NODE_D_DIR + '/' + files[len];
226 netdata.options.modules[n].loaded = false;
227
209 - if(typeof(netdata.options.modules[n].config_filename) !== 'string')
210 - netdata.options.modules[n].config_filename = pluginConfig(files[len]);
211 -
228 // load the module
229 try {
230 netdata.debug('loading module ' + netdata.options.modules[n].filename);
@@ -228,16 +244,9 @@ function findModules() {
244 enable_autodetect: netdata.options.modules_enable_autodetect,
245 update_every: netdata.options.update_every
246 };
231 - try {
232 - netdata.debug('loading module\'s ' + netdata.options.modules[n].name + ' config ' + netdata.options.modules[n].config_filename);
233 - var c2 = JSON.parse(fs.readFileSync(netdata.options.modules[n].config_filename, 'utf8'));
234 - extend(true, c, c2);
235 - netdata.debug('loaded module\'s ' + netdata.options.modules[n].name + ' config ' + netdata.options.modules[n].config_filename);
236 - }
237 - catch(e) {
238 - netdata.error('Cannot load module\'s ' + netdata.options.modules[n].name + ' config from ' + netdata.options.modules[n].config_filename + ' exception: ' + e + ', using internal defaults.');
239 - dumpError(e);
240 - }
247 +
248 + var c2 = netdata_read_json_config_file(files[len]);
249 + extend(true, c, c2);
250
251 // call module auto-detection / configuration
252 try {
plugins.d/python.d.plugin.in renamed
+41 -17
@@ -21,10 +21,20 @@ GC_RUN = True
21 GC_COLLECT_EVERY = 300
22
23 PY_VERSION = version_info[:2]
24 -PLUGIN_CONFIG_DIR = os.getenv('NETDATA_CONFIG_DIR', os.path.dirname(__file__) + '/../../../../etc/netdata') + '/'
25 -CHARTS_PY_DIR = os.path.abspath(os.getenv('NETDATA_PLUGINS_DIR', os.path.dirname(__file__)) + '/../python.d') + '/'
26 -CHARTS_PY_CONFIG_DIR = PLUGIN_CONFIG_DIR + 'python.d/'
27 -PYTHON_MODULES_DIR = CHARTS_PY_DIR + 'python_modules'
24 +
25 +USER_CONFIG_DIR = os.getenv('NETDATA_USER_CONFIG_DIR', '@configdir_POST@')
26 +STOCK_CONFIG_DIR = os.getenv('NETDATA_STOCK_CONFIG_DIR', '@libconfigdir_POST@')
27 +
28 +PLUGINS_USER_CONFIG_DIR = os.path.join(USER_CONFIG_DIR, 'python.d')
29 +PLUGINS_STOCK_CONFIG_DIR = os.path.join(STOCK_CONFIG_DIR, 'python.d')
30 +
31 +
32 +PLUGINS_DIR = os.path.abspath(os.getenv(
33 + 'NETDATA_PLUGINS_DIR',
34 + os.path.dirname(__file__)) + '/../python.d')
35 +
36 +
37 +PYTHON_MODULES_DIR = os.path.join(PLUGINS_DIR, 'python_modules')
38
39 sys.path.append(PYTHON_MODULES_DIR)
40
@@ -53,7 +63,7 @@ def module_ok(m):
63 return m.endswith(MODULE_EXTENSION) and m[:-len(MODULE_EXTENSION)] not in OBSOLETE_MODULES
64
65
56 -ALL_MODULES = [m for m in sorted(os.listdir(CHARTS_PY_DIR)) if module_ok(m)]
66 +ALL_MODULES = [m for m in sorted(os.listdir(PLUGINS_DIR)) if module_ok(m)]
67
68
69 def parse_cmd():
@@ -69,10 +79,9 @@ def multi_job_check(config):
79
80
81 class RawModule:
72 - def __init__(self, name, path, conf_path, explicitly_enabled=True):
82 + def __init__(self, name, path, explicitly_enabled=True):
83 self.name = name
84 self.path = path
75 - self.conf_path = conf_path
85 self.explicitly_enabled = explicitly_enabled
86
87
@@ -202,9 +211,20 @@ class Plugin(object):
211 self.modules = OrderedDict()
212 self.sleep_time = 1
213 self.runs_counter = 0
205 - self.config, error = self.loader.load_config_from_file(PLUGIN_CONFIG_DIR + 'python.d.conf')
214 +
215 + user_config = os.path.join(USER_CONFIG_DIR, 'python.d.conf')
216 + stock_config = os.path.join(STOCK_CONFIG_DIR, 'python.d.conf')
217 +
218 + Logger.debug("loading '{0}'".format(user_config))
219 + self.config, error = self.loader.load_config_from_file(user_config)
220 +
221 + if error:
222 + Logger.error("cannot load '{0}': {1}. Will try stock version.".format(user_config, error))
223 + Logger.debug("loading '{0}'".format(stock_config))
224 + self.config, error = self.loader.load_config_from_file(stock_config)
225 if error:
207 - Logger.error('"python.d.conf" configuration file not found. Using defaults.')
226 + Logger.error("cannot load '{0}': {1}".format(stock_config, error))
227 +
228 self.do_gc = self.config.get("gc_run", GC_RUN)
229 self.gc_interval = self.config.get("gc_interval", GC_COLLECT_EVERY)
230
@@ -233,8 +253,7 @@ class Plugin(object):
253 def enabled_modules(self):
254 for mod in MODULES_TO_RUN:
255 mod_name = mod[:-len(MODULE_EXTENSION)]
236 - mod_path = CHARTS_PY_DIR + mod
237 - conf_path = ''.join([CHARTS_PY_CONFIG_DIR, mod_name, '.conf'])
256 + mod_path = os.path.join(PLUGINS_DIR, mod)
257 if any(
258 [
259 self.config.get('default_run', True) and self.config.get(mod_name, True),
@@ -244,7 +263,6 @@ class Plugin(object):
263 yield RawModule(
264 name=mod_name,
265 path=mod_path,
247 - conf_path=conf_path,
266 explicitly_enabled=self.config.get(mod_name),
267 )
268
@@ -261,12 +279,18 @@ class Plugin(object):
279 continue
280
281 # Load module config from file ------------------------------------------------------
264 - loaded_config, error = self.loader.load_config_from_file(mod.conf_path)
265 - log = Logger.error if error else Logger.debug
266 - log("module load config: '{module_name}' => [{status}]".format(status='FAILED' if error else 'OK',
267 - module_name=mod.name))
282 + user_config = os.path.join(PLUGINS_USER_CONFIG_DIR, mod.name + '.conf')
283 + stock_config = os.path.join(PLUGINS_STOCK_CONFIG_DIR, mod.name + '.conf')
284 +
285 + Logger.debug("loading '{0}'".format(user_config))
286 + loaded_config, error = self.loader.load_config_from_file(user_config)
287 + if error:
288 + Logger.error("cannot load '{0}' : {1}. Will try stock version.".format(user_config, error))
289 + Logger.debug("loading '{0}'".format(stock_config))
290 + loaded_config, error = self.loader.load_config_from_file(stock_config)
291 +
292 if error:
269 - Logger.error('load config error : {0}'.format(error))
293 + Logger.error("cannot load '{0}': {1}".format(stock_config, error))
294
295 # Skip disabled modules
296 if getattr(loaded_module, 'disabled_by_default', False) and not mod.explicitly_enabled:
plugins.d/tc-qos-helper.sh.in renamed
+13 -6
@@ -107,10 +107,10 @@ fi
107 # -----------------------------------------------------------------------------
108
109 [ -z "${NETDATA_PLUGINS_DIR}" ] && NETDATA_PLUGINS_DIR="$(dirname "${0}")"
110 -[ -z "${NETDATA_CONFIG_DIR}" ] && NETDATA_CONFIG_DIR="$(dirname "${0}")/../../../../etc/netdata"
110 +[ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
111 +[ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
112
113 plugins_dir="${NETDATA_PLUGINS_DIR}"
113 -config_dir="${NETDATA_CONFIG_DIR}"
114 tc="$(which tc 2>/dev/null || command -v tc 2>/dev/null)"
115
116
@@ -139,10 +139,17 @@ update_every=$((t))
139 # -----------------------------------------------------------------------------
140 # allow the user to override our defaults
141
142 -if [ -f "${config_dir}/tc-qos-helper.conf" ]
143 - then
144 - source "${config_dir}/tc-qos-helper.conf"
145 -fi
142 +for CONFIG in "${NETDATA_STOCK_CONFIG_DIR}/tc-qos-helper.conf" "${NETDATA_USER_CONFIG_DIR}/tc-qos-helper.conf"
143 +do
144 + if [ -f "${CONFIG}" ]
145 + then
146 + info "Loading config file '${CONFIG}'..."
147 + source "${CONFIG}"
148 + [ $? -ne 0 ] && error "Failed to load config file '${CONFIG}'."
149 + else
150 + warning "Cannot find file '${CONFIG}'."
151 + fi
152 +done
153
154 case "${tc_show}" in
155 qdisc|class)
src/Makefile.am
+1
@@ -8,6 +8,7 @@ AM_CPPFLAGS = \
8 -DVARLIB_DIR="\"$(varlibdir)\"" \
9 -DCACHE_DIR="\"$(cachedir)\"" \
10 -DCONFIG_DIR="\"$(configdir)\"" \
11 + -DLIBCONFIG_DIR="\"$(libconfigdir)\"" \
12 -DLOG_DIR="\"$(logdir)\"" \
13 -DPLUGINS_DIR="\"$(pluginsdir)\"" \
14 -DRUN_DIR="\"$(localstatedir)/run/netdata\"" \
src/apps_plugin.c
+27 -11
@@ -100,8 +100,9 @@ static int
100 include_exited_childs = 1;
101
102
103 -// will be changed to getenv(NETDATA_CONFIG_DIR) if it exists
104 -static char *config_dir = CONFIG_DIR;
103 +// will be changed to getenv(NETDATA_USER_CONFIG_DIR) if it exists
104 +static char *user_config_dir = CONFIG_DIR;
105 +static char *stock_config_dir = LIBCONFIG_DIR;
106
107 // ----------------------------------------------------------------------------
108 // internal flags
@@ -631,11 +632,11 @@ static struct target *get_apps_groups_target(const char *id, struct target *targ
632 }
633
634 // read the apps_groups.conf file
634 -static int read_apps_groups_conf(const char *file)
635 +static int read_apps_groups_conf(const char *path, const char *file)
636 {
637 char filename[FILENAME_MAX + 1];
638
638 - snprintfz(filename, FILENAME_MAX, "%s/apps_%s.conf", config_dir, file);
639 + snprintfz(filename, FILENAME_MAX, "%s/apps_%s.conf", path, file);
640
641 debug_log("process groups file: '%s'", filename);
642
@@ -3569,10 +3570,18 @@ static void parse_args(int argc, char **argv)
3570
3571 if(freq > 0) update_every = freq;
3572
3572 - if(read_apps_groups_conf("groups")) {
3573 - error("Cannot read process groups '%s/apps_groups.conf'. There are no internal defaults. Failing.", config_dir);
3574 - exit(1);
3573 + if(read_apps_groups_conf(user_config_dir, "groups")) {
3574 + error("Cannot read process groups configuration file '%s/apps_groups.conf'. Will try '%s/apps_groups.conf'", user_config_dir, stock_config_dir);
3575 +
3576 + if(read_apps_groups_conf(stock_config_dir, "groups")) {
3577 + error("Cannot read process groups '%s/apps_groups.conf'. There are no internal defaults. Failing.", stock_config_dir);
3578 + exit(1);
3579 + }
3580 + else
3581 + info("Loaded config file '%s/apps_groups.conf'", stock_config_dir);
3582 }
3583 + else
3584 + info("Loaded config file '%s/apps_groups.conf'", user_config_dir);
3585 }
3586
3587 static int am_i_running_as_root() {
@@ -3658,12 +3667,19 @@ int main(int argc, char **argv) {
3667 netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
3668 if(verify_netdata_host_prefix() == -1) exit(1);
3669
3661 - config_dir = getenv("NETDATA_CONFIG_DIR");
3662 - if(config_dir == NULL) {
3670 + user_config_dir = getenv("NETDATA_USER_CONFIG_DIR");
3671 + if(user_config_dir == NULL) {
3672 + // info("NETDATA_CONFIG_DIR is not passed from netdata");
3673 + user_config_dir = CONFIG_DIR;
3674 + }
3675 + // else info("Found NETDATA_USER_CONFIG_DIR='%s'", user_config_dir);
3676 +
3677 + stock_config_dir = getenv("NETDATA_STOCK_CONFIG_DIR");
3678 + if(stock_config_dir == NULL) {
3679 // info("NETDATA_CONFIG_DIR is not passed from netdata");
3664 - config_dir = CONFIG_DIR;
3680 + stock_config_dir = LIBCONFIG_DIR;
3681 }
3666 - // else info("Found NETDATA_CONFIG_DIR='%s'", config_dir);
3682 + // else info("Found NETDATA_USER_CONFIG_DIR='%s'", user_config_dir);
3683
3684 #ifdef NETDATA_INTERNAL_CHECKS
3685 if(debug_flags != 0) {
src/common.c
+199 -10
@@ -10,16 +10,17 @@
10 # define MADV_DONTFORK INHERIT_NONE
11 #endif /* __FreeBSD__ || __APPLE__*/
12
13 -char *netdata_configured_hostname = NULL;
14 -char *netdata_configured_config_dir = NULL;
15 -char *netdata_configured_log_dir = NULL;
16 -char *netdata_configured_plugins_dir = NULL;
17 -char *netdata_configured_web_dir = NULL;
18 -char *netdata_configured_cache_dir = NULL;
19 -char *netdata_configured_varlib_dir = NULL;
20 -char *netdata_configured_home_dir = NULL;
21 -char *netdata_configured_host_prefix = NULL;
22 -char *netdata_configured_timezone = NULL;
13 +char *netdata_configured_hostname = NULL;
14 +char *netdata_configured_user_config_dir = CONFIG_DIR;
15 +char *netdata_configured_stock_config_dir = LIBCONFIG_DIR;
16 +char *netdata_configured_log_dir = LOG_DIR;
17 +char *netdata_configured_plugins_dir = NULL;
18 +char *netdata_configured_web_dir = WEB_DIR;
19 +char *netdata_configured_cache_dir = CACHE_DIR;
20 +char *netdata_configured_varlib_dir = VARLIB_DIR;
21 +char *netdata_configured_home_dir = CACHE_DIR;
22 +char *netdata_configured_host_prefix = NULL;
23 +char *netdata_configured_timezone = NULL;
24
25 struct rlimit rlimit_nofile = { .rlim_cur = 1024, .rlim_max = 1024 };
26 int enable_ksm = 1;
@@ -1431,3 +1432,191 @@ failed:
1432 netdata_configured_host_prefix = "";
1433 return -1;
1434 }
1435 +
1436 +char *strdupz_path_subpath(const char *path, const char *subpath) {
1437 + if(unlikely(!path || !*path)) path = ".";
1438 + if(unlikely(!subpath)) subpath = "";
1439 +
1440 + // skip trailing slashes in path
1441 + size_t len = strlen(path);
1442 + while(len > 0 && path[len - 1] == '/') len--;
1443 +
1444 + // skip leading slashes in subpath
1445 + while(subpath && subpath[0] == '/') subpath++;
1446 +
1447 + // if the last character in path is / and (there is a subpath or path is now empty)
1448 + // keep the trailing slash in path and remove the additional slash
1449 + char *slash = "/";
1450 + if(path[len] == '/' && (*subpath || len == 0)) {
1451 + slash = "";
1452 + len++;
1453 + }
1454 + else if(!*subpath) {
1455 + // there is no subpath
1456 + // no need for trailing slash
1457 + slash = "";
1458 + }
1459 +
1460 + char buffer[FILENAME_MAX + 1];
1461 + snprintfz(buffer, FILENAME_MAX, "%.*s%s%s", (int)len, path, slash, (subpath)?subpath:"");
1462 + return strdupz(buffer);
1463 +}
1464 +
1465 +int path_is_dir(const char *path, const char *subpath) {
1466 + char *s = strdupz_path_subpath(path, subpath);
1467 +
1468 + size_t max_links = 100;
1469 +
1470 + int is_dir = 0;
1471 + struct stat statbuf;
1472 + while(max_links-- && stat(s, &statbuf) == 0) {
1473 + if((statbuf.st_mode & S_IFMT) == S_IFDIR) {
1474 + is_dir = 1;
1475 + break;
1476 + }
1477 + else if((statbuf.st_mode & S_IFMT) == S_IFLNK) {
1478 + char buffer[FILENAME_MAX + 1];
1479 + ssize_t l = readlink(s, buffer, FILENAME_MAX);
1480 + if(l > 0) {
1481 + buffer[l] = '\0';
1482 + freez(s);
1483 + s = strdupz(buffer);
1484 + continue;
1485 + }
1486 + else {
1487 + is_dir = 0;
1488 + break;
1489 + }
1490 + }
1491 + else {
1492 + is_dir = 0;
1493 + break;
1494 + }
1495 + }
1496 +
1497 + freez(s);
1498 + return is_dir;
1499 +}
1500 +
1501 +int path_is_file(const char *path, const char *subpath) {
1502 + char *s = strdupz_path_subpath(path, subpath);
1503 +
1504 + size_t max_links = 100;
1505 +
1506 + int is_file = 0;
1507 + struct stat statbuf;
1508 + while(max_links-- && stat(s, &statbuf) == 0) {
1509 + if((statbuf.st_mode & S_IFMT) == S_IFREG) {
1510 + is_file = 1;
1511 + break;
1512 + }
1513 + else if((statbuf.st_mode & S_IFMT) == S_IFLNK) {
1514 + char buffer[FILENAME_MAX + 1];
1515 + ssize_t l = readlink(s, buffer, FILENAME_MAX);
1516 + if(l > 0) {
1517 + buffer[l] = '\0';
1518 + freez(s);
1519 + s = strdupz(buffer);
1520 + continue;
1521 + }
1522 + else {
1523 + is_file = 0;
1524 + break;
1525 + }
1526 + }
1527 + else {
1528 + is_file = 0;
1529 + break;
1530 + }
1531 + }
1532 +
1533 + freez(s);
1534 + return is_file;
1535 +}
1536 +
1537 +void recursive_config_double_dir_load(const char *user_path, const char *stock_path, const char *subpath, int (*callback)(const char *filename, void *data), void *data) {
1538 + char *udir = strdupz_path_subpath(user_path, subpath);
1539 + char *sdir = strdupz_path_subpath(stock_path, subpath);
1540 +
1541 + debug(D_HEALTH, "Configuration traversing user-config directory '%s', stock config directory '%s'", udir, sdir);
1542 +
1543 + DIR *dir = opendir(udir);
1544 + if (!dir) {
1545 + error("Configuration cannot open user-config directory '%s'.", udir);
1546 + }
1547 + else {
1548 + struct dirent *de = NULL;
1549 + while((de = readdir(dir))) {
1550 + if(de->d_type == DT_DIR
1551 + && ( (de->d_name[0] == '.' && de->d_name[1] == '\0')
1552 + || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
1553 + )) {
1554 + debug(D_HEALTH, "Configuration ignoring user-config directory '%s/%s'", udir, de->d_name);
1555 + continue;
1556 + }
1557 +
1558 + if(path_is_dir(udir, de->d_name)) {
1559 + recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data);
1560 + continue;
1561 + }
1562 +
1563 + size_t len = strlen(de->d_name);
1564 + if(path_is_file(udir, de->d_name) &&
1565 + len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
1566 + char *filename = strdupz_path_subpath(udir, de->d_name);
1567 + callback(filename, data);
1568 + freez(filename);
1569 + }
1570 +
1571 + else
1572 + debug(D_HEALTH, "Health ignoring user config file '%s/%s'", udir, de->d_name);
1573 + }
1574 +
1575 + closedir(dir);
1576 + }
1577 +
1578 + debug(D_HEALTH, "Health configuration traversing stock config directory '%s', user config directory '%s'", sdir, udir);
1579 +
1580 + dir = opendir(sdir);
1581 + if (!dir) {
1582 + error("Health configuration cannot open stock config directory '%s'.", sdir);
1583 + }
1584 + else {
1585 + struct dirent *de = NULL;
1586 + while((de = readdir(dir))) {
1587 + if(de->d_type == DT_DIR
1588 + && ( (de->d_name[0] == '.' && de->d_name[1] == '\0')
1589 + || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
1590 + )) {
1591 + debug(D_HEALTH, "Health ignoring stock config directory '%s/%s'", sdir, de->d_name);
1592 + continue;
1593 + }
1594 +
1595 + if(path_is_dir(sdir, de->d_name)) {
1596 + // we recurse in stock subdirectory, only when there is no corresponding
1597 + // user subdirectory - to avoid reading the files twice
1598 +
1599 + if(!path_is_dir(udir, de->d_name))
1600 + recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data);
1601 +
1602 + continue;
1603 + }
1604 +
1605 + size_t len = strlen(de->d_name);
1606 + if(path_is_file(sdir, de->d_name) && !path_is_file(udir, de->d_name) &&
1607 + len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
1608 + char *filename = strdupz_path_subpath(sdir, de->d_name);
1609 + callback(filename, data);
1610 + freez(filename);
1611 + }
1612 +
1613 + else
1614 + debug(D_HEALTH, "Health ignoring stock config file '%s/%s'", sdir, de->d_name);
1615 + }
1616 +
1617 + closedir(dir);
1618 + }
1619 +
1620 + freez(udir);
1621 + freez(sdir);
1622 +}
src/common.h
+9 -1
@@ -42,6 +42,7 @@
42 #include <arpa/inet.h>
43 #include <netinet/tcp.h>
44 #include <sys/ioctl.h>
45 +#include <libgen.h>
46
47 #ifdef HAVE_NETINET_IN_H
48 #include <netinet/in.h>
@@ -349,7 +350,8 @@ typedef enum rrdcalc_status {
350 #include "web_api_v1.h"
351
352 extern char *netdata_configured_hostname;
352 -extern char *netdata_configured_config_dir;
353 +extern char *netdata_configured_user_config_dir;
354 +extern char *netdata_configured_stock_config_dir;
355 extern char *netdata_configured_log_dir;
356 extern char *netdata_configured_plugins_dir_base;
357 extern char *netdata_configured_plugins_dir;
@@ -426,6 +428,12 @@ extern const char *os_type;
428
429 extern const char *program_version;
430
431 +extern char *strdupz_path_subpath(const char *path, const char *subpath);
432 +extern int path_is_dir(const char *path, const char *subpath);
433 +extern int path_is_file(const char *path, const char *subpath);
434 +extern void recursive_config_double_dir_load(const char *user_path, const char *stock_path, const char *subpath
435 + , int (*callback)(const char *filename, void *data), void *data);
436 +
437 /* fix for alpine linux */
438 #ifndef RUSAGE_THREAD
439 #ifdef RUSAGE_CHILDREN
src/health.c
+11 -4
@@ -8,12 +8,18 @@ unsigned int default_health_enabled = 1;
8 // ----------------------------------------------------------------------------
9 // health initialization
10
11 -inline char *health_config_dir(void) {
11 +inline char *health_user_config_dir(void) {
12 char buffer[FILENAME_MAX + 1];
13 - snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_config_dir);
13 + snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_user_config_dir);
14 return config_get(CONFIG_SECTION_HEALTH, "health configuration directory", buffer);
15 }
16
17 +inline char *health_stock_config_dir(void) {
18 + char buffer[FILENAME_MAX + 1];
19 + snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_stock_config_dir);
20 + return config_get(CONFIG_SECTION_HEALTH, "stock health configuration directory", buffer);
21 +}
22 +
23 void health_init(void) {
24 debug(D_HEALTH, "Health configuration initializing");
25
@@ -30,7 +36,8 @@ void health_reload_host(RRDHOST *host) {
36 if(unlikely(!host->health_enabled))
37 return;
38
33 - char *path = health_config_dir();
39 + char *user_path = health_user_config_dir();
40 + char *stock_path = health_stock_config_dir();
41
42 // free all running alarms
43 rrdhost_wrlock(host);
@@ -61,7 +68,7 @@ void health_reload_host(RRDHOST *host) {
68
69 // load the new alarms
70 rrdhost_wrlock(host);
64 - health_readdir(host, path);
71 + health_readdir(host, user_path, stock_path, NULL);
72
73 // link the loaded alarms to their charts
74 rrdset_foreach_write(st, host) {
src/health.h
+5 -2
@@ -1,5 +1,7 @@
1 // SPDX-License-Identifier: GPL-3.0+
2
3 +#include "common.h"
4 +
5 #ifndef NETDATA_HEALTH_H
6 #define NETDATA_HEALTH_H 1
7
@@ -415,8 +417,9 @@ extern void health_alarm_log(
417 uint32_t flags
418 );
419
418 -extern void health_readdir(RRDHOST *host, const char *path);
419 -extern char *health_config_dir(void);
420 +extern void health_readdir(RRDHOST *host, const char *user_path, const char *stock_path, const char *subpath);
421 +extern char *health_user_config_dir(void);
422 +extern char *health_stock_config_dir(void);
423 extern void health_reload_host(RRDHOST *host);
424 extern void health_alarm_log_free(RRDHOST *host);
425
src/health_config.c
+100 -143
@@ -178,7 +178,7 @@ static inline int health_parse_duration(char *string, int *result) {
178 }
179
180 static inline int health_parse_delay(
181 - size_t line, const char *path, const char *file, char *string,
181 + size_t line, const char *filename, char *string,
182 int *delay_up_duration,
183 int *delay_down_duration,
184 int *delay_max_duration,
@@ -204,36 +204,36 @@ static inline int health_parse_delay(
204
205 if(!strcasecmp(key, "up")) {
206 if (!health_parse_duration(value, delay_up_duration)) {
207 - error("Health configuration at line %zu of file '%s/%s': invalid value '%s' for '%s' keyword",
208 - line, path, file, value, key);
207 + error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
208 + line, filename, value, key);
209 }
210 else given_up = 1;
211 }
212 else if(!strcasecmp(key, "down")) {
213 if (!health_parse_duration(value, delay_down_duration)) {
214 - error("Health configuration at line %zu of file '%s/%s': invalid value '%s' for '%s' keyword",
215 - line, path, file, value, key);
214 + error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
215 + line, filename, value, key);
216 }
217 else given_down = 1;
218 }
219 else if(!strcasecmp(key, "multiplier")) {
220 *delay_multiplier = strtof(value, NULL);
221 if(isnan(*delay_multiplier) || isinf(*delay_multiplier) || islessequal(*delay_multiplier, 0)) {
222 - error("Health configuration at line %zu of file '%s/%s': invalid value '%s' for '%s' keyword",
223 - line, path, file, value, key);
222 + error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
223 + line, filename, value, key);
224 }
225 else given_multiplier = 1;
226 }
227 else if(!strcasecmp(key, "max")) {
228 if (!health_parse_duration(value, delay_max_duration)) {
229 - error("Health configuration at line %zu of file '%s/%s': invalid value '%s' for '%s' keyword",
230 - line, path, file, value, key);
229 + error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
230 + line, filename, value, key);
231 }
232 else given_max = 1;
233 }
234 else {
235 - error("Health configuration at line %zu of file '%s/%s': unknown keyword '%s'",
236 - line, path, file, key);
235 + error("Health configuration at line %zu of file '%s': unknown keyword '%s'",
236 + line, filename, key);
237 }
238 }
239
@@ -287,11 +287,11 @@ static inline uint32_t health_parse_options(const char *s) {
287 }
288
289 static inline int health_parse_db_lookup(
290 - size_t line, const char *path, const char *file, char *string,
290 + size_t line, const char *filename, char *string,
291 int *group_method, int *after, int *before, int *every,
292 uint32_t *options, char **dimensions
293 ) {
294 - debug(D_HEALTH, "Health configuration parsing database lookup %zu@%s/%s: %s", line, path, file, string);
294 + debug(D_HEALTH, "Health configuration parsing database lookup %zu@%s: %s", line, filename, string);
295
296 if(*dimensions) freez(*dimensions);
297 *dimensions = NULL;
@@ -307,14 +307,14 @@ static inline int health_parse_db_lookup(
307 while(*s && !isspace(*s)) s++;
308 while(*s && isspace(*s)) *s++ = '\0';
309 if(!*s) {
310 - error("Health configuration invalid chart calculation at line %zu of file '%s/%s': expected group method followed by the 'after' time, but got '%s'",
311 - line, path, file, key);
310 + error("Health configuration invalid chart calculation at line %zu of file '%s': expected group method followed by the 'after' time, but got '%s'",
311 + line, filename, key);
312 return 0;
313 }
314
315 if((*group_method = web_client_api_request_v1_data_group(key, -1)) == -1) {
316 - error("Health configuration at line %zu of file '%s/%s': invalid group method '%s'",
317 - line, path, file, key);
316 + error("Health configuration at line %zu of file '%s': invalid group method '%s'",
317 + line, filename, key);
318 return 0;
319 }
320
@@ -324,8 +324,8 @@ static inline int health_parse_db_lookup(
324 while(*s && isspace(*s)) *s++ = '\0';
325
326 if(!health_parse_duration(key, after)) {
327 - error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' after group method",
328 - line, path, file, key);
327 + error("Health configuration at line %zu of file '%s': invalid duration '%s' after group method",
328 + line, filename, key);
329 return 0;
330 }
331
@@ -345,8 +345,8 @@ static inline int health_parse_db_lookup(
345 while(*s && isspace(*s)) *s++ = '\0';
346
347 if (!health_parse_duration(value, before)) {
348 - error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' for '%s' keyword",
349 - line, path, file, value, key);
348 + error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
349 + line, filename, value, key);
350 }
351 }
352 else if(!strcasecmp(key, HEALTH_EVERY_KEY)) {
@@ -355,8 +355,8 @@ static inline int health_parse_db_lookup(
355 while(*s && isspace(*s)) *s++ = '\0';
356
357 if (!health_parse_duration(value, every)) {
358 - error("Health configuration at line %zu of file '%s/%s': invalid duration '%s' for '%s' keyword",
359 - line, path, file, value, key);
358 + error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
359 + line, filename, value, key);
360 }
361 }
362 else if(!strcasecmp(key, "absolute") || !strcasecmp(key, "abs") || !strcasecmp(key, "absolute_sum")) {
@@ -386,17 +386,17 @@ static inline int health_parse_db_lookup(
386 break;
387 }
388 else {
389 - error("Health configuration at line %zu of file '%s/%s': unknown keyword '%s'",
390 - line, path, file, key);
389 + error("Health configuration at line %zu of file '%s': unknown keyword '%s'",
390 + line, filename, key);
391 }
392 }
393
394 return 1;
395 }
396
397 -static inline char *health_source_file(size_t line, const char *path, const char *filename) {
397 +static inline char *health_source_file(size_t line, const char *file) {
398 char buffer[FILENAME_MAX + 1];
399 - snprintfz(buffer, FILENAME_MAX, "%zu@%s/%s", line, path, filename);
399 + snprintfz(buffer, FILENAME_MAX, "%zu@%s", line, file);
400 return strdupz(buffer);
401 }
402
@@ -407,8 +407,10 @@ static inline void strip_quotes(char *s) {
407 }
408 }
409
410 -int health_readfile(RRDHOST *host, const char *path, const char *filename) {
411 - debug(D_HEALTH, "Health configuration reading file '%s/%s'", path, filename);
410 +static int health_readfile(const char *filename, void *data) {
411 + RRDHOST *host = (RRDHOST *)data;
412 +
413 + debug(D_HEALTH, "Health configuration reading file '%s'", filename);
414
415 static uint32_t
416 hash_alarm = 0,
@@ -455,10 +457,9 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
457 hash_options = simple_uhash(HEALTH_OPTIONS_KEY);
458 }
459
458 - snprintfz(buffer, HEALTH_CONF_MAX_LINE, "%s/%s", path, filename);
459 - FILE *fp = fopen(buffer, "r");
460 + FILE *fp = fopen(filename, "r");
461 if(!fp) {
461 - error("Health configuration cannot read file '%s'.", buffer);
462 + error("Health configuration cannot read file '%s'.", filename);
463 return 0;
464 }
465
@@ -481,7 +482,7 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
482 if(append < HEALTH_CONF_MAX_LINE)
483 continue;
484 else {
484 - error("Health configuration has too long muli-line at line %zu of file '%s/%s'.", line, path, filename);
485 + error("Health configuration has too long muli-line at line %zu of file '%s'.", line, filename);
486 }
487 }
488 append = 0;
@@ -489,7 +490,7 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
490 char *key = s;
491 while(*s && *s != ':') s++;
492 if(!*s) {
492 - error("Health configuration has invalid line %zu of file '%s/%s'. It does not contain a ':'. Ignoring it.", line, path, filename);
493 + error("Health configuration has invalid line %zu of file '%s'. It does not contain a ':'. Ignoring it.", line, filename);
494 continue;
495 }
496 *s = '\0';
@@ -500,12 +501,12 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
501 value = trim_all(value);
502
503 if(!key) {
503 - error("Health configuration has invalid line %zu of file '%s/%s'. Keyword is empty. Ignoring it.", line, path, filename);
504 + error("Health configuration has invalid line %zu of file '%s'. Keyword is empty. Ignoring it.", line, filename);
505 continue;
506 }
507
508 if(!value) {
508 - error("Health configuration has invalid line %zu of file '%s/%s'. value is empty. Ignoring it.", line, path, filename);
509 + error("Health configuration has invalid line %zu of file '%s'. value is empty. Ignoring it.", line, filename);
510 continue;
511 }
512
@@ -526,7 +527,7 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
527 rc->next_event_id = 1;
528 rc->name = strdupz(value);
529 rc->hash = simple_hash(rc->name);
529 - rc->source = health_source_file(line, path, filename);
530 + rc->source = health_source_file(line, filename);
531 rc->green = NAN;
532 rc->red = NAN;
533 rc->value = NAN;
@@ -552,7 +553,7 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
553 rt = callocz(1, sizeof(RRDCALCTEMPLATE));
554 rt->name = strdupz(value);
555 rt->hash_name = simple_hash(rt->name);
555 - rt->source = health_source_file(line, path, filename);
556 + rt->source = health_source_file(line, filename);
557 rt->green = NAN;
558 rt->red = NAN;
559 rt->delay_multiplier = 1.0;
@@ -568,10 +569,10 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
569
570 if(!simple_pattern_matches(os_pattern, host->os)) {
571 if(rc)
571 - debug(D_HEALTH, "HEALTH on '%s' ignoring alarm '%s' defined at %zu@%s/%s: host O/S does not match '%s'", host->hostname, rc->name, line, path, filename, os_match);
572 + debug(D_HEALTH, "HEALTH on '%s' ignoring alarm '%s' defined at %zu@%s: host O/S does not match '%s'", host->hostname, rc->name, line, filename, os_match);
573
574 if(rt)
574 - debug(D_HEALTH, "HEALTH on '%s' ignoring template '%s' defined at %zu@%s/%s: host O/S does not match '%s'", host->hostname, rt->name, line, path, filename, os_match);
575 + debug(D_HEALTH, "HEALTH on '%s' ignoring template '%s' defined at %zu@%s: host O/S does not match '%s'", host->hostname, rt->name, line, filename, os_match);
576
577 ignore_this = 1;
578 }
@@ -584,10 +585,10 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
585
586 if(!simple_pattern_matches(host_pattern, host->hostname)) {
587 if(rc)
587 - debug(D_HEALTH, "HEALTH on '%s' ignoring alarm '%s' defined at %zu@%s/%s: hostname does not match '%s'", host->hostname, rc->name, line, path, filename, host_match);
588 + debug(D_HEALTH, "HEALTH on '%s' ignoring alarm '%s' defined at %zu@%s: hostname does not match '%s'", host->hostname, rc->name, line, filename, host_match);
589
590 if(rt)
590 - debug(D_HEALTH, "HEALTH on '%s' ignoring template '%s' defined at %zu@%s/%s: hostname does not match '%s'", host->hostname, rt->name, line, path, filename, host_match);
591 + debug(D_HEALTH, "HEALTH on '%s' ignoring template '%s' defined at %zu@%s: hostname does not match '%s'", host->hostname, rt->name, line, filename, host_match);
592
593 ignore_this = 1;
594 }
@@ -598,8 +599,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
599 if(hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
600 if(rc->chart) {
601 if(strcmp(rc->chart, value) != 0)
601 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
602 - line, path, filename, rc->name, key, rc->chart, value, value);
602 + error("Health configuration at line %zu of file '%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
603 + line, filename, rc->name, key, rc->chart, value, value);
604
605 freez(rc->chart);
606 }
@@ -607,29 +608,29 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
608 rc->hash_chart = simple_hash(rc->chart);
609 }
610 else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
610 - health_parse_db_lookup(line, path, filename, value, &rc->group, &rc->after, &rc->before,
611 + health_parse_db_lookup(line, filename, value, &rc->group, &rc->after, &rc->before,
612 &rc->update_every,
613 &rc->options, &rc->dimensions);
614 }
615 else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
616 if(!health_parse_duration(value, &rc->update_every))
616 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' cannot parse duration: '%s'.",
617 - line, path, filename, rc->name, key, value);
617 + error("Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' cannot parse duration: '%s'.",
618 + line, filename, rc->name, key, value);
619 }
620 else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
621 char *e;
622 rc->green = str2ld(value, &e);
623 if(e && *e) {
623 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' leaves this string unmatched: '%s'.",
624 - line, path, filename, rc->name, key, e);
624 + error("Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' leaves this string unmatched: '%s'.",
625 + line, filename, rc->name, key, e);
626 }
627 }
628 else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
629 char *e;
630 rc->red = str2ld(value, &e);
631 if(e && *e) {
631 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' leaves this string unmatched: '%s'.",
632 - line, path, filename, rc->name, key, e);
632 + error("Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' leaves this string unmatched: '%s'.",
633 + line, filename, rc->name, key, e);
634 }
635 }
636 else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) {
@@ -637,8 +638,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
638 int error = 0;
639 rc->calculation = expression_parse(value, &failed_at, &error);
640 if(!rc->calculation) {
640 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
641 - line, path, filename, rc->name, key, value, expression_strerror(error), failed_at);
641 + error("Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
642 + line, filename, rc->name, key, value, expression_strerror(error), failed_at);
643 }
644 }
645 else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) {
@@ -646,8 +647,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
647 int error = 0;
648 rc->warning = expression_parse(value, &failed_at, &error);
649 if(!rc->warning) {
649 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
650 - line, path, filename, rc->name, key, value, expression_strerror(error), failed_at);
650 + error("Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
651 + line, filename, rc->name, key, value, expression_strerror(error), failed_at);
652 }
653 }
654 else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) {
@@ -655,15 +656,15 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
656 int error = 0;
657 rc->critical = expression_parse(value, &failed_at, &error);
658 if(!rc->critical) {
658 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
659 - line, path, filename, rc->name, key, value, expression_strerror(error), failed_at);
659 + error("Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
660 + line, filename, rc->name, key, value, expression_strerror(error), failed_at);
661 }
662 }
663 else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) {
664 if(rc->exec) {
665 if(strcmp(rc->exec, value) != 0)
665 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
666 - line, path, filename, rc->name, key, rc->exec, value, value);
666 + error("Health configuration at line %zu of file '%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
667 + line, filename, rc->name, key, rc->exec, value, value);
668
669 freez(rc->exec);
670 }
@@ -672,8 +673,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
673 else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
674 if(rc->recipient) {
675 if(strcmp(rc->recipient, value) != 0)
675 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
676 - line, path, filename, rc->name, key, rc->recipient, value, value);
676 + error("Health configuration at line %zu of file '%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
677 + line, filename, rc->name, key, rc->recipient, value, value);
678
679 freez(rc->recipient);
680 }
@@ -682,8 +683,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
683 else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) {
684 if(rc->units) {
685 if(strcmp(rc->units, value) != 0)
685 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
686 - line, path, filename, rc->name, key, rc->units, value, value);
686 + error("Health configuration at line %zu of file '%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
687 + line, filename, rc->name, key, rc->units, value, value);
688
689 freez(rc->units);
690 }
@@ -693,8 +694,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
694 else if(hash == hash_info && !strcasecmp(key, HEALTH_INFO_KEY)) {
695 if(rc->info) {
696 if(strcmp(rc->info, value) != 0)
696 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
697 - line, path, filename, rc->name, key, rc->info, value, value);
697 + error("Health configuration at line %zu of file '%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
698 + line, filename, rc->name, key, rc->info, value, value);
699
700 freez(rc->info);
701 }
@@ -702,22 +703,22 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
703 strip_quotes(rc->info);
704 }
705 else if(hash == hash_delay && !strcasecmp(key, HEALTH_DELAY_KEY)) {
705 - health_parse_delay(line, path, filename, value, &rc->delay_up_duration, &rc->delay_down_duration, &rc->delay_max_duration, &rc->delay_multiplier);
706 + health_parse_delay(line, filename, value, &rc->delay_up_duration, &rc->delay_down_duration, &rc->delay_max_duration, &rc->delay_multiplier);
707 }
708 else if(hash == hash_options && !strcasecmp(key, HEALTH_OPTIONS_KEY)) {
709 rc->options |= health_parse_options(value);
710 }
711 else {
711 - error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has unknown key '%s'.",
712 - line, path, filename, rc->name, key);
712 + error("Health configuration at line %zu of file '%s' for alarm '%s' has unknown key '%s'.",
713 + line, filename, rc->name, key);
714 }
715 }
716 else if(rt) {
717 if(hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
718 if(rt->context) {
719 if(strcmp(rt->context, value) != 0)
719 - error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
720 - line, path, filename, rt->name, key, rt->context, value, value);
720 + error("Health configuration at line %zu of file '%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
721 + line, filename, rt->name, key, rt->context, value, value);
722
723 freez(rt->context);
724 }
@@ -732,28 +733,28 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
733 rt->family_pattern = simple_pattern_create(rt->family_match, NULL, SIMPLE_PATTERN_EXACT);
734 }
735 else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
735 - health_parse_db_lookup(line, path, filename, value, &rt->group, &rt->after, &rt->before,
736 + health_parse_db_lookup(line, filename, value, &rt->group, &rt->after, &rt->before,
737 &rt->update_every, &rt->options, &rt->dimensions);
738 }
739 else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
740 if(!health_parse_duration(value, &rt->update_every))
740 - error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' cannot parse duration: '%s'.",
741 - line, path, filename, rt->name, key, value);
741 + error("Health configuration at line %zu of file '%s' for template '%s' at key '%s' cannot parse duration: '%s'.",
742 + line, filename, rt->name, key, value);
743 }
744 else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
745 char *e;
746 rt->green = str2ld(value, &e);
747 if(e && *e) {
747 - error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' leaves this string unmatched: '%s'.",
748 - line, path, filename, rt->name, key, e);
748 + error("Health configuration at line %zu of file '%s' for template '%s' at key '%s' leaves this string unmatched: '%s'.",
749 + line, filename, rt->name, key, e);
750 }
751 }
752 else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
753 char *e;
754 rt->red = str2ld(value, &e);
755 if(e && *e) {
755 - error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' leaves this string unmatched: '%s'.",
756 - line, path, filename, rt->name, key, e);
756 + error("Health configuration at line %zu of file '%s' for template '%s' at key '%s' leaves this string unmatched: '%s'.",
757 + line, filename, rt->name, key, e);
758 }
759 }
760 else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) {
@@ -761,8 +762,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
762 int error = 0;
763 rt->calculation = expression_parse(value, &failed_at, &error);
764 if(!rt->calculation) {
764 - error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
765 - line, path, filename, rt->name, key, value, expression_strerror(error), failed_at);
765 + error("Health configuration at line %zu of file '%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
766 + line, filename, rt->name, key, value, expression_strerror(error), failed_at);
767 }
768 }
769 else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) {
@@ -770,8 +771,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
771 int error = 0;
772 rt->warning = expression_parse(value, &failed_at, &error);
773 if(!rt->warning) {
773 - error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
774 - line, path, filename, rt->name, key, value, expression_strerror(error), failed_at);
774 + error("Health configuration at line %zu of file '%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
775 + line, filename, rt->name, key, value, expression_strerror(error), failed_at);
776 }
777 }
778 else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) {
@@ -779,15 +780,15 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
780 int error = 0;
781 rt->critical = expression_parse(value, &failed_at, &error);
782 if(!rt->critical) {
782 - error("Health configuration at line %zu of file '%s/%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
783 - line, path, filename, rt->name, key, value, expression_strerror(error), failed_at);
783 + error("Health configuration at line %zu of file '%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'",
784 + line, filename, rt->name, key, value, expression_strerror(error), failed_at);
785 }
786 }
787 else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) {
788 if(rt->exec) {
789 if(strcmp(rt->exec, value) != 0)
789 - error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
790 - line, path, filename, rt->name, key, rt->exec, value, value);
790 + error("Health configuration at line %zu of file '%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
791 + line, filename, rt->name, key, rt->exec, value, value);
792
793 freez(rt->exec);
794 }
@@ -796,8 +797,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
797 else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
798 if(rt->recipient) {
799 if(strcmp(rt->recipient, value) != 0)
799 - error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
800 - line, path, filename, rt->name, key, rt->recipient, value, value);
800 + error("Health configuration at line %zu of file '%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
801 + line, filename, rt->name, key, rt->recipient, value, value);
802
803 freez(rt->recipient);
804 }
@@ -806,8 +807,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
807 else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) {
808 if(rt->units) {
809 if(strcmp(rt->units, value) != 0)
809 - error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
810 - line, path, filename, rt->name, key, rt->units, value, value);
810 + error("Health configuration at line %zu of file '%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
811 + line, filename, rt->name, key, rt->units, value, value);
812
813 freez(rt->units);
814 }
@@ -817,8 +818,8 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
818 else if(hash == hash_info && !strcasecmp(key, HEALTH_INFO_KEY)) {
819 if(rt->info) {
820 if(strcmp(rt->info, value) != 0)
820 - error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
821 - line, path, filename, rt->name, key, rt->info, value, value);
821 + error("Health configuration at line %zu of file '%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
822 + line, filename, rt->name, key, rt->info, value, value);
823
824 freez(rt->info);
825 }
@@ -826,19 +827,19 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
827 strip_quotes(rt->info);
828 }
829 else if(hash == hash_delay && !strcasecmp(key, HEALTH_DELAY_KEY)) {
829 - health_parse_delay(line, path, filename, value, &rt->delay_up_duration, &rt->delay_down_duration, &rt->delay_max_duration, &rt->delay_multiplier);
830 + health_parse_delay(line, filename, value, &rt->delay_up_duration, &rt->delay_down_duration, &rt->delay_max_duration, &rt->delay_multiplier);
831 }
832 else if(hash == hash_options && !strcasecmp(key, HEALTH_OPTIONS_KEY)) {
833 rt->options |= health_parse_options(value);
834 }
835 else {
835 - error("Health configuration at line %zu of file '%s/%s' for template '%s' has unknown key '%s'.",
836 - line, path, filename, rt->name, key);
836 + error("Health configuration at line %zu of file '%s' for template '%s' has unknown key '%s'.",
837 + line, filename, rt->name, key);
838 }
839 }
840 else {
840 - error("Health configuration at line %zu of file '%s/%s' has unknown key '%s'. Expected either '" HEALTH_ALARM_KEY "' or '" HEALTH_TEMPLATE_KEY "'.",
841 - line, path, filename, key);
841 + error("Health configuration at line %zu of file '%s' has unknown key '%s'. Expected either '" HEALTH_ALARM_KEY "' or '" HEALTH_TEMPLATE_KEY "'.",
842 + line, filename, key);
843 }
844 }
845
@@ -852,51 +853,7 @@ int health_readfile(RRDHOST *host, const char *path, const char *filename) {
853 return 1;
854 }
855
855 -void health_readdir(RRDHOST *host, const char *path) {
856 - if(!host->health_enabled) return;
857 -
858 - size_t pathlen = strlen(path);
859 -
860 - debug(D_HEALTH, "Health configuration reading directory '%s'", path);
861 -
862 - DIR *dir = opendir(path);
863 - if (!dir) {
864 - error("Health configuration cannot open directory '%s'.", path);
865 - return;
866 - }
867 -
868 - struct dirent *de = NULL;
869 - while ((de = readdir(dir))) {
870 - size_t len = strlen(de->d_name);
871 -
872 - if(de->d_type == DT_DIR
873 - && (
874 - (de->d_name[0] == '.' && de->d_name[1] == '\0')
875 - || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
876 - )) {
877 - debug(D_HEALTH, "Ignoring directory '%s'", de->d_name);
878 - continue;
879 - }
880 -
881 - else if(de->d_type == DT_DIR) {
882 - char *s = mallocz(pathlen + strlen(de->d_name) + 2);
883 - strcpy(s, path);
884 - strcat(s, "/");
885 - strcat(s, de->d_name);
886 - health_readdir(host, s);
887 - freez(s);
888 - continue;
889 - }
890 -
891 - else if((de->d_type == DT_LNK || de->d_type == DT_REG || de->d_type == DT_UNKNOWN) &&
892 - len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
893 - health_readfile(host, path, de->d_name);
894 - }
895 -
896 - else debug(D_HEALTH, "Ignoring file '%s'", de->d_name);
897 - }
898 -
899 - closedir(dir);
856 +void health_readdir(RRDHOST *host, const char *user_path, const char *stock_path, const char *subpath) {
857 + if(unlikely(!host->health_enabled)) return;
858 + recursive_config_double_dir_load(user_path, stock_path, subpath, health_readfile, (void *) host);
859 }
901 -
902 -
src/main.c
+51 -28
@@ -460,12 +460,13 @@ static void get_netdata_configured_variables() {
460 // ------------------------------------------------------------------------
461 // get system paths
462
463 - netdata_configured_config_dir = config_get(CONFIG_SECTION_GLOBAL, "config directory", CONFIG_DIR);
464 - netdata_configured_log_dir = config_get(CONFIG_SECTION_GLOBAL, "log directory", LOG_DIR);
465 - netdata_configured_web_dir = config_get(CONFIG_SECTION_GLOBAL, "web files directory", WEB_DIR);
466 - netdata_configured_cache_dir = config_get(CONFIG_SECTION_GLOBAL, "cache directory", CACHE_DIR);
467 - netdata_configured_varlib_dir = config_get(CONFIG_SECTION_GLOBAL, "lib directory", VARLIB_DIR);
468 - netdata_configured_home_dir = config_get(CONFIG_SECTION_GLOBAL, "home directory", CACHE_DIR);
463 + netdata_configured_user_config_dir = config_get(CONFIG_SECTION_GLOBAL, "config directory", netdata_configured_user_config_dir);
464 + netdata_configured_stock_config_dir = config_get(CONFIG_SECTION_GLOBAL, "stock config directory", netdata_configured_stock_config_dir);
465 + netdata_configured_log_dir = config_get(CONFIG_SECTION_GLOBAL, "log directory", netdata_configured_log_dir);
466 + netdata_configured_web_dir = config_get(CONFIG_SECTION_GLOBAL, "web files directory", netdata_configured_web_dir);
467 + netdata_configured_cache_dir = config_get(CONFIG_SECTION_GLOBAL, "cache directory", netdata_configured_cache_dir);
468 + netdata_configured_varlib_dir = config_get(CONFIG_SECTION_GLOBAL, "lib directory", netdata_configured_varlib_dir);
469 + netdata_configured_home_dir = config_get(CONFIG_SECTION_GLOBAL, "home directory", netdata_configured_home_dir);
470
471 {
472 char plugins_dirs[(FILENAME_MAX * 2) + 1];
@@ -596,15 +597,17 @@ void set_global_environment() {
597 setenv("NETDATA_UPDATE_EVERY", b, 1);
598 }
599
599 - setenv("NETDATA_HOSTNAME" , netdata_configured_hostname, 1);
600 - setenv("NETDATA_CONFIG_DIR" , verify_required_directory(netdata_configured_config_dir), 1);
601 - setenv("NETDATA_PLUGINS_DIR", verify_required_directory(netdata_configured_plugins_dir), 1);
602 - setenv("NETDATA_WEB_DIR" , verify_required_directory(netdata_configured_web_dir), 1);
603 - setenv("NETDATA_CACHE_DIR" , verify_required_directory(netdata_configured_cache_dir), 1);
604 - setenv("NETDATA_LIB_DIR" , verify_required_directory(netdata_configured_varlib_dir), 1);
605 - setenv("NETDATA_LOG_DIR" , verify_required_directory(netdata_configured_log_dir), 1);
606 - setenv("HOME" , verify_required_directory(netdata_configured_home_dir), 1);
607 - setenv("NETDATA_HOST_PREFIX", netdata_configured_host_prefix, 1);
600 + setenv("NETDATA_HOSTNAME" , netdata_configured_hostname, 1);
601 + setenv("NETDATA_CONFIG_DIR" , verify_required_directory(netdata_configured_user_config_dir), 1);
602 + setenv("NETDATA_USER_CONFIG_DIR" , verify_required_directory(netdata_configured_user_config_dir), 1);
603 + setenv("NETDATA_STOCK_CONFIG_DIR" , verify_required_directory(netdata_configured_stock_config_dir), 1);
604 + setenv("NETDATA_PLUGINS_DIR" , verify_required_directory(netdata_configured_plugins_dir), 1);
605 + setenv("NETDATA_WEB_DIR" , verify_required_directory(netdata_configured_web_dir), 1);
606 + setenv("NETDATA_CACHE_DIR" , verify_required_directory(netdata_configured_cache_dir), 1);
607 + setenv("NETDATA_LIB_DIR" , verify_required_directory(netdata_configured_varlib_dir), 1);
608 + setenv("NETDATA_LOG_DIR" , verify_required_directory(netdata_configured_log_dir), 1);
609 + setenv("HOME" , verify_required_directory(netdata_configured_home_dir), 1);
610 + setenv("NETDATA_HOST_PREFIX" , netdata_configured_host_prefix, 1);
611
612 get_system_timezone();
613
@@ -626,6 +629,33 @@ void set_global_environment() {
629 setenv("LC_ALL", "C", 1);
630 }
631
632 +static int load_netdata_conf(char *filename, char overwrite_used) {
633 + if(filename)
634 + return config_load(filename, overwrite_used);
635 +
636 + filename = strdupz_path_subpath(netdata_configured_user_config_dir, "netdata.conf");
637 +
638 + int ret = config_load(filename, overwrite_used);
639 + if(!ret) {
640 + freez(filename);
641 + filename = strdupz_path_subpath(netdata_configured_stock_config_dir, "netdata.conf");
642 + ret = config_load(filename, overwrite_used);
643 + }
644 + freez(filename);
645 +
646 + return ret;
647 +}
648 +
649 +static void load_stream_conf() {
650 + char *filename = strdupz_path_subpath(netdata_configured_user_config_dir, "stream.conf");
651 + if(!appconfig_load(&stream_config, filename, 0)) {
652 + freez(filename);
653 + filename = strdupz_path_subpath(netdata_configured_stock_config_dir, "stream.conf");
654 + appconfig_load(&stream_config, filename, 0);
655 + }
656 + freez(filename);
657 +}
658 +
659 int main(int argc, char **argv) {
660 int i;
661 int config_loaded = 0;
@@ -686,7 +716,7 @@ int main(int argc, char **argv) {
716 while( (opt = getopt(argc, argv, optstring)) != -1 ) {
717 switch(opt) {
718 case 'c':
689 - if(config_load(optarg, 1) != 1) {
719 + if(load_netdata_conf(optarg, 1) != 1) {
720 error("Cannot load configuration file %s.", optarg);
721 return 1;
722 }
@@ -731,9 +761,6 @@ int main(int argc, char **argv) {
761 if(strcmp(optarg, "unittest") == 0) {
762 if(unit_test_buffer()) return 1;
763 if(unit_test_str2ld()) return 1;
734 - //default_rrd_update_every = 1;
735 - //default_rrd_memory_mode = RRD_MEMORY_MODE_RAM;
736 - //if(!config_loaded) config_load(NULL, 0);
764 get_netdata_configured_variables();
765 default_rrd_update_every = 1;
766 default_rrd_memory_mode = RRD_MEMORY_MODE_RAM;
@@ -841,7 +868,7 @@ int main(int argc, char **argv) {
868
869 if(!config_loaded) {
870 fprintf(stderr, "warning: no configuration file has been loaded. Use -c CONFIG_FILE, before -W get. Using default config.\n");
844 - config_load(NULL, 0);
871 + load_netdata_conf(NULL, 0);
872 }
873
874 backwards_compatible_config();
@@ -879,7 +906,7 @@ int main(int argc, char **argv) {
906 #endif
907
908 if(!config_loaded)
882 - config_load(NULL, 0);
909 + load_netdata_conf(NULL, 0);
910
911 // ------------------------------------------------------------------------
912 // initialize netdata
@@ -902,8 +929,8 @@ int main(int argc, char **argv) {
929 // work while we are cd into config_dir
930 // to allow the plugins refer to their config
931 // files using relative filenames
905 - if(chdir(netdata_configured_config_dir) == -1)
906 - fatal("Cannot cd to '%s'", netdata_configured_config_dir);
932 + if(chdir(netdata_configured_user_config_dir) == -1)
933 + fatal("Cannot cd to '%s'", netdata_configured_user_config_dir);
934 }
935
936 char *user = NULL;
@@ -938,11 +965,7 @@ int main(int argc, char **argv) {
965
966 // --------------------------------------------------------------------
967 // load stream.conf
941 - {
942 - char filename[FILENAME_MAX + 1];
943 - snprintfz(filename, FILENAME_MAX, "%s/stream.conf", netdata_configured_config_dir);
944 - appconfig_load(&stream_config, filename, 0);
945 - }
968 + load_stream_conf();
969
970
971 // --------------------------------------------------------------------
src/rrdhost.c
+1 -1
@@ -240,7 +240,7 @@ RRDHOST *rrdhost_create(const char *hostname,
240 health_alarm_log_open(host);
241
242 rrdhost_wrlock(host);
243 - health_readdir(host, health_config_dir());
243 + health_readdir(host, health_user_config_dir(), health_stock_config_dir(), NULL);
244 rrdhost_unlock(host);
245 }
246
src/statsd.c
+39 -71
@@ -1031,7 +1031,7 @@ void *statsd_collector_thread(void *ptr) {
1031
1032 #define STATSD_CONF_LINE_MAX 8192
1033
1034 -static STATSD_APP_CHART_DIM_VALUE_TYPE string2valuetype(const char *type, size_t line, const char *path, const char *filename) {
1034 +static STATSD_APP_CHART_DIM_VALUE_TYPE string2valuetype(const char *type, size_t line, const char *filename) {
1035 if(!type || !*type) type = "last";
1036
1037 if(!strcmp(type, "events")) return STATSD_APP_CHART_DIM_VALUE_TYPE_EVENTS;
@@ -1044,7 +1044,7 @@ static STATSD_APP_CHART_DIM_VALUE_TYPE string2valuetype(const char *type, size_t
1044 else if(!strcmp(type, "stddev")) return STATSD_APP_CHART_DIM_VALUE_TYPE_STDDEV;
1045 else if(!strcmp(type, "percentile")) return STATSD_APP_CHART_DIM_VALUE_TYPE_PERCENTILE;
1046
1047 - error("STATSD: invalid type '%s' at line %zu of file '%s/%s'. Using 'last'.", type, line, path, filename);
1047 + error("STATSD: invalid type '%s' at line %zu of file '%s'. Using 'last'.", type, line, filename);
1048 return STATSD_APP_CHART_DIM_VALUE_TYPE_LAST;
1049 }
1050
@@ -1110,19 +1110,14 @@ static STATSD_APP_CHART_DIM *add_dimension_to_app_chart(
1110 return dim;
1111 }
1112
1113 -static int statsd_readfile(const char *path, const char *filename, STATSD_APP *app, STATSD_APP_CHART *chart, DICTIONARY *dict) {
1114 - debug(D_STATSD, "STATSD configuration reading file '%s/%s'", path, filename);
1113 +static int statsd_readfile(const char *filename, STATSD_APP *app, STATSD_APP_CHART *chart, DICTIONARY *dict) {
1114 + debug(D_STATSD, "STATSD configuration reading file '%s'", filename);
1115
1116 char *buffer = mallocz(STATSD_CONF_LINE_MAX + 1);
1117
1118 - if(filename[0] == '/')
1119 - strncpyz(buffer, filename, STATSD_CONF_LINE_MAX);
1120 - else
1121 - snprintfz(buffer, STATSD_CONF_LINE_MAX, "%s/%s", path, filename);
1122 -
1123 - FILE *fp = fopen(buffer, "r");
1118 + FILE *fp = fopen(filename, "r");
1119 if(!fp) {
1125 - error("STATSD: cannot open file '%s'.", buffer);
1120 + error("STATSD: cannot open file '%s'.", filename);
1121 freez(buffer);
1122 return -1;
1123 }
@@ -1135,18 +1130,31 @@ static int statsd_readfile(const char *path, const char *filename, STATSD_APP *a
1130
1131 s = trim(buffer);
1132 if (!s || *s == '#') {
1138 - debug(D_STATSD, "STATSD: ignoring line %zu of file '%s/%s', it is empty.", line, path, filename);
1133 + debug(D_STATSD, "STATSD: ignoring line %zu of file '%s', it is empty.", line, filename);
1134 continue;
1135 }
1136
1142 - debug(D_STATSD, "STATSD: processing line %zu of file '%s/%s': %s", line, path, filename, buffer);
1137 + debug(D_STATSD, "STATSD: processing line %zu of file '%s': %s", line, filename, buffer);
1138
1139 if(*s == 'i' && strncmp(s, "include", 7) == 0) {
1140 s = trim(&s[7]);
1146 - if(s && *s)
1147 - statsd_readfile(path, s, app, chart, dict);
1141 + if(s && *s) {
1142 + char *tmp;
1143 + if(*s == '/')
1144 + tmp = strdupz(s);
1145 + else {
1146 + // the file to be included is relative to current file
1147 + // find the directory name from the file we already read
1148 + char *filename2 = strdupz(filename); // copy filename, since dirname() will change it
1149 + char *dir = dirname(filename2); // find the directory part of the filename
1150 + tmp = strdupz_path_subpath(dir, s); // compose the new filename to read;
1151 + freez(filename2); // free the filename we copied
1152 + }
1153 + statsd_readfile(tmp, app, chart, dict);
1154 + freez(tmp);
1155 + }
1156 else
1149 - error("STATSD: ignoring line %zu of file '%s/%s', include filename is empty", line, path, s);
1157 + error("STATSD: ignoring line %zu of file '%s', include filename is empty", line, filename);
1158
1159 continue;
1160 }
@@ -1208,20 +1216,20 @@ static int statsd_readfile(const char *path, const char *filename, STATSD_APP *a
1216 }
1217 }
1218 else
1211 - error("STATSD: ignoring line %zu ('%s') of file '%s/%s', [app] is not defined.", line, s, path, filename);
1219 + error("STATSD: ignoring line %zu ('%s') of file '%s', [app] is not defined.", line, s, filename);
1220
1221 continue;
1222 }
1223
1224 if(!app) {
1217 - error("STATSD: ignoring line %zu ('%s') of file '%s/%s', it is outside all sections.", line, s, path, filename);
1225 + error("STATSD: ignoring line %zu ('%s') of file '%s', it is outside all sections.", line, s, filename);
1226 continue;
1227 }
1228
1229 char *name = s;
1230 char *value = strchr(s, '=');
1231 if(!value) {
1224 - error("STATSD: ignoring line %zu ('%s') of file '%s/%s', there is no = in it.", line, s, path, filename);
1232 + error("STATSD: ignoring line %zu ('%s') of file '%s', there is no = in it.", line, s, filename);
1233 continue;
1234 }
1235 *value = '\0';
@@ -1231,11 +1239,11 @@ static int statsd_readfile(const char *path, const char *filename, STATSD_APP *a
1239 value = trim(value);
1240
1241 if(!name || *name == '#') {
1234 - error("STATSD: ignoring line %zu of file '%s/%s', name is empty.", line, path, filename);
1242 + error("STATSD: ignoring line %zu of file '%s', name is empty.", line, filename);
1243 continue;
1244 }
1245 if(!value) {
1238 - debug(D_CONFIG, "STATSD: ignoring line %zu of file '%s/%s', value is empty.", line, path, filename);
1246 + debug(D_CONFIG, "STATSD: ignoring line %zu of file '%s', value is empty.", line, filename);
1247 continue;
1248 }
1249
@@ -1275,7 +1283,7 @@ static int statsd_readfile(const char *path, const char *filename, STATSD_APP *a
1283 app->rrd_history_entries = 5;
1284 }
1285 else {
1278 - error("STATSD: ignoring line %zu ('%s') of file '%s/%s'. Unknown keyword for the [app] section.", line, name, path, filename);
1286 + error("STATSD: ignoring line %zu ('%s') of file '%s'. Unknown keyword for the [app] section.", line, name, filename);
1287 continue;
1288 }
1289 }
@@ -1360,14 +1368,14 @@ static int statsd_readfile(const char *path, const char *filename, STATSD_APP *a
1368 , (multipler && *multipler)?str2l(multipler):1
1369 , (divisor && *divisor)?str2l(divisor):1
1370 , flags
1363 - , string2valuetype(type, line, path, filename)
1371 + , string2valuetype(type, line, filename)
1372 );
1373
1374 if(pattern)
1375 dim->metric_pattern = simple_pattern_create(dim->metric, NULL, SIMPLE_PATTERN_EXACT);
1376 }
1377 else {
1370 - error("STATSD: ignoring line %zu ('%s') of file '%s/%s'. Unknown keyword for the [%s] section.", line, name, path, filename, chart->id);
1378 + error("STATSD: ignoring line %zu ('%s') of file '%s'. Unknown keyword for the [%s] section.", line, name, filename, chart->id);
1379 continue;
1380 }
1381 }
@@ -1378,49 +1386,13 @@ static int statsd_readfile(const char *path, const char *filename, STATSD_APP *a
1386 return 0;
1387 }
1388
1381 -static void statsd_readdir(const char *path) {
1382 - size_t pathlen = strlen(path);
1383 -
1384 - debug(D_STATSD, "STATSD configuration reading directory '%s'", path);
1385 -
1386 - DIR *dir = opendir(path);
1387 - if (!dir) {
1388 - error("STATSD configuration cannot open directory '%s'.", path);
1389 - return;
1390 - }
1391 -
1392 - struct dirent *de = NULL;
1393 - while ((de = readdir(dir))) {
1394 - size_t len = strlen(de->d_name);
1395 -
1396 - if(de->d_type == DT_DIR
1397 - && (
1398 - (de->d_name[0] == '.' && de->d_name[1] == '\0')
1399 - || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
1400 - )) {
1401 - debug(D_STATSD, "STATSD: ignoring directory '%s'", de->d_name);
1402 - continue;
1403 - }
1404 -
1405 - else if(de->d_type == DT_DIR) {
1406 - char *s = mallocz(pathlen + strlen(de->d_name) + 2);
1407 - strcpy(s, path);
1408 - strcat(s, "/");
1409 - strcat(s, de->d_name);
1410 - statsd_readdir(s);
1411 - freez(s);
1412 - continue;
1413 - }
1414 -
1415 - else if((de->d_type == DT_LNK || de->d_type == DT_REG || de->d_type == DT_UNKNOWN) &&
1416 - len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
1417 - statsd_readfile(path, de->d_name, NULL, NULL, NULL);
1418 - }
1419 -
1420 - else debug(D_STATSD, "STATSD: ignoring file '%s'", de->d_name);
1421 - }
1389 +static int statsd_file_callback(const char *filename, void *data) {
1390 + (void)data;
1391 + return statsd_readfile(filename, NULL, NULL, NULL);
1392 +}
1393
1423 - closedir(dir);
1394 +static inline void statsd_readdir(const char *user_path, const char *stock_path, const char *subpath) {
1395 + recursive_config_double_dir_load(user_path, stock_path, subpath, statsd_file_callback, NULL);
1396 }
1397
1398 // --------------------------------------------------------------------------------------------------------------------
@@ -2243,11 +2215,7 @@ void *statsd_main(void *ptr) {
2215 #endif
2216
2217 // read custom application definitions
2246 - {
2247 - char filename[FILENAME_MAX + 1];
2248 - snprintfz(filename, FILENAME_MAX, "%s/statsd.d", netdata_configured_config_dir);
2249 - statsd_readdir(filename);
2250 - }
2218 + statsd_readdir(netdata_configured_user_config_dir, netdata_configured_stock_config_dir, "statsd.d");
2219
2220 // ----------------------------------------------------------------------------------------------------------------
2221 // statsd setup
src/unit_test.c
+42
@@ -1199,8 +1199,50 @@ static int test_variable_renames(void) {
1199 return 1;
1200 }
1201
1202 +int check_strdupz_path_subpath() {
1203 +
1204 + struct strdupz_path_subpath_checks {
1205 + const char *path;
1206 + const char *subpath;
1207 + const char *result;
1208 + } checks[] = {
1209 + { "", "", "." },
1210 + { "/", "", "/" },
1211 + { "/etc/netdata", "", "/etc/netdata" },
1212 + { "/etc/netdata///", "", "/etc/netdata" },
1213 + { "/etc/netdata///", "health.d", "/etc/netdata/health.d" },
1214 + { "/etc/netdata///", "///health.d", "/etc/netdata/health.d" },
1215 + { "/etc/netdata", "///health.d", "/etc/netdata/health.d" },
1216 + { "", "///health.d", "./health.d" },
1217 + { "/", "///health.d", "/health.d" },
1218 +
1219 + // terminator
1220 + { NULL, NULL, NULL }
1221 + };
1222 +
1223 + size_t i;
1224 + for(i = 0; checks[i].result ; i++) {
1225 + char *s = strdupz_path_subpath(checks[i].path, checks[i].subpath);
1226 + fprintf(stderr, "strdupz_path_subpath(\"%s\", \"%s\") = \"%s\": ", checks[i].path, checks[i].subpath, s);
1227 + if(!s || strcmp(s, checks[i].result) != 0) {
1228 + freez(s);
1229 + fprintf(stderr, "FAILED\n");
1230 + return 1;
1231 + }
1232 + else {
1233 + freez(s);
1234 + fprintf(stderr, "OK\n");
1235 + }
1236 + }
1237 +
1238 + return 0;
1239 +}
1240 +
1241 int run_all_mockup_tests(void)
1242 {
1243 + if(check_strdupz_path_subpath())
1244 + return 1;
1245 +
1246 if(check_number_printing())
1247 return 1;
1248