diff options
Diffstat (limited to 'plugins.d/charts.d.plugin')
-rwxr-xr-x | plugins.d/charts.d.plugin | 592 |
1 files changed, 592 insertions, 0 deletions
diff --git a/plugins.d/charts.d.plugin b/plugins.d/charts.d.plugin new file mode 100755 index 000000000..40c0356c8 --- /dev/null +++ b/plugins.d/charts.d.plugin @@ -0,0 +1,592 @@ +#!/bin/bash + +PROGRAM_FILE="$0" +PROGRAM_NAME="$(basename $0)" +PROGRAM_NAME="${PROGRAM_NAME/.plugin}" + +# if you need to run parallel charts.d processes +# just link this files with a different name +# in the same directory, with a .plugin suffix +# netdata will start multiple of them +# each will have a different config file + +echo >&2 "$PROGRAM_NAME: started from '$PROGRAM_FILE' with options: $*" + +if [ $(( ${BASH_VERSINFO[0]} )) -lt 4 ] +then + echo >&2 + echo >&2 "$PROGRAM_NAME: ERROR" + echo >&2 "BASH version 4 or later is required." + echo >&2 "You are running version: ${BASH_VERSION}" + echo >&2 "Please upgrade." + echo >&2 + exit 1 +fi + +# check a few commands +require_cmd() { + which "$1" >/dev/null + if [ $? -ne 0 ] + then + echo >&2 "$PROGRAM_NAME: ERROR: Command '$1' is not found in the system path." + return 1 + fi + return 0 +} + +require_cmd date || exit 1 +require_cmd sed || exit 1 +require_cmd basename || exit 1 +require_cmd dirname || exit 1 +require_cmd cat || exit 1 +require_cmd grep || exit 1 +require_cmd egrep || exit 1 +require_cmd mktemp || exit 1 +require_cmd awk || exit 1 + +# ----------------------------------------------------------------------------- +# insternal defaults +# netdata exposes a few environment variables for us + +pause_method="sleep" # use either "suspend" or "sleep" + # DO NOT USE SUSPEND - LINUX WILL SUSPEND NETDATA TOO + # THE WHOLE PROCESS GROUP - NOT JUST THE SHELL + +pluginsd="${NETDATA_PLUGINS_DIR}" +[ -z "$pluginsd" ] && pluginsd="$( dirname $PROGRAM_FILE )" + +confd="${NETDATA_CONFIG_DIR-/etc/netdata}" +chartsd="$pluginsd/../charts.d" + +myconfig="$confd/$PROGRAM_NAME.conf" + +minimum_update_frequency="${NETDATA_UPDATE_EVERY-1}" +update_every=${minimum_update_frequency} # this will be overwritten by the command line + +# work around for non BASH shells +charts_create="_create" +charts_update="_update" +charts_check="_check" +charts_undescore="_" + +# when making iterations, charts.d can loop more frequently +# to prevent plugins missing iterations. +# this is a percentage relative to update_every to align its +# iterations. +# The minimum is 10%, the maximum 100%. +# So, if update_every is 1 second and time_divisor is 50, +# charts.d will iterate every 500ms. +# Charts will be called to collect data only if the time +# passed since the last time the collected data is equal or +# above their update_every. +time_divisor=50 + +# number of seconds to run without restart +# after this time, charts.d.plugin will exit +# netdata will restart it +restart_timeout=$[3600 * 4] + +# check if the charts.d plugins are using global variables +# they should not. +# It does not currently support BASH v4 arrays, so it is +# disabled +dryrunner=0 + +# check for timeout command +check_for_timeout=1 + +# the default enable/disable value for all charts +enable_all_charts="yes" + +# ----------------------------------------------------------------------------- +# parse parameters + +debug=0 +check=0 +chart_only= +while [ ! -z "$1" ] +do + if [ "$1" = "check" ] + then + check=1 + shift + continue + fi + + if [ "$1" = "debug" -o "$1" = "all" ] + then + debug=1 + shift + continue + fi + + if [ -f "$chartsd/$1.chart.sh" ] + then + debug=1 + chart_only="$( echo $1.chart.sh | sed "s/\.chart\.sh$//g" )" + shift + continue + fi + + if [ -f "$chartsd/$1" ] + then + debug=1 + chart_only="$( echo $1 | sed "s/\.chart\.sh$//g" )" + shift + continue + fi + + # number check + n="$1" + x=$(( n )) + if [ "$x" = "$n" ] + then + shift + update_every=$x + [ $update_every -lt $minimum_update_frequency ] && update_every=$minimum_update_frequency + continue + fi + + echo >&2 "Cannot understand parameter $1. Aborting." + echo "DISABLE" + exit 1 +done + + +# ----------------------------------------------------------------------------- +# load my configuration + +if [ -f "$myconfig" ] + then + . "$myconfig" + if [ $? -ne 0 ] + then + echo >&2 "$PROGRAM_NAME: cannot load $myconfig" + echo "DISABLE" + exit 1 + fi + time_divisor=$((time_divisor)) + [ $time_divisor -lt 10 ] && time_divisor=10 + [ $time_divisor -gt 100 ] && time_divisor=100 +else + echo >&2 "$PROGRAM_NAME: configuration file '$myconfig' not found. Using defaults." +fi + +if [ "$pause_method" = "suspend" ] +then + # enable bash job control + # this is required for suspend to work + set -m +fi + +# we check for the timeout command, after we load our +# configuration, so that the user may overwrite the +# timeout command we use, providing a function that +# can emulate the timeout command we need: +# > timeout SECONDS command ... +if [ $check_for_timeout -eq 1 ] + then + require_cmd timeout || exit 1 +fi + +# ----------------------------------------------------------------------------- +# internal checks + +# netdata passes the requested update frequency as the first argument +update_every=$(( update_every + 1 - 1)) # makes sure it is a number +test $update_every -eq 0 && update_every=1 # if it is zero, make it 1 + +# check the charts.d directory +if [ ! -d "$chartsd" ] + then + echo >&2 "$PROGRAM_NAME: cannot find charts directory '$chartsd'" + echo "DISABLE" +fi + + +# ----------------------------------------------------------------------------- +# loop control + +# default sleep function +LOOPSLEEPMS_HIGHRES=0 +loopsleepms() { + [ "$1" = "tellwork" ] && shift + sleep $1 +} + +now_ms= +current_time_ms() { + now_ms="$(date +'%s')000" +} + +# if found and included, this file overwrites loopsleepms() +# and current_time_ms() with a high resolution timer function +# for precise looping. +. "$pluginsd/loopsleepms.sh.inc" + + +# ----------------------------------------------------------------------------- +# library functions + +fixid() { + echo "$*" |\ + tr -c "[A-Z][a-z][0-9]" "_" |\ + sed -e "s|^_\+||g" -e "s|_\+$||g" -e "s|_\+|_|g" |\ + tr "[A-Z]" "[a-z]" +} + +# convert any floating point number +# to integer, give a multiplier +# the result is stored in ${FLOAT2INT_RESULT} +# so that no fork is necessary +# the multiplier must be a power of 10 +float2int() { + local f m="$2" a b l v=($1) + f=${v[0]} + + # echo >&2 "value='${1}' f='${f}', m='${m}'" + + # the length of the multiplier - 1 + l=$[ ${#m} - 1 ] + + # split the floating point number + # in integer (a) and decimal (b) + a=${f/.*/} + b=${f/*./} + + # prepend a zero to the integer part + # if it is missing + [ -z "${a}" ] && a="0" + # strip leading zeros - base 10 convertion + a=$[10#$a] + + # check the length of the decimal part + # against the length of the multiplier + if [ ${#b} -gt ${l} ] + then + # too many digits - take the most significant + b=${b:0:${l}} + elif [ ${#b} -lt ${l} ] + then + # too few digits - pad with zero on the right + local z="00000000000000000000000" r=$[l - ${#b}] + b="${b}${z:0:${r}}" + fi + # strip leading zeros - base 10 convertion + b=$[10#$b] + + # store the result + FLOAT2INT_RESULT=$[ (a * m) + b ] + #echo >&2 "FLOAT2INT_RESULT='${FLOAT2INT_RESULT}'" +} + + +# ----------------------------------------------------------------------------- +# charts check functions + +all_charts() { + cd "$chartsd" + [ $? -ne 0 ] && echo >&2 "$PROGRAM_NAME: Cannot cd to $chartsd" && return 1 + + ls *.chart.sh | sed "s/\.chart\.sh$//g" +} + +all_enabled_charts() { + local charts= enabled= + + # find all enabled charts + + for chart in $( all_charts ) + do + eval "enabled=\$$chart" + if [ -z "${enabled}" ] + then + enabled="${enable_all_charts}" + fi + + if [ ! "${enabled}" = "yes" ] + then + 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)." + else + [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' is enabled." + local charts="$charts $chart" + fi + done + + local charts2= + for chart in $charts + do + # check the enabled charts + local check="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_check()" )" + if [ -z "$check" ] + then + echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_check() function. Disabling it." + continue + fi + + local create="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_create()" )" + if [ -z "$create" ] + then + echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_create() function. Disabling it." + continue + fi + + local update="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_update()" )" + if [ -z "$update" ] + then + echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_update() function. Disabling it." + continue + fi + + # check its config + #if [ -f "$confd/$chart.conf" ] + #then + # if [ ! -z "$( cat "$confd/$chart.conf" | sed "s/^ \+//g" | grep -v "^$" | grep -v "^#" | grep -v "^$chart$charts_undescore" )" ] + # then + # echo >&2 "$PROGRAM_NAME: chart's $chart config $confd/$chart.conf should only have lines starting with $chart$charts_undescore . Disabling it." + # continue + # fi + #fi + + #if [ $dryrunner -eq 1 ] + # then + # "$pluginsd/charts.d.dryrun-helper.sh" "$chart" "$chartsd/$chart.chart.sh" "$confd/$chart.conf" >/dev/null + # if [ $? -ne 0 ] + # then + # 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." + # continue + # fi + #fi + + local charts2="$charts2 $chart" + done + + echo $charts2 + [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: enabled charts: $charts2" +} + + +# ----------------------------------------------------------------------------- +# load the charts + +suffix_update_every="_update_every" +active_charts= +for chart in $( all_enabled_charts ) +do + [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart: '$chartsd/$chart.chart.sh'" + . "$chartsd/$chart.chart.sh" + + if [ -f "$confd/$chart.conf" ] + then + [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart options: '$confd/$chart.conf'" + . "$confd/$chart.conf" + else + echo >&2 "$PROGRAM_NAME: $chart: configuration file '$confd/$chart.conf' not found. Using defaults." + fi + + eval "dt=\$$chart$suffix_update_every" + dt=$(( dt + 1 - 1 )) # make sure it is a number + if [ $dt -lt $update_every ] + then + eval "$chart$suffix_update_every=$update_every" + fi + + $chart$charts_check + if [ $? -eq 0 ] + then + [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' activated" + active_charts="$active_charts $chart" + else + echo >&2 "$PROGRAM_NAME: chart '$chart' check() function reports failure." + fi +done +[ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts" + + +# ----------------------------------------------------------------------------- +# check overwrites + +# enable work time reporting +debug_time= +test $debug -eq 1 && debug_time=tellwork + +# if we only need a specific chart, remove all the others +if [ ! -z "${chart_only}" ] +then + [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: requested to run only for: '${chart_only}'" + check_charts= + for chart in $active_charts + do + if [ "$chart" = "$chart_only" ] + then + check_charts="$chart" + break + fi + done + active_charts="$check_charts" +fi +[ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts" + +# stop if we just need a pre-check +if [ $check -eq 1 ] +then + echo >&2 "CHECK RESULT" + echo >&2 "Will run the charts: $active_charts" + exit 0 +fi + +# ----------------------------------------------------------------------------- +# create temp dir + +TMP_DIR= +chartsd_cleanup() { + cd /tmp + if [ ! -z "$TMP_DIR" -a -d "$TMP_DIR" ] + then + [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: cleaning up temporary directory $TMP_DIR ..." + rm -rf "$TMP_DIR" + fi + exit 0 +} +trap chartsd_cleanup EXIT +trap chartsd_cleanup SIGHUP +trap chartsd_cleanup INT + +if [ $UID = "0" ] +then + TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )" +else + TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )" +fi + +cd "$TMP_DIR" || exit 1 + +# ----------------------------------------------------------------------------- +# create charts + +run_charts= +for chart in $active_charts +do + [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: Calling '$chart$charts_create()'..." + $chart$charts_create + if [ $? -eq 0 ] + then + run_charts="$run_charts $chart" + [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' has initialized." + else + echo >&2 "$PROGRAM_NAME: chart '$chart' function '$chart$charts_create()' reports failure." + fi +done +[ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: run_charts='$run_charts'" + + +# ----------------------------------------------------------------------------- +# update dimensions + +if [ -z "$run_charts" ] + then + echo >&2 "$PROGRAM_NAME: No charts to collect data from." + echo "DISABLE" + exit 1 +fi + +declare -A charts_last_update=() charts_update_every=() charts_next_update=() charts_run_counter=() +global_update() { + local exit_at \ + c=0 dt ret last_ms exec_start_ms exec_end_ms \ + chart now_charts=() next_charts=($run_charts) + + # return the current time in ms in $now_ms + current_time_ms + + exit_at=$(( now_ms + (restart_timeout * 1000) )) + + for chart in $run_charts + do + eval "charts_update_every[$chart]=\$$chart$suffix_update_every" + test -z "${charts_update_every[$chart]}" && charts_update_every[$charts]=$update_every + charts_last_update[$chart]=$((now_ms - (now_ms % (charts_update_every[$chart] * 1000) ) )) + charts_next_update[$chart]=$(( charts_last_update[$chart] + (charts_update_every[$chart] * 1000) )) + charts_run_counter[$chart]=0 + + echo "CHART netdata.plugin_chartsd_$chart '' 'Execution time for $chart plugin' 'milliseconds / run' charts.d netdata.plugin_charts area 145000 ${charts_update_every[$chart]}" + echo "DIMENSION run_time 'run time' absolute 1 1" + done + + # the main loop + while [ 1 ] + do + c=$((c + 1)) + now_charts=("${next_charts[@]}") + next_charts=() + + # return the current time in ms in $now_ms + current_time_ms + + for chart in "${now_charts[@]}" + do + # echo >&2 "DEBUG: chart: $chart last: ${charts_last_update[$chart]}, next: ${charts_next_update[$chart]}, now: ${now_ms}" + if [ ${now_ms} -ge ${charts_next_update[$chart]} ] + then + last_ms=${charts_last_update[$chart]} + dt=$(( (now_ms - last_ms) )) + # echo >&2 "DEBUG: chart: $chart last: ${charts_last_update[$chart]}, next: ${charts_next_update[$chart]}, now: ${now_ms}, dt: ${dt}" + + charts_last_update[$chart]=${now_ms} + + while [ ${charts_next_update[$chart]} -lt ${now_ms} ] + do + charts_next_update[$chart]=$(( charts_next_update[$chart] + (charts_update_every[$chart] * 1000) )) + done + + # the first call should not give a duration + # so that netdata calibrates to current time + dt=$(( dt * 1000 )) + charts_run_counter[$chart]=$(( charts_run_counter[$chart] + 1 )) + if [ ${charts_run_counter[$chart]} -eq 1 ] + then + dt= + fi + + exec_start_ms=$now_ms + $chart$charts_update $dt + ret=$? + + # return the current time in ms in $now_ms + current_time_ms; exec_end_ms=$now_ms + + echo "BEGIN netdata.plugin_chartsd_$chart $dt" + if [ $ret -eq 0 ] + then + echo "SET run_time = $(( exec_end_ms - exec_start_ms ))" + next_charts+=($chart) + else + echo "SET run_time = $(( (exec_end_ms - exec_start_ms) * -1 ))" + echo >&2 "$PROGRAM_NAME: chart '$chart' update() function reports failure. Disabling it." + fi + echo "END" + else + next_charts+=($chart) + fi + done + + if [ "$pause_method" = "suspend" ] + then + echo "STOPPING_WAKE_ME_UP_PLEASE" + suspend || ( echo >&2 "$PROGRAM_NAME: suspend returned error $?, falling back to sleep."; loopsleepms $debug_time $update_every $time_divisor) + else + # wait the time you are required to + #loopsleepms $debug_time $update_every $time_divisor + if [ ${LOOPSLEEPMS_HIGHRES} -eq 1 ] + then + sleep 0.2 + else + sleep 1 + fi + fi + + test ${now_ms} -ge ${exit_at} && exit 0 + done +} + +global_update |