Home | History | Annotate | Line # | Download | only in contrib
      1 #!/bin/sh
      2 #
      3 # plugin for munin to monitor usage of NSD.
      4 #
      5 # (C) 2008 W.C.A. Wijngaards.  BSD Licensed.
      6 #
      7 # To install; compile with --enable-bind8-stats (enabled by default)
      8 #	and enable nsd-control in nsd.conf with the line
      9 #	remote-control:	control-enable: yes
     10 # Run the command nsd-control-setup as root to generate the key files.
     11 #
     12 # Environment variables for this script
     13 #	statefile	- where to put temporary statefile.
     14 #	nsd_conf	- where the nsd.conf file is located.
     15 #	nsd_control	- where to find nsd-control executable.
     16 #	nsd_checkconf	- where to find nsd-checkconf executable.
     17 #
     18 # You can set them in your munin/plugin-conf.d/plugins.conf file
     19 # with:
     20 # [nsd_munin*]
     21 # user root
     22 # env.statefile /usr/local/var/munin/plugin-state/nsd-state
     23 # env.nsd_conf /usr/local/etc/nsd.conf
     24 # env.nsd_control /usr/local/sbin/nsd-control
     25 # env.nsd_checkconf /usr/local/sbin/nsd-checkconf
     26 #
     27 # This plugin can create different graphs depending on what name
     28 # you link it as (with ln -s) into the plugins directory
     29 # You can link it multiple times.
     30 # If you are only a casual user, the _hits and _by_type are most interesting,
     31 # possibly followed by _by_rcode.
     32 #
     33 #	nsd_munin_hits		- base volume, transport type, failures 
     34 #	nsd_munin_memory	- memory usage
     35 #	nsd_munin_by_type	- incoming queries by type
     36 #	nsd_munin_by_class	- incoming queries by class
     37 #	nsd_munin_by_opcode	- incoming queries by opcode
     38 #	nsd_munin_by_rcode	- answers by rcode
     39 #	nsd_munin_zones		- number of zones
     40 #
     41 # Magic markers - optional - used by installation scripts and
     42 # munin-config:
     43 #
     44 #%# family=contrib
     45 #%# capabilities=autoconf suggest
     46 
     47 # POD documentation
     48 : <<=cut
     49 =head1 NAME
     50 
     51 nsd_munin_ - Munin plugin to monitor the NSD server.
     52 
     53 =head1 APPLICABLE SYSTEMS
     54 
     55 System with NSD daemon.
     56 
     57 =head1 CONFIGURATION
     58 
     59   [nsd_munin*]
     60   user root
     61   env.statefile /usr/local/var/munin/plugin-state/nsd-state
     62   env.nsd_conf /usr/local/etc/nsd.conf
     63   env.nsd_control /usr/local/sbin/nsd-control
     64   env.nsd_checkconf /usr/local/sbin/nsd-checkconf
     65 
     66 Use the .env settings to override the defaults.
     67 
     68 =head1 USAGE
     69 
     70 Can be used to present different graphs. Use ln -s for that name in
     71 the plugins directory to enable the graph.
     72 nsd_munin_hits		- base volume, transport type, failures 
     73 nsd_munin_memory	- memory usage
     74 nsd_munin_by_type	- incoming queries by type
     75 nsd_munin_by_class	- incoming queries by class
     76 nsd_munin_by_opcode	- incoming queries by opcode
     77 nsd_munin_by_rcode	- answers by rcode
     78 nsd_munin_zones		- number of zones
     79 
     80 =head1 AUTHOR
     81 
     82 Copyright 2008 W.C.A. Wijngaards
     83 
     84 =head1 LICENSE
     85 
     86 BSD
     87 
     88 =cut
     89 
     90 state=${statefile:-/usr/local/var/munin/plugin-state/nsd-state}
     91 conf=${nsd_conf:-/usr/local/etc/nsd.conf}
     92 ctrl=${nsd_control:-/usr/local/sbin/nsd-control}
     93 chkconf=${nsd_checkconf:-/usr/local/sbin/nsd-checkconf}
     94 lock=$state.lock
     95 
     96 # number of seconds between polling attempts.
     97 # makes the statefile hang around for at least this many seconds,
     98 # so that multiple links of this script can share the results.
     99 lee=55
    100 
    101 # to keep things within 19 characters
    102 ABBREV="-e s/num/n/ -e s/type/t/ -e s/opcode/o/ -e s/rcode/r/ -e s/class/c/"
    103 
    104 # get value from $1 into return variable $value
    105 get_value ( ) {
    106 	value="`grep '^'$1'=' $state | sed -e 's/^.*=//'`"
    107 	if test "$value"x = ""x; then
    108 		value="0"
    109 	fi
    110 }
    111 
    112 # download the state from NSD.
    113 get_state ( ) {
    114 	# obtain lock for fetching the state
    115 	# because there is a race condition in fetching and writing to file
    116 
    117 	# see if the lock is stale, if so, take it 
    118 	if test -f $lock ; then
    119 		pid="`cat $lock 2>&1`"
    120 		kill -0 "$pid" >/dev/null 2>&1
    121 		if test $? -ne 0 -a "$pid" != $$ ; then
    122 			echo $$ >$lock
    123 		fi
    124 	fi
    125 
    126 	i=0
    127 	while test ! -f $lock || test "`cat $lock 2>&1`" != $$; do
    128 		while test -f $lock; do
    129 			# wait
    130 			i=`expr $i + 1`
    131 			if test $i -gt 1000; then
    132 				sleep 1;
    133 			fi
    134 			if test $i -gt 1500; then
    135 				echo "error locking $lock" "=" `cat $lock`
    136 				rm -f $lock
    137 				exit 1
    138 			fi
    139 		done
    140 		# try to get it
    141 		echo $$ >$lock
    142 	done
    143 	# do not refetch if the file exists and only LEE seconds old
    144 	if test -f $state; then
    145 		now=`date +%s`
    146 		get_value "timestamp"
    147 		if test $now -lt `expr $value + $lee`; then
    148 			rm -f $lock
    149 			return
    150 		fi
    151 	fi
    152 	$ctrl -c $conf stats > $state
    153 	if test $? -ne 0; then
    154 		echo "error retrieving data from the server"
    155 		rm -f $lock
    156 		exit 1
    157 	fi
    158 	echo "timestamp="`date +%s` >> $state
    159 	rm -f $lock
    160 }
    161 
    162 if test "$1" = "autoconf" ; then
    163 	if test ! -f $conf; then
    164 		echo no "($conf does not exist)"
    165 		exit 1
    166 	fi
    167 	if test ! -d `dirname $state`; then
    168 		mkdir -p `dirname $state`
    169 		if test ! -d `dirname $state`; then
    170 			echo no "($state directory does not exist)"
    171 			exit 1
    172 		fi
    173 	fi
    174 	echo yes
    175 	exit 0
    176 fi
    177 
    178 if test "$1" = "suggest" ; then
    179 	echo "hits"
    180 	echo "memory"
    181 	echo "by_type"
    182 	echo "by_class"
    183 	echo "by_opcode"
    184 	echo "by_rcode"
    185 	echo "zones"
    186 	exit 0
    187 fi
    188 
    189 # determine my type, by name
    190 id=`echo $0 | sed -e 's/^.*nsd_munin_//'`
    191 if test "$id"x = ""x; then
    192 	# some default to keep people sane.
    193 	id="hits"
    194 fi
    195 
    196 # if $1 exists in statefile, config is echoed with label $2
    197 exist_config ( ) {
    198 	mn=`echo $1 | sed $ABBREV | tr . _`
    199 	if grep '^'$1'=' $state >/dev/null 2>&1; then
    200 		echo "$mn.label $2"
    201 		echo "$mn.min 0"
    202 		echo "$mn.type ABSOLUTE"
    203 	fi
    204 }
    205 
    206 # print label and min 0 for a name $1 in nsd format
    207 p_config ( ) {
    208 	mn=`echo $1 | sed $ABBREV | tr . _`
    209 	echo $mn.label "$2"
    210 	echo $mn.min 0
    211 	echo $mn.type $3
    212 }
    213 
    214 if test "$1" = "config" ; then
    215 	if test ! -f $state; then
    216 		get_state
    217 	fi
    218 	case $id in
    219 	hits)
    220 		echo "graph_title NSD traffic"
    221 		echo "graph_args --base 1000 -l 0"
    222 		echo "graph_vlabel queries / \${graph_period}"
    223 		echo "graph_scale no"
    224 		echo "graph_category DNS"
    225 		for x in server0.queries server1.queries server2.queries \
    226 			server3.queries server4.queries server5.queries \
    227 			server6.queries server7.queries server8.queries \
    228 			server9.queries server10.queries server11.queries \
    229 			server12.queries server13.queries server14.queries \
    230 			server15.queries ; do
    231 			exist_config $x "queries handled by `basename $x .queries`"
    232 		done
    233 		p_config "num.queries" "total queries" "ABSOLUTE"
    234 		p_config "num.udp" "UDP ip4 queries" "ABSOLUTE"
    235 		p_config "num.udp6" "UDP ip6 queries" "ABSOLUTE"
    236 		p_config "num.tcp" "TCP ip4 queries" "ABSOLUTE"
    237 		p_config "num.tcp6" "TCP ip6 queries" "ABSOLUTE"
    238 		p_config "num.edns" "queries with EDNS OPT" "ABSOLUTE"
    239 		p_config "num.ednserr" "queries failed EDNS parse" "ABSOLUTE"
    240 		p_config "num.answer_wo_aa" "nonauthor. queries (referrals)" "ABSOLUTE"
    241 		p_config "num.rxerr" "receive failed" "ABSOLUTE"
    242 		p_config "num.txerr" "transmit failed" "ABSOLUTE"
    243 		p_config "num.truncated" "truncated replies with TC" "ABSOLUTE"
    244 		p_config "num.raxfr" "AXFR from allowed client" "ABSOLUTE"
    245 		p_config "num.rixfr" "IXFR from allowed client" "ABSOLUTE"
    246 		p_config "num.dropped" "dropped due to sanity check" "ABSOLUTE"
    247 		echo "graph_info DNS queries."
    248 		;;
    249 	memory)
    250 		echo "graph_title NSD memory usage"
    251 		echo "graph_args --base 1024 -l 0"
    252 		echo "graph_vlabel memory used in bytes"
    253 		echo "graph_category DNS"
    254 		p_config "size.vsz" "Total virtual memory (VSZ)" "GAUGE"
    255 		p_config "size.rss" "Total resident memory (RSS)" "GAUGE"
    256 		p_config "size.db.mem" "data in memory" "GAUGE"
    257 		p_config "size.xfrd.mem" "xfr and notify memory" "GAUGE"
    258 		p_config "size.config.mem" "config memory" "GAUGE"
    259 		p_config "size.db.disk" "mmap of nsd.db file" "GAUGE"
    260 		p_config "size.config.disk" "config zonelist on disk" "GAUGE"
    261 		echo "graph_info The memory used by NSD, xfrd and config. Disk size of nsd.db and zonelist."
    262 		;;
    263 	by_type)
    264 		echo "graph_title NSD queries by type"
    265 		echo "graph_args --base 1000 -l 0"
    266 		echo "graph_vlabel queries / \${graph_period}"
    267 		echo "graph_scale no"
    268 		echo "graph_category DNS"
    269 		for x in `grep "^num.type" $state`; do
    270 			nm=`echo $x | sed -e 's/=.*$//'`
    271 			tp=`echo $nm | sed -e s/num.type.//`
    272 			p_config "$nm" "$tp" "ABSOLUTE"
    273 		done
    274 		echo "graph_info queries by DNS RR type queried for"
    275 		;;
    276 	by_class)
    277 		echo "graph_title NSD queries by class"
    278 		echo "graph_args --base 1000 -l 0"
    279 		echo "graph_vlabel queries / \${graph_period}"
    280 		echo "graph_scale no"
    281 		echo "graph_category DNS"
    282 		for x in `grep "^num.class" $state`; do
    283 			nm=`echo $x | sed -e 's/=.*$//'`
    284 			tp=`echo $nm | sed -e s/num.class.//`
    285 			p_config "$nm" "$tp" "ABSOLUTE"
    286 		done
    287 		echo "graph_info queries by DNS RR class queried for."
    288 		;;
    289 	by_opcode)
    290 		echo "graph_title NSD queries by opcode"
    291 		echo "graph_args --base 1000 -l 0"
    292 		echo "graph_vlabel queries / \${graph_period}"
    293 		echo "graph_scale no"
    294 		echo "graph_category DNS"
    295 		for x in `grep "^num.opcode" $state`; do
    296 			nm=`echo $x | sed -e 's/=.*$//'`
    297 			tp=`echo $nm | sed -e s/num.opcode.//`
    298 			p_config "$nm" "$tp" "ABSOLUTE"
    299 		done
    300 		echo "graph_info queries by opcode in the query packet."
    301 		;;
    302 	by_rcode)
    303 		echo "graph_title NSD answers by return code"
    304 		echo "graph_args --base 1000 -l 0"
    305 		echo "graph_vlabel answer packets / \${graph_period}"
    306 		echo "graph_scale no"
    307 		echo "graph_category DNS"
    308 		for x in `grep "^num.rcode" $state`; do
    309 			nm=`echo $x | sed -e 's/=.*$//'`
    310 			tp=`echo $nm | sed -e s/num.rcode.//`
    311 			p_config "$nm" "$tp" "ABSOLUTE"
    312 		done
    313 		echo "graph_info answers split out by return value."
    314 		;;
    315 	zones)
    316 		echo "graph_title NSD number of zones"
    317 		echo "graph_args --base 1000 -l 0"
    318 		echo "graph_vlabel zone count"
    319 		echo "graph_category DNS"
    320 		p_config "zone.total" "total zones" "GAUGE"
    321 		p_config "zone.primary" "primary zones" "GAUGE"
    322 		p_config "zone.secondary" "secondary zones" "GAUGE"
    323 		echo "graph_info number of zones served by NSD."
    324 		;;
    325 	esac
    326 
    327 	exit 0
    328 fi
    329 
    330 # do the stats itself
    331 get_state
    332 
    333 # get the time elapsed
    334 get_value "time.elapsed"
    335 if test $value = 0 || test $value = "0.000000"; then
    336 	echo "error: time elapsed 0 or could not retrieve data"
    337 	exit 1
    338 fi
    339 elapsed="$value"
    340 
    341 # print value for $1
    342 print_value ( ) {
    343 	mn=`echo $1 | sed $ABBREV | tr . _`
    344 	get_value $1
    345 	echo "$mn.value" $value
    346 }
    347 
    348 # print value if line already found in $2
    349 print_value_line ( ) {
    350 	mn=`echo $1 | sed $ABBREV | tr . _`
    351 	value="`echo $2 | sed -e 's/^.*=//'`"
    352 	echo "$mn.value" $value
    353 }
    354 
    355 
    356 case $id in
    357 hits)
    358 	for x in server0.queries server1.queries server2.queries \
    359 		server3.queries server4.queries server5.queries \
    360 		server6.queries server7.queries server8.queries \
    361 		server9.queries server10.queries server11.queries \
    362 		server12.queries server13.queries server14.queries \
    363 		server15.queries \
    364 		num.queries num.udp num.udp6 num.tcp num.tcp6 \
    365 		num.edns num.ednserr num.answer_wo_aa num.rxerr num.txerr \
    366 		num.truncated num.raxfr num.rixfr num.dropped ; do
    367 		if grep "^"$x"=" $state >/dev/null 2>&1; then
    368 			print_value $x
    369 		fi
    370 	done
    371 	;;
    372 memory)
    373 	# get the total memory for NSD
    374 	serverpid=`$ctrl -c $conf serverpid 2>&1`
    375 	# small race condition, if reload happens between previous and next
    376 	# lines, if so, detect by checking if we have a number as output.
    377 	rssval=`ps -p $serverpid -o rss= 2>&1`
    378 	vszval=`ps -p $serverpid -o vsz= 2>&1`
    379 	if test "`expr $rssval + 1 - 1 2>&1`" -eq "$rssval" >/dev/null 2>&1; then
    380 		rssval=`expr $rssval \* 1024` 
    381 	else
    382 		rssval=0
    383 	fi
    384 	if test "`expr $vszval + 1 - 1 2>&1`" -eq "$vszval" >/dev/null 2>&1; then
    385 		vszval=`expr $vszval \* 1024` 
    386 	else
    387 		vszval=0
    388 	fi
    389 	echo "size_vsz.value" $vszval
    390 	echo "size_rss.value" $rssval
    391 	for x in size.db.mem size.xfrd.mem size.config.mem \
    392 		size.db.disk size.config.disk; do
    393 		print_value $x
    394 	done
    395 	;;
    396 by_type)
    397 	for x in `grep "^num.type" $state`; do
    398 		nm=`echo $x | sed -e 's/=.*$//'`
    399 		print_value_line $nm $x
    400 	done
    401 	;;
    402 by_class)
    403 	for x in `grep "^num.class" $state`; do
    404 		nm=`echo $x | sed -e 's/=.*$//'`
    405 		print_value_line $nm $x
    406 	done
    407 	;;
    408 by_opcode)
    409 	for x in `grep "^num.opcode" $state`; do
    410 		nm=`echo $x | sed -e 's/=.*$//'`
    411 		print_value_line $nm $x
    412 	done
    413 	;;
    414 by_rcode)
    415 	for x in `grep "^num.rcode" $state`; do
    416 		nm=`echo $x | sed -e 's/=.*$//'`
    417 		print_value_line $nm $x
    418 	done
    419 	;;
    420 zones)
    421 	get_value "zone.primary"
    422 	nummas="$value"
    423 	get_value "zone.secondary"
    424 	numsla="$value"
    425 	echo "zone_total.value" `expr $nummas + $numsla`
    426 	echo "zone_primary.value" "$nummas"
    427 	echo "zone_secondary.value" "$numsla"
    428 esac
    429