diff --git a/plugins/other/nsd3 b/plugins/other/nsd3 index 3c435966..4297a222 100755 --- a/plugins/other/nsd3 +++ b/plugins/other/nsd3 @@ -1,28 +1,71 @@ #!/usr/bin/python #%# family=auto -#%# capabilities=suggest autoconf +#%# capabilities=autoconf """ -Munin plugin to monitor NSD3 statistics -J.T.Sage , 2010/06/10 +=head1 NAME -To use: -ln -s path_to_this_script /etc/munin/plugins/nsd + nsd - Munin plugin to monitor the number of queries a running process + of nsd3 has recievied. -Configuration -[nsd] - user nsd - env.statsfile /var/log/nsd.log - env.pidfile /var/run/nsd3/nsd.pid +=head1 APPLICABLE SYSTEMS -Notes: - Note that the plugin has a built in half second pause for the lastest NSD3 - statistics to write. This is done via a SIGUSR1 signal to the process. - If your system is sufficiently loaded to the point that half a second - is not enough time for the file to be written to, please update that - number in the source. It should be more than long enough for most - users. + Linux or *nix system with a logging installtion of NSD v3 installed. + (http://nlnetlabs.nl/projects/nsd/) + +=head1 CONFIGURATION + + The plugin needs access to the nsd logfile and the nsd pid file to + force the running nsd process to write the current statistics. + + Tip: To see if it's already set up correctly, just run this plugin + with the paramater "autoconf". If you get a "yes", everything should + work like a charm already. + + This configuration section shows the defaults of the plugin: + + The stats line is a set of space-seperated values that you wish to + retrieve from NSD. The format is VALUE=Caption. For spaces in a + caption value, replace them with an underscore (_). + + [nsd] + env.statsfile /var/log/nsd.log + env.pidfile /var/run/nsd3/nsd.pid + env.stats "A=A AAAA=AAAA MX=MX PTR=PTR TYPE252=AXFR SNXD=NXDOMAIN RQ=Total_Successful" + + If you need to set a user for the logfile to be readable, and most + importantly, the process to recieve the signal, you may specify it. + For example: + + [nsd] + user nsd + +=head1 INTERPRETATION + + The plugin shows the number of queries that nsd has recieved, + averaged over a period to gain the number of queries per second. + For most servers, these values will be very low. In the event + of a misconfiguration, the plugin will return undefined values. + +=head1 MAGIC MARKERS + + #%# family=auto + #%# capabilities=autoconf + +=head1 VERSION + + v1.0.1 + +=head1 AUTHOR + + J.T.Sage + +=head1 LICENSE + + GPLv2 + +=cut """ import os @@ -30,130 +73,126 @@ import sys import subprocess import time import re +import signal STATS_FILE = os.environ.get('statsfile', '/var/log/nsd.log') PID_FILE = os.environ.get('pidfile', '/var/run/nsd3/nsd.pid') +STATS_STRING = os.environ.get('stats', "A=A AAAA=AAAA MX=MX PTR=PTR TYPE252=AXFR SNXD=NXDOMAIN RQ=Total_Succesful") + +BOTH_LISTS = STATS_STRING.split() def print_config(): - """Generates and prints a munin config for a given chart.""" + """Generates and prints a munin config for a given chart.""" - print "graph_title NSD3 Queries" - print "graph_vlabel qps" - print "graph_category network" - print "graph_info Queries per second" - print "a.label A" - print "a.type DERIVE" - print "a.min 0" - print "aaaa.label AAAA" - print "aaaa.type DERIVE" - print "aaaa.min 0" - print "ptr.label PTR" - print "ptr.type DERIVE" - print "ptr.min 0" - print "mx.label MX" - print "mx.type DERIVE" - print "mx.min 0" - print "type252.label AXFR" - print "type252.type DERIVE" - print "type252.min 0" - print "snxd.label NXDOMAIN" - print "snxd.type DERIVE" - print "snxd.min 0" - print "rq.label Total Successful" - print "rq.type DERIVE" - print "rq.min 0" - + print "graph_title NSD3 Queries" + print "graph_vlabel qps" + print "graph_category network" + print "graph_info Queries per second" + for x in BOTH_LISTS: + val = x.split('=') + name = val[0].lower() + label = val[1].replace('_', ' ') + print name + '.label ' + label + print name + '.type DERIVE' + print name + '.min 0' + + sys.exit(0) def print_values(): - """Gets NSD's latest stats.""" - - pidf = open(PID_FILE, 'r') - pidn = pidf.read() - pidf.close(); - statscmd = ['kill', '-SIGUSR1'] - statscmd.append(pidn.strip()) - dropstats = subprocess.call(statscmd) + """Gets NSD's latest stats.""" - time.sleep(.5) # Wait for the log to write. - statf = open(STATS_FILE, 'r') - stats = tail(statf, 10) + bigfail = False + if ( not os.access(STATS_FILE, os.R_OK) ) : + bigfail = True + if ( not os.access(PID_FILE, os.R_OK) ) : + bigfail = True - nstats = [] - xstats = [] - - for line in stats: - if "XSTATS" in line: - xstats.append(line.strip()) - if "NSTATS" in line: - nstats.append(line.strip()) - - matches = re.compile(' A=(\d+)').findall(nstats[-1]) - if matches == []: - print "a.value 0" - else: - print "a.value " + matches[0] - - matches = re.compile(' AAAA=(\d+)').findall(nstats[-1]) - if matches == []: - print "aaaa.value 0" - else: - print "aaaa.value " + matches[0] - - matches = re.compile(' PTR=(\d+)').findall(nstats[-1]) - if matches == []: - print "ptr.value 0" - else: - print "ptr.value " + matches[0] + if ( not bigfail ): + pidf = open(PID_FILE, 'r') + pidn = pidf.read() + pidf.close(); + try: + os.kill(int(pidn.strip()), signal.SIGUSR1) + except OSError: + bigfail = True - matches = re.compile(' MX=(\d+)').findall(nstats[-1]) - if matches == []: - print "mx.value 0" - else: - print "mx.value " + matches[0] + time.sleep(.5) # Wait for the log to write. - matches = re.compile(' TYPE252=(\d+)').findall(nstats[-1]) - if matches == []: - print "type252.value 0" - else: - print "type252.value " + matches[0] + if ( not bigfail ): + statf = open(STATS_FILE, 'r') + stats = tail(statf, 10) - matches = re.compile(' SNXD=(\d+) ').findall(xstats[-1]) - if matches == []: - print "snxd.value 0" - else: - print "snxd.value " + matches[0] + nstats = [] + xstats = [] - matches = re.compile(' RQ=(\d+) ').findall(xstats[-1]) - if matches == []: - print "rq.value 0" - else: - print "rq.value " + matches[0] + for line in stats: + if "XSTATS" in line: + xstats.append(line.strip()) + if "NSTATS" in line: + nstats.append(line.strip()) + + statsline = nstats[-1] + xstats[-1] + else: + statsline = " " + + relist = [] + for x in BOTH_LISTS: + val = x.split('=') + name = val[0].lower() + rxp = val[0] + relist.append([name, rxp]) + + for point in relist: + matches = re.compile(' '+point[1]+'=(\d+)').findall(statsline) + if bigfail: + print point[0]+'.value U' + elif matches == []: + print point[0]+'.value 0' + else: + print point[0]+'.value '+matches[0] def tail( f, window=20 ): - f.seek( 0, 2 ) - bytes= f.tell() - size= window - block= -1 - while size > 0 and bytes+block*1024 > 0: - f.seek( block*1024, 2 ) # from the end! - data= f.read( 1024 ) - linesFound= data.count('\n') - size -= linesFound - block -= 1 - f.seek( block*1024, 2 ) - f.readline() # find a newline - lastBlocks= list( f.readlines() ) - return lastBlocks[-window:] + f.seek( 0, 2 ) + bytes = f.tell() + size = window + block = -1 + while size > 0 and bytes+block*1024 > 0: + f.seek( block*1024, 2 ) # from the end! + data = f.read( 1024 ) + linesFound = data.count('\n') + size -= linesFound + block -= 1 + f.seek( block*1024, 2 ) + f.readline() # find a newline + lastBlocks = list( f.readlines() ) + return lastBlocks[-window:] if __name__ == '__main__': - if len(sys.argv) > 1: - if sys.argv[1] == 'autoconf': - print 'yes' - sys.exit(0) + if len(sys.argv) > 1: + if sys.argv[1] == 'autoconf': + if ( not os.path.isfile(STATS_FILE) ) : + print 'no (Log file not found)' + elif ( not os.path.isfile(PID_FILE) ) : + print 'no (PID file not found)' + elif ( not os.access(STATS_FILE, os.R_OK) ) : + print 'no (Log file exists, access denied for read)' + elif ( not os.access(PID_FILE, os.R_OK) ) : + print 'no (PID file exists, access denied for read)' + else: + pidf = open(PID_FILE, 'r') + pidn = pidf.read() + pidf.close(); + try: + os.kill(int(pidn.strip()), signal.SIGUSR1) + except OSError as (errno, strerror): + print 'no (Unable to signal process :: '+strerror+')' + sys.exit(0) + print 'yes' + sys.exit(0) - if len(sys.argv) > 1 and sys.argv[1] == 'config': - print_config() - sys.exit(0) + if len(sys.argv) > 1 and sys.argv[1] == 'config': + print_config() + sys.exit(0) - print_values() + print_values()