]> arthur.barton.de Git - netdata.git/blob - plugins.d/charts.d.plugin
minor fixes; mysql now accepts multiple servers
[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 mktemp || exit 1
44
45 # -----------------------------------------------------------------------------
46 # insternal defaults
47 # netdata exposes a few environment variables for us
48
49 pause_method="sleep" # use either "suspend" or "sleep"
50                      # DO NOT USE SUSPEND - LINUX WILL SUSPEND NETDATA TOO
51                      # THE WHOLE PROCESS GROUP - NOT JUST THE SHELL
52
53 pluginsd="${NETDATA_PLUGINS_DIR}"
54 [ -z "$pluginsd" ] && pluginsd="$( dirname $PROGRAM_FILE )"
55
56 confd="${NETDATA_CONFIG_DIR-/etc/netdata}"
57 chartsd="$pluginsd/../charts.d"
58
59 myconfig="$confd/$PROGRAM_NAME.conf"
60
61 minimum_update_frequency="${NETDATA_UPDATE_EVERY-1}"
62 update_every=${minimum_update_frequency}        # this will be overwritten by the command line
63
64 # work around for non BASH shells
65 charts_create="_create"
66 charts_update="_update"
67 charts_check="_check"
68 charts_undescore="_"
69
70 # when making iterations, charts.d can loop more frequently
71 # to prevent plugins missing iterations.
72 # this is a percentage relative to update_every to align its
73 # iterations.
74 # The minimum is 10%, the maximum 100%.
75 # So, if update_every is 1 second and time_divisor is 50,
76 # charts.d will iterate every 500ms.
77 # Charts will be called to collect data only if the time
78 # passes since the last time the collected data is equal or
79 # above their update_every.
80 time_divisor=50
81
82 # number of seconds to run without restart
83 # after this time, charts.d.plugin will exit
84 # netdata will restart it
85 restart_timeout=1800
86
87 # -----------------------------------------------------------------------------
88 # parse parameters
89
90 debug=0
91 check=0
92 chart_only=
93 while [ ! -z "$1" ]
94 do
95         if [ "$1" = "check" ]
96         then
97                 check=1
98                 shift
99                 continue
100         fi
101
102         if [ "$1" = "debug" -o "$1" = "all" ]
103         then
104                 debug=1
105                 shift
106                 continue
107         fi
108
109         if [ -f "$chartsd/$1.chart.sh" ]
110         then
111                 debug=1
112                 chart_only="$( echo $1.chart.sh | sed "s/\.chart\.sh$//g" )"
113                 shift
114                 continue
115         fi
116
117         if [ -f "$chartsd/$1" ]
118         then
119                 debug=1
120                 chart_only="$( echo $1 | sed "s/\.chart\.sh$//g" )"
121                 shift
122                 continue
123         fi
124
125         # number check
126         n="$1"
127         x=$(( n ))
128         if [ "$x" = "$n" ]
129         then
130                 shift
131                 update_every=$x
132                 [ $update_every -lt $minimum_update_frequency ] && update_every=$minimum_update_frequency
133                 continue
134         fi
135
136         echo >&2 "Cannot understand parameter $1. Aborting."
137         echo "DISABLE"
138         exit 1
139 done
140
141
142 # -----------------------------------------------------------------------------
143 # load my configuration
144
145 if [ -f "$myconfig" ]
146         then
147         . "$myconfig"
148         if [ $? -ne 0 ]
149         then
150                 echo >&2 "$PROGRAM_NAME: cannot load $myconfig"
151                 echo "DISABLE"
152                 exit 1
153         fi
154         time_divisor=$((time_divisor))
155         [ $time_divisor -lt 10 ] && time_divisor=10
156         [ $time_divisor -gt 100 ] && time_divisor=100
157 fi
158
159 if [ "$pause_method" = "suspend" ]
160 then
161         # enable bash job control
162         # this is required for suspend to work
163         set -m
164 fi
165
166 # -----------------------------------------------------------------------------
167 # internal checks
168
169 # netdata passes the requested update frequency as the first argument
170 update_every=$(( update_every + 1 - 1)) # makes sure it is a number
171 test $update_every -eq 0 && update_every=1 # if it is zero, make it 1
172
173 # check the charts.d directory
174 if [ ! -d "$chartsd" ]
175         then
176         echo >&2 "$PROGRAM_NAME: cannot find charts directory '$chartsd'"
177         echo "DISABLE"
178 fi
179
180
181 # -----------------------------------------------------------------------------
182 # loop control
183
184 # default sleep function
185 loopsleepms() {
186         [ "$1" = "tellwork" ] && shift
187         sleep $1
188 }
189
190 now_ms=
191 current_time_ms() {
192         now_ms="$(date +'%s')000"
193 }
194
195 # if found and included, this file overwrites loopsleepms()
196 # and current_time_ms() with a high resolution timer function
197 # for precise looping.
198 . "$pluginsd/loopsleepms.sh.inc"
199
200
201 # -----------------------------------------------------------------------------
202 # library functions
203
204 fixid() {
205         echo "$*" |\
206                 tr -c "[A-Z][a-z][0-9]" "_" |\
207                 sed -e "s|^_\+||g" -e "s|_\+$||g" -e "s|_\+|_|g" |\
208                 tr "[A-Z]" "[a-z]"
209 }
210
211
212 # -----------------------------------------------------------------------------
213 # charts check functions
214
215 all_charts() {
216         cd "$chartsd"
217         [ $? -ne 0 ] && echo >&2 "$PROGRAM_NAME: Cannot cd to $chartsd" && return 1
218
219         ls *.chart.sh | sed "s/\.chart\.sh$//g"
220 }
221
222 all_enabled_charts() {
223         local charts=
224
225         # find all enabled charts
226
227         for chart in $( all_charts )
228         do
229                 eval "enabled=\$$chart"
230                 if [ "$enabled" = "yes" ]
231                 then
232                         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' is enabled."
233                         local charts="$charts $chart"
234                 else
235                         echo >&2 "$PROGRAM_NAME: '$chart' is NOT enabled. Add a line with $chart=yes in $myconfig to enable it."
236                 fi
237         done
238
239         local charts2=
240         for chart in $charts
241         do
242                 # check the enabled charts
243                 local check="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_check()" )"
244                 if [ -z "$check" ]
245                 then
246                         echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_check() function. Disabling it."
247                         continue
248                 fi
249
250                 local create="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_create()" )"
251                 if [ -z "$create" ]
252                 then
253                         echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_create() function. Disabling it."
254                         continue
255                 fi
256
257                 local update="$( cat "$chartsd/$chart.chart.sh" | sed "s/^ \+//g" | grep "^$chart$charts_update()" )"
258                 if [ -z "$update" ]
259                 then
260                         echo >&2 "$PROGRAM_NAME: chart '$chart' does not seem to have a $chart$charts_update() function. Disabling it."
261                         continue
262                 fi
263
264                 # check its config
265                 #if [ -f "$confd/$chart.conf" ]
266                 #then
267                 #       if [ ! -z "$( cat "$confd/$chart.conf" | sed "s/^ \+//g" | grep -v "^$" | grep -v "^#" | grep -v "^$chart$charts_undescore" )" ]
268                 #       then
269                 #               echo >&2 "$PROGRAM_NAME: chart's $chart config $confd/$chart.conf should only have lines starting with $chart$charts_undescore . Disabling it."
270                 #               continue
271                 #       fi
272                 #fi
273
274                 "$pluginsd/charts.d.dryrun-helper.sh" "$chart" "$chartsd/$chart.chart.sh" "$confd/$chart.conf" >/dev/null
275                 if [ $? -ne 0 ]
276                 then
277                         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."
278                         continue
279                 fi
280
281                 local charts2="$charts2 $chart"
282         done
283
284         echo $charts2
285         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: enabled charts: $charts2"
286 }
287
288
289 # -----------------------------------------------------------------------------
290 # load the charts
291
292 suffix_update_every="_update_every"
293 active_charts=
294 for chart in $( all_enabled_charts )
295 do
296         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart: '$chartsd/$chart.chart.sh'"
297         . "$chartsd/$chart.chart.sh"
298
299         if [ -f "$confd/$chart.conf" ]
300         then
301                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: loading chart options: '$confd/$chart.conf'"
302                 . "$confd/$chart.conf"
303         fi
304
305         eval "dt=\$$chart$suffix_update_every"
306         dt=$(( dt + 1 - 1 )) # make sure it is a number
307         if [ $dt -lt $update_every ]
308         then
309                 eval "$chart$suffix_update_every=$update_every"
310         fi
311
312         $chart$charts_check
313         if [ $? -eq 0 ]
314         then
315                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' activated"
316                 active_charts="$active_charts $chart"
317         else
318                 echo >&2 "$PROGRAM_NAME: chart '$chart' check() function reports failure."
319         fi
320 done
321 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts"
322
323
324 # -----------------------------------------------------------------------------
325 # check overwrites
326
327 # enable work time reporting
328 debug_time=
329 test $debug -eq 1 && debug_time=tellwork
330
331 # if we only need a specific chart, remove all the others
332 if [ ! -z "${chart_only}" ]
333 then
334         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: requested to run only for: '${chart_only}'"
335         check_charts=
336         for chart in $active_charts
337         do
338                 if [ "$chart" = "$chart_only" ]
339                 then
340                         check_charts="$chart"
341                         break
342                 fi
343         done
344         active_charts="$check_charts"
345 fi
346 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: activated charts: $active_charts"
347
348 # stop if we just need a pre-check
349 if [ $check -eq 1 ]
350 then
351         echo >&2 "CHECK RESULT"
352         echo >&2 "Will run the charts: $active_charts"
353         exit 0
354 fi
355
356 # -----------------------------------------------------------------------------
357 # create temp dir
358
359 TMP_DIR=
360 chartsd_cleanup() {
361         cd /tmp
362         if [ ! -z "$TMP_DIR" -a -d "$TMP_DIR" ]
363         then
364                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: cleaning up temporary directory $TMP_DIR ..."
365                 rm -rf "$TMP_DIR"
366         fi
367         exit 0
368 }
369 trap chartsd_cleanup EXIT
370 trap chartsd_cleanup SIGHUP
371 trap chartsd_cleanup INT
372
373 if [ $UID = "0" ]
374 then
375         TMP_DIR="$( mktemp -d /var/run/netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
376 else
377         TMP_DIR="$( mktemp -d /tmp/.netdata-${PROGRAM_NAME}-XXXXXXXXXX )"
378 fi
379
380 cd "$TMP_DIR" || exit 1
381
382 # -----------------------------------------------------------------------------
383 # create charts
384
385 run_charts=
386 for chart in $active_charts
387 do
388         [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: Calling '$chart$charts_create()'..."
389         $chart$charts_create
390         if [ $? -eq 0 ]
391         then
392                 run_charts="$run_charts $chart"
393                 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: '$chart' has initialized."
394         else
395                 echo >&2 "$PROGRAM_NAME: chart '$chart' function '$chart$charts_create()' reports failure."
396         fi
397 done
398 [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: run_charts='$run_charts'"
399
400
401 # -----------------------------------------------------------------------------
402 # update dimensions
403
404 if [ -z "$run_charts" ]
405         then
406         echo >&2 "$PROGRAM_NAME: No charts to collect data from."
407         echo "DISABLE"
408         exit 1
409 fi
410
411 declare -A charts_last_update=() charts_min_dt=()
412 global_update() {
413         local exit_after=$((restart_timeout / update_every)) \
414                 c=0 dt \
415                 chart now_charts=() next_charts=($run_charts)
416
417         # return the current time in ms in $now_ms
418         current_time_ms
419
420         for chart in $run_charts
421         do
422                 eval "charts_min_dt[$chart]=\$$chart$suffix_update_every"
423                 test -z "${charts_min_dt[$chart]}" && charts_min_dt[$charts]=$update_every
424                 charts_last_update[$chart]=$((now_ms - (charts_min_dt[$chart] * 1000) ))
425         done
426
427         # the main loop
428         while [ 1 ]
429         do
430                 c=$((c + 1))
431                 now_charts=("${next_charts[@]}")
432                 next_charts=()
433
434                 for chart in "${now_charts[@]}"
435                 do
436                         # return the current time in ms in $now_ms
437                         current_time_ms
438
439                         dt=$(( (now_ms - charts_last_update[$chart]) * 1000 ))
440                         if [ $dt -ge $(( charts_min_dt[$chart] * 1000000 )) ]
441                         then
442                                 charts_last_update[$chart]=$now_ms
443
444                                 # the first call should not give a duration
445                                 # so that netdata calibrates to current time
446                                 test $c -eq 1 && dt=
447
448                                 $chart$charts_update $dt
449                                 if [ $? -eq 0 ]
450                                 then
451                                         next_charts+=($chart)
452                                 else
453                                         echo >&2 "$PROGRAM_NAME: chart '$chart' update() function reports failure. Disabling it."
454                                 fi
455                         else
456                                 next_charts+=($chart)
457                         fi
458                 done
459
460                 if [ "$pause_method" = "suspend" ]
461                 then
462                         echo "STOPPING_WAKE_ME_UP_PLEASE"
463                         suspend || ( echo >&2 "$PROGRAM_NAME: suspend returned error $?, falling back to sleep."; loopsleepms $debug_time $update_every $time_divisor)
464                 else
465                         # wait the time you are required to
466                         loopsleepms $debug_time $update_every $time_divisor
467                 fi
468
469                 test $c -gt $exit_after && exit 0
470         done
471 }
472
473 global_update