1
0
Fork 0
mirror of https://github.com/munin-monitoring/contrib.git synced 2025-07-23 06:35:42 +00:00

true autoconf, a few extra configuration options (extensible use)

This commit is contained in:
J.T. Sage 2010-06-12 03:17:46 +02:00 committed by Steve Schnepp
parent 2f01b83cac
commit 6f28e159c6

View file

@ -1,28 +1,71 @@
#!/usr/bin/python
#%# family=auto
#%# capabilities=suggest autoconf
#%# capabilities=autoconf
"""
Munin plugin to monitor NSD3 statistics
J.T.Sage <jtsage@gmail.com>, 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 <jtsage@gmail.com>
=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."""
"""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)
bigfail = False
if ( not os.access(STATS_FILE, os.R_OK) ) :
bigfail = True
if ( not os.access(PID_FILE, os.R_OK) ) :
bigfail = True
time.sleep(.5) # Wait for the log to write.
statf = open(STATS_FILE, 'r')
stats = tail(statf, 10)
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()