3 export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
6 PROGRAM_NAME="$(basename $0)"
7 PROGRAM_NAME="${PROGRAM_NAME/.plugin}"
10 # if you need to run parallel charts.d processes
11 # just link this files with a different name
12 # in the same directory, with a .plugin suffix
13 # netdata will start multiple of them
14 # each will have a different config file
16 # -----------------------------------------------------------------------------
22 if [ ! -z "$TMP_DIR" -a -d "$TMP_DIR" ]
24 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: cleaning up temporary directory $TMP_DIR ..."
29 trap chartsd_cleanup EXIT
30 trap chartsd_cleanup SIGHUP
31 trap chartsd_cleanup INT
35 TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
37 TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
41 date "+%Y-%m-%d %H:%M:%S"
48 echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${MODULE_NAME}: ${*}"
71 [ $debug -eq 1 ] && log DEBUG "${@}"
74 # -----------------------------------------------------------------------------
75 # check a few commands
78 local x=$(which "${1}" 2>/dev/null || command -v "${1}" 2>/dev/null)
79 if [ -z "${x}" -o ! -x "${x}" ]
81 warning "command '${1}' is not found in ${PATH}."
82 eval "${1^^}_CMD=\"\""
86 eval "${1^^}_CMD=\"${x}\""
90 require_cmd date || exit 1
91 require_cmd sed || exit 1
92 require_cmd basename || exit 1
93 require_cmd dirname || exit 1
94 require_cmd cat || exit 1
95 require_cmd grep || exit 1
96 require_cmd egrep || exit 1
97 require_cmd mktemp || exit 1
98 require_cmd awk || exit 1
99 require_cmd timeout || exit 1
100 require_cmd curl || exit 1
102 # -----------------------------------------------------------------------------
104 [ $(( ${BASH_VERSINFO[0]} )) -lt 4 ] && fatal "BASH version 4 or later is required, but found version: ${BASH_VERSION}. Please upgrade."
106 info "started from '$PROGRAM_FILE' with options: $*"
108 # -----------------------------------------------------------------------------
110 # netdata exposes a few environment variables for us
112 pluginsd="${NETDATA_PLUGINS_DIR}"
113 [ -z "$pluginsd" ] && pluginsd="$( dirname $PROGRAM_FILE )"
115 confd="${NETDATA_CONFIG_DIR-/etc/netdata}"
116 chartsd="$pluginsd/../charts.d"
118 myconfig="$confd/$PROGRAM_NAME.conf"
120 minimum_update_frequency="${NETDATA_UPDATE_EVERY-1}"
121 update_every=${minimum_update_frequency} # this will be overwritten by the command line
123 # work around for non BASH shells
124 charts_create="_create"
125 charts_update="_update"
126 charts_check="_check"
129 # when making iterations, charts.d can loop more frequently
130 # to prevent plugins missing iterations.
131 # this is a percentage relative to update_every to align its
133 # The minimum is 10%, the maximum 100%.
134 # So, if update_every is 1 second and time_divisor is 50,
135 # charts.d will iterate every 500ms.
136 # Charts will be called to collect data only if the time
137 # passed since the last time the collected data is equal or
138 # above their update_every.
141 # number of seconds to run without restart
142 # after this time, charts.d.plugin will exit
143 # netdata will restart it
144 restart_timeout=$((3600 * 4))
146 # check if the charts.d plugins are using global variables
148 # It does not currently support BASH v4 arrays, so it is
152 # check for timeout command
155 # the default enable/disable value for all charts
156 enable_all_charts="yes"
158 # -----------------------------------------------------------------------------
165 if [ "$1" = "check" ]
172 if [ "$1" = "debug" -o "$1" = "all" ]
179 if [ -f "$chartsd/$1.chart.sh" ]
182 chart_only="$( echo $1.chart.sh | sed "s/\.chart\.sh$//g" )"
187 if [ -f "$chartsd/$1" ]
190 chart_only="$( echo $1 | sed "s/\.chart\.sh$//g" )"
202 [ $update_every -lt $minimum_update_frequency ] && update_every=$minimum_update_frequency
206 fatal "Cannot understand parameter $1. Aborting."
210 # -----------------------------------------------------------------------------
213 # default sleep function
214 LOOPSLEEPMS_HIGHRES=0
216 current_time_ms_default() {
217 now_ms="$(date +'%s')000"
219 current_time_ms="current_time_ms_default"
220 current_time_ms_accuracy=1
223 # if found and included, this file overwrites loopsleepms()
224 # and current_time_ms() with a high resolution timer function
225 # for precise looping.
226 . "$pluginsd/loopsleepms.sh.inc"
228 # -----------------------------------------------------------------------------
229 # load my configuration
231 if [ -f "$myconfig" ]
234 [ $? -ne 0 ] && fatal "cannot load $myconfig"
236 time_divisor=$((time_divisor))
237 [ $time_divisor -lt 10 ] && time_divisor=10
238 [ $time_divisor -gt 100 ] && time_divisor=100
240 info "configuration file '$myconfig' not found. Using defaults."
243 # we check for the timeout command, after we load our
244 # configuration, so that the user may overwrite the
245 # timeout command we use, providing a function that
246 # can emulate the timeout command we need:
247 # > timeout SECONDS command ...
248 if [ $check_for_timeout -eq 1 ]
250 require_cmd timeout || exit 1
253 # -----------------------------------------------------------------------------
256 # netdata passes the requested update frequency as the first argument
257 update_every=$(( update_every + 1 - 1)) # makes sure it is a number
258 test $update_every -eq 0 && update_every=1 # if it is zero, make it 1
260 # check the charts.d directory
261 [ ! -d "$chartsd" ] && fatal "cannot find charts directory '$chartsd'"
263 # -----------------------------------------------------------------------------
268 tr -c "[A-Z][a-z][0-9]" "_" |\
269 sed -e "s|^_\+||g" -e "s|_\+$||g" -e "s|_\+|_|g" |\
274 local ret pid="${BASHPID}" t
276 if [ "z${1}" = "z-t" -a "${2}" != "0" ]
280 timeout ${t} "${@}" 2>"${TMP_DIR}/run.${pid}"
283 "${@}" 2>"${TMP_DIR}/run.${pid}"
290 printf "$(logdate): ${PROGRAM_NAME}: ${status}: ${MODULE_NAME}: command '"
292 printf "' failed:\n --- BEGIN TRACE ---\n"
293 cat "${TMP_DIR}/run.${pid}"
294 printf " --- END TRACE ---\n"
297 rm "${TMP_DIR}/run.${pid}"
302 # convert any floating point number
303 # to integer, give a multiplier
304 # the result is stored in ${FLOAT2INT_RESULT}
305 # so that no fork is necessary
306 # the multiplier must be a power of 10
308 local f m="$2" a b l v=($1)
311 # the length of the multiplier - 1
314 # check if the number is in scientific notation
315 if [[ ${f} =~ ^[[:space:]]*(-)?[0-9.]+(e|E)(\+|-)[0-9]+ ]]
317 # convert it to decimal
318 # unfortunately, this fork cannot be avoided
319 # if you know of a way to avoid it, please let me know
320 f=$(printf "%0.${l}f" ${f})
323 # split the floating point number
324 # in integer (a) and decimal (b)
328 # if the integer part is missing
330 [ -z "${a}" ] && a="0"
332 # strip leading zeros from the integer part
336 # check the length of the decimal part
337 # against the length of the multiplier
338 if [ ${#b} -gt ${l} ]
340 # too many digits - take the most significant
343 elif [ ${#b} -lt ${l} ]
345 # too few digits - pad with zero on the right
346 local z="00000000000000000000000" r=$((l - ${#b}))
350 # strip leading zeros from the decimal part
355 FLOAT2INT_RESULT=$(( (a * m) + b ))
359 # -----------------------------------------------------------------------------
360 # charts check functions
364 [ $? -ne 0 ] && error "cannot cd to $chartsd" && return 1
366 ls *.chart.sh | sed "s/\.chart\.sh$//g"
369 declare -A charts_enable_keyword=(
376 ['load_average']="force"
387 all_enabled_charts() {
388 local charts= enabled= required=
390 # find all enabled charts
392 for chart in $( all_charts )
394 MODULE_NAME="${chart}"
396 eval "enabled=\$$chart"
397 if [ -z "${enabled}" ]
399 enabled="${enable_all_charts}"
402 required="${charts_enable_keyword[${chart}]}"
403 [ -z "${required}" ] && required="yes"
405 if [ ! "${enabled}" = "${required}" ]
407 info "is disabled. Add a line with $chart=$required in $myconfig to enable it (or remove the line that disables it)."
409 debug "is enabled for auto-detection."
410 local charts="$charts $chart"
418 MODULE_NAME="${chart}"
420 # check the enabled charts
421 local check="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_check()" )"
424 error "module '$chart' does not seem to have a $chart$charts_check() function. Disabling it."
428 local create="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_create()" )"
431 error "module '$chart' does not seem to have a $chart$charts_create() function. Disabling it."
435 local update="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_update()" )"
438 error "module '$chart' does not seem to have a $chart$charts_update() function. Disabling it."
443 #if [ -f "$confd/$chart.conf" ]
445 # if [ ! -z "$( cat "$confd/$chart.conf" | sed "s/^ \+//g" | grep -v "^$" | grep -v "^#" | grep -v "^$chart$charts_undescore" )" ]
447 # error "module's $chart config $confd/$chart.conf should only have lines starting with $chart$charts_undescore . Disabling it."
452 #if [ $dryrunner -eq 1 ]
454 # "$pluginsd/charts.d.dryrun-helper.sh" "$chart" "$chartsd/$chart.chart.sh" "$confd/$chart.conf" >/dev/null
457 # error "module's $chart did not pass the dry run check. This means it uses global variables not starting with $chart. Disabling it."
462 local charts2="$charts2 $chart"
467 debug "enabled charts: $charts2"
470 # -----------------------------------------------------------------------------
473 suffix_update_every="_update_every"
475 for chart in $( all_enabled_charts )
477 MODULE_NAME="${chart}"
479 debug "loading module: '$chartsd/$chart.chart.sh'"
481 . "$chartsd/$chart.chart.sh"
483 if [ -f "$confd/$PROGRAM_NAME/$chart.conf" ]
485 debug "loading module configuration: '$confd/$PROGRAM_NAME/$chart.conf'"
486 . "$confd/$PROGRAM_NAME/$chart.conf"
487 elif [ -f "$confd/$chart.conf" ]
489 debug "loading module configuration: '$confd/$chart.conf'"
490 . "$confd/$chart.conf"
492 warning "configuration file '$confd/$PROGRAM_NAME/$chart.conf' not found. Using defaults."
495 eval "dt=\$$chart$suffix_update_every"
496 dt=$(( dt + 1 - 1 )) # make sure it is a number
497 if [ $dt -lt $update_every ]
499 eval "$chart$suffix_update_every=$update_every"
505 debug "module '$chart' activated"
506 active_charts="$active_charts $chart"
508 error "module's '$chart' check() function reports failure."
512 debug "activated modules: $active_charts"
515 # -----------------------------------------------------------------------------
518 # enable work time reporting
520 test $debug -eq 1 && debug_time=tellwork
522 # if we only need a specific chart, remove all the others
523 if [ ! -z "${chart_only}" ]
525 debug "requested to run only for: '${chart_only}'"
527 for chart in $active_charts
529 if [ "$chart" = "$chart_only" ]
531 check_charts="$chart"
535 active_charts="$check_charts"
537 debug "activated charts: $active_charts"
539 # stop if we just need a pre-check
543 info "Will run the charts: $active_charts"
547 # -----------------------------------------------------------------------------
549 cd "${TMP_DIR}" || exit 1
551 # -----------------------------------------------------------------------------
555 for chart in $active_charts
557 MODULE_NAME="${chart}"
559 debug "calling '$chart$charts_create()'..."
563 run_charts="$run_charts $chart"
564 debug "'$chart' initialized."
566 error "module's '$chart' function '$chart$charts_create()' reports failure."
570 debug "run_charts='$run_charts'"
573 # -----------------------------------------------------------------------------
576 [ -z "$run_charts" ] && fatal "No charts to collect data from."
578 declare -A charts_last_update=() charts_update_every=() charts_next_update=() charts_run_counter=() charts_serial_failures=()
581 c=0 dt ret last_ms exec_start_ms exec_end_ms \
582 chart now_charts=() next_charts=($run_charts) \
583 next_ms x seconds millis
585 # return the current time in ms in $now_ms
588 exit_at=$(( now_ms + (restart_timeout * 1000) ))
590 for chart in $run_charts
592 eval "charts_update_every[$chart]=\$$chart$suffix_update_every"
593 test -z "${charts_update_every[$chart]}" && charts_update_every[$charts]=$update_every
594 charts_last_update[$chart]=$((now_ms - (now_ms % (charts_update_every[$chart] * 1000) ) ))
595 charts_next_update[$chart]=$(( charts_last_update[$chart] + (charts_update_every[$chart] * 1000) ))
596 charts_run_counter[$chart]=0
597 charts_serial_failures[$chart]=0
599 echo "CHART netdata.plugin_chartsd_$chart '' 'Execution time for $chart plugin' 'milliseconds / run' charts.d netdata.plugin_charts area 145000 ${charts_update_every[$chart]}"
600 echo "DIMENSION run_time 'run time' absolute 1 1"
604 while [ "${#next_charts[@]}" -gt 0 ]
607 now_charts=("${next_charts[@]}")
610 # return the current time in ms in $now_ms
613 for chart in "${now_charts[@]}"
615 MODULE_NAME="${chart}"
617 if [ ${now_ms} -ge ${charts_next_update[$chart]} ]
619 last_ms=${charts_last_update[$chart]}
620 dt=$(( (now_ms - last_ms) ))
622 charts_last_update[$chart]=${now_ms}
624 while [ ${charts_next_update[$chart]} -lt ${now_ms} ]
626 charts_next_update[$chart]=$(( charts_next_update[$chart] + (charts_update_every[$chart] * 1000) ))
629 # the first call should not give a duration
630 # so that netdata calibrates to current time
632 charts_run_counter[$chart]=$(( charts_run_counter[$chart] + 1 ))
633 if [ ${charts_run_counter[$chart]} -eq 1 ]
638 exec_start_ms=$now_ms
639 $chart$charts_update $dt
642 # return the current time in ms in $now_ms
643 ${current_time_ms}; exec_end_ms=$now_ms
645 echo "BEGIN netdata.plugin_chartsd_$chart $dt"
646 echo "SET run_time = $(( exec_end_ms - exec_start_ms ))"
651 charts_serial_failures[$chart]=0
652 next_charts+=($chart)
654 charts_serial_failures[$chart]=$(( charts_serial_failures[$chart] + 1 ))
656 if [ ${charts_serial_failures[$chart]} -gt 10 ]
658 error "module's '$chart' update() function reported failure ${charts_serial_failures[$chart]} times. Disabling it."
660 error "module's '$chart' update() function reports failure. Will keep trying for a while."
661 next_charts+=($chart)
665 next_charts+=($chart)
668 MODULE_NAME="${chart}"
670 # wait the time you are required to
671 next_ms=$((now_ms + (update_every * 1000 * 100) ))
672 for x in "${charts_next_update[@]}"; do [ ${x} -lt ${next_ms} ] && next_ms=${x}; done
673 next_ms=$((next_ms - now_ms))
675 if [ ${LOOPSLEEPMS_HIGHRES} -eq 1 -a ${next_ms} -gt 0 ]
677 next_ms=$(( next_ms + current_time_ms_accuracy ))
678 seconds=$(( next_ms / 1000 ))
679 millis=$(( next_ms % 1000 ))
680 if [ ${millis} -lt 10 ]
683 elif [ ${millis} -lt 100 ]
688 debug "sleeping for ${seconds}.${millis} seconds."
689 ${mysleep} ${seconds}.${millis}
691 debug "sleeping for ${update_every} seconds."
692 ${mysleep} $update_every
695 test ${now_ms} -ge ${exit_at} && exit 0
698 fatal "nothing left to do, exiting..."