summaryrefslogtreecommitdiffstats
path: root/plugins.d
diff options
context:
space:
mode:
Diffstat (limited to 'plugins.d')
-rw-r--r--plugins.d/Makefile.am2
-rw-r--r--plugins.d/Makefile.in4
-rwxr-xr-xplugins.d/alarm-email.sh264
-rwxr-xr-xplugins.d/cgroup-name.sh95
-rwxr-xr-xplugins.d/charts.d.dryrun-helper.sh48
-rwxr-xr-xplugins.d/charts.d.plugin789
-rw-r--r--plugins.d/loopsleepms.sh.inc108
-rwxr-xr-xplugins.d/node.d.plugin361
-rwxr-xr-xplugins.d/python.d.plugin533
-rwxr-xr-xplugins.d/tc-qos-helper.sh141
10 files changed, 1611 insertions, 734 deletions
diff --git a/plugins.d/Makefile.am b/plugins.d/Makefile.am
index a717cbed1..b8a28610a 100644
--- a/plugins.d/Makefile.am
+++ b/plugins.d/Makefile.am
@@ -8,10 +8,12 @@ dist_plugins_DATA = \
$(NULL)
dist_plugins_SCRIPTS = \
+ alarm-email.sh \
cgroup-name.sh \
charts.d.dryrun-helper.sh \
charts.d.plugin \
node.d.plugin \
+ python.d.plugin \
tc-qos-helper.sh \
loopsleepms.sh.inc \
$(NULL)
diff --git a/plugins.d/Makefile.in b/plugins.d/Makefile.in
index c74997688..06211d51c 100644
--- a/plugins.d/Makefile.in
+++ b/plugins.d/Makefile.in
@@ -263,6 +263,8 @@ pluginsdir = @pluginsdir@
prefix = @prefix@
program_transform_name = @program_transform_name@
psdir = @psdir@
+pythondir = @pythondir@
+registrydir = @registrydir@
sbindir = @sbindir@
sharedstatedir = @sharedstatedir@
srcdir = @srcdir@
@@ -283,10 +285,12 @@ dist_plugins_DATA = \
$(NULL)
dist_plugins_SCRIPTS = \
+ alarm-email.sh \
cgroup-name.sh \
charts.d.dryrun-helper.sh \
charts.d.plugin \
node.d.plugin \
+ python.d.plugin \
tc-qos-helper.sh \
loopsleepms.sh.inc \
$(NULL)
diff --git a/plugins.d/alarm-email.sh b/plugins.d/alarm-email.sh
new file mode 100755
index 000000000..78c79ccdb
--- /dev/null
+++ b/plugins.d/alarm-email.sh
@@ -0,0 +1,264 @@
+#!/usr/bin/env bash
+
+me="${0}"
+
+sendmail="$(which sendmail 2>/dev/null || command -v sendmail 2>/dev/null)"
+if [ -z "${sendmail}" ]
+then
+ echo >&2 "I cannot send emails - there is no sendmail command available."
+fi
+
+sendmail_from_pipe() {
+ "${sendmail}" -t
+
+ if [ $? -eq 0 ]
+ then
+ echo >&2 "${me}: Sent notification email for ${status} on '${chart}.${name}'"
+ return 0
+ else
+ echo >&2 "${me}: FAILED to send notification email for ${status} on '${chart}.${name}'"
+ return 1
+ fi
+}
+
+name="${1}" # the name of the alarm, as given in netdata health.d entries
+chart="${2}" # the name of the chart (type.id)
+family="${3}" # the family of the chart
+status="${4}" # the current status : UNITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL
+old_status="${5}" # the previous status: UNITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL
+value="${6}" # the current value
+old_value="${7}" # the previous value
+src="${8}" # the line number and file the alarm has been configured
+duration="${9}" # the duration in seconds the previous state took
+non_clear_duration="${10}" # the total duration in seconds this is non-clear
+units="${11}" # the units of the value
+info="${12}" # a short description of the alarm
+
+[ ! -z "${info}" ] && info=" <small><br/>${info}</small>"
+
+# get the system hostname
+hostname="${NETDATA_HOSTNAME}"
+[ -z "${hostname}" ] && hostname="${NETDATA_REGISTRY_HOSTNAME}"
+[ -z "${hostname}" ] && hostname="$(hostname)"
+
+goto_url="${NETDATA_REGISTRY_URL}/goto-host-from-alarm.html?machine_guid=${NETDATA_REGISTRY_UNIQUE_ID}&chart=${chart}&family=${family}"
+
+# get the current date
+date="$(date)"
+
+duration4human() {
+ local s="${1}" d=0 h=0 m=0 ds="day" hs="hour" ms="minute" ss="second"
+ d=$(( s / 86400 ))
+ s=$(( s - (d * 86400) ))
+ h=$(( s / 3600 ))
+ s=$(( s - (h * 3600) ))
+ m=$(( s / 60 ))
+ s=$(( s - (m * 60) ))
+
+ if [ ${d} -gt 0 ]
+ then
+ [ ${m} -ge 30 ] && h=$(( h + 1 ))
+ [ ${d} -gt 1 ] && ds="days"
+ [ ${h} -gt 1 ] && hs="hours"
+ if [ ${h} -gt 0 ]
+ then
+ echo "${d} ${ds} and ${h} ${hs}"
+ else
+ echo "${d} ${ds}"
+ fi
+ elif [ ${h} -gt 0 ]
+ then
+ [ ${s} -ge 30 ] && m=$(( m + 1 ))
+ [ ${h} -gt 1 ] && hs="hours"
+ [ ${m} -gt 1 ] && ms="minutes"
+ if [ ${m} -gt 0 ]
+ then
+ echo "${h} ${hs} and ${m} ${ms}"
+ else
+ echo "${h} ${hs}"
+ fi
+ elif [ ${m} -gt 0 ]
+ then
+ [ ${m} -gt 1 ] && ms="minutes"
+ [ ${s} -gt 1 ] && ss="seconds"
+ if [ ${s} -gt 0 ]
+ then
+ echo "${m} ${ms} and ${s} ${ss}"
+ else
+ echo "${m} ${ms}"
+ fi
+ else
+ [ ${s} -gt 1 ] && ss="seconds"
+ echo "${s} ${ss}"
+ fi
+}
+
+severity="${status}"
+raised_for="<br/>(was ${old_status,,} for $(duration4human ${duration}))"
+status_message="status unknown"
+color="grey"
+alarm="${name} = ${value} ${units}"
+
+# prepare the title based on status
+case "${status}" in
+ CRITICAL)
+ status_message="is critical"
+ color="#ca414b"
+ ;;
+
+ WARNING)
+ status_message="needs attention"
+ color="#caca4b"
+ ;;
+
+ CLEAR)
+ status_message="recovered"
+ color="#77ca6d"
+
+ # don't show the value when the status is CLEAR
+ # for certain alarms, this value might not have any meaning
+ alarm="${name}"
+ ;;
+esac
+
+if [ "${status}" != "WARNING" -a "${status}" != "CRITICAL" -a "${status}" != "CLEAR" ]
+then
+ # don't do anything if this is not WARNING, CRITICAL or CLEAR
+ echo >&2 "${me}: not sending notification email for ${status} on '${chart}.${name}'"
+ exit 0
+elif [ "${old_status}" != "WARNING" -a "${old_status}" != "CRITICAL" -a "${status}" = "CLEAR" ]
+then
+ # don't do anything if this is CLEAR, but it was not WARNING or CRITICAL
+ echo >&2 "${me}: not sending notification email for ${status} on '${chart}.${name}' (last status was ${old_status})"
+ exit 0
+elif [ "${status}" = "CLEAR" ]
+then
+ severity="Recovered from ${old_status}"
+ if [ $non_clear_duration -gt $duration ]
+ then
+ raised_for="<br/>(had issues for $(duration4human ${non_clear_duration}))"
+ fi
+
+elif [ "${old_status}" = "WARNING" -a "${status}" = "CRITICAL" ]
+then
+ severity="Escalated to ${status}"
+ if [ $non_clear_duration -gt $duration ]
+ then
+ raised_for="<br/>(has issues for $(duration4human ${non_clear_duration}))"
+ fi
+
+elif [ "${old_status}" = "CRITICAL" -a "${status}" = "WARNING" ]
+then
+ severity="Demoted to ${status}"
+ if [ $non_clear_duration -gt $duration ]
+ then
+ raised_for="<br/>(has issues for $(duration4human ${non_clear_duration}))"
+ fi
+
+else
+ raised_for=
+fi
+
+# send the email
+cat <<EOF | sendmail_from_pipe
+To: root
+Subject: ${hostname} ${status_message} - ${chart}.${name}
+Content-Type: text/html
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
+<body style="font-family:'Helvetica Neue','Helvetica',Helvetica,Arial,sans-serif;font-size:14px;width:100%!important;min-height:100%;line-height:1.6;background:#f6f6f6;margin:0;padding:0">
+<table>
+ <tbody>
+ <tr>
+ <td style="vertical-align:top;" valign="top"></td>
+ <td width="700" style="vertical-align:top;display:block!important;max-width:700px!important;clear:both!important;margin:0 auto;padding:0" valign="top">
+ <div style="max-width:700px;display:block;margin:0 auto;padding:20px">
+ <table width="100%" cellpadding="0" cellspacing="0"
+ style="background:#fff;border:1px solid #e9e9e9">
+ <tbody>
+ <tr>
+ <td bgcolor="#eee"
+ style="padding: 5px 20px 5px 20px;background-color:#eee;">
+ <div style="font-size:20px;color:#777;font-weight: bold;">netdata notification</div>
+ </td>
+ </tr>
+ <tr>
+ <td bgcolor="${color}"
+ style="font-size:16px;vertical-align:top;font-weight:400;text-align:center;margin:0;padding:10px;color:#ffffff;background:${color}!important;border:1px solid ${color};border-top-color:${color}" align="center" valign="top">
+ <h1 style="font-weight:400;margin:0">${hostname} ${status_message}</h1>
+ </td>
+ </tr>
+ <tr>
+ <td style="vertical-align:top" valign="top">
+ <div style="margin:0;padding:20px;max-width:700px">
+ <table width="100%" cellpadding="0" cellspacing="0" style="max-width:700px">
+ <tbody>
+ <tr>
+ <td style="font-size:18px;vertical-align:top;margin:0;padding:0 0 20px"
+ align="left" valign="top">
+ <span>${chart}</span>
+ <span style="display:block;color:#666666;font-size:12px;font-weight:300;line-height:1;text-transform:uppercase">Chart</span>
+ </td>
+ </tr>
+ <tr style="margin:0;padding:0">
+ <td style="font-size:18px;vertical-align:top;margin:0;padding:0 0 20px"
+ align="left" valign="top">
+ <span><b>${alarm}</b>${info}</span>
+ <span style="display:block;color:#666666;font-size:12px;font-weight:300;line-height:1;text-transform:uppercase">Alarm</span>
+ </td>
+ </tr>
+ <tr>
+ <td style="font-size:18px;vertical-align:top;margin:0;padding:0 0 20px"
+ align="left" valign="top">
+ <span>${family}</span>
+ <span style="display:block;color:#666666;font-size:12px;font-weight:300;line-height:1;text-transform:uppercase">Family</span>
+ </td>
+ </tr>
+ <tr style="margin:0;padding:0">
+ <td style="font-size:18px;vertical-align:top;margin:0;padding:0 0 20px"
+ align="left" valign="top">
+ <span>${severity}</span>
+ <span style="display:block;color:#666666;font-size:12px;font-weight:300;line-height:1;text-transform:uppercase">Severity</span>
+ </td>
+ </tr>
+ <tr style="margin:0;padding:0">
+ <td style="font-size:18px;vertical-align:top;margin:0;padding:0 0 20px"
+ align="left" valign="top"><span>${date}</span>
+ <span>${raised_for}</span> <span
+ style="display:block;color:#666666;font-size:12px;font-weight:300;line-height:1;text-transform:uppercase">Time</span>
+ </td>
+ </tr>
+ <!--
+ <tr style="margin:0;padding:0">
+ <td style="font-size:18px;vertical-align:top;margin:0;padding:0 0 20px">
+ <a href="${goto_url}" style="font-size:14px;color:#ffffff;text-decoration:none;line-height:1.5;font-weight:bold;text-align:center;display:inline-block;text-transform:capitalize;background:#35568d;border-width:1px;border-style:solid;border-color:#2b4c86;margin:0;padding:10px 15px" target="_blank">View Netdata</a>
+ </td>
+ </tr>
+ -->
+ <tr style="text-align:center;margin:0;padding:0">
+ <td style="font-size:11px;vertical-align:top;margin:0;padding:10px 0 0 0;color:#666666"
+ align="center" valign="bottom">The source of this alarm is line <code>${src}</code>
+ </td>
+ </tr>
+ <tr style="text-align:center;margin:0;padding:0">
+ <td style="font-size:12px;vertical-align:top;margin:0;padding:20px 0 0 0;color:#666666;border-top:1px solid #f0f0f0"
+ align="center" valign="bottom">Sent by
+ <a href="https://mynetdata.io/" target="_blank">netdata</a>, the real-time performance monitoring.
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+</table>
+</body>
+</html>
+EOF
diff --git a/plugins.d/cgroup-name.sh b/plugins.d/cgroup-name.sh
index 8ce64b3d7..8bfc984c2 100755
--- a/plugins.d/cgroup-name.sh
+++ b/plugins.d/cgroup-name.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin"
export LC_ALL=C
@@ -9,66 +9,69 @@ CGROUP="${1}"
NAME=
if [ -z "${CGROUP}" ]
- then
- echo >&2 "${0}: called without a cgroup name. Nothing to do."
- exit 1
+ then
+ echo >&2 "${0}: called without a cgroup name. Nothing to do."
+ exit 1
fi
if [ -f "${CONFIG}" ]
- then
- NAME="$(cat "${CONFIG}" | grep "^${CGROUP} " | sed "s/[[:space:]]\+/ /g" | cut -d ' ' -f 2)"
- if [ -z "${NAME}" ]
- then
- echo >&2 "${0}: cannot find cgroup '${CGROUP}' in '${CONFIG}'."
- fi
+ then
+ NAME="$(grep "^${CGROUP} " "${CONFIG}" | sed "s/[[:space:]]\+/ /g" | cut -d ' ' -f 2)"
+ if [ -z "${NAME}" ]
+ then
+ echo >&2 "${0}: cannot find cgroup '${CGROUP}' in '${CONFIG}'."
+ fi
#else
-# echo >&2 "${0}: configuration file '${CONFIG}' is not available."
+# echo >&2 "${0}: configuration file '${CONFIG}' is not available."
fi
function get_name_classic {
- DOCKERID=$1
- echo >&2 "Running command: docker ps --filter=id=\"${DOCKERID}\" --format=\"{{.Names}}\""
- NAME="$( docker ps --filter=id="${DOCKERID}" --format="{{.Names}}" )"
+ local DOCKERID="$1"
+ echo >&2 "Running command: docker ps --filter=id=\"${DOCKERID}\" --format=\"{{.Names}}\""
+ NAME="$( docker ps --filter=id="${DOCKERID}" --format="{{.Names}}" )"
+ return 0
}
function get_name_api {
- DOCKERID=$1
- if [ ! -S "/var/run/docker.sock" ]
- then
- echo >&2 "Can't find /var/run/docker.sock"
- return
- fi
- echo >&2 "Running API command: /containers/${DOCKERID}/json"
- JSON=$(echo -e "GET /containers/${DOCKERID}/json HTTP/1.0\r\n" | nc -U /var/run/docker.sock | egrep '^{.*')
- NAME=$(echo $JSON | jq -r .Name,.Config.Hostname | grep -v null | head -n1 | sed 's|^/||')
+ local DOCKERID="$1"
+ if [ ! -S "/var/run/docker.sock" ]
+ then
+ echo >&2 "Can't find /var/run/docker.sock"
+ return 1
+ fi
+ echo >&2 "Running API command: /containers/${DOCKERID}/json"
+ JSON=$(echo -e "GET /containers/${DOCKERID}/json HTTP/1.0\r\n" | nc -U /var/run/docker.sock | egrep '^{.*')
+ NAME=$(echo $JSON | jq -r .Name,.Config.Hostname | grep -v null | head -n1 | sed 's|^/||')
+ return 0
}
if [ -z "${NAME}" ]
- then
- if [[ "${CGROUP}" =~ ^.*docker[-/\.][a-fA-F0-9]+[-\.]?.*$ ]]
- then
- DOCKERID="$( echo "${CGROUP}" | sed "s|^.*docker[-/]\([a-fA-F0-9]\+\)[-\.]\?.*$|\1|" )"
+ then
+ if [[ "${CGROUP}" =~ ^.*docker[-_/\.][a-fA-F0-9]+[-_\.]?.*$ ]]
+ then
+ DOCKERID="$( echo "${CGROUP}" | sed "s|^.*docker[-_/]\([a-fA-F0-9]\+\)[-_\.]\?.*$|\1|" )"
+ # echo "DOCKERID=${DOCKERID}"
- if [ ! -z "${DOCKERID}" -a \( ${#DOCKERID} -eq 64 -o ${#DOCKERID} -eq 12 \) ]
- then
- if hash docker 2>/dev/null
- then
- get_name_classic $DOCKERID
- else
- get_name_api $DOCKERID
- fi
- if [ -z "${NAME}" ]
- then
- echo >&2 "Cannot find the name of docker container '${DOCKERID}'"
- NAME="${DOCKERID:0:12}"
- else
- echo >&2 "Docker container '${DOCKERID}' is named '${NAME}'"
- fi
- fi
- fi
+ if [ ! -z "${DOCKERID}" -a \( ${#DOCKERID} -eq 64 -o ${#DOCKERID} -eq 12 \) ]
+ then
+ if hash docker 2>/dev/null
+ then
+ get_name_classic $DOCKERID
+ else
+ get_name_api $DOCKERID || get_name_classic $DOCKERID
+ fi
+ if [ -z "${NAME}" ]
+ then
+ echo >&2 "Cannot find the name of docker container '${DOCKERID}'"
+ NAME="${DOCKERID:0:12}"
+ else
+ echo >&2 "Docker container '${DOCKERID}' is named '${NAME}'"
+ fi
+ fi
+ fi
- [ -z "${NAME}" ] && NAME="${CGROUP}"
- [ ${#NAME} -gt 50 ] && NAME="${NAME:0:50}"
+ [ -z "${NAME}" ] && NAME="${CGROUP}"
+ [ ${#NAME} -gt 50 ] && NAME="${NAME:0:50}"
fi
echo >&2 "${0}: cgroup '${CGROUP}' is called '${NAME}'"
diff --git a/plugins.d/charts.d.dryrun-helper.sh b/plugins.d/charts.d.dryrun-helper.sh
index 646452892..8142f9882 100755
--- a/plugins.d/charts.d.dryrun-helper.sh
+++ b/plugins.d/charts.d.dryrun-helper.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/usr/bin/env bash
# will stop the script for any error
set -e
@@ -14,7 +14,7 @@ tmp1="`mktemp`"
tmp2="`mktemp`"
myset() {
- set | grep -v "^_=" | grep -v "^PIPESTATUS=" | grep -v "^BASH_LINENO="
+ set | grep -v "^_=" | grep -v "^PIPESTATUS=" | grep -v "^BASH_LINENO="
}
# save 2 'set'
@@ -25,9 +25,9 @@ myset >"$tmp2"
diff "$tmp1" "$tmp2" >/dev/null 2>&1
if [ $? -ne 0 ]
then
- # they differ, we cannot do the check
- echo >&2 "$me: cannot check with diff."
- can_diff=0
+ # they differ, we cannot do the check
+ echo >&2 "$me: cannot check with diff."
+ can_diff=0
fi
# do it again, now including the script
@@ -36,21 +36,21 @@ myset >"$tmp1"
# include the plugin and its config
if [ -f "$conf" ]
then
- . "$conf"
- if [ $? -ne 0 ]
- then
- echo >&2 "$me: cannot load config file $conf"
- rm "$tmp1" "$tmp2"
- exit 1
- fi
+ . "$conf"
+ if [ $? -ne 0 ]
+ then
+ echo >&2 "$me: cannot load config file $conf"
+ rm "$tmp1" "$tmp2"
+ exit 1
+ fi
fi
. "$chart"
if [ $? -ne 0 ]
then
- echo >&2 "$me: cannot load chart file $chart"
- rm "$tmp1" "$tmp2"
- exit 1
+ echo >&2 "$me: cannot load chart file $chart"
+ rm "$tmp1" "$tmp2"
+ exit 1
fi
# remove all variables starting with the plugin name
@@ -58,15 +58,15 @@ myset | grep -v "^$name" >"$tmp2"
if [ $can_diff -eq 1 ]
then
- # check if they are different
- # make sure they don't differ
- diff "$tmp1" "$tmp2" >&2
- if [ $? -ne 0 ]
- then
- # they differ
- rm "$tmp1" "$tmp2"
- exit 1
- fi
+ # check if they are different
+ # make sure they don't differ
+ diff "$tmp1" "$tmp2" >&2
+ if [ $? -ne 0 ]
+ then
+ # they differ
+ rm "$tmp1" "$tmp2"
+ exit 1
+ fi
fi
rm "$tmp1" "$tmp2"
diff --git a/plugins.d/charts.d.plugin b/plugins.d/charts.d.plugin
index 2824fa3c6..9aaadc168 100755
--- a/plugins.d/charts.d.plugin
+++ b/plugins.d/charts.d.plugin
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
PROGRAM_FILE="$0"
PROGRAM_NAME="$(basename $0)"
@@ -14,24 +14,24 @@ 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
@@ -61,7 +61,7 @@ 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
+update_every=${minimum_update_frequency} # this will be overwritten by the command line
# work around for non BASH shells
charts_create="_create"
@@ -106,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
@@ -157,26 +157,26 @@ 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
+ 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."
+ 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
@@ -185,22 +185,22 @@ fi
# can emulate the timeout command we need:
# > timeout SECONDS command ...
if [ $check_for_timeout -eq 1 ]
- then
- require_cmd timeout || exit 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
@@ -210,13 +210,13 @@ fi
# default sleep function
LOOPSLEEPMS_HIGHRES=0
loopsleepms() {
- [ "$1" = "tellwork" ] && shift
- 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()
@@ -229,10 +229,10 @@ current_time_ms() {
# 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]"
+ 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
@@ -241,57 +241,57 @@ fixid() {
# 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}'"
+ 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}'"
}
@@ -299,84 +299,105 @@ float2int() {
# 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= 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"
+ 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"
}
@@ -387,32 +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"
- 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
+ [ $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"
@@ -427,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
# -----------------------------------------------------------------------------
@@ -454,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
@@ -468,9 +493,9 @@ trap chartsd_cleanup INT
if [ $UID = "0" ]
then
- TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
+ TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
else
- TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
+ TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
fi
cd "$TMP_DIR" || exit 1
@@ -481,15 +506,15 @@ cd "$TMP_DIR" || exit 1
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'"
@@ -498,109 +523,131 @@ done
# update dimensions
if [ -z "$run_charts" ]
- then
- echo >&2 "$PROGRAM_NAME: No charts to collect data from."
- echo "DISABLE"
- exit 1
+ 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=()
+declare -A charts_last_update=() charts_update_every=() charts_next_update=() charts_run_counter=() charts_serial_failures=()
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
+ 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
diff --git a/plugins.d/loopsleepms.sh.inc b/plugins.d/loopsleepms.sh.inc
index 2e22de3d8..02ab694d2 100644
--- a/plugins.d/loopsleepms.sh.inc
+++ b/plugins.d/loopsleepms.sh.inc
@@ -1,4 +1,4 @@
-#!/bin/bash
+# no need for shebang - this file is included from other scripts
# this function is used to sleep a fraction of a second
# it calculates the difference between every time is called
@@ -7,9 +7,9 @@
LOOPSLEEP_DATE="$(which date)"
if [ -z "$LOOPSLEEP_DATE" ]
- then
- echo >&2 "$0: ERROR: Cannot find the command 'date' in the system path."
- exit 1
+ then
+ echo >&2 "$0: ERROR: Cannot find the command 'date' in the system path."
+ exit 1
fi
LOOPSLEEPMS_LASTRUN=0
@@ -21,70 +21,70 @@ test "$($LOOPSLEEP_DATE +%N)" = "%N" && LOOPSLEEPMS_HIGHRES=0
now_ms=
current_time_ms() {
- # if high resolution is not supported
- # just sleep the time requested, in seconds
- if [ $LOOPSLEEPMS_HIGHRES -eq 0 ]
- then
- now_ms="$($LOOPSLEEP_DATE +'%s')000"
- else
- now_ms="$(( $( $LOOPSLEEP_DATE +'%s * 1000 + %-N / 1000000' ) ))"
- fi
+ # if high resolution is not supported
+ # just sleep the time requested, in seconds
+ if [ $LOOPSLEEPMS_HIGHRES -eq 0 ]
+ then
+ now_ms="$($LOOPSLEEP_DATE +'%s')000"
+ else
+ now_ms="$(( $( $LOOPSLEEP_DATE +'%s * 1000 + %-N / 1000000' ) ))"
+ fi
}
loopsleepms() {
- local tellwork=0 t="$1" div s m now mstosleep
+ local tellwork=0 t="$1" div s m now mstosleep
- if [ "$t" = "tellwork" ]
- then
- tellwork=1
- shift
- t="$1"
- fi
- div="${2-100}"
+ if [ "$t" = "tellwork" ]
+ then
+ tellwork=1
+ shift
+ t="$1"
+ fi
+ div="${2-100}"
- # $t = the time in seconds to wait
+ # $t = the time in seconds to wait
- # if high resolution is not supported
- # just sleep the time requested, in seconds
- if [ $LOOPSLEEPMS_HIGHRES -eq 0 ]
- then
- sleep $t
- return
- fi
+ # if high resolution is not supported
+ # just sleep the time requested, in seconds
+ if [ $LOOPSLEEPMS_HIGHRES -eq 0 ]
+ then
+ sleep $t
+ return
+ fi
- # get the current time, in ms
- # milliseconds since epoch (1-1-1970)
- now="$(( $( $LOOPSLEEP_DATE +'%s * 1000 + %-N / 1000000' ) ))"
+ # get the current time, in ms
+ # milliseconds since epoch (1-1-1970)
+ now="$(( $( $LOOPSLEEP_DATE +'%s * 1000 + %-N / 1000000' ) ))"
- # calculate required sleep in ms
- t=$((t * 1000 * div / 100))
+ # calculate required sleep in ms
+ t=$((t * 1000 * div / 100))
- # this is our first run
- # just wait the requested time
- test $LOOPSLEEPMS_LASTRUN -eq 0 && LOOPSLEEPMS_LASTRUN=$now
+ # this is our first run
+ # just wait the requested time
+ test $LOOPSLEEPMS_LASTRUN -eq 0 && LOOPSLEEPMS_LASTRUN=$now
- # calculate ms since last run
- LOOPSLEEPMS_LASTWORK=$((now - LOOPSLEEPMS_LASTRUN - LOOPSLEEPMS_LASTSLEEP))
- # echo "# last loop's work took $LOOPSLEEPMS_LASTWORK ms"
+ # calculate ms since last run
+ LOOPSLEEPMS_LASTWORK=$((now - LOOPSLEEPMS_LASTRUN - LOOPSLEEPMS_LASTSLEEP))
+ # echo "# last loop's work took $LOOPSLEEPMS_LASTWORK ms"
- # calculate ms to sleep
- mstosleep=$(( t - LOOPSLEEPMS_LASTWORK ))
- # echo "# mstosleep is $mstosleep ms"
+ # calculate ms to sleep
+ mstosleep=$(( t - LOOPSLEEPMS_LASTWORK ))
+ # echo "# mstosleep is $mstosleep ms"
- # if we are too slow, sleep some time
- test $mstosleep -lt 200 && mstosleep=200
+ # if we are too slow, sleep some time
+ test $mstosleep -lt 200 && mstosleep=200
- s=$((mstosleep / 1000))
- m=$((mstosleep - (s * 1000)))
+ s=$((mstosleep / 1000))
+ m=$((mstosleep - (s * 1000)))
- test $tellwork -eq 1 && echo >&2 " >>> PERFORMANCE >>> WORK TOOK $LOOPSLEEPMS_LASTWORK ms ( $((LOOPSLEEPMS_LASTWORK * 100 / 1000)).$((LOOPSLEEPMS_LASTWORK % 10))% cpu ) >>> SLEEPING $mstosleep ms"
+ test $tellwork -eq 1 && echo >&2 " >>> PERFORMANCE >>> WORK TOOK $LOOPSLEEPMS_LASTWORK ms ( $((LOOPSLEEPMS_LASTWORK * 100 / 1000)).$((LOOPSLEEPMS_LASTWORK % 10))% cpu ) >>> SLEEPING $mstosleep ms"
- # echo "# sleeping $s.$m"
- # echo
- sleep $s.$m
+ # echo "# sleeping $s.$m"
+ # echo
+ sleep $s.$m
- # keep the values we need
- # for our next run
- LOOPSLEEPMS_LASTRUN=$now
- LOOPSLEEPMS_LASTSLEEP=$mstosleep
+ # keep the values we need
+ # for our next run
+ LOOPSLEEPMS_LASTRUN=$now
+ LOOPSLEEPMS_LASTSLEEP=$mstosleep
}
diff --git a/plugins.d/node.d.plugin b/plugins.d/node.d.plugin
index a1fa754fa..21b04384e 100755
--- a/plugins.d/node.d.plugin
+++ b/plugins.d/node.d.plugin
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/usr/bin/env bash
':' //; exec "$(command -v nodejs || command -v node || command -v js || echo "ERROR node.js IS NOT AVAILABLE IN THIS SYSTEM")" "$0" "$@"
// shebang hack from:
@@ -40,55 +40,60 @@ var netdata = require('netdata');
// configuration
function pluginConfig(filename) {
- var f = path.basename(filename);
+ var f = path.basename(filename);
- var m = f.match('.plugin' + '$');
- if(m === null) m = f.match('.node.js' + '$');
- if(m !== null)
- return netdata.options.paths.config + '/' + f.substring(0, m.index) + '.conf';
+ // node.d.plugin configuration
+ var m = f.match('.plugin' + '$');
+ if(m !== null)
+ return netdata.options.paths.config + '/' + f.substring(0, m.index) + '.conf';
- return netdata.options.paths.config + '/' + f + '.conf';
+ // node.d modules configuration
+ m = f.match('.node.js' + '$');
+ if(m !== null)
+ return netdata.options.paths.config + '/node.d/' + f.substring(0, m.index) + '.conf';
+
+ return netdata.options.paths.config + '/node.d/' + f + '.conf';
}
// internal defaults
extend(true, netdata.options, {
- filename: path.basename(__filename),
+ filename: path.basename(__filename),
- update_every: NETDATA_UPDATE_EVERY,
+ update_every: NETDATA_UPDATE_EVERY,
- exit_after_ms: 3600 * 4 * 1000,
+ exit_after_ms: 3600 * 4 * 1000,
- paths: {
- plugins: NETDATA_PLUGINS_DIR,
- config: NETDATA_CONFIG_DIR,
- modules: [],
- },
+ paths: {
+ plugins: NETDATA_PLUGINS_DIR,
+ config: NETDATA_CONFIG_DIR,
+ modules: [],
+ },
- modules_enable_autodetect: true,
- modules_enable_all: true,
- modules: {},
+ modules_enable_autodetect: true,
+ modules_enable_all: true,
+ modules: {},
});
netdata.options.config_filename = pluginConfig(__filename);
// load configuration file
try {
- netdata.options_loaded = JSON.parse(fs.readFileSync(netdata.options.config_filename, 'utf8'));
- extend(true, netdata.options, netdata.options_loaded);
- console.error('merged netdata object:');
- console.error(netdata);
+ netdata.options_loaded = JSON.parse(fs.readFileSync(netdata.options.config_filename, 'utf8'));
+ extend(true, netdata.options, netdata.options_loaded);
+ console.error('merged netdata object:');
+ console.error(netdata);
}
catch(e) {
- netdata.error('Cannot read configuration file ' + netdata.options.config_filename + ': ' + e.message + ', using internal defaults.');
- netdata.options_loaded = undefined;
- dumpError(e);
+ netdata.error('Cannot read configuration file ' + netdata.options.config_filename + ': ' + e.message + ', using internal defaults.');
+ netdata.options_loaded = undefined;
+ dumpError(e);
}
// apply module paths to node.js process
function applyModulePaths() {
- var len = netdata.options.paths.modules.length;
- while(len--)
- process.mainModule.paths.unshift(netdata.options.paths.modules[len]);
+ var len = netdata.options.paths.modules.length;
+ while(len--)
+ process.mainModule.paths.unshift(netdata.options.paths.modules[len]);
}
applyModulePaths();
@@ -97,165 +102,165 @@ applyModulePaths();
// tracing
function dumpError(err) {
- if (typeof err === 'object') {
- if (err.stack) {
- netdata.debug(err.stack);
- }
- }
+ if (typeof err === 'object') {
+ if (err.stack) {
+ netdata.debug(err.stack);
+ }
+ }
}
// --------------------------------------------------------------------------------------------------------------------
// get command line arguments
{
- var found_myself = false;
- var found_number = false;
- var found_modules = false;
- process.argv.forEach(function (val, index, array) {
- netdata.debug('PARAM: ' + val);
-
- if(!found_myself) {
- if(val === __filename)
- found_myself = true;
- }
- else {
- switch(val) {
- case 'debug':
- netdata.options.DEBUG = true;
- netdata.debug('DEBUG enabled');
- break;
-
- default:
- if(found_number === true) {
- if(found_modules === false) {
- for(var i in netdata.options.modules)
- netdata.options.modules[i].enabled = false;
- }
-
- if(typeof netdata.options.modules[val] === 'undefined')
- netdata.options.modules[val] = {};
-
- netdata.options.modules[val].enabled = true;
- netdata.options.modules_enable_all = false;
- netdata.debug('enabled module ' + val);
- }
- else {
- try {
- var x = parseInt(val);
- if(x > 0) {
- netdata.options.update_every = x;
- if(netdata.options.update_every < NETDATA_UPDATE_EVERY) {
- netdata.options.update_every = NETDATA_UPDATE_EVERY;
- netdata.debug('Update frequency ' + x + 's is too low');
- }
-
- found_number = true;
- netdata.debug('Update frequency set to ' + netdata.options.update_every + ' seconds');
- }
- else netdata.error('Ignoring parameter: ' + val);
- }
- catch(e) {
- netdata.error('Cannot get value of parameter: ' + val);
- dumpError(e);
- }
- }
- break;
- }
- }
- });
+ var found_myself = false;
+ var found_number = false;
+ var found_modules = false;
+ process.argv.forEach(function (val, index, array) {
+ netdata.debug('PARAM: ' + val);
+
+ if(!found_myself) {
+ if(val === __filename)
+ found_myself = true;
+ }
+ else {
+ switch(val) {
+ case 'debug':
+ netdata.options.DEBUG = true;
+ netdata.debug('DEBUG enabled');
+ break;
+
+ default:
+ if(found_number === true) {
+ if(found_modules === false) {
+ for(var i in netdata.options.modules)
+ netdata.options.modules[i].enabled = false;
+ }
+
+ if(typeof netdata.options.modules[val] === 'undefined')
+ netdata.options.modules[val] = {};
+
+ netdata.options.modules[val].enabled = true;
+ netdata.options.modules_enable_all = false;
+ netdata.debug('enabled module ' + val);
+ }
+ else {
+ try {
+ var x = parseInt(val);
+ if(x > 0) {
+ netdata.options.update_every = x;
+ if(netdata.options.update_every < NETDATA_UPDATE_EVERY) {
+ netdata.options.update_every = NETDATA_UPDATE_EVERY;
+ netdata.debug('Update frequency ' + x + 's is too low');
+ }
+
+ found_number = true;
+ netdata.debug('Update frequency set to ' + netdata.options.update_every + ' seconds');
+ }
+ else netdata.error('Ignoring parameter: ' + val);
+ }
+ catch(e) {
+ netdata.error('Cannot get value of parameter: ' + val);
+ dumpError(e);
+ }
+ }
+ break;
+ }
+ }
+ });
}
if(netdata.options.update_every < 1) {
- netdata.debug('Adjusting update frequency to 1 second');
- netdata.options.update_every = 1;
+ netdata.debug('Adjusting update frequency to 1 second');
+ netdata.options.update_every = 1;
}
// --------------------------------------------------------------------------------------------------------------------
// find modules
function findModules() {
- var found = 0;
-
- var files = fs.readdirSync(NODE_D_DIR);
- var len = files.length;
- while(len--) {
- var m = files[len].match('.node.js' + '$');
- if(m !== null) {
- var n = files[len].substring(0, m.index);
-
- if(typeof(netdata.options.modules[n]) === 'undefined')
- netdata.options.modules[n] = { name: n, enabled: netdata.options.modules_enable_all };
-
- if(netdata.options.modules[n].enabled === true) {
- netdata.options.modules[n].name = n;
- netdata.options.modules[n].filename = NODE_D_DIR + '/' + files[len];
- netdata.options.modules[n].loaded = false;
-
- if(typeof(netdata.options.modules[n].config_filename) !== 'string')
- netdata.options.modules[n].config_filename = pluginConfig(files[len]);
-
- // load the module
- try {
- netdata.debug('loading module ' + netdata.options.modules[n].filename);
- netdata.options.modules[n].module = require(netdata.options.modules[n].filename);
- netdata.options.modules[n].module.name = n;
- netdata.debug('loaded module ' + netdata.options.modules[n].name + ' from ' + netdata.options.modules[n].filename);
- }
- catch(e) {
- netdata.options.modules[n].enabled = false;
- netdata.error('Cannot load module: ' + netdata.options.modules[n].filename + ' exception: ' + e);
- dumpError(e);
- continue;
- }
-
- // load its configuration
- var c = {
- enable_autodetect: netdata.options.modules_enable_autodetect,
- update_every: netdata.options.update_every
- };
- try {
- netdata.debug('loading module\'s ' + netdata.options.modules[n].name + ' config ' + netdata.options.modules[n].config_filename);
- var c2 = JSON.parse(fs.readFileSync(netdata.options.modules[n].config_filename, 'utf8'));
- extend(true, c, c2);
- netdata.debug('loaded module\'s ' + netdata.options.modules[n].name + ' config ' + netdata.options.modules[n].config_filename);
- }
- catch(e) {
- netdata.error('Cannot load module\'s ' + netdata.options.modules[n].name + ' config from ' + netdata.options.modules[n].config_filename + ' exception: ' + e + ', using internal defaults.');
- dumpError(e);
- }
-
- // call module auto-detection / configuration
- try {
- netdata.modules_configuring++;
- netdata.debug('Configuring module ' + netdata.options.modules[n].name);
- var serv = netdata.configure(netdata.options.modules[n].module, c, function() {
- netdata.debug('Configured module ' + netdata.options.modules[n].name);
- netdata.modules_configuring--;
- });
-
- netdata.debug('Configuring module ' + netdata.options.modules[n].name + ' reports ' + serv + ' eligible services.');
- }
- catch(e) {
- netdata.modules_configuring--;
- netdata.options.modules[n].enabled = false;
- netdata.error('Failed module auto-detection: ' + netdata.options.modules[n].name + ' exception: ' + e + ', disabling module.');
- dumpError(e);
- continue;
- }
-
- netdata.options.modules[n].loaded = true;
- found++;
- }
- }
- }
-
- // netdata.debug(netdata.options.modules);
- return found;
+ var found = 0;
+
+ var files = fs.readdirSync(NODE_D_DIR);
+ var len = files.length;
+ while(len--) {
+ var m = files[len].match('.node.js' + '$');
+ if(m !== null) {
+ var n = files[len].substring(0, m.index);
+
+ if(typeof(netdata.options.modules[n]) === 'undefined')
+ netdata.options.modules[n] = { name: n, enabled: netdata.options.modules_enable_all };
+
+ if(netdata.options.modules[n].enabled === true) {
+ netdata.options.modules[n].name = n;
+ netdata.options.modules[n].filename = NODE_D_DIR + '/' + files[len];
+ netdata.options.modules[n].loaded = false;
+
+ if(typeof(netdata.options.modules[n].config_filename) !== 'string')
+ netdata.options.modules[n].config_filename = pluginConfig(files[len]);
+
+ // load the module
+ try {
+ netdata.debug('loading module ' + netdata.options.modules[n].filename);
+ netdata.options.modules[n].module = require(netdata.options.modules[n].filename);
+ netdata.options.modules[n].module.name = n;
+ netdata.debug('loaded module ' + netdata.options.modules[n].name + ' from ' + netdata.options.modules[n].filename);
+ }
+ catch(e) {
+ netdata.options.modules[n].enabled = false;
+ netdata.error('Cannot load module: ' + netdata.options.modules[n].filename + ' exception: ' + e);
+ dumpError(e);
+ continue;
+ }
+
+ // load its configuration
+ var c = {
+ enable_autodetect: netdata.options.modules_enable_autodetect,
+ update_every: netdata.options.update_every
+ };
+ try {
+ netdata.debug('loading module\'s ' + netdata.options.modules[n].name + ' config ' + netdata.options.modules[n].config_filename);
+ var c2 = JSON.parse(fs.readFileSync(netdata.options.modules[n].config_filename, 'utf8'));
+ extend(true, c, c2);
+ netdata.debug('loaded module\'s ' + netdata.options.modules[n].name + ' config ' + netdata.options.modules[n].config_filename);
+ }
+ catch(e) {
+ netdata.error('Cannot load module\'s ' + netdata.options.modules[n].name + ' config from ' + netdata.options.modules[n].config_filename + ' exception: ' + e + ', using internal defaults.');
+ dumpError(e);
+ }
+
+ // call module auto-detection / configuration
+ try {
+ netdata.modules_configuring++;
+ netdata.debug('Configuring module ' + netdata.options.modules[n].name);
+ var serv = netdata.configure(netdata.options.modules[n].module, c, function() {
+ netdata.debug('Configured module ' + netdata.options.modules[n].name);
+ netdata.modules_configuring--;
+ });
+
+ netdata.debug('Configuring module ' + netdata.options.modules[n].name + ' reports ' + serv + ' eligible services.');
+ }
+ catch(e) {
+ netdata.modules_configuring--;
+ netdata.options.modules[n].enabled = false;
+ netdata.error('Failed module auto-detection: ' + netdata.options.modules[n].name + ' exception: ' + e + ', disabling module.');
+ dumpError(e);
+ continue;
+ }
+
+ netdata.options.modules[n].loaded = true;
+ found++;
+ }
+ }
+ }
+
+ // netdata.debug(netdata.options.modules);
+ return found;
}
if(findModules() === 0) {
- netdata.error('Cannot load any .node.js module from: ' + NODE_D_DIR);
- netdata.disableNodePlugin();
- process.exit(1);
+ netdata.error('Cannot load any .node.js module from: ' + NODE_D_DIR);
+ netdata.disableNodePlugin();
+ process.exit(1);
}
@@ -263,14 +268,14 @@ if(findModules() === 0) {
// start
function start_when_configuring_ends() {
- if(netdata.modules_configuring > 0) {
- netdata.debug('Waiting modules configuration, still running ' + netdata.modules_configuring);
- setTimeout(start_when_configuring_ends, 500);
- return;
- }
-
- netdata.modules_configuring = 0;
- netdata.start();
+ if(netdata.modules_configuring > 0) {
+ netdata.debug('Waiting modules configuration, still running ' + netdata.modules_configuring);
+ setTimeout(start_when_configuring_ends, 500);
+ return;
+ }
+
+ netdata.modules_configuring = 0;
+ netdata.start();
}
start_when_configuring_ends();
diff --git a/plugins.d/python.d.plugin b/plugins.d/python.d.plugin
new file mode 100755
index 000000000..5e81fb263
--- /dev/null
+++ b/plugins.d/python.d.plugin
@@ -0,0 +1,533 @@
+#!/usr/bin/env bash
+'''':; exec "$(command -v python || command -v python3 || command -v python2 || echo "ERROR python IS NOT AVAILABLE IN THIS SYSTEM")" "$0" "$@" # '''
+# -*- coding: utf-8 -*-
+
+# Description: netdata python modules supervisor
+# Author: Pawel Krupa (paulfantom)
+
+import os
+import sys
+import time
+import threading
+
+# -----------------------------------------------------------------------------
+# globals & environment setup
+# https://github.com/firehol/netdata/wiki/External-Plugins#environment-variables
+MODULE_EXTENSION = ".chart.py"
+BASE_CONFIG = {'update_every': os.getenv('NETDATA_UPDATE_EVERY', 1),
+ 'priority': 90000,
+ 'retries': 10}
+
+MODULES_DIR = os.path.abspath(os.getenv('NETDATA_PLUGINS_DIR',
+ os.path.dirname(__file__)) + "/../python.d") + "/"
+CONFIG_DIR = os.getenv('NETDATA_CONFIG_DIR', "/etc/netdata/")
+# directories should end with '/'
+if CONFIG_DIR[-1] != "/":
+ CONFIG_DIR += "/"
+sys.path.append(MODULES_DIR + "python_modules")
+
+PROGRAM = os.path.basename(__file__).replace(".plugin", "")
+DEBUG_FLAG = False
+OVERRIDE_UPDATE_EVERY = False
+
+# -----------------------------------------------------------------------------
+# custom, third party and version specific python modules management
+import msg
+
+try:
+ assert sys.version_info >= (3, 1)
+ import importlib.machinery
+ PY_VERSION = 3
+ # change this hack below if we want PY_VERSION to be used in modules
+ # import builtins
+ # builtins.PY_VERSION = 3
+ msg.info('Using python v3')
+except (AssertionError, ImportError):
+ try:
+ import imp
+
+ # change this hack below if we want PY_VERSION to be used in modules
+ # import __builtin__
+ # __builtin__.PY_VERSION = 2
+ PY_VERSION = 2
+ msg.info('Using python v2')
+ except ImportError:
+ msg.fatal('Cannot start. No importlib.machinery on python3 or lack of imp on python2')
+# try:
+# import yaml
+# except ImportError:
+# msg.fatal('Cannot find yaml library')
+try:
+ if PY_VERSION == 3:
+ import pyyaml3 as yaml
+ else:
+ import pyyaml2 as yaml
+except ImportError:
+ msg.fatal('Cannot find yaml library')
+
+
+class PythonCharts(object):
+ """
+ Main class used to control every python module.
+ """
+
+ def __init__(self,
+ modules=None,
+ modules_path='../python.d/',
+ modules_configs='../conf.d/',
+ modules_disabled=None):
+ """
+ :param modules: list
+ :param modules_path: str
+ :param modules_configs: str
+ :param modules_disabled: list
+ """
+
+ if modules is None:
+ modules = []
+ if modules_disabled is None:
+ modules_disabled = []
+
+ self.first_run = True
+ # set configuration directory
+ self.configs = modules_configs
+
+ # load modules
+ loaded_modules = self._load_modules(modules_path, modules, modules_disabled)
+
+ # load configuration files
+ configured_modules = self._load_configs(loaded_modules)
+
+ # good economy and prosperity:
+ self.jobs = self._create_jobs(configured_modules) # type: list
+
+ # enable timetable override like `python.d.plugin mysql debug 1`
+ if DEBUG_FLAG and OVERRIDE_UPDATE_EVERY:
+ for job in self.jobs:
+ job.create_timetable(BASE_CONFIG['update_every'])
+
+ @staticmethod
+ def _import_module(path, name=None):
+ """
+ Try to import module using only its path.
+ :param path: str
+ :param name: str
+ :return: object
+ """
+
+ if name is None:
+ name = path.split('/')[-1]
+ if name[-len(MODULE_EXTENSION):] != MODULE_EXTENSION:
+ return None
+ name = name[:-len(MODULE_EXTENSION)]
+ try:
+ if PY_VERSION == 3:
+ return importlib.machinery.SourceFileLoader(name, path).load_module()
+ else:
+ return imp.load_source(name, path)
+ except Exception as e:
+ msg.error("Problem loading", name, str(e))
+ return None
+
+ def _load_modules(self, path, modules, disabled):
+ """
+ Load modules from 'modules' list or dynamically every file from 'path' (only .chart.py files)
+ :param path: str
+ :param modules: list
+ :param disabled: list
+ :return: list
+ """
+
+ # check if plugin directory exists
+ if not os.path.isdir(path):
+ msg.fatal("cannot find charts directory ", path)
+
+ # load modules
+ loaded = []
+ if len(modules) > 0:
+ for m in modules:
+ if m in disabled:
+ continue
+ mod = self._import_module(path + m + MODULE_EXTENSION)
+ if mod is not None:
+ loaded.append(mod)
+ else: # exit if plugin is not found
+ msg.fatal('no modules found.')
+ else:
+ # scan directory specified in path and load all modules from there
+ names = os.listdir(path)
+ for mod in names:
+ if mod.replace(MODULE_EXTENSION, "") in disabled:
+ msg.error(mod + ": disabled module ", mod.replace(MODULE_EXTENSION, ""))
+ continue
+ m = self._import_module(path + mod)
+ if m is not None:
+ msg.debug(mod + ": loading module '" + path + mod + "'")
+ loaded.append(m)
+ return loaded
+
+ def _load_configs(self, modules):
+ """
+ Append configuration in list named `config` to every module.
+ For multi-job modules `config` list is created in _parse_config,
+ otherwise it is created here based on BASE_CONFIG prototype with None as identifier.
+ :param modules: list
+ :return: list
+ """
+ for mod in modules:
+ configfile = self.configs + mod.__name__ + ".conf"
+ if os.path.isfile(configfile):
+ msg.debug(mod.__name__ + ": loading module configuration: '" + configfile + "'")
+ try:
+ if not hasattr(mod, 'config'):
+ mod.config = {}
+ setattr(mod,
+ 'config',
+ self._parse_config(mod, read_config(configfile)))
+ except Exception as e:
+ msg.error(mod.__name__ + ": cannot parse configuration file '" + configfile + "':", str(e))
+ else:
+ msg.error(mod.__name__ + ": configuration file '" + configfile + "' not found. Using defaults.")
+ # set config if not found
+ if not hasattr(mod, 'config'):
+ msg.debug(mod.__name__ + ": setting configuration for only one job")
+ mod.config = {None: {}}
+ for var in BASE_CONFIG:
+ try:
+ mod.config[None][var] = getattr(mod, var)
+ except AttributeError:
+ mod.config[None][var] = BASE_CONFIG[var]
+ return modules
+
+ @staticmethod
+ def _parse_config(module, config):
+ """
+ Parse configuration file or extract configuration from module file.
+ Example of returned dictionary:
+ config = {'name': {
+ 'update_every': 2,
+ 'retries': 3,
+ 'priority': 30000
+ 'other_val': 123}}
+ :param module: object
+ :param config: dict
+ :return: dict
+ """
+ if config is None:
+ config = {}
+ # get default values
+ defaults = {}
+ msg.debug(module.__name__ + ": reading configuration")
+ for key in BASE_CONFIG:
+ try:
+ # get defaults from module config
+ defaults[key] = int(config.pop(key))
+ except (KeyError, ValueError):
+ try:
+ # get defaults from module source code
+ defaults[key] = getattr(module, key)
+ except (KeyError, ValueError, AttributeError):
+ # if above failed, get defaults from global dict
+ defaults[key] = BASE_CONFIG[key]
+
+ # check if there are dict in config dict
+ many_jobs = False
+ for name in config:
+ if type(config[name]) is dict:
+ many_jobs = True
+ break
+
+ # assign variables needed by supervisor to every job configuration
+ if many_jobs:
+ for name in config:
+ for key in defaults:
+ if key not in config[name]:
+ config[name][key] = defaults[key]
+ # if only one job is needed, values doesn't have to be in dict (in YAML)
+ else:
+ config = {None: config.copy()}
+ config[None].update(defaults)
+
+ # return dictionary of jobs where every job has BASE_CONFIG variables
+ return config
+
+ @staticmethod
+ def _create_jobs(modules):
+ """
+ Create jobs based on module.config dictionary and module.Service class definition.
+ :param modules: list
+ :return: list
+ """
+ jobs = []
+ for module in modules:
+ for name in module.config:
+ # register a new job
+ conf = module.config[name]
+ try:
+ job = module.Service(configuration=conf, name=name)
+ except Exception as e:
+ msg.error(module.__name__ +
+ ("/" + str(name) if name is not None else "") +
+ ": cannot start job: '" +
+ str(e))
+ return None
+ else:
+ # set chart_name (needed to plot run time graphs)
+ job.chart_name = module.__name__
+ if name is not None:
+ job.chart_name += "_" + name
+ jobs.append(job)
+ msg.debug(module.__name__ + ("/" + str(name) if name is not None else "") + ": job added")
+
+ return [j for j in jobs if j is not None]
+
+ def _stop(self, job, reason=None):
+ """
+ Stop specified job and remove it from self.jobs list
+ Also notifies user about job failure if DEBUG_FLAG is set
+ :param job: object
+ :param reason: str
+ """
+ prefix = job.__module__
+ if job.name is not None and len(job.name) != 0:
+ prefix += "/" + job.name
+ try:
+ self.jobs.remove(job)
+ msg.info("Disabled", prefix)
+ except Exception as e:
+ msg.debug("This shouldn't happen. NO " + prefix + " IN LIST:" + str(self.jobs) + " ERROR: " + str(e))
+
+ # TODO remove section below and remove `reason`.
+ prefix += ": "
+ if reason is None:
+ return
+ elif reason[:3] == "no ":
+ msg.error(prefix +
+ "does not seem to have " +
+ reason[3:] +
+ "() function. Disabling it.")
+ elif reason[:7] == "failed ":
+ msg.error(prefix +
+ reason[7:] +
+ "() function reports failure.")
+ elif reason[:13] == "configuration":
+ msg.error(prefix +
+ "configuration file '" +
+ self.configs +
+ job.__module__ +
+ ".conf' not found. Using defaults.")
+ elif reason[:11] == "misbehaving":
+ msg.error(prefix + "is " + reason)
+
+ def check(self):
+ """
+ Tries to execute check() on every job.
+ This cannot fail thus it is catching every exception
+ If job.check() fails job is stopped
+ """
+ i = 0
+ overridden = []
+ msg.debug("all job objects", str(self.jobs))
+ while i < len(self.jobs):
+ job = self.jobs[i]
+ try:
+ if not job.check():
+ msg.error(job.chart_name, "check function failed.")
+ self._stop(job)
+ else:
+ msg.debug(job.chart_name, "check succeeded")
+ i += 1
+ try:
+ if job.override_name is not None:
+ new_name = job.__module__ + '_' + job.override_name
+ if new_name in overridden:
+ msg.error(job.override_name + " already exists. Stopping '" + job.name + "'")
+ self._stop(job)
+ i -= 1
+ else:
+ job.name = job.override_name
+ msg.debug(job.chart_name + " changing chart name to: '" + new_name + "'")
+ job.chart_name = new_name
+ overridden.append(job.chart_name)
+ except Exception:
+ pass
+ except AttributeError as e:
+ self._stop(job)
+ msg.error(job.chart_name, "cannot find check() function or it thrown unhandled exception.")
+ msg.debug(str(e))
+ except (UnboundLocalError, Exception) as e:
+ msg.error(job.chart_name, str(e))
+ self._stop(job)
+ msg.debug("overridden job names:", str(overridden))
+ msg.debug("all remaining job objects:", str(self.jobs))
+
+ def create(self):
+ """
+ Tries to execute create() on every job.
+ This cannot fail thus it is catching every exception.
+ If job.create() fails job is stopped.
+ This is also creating job run time chart.
+ """
+ i = 0
+ while i < len(self.jobs):
+ job = self.jobs[i]
+ try:
+ if not job.create():
+ msg.error(job.chart_name, "create function failed.")
+ self._stop(job)
+ else:
+ chart = job.chart_name
+ sys.stdout.write(
+ "CHART netdata.plugin_pythond_" +
+ chart +
+ " '' 'Execution time for " +
+ chart +
+ " plugin' 'milliseconds / run' python.d netdata.plugin_python area 145000 " +
+ str(job.timetable['freq']) +
+ '\n')
+ sys.stdout.write("DIMENSION run_time 'run time' absolute 1 1\n\n")
+ msg.debug("created charts for", job.chart_name)
+ # sys.stdout.flush()
+ i += 1
+ except AttributeError:
+ msg.error(job.chart_name, "cannot find create() function or it thrown unhandled exception.")
+ self._stop(job)
+ except (UnboundLocalError, Exception) as e:
+ msg.error(job.chart_name, str(e))
+ self._stop(job)
+
+ def update(self):
+ """
+ Creates and supervises every job thread.
+ This will stay forever and ever and ever forever and ever it'll be the one...
+ """
+ for job in self.jobs:
+ job.start()
+
+ while True:
+ if threading.active_count() <= 1:
+ msg.fatal("no more jobs")
+ time.sleep(1)
+
+
+def read_config(path):
+ """
+ Read YAML configuration from specified file
+ :param path: str
+ :return: dict
+ """
+ try:
+ with open(path, 'r') as stream:
+ config = yaml.load(stream)
+ except (OSError, IOError):
+ msg.error(str(path), "is not a valid configuration file")
+ return None
+ except yaml.YAMLError as e:
+ msg.error(str(path), "is malformed:", e)
+ return None
+ return config
+
+
+def parse_cmdline(directory, *commands):
+ """
+ Parse parameters from command line.
+ :param directory: str
+ :param commands: list of str
+ :return: dict
+ """
+ global DEBUG_FLAG
+ global OVERRIDE_UPDATE_EVERY
+ global BASE_CONFIG
+
+ changed_update = False
+ mods = []
+ for cmd in commands[1:]:
+ if cmd == "check":
+ pass
+ elif cmd == "debug" or cmd == "all":
+ DEBUG_FLAG = True
+ # redirect stderr to stdout?
+ elif os.path.isfile(directory + cmd + ".chart.py") or os.path.isfile(directory + cmd):
+ #DEBUG_FLAG = True
+ mods.append(cmd.replace(".chart.py", ""))
+ else:
+ try:
+ BASE_CONFIG['update_every'] = int(cmd)
+ changed_update = True
+ except ValueError:
+ pass
+ if changed_update and DEBUG_FLAG:
+ OVERRIDE_UPDATE_EVERY = True
+ msg.debug(PROGRAM, "overriding update interval to", str(BASE_CONFIG['update_every']))
+
+ msg.debug("started from", commands[0], "with options:", *commands[1:])
+
+ return mods
+
+
+# if __name__ == '__main__':
+def run():
+ """
+ Main program.
+ """
+ global DEBUG_FLAG, BASE_CONFIG
+
+ # read configuration file
+ disabled = []
+ configfile = CONFIG_DIR + "python.d.conf"
+ msg.PROGRAM = PROGRAM
+ msg.info("reading configuration file:", configfile)
+ log_counter = 200
+ log_interval = 3600
+
+ conf = read_config(configfile)
+ if conf is not None:
+ try:
+ # exit the whole plugin when 'enabled: no' is set in 'python.d.conf'
+ if conf['enabled'] is False:
+ msg.fatal('disabled in configuration file.\n')
+ except (KeyError, TypeError):
+ pass
+ try:
+ for param in BASE_CONFIG:
+ BASE_CONFIG[param] = conf[param]
+ except (KeyError, TypeError):
+ pass # use default update_every from NETDATA_UPDATE_EVERY
+ try:
+ DEBUG_FLAG = conf['debug']
+ except (KeyError, TypeError):
+ pass
+ try:
+ log_counter = conf['logs_per_interval']
+ except (KeyError, TypeError):
+ pass
+ try:
+ log_interval = conf['log_interval']
+ except (KeyError, TypeError):
+ pass
+ for k, v in conf.items():
+ if k in ("update_every", "debug", "enabled"):
+ continue
+ if v is False:
+ disabled.append(k)
+
+ # parse passed command line arguments
+ modules = parse_cmdline(MODULES_DIR, *sys.argv)
+ msg.DEBUG_FLAG = DEBUG_FLAG
+ msg.LOG_COUNTER = log_counter
+ msg.LOG_INTERVAL = log_interval
+ msg.info("MODULES_DIR='" + MODULES_DIR +
+ "', CONFIG_DIR='" + CONFIG_DIR +
+ "', UPDATE_EVERY=" + str(BASE_CONFIG['update_every']) +
+ ", ONLY_MODULES=" + str(modules))
+
+ # run plugins
+ charts = PythonCharts(modules, MODULES_DIR, CONFIG_DIR + "python.d/", disabled)
+ charts.check()
+ charts.create()
+ charts.update()
+ msg.fatal("finished")
+
+
+if __name__ == '__main__':
+ run()
diff --git a/plugins.d/tc-qos-helper.sh b/plugins.d/tc-qos-helper.sh
index 7b4739815..bff5217d2 100755
--- a/plugins.d/tc-qos-helper.sh
+++ b/plugins.d/tc-qos-helper.sh
@@ -1,107 +1,126 @@
-#!/bin/bash
+#!/usr/bin/env bash
export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin"
+PROGRAM_FILE="$0"
+PROGRAM_NAME="$(basename $0)"
+PROGRAM_NAME="${PROGRAM_NAME/.plugin}"
+
+plugins_dir="${NETDATA_PLUGINS_DIR}"
+[ -z "$plugins_dir" ] && plugins_dir="$( dirname $PROGRAM_FILE )"
+
+config_dir=${NETDATA_CONFIG_DIR-/etc/netdata}
+tc="$(which tc 2>/dev/null)"
+fireqos_run_dir="/var/run/fireqos"
+qos_get_class_names_every=120
+qos_exit_every=3600
+
+# check if we have a valid number for interval
+t=${1}
+update_every=$((t))
+[ $((update_every)) -lt 1 ] && update_every=${NETDATA_UPDATE_EVERY}
+[ $((update_every)) -lt 1 ] && update_every=1
+
+# allow the user to override our defaults
+if [ -f "${config_dir}/tc-qos-helper.conf" ]
+ then
+ source "${config_dir}/tc-qos-helper.conf"
+fi
+
# default time function
now_ms=
current_time_ms() {
- now_ms="$(date +'%s')000"
+ now_ms="$(date +'%s')000"
}
# default sleep function
LOOPSLEEPMS_LASTWORK=0
loopsleepms() {
- [ "$1" = "tellwork" ] && shift
- sleep $1
+ [ "$1" = "tellwork" ] && shift
+ sleep $1
}
# if found and included, this file overwrites loopsleepms()
# with a high resolution timer function for precise looping.
-. "$NETDATA_PLUGINS_DIR/loopsleepms.sh.inc"
+. "${plugins_dir}/loopsleepms.sh.inc"
-# check if we have a valid number for interval
-t=$1
-sleep_time=$((t))
-[ $((sleep_time)) -lt 1 ] && $NETDATA_UPDATE_EVERY
-[ $((sleep_time)) -lt 1 ] && sleep_time=1
-
-tc_cmd="$(which tc)"
-if [ -z "$tc_cmd" ]
- then
- echo >&2 "tc: Cannot find a 'tc' command in this system."
- exit 1
+if [ -z "${tc}" -o ! -x "${tc}" ]
+ then
+ echo >&2 "${PROGRAM_NAME}: Cannot find command 'tc' in this system."
+ exit 1
fi
devices=
fix_names=
setclassname() {
- echo "SETCLASSNAME $3 $2"
+ echo "SETCLASSNAME $3 $2"
}
show_tc() {
- local x="$1"
-
- echo "BEGIN $x"
- $tc_cmd -s class show dev $x
-
- # check FireQOS names for classes
- if [ ! -z "$fix_names" -a -f /var/run/fireqos/ifaces/$x ]
- then
- name="$(cat /var/run/fireqos/ifaces/$x)"
- echo "SETDEVICENAME $name"
-
- interface_classes=
- interface_classes_monitor=
- . /var/run/fireqos/$name.conf
- for n in $interface_classes_monitor
- do
- setclassname $(echo $n | tr '|' ' ')
- done
- echo "SETDEVICEGROUP $interface_dev"
- fi
- echo "END $x"
+ local x="${1}" interface_dev interface_classes interface_classes_monitor
+
+ echo "BEGIN ${x}"
+ ${tc} -s class show dev ${x}
+
+ # check FireQOS names for classes
+ if [ ! -z "${fix_names}" -a -f "${fireqos_run_dir}/ifaces/${x}" ]
+ then
+ name="$(<"${fireqos_run_dir}/ifaces/${x}")"
+ echo "SETDEVICENAME ${name}"
+
+ interface_dev=
+ interface_classes=
+ interface_classes_monitor=
+ source "${fireqos_run_dir}/${name}.conf"
+ for n in ${interface_classes_monitor}
+ do
+ setclassname ${n//|/ }
+ done
+ [ ! -z "${interface_dev}" ] && echo "SETDEVICEGROUP ${interface_dev}"
+ fi
+ echo "END ${x}"
}
all_devices() {
- cat /proc/net/dev | grep ":" | cut -d ':' -f 1 | while read dev
- do
- l=$($tc_cmd class show dev $dev | wc -l)
- [ $l -ne 0 ] && echo $dev
- done
+ cat /proc/net/dev | grep ":" | cut -d ':' -f 1 | while read dev
+ do
+ l=$(${tc} class show dev ${dev} | wc -l)
+ [ $l -ne 0 ] && echo ${dev}
+ done
}
# update devices and class names
# once every 2 minutes
-names_every=$((120 / sleep_time))
+names_every=$((qos_get_class_names_every / update_every))
# exit this script every hour
# it will be restarted automatically
-exit_after=$((3600 / sleep_time))
+exit_after=$((qos_exit_every / update_every))
c=0
gc=0
while [ 1 ]
do
- fix_names=
- c=$((c + 1))
- gc=$((gc + 1))
+ fix_names=
+ c=$((c + 1))
+ gc=$((gc + 1))
- if [ $c -le 1 -o $c -ge $names_every ]
- then
- c=1
- fix_names="YES"
- devices="$( all_devices )"
- fi
+ if [ ${c} -le 1 -o ${c} -ge ${names_every} ]
+ then
+ c=1
+ fix_names="YES"
+ devices="$( all_devices )"
+ fi
- for d in $devices
- do
- show_tc $d
- done
+ for d in ${devices}
+ do
+ show_tc ${d}
+ done
- echo "WORKTIME $LOOPSLEEPMS_LASTWORK"
+ echo "WORKTIME ${LOOPSLEEPMS_LASTWORK}"
- loopsleepms $sleep_time
+ loopsleepms ${update_every}
- [ $gc -gt $exit_after ] && exit 0
+ [ ${gc} -gt ${exit_after} ] && exit 0
done