]> arthur.barton.de Git - netdata.git/blobdiff - plugins.d/charts.d.plugin
converted tabs for C, JS, BASH to 4 spaces
[netdata.git] / plugins.d / charts.d.plugin
index 769d5ba69c995a80e962dc6e9d7cc483fcdfa400..9aaadc168547e35d2a895436292da6746f13695a 100755 (executable)
@@ -1,29 +1,37 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 PROGRAM_FILE="$0"
-PROGRAM_NAME="charts.d"
+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
+    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
+    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
@@ -32,7 +40,9 @@ 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
@@ -47,10 +57,11 @@ pluginsd="${NETDATA_PLUGINS_DIR}"
 
 confd="${NETDATA_CONFIG_DIR-/etc/netdata}"
 chartsd="$pluginsd/../charts.d"
-myconfig="$confd/charts.d.conf"
+
+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
+update_every=${minimum_update_frequency}    # this will be overwritten by the command line
 
 # work around for non BASH shells
 charts_create="_create"
@@ -58,6 +69,35 @@ 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
 
@@ -66,50 +106,50 @@ 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
+    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
 
 
@@ -117,35 +157,50 @@ 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
+    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
+    # 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
+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"
+    then
+    echo >&2 "$PROGRAM_NAME: cannot find charts directory '$chartsd'"
+    echo "DISABLE"
 fi
 
 
@@ -153,13 +208,15 @@ fi
 # loop control
 
 # default sleep function
+LOOPSLEEPMS_HIGHRES=0
 loopsleepms() {
-       sleep $1
+    [ "$1" = "tellwork" ] && shift
+    sleep $1
 }
 
 now_ms=
 current_time_ms() {
-       now_ms="$(date +'%s')000"
+    now_ms="$(date +'%s')000"
 }
 
 # if found and included, this file overwrites loopsleepms()
@@ -168,80 +225,179 @@ current_time_ms() {
 . "$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 ))
+
+    # check if the number is in scientific notation
+    if [[ ${f} =~ ^[[:space:]]*(-)?[0-9.]+(e|E)(\+|-)[0-9]+ ]]
+        then
+        # convert it to decimal
+        # unfortunately, this fork cannot be avoided
+        # if you know of a way to avoid it, please let me know
+        f=$(printf "%0.${l}f" ${f})
+    fi
+
+    # split the floating point number
+    # in integer (a) and decimal (b)
+    a=${f/.*/}
+    b=${f/*./}
+
+    # if the integer part is missing
+    # set it to zero
+    [ -z "${a}" ] && a="0"
+
+    # strip leading zeros from the integer part
+    # 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 from the decimal part
+    # 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
+    cd "$chartsd"
+    [ $? -ne 0 ] && echo >&2 "$PROGRAM_NAME: Cannot cd to $chartsd" && return 1
 
-       ls *.chart.sh | sed "s/\.chart\.sh$//g"
+    ls *.chart.sh | sed "s/\.chart\.sh$//g"
 }
 
+declare -A charts_enable_keyword=(
+          ['apache']="force"
+        ['cpu_apps']="force"
+         ['cpufreq']="force"
+         ['example']="force"
+            ['exim']="force"
+         ['hddtemp']="force"
+    ['load_average']="force"
+        ['mem_apps']="force"
+           ['mysql']="force"
+           ['nginx']="force"
+          ['phpfpm']="force"
+         ['postfix']="force"
+         ['sensors']="force"
+           ['squid']="force"
+          ['tomcat']="force"
+    )
+
 all_enabled_charts() {
-       local charts=
-
-       # find all enabled charts
-
-       for chart in $( all_charts )
-       do
-               eval "enabled=\$$chart"
-               if [ "$enabled" = "yes" ]
-               then
-                       [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' is enabled."
-                       local charts="$charts $chart"
-               else
-                       echo >&2 "$PROGRAM_NAME: '$chart' is NOT enabled. Add a line with $chart=yes in $myconfig to enable it."
-               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
-
-               "$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
-
-               local charts2="$charts2 $chart"
-       done
-
-       echo $charts2
-       [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: enabled charts: $charts2"
+    local charts= enabled= required=
+
+    # find all enabled charts
+
+    for chart in $( all_charts )
+    do
+        eval "enabled=\$$chart"
+        if [ -z "${enabled}" ]
+            then
+            enabled="${enable_all_charts}"
+        fi
+
+        required="${charts_enable_keyword[${chart}]}"
+        [ -z "${required}" ] && required="yes"
+
+        if [ ! "${enabled}" = "${required}" ]
+        then
+            echo >&2 "$PROGRAM_NAME: '$chart' is NOT enabled. Add a line with $chart=$required 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"
 }
 
 
@@ -252,30 +408,36 @@ 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"
-       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
+    [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart: '$chartsd/$chart.chart.sh'"
+    . "$chartsd/$chart.chart.sh"
+
+    if [ -f "$confd/charts.d/$chart.conf" ]
+    then
+        [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart options: '$confd/charts.d/$chart.conf'"
+        . "$confd/charts.d/$chart.conf"
+    elif [ -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/charts.d/$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"
 
@@ -290,26 +452,26 @@ 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"
+    [ $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
+    echo >&2 "CHECK RESULT"
+    echo >&2 "Will run the charts: $active_charts"
+    exit 0
 fi
 
 # -----------------------------------------------------------------------------
@@ -317,13 +479,13 @@ fi
 
 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
+    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
@@ -331,39 +493,28 @@ trap chartsd_cleanup INT
 
 if [ $UID = "0" ]
 then
-       TMP_DIR="$( mktemp -d /var/run/netdata-charts.d-XXXXXXXXXX )"
+    TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
 else
-       TMP_DIR="$( mktemp -d /tmp/.netdata-charts.d-XXXXXXXXXX )"
+    TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
 fi
 
 cd "$TMP_DIR" || exit 1
 
-# -----------------------------------------------------------------------------
-# 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]"
-}
-
-
 # -----------------------------------------------------------------------------
 # 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
+    [ $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'"
 
@@ -371,66 +522,132 @@ done
 # -----------------------------------------------------------------------------
 # update dimensions
 
-declare -A charts_last_update=() charts_min_dt=()
+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=() charts_serial_failures=()
 global_update() {
-       local exit_after=$((3600 / update_every)) \
-               c=0 dt \
-               chart now_charts=() next_charts=($run_charts)
-
-       # return the current time in ms in $now_ms
-       current_time_ms
-
-       for chart in $run_charts
-       do
-               eval "charts_min_dt[$chart]=\$$chart$suffix_update_every"
-               test -z "${charts_min_dt[$chart]}" && charts_min_dt[$charts]=$update_every
-               charts_last_update[$chart]=$((now_ms - (charts_min_dt[$chart] * 1000) ))
-       done
-
-       # the main loop
-       while [ 1 ]
-       do
-               c=$((c + 1))
-               now_charts=("${next_charts[@]}")
-               next_charts=()
-
-               for chart in "${now_charts[@]}"
-               do
-                       # return the current time in ms in $now_ms
-                       current_time_ms
-
-                       dt=$(( (now_ms - charts_last_update[$chart]) * 1000 ))
-                       if [ $dt -ge $(( charts_min_dt[$chart] * 1000000 )) ]
-                       then
-                               charts_last_update[$chart]=$now_ms
-
-                               # the first call should not give a duration
-                               # so that netdata calibrates to current time
-                               test $c -eq 1 && dt=
-
-                               $chart$charts_update $dt
-                               if [ $? -eq 0 ]
-                               then
-                                       next_charts+=($chart)
-                               else
-                                       echo >&2 "$PROGRAM_NAME: chart '$chart' update() function reports failure. Disabling it."
-                               fi
-                       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 )
-               else
-                       # wait the time you are required to
-                       loopsleepms $debug_time $update_every
-               fi
-
-               test $c -gt $exit_after && exit 0
-       done
+    local exit_at \
+        c=0 dt ret last_ms exec_start_ms exec_end_ms \
+        chart now_charts=() next_charts=($run_charts) \
+        next_ms x seconds millis
+
+    # 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
+        charts_serial_failures[$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 [ "${#next_charts[@]}" -gt 0 ]
+    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"
+                echo "SET run_time = $(( exec_end_ms - exec_start_ms ))"
+                echo "END"
+
+                if [ $ret -eq 0 ]
+                then
+                    charts_serial_failures[$chart]=0
+                    next_charts+=($chart)
+                else
+                    charts_serial_failures[$chart]=$(( charts_serial_failures[$chart] + 1 ))
+
+                    if [ ${charts_serial_failures[$chart]} -gt 10 ]
+                        then
+                        echo >&2 "$PROGRAM_NAME: chart '$chart' update() function reported failure ${charts_serial_failures[$chart]} times. Disabling it."
+                    else
+                        echo >&2 "$PROGRAM_NAME: chart '$chart' update() function reports failure. Will keep trying for a while."
+                        next_charts+=($chart)
+                    fi
+                fi
+            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
+            next_ms=$((now_ms + (update_every * 1000 * 100) ))
+            for x in "${charts_next_update[@]}"; do [ ${x} -lt ${next_ms} ] && next_ms=${x}; done
+            next_ms=$((next_ms - now_ms))
+
+            if [ ${LOOPSLEEPMS_HIGHRES} -eq 1 -a ${next_ms} -gt 0 ]
+                then
+                seconds=$(( next_ms / 1000 ))
+                millis=$(( next_ms % 1000 ))
+                [ ${millis} -lt 10  ] && millis="0${millis}"
+                [ ${millis} -lt 100 ] && millis="0${millis}"
+                [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: sleeping for ${seconds}.${millis} seconds."
+                sleep ${seconds}.${millis}
+            else
+                sleep $update_every
+            fi
+        fi
+
+        test ${now_ms} -ge ${exit_at} && exit 0
+    done
+
+    echo >&2 "$PROGRAM_NAME: Nothing left to do. Disabling charts.d.plugin."
+    echo "DISABLE"
 }
 
 global_update