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