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