]> arthur.barton.de Git - netdata.git/blob - plugins.d/charts.d.plugin
normalized all charts.d plugins
[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=1800
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 # -----------------------------------------------------------------------------
99 # parse parameters
100
101 debug=0
102 check=0
103 chart_only=
104 while [ ! -z "$1" ]
105 do
106         if [ "$1" = "check" ]
107         then
108                 check=1
109                 shift
110                 continue
111         fi
112
113         if [ "$1" = "debug" -o "$1" = "all" ]
114         then
115                 debug=1
116                 shift
117                 continue
118         fi
119
120         if [ -f "$chartsd/$1.chart.sh" ]
121         then
122                 debug=1
123                 chart_only="$( echo $1.chart.sh | sed "s/\.chart\.sh$//g" )"
124                 shift
125                 continue
126         fi
127
128         if [ -f "$chartsd/$1" ]
129         then
130                 debug=1
131                 chart_only="$( echo $1 | sed "s/\.chart\.sh$//g" )"
132                 shift
133                 continue
134         fi
135
136         # number check
137         n="$1"
138         x=$(( n ))
139         if [ "$x" = "$n" ]
140         then
141                 shift
142                 update_every=$x
143                 [ $update_every -lt $minimum_update_frequency ] && update_every=$minimum_update_frequency
144                 continue
145         fi
146
147         echo >&2 "Cannot understand parameter $1. Aborting."
148         echo "DISABLE"
149         exit 1
150 done
151
152
153 # -----------------------------------------------------------------------------
154 # load my configuration
155
156 if [ -f "$myconfig" ]
157         then
158         . "$myconfig"
159         if [ $? -ne 0 ]
160         then
161                 echo >&2 "$PROGRAM_NAME: cannot load $myconfig"
162                 echo "DISABLE"
163                 exit 1
164         fi
165         time_divisor=$((time_divisor))
166         [ $time_divisor -lt 10 ] && time_divisor=10
167         [ $time_divisor -gt 100 ] && time_divisor=100
168 else
169         echo >&2 "$PROGRAM_NAME: configuration file '$myconfig' not found. Using defaults."
170 fi
171
172 if [ "$pause_method" = "suspend" ]
173 then
174         # enable bash job control
175         # this is required for suspend to work
176         set -m
177 fi
178
179 # we check for the timeout command, after we load our
180 # configuration, so that the user may overwrite the
181 # timeout command we use, providing a function that
182 # can emulate the timeout command we need:
183 # > timeout SECONDS command ...
184 if [ $check_for_timeout -eq 1 ]
185         then
186         require_cmd timeout || exit 1
187 fi
188
189 # -----------------------------------------------------------------------------
190 # internal checks
191
192 # netdata passes the requested update frequency as the first argument
193 update_every=$(( update_every + 1 - 1)) # makes sure it is a number
194 test $update_every -eq 0 && update_every=1 # if it is zero, make it 1
195
196 # check the charts.d directory
197 if [ ! -d "$chartsd" ]
198         then
199         echo >&2 "$PROGRAM_NAME: cannot find charts directory '$chartsd'"
200         echo "DISABLE"
201 fi
202
203
204 # -----------------------------------------------------------------------------
205 # loop control
206
207 # default sleep function
208 loopsleepms() {
209         [ "$1" = "tellwork" ] && shift
210         sleep $1
211 }
212
213 now_ms=
214 current_time_ms() {
215         now_ms="$(date +'%s')000"
216 }
217
218 # if found and included, this file overwrites loopsleepms()
219 # and current_time_ms() with a high resolution timer function
220 # for precise looping.
221 . "$pluginsd/loopsleepms.sh.inc"
222
223
224 # -----------------------------------------------------------------------------
225 # library functions
226
227 fixid() {
228         echo "$*" |\
229                 tr -c "[A-Z][a-z][0-9]" "_" |\
230                 sed -e "s|^_\+||g" -e "s|_\+$||g" -e "s|_\+|_|g" |\
231                 tr "[A-Z]" "[a-z]"
232 }
233
234
235 # -----------------------------------------------------------------------------
236 # charts check functions
237
238 all_charts() {
239         cd "$chartsd"
240         [ $? -ne 0 ] && echo >&2 "$PROGRAM_NAME: Cannot cd to $chartsd" && return 1
241
242         ls *.chart.sh | sed "s/\.chart\.sh$//g"
243 }
244
245 all_enabled_charts() {
246         local charts=
247
248         # find all enabled charts
249
250         for chart in $( all_charts )
251         do
252                 eval "enabled=\$$chart"
253                 if [ "$enabled" = "no" ]
254                 then
255                         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)."
256                 else
257                         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' is enabled."
258                         local charts="$charts $chart"
259                 fi
260         done
261
262         local charts2=
263         for chart in $charts
264         do
265                 # check the enabled charts
266                 local check="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_check()" )"
267                 if [ -z "$check" ]
268                 then
269                         echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_check() function. Disabling it."
270                         continue
271                 fi
272
273                 local create="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_create()" )"
274                 if [ -z "$create" ]
275                 then
276                         echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_create() function. Disabling it."
277                         continue
278                 fi
279
280                 local update="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_update()" )"
281                 if [ -z "$update" ]
282                 then
283                         echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_update() function. Disabling it."
284                         continue
285                 fi
286
287                 # check its config
288                 #if [ -f "$confd/$chart.conf" ]
289                 #then
290                 #       if [ ! -z "$( cat "$confd/$chart.conf" | sed "s/^ \+//g" | grep -v "^$" | grep -v "^#" | grep -v "^$chart$charts_undescore" )" ]
291                 #       then
292                 #               echo >&2 "$PROGRAM_NAME: chart's $chart config $confd/$chart.conf should only have lines starting with $chart$charts_undescore . Disabling it."
293                 #               continue
294                 #       fi
295                 #fi
296
297                 #if [ $dryrunner -eq 1 ]
298                 #       then
299                 #       "$pluginsd/charts.d.dryrun-helper.sh" "$chart" "$chartsd/$chart.chart.sh" "$confd/$chart.conf" >/dev/null
300                 #       if [ $? -ne 0 ]
301                 #       then
302                 #               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."
303                 #               continue
304                 #       fi
305                 #fi
306
307                 local charts2="$charts2 $chart"
308         done
309
310         echo $charts2
311         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: enabled charts: $charts2"
312 }
313
314
315 # -----------------------------------------------------------------------------
316 # load the charts
317
318 suffix_update_every="_update_every"
319 active_charts=
320 for chart in $( all_enabled_charts )
321 do
322         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart: '$chartsd/$chart.chart.sh'"
323         . "$chartsd/$chart.chart.sh"
324
325         if [ -f "$confd/$chart.conf" ]
326         then
327                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart options: '$confd/$chart.conf'"
328                 . "$confd/$chart.conf"
329         else
330                 echo >&2 "$PROGRAM_NAME: $chart: configuration file '$confd/$chart.conf' not found. Using defaults."
331         fi
332
333         eval "dt=\$$chart$suffix_update_every"
334         dt=$(( dt + 1 - 1 )) # make sure it is a number
335         if [ $dt -lt $update_every ]
336         then
337                 eval "$chart$suffix_update_every=$update_every"
338         fi
339
340         $chart$charts_check
341         if [ $? -eq 0 ]
342         then
343                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' activated"
344                 active_charts="$active_charts $chart"
345         else
346                 echo >&2 "$PROGRAM_NAME: chart '$chart' check() function reports failure."
347         fi
348 done
349 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts"
350
351
352 # -----------------------------------------------------------------------------
353 # check overwrites
354
355 # enable work time reporting
356 debug_time=
357 test $debug -eq 1 && debug_time=tellwork
358
359 # if we only need a specific chart, remove all the others
360 if [ ! -z "${chart_only}" ]
361 then
362         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: requested to run only for: '${chart_only}'"
363         check_charts=
364         for chart in $active_charts
365         do
366                 if [ "$chart" = "$chart_only" ]
367                 then
368                         check_charts="$chart"
369                         break
370                 fi
371         done
372         active_charts="$check_charts"
373 fi
374 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts"
375
376 # stop if we just need a pre-check
377 if [ $check -eq 1 ]
378 then
379         echo >&2 "CHECK RESULT"
380         echo >&2 "Will run the charts: $active_charts"
381         exit 0
382 fi
383
384 # -----------------------------------------------------------------------------
385 # create temp dir
386
387 TMP_DIR=
388 chartsd_cleanup() {
389         cd /tmp
390         if [ ! -z "$TMP_DIR" -a -d "$TMP_DIR" ]
391         then
392                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: cleaning up temporary directory $TMP_DIR ..."
393                 rm -rf "$TMP_DIR"
394         fi
395         exit 0
396 }
397 trap chartsd_cleanup EXIT
398 trap chartsd_cleanup SIGHUP
399 trap chartsd_cleanup INT
400
401 if [ $UID = "0" ]
402 then
403         TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
404 else
405         TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
406 fi
407
408 cd "$TMP_DIR" || exit 1
409
410 # -----------------------------------------------------------------------------
411 # create charts
412
413 run_charts=
414 for chart in $active_charts
415 do
416         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: Calling '$chart$charts_create()'..."
417         $chart$charts_create
418         if [ $? -eq 0 ]
419         then
420                 run_charts="$run_charts $chart"
421                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' has initialized."
422         else
423                 echo >&2 "$PROGRAM_NAME: chart '$chart' function '$chart$charts_create()' reports failure."
424         fi
425 done
426 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: run_charts='$run_charts'"
427
428
429 # -----------------------------------------------------------------------------
430 # update dimensions
431
432 if [ -z "$run_charts" ]
433         then
434         echo >&2 "$PROGRAM_NAME: No charts to collect data from."
435         echo "DISABLE"
436         exit 1
437 fi
438
439 declare -A charts_last_update=() charts_min_dt=()
440 global_update() {
441         local exit_after=$((restart_timeout / update_every)) \
442                 c=0 dt ret exec_start_ms exec_end_ms \
443                 chart now_charts=() next_charts=($run_charts)
444
445         # return the current time in ms in $now_ms
446         current_time_ms
447
448         for chart in $run_charts
449         do
450                 eval "charts_min_dt[$chart]=\$$chart$suffix_update_every"
451                 test -z "${charts_min_dt[$chart]}" && charts_min_dt[$charts]=$update_every
452                 charts_last_update[$chart]=$((now_ms - (charts_min_dt[$chart] * 1000) ))
453
454                 echo "CHART netdata.plugin_$chart '' 'Execution time for $chart plugin' 'milliseconds / run' netdata netdata area 90000 ${charts_min_dt[$chart]}"
455                 echo "DIMENSION run_time 'run time' absolute 1 1"
456         done
457
458         # the main loop
459         while [ 1 ]
460         do
461                 c=$((c + 1))
462                 now_charts=("${next_charts[@]}")
463                 next_charts=()
464
465                 for chart in "${now_charts[@]}"
466                 do
467                         # return the current time in ms in $now_ms
468                         current_time_ms
469                         dt=$(( (now_ms - charts_last_update[$chart]) * 1000 ))
470                         if [ $dt -ge $(( charts_min_dt[$chart] * 1000000 )) ]
471                         then
472                                 charts_last_update[$chart]=$now_ms
473
474                                 # the first call should not give a duration
475                                 # so that netdata calibrates to current time
476                                 test $c -eq 1 && dt=
477
478                                 exec_start_ms=$now_ms
479                                 $chart$charts_update $dt
480                                 ret=$?
481                                 current_time_ms; exec_end_ms=$now_ms
482
483                                 echo "BEGIN netdata.plugin_$chart $dt"
484                                 if [ $ret -eq 0 ]
485                                 then
486                                         echo "SET run_time = $(( exec_end_ms - exec_start_ms ))"
487                                         next_charts+=($chart)
488                                 else
489                                         echo "SET run_time = $(( (exec_end_ms - exec_start_ms) * -1 ))"
490                                         echo >&2 "$PROGRAM_NAME: chart '$chart' update() function reports failure. Disabling it."
491                                 fi
492                                 echo "END"
493                         else
494                                 next_charts+=($chart)
495                         fi
496                 done
497
498                 if [ "$pause_method" = "suspend" ]
499                 then
500                         echo "STOPPING_WAKE_ME_UP_PLEASE"
501                         suspend || ( echo >&2 "$PROGRAM_NAME: suspend returned error $?, falling back to sleep."; loopsleepms $debug_time $update_every $time_divisor)
502                 else
503                         # wait the time you are required to
504                         loopsleepms $debug_time $update_every $time_divisor
505                 fi
506
507                 test $c -gt $exit_after && exit 0
508         done
509 }
510
511 global_update