]> arthur.barton.de Git - netdata.git/blob - plugins.d/charts.d.plugin
exit every 4 hours
[netdata.git] / plugins.d / charts.d.plugin
1 #!/bin/bash
2
3 PROGRAM_FILE="$0"
4 PROGRAM_NAME="$(basename $0)"
5 PROGRAM_NAME="${PROGRAM_NAME/.plugin}"
6
7 # if you need to run parallel charts.d processes
8 # just link this files with a different name
9 # in the same directory, with a .plugin suffix
10 # netdata will start multiple of them
11 # each will have a different config file
12
13 echo >&2 "$PROGRAM_NAME: started from '$PROGRAM_FILE' with options: $*"
14
15 if [ $(( ${BASH_VERSINFO[0]} )) -lt 4 ]
16 then
17         echo >&2
18         echo >&2 "$PROGRAM_NAME: ERROR"
19         echo >&2 "BASH version 4 or later is required."
20         echo >&2 "You are running version: ${BASH_VERSION}"
21         echo >&2 "Please upgrade."
22         echo >&2
23         exit 1
24 fi
25
26 # check a few commands
27 require_cmd() {
28         which "$1" >/dev/null
29         if [ $? -ne 0 ]
30                 then
31                 echo >&2 "$PROGRAM_NAME: ERROR: Command '$1' is not found in the system path."
32                 return 1
33         fi
34         return 0
35 }
36
37 require_cmd date || exit 1
38 require_cmd sed || exit 1
39 require_cmd basename || exit 1
40 require_cmd dirname || exit 1
41 require_cmd cat || exit 1
42 require_cmd grep || exit 1
43 require_cmd egrep || exit 1
44 require_cmd mktemp || exit 1
45 require_cmd awk || exit 1
46
47 # -----------------------------------------------------------------------------
48 # insternal defaults
49 # netdata exposes a few environment variables for us
50
51 pause_method="sleep" # use either "suspend" or "sleep"
52                      # DO NOT USE SUSPEND - LINUX WILL SUSPEND NETDATA TOO
53                      # THE WHOLE PROCESS GROUP - NOT JUST THE SHELL
54
55 pluginsd="${NETDATA_PLUGINS_DIR}"
56 [ -z "$pluginsd" ] && pluginsd="$( dirname $PROGRAM_FILE )"
57
58 confd="${NETDATA_CONFIG_DIR-/etc/netdata}"
59 chartsd="$pluginsd/../charts.d"
60
61 myconfig="$confd/$PROGRAM_NAME.conf"
62
63 minimum_update_frequency="${NETDATA_UPDATE_EVERY-1}"
64 update_every=${minimum_update_frequency}        # this will be overwritten by the command line
65
66 # work around for non BASH shells
67 charts_create="_create"
68 charts_update="_update"
69 charts_check="_check"
70 charts_undescore="_"
71
72 # when making iterations, charts.d can loop more frequently
73 # to prevent plugins missing iterations.
74 # this is a percentage relative to update_every to align its
75 # iterations.
76 # The minimum is 10%, the maximum 100%.
77 # So, if update_every is 1 second and time_divisor is 50,
78 # charts.d will iterate every 500ms.
79 # Charts will be called to collect data only if the time
80 # passes since the last time the collected data is equal or
81 # above their update_every.
82 time_divisor=50
83
84 # number of seconds to run without restart
85 # after this time, charts.d.plugin will exit
86 # netdata will restart it
87 restart_timeout=$[3600 * 4]
88
89 # check if the charts.d plugins are using global variables
90 # they should not.
91 # It does not currently support BASH v4 arrays, so it is
92 # disabled
93 dryrunner=0
94
95 # check for timeout command
96 check_for_timeout=1
97
98 # the default enable/disable value for all charts
99 enable_all_charts="yes"
100
101 # -----------------------------------------------------------------------------
102 # parse parameters
103
104 debug=0
105 check=0
106 chart_only=
107 while [ ! -z "$1" ]
108 do
109         if [ "$1" = "check" ]
110         then
111                 check=1
112                 shift
113                 continue
114         fi
115
116         if [ "$1" = "debug" -o "$1" = "all" ]
117         then
118                 debug=1
119                 shift
120                 continue
121         fi
122
123         if [ -f "$chartsd/$1.chart.sh" ]
124         then
125                 debug=1
126                 chart_only="$( echo $1.chart.sh | sed "s/\.chart\.sh$//g" )"
127                 shift
128                 continue
129         fi
130
131         if [ -f "$chartsd/$1" ]
132         then
133                 debug=1
134                 chart_only="$( echo $1 | sed "s/\.chart\.sh$//g" )"
135                 shift
136                 continue
137         fi
138
139         # number check
140         n="$1"
141         x=$(( n ))
142         if [ "$x" = "$n" ]
143         then
144                 shift
145                 update_every=$x
146                 [ $update_every -lt $minimum_update_frequency ] && update_every=$minimum_update_frequency
147                 continue
148         fi
149
150         echo >&2 "Cannot understand parameter $1. Aborting."
151         echo "DISABLE"
152         exit 1
153 done
154
155
156 # -----------------------------------------------------------------------------
157 # load my configuration
158
159 if [ -f "$myconfig" ]
160         then
161         . "$myconfig"
162         if [ $? -ne 0 ]
163         then
164                 echo >&2 "$PROGRAM_NAME: cannot load $myconfig"
165                 echo "DISABLE"
166                 exit 1
167         fi
168         time_divisor=$((time_divisor))
169         [ $time_divisor -lt 10 ] && time_divisor=10
170         [ $time_divisor -gt 100 ] && time_divisor=100
171 else
172         echo >&2 "$PROGRAM_NAME: configuration file '$myconfig' not found. Using defaults."
173 fi
174
175 if [ "$pause_method" = "suspend" ]
176 then
177         # enable bash job control
178         # this is required for suspend to work
179         set -m
180 fi
181
182 # we check for the timeout command, after we load our
183 # configuration, so that the user may overwrite the
184 # timeout command we use, providing a function that
185 # can emulate the timeout command we need:
186 # > timeout SECONDS command ...
187 if [ $check_for_timeout -eq 1 ]
188         then
189         require_cmd timeout || exit 1
190 fi
191
192 # -----------------------------------------------------------------------------
193 # internal checks
194
195 # netdata passes the requested update frequency as the first argument
196 update_every=$(( update_every + 1 - 1)) # makes sure it is a number
197 test $update_every -eq 0 && update_every=1 # if it is zero, make it 1
198
199 # check the charts.d directory
200 if [ ! -d "$chartsd" ]
201         then
202         echo >&2 "$PROGRAM_NAME: cannot find charts directory '$chartsd'"
203         echo "DISABLE"
204 fi
205
206
207 # -----------------------------------------------------------------------------
208 # loop control
209
210 # default sleep function
211 LOOPSLEEPMS_HIGHRES=0
212 loopsleepms() {
213         [ "$1" = "tellwork" ] && shift
214         sleep $1
215 }
216
217 now_ms=
218 current_time_ms() {
219         now_ms="$(date +'%s')000"
220 }
221
222 # if found and included, this file overwrites loopsleepms()
223 # and current_time_ms() with a high resolution timer function
224 # for precise looping.
225 . "$pluginsd/loopsleepms.sh.inc"
226
227
228 # -----------------------------------------------------------------------------
229 # library functions
230
231 fixid() {
232         echo "$*" |\
233                 tr -c "[A-Z][a-z][0-9]" "_" |\
234                 sed -e "s|^_\+||g" -e "s|_\+$||g" -e "s|_\+|_|g" |\
235                 tr "[A-Z]" "[a-z]"
236 }
237
238 # convert any floating point number
239 # to integer, give a multiplier
240 # the result is stored in ${FLOAT2INT_RESULT}
241 # so that no fork is necessary
242 # the multiplier must be a power of 10
243 float2int() {
244         local f m="$2" a b l v=($1)
245         f=${v[0]}
246
247         # echo >&2 "value='${1}' f='${f}', m='${m}'"
248
249         # the length of the multiplier - 1
250         l=$[ ${#m} - 1 ]
251
252         # split the floating point number
253         # in integer (a) and decimal (b)
254         a=${f/.*/}
255         b=${f/*./}
256
257         # prepend a zero to the integer part
258         # if it is missing
259         [ -z "${a}" ] && a="0"
260         # strip leading zeros - base 10 convertion
261         a=$[10#$a]
262
263         # check the length of the decimal part
264         # against the length of the multiplier
265         if [ ${#b} -gt ${l} ]
266                 then
267                 # too many digits - take the most significant
268                 b=${b:0:${l}}
269         elif [ ${#b} -lt ${l} ]
270                 then
271                 # too few digits - pad with zero on the right
272                 local z="00000000000000000000000" r=$[l - ${#b}]
273                 b="${b}${z:0:${r}}"
274         fi
275         # strip leading zeros - base 10 convertion
276         b=$[10#$b]
277
278         # store the result
279         FLOAT2INT_RESULT=$[ (a * m) + b ]
280         #echo >&2 "FLOAT2INT_RESULT='${FLOAT2INT_RESULT}'"
281 }
282
283
284 # -----------------------------------------------------------------------------
285 # charts check functions
286
287 all_charts() {
288         cd "$chartsd"
289         [ $? -ne 0 ] && echo >&2 "$PROGRAM_NAME: Cannot cd to $chartsd" && return 1
290
291         ls *.chart.sh | sed "s/\.chart\.sh$//g"
292 }
293
294 all_enabled_charts() {
295         local charts= enabled=
296
297         # find all enabled charts
298
299         for chart in $( all_charts )
300         do
301                 eval "enabled=\$$chart"
302                 if [ -z "${enabled}" ]
303                         then
304                         enabled="${enable_all_charts}"
305                 fi
306
307                 if [ ! "${enabled}" = "yes" ]
308                 then
309                         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)."
310                 else
311                         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' is enabled."
312                         local charts="$charts $chart"
313                 fi
314         done
315
316         local charts2=
317         for chart in $charts
318         do
319                 # check the enabled charts
320                 local check="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_check()" )"
321                 if [ -z "$check" ]
322                 then
323                         echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_check() function. Disabling it."
324                         continue
325                 fi
326
327                 local create="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_create()" )"
328                 if [ -z "$create" ]
329                 then
330                         echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_create() function. Disabling it."
331                         continue
332                 fi
333
334                 local update="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_update()" )"
335                 if [ -z "$update" ]
336                 then
337                         echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_update() function. Disabling it."
338                         continue
339                 fi
340
341                 # check its config
342                 #if [ -f "$confd/$chart.conf" ]
343                 #then
344                 #       if [ ! -z "$( cat "$confd/$chart.conf" | sed "s/^ \+//g" | grep -v "^$" | grep -v "^#" | grep -v "^$chart$charts_undescore" )" ]
345                 #       then
346                 #               echo >&2 "$PROGRAM_NAME: chart's $chart config $confd/$chart.conf should only have lines starting with $chart$charts_undescore . Disabling it."
347                 #               continue
348                 #       fi
349                 #fi
350
351                 #if [ $dryrunner -eq 1 ]
352                 #       then
353                 #       "$pluginsd/charts.d.dryrun-helper.sh" "$chart" "$chartsd/$chart.chart.sh" "$confd/$chart.conf" >/dev/null
354                 #       if [ $? -ne 0 ]
355                 #       then
356                 #               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."
357                 #               continue
358                 #       fi
359                 #fi
360
361                 local charts2="$charts2 $chart"
362         done
363
364         echo $charts2
365         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: enabled charts: $charts2"
366 }
367
368
369 # -----------------------------------------------------------------------------
370 # load the charts
371
372 suffix_update_every="_update_every"
373 active_charts=
374 for chart in $( all_enabled_charts )
375 do
376         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart: '$chartsd/$chart.chart.sh'"
377         . "$chartsd/$chart.chart.sh"
378
379         if [ -f "$confd/$chart.conf" ]
380         then
381                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart options: '$confd/$chart.conf'"
382                 . "$confd/$chart.conf"
383         else
384                 echo >&2 "$PROGRAM_NAME: $chart: configuration file '$confd/$chart.conf' not found. Using defaults."
385         fi
386
387         eval "dt=\$$chart$suffix_update_every"
388         dt=$(( dt + 1 - 1 )) # make sure it is a number
389         if [ $dt -lt $update_every ]
390         then
391                 eval "$chart$suffix_update_every=$update_every"
392         fi
393
394         $chart$charts_check
395         if [ $? -eq 0 ]
396         then
397                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' activated"
398                 active_charts="$active_charts $chart"
399         else
400                 echo >&2 "$PROGRAM_NAME: chart '$chart' check() function reports failure."
401         fi
402 done
403 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts"
404
405
406 # -----------------------------------------------------------------------------
407 # check overwrites
408
409 # enable work time reporting
410 debug_time=
411 test $debug -eq 1 && debug_time=tellwork
412
413 # if we only need a specific chart, remove all the others
414 if [ ! -z "${chart_only}" ]
415 then
416         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: requested to run only for: '${chart_only}'"
417         check_charts=
418         for chart in $active_charts
419         do
420                 if [ "$chart" = "$chart_only" ]
421                 then
422                         check_charts="$chart"
423                         break
424                 fi
425         done
426         active_charts="$check_charts"
427 fi
428 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts"
429
430 # stop if we just need a pre-check
431 if [ $check -eq 1 ]
432 then
433         echo >&2 "CHECK RESULT"
434         echo >&2 "Will run the charts: $active_charts"
435         exit 0
436 fi
437
438 # -----------------------------------------------------------------------------
439 # create temp dir
440
441 TMP_DIR=
442 chartsd_cleanup() {
443         cd /tmp
444         if [ ! -z "$TMP_DIR" -a -d "$TMP_DIR" ]
445         then
446                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: cleaning up temporary directory $TMP_DIR ..."
447                 rm -rf "$TMP_DIR"
448         fi
449         exit 0
450 }
451 trap chartsd_cleanup EXIT
452 trap chartsd_cleanup SIGHUP
453 trap chartsd_cleanup INT
454
455 if [ $UID = "0" ]
456 then
457         TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
458 else
459         TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
460 fi
461
462 cd "$TMP_DIR" || exit 1
463
464 # -----------------------------------------------------------------------------
465 # create charts
466
467 run_charts=
468 for chart in $active_charts
469 do
470         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: Calling '$chart$charts_create()'..."
471         $chart$charts_create
472         if [ $? -eq 0 ]
473         then
474                 run_charts="$run_charts $chart"
475                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' has initialized."
476         else
477                 echo >&2 "$PROGRAM_NAME: chart '$chart' function '$chart$charts_create()' reports failure."
478         fi
479 done
480 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: run_charts='$run_charts'"
481
482
483 # -----------------------------------------------------------------------------
484 # update dimensions
485
486 if [ -z "$run_charts" ]
487         then
488         echo >&2 "$PROGRAM_NAME: No charts to collect data from."
489         echo "DISABLE"
490         exit 1
491 fi
492
493 declare -A charts_last_update=() charts_update_every=() charts_next_update=() charts_run_counter=()
494 global_update() {
495         local exit_at \
496                 c=0 dt ret last_ms exec_start_ms exec_end_ms \
497                 chart now_charts=() next_charts=($run_charts)
498
499         # return the current time in ms in $now_ms
500         current_time_ms
501
502         exit_at=$(( now_ms + (restart_timeout * 1000) ))
503
504         for chart in $run_charts
505         do
506                 eval "charts_update_every[$chart]=\$$chart$suffix_update_every"
507                 test -z "${charts_update_every[$chart]}" && charts_update_every[$charts]=$update_every
508                 charts_last_update[$chart]=$((now_ms - (now_ms % (charts_update_every[$chart] * 1000) ) ))
509                 charts_next_update[$chart]=$(( charts_last_update[$chart] + (charts_update_every[$chart] * 1000) ))
510                 charts_run_counter[$chart]=0
511
512                 echo "CHART netdata.plugin_chartsd_$chart '' 'Execution time for $chart plugin' 'milliseconds / run' netdata netdata area 90000 ${charts_update_every[$chart]}"
513                 echo "DIMENSION run_time 'run time' absolute 1 1"
514         done
515
516         # the main loop
517         while [ 1 ]
518         do
519                 c=$((c + 1))
520                 now_charts=("${next_charts[@]}")
521                 next_charts=()
522
523                 # return the current time in ms in $now_ms
524                 current_time_ms
525
526                 for chart in "${now_charts[@]}"
527                 do
528                         # echo >&2 "DEBUG: chart: $chart last: ${charts_last_update[$chart]}, next: ${charts_next_update[$chart]}, now: ${now_ms}"
529                         if [ ${now_ms} -ge ${charts_next_update[$chart]} ]
530                         then
531                                 last_ms=${charts_last_update[$chart]}
532                                 dt=$(( (now_ms - last_ms) ))
533                                 # echo >&2 "DEBUG: chart: $chart last: ${charts_last_update[$chart]}, next: ${charts_next_update[$chart]}, now: ${now_ms}, dt: ${dt}"
534
535                                 charts_last_update[$chart]=${now_ms}
536
537                                 while [ ${charts_next_update[$chart]} -lt ${now_ms} ]
538                                 do
539                                         charts_next_update[$chart]=$(( charts_next_update[$chart] + (charts_update_every[$chart] * 1000) ))
540                                 done
541
542                                 # the first call should not give a duration
543                                 # so that netdata calibrates to current time
544                                 dt=$(( dt * 1000 ))
545                                 charts_run_counter[$chart]=$(( charts_run_counter[$chart] + 1 ))
546                                 if [ ${charts_run_counter[$chart]} -eq 1 ]
547                                         then
548                                         dt=
549                                 fi
550
551                                 exec_start_ms=$now_ms
552                                 $chart$charts_update $dt
553                                 ret=$?
554                                 
555                                 # return the current time in ms in $now_ms
556                                 current_time_ms; exec_end_ms=$now_ms
557
558                                 echo "BEGIN netdata.plugin_chartsd_$chart $dt"
559                                 if [ $ret -eq 0 ]
560                                 then
561                                         echo "SET run_time = $(( exec_end_ms - exec_start_ms ))"
562                                         next_charts+=($chart)
563                                 else
564                                         echo "SET run_time = $(( (exec_end_ms - exec_start_ms) * -1 ))"
565                                         echo >&2 "$PROGRAM_NAME: chart '$chart' update() function reports failure. Disabling it."
566                                 fi
567                                 echo "END"
568                         else
569                                 next_charts+=($chart)
570                         fi
571                 done
572
573                 if [ "$pause_method" = "suspend" ]
574                 then
575                         echo "STOPPING_WAKE_ME_UP_PLEASE"
576                         suspend || ( echo >&2 "$PROGRAM_NAME: suspend returned error $?, falling back to sleep."; loopsleepms $debug_time $update_every $time_divisor)
577                 else
578                         # wait the time you are required to
579                         #loopsleepms $debug_time $update_every $time_divisor
580                         if [ ${LOOPSLEEPMS_HIGHRES} -eq 1 ]
581                                 then
582                                 sleep 0.2
583                         else
584                                 sleep 1
585                         fi
586                 fi
587
588                 test ${now_ms} -ge ${exit_at} && exit 0
589         done
590 }
591
592 global_update