mirror of
https://github.com/munin-monitoring/contrib.git
synced 2025-07-22 22:25:23 +00:00
true autoconf, a few extra configuration options (extensible use)
This commit is contained in:
parent
2f01b83cac
commit
6f28e159c6
1 changed files with 162 additions and 123 deletions
|
@ -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."""
|
||||
|
||||
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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue