4 PROGRAM_NAME="$(basename $0)"
5 PROGRAM_NAME="${PROGRAM_NAME/.plugin}"
7 # if you need to run parallel charts.d processes
8 # just link this files with a different name
9 # in the same directory, with a .plugin suffix
10 # netdata will start multiple of them
11 # each will have a different config file
13 echo >&2 "$PROGRAM_NAME: started from '$PROGRAM_FILE' with options: $*"
15 if [ $(( ${BASH_VERSINFO[0]} )) -lt 4 ]
18 echo >&2 "$PROGRAM_NAME: ERROR"
19 echo >&2 "BASH version 4 or later is required."
20 echo >&2 "You are running version: ${BASH_VERSION}"
21 echo >&2 "Please upgrade."
26 # check a few commands
31 echo >&2 "$PROGRAM_NAME: ERROR: Command '$1' is not found in the system path."
37 require_cmd date || exit 1
38 require_cmd sed || exit 1
39 require_cmd basename || exit 1
40 require_cmd dirname || exit 1
41 require_cmd cat || exit 1
42 require_cmd grep || exit 1
43 require_cmd egrep || exit 1
44 require_cmd mktemp || exit 1
45 require_cmd awk || exit 1
47 # -----------------------------------------------------------------------------
49 # netdata exposes a few environment variables for us
51 pause_method="sleep" # use either "suspend" or "sleep"
52 # DO NOT USE SUSPEND - LINUX WILL SUSPEND NETDATA TOO
53 # THE WHOLE PROCESS GROUP - NOT JUST THE SHELL
55 pluginsd="${NETDATA_PLUGINS_DIR}"
56 [ -z "$pluginsd" ] && pluginsd="$( dirname $PROGRAM_FILE )"
58 confd="${NETDATA_CONFIG_DIR-/etc/netdata}"
59 chartsd="$pluginsd/../charts.d"
61 myconfig="$confd/$PROGRAM_NAME.conf"
63 minimum_update_frequency="${NETDATA_UPDATE_EVERY-1}"
64 update_every=${minimum_update_frequency} # this will be overwritten by the command line
66 # work around for non BASH shells
67 charts_create="_create"
68 charts_update="_update"
72 # when making iterations, charts.d can loop more frequently
73 # to prevent plugins missing iterations.
74 # this is a percentage relative to update_every to align its
76 # The minimum is 10%, the maximum 100%.
77 # So, if update_every is 1 second and time_divisor is 50,
78 # charts.d will iterate every 500ms.
79 # Charts will be called to collect data only if the time
80 # passed since the last time the collected data is equal or
81 # above their update_every.
84 # number of seconds to run without restart
85 # after this time, charts.d.plugin will exit
86 # netdata will restart it
87 restart_timeout=$[3600 * 4]
89 # check if the charts.d plugins are using global variables
91 # It does not currently support BASH v4 arrays, so it is
95 # check for timeout command
98 # the default enable/disable value for all charts
99 enable_all_charts="yes"
101 # -----------------------------------------------------------------------------
109 if [ "$1" = "check" ]
116 if [ "$1" = "debug" -o "$1" = "all" ]
123 if [ -f "$chartsd/$1.chart.sh" ]
126 chart_only="$( echo $1.chart.sh | sed "s/\.chart\.sh$//g" )"
131 if [ -f "$chartsd/$1" ]
134 chart_only="$( echo $1 | sed "s/\.chart\.sh$//g" )"
146 [ $update_every -lt $minimum_update_frequency ] && update_every=$minimum_update_frequency
150 echo >&2 "Cannot understand parameter $1. Aborting."
156 # -----------------------------------------------------------------------------
157 # load my configuration
159 if [ -f "$myconfig" ]
164 echo >&2 "$PROGRAM_NAME: cannot load $myconfig"
168 time_divisor=$((time_divisor))
169 [ $time_divisor -lt 10 ] && time_divisor=10
170 [ $time_divisor -gt 100 ] && time_divisor=100
172 echo >&2 "$PROGRAM_NAME: configuration file '$myconfig' not found. Using defaults."
175 if [ "$pause_method" = "suspend" ]
177 # enable bash job control
178 # this is required for suspend to work
182 # we check for the timeout command, after we load our
183 # configuration, so that the user may overwrite the
184 # timeout command we use, providing a function that
185 # can emulate the timeout command we need:
186 # > timeout SECONDS command ...
187 if [ $check_for_timeout -eq 1 ]
189 require_cmd timeout || exit 1
192 # -----------------------------------------------------------------------------
195 # netdata passes the requested update frequency as the first argument
196 update_every=$(( update_every + 1 - 1)) # makes sure it is a number
197 test $update_every -eq 0 && update_every=1 # if it is zero, make it 1
199 # check the charts.d directory
200 if [ ! -d "$chartsd" ]
202 echo >&2 "$PROGRAM_NAME: cannot find charts directory '$chartsd'"
207 # -----------------------------------------------------------------------------
210 # default sleep function
211 LOOPSLEEPMS_HIGHRES=0
213 [ "$1" = "tellwork" ] && shift
219 now_ms="$(date +'%s')000"
222 # if found and included, this file overwrites loopsleepms()
223 # and current_time_ms() with a high resolution timer function
224 # for precise looping.
225 . "$pluginsd/loopsleepms.sh.inc"
228 # -----------------------------------------------------------------------------
233 tr -c "[A-Z][a-z][0-9]" "_" |\
234 sed -e "s|^_\+||g" -e "s|_\+$||g" -e "s|_\+|_|g" |\
238 # convert any floating point number
239 # to integer, give a multiplier
240 # the result is stored in ${FLOAT2INT_RESULT}
241 # so that no fork is necessary
242 # the multiplier must be a power of 10
244 local f m="$2" a b l v=($1)
247 # echo >&2 "value='${1}' f='${f}', m='${m}'"
249 # the length of the multiplier - 1
252 # check if the number is in scientific notation
253 if [[ ${f} =~ ^[[:space:]]*(-)?[0-9.]+(e|E)(\+|-)[0-9]+ ]]
255 # convert it to decimal
256 # unfortunately, this fork cannot be avoided
257 # if you know of a way to avoid it, please let me know
258 f=$(printf "%0.${l}f" ${f})
261 # split the floating point number
262 # in integer (a) and decimal (b)
266 # if the integer part is missing
268 [ -z "${a}" ] && a="0"
270 # strip leading zeros from the integer part
274 # check the length of the decimal part
275 # against the length of the multiplier
276 if [ ${#b} -gt ${l} ]
278 # too many digits - take the most significant
281 elif [ ${#b} -lt ${l} ]
283 # too few digits - pad with zero on the right
284 local z="00000000000000000000000" r=$[l - ${#b}]
288 # strip leading zeros from the decimal part
293 FLOAT2INT_RESULT=$[ (a * m) + b ]
294 #echo >&2 "FLOAT2INT_RESULT='${FLOAT2INT_RESULT}'"
298 # -----------------------------------------------------------------------------
299 # charts check functions
303 [ $? -ne 0 ] && echo >&2 "$PROGRAM_NAME: Cannot cd to $chartsd" && return 1
305 ls *.chart.sh | sed "s/\.chart\.sh$//g"
308 all_enabled_charts() {
309 local charts= enabled=
311 # find all enabled charts
313 for chart in $( all_charts )
315 eval "enabled=\$$chart"
316 if [ -z "${enabled}" ]
318 enabled="${enable_all_charts}"
321 if [ ! "${enabled}" = "yes" ]
323 echo >&2 "$PROGRAM_NAME: '$chart' is NOT enabled. Add a line with $chart=yes in $myconfig to enable it (or remove the line that disables it)."
325 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' is enabled."
326 local charts="$charts $chart"
333 # check the enabled charts
334 local check="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_check()" )"
337 echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_check() function. Disabling it."
341 local create="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_create()" )"
344 echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_create() function. Disabling it."
348 local update="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_update()" )"
351 echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_update() function. Disabling it."
356 #if [ -f "$confd/$chart.conf" ]
358 # if [ ! -z "$( cat "$confd/$chart.conf" | sed "s/^ \+//g" | grep -v "^$" | grep -v "^#" | grep -v "^$chart$charts_undescore" )" ]
360 # echo >&2 "$PROGRAM_NAME: chart's $chart config $confd/$chart.conf should only have lines starting with $chart$charts_undescore . Disabling it."
365 #if [ $dryrunner -eq 1 ]
367 # "$pluginsd/charts.d.dryrun-helper.sh" "$chart" "$chartsd/$chart.chart.sh" "$confd/$chart.conf" >/dev/null
370 # echo >&2 "$PROGRAM_NAME: chart's $chart did not pass the dry run check. This means it uses global variables not starting with $chart. Disabling it."
375 local charts2="$charts2 $chart"
379 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: enabled charts: $charts2"
383 # -----------------------------------------------------------------------------
386 suffix_update_every="_update_every"
388 for chart in $( all_enabled_charts )
390 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart: '$chartsd/$chart.chart.sh'"
391 . "$chartsd/$chart.chart.sh"
393 if [ -f "$confd/$chart.conf" ]
395 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart options: '$confd/$chart.conf'"
396 . "$confd/$chart.conf"
398 echo >&2 "$PROGRAM_NAME: $chart: configuration file '$confd/$chart.conf' not found. Using defaults."
401 eval "dt=\$$chart$suffix_update_every"
402 dt=$(( dt + 1 - 1 )) # make sure it is a number
403 if [ $dt -lt $update_every ]
405 eval "$chart$suffix_update_every=$update_every"
411 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' activated"
412 active_charts="$active_charts $chart"
414 echo >&2 "$PROGRAM_NAME: chart '$chart' check() function reports failure."
417 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts"
420 # -----------------------------------------------------------------------------
423 # enable work time reporting
425 test $debug -eq 1 && debug_time=tellwork
427 # if we only need a specific chart, remove all the others
428 if [ ! -z "${chart_only}" ]
430 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: requested to run only for: '${chart_only}'"
432 for chart in $active_charts
434 if [ "$chart" = "$chart_only" ]
436 check_charts="$chart"
440 active_charts="$check_charts"
442 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts"
444 # stop if we just need a pre-check
447 echo >&2 "CHECK RESULT"
448 echo >&2 "Will run the charts: $active_charts"
452 # -----------------------------------------------------------------------------
458 if [ ! -z "$TMP_DIR" -a -d "$TMP_DIR" ]
460 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: cleaning up temporary directory $TMP_DIR ..."
465 trap chartsd_cleanup EXIT
466 trap chartsd_cleanup SIGHUP
467 trap chartsd_cleanup INT
471 TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
473 TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
476 cd "$TMP_DIR" || exit 1
478 # -----------------------------------------------------------------------------
482 for chart in $active_charts
484 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: Calling '$chart$charts_create()'..."
488 run_charts="$run_charts $chart"
489 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' has initialized."
491 echo >&2 "$PROGRAM_NAME: chart '$chart' function '$chart$charts_create()' reports failure."
494 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: run_charts='$run_charts'"
497 # -----------------------------------------------------------------------------
500 if [ -z "$run_charts" ]
502 echo >&2 "$PROGRAM_NAME: No charts to collect data from."
507 declare -A charts_last_update=() charts_update_every=() charts_next_update=() charts_run_counter=()
510 c=0 dt ret last_ms exec_start_ms exec_end_ms \
511 chart now_charts=() next_charts=($run_charts)
513 # return the current time in ms in $now_ms
516 exit_at=$(( now_ms + (restart_timeout * 1000) ))
518 for chart in $run_charts
520 eval "charts_update_every[$chart]=\$$chart$suffix_update_every"
521 test -z "${charts_update_every[$chart]}" && charts_update_every[$charts]=$update_every
522 charts_last_update[$chart]=$((now_ms - (now_ms % (charts_update_every[$chart] * 1000) ) ))
523 charts_next_update[$chart]=$(( charts_last_update[$chart] + (charts_update_every[$chart] * 1000) ))
524 charts_run_counter[$chart]=0
526 echo "CHART netdata.plugin_chartsd_$chart '' 'Execution time for $chart plugin' 'milliseconds / run' charts.d netdata.plugin_charts area 145000 ${charts_update_every[$chart]}"
527 echo "DIMENSION run_time 'run time' absolute 1 1"
534 now_charts=("${next_charts[@]}")
537 # return the current time in ms in $now_ms
540 for chart in "${now_charts[@]}"
542 # echo >&2 "DEBUG: chart: $chart last: ${charts_last_update[$chart]}, next: ${charts_next_update[$chart]}, now: ${now_ms}"
543 if [ ${now_ms} -ge ${charts_next_update[$chart]} ]
545 last_ms=${charts_last_update[$chart]}
546 dt=$(( (now_ms - last_ms) ))
547 # echo >&2 "DEBUG: chart: $chart last: ${charts_last_update[$chart]}, next: ${charts_next_update[$chart]}, now: ${now_ms}, dt: ${dt}"
549 charts_last_update[$chart]=${now_ms}
551 while [ ${charts_next_update[$chart]} -lt ${now_ms} ]
553 charts_next_update[$chart]=$(( charts_next_update[$chart] + (charts_update_every[$chart] * 1000) ))
556 # the first call should not give a duration
557 # so that netdata calibrates to current time
559 charts_run_counter[$chart]=$(( charts_run_counter[$chart] + 1 ))
560 if [ ${charts_run_counter[$chart]} -eq 1 ]
565 exec_start_ms=$now_ms
566 $chart$charts_update $dt
569 # return the current time in ms in $now_ms
570 current_time_ms; exec_end_ms=$now_ms
572 echo "BEGIN netdata.plugin_chartsd_$chart $dt"
575 echo "SET run_time = $(( exec_end_ms - exec_start_ms ))"
576 next_charts+=($chart)
578 echo "SET run_time = $(( (exec_end_ms - exec_start_ms) * -1 ))"
579 echo >&2 "$PROGRAM_NAME: chart '$chart' update() function reports failure. Disabling it."
583 next_charts+=($chart)
587 if [ "$pause_method" = "suspend" ]
589 echo "STOPPING_WAKE_ME_UP_PLEASE"
590 suspend || ( echo >&2 "$PROGRAM_NAME: suspend returned error $?, falling back to sleep."; loopsleepms $debug_time $update_every $time_divisor)
592 # wait the time you are required to
593 #loopsleepms $debug_time $update_every $time_divisor
594 if [ ${LOOPSLEEPMS_HIGHRES} -eq 1 ]
602 test ${now_ms} -ge ${exit_at} && exit 0