munin: collect bind stats
							parent
							
								
									36440dcf2a
								
							
						
					
					
						commit
						74ca81df46
					
				| @ -0,0 +1,358 @@ | ||||
| #!/usr/bin/env python | ||||
| 
 | ||||
| """ | ||||
| Munin monitoring plug-in for BIND9 DNS statistics server. Tested | ||||
| with BIND 9.10, 9.11, and 9.12, exporting version 3.x of the XML | ||||
| statistics. | ||||
| 
 | ||||
| Copyright (c) 2013-2015, Shumon Huque. All rights reserved. | ||||
| This program is free software; you can redistribute it and/or modify | ||||
| it under the same terms as Python itself. | ||||
| """ | ||||
| 
 | ||||
| import os, sys | ||||
| import xml.etree.ElementTree as et | ||||
| try: | ||||
|     from urllib2 import urlopen                  # for Python 2 | ||||
| except ImportError: | ||||
|     from urllib.request import urlopen           # for Python 3 | ||||
| 
 | ||||
| VERSION = "0.31" | ||||
| 
 | ||||
| HOST = os.environ.get('HOST', "127.0.0.1") | ||||
| PORT = os.environ.get('PORT', "8053") | ||||
| INSTANCE = os.environ.get('INSTANCE', "") | ||||
| SUBTITLE = os.environ.get('SUBTITLE', "") | ||||
| 
 | ||||
| STATS_TYPE = "xml"                           # will support json later | ||||
| BINDSTATS_URL = "http://%s:%s/%s" % (HOST, PORT, STATS_TYPE) | ||||
| 
 | ||||
| if SUBTITLE != '': | ||||
|     SUBTITLE = ' ' + SUBTITLE | ||||
| 
 | ||||
| GraphCategoryName = "dns_bind" | ||||
| 
 | ||||
| # Note: munin displays these graphs ordered alphabetically by graph title | ||||
| 
 | ||||
| GraphConfig = ( | ||||
| 
 | ||||
|     ('dns_opcode_in' + INSTANCE, | ||||
|      dict(title='BIND [00] Opcodes In', | ||||
|           enable=True, | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Queries/sec', | ||||
|           location="server/counters[@type='opcode']/counter", | ||||
|           config=dict(type='DERIVE', min=0, draw='AREASTACK'))), | ||||
| 
 | ||||
|     ('dns_qtypes_in' + INSTANCE, | ||||
|      dict(title='BIND [01] Query Types In', | ||||
|           enable=True, | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Queries/sec', | ||||
|           location="server/counters[@type='qtype']/counter", | ||||
|           config=dict(type='DERIVE', min=0, draw='AREASTACK'))), | ||||
| 
 | ||||
|     ('dns_server_stats' + INSTANCE, | ||||
|      dict(title='BIND [02] Server Stats', | ||||
|           enable=True, | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Queries/sec', | ||||
|           location="server/counters[@type='nsstat']/counter", | ||||
|           fields=("Requestv4", "Requestv6", "ReqEdns0", "ReqTCP", "ReqTSIG", | ||||
|                   "Response", "TruncatedResp", "RespEDNS0", "RespTSIG", | ||||
|                   "QrySuccess", "QryAuthAns", "QryNoauthAns", "QryReferral", | ||||
|                   "QryNxrrset", "QrySERVFAIL", "QryFORMERR", "QryNXDOMAIN", | ||||
|                   "QryRecursion", "QryDuplicate", "QryDropped", "QryFailure", | ||||
|                   "XfrReqDone", "UpdateDone", "QryUDP", "QryTCP"), | ||||
|           config=dict(type='DERIVE', min=0))), | ||||
| 
 | ||||
|     ('dns_cachedb' + INSTANCE, | ||||
|      dict(title='BIND [03] CacheDB RRsets', | ||||
|           enable=True, | ||||
|           stattype='cachedb', | ||||
|           args='-l 0', | ||||
|           vlabel='Count', | ||||
|           location="views/view[@name='_default']/cache[@name='_default']/rrset", | ||||
|           config=dict(type='GAUGE', min=0))), | ||||
| 
 | ||||
|     ('dns_resolver_stats' + INSTANCE, | ||||
|      dict(title='BIND [04] Resolver Stats', | ||||
|           enable=False,                         # appears to be empty | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Count/sec', | ||||
|           location="server/counters[@type='resstat']/counter", | ||||
|           config=dict(type='DERIVE', min=0))), | ||||
| 
 | ||||
|     ('dns_resolver_stats_qtype' + INSTANCE, | ||||
|      dict(title='BIND [05] Resolver Outgoing Queries', | ||||
|           enable=True, | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Count/sec', | ||||
|           location="views/view[@name='_default']/counters[@type='resqtype']/counter", | ||||
|           config=dict(type='DERIVE', min=0))), | ||||
| 
 | ||||
|     ('dns_resolver_stats_view' + INSTANCE, | ||||
|      dict(title='BIND [06] Resolver Stats', | ||||
|           enable=True, | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Count/sec', | ||||
|           location="views/view[@name='_default']/counters[@type='resstats']/counter", | ||||
|           config=dict(type='DERIVE', min=0))), | ||||
| 
 | ||||
|     ('dns_cachestats' + INSTANCE, | ||||
|      dict(title='BIND [07] Resolver Cache Stats', | ||||
|           enable=True, | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Count/sec', | ||||
|           location="views/view[@name='_default']/counters[@type='cachestats']/counter", | ||||
|           fields=("CacheHits", "CacheMisses", "QueryHits", "QueryMisses", | ||||
|                   "DeleteLRU", "DeleteTTL"), | ||||
|           config=dict(type='DERIVE', min=0))), | ||||
| 
 | ||||
|     ('dns_cache_mem' + INSTANCE, | ||||
|      dict(title='BIND [08] Resolver Cache Memory Stats', | ||||
|           enable=True, | ||||
|           stattype='counter', | ||||
|           args='-l 0 --base 1024', | ||||
|           vlabel='Memory In-Use', | ||||
|           location="views/view[@name='_default']/counters[@type='cachestats']/counter", | ||||
|           fields=("TreeMemInUse", "HeapMemInUse"), | ||||
|           config=dict(type='GAUGE', min=0))), | ||||
| 
 | ||||
|     ('dns_socket_activity' + INSTANCE, | ||||
|      dict(title='BIND [09] Socket Activity', | ||||
|           enable=True, | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Active', | ||||
|           location="server/counters[@type='sockstat']/counter", | ||||
|           fields=("UDP4Active", "UDP6Active", | ||||
|                   "TCP4Active", "TCP6Active", | ||||
|                   "UnixActive", "RawActive"), | ||||
|           config=dict(type='GAUGE', min=0))), | ||||
| 
 | ||||
|     ('dns_socket_stats' + INSTANCE, | ||||
|      dict(title='BIND [10] Socket Rates', | ||||
|           enable=True, | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Count/sec', | ||||
|           location="server/counters[@type='sockstat']/counter", | ||||
|           fields=("UDP4Open", "UDP6Open", | ||||
|                   "TCP4Open", "TCP6Open", | ||||
|                   "UDP4OpenFail", "UDP6OpenFail", | ||||
|                   "TCP4OpenFail", "TCP6OpenFail", | ||||
|                   "UDP4Close", "UDP6Close", | ||||
|                   "TCP4Close", "TCP6Close", | ||||
|                   "UDP4BindFail", "UDP6BindFail", | ||||
|                   "TCP4BindFail", "TCP6BindFail", | ||||
|                   "UDP4ConnFail", "UDP6ConnFail", | ||||
|                   "TCP4ConnFail", "TCP6ConnFail", | ||||
|                   "UDP4Conn", "UDP6Conn", | ||||
|                   "TCP4Conn", "TCP6Conn", | ||||
|                   "TCP4AcceptFail", "TCP6AcceptFail", | ||||
|                   "TCP4Accept", "TCP6Accept", | ||||
|                   "UDP4SendErr", "UDP6SendErr", | ||||
|                   "TCP4SendErr", "TCP6SendErr", | ||||
|                   "UDP4RecvErr", "UDP6RecvErr", | ||||
|                   "TCP4RecvErr", "TCP6RecvErr"), | ||||
|           config=dict(type='DERIVE', min=0))), | ||||
| 
 | ||||
|     ('dns_zone_stats' + INSTANCE, | ||||
|      dict(title='BIND [11] Zone Maintenance', | ||||
|           enable=False, | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Count/sec', | ||||
|           location="server/counters[@type='zonestat']/counter", | ||||
|           config=dict(type='DERIVE', min=0))), | ||||
| 
 | ||||
|     ('dns_memory_usage' + INSTANCE, | ||||
|      dict(title='BIND [12] Memory Usage', | ||||
|           enable=True, | ||||
|           stattype='memory', | ||||
|           args='-l 0 --base 1024', | ||||
|           vlabel='Memory In-Use', | ||||
|           location='memory/summary', | ||||
|           fields=("ContextSize", "BlockSize", "Lost", "InUse"), | ||||
|           config=dict(type='GAUGE', min=0))), | ||||
| 
 | ||||
|     ('dns_adbstat' + INSTANCE, | ||||
|      dict(title='BIND [13] adbstat', | ||||
|           enable=True, | ||||
|           stattype='counter', | ||||
|           args='-l 0', | ||||
|           vlabel='Count', | ||||
|           location="views/view[@name='_default']/counters[@type='adbstat']/counter", | ||||
|           config=dict(type='GAUGE', min=0))), | ||||
| 
 | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| def unsetenvproxy(): | ||||
|     """Unset HTTP Proxy environment variables that might interfere""" | ||||
|     for proxyvar in [ 'http_proxy', 'HTTP_PROXY' ]: | ||||
|         os.unsetenv(proxyvar) | ||||
|     return | ||||
| 
 | ||||
| 
 | ||||
| def getstatsversion(etree): | ||||
|     """return version of BIND statistics""" | ||||
|     return etree.attrib['version'] | ||||
| 
 | ||||
| 
 | ||||
| def getdata(graph, etree, getvals=False): | ||||
| 
 | ||||
|     stattype = graph[1]['stattype'] | ||||
|     location = graph[1]['location'] | ||||
| 
 | ||||
|     if stattype == 'memory': | ||||
|         return getdata_memory(graph, etree, getvals) | ||||
|     elif stattype == 'cachedb': | ||||
|         return getdata_cachedb(graph, etree, getvals) | ||||
| 
 | ||||
|     results = [] | ||||
|     counters = etree.findall(location) | ||||
| 
 | ||||
|     if counters is None:                     # empty result | ||||
|         return results | ||||
| 
 | ||||
|     for c in counters: | ||||
|         key = c.attrib['name'] | ||||
|         val = c.text | ||||
|         if getvals: | ||||
|             results.append((key, val)) | ||||
|         else: | ||||
|             results.append(key) | ||||
|     return results | ||||
| 
 | ||||
| 
 | ||||
| def getdata_memory(graph, etree, getvals=False): | ||||
| 
 | ||||
|     location = graph[1]['location'] | ||||
| 
 | ||||
|     results = [] | ||||
|     counters = etree.find(location) | ||||
| 
 | ||||
|     if counters is None:                     # empty result | ||||
|         return results | ||||
| 
 | ||||
|     for c in counters: | ||||
|         key = c.tag | ||||
|         val = c.text | ||||
|         if getvals: | ||||
|             results.append((key, val)) | ||||
|         else: | ||||
|             results.append(key) | ||||
|     return results | ||||
| 
 | ||||
| 
 | ||||
| def getdata_cachedb(graph, etree, getvals=False): | ||||
| 
 | ||||
|     location = graph[1]['location'] | ||||
| 
 | ||||
|     results = [] | ||||
|     counters = etree.findall(location) | ||||
| 
 | ||||
|     if counters is None:                     # empty result | ||||
|         return results | ||||
| 
 | ||||
|     for c in counters: | ||||
|         key = c.find('name').text | ||||
|         val = c.find('counter').text | ||||
|         if getvals: | ||||
|             results.append((key, val)) | ||||
|         else: | ||||
|             results.append(key) | ||||
|     return results | ||||
| 
 | ||||
| 
 | ||||
| def validkey(graph, key): | ||||
|     fieldlist = graph[1].get('fields', None) | ||||
|     if fieldlist and (key not in fieldlist): | ||||
|         return False | ||||
|     else: | ||||
|         return True | ||||
| 
 | ||||
| 
 | ||||
| def get_etree_root(url): | ||||
|     """Return the root of an ElementTree structure populated by | ||||
|     parsing BIND9 statistics obtained at the given URL""" | ||||
| 
 | ||||
|     data = urlopen(url) | ||||
|     return et.parse(data).getroot() | ||||
| 
 | ||||
| 
 | ||||
| def muninconfig(etree): | ||||
|     """Generate munin config for the BIND stats plugin""" | ||||
| 
 | ||||
|     for g in GraphConfig: | ||||
|         if not g[1]['enable']: | ||||
|             continue | ||||
|         print("multigraph %s" % g[0]) | ||||
|         print("graph_title %s" % g[1]['title'] + SUBTITLE) | ||||
|         print("graph_args %s" % g[1]['args']) | ||||
|         print("graph_vlabel %s" % g[1]['vlabel']) | ||||
|         print("graph_category %s" % GraphCategoryName) | ||||
| 
 | ||||
|         data = getdata(g, etree, getvals=False) | ||||
|         if data != None: | ||||
|             for key in data: | ||||
|                 if validkey(g, key): | ||||
|                     print("%s.label %s" % (key, key)) | ||||
|                     if 'draw' in g[1]['config']: | ||||
|                         print("%s.draw %s" % (key, g[1]['config']['draw'])) | ||||
|                     print("%s.min %s" % (key, g[1]['config']['min'])) | ||||
|                     print("%s.type %s" % (key, g[1]['config']['type'])) | ||||
|         print('') | ||||
| 
 | ||||
| 
 | ||||
| def munindata(etree): | ||||
|     """Generate munin data for the BIND stats plugin""" | ||||
| 
 | ||||
|     for g in GraphConfig: | ||||
|         if not g[1]['enable']: | ||||
|             continue | ||||
|         print("multigraph %s" % g[0]) | ||||
|         data = getdata(g, etree, getvals=True) | ||||
|         if data != None: | ||||
|             for (key, value) in data: | ||||
|                 if validkey(g, key): | ||||
|                     print("%s.value %s" % (key, value)) | ||||
|         print('') | ||||
| 
 | ||||
| 
 | ||||
| def usage(): | ||||
|     """Print plugin usage""" | ||||
|     print("""\ | ||||
| \nUsage: bind9stats.py [config|statsversion]\n""") | ||||
|     sys.exit(1) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
| 
 | ||||
|     tree = get_etree_root(BINDSTATS_URL) | ||||
| 
 | ||||
|     args = sys.argv[1:] | ||||
|     argslen = len(args) | ||||
|     unsetenvproxy() | ||||
| 
 | ||||
|     if argslen == 0: | ||||
|         munindata(tree) | ||||
|     elif argslen == 1: | ||||
|         if args[0] == "config": | ||||
|             muninconfig(tree) | ||||
|         elif args[0] == "statsversion": | ||||
|             print("bind9stats %s version %s" % (STATS_TYPE, getstatsversion(tree))) | ||||
|         else: | ||||
|             usage() | ||||
|     else: | ||||
|         usage() | ||||
| 
 | ||||
					Loading…
					
					
				
		Reference in New Issue