| 1 | #!/usr/bin/env bash |
| 2 | # SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | # |
| 4 | # charts.d.plugin allows easy development of BASH plugins |
| 5 | # |
| 6 | # if you need to run parallel charts.d processes, link this file to a different name |
| 7 | # in the same directory, with a .plugin suffix and netdata will start both of them, |
| 8 | # each will have a different config file and modules configuration directory. |
| 9 | # |
| 10 | |
| 11 | export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:@sbindir_POST@" |
| 12 | |
| 13 | PROGRAM_FILE="$0" |
| 14 | MODULE_NAME="main" |
| 15 | |
| 16 | # ----------------------------------------------------------------------------- |
| 17 | # logging |
| 18 | |
| 19 | PROGRAM_NAME="$(basename "${0}")" |
| 20 | SHORT_PROGRAM_NAME="${PROGRAM_NAME/.plugin/}" |
| 21 | |
| 22 | # these should be the same with syslog() priorities |
| 23 | NDLP_EMERG=0 # system is unusable |
| 24 | NDLP_ALERT=1 # action must be taken immediately |
| 25 | NDLP_CRIT=2 # critical conditions |
| 26 | NDLP_ERR=3 # error conditions |
| 27 | NDLP_WARN=4 # warning conditions |
| 28 | NDLP_NOTICE=5 # normal but significant condition |
| 29 | NDLP_INFO=6 # informational |
| 30 | NDLP_DEBUG=7 # debug-level messages |
| 31 | |
| 32 | # the max (numerically) log level we will log |
| 33 | LOG_LEVEL=$NDLP_INFO |
| 34 | |
| 35 | set_log_min_priority() { |
| 36 | case "${NETDATA_LOG_LEVEL,,}" in |
| 37 | "emerg" | "emergency") |
| 38 | LOG_LEVEL=$NDLP_EMERG |
| 39 | ;; |
| 40 | |
| 41 | "alert") |
| 42 | LOG_LEVEL=$NDLP_ALERT |
| 43 | ;; |
| 44 | |
| 45 | "crit" | "critical") |
| 46 | LOG_LEVEL=$NDLP_CRIT |
| 47 | ;; |
| 48 | |
| 49 | "err" | "error") |
| 50 | LOG_LEVEL=$NDLP_ERR |
| 51 | ;; |
| 52 | |
| 53 | "warn" | "warning") |
| 54 | LOG_LEVEL=$NDLP_WARN |
| 55 | ;; |
| 56 | |
| 57 | "notice") |
| 58 | LOG_LEVEL=$NDLP_NOTICE |
| 59 | ;; |
| 60 | |
| 61 | "info") |
| 62 | LOG_LEVEL=$NDLP_INFO |
| 63 | ;; |
| 64 | |
| 65 | "debug") |
| 66 | LOG_LEVEL=$NDLP_DEBUG |
| 67 | ;; |
| 68 | esac |
| 69 | } |
| 70 | |
| 71 | set_log_min_priority |
| 72 | |
| 73 | log() { |
| 74 | local level="${1}" |
| 75 | shift 1 |
| 76 | |
| 77 | [[ -n "$level" && -n "$LOG_LEVEL" && "$level" -gt "$LOG_LEVEL" ]] && return |
| 78 | |
| 79 | systemd-cat-native --log-as-netdata <<EOFLOG |
| 80 | INVOCATION_ID=${NETDATA_INVOCATION_ID} |
| 81 | SYSLOG_IDENTIFIER=${PROGRAM_NAME} |
| 82 | PRIORITY=${level} |
| 83 | THREAD_TAG=charts.d.plugin |
| 84 | ND_LOG_SOURCE=collector |
| 85 | MESSAGE=${MODULE_NAME}: ${*//$'\n'/\\n} |
| 86 | |
| 87 | EOFLOG |
| 88 | # AN EMPTY LINE IS NEEDED ABOVE |
| 89 | } |
| 90 | |
| 91 | info() { |
| 92 | log "$NDLP_INFO" "${@}" |
| 93 | } |
| 94 | |
| 95 | warning() { |
| 96 | log "$NDLP_WARN" "${@}" |
| 97 | } |
| 98 | |
| 99 | error() { |
| 100 | log "$NDLP_ERR" "${@}" |
| 101 | } |
| 102 | |
| 103 | fatal() { |
| 104 | log "$NDLP_ALERT" "${@}" |
| 105 | echo "DISABLE" |
| 106 | exit 1 |
| 107 | } |
| 108 | |
| 109 | debug() { |
| 110 | [ "$debug" = "1" ] && log "$NDLP_DEBUG" "${@}" |
| 111 | } |
| 112 | |
| 113 | # ----------------------------------------------------------------------------- |
| 114 | # check for BASH v4+ (required for associative arrays) |
| 115 | |
| 116 | if [ ${BASH_VERSINFO[0]} -lt 4 ]; then |
| 117 | echo >&2 "BASH version 4 or later is required (this is ${BASH_VERSION})." |
| 118 | exit 1 |
| 119 | fi |
| 120 | |
| 121 | # ----------------------------------------------------------------------------- |
| 122 | # create temp dir |
| 123 | |
| 124 | debug=0 |
| 125 | TMP_DIR= |
| 126 | chartsd_cleanup() { |
| 127 | trap '' EXIT QUIT HUP INT TERM |
| 128 | |
| 129 | if [ ! -z "$TMP_DIR" -a -d "$TMP_DIR" ]; then |
| 130 | [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: cleaning up temporary directory $TMP_DIR ..." |
| 131 | rm -rf "$TMP_DIR" |
| 132 | fi |
| 133 | echo "EXIT" 2>/dev/null |
| 134 | exit 0 |
| 135 | } |
| 136 | trap chartsd_cleanup EXIT QUIT HUP INT TERM |
| 137 | |
| 138 | if [ $UID = "0" ]; then |
| 139 | TMP_DIR="$(mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX)" |
| 140 | else |
| 141 | TMP_DIR="$(mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX)" |
| 142 | fi |
| 143 | |
| 144 | logdate() { |
| 145 | date "+%Y-%m-%d %H:%M:%S" |
| 146 | } |
| 147 | |
| 148 | # ----------------------------------------------------------------------------- |
| 149 | # check a few commands |
| 150 | |
| 151 | require_cmd() { |
| 152 | local x=$(which "${1}" 2>/dev/null || command -v "${1}" 2>/dev/null) |
| 153 | if [ -z "${x}" -o ! -x "${x}" ]; then |
| 154 | warning "command '${1}' is not found in ${PATH}." |
| 155 | eval "${1^^}_CMD=\"\"" |
| 156 | return 1 |
| 157 | fi |
| 158 | |
| 159 | eval "${1^^}_CMD=\"${x}\"" |
| 160 | return 0 |
| 161 | } |
| 162 | |
| 163 | require_cmd date || exit 1 |
| 164 | require_cmd sed || exit 1 |
| 165 | require_cmd basename || exit 1 |
| 166 | require_cmd dirname || exit 1 |
| 167 | require_cmd cat || exit 1 |
| 168 | require_cmd grep || exit 1 |
| 169 | require_cmd egrep || exit 1 |
| 170 | require_cmd mktemp || exit 1 |
| 171 | require_cmd awk || exit 1 |
| 172 | require_cmd timeout || exit 1 |
| 173 | require_cmd curl || exit 1 |
| 174 | |
| 175 | # ----------------------------------------------------------------------------- |
| 176 | |
| 177 | [ $((BASH_VERSINFO[0])) -lt 4 ] && fatal "BASH version 4 or later is required, but found version: ${BASH_VERSION}. Please upgrade." |
| 178 | |
| 179 | info "started from '$PROGRAM_FILE' with options: $*" |
| 180 | |
| 181 | # ----------------------------------------------------------------------------- |
| 182 | # internal defaults |
| 183 | # netdata exposes a few environment variables for us |
| 184 | |
| 185 | [ -z "${NETDATA_PLUGINS_DIR}" ] && NETDATA_PLUGINS_DIR="$(dirname "${0}")" |
| 186 | [ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@" |
| 187 | [ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@" |
| 188 | |
| 189 | pluginsd="${NETDATA_PLUGINS_DIR}" |
| 190 | stockconfd="${NETDATA_STOCK_CONFIG_DIR}/${SHORT_PROGRAM_NAME}" |
| 191 | userconfd="${NETDATA_USER_CONFIG_DIR}/${SHORT_PROGRAM_NAME}" |
| 192 | olduserconfd="${NETDATA_USER_CONFIG_DIR}" |
| 193 | chartsd="$pluginsd/../charts.d" |
| 194 | |
| 195 | minimum_update_frequency="${NETDATA_UPDATE_EVERY-1}" |
| 196 | update_every=${minimum_update_frequency} # this will be overwritten by the command line |
| 197 | |
| 198 | # work around for non BASH shells |
| 199 | charts_create="_create" |
| 200 | charts_update="_update" |
| 201 | charts_check="_check" |
| 202 | charts_underscore="_" |
| 203 | |
| 204 | # when making iterations, charts.d can loop more frequently |
| 205 | # to prevent plugins missing iterations. |
| 206 | # this is a percentage relative to update_every to align its |
| 207 | # iterations. |
| 208 | # The minimum is 10%, the maximum 100%. |
| 209 | # So, if update_every is 1 second and time_divisor is 50, |
| 210 | # charts.d will iterate every 500ms. |
| 211 | # Charts will be called to collect data only if the time |
| 212 | # passed since the last time the collected data is equal or |
| 213 | # above their update_every. |
| 214 | time_divisor=50 |
| 215 | |
| 216 | # number of seconds to run without restart |
| 217 | # after this time, charts.d.plugin will exit |
| 218 | # netdata will restart it |
| 219 | restart_timeout=$((3600 * 4)) |
| 220 | |
| 221 | # check if the charts.d plugins are using global variables |
| 222 | # they should not. |
| 223 | # It does not currently support BASH v4 arrays, so it is |
| 224 | # disabled |
| 225 | dryrunner=0 |
| 226 | |
| 227 | # check for timeout command |
| 228 | check_for_timeout=1 |
| 229 | |
| 230 | # the default enable/disable value for all charts |
| 231 | enable_all_charts="yes" |
| 232 | |
| 233 | # ----------------------------------------------------------------------------- |
| 234 | # parse parameters |
| 235 | |
| 236 | check=0 |
| 237 | chart_only= |
| 238 | while [ ! -z "$1" ]; do |
| 239 | if [ "$1" = "check" ]; then |
| 240 | check=1 |
| 241 | shift |
| 242 | continue |
| 243 | fi |
| 244 | |
| 245 | if [ "$1" = "debug" -o "$1" = "all" ]; then |
| 246 | debug=1 |
| 247 | LOG_LEVEL=$NDLP_DEBUG |
| 248 | shift |
| 249 | continue |
| 250 | fi |
| 251 | |
| 252 | if [ -f "$chartsd/$1.chart.sh" ]; then |
| 253 | debug=1 |
| 254 | LOG_LEVEL=$NDLP_DEBUG |
| 255 | chart_only="$(echo $1.chart.sh | sed "s/\.chart\.sh$//g")" |
| 256 | shift |
| 257 | continue |
| 258 | fi |
| 259 | |
| 260 | if [ -f "$chartsd/$1" ]; then |
| 261 | debug=1 |
| 262 | LOG_LEVEL=$NDLP_DEBUG |
| 263 | chart_only="$(echo $1 | sed "s/\.chart\.sh$//g")" |
| 264 | shift |
| 265 | continue |
| 266 | fi |
| 267 | |
| 268 | # number check |
| 269 | n="$1" |
| 270 | x=$((n)) |
| 271 | if [ "$x" = "$n" ]; then |
| 272 | shift |
| 273 | update_every=$x |
| 274 | [ $update_every -lt $minimum_update_frequency ] && update_every=$minimum_update_frequency |
| 275 | continue |
| 276 | fi |
| 277 | |
| 278 | fatal "Cannot understand parameter $1. Aborting." |
| 279 | done |
| 280 | |
| 281 | # ----------------------------------------------------------------------------- |
| 282 | # loop control |
| 283 | |
| 284 | # default sleep function |
| 285 | LOOPSLEEPMS_HIGHRES=0 |
| 286 | now_ms= |
| 287 | current_time_ms_default() { |
| 288 | now_ms="$(date +'%s')000" |
| 289 | } |
| 290 | current_time_ms="current_time_ms_default" |
| 291 | current_time_ms_accuracy=1 |
| 292 | mysleep="sleep" |
| 293 | |
| 294 | # if found and included, this file overwrites loopsleepms() |
| 295 | # and current_time_ms() with a high resolution timer function |
| 296 | # for precise looping. |
| 297 | source "$pluginsd/loopsleepms.sh.inc" |
| 298 | [ $? -ne 0 ] && error "Failed to load '$pluginsd/loopsleepms.sh.inc'." |
| 299 | |
| 300 | # ----------------------------------------------------------------------------- |
| 301 | # load my configuration |
| 302 | |
| 303 | for myconfig in "${NETDATA_STOCK_CONFIG_DIR}/${SHORT_PROGRAM_NAME}.conf" "${NETDATA_USER_CONFIG_DIR}/${SHORT_PROGRAM_NAME}.conf"; do |
| 304 | if [ -f "$myconfig" ]; then |
| 305 | source "$myconfig" |
| 306 | if [ $? -ne 0 ]; then |
| 307 | error "Config file '$myconfig' loaded with errors." |
| 308 | else |
| 309 | info "Configuration file '$myconfig' loaded." |
| 310 | fi |
| 311 | else |
| 312 | warning "Configuration file '$myconfig' not found." |
| 313 | fi |
| 314 | done |
| 315 | |
| 316 | # make sure time_divisor is right |
| 317 | time_divisor=$((time_divisor)) |
| 318 | [ $time_divisor -lt 10 ] && time_divisor=10 |
| 319 | [ $time_divisor -gt 100 ] && time_divisor=100 |
| 320 | |
| 321 | # we check for the timeout command, after we load our |
| 322 | # configuration, so that the user may overwrite the |
| 323 | # timeout command we use, providing a function that |
| 324 | # can emulate the timeout command we need: |
| 325 | # > timeout SECONDS command ... |
| 326 | if [ $check_for_timeout -eq 1 ]; then |
| 327 | require_cmd timeout || exit 1 |
| 328 | fi |
| 329 | |
| 330 | # ----------------------------------------------------------------------------- |
| 331 | # internal checks |
| 332 | |
| 333 | # netdata passes the requested update frequency as the first argument |
| 334 | update_every=$((update_every + 1 - 1)) # makes sure it is a number |
| 335 | test $update_every -eq 0 && update_every=1 # if it is zero, make it 1 |
| 336 | |
| 337 | # check the charts.d directory |
| 338 | [ ! -d "$chartsd" ] && fatal "cannot find charts directory '$chartsd'" |
| 339 | |
| 340 | # ----------------------------------------------------------------------------- |
| 341 | # library functions |
| 342 | |
| 343 | fixid() { |
| 344 | echo "$*" | |
| 345 | tr -c "[A-Z][a-z][0-9]" "_" | |
| 346 | sed -e "s|^_\+||g" -e "s|_\+$||g" -e "s|_\+|_|g" | |
| 347 | tr "[A-Z]" "[a-z]" |
| 348 | } |
| 349 | |
| 350 | isvarset() { |
| 351 | [ -n "$1" ] && [ "$1" != "unknown" ] && [ "$1" != "none" ] |
| 352 | return $? |
| 353 | } |
| 354 | |
| 355 | getosid() { |
| 356 | if isvarset "${NETDATA_CONTAINER_OS_ID}"; then |
| 357 | echo "${NETDATA_CONTAINER_OS_ID}" |
| 358 | else |
| 359 | echo "${NETDATA_SYSTEM_OS_ID}" |
| 360 | fi |
| 361 | } |
| 362 | |
| 363 | run() { |
| 364 | local ret pid="${BASHPID}" t |
| 365 | |
| 366 | if [ "z${1}" = "z-t" -a "${2}" != "0" ]; then |
| 367 | t="${2}" |
| 368 | shift 2 |
| 369 | timeout "${t}" "${@}" 2>"${TMP_DIR}/run.${pid}" |
| 370 | ret=$? |
| 371 | else |
| 372 | "${@}" 2>"${TMP_DIR}/run.${pid}" |
| 373 | ret=$? |
| 374 | fi |
| 375 | |
| 376 | if [ ${ret} -ne 0 ]; then |
| 377 | { |
| 378 | printf "$(logdate): ${PROGRAM_NAME}: ${status}: ${MODULE_NAME}: command '" |
| 379 | printf "%q " "${@}" |
| 380 | printf "' failed with code ${ret}:\n --- BEGIN TRACE ---\n" |
| 381 | cat "${TMP_DIR}/run.${pid}" |
| 382 | printf " --- END TRACE ---\n" |
| 383 | } >&2 |
| 384 | fi |
| 385 | rm -f "${TMP_DIR}/run.${pid}" |
| 386 | |
| 387 | return ${ret} |
| 388 | } |
| 389 | |
| 390 | # convert any floating point number |
| 391 | # to integer, give a multiplier |
| 392 | # the result is stored in ${FLOAT2INT_RESULT} |
| 393 | # so that no fork is necessary |
| 394 | # the multiplier must be a power of 10 |
| 395 | float2int() { |
| 396 | local f m="$2" a b l v=($1) |
| 397 | f=${v[0]} |
| 398 | |
| 399 | # the length of the multiplier - 1 |
| 400 | l=$((${#m} - 1)) |
| 401 | |
| 402 | # check if the number is in scientific notation |
| 403 | if [[ ${f} =~ ^[[:space:]]*(-)?[0-9.]+(e|E)(\+|-)[0-9]+ ]]; then |
| 404 | # convert it to decimal |
| 405 | # unfortunately, this fork cannot be avoided |
| 406 | # if you know of a way to avoid it, please let me know |
| 407 | f=$(printf "%0.${l}f" ${f}) |
| 408 | fi |
| 409 | |
| 410 | # split the floating point number |
| 411 | # in integer (a) and decimal (b) |
| 412 | a=${f/.*/} |
| 413 | b=${f/*./} |
| 414 | |
| 415 | # if the integer part is missing |
| 416 | # set it to zero |
| 417 | [ -z "${a}" ] && a="0" |
| 418 | |
| 419 | # strip leading zeros from the integer part |
| 420 | # base 10 conversion |
| 421 | a=$((10#$a)) |
| 422 | |
| 423 | # check the length of the decimal part |
| 424 | # against the length of the multiplier |
| 425 | if [ ${#b} -gt ${l} ]; then |
| 426 | # too many digits - take the most significant |
| 427 | b=${b:0:l} |
| 428 | |
| 429 | elif [ ${#b} -lt ${l} ]; then |
| 430 | # too few digits - pad with zero on the right |
| 431 | local z="00000000000000000000000" r=$((l - ${#b})) |
| 432 | b="${b}${z:0:r}" |
| 433 | fi |
| 434 | |
| 435 | # strip leading zeros from the decimal part |
| 436 | # base 10 conversion |
| 437 | b=$((10#$b)) |
| 438 | |
| 439 | # store the result |
| 440 | FLOAT2INT_RESULT=$(((a * m) + b)) |
| 441 | } |
| 442 | |
| 443 | # ----------------------------------------------------------------------------- |
| 444 | # charts check functions |
| 445 | |
| 446 | all_charts() { |
| 447 | cd "$chartsd" |
| 448 | [ $? -ne 0 ] && error "cannot cd to $chartsd" && return 1 |
| 449 | |
| 450 | ls *.chart.sh | sed "s/\.chart\.sh$//g" |
| 451 | } |
| 452 | |
| 453 | declare -A charts_enable_keyword=( |
| 454 | ['apache']="force" |
| 455 | ['cpu_apps']="force" |
| 456 | ['cpufreq']="force" |
| 457 | ['example']="force" |
| 458 | ['exim']="force" |
| 459 | ['hddtemp']="force" |
| 460 | ['load_average']="force" |
| 461 | ['mem_apps']="force" |
| 462 | ['mysql']="force" |
| 463 | ['nginx']="force" |
| 464 | ['phpfpm']="force" |
| 465 | ['postfix']="force" |
| 466 | ['sensors']="force" |
| 467 | ['squid']="force" |
| 468 | ['tomcat']="force" |
| 469 | ) |
| 470 | |
| 471 | declare -A obsolete_charts=( |
| 472 | ['ap']="go.d/ap" |
| 473 | ['apache']="python.d.plugin module" |
| 474 | ['cpu_apps']="apps.plugin" |
| 475 | ['cpufreq']="proc plugin" |
| 476 | ['exim']="python.d.plugin module" |
| 477 | ['hddtemp']="python.d.plugin module" |
| 478 | ['load_average']="proc plugin" |
| 479 | ['mem_apps']="proc plugin" |
| 480 | ['mysql']="python.d.plugin module" |
| 481 | ['nginx']="python.d.plugin module" |
| 482 | ['phpfpm']="python.d.plugin module" |
| 483 | ['postfix']="python.d.plugin module" |
| 484 | ['squid']="python.d.plugin module" |
| 485 | ['tomcat']="python.d.plugin module" |
| 486 | ) |
| 487 | |
| 488 | all_enabled_charts() { |
| 489 | local charts enabled required |
| 490 | |
| 491 | # find all enabled charts |
| 492 | for chart in $(all_charts); do |
| 493 | MODULE_NAME="${chart}" |
| 494 | |
| 495 | if [ -n "${obsolete_charts["$MODULE_NAME"]}" ]; then |
| 496 | debug "is replaced by ${obsolete_charts["$MODULE_NAME"]}, skipping it." |
| 497 | continue |
| 498 | fi |
| 499 | |
| 500 | eval "enabled=\$$chart" |
| 501 | if [ -z "${enabled}" ]; then |
| 502 | enabled="${enable_all_charts}" |
| 503 | fi |
| 504 | |
| 505 | required="${charts_enable_keyword[${chart}]}" |
| 506 | [ -z "${required}" ] && required="yes" |
| 507 | |
| 508 | if [ ! "${enabled}" = "${required}" ]; then |
| 509 | info "is disabled. Add a line with $chart=$required in '${NETDATA_USER_CONFIG_DIR}/${PROGRAM_NAME}.conf' to enable it (or remove the line that disables it)." |
| 510 | else |
| 511 | debug "is enabled for auto-detection." |
| 512 | local charts="$charts $chart" |
| 513 | fi |
| 514 | done |
| 515 | MODULE_NAME="main" |
| 516 | |
| 517 | local charts2= |
| 518 | for chart in $charts; do |
| 519 | MODULE_NAME="${chart}" |
| 520 | |
| 521 | # check the enabled charts |
| 522 | local check="$(cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_check()")" |
| 523 | if [ -z "$check" ]; then |
| 524 | error "module '$chart' does not seem to have a $chart$charts_check() function. Disabling it." |
| 525 | continue |
| 526 | fi |
| 527 | |
| 528 | local create="$(cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_create()")" |
| 529 | if [ -z "$create" ]; then |
| 530 | error "module '$chart' does not seem to have a $chart$charts_create() function. Disabling it." |
| 531 | continue |
| 532 | fi |
| 533 | |
| 534 | local update="$(cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_update()")" |
| 535 | if [ -z "$update" ]; then |
| 536 | error "module '$chart' does not seem to have a $chart$charts_update() function. Disabling it." |
| 537 | continue |
| 538 | fi |
| 539 | |
| 540 | # check its config |
| 541 | #if [ -f "$userconfd/$chart.conf" ] |
| 542 | #then |
| 543 | # if [ ! -z "$( cat "$userconfd/$chart.conf" | sed "s/^ \+//g" | grep -v "^$" | grep -v "^#" | grep -v "^$chart$charts_underscore" )" ] |
| 544 | # then |
| 545 | # error "module's $chart config $userconfd/$chart.conf should only have lines starting with $chart$charts_underscore . Disabling it." |
| 546 | # continue |
| 547 | # fi |
| 548 | #fi |
| 549 | |
| 550 | #if [ $dryrunner -eq 1 ] |
| 551 | # then |
| 552 | # "$pluginsd/charts.d.dryrun-helper.sh" "$chart" "$chartsd/$chart.chart.sh" "$userconfd/$chart.conf" >/dev/null |
| 553 | # if [ $? -ne 0 ] |
| 554 | # then |
| 555 | # error "module's $chart did not pass the dry run check. This means it uses global variables not starting with $chart. Disabling it." |
| 556 | # continue |
| 557 | # fi |
| 558 | #fi |
| 559 | |
| 560 | local charts2="$charts2 $chart" |
| 561 | done |
| 562 | MODULE_NAME="main" |
| 563 | |
| 564 | echo $charts2 |
| 565 | debug "enabled charts: $charts2" |
| 566 | } |
| 567 | |
| 568 | # ----------------------------------------------------------------------------- |
| 569 | # load the charts |
| 570 | |
| 571 | suffix_retries="_retries" |
| 572 | suffix_update_every="_update_every" |
| 573 | active_charts= |
| 574 | for chart in $(all_enabled_charts); do |
| 575 | MODULE_NAME="${chart}" |
| 576 | |
| 577 | debug "loading module: '$chartsd/$chart.chart.sh'" |
| 578 | |
| 579 | source "$chartsd/$chart.chart.sh" |
| 580 | [ $? -ne 0 ] && warning "Module '$chartsd/$chart.chart.sh' loaded with errors." |
| 581 | |
| 582 | # first load the stock config |
| 583 | if [ -f "$stockconfd/$chart.conf" ]; then |
| 584 | debug "loading module configuration: '$stockconfd/$chart.conf'" |
| 585 | source "$stockconfd/$chart.conf" |
| 586 | [ $? -ne 0 ] && warning "Config file '$stockconfd/$chart.conf' loaded with errors." |
| 587 | else |
| 588 | debug "not found module configuration: '$stockconfd/$chart.conf'" |
| 589 | fi |
| 590 | |
| 591 | # then load the user config (it overwrites the stock) |
| 592 | if [ -f "$userconfd/$chart.conf" ]; then |
| 593 | debug "loading module configuration: '$userconfd/$chart.conf'" |
| 594 | source "$userconfd/$chart.conf" |
| 595 | [ $? -ne 0 ] && warning "Config file '$userconfd/$chart.conf' loaded with errors." |
| 596 | else |
| 597 | debug "not found module configuration: '$userconfd/$chart.conf'" |
| 598 | |
| 599 | if [ -f "$olduserconfd/$chart.conf" ]; then |
| 600 | # support for very old netdata that had the charts.d module configs in /etc/netdata |
| 601 | info "loading module configuration from obsolete location: '$olduserconfd/$chart.conf'" |
| 602 | source "$olduserconfd/$chart.conf" |
| 603 | [ $? -ne 0 ] && warning "Config file '$olduserconfd/$chart.conf' loaded with errors." |
| 604 | fi |
| 605 | fi |
| 606 | |
| 607 | eval "dt=\$$chart$suffix_update_every" |
| 608 | dt=$((dt + 1 - 1)) # make sure it is a number |
| 609 | if [ $dt -lt $update_every ]; then |
| 610 | eval "$chart$suffix_update_every=$update_every" |
| 611 | fi |
| 612 | |
| 613 | $chart$charts_check |
| 614 | if [ $? -eq 0 ]; then |
| 615 | debug "module '$chart' activated" |
| 616 | active_charts="$active_charts $chart" |
| 617 | else |
| 618 | error "module's '$chart' check() function reports failure." |
| 619 | fi |
| 620 | done |
| 621 | MODULE_NAME="main" |
| 622 | debug "activated modules: $active_charts" |
| 623 | |
| 624 | # ----------------------------------------------------------------------------- |
| 625 | # check overwrites |
| 626 | |
| 627 | # enable work time reporting |
| 628 | debug_time= |
| 629 | test $debug -eq 1 && debug_time=tellwork |
| 630 | |
| 631 | # if we only need a specific chart, remove all the others |
| 632 | if [ ! -z "${chart_only}" ]; then |
| 633 | debug "requested to run only for: '${chart_only}'" |
| 634 | check_charts= |
| 635 | for chart in $active_charts; do |
| 636 | if [ "$chart" = "$chart_only" ]; then |
| 637 | check_charts="$chart" |
| 638 | break |
| 639 | fi |
| 640 | done |
| 641 | active_charts="$check_charts" |
| 642 | fi |
| 643 | debug "activated charts: $active_charts" |
| 644 | |
| 645 | # stop if we just need a pre-check |
| 646 | if [ $check -eq 1 ]; then |
| 647 | info "CHECK RESULT" |
| 648 | info "Will run the charts: $active_charts" |
| 649 | exit 0 |
| 650 | fi |
| 651 | |
| 652 | # ----------------------------------------------------------------------------- |
| 653 | |
| 654 | cd "${TMP_DIR}" || exit 1 |
| 655 | |
| 656 | # ----------------------------------------------------------------------------- |
| 657 | # create charts |
| 658 | |
| 659 | run_charts= |
| 660 | for chart in $active_charts; do |
| 661 | MODULE_NAME="${chart}" |
| 662 | |
| 663 | debug "calling '$chart$charts_create()'..." |
| 664 | $chart$charts_create |
| 665 | if [ $? -eq 0 ]; then |
| 666 | run_charts="$run_charts $chart" |
| 667 | debug "'$chart' initialized." |
| 668 | else |
| 669 | error "module's '$chart' function '$chart$charts_create()' reports failure." |
| 670 | fi |
| 671 | done |
| 672 | MODULE_NAME="main" |
| 673 | debug "run_charts='$run_charts'" |
| 674 | |
| 675 | # ----------------------------------------------------------------------------- |
| 676 | # update dimensions |
| 677 | |
| 678 | [ -z "$run_charts" ] && fatal "No charts to collect data from." |
| 679 | |
| 680 | keepalive() { |
| 681 | if [ ! -t 1 ] && ! printf "\n"; then |
| 682 | chartsd_cleanup |
| 683 | fi |
| 684 | } |
| 685 | |
| 686 | declare -A charts_last_update=() charts_update_every=() charts_retries=() charts_next_update=() charts_run_counter=() charts_serial_failures=() |
| 687 | global_update() { |
| 688 | local exit_at \ |
| 689 | c=0 dt ret last_ms exec_start_ms exec_end_ms \ |
| 690 | chart now_charts=() next_charts=($run_charts) \ |
| 691 | next_ms x seconds millis |
| 692 | |
| 693 | # return the current time in ms in $now_ms |
| 694 | ${current_time_ms} |
| 695 | |
| 696 | exit_at=$((now_ms + (restart_timeout * 1000))) |
| 697 | |
| 698 | for chart in $run_charts; do |
| 699 | eval "charts_update_every[$chart]=\$$chart$suffix_update_every" |
| 700 | test -z "${charts_update_every[$chart]}" && charts_update_every[$chart]=$update_every |
| 701 | |
| 702 | eval "charts_retries[$chart]=\$$chart$suffix_retries" |
| 703 | test -z "${charts_retries[$chart]}" && charts_retries[$chart]=10 |
| 704 | |
| 705 | charts_last_update[$chart]=$((now_ms - (now_ms % (charts_update_every[$chart] * 1000)))) |
| 706 | charts_next_update[$chart]=$((charts_last_update[$chart] + (charts_update_every[$chart] * 1000))) |
| 707 | charts_run_counter[$chart]=0 |
| 708 | charts_serial_failures[$chart]=0 |
| 709 | |
| 710 | echo "CHART netdata.plugin_chartsd_$chart '' 'Execution time for $chart plugin' 'milliseconds / run' charts.d netdata.plugin_charts area 145000 ${charts_update_every[$chart]} '' '' '$chart'" |
| 711 | echo "DIMENSION run_time 'run time' absolute 1 1" |
| 712 | done |
| 713 | |
| 714 | # the main loop |
| 715 | while [ "${#next_charts[@]}" -gt 0 ]; do |
| 716 | keepalive |
| 717 | |
| 718 | c=$((c + 1)) |
| 719 | now_charts=("${next_charts[@]}") |
| 720 | next_charts=() |
| 721 | |
| 722 | # return the current time in ms in $now_ms |
| 723 | ${current_time_ms} |
| 724 | |
| 725 | for chart in "${now_charts[@]}"; do |
| 726 | MODULE_NAME="${chart}" |
| 727 | |
| 728 | if [ ${now_ms} -ge ${charts_next_update[$chart]} ]; then |
| 729 | last_ms=${charts_last_update[$chart]} |
| 730 | dt=$((now_ms - last_ms)) |
| 731 | |
| 732 | charts_last_update[$chart]=${now_ms} |
| 733 | |
| 734 | while [ ${charts_next_update[$chart]} -lt ${now_ms} ]; do |
| 735 | charts_next_update[$chart]=$((charts_next_update[$chart] + (charts_update_every[$chart] * 1000))) |
| 736 | done |
| 737 | |
| 738 | # the first call should not give a duration |
| 739 | # so that netdata calibrates to current time |
| 740 | dt=$((dt * 1000)) |
| 741 | charts_run_counter[$chart]=$((charts_run_counter[$chart] + 1)) |
| 742 | if [ ${charts_run_counter[$chart]} -eq 1 ]; then |
| 743 | dt= |
| 744 | fi |
| 745 | |
| 746 | exec_start_ms=$now_ms |
| 747 | $chart$charts_update $dt |
| 748 | ret=$? |
| 749 | |
| 750 | # return the current time in ms in $now_ms |
| 751 | ${current_time_ms} |
| 752 | exec_end_ms=$now_ms |
| 753 | |
| 754 | echo "BEGIN netdata.plugin_chartsd_$chart $dt" |
| 755 | echo "SET run_time = $((exec_end_ms - exec_start_ms))" |
| 756 | echo "END" |
| 757 | |
| 758 | if [ $ret -eq 0 ]; then |
| 759 | charts_serial_failures[$chart]=0 |
| 760 | next_charts+=($chart) |
| 761 | else |
| 762 | charts_serial_failures[$chart]=$((charts_serial_failures[$chart] + 1)) |
| 763 | |
| 764 | if [ ${charts_serial_failures[$chart]} -gt ${charts_retries[$chart]} ]; then |
| 765 | error "module's '$chart' update() function reported failure ${charts_serial_failures[$chart]} times. Disabling it." |
| 766 | else |
| 767 | error "module's '$chart' update() function reports failure. Will keep trying for a while." |
| 768 | next_charts+=($chart) |
| 769 | fi |
| 770 | fi |
| 771 | else |
| 772 | next_charts+=($chart) |
| 773 | fi |
| 774 | done |
| 775 | MODULE_NAME="${chart}" |
| 776 | |
| 777 | # wait the time you are required to |
| 778 | next_ms=$((now_ms + (update_every * 1000 * 100))) |
| 779 | for x in "${charts_next_update[@]}"; do [ ${x} -lt ${next_ms} ] && next_ms=${x}; done |
| 780 | next_ms=$((next_ms - now_ms)) |
| 781 | |
| 782 | if [ ${LOOPSLEEPMS_HIGHRES} -eq 1 -a ${next_ms} -gt 0 ]; then |
| 783 | next_ms=$((next_ms + current_time_ms_accuracy)) |
| 784 | seconds=$((next_ms / 1000)) |
| 785 | millis=$((next_ms % 1000)) |
| 786 | if [ ${millis} -lt 10 ]; then |
| 787 | millis="00${millis}" |
| 788 | elif [ ${millis} -lt 100 ]; then |
| 789 | millis="0${millis}" |
| 790 | fi |
| 791 | |
| 792 | debug "sleeping for ${seconds}.${millis} seconds." |
| 793 | ${mysleep} ${seconds}.${millis} |
| 794 | else |
| 795 | debug "sleeping for ${update_every} seconds." |
| 796 | ${mysleep} $update_every |
| 797 | fi |
| 798 | |
| 799 | test ${now_ms} -ge ${exit_at} && exit 0 |
| 800 | done |
| 801 | |
| 802 | fatal "nothing left to do, exiting..." |
| 803 | } |
| 804 | |
| 805 | global_update |