summaryrefslogtreecommitdiff
path: root/roles
diff options
context:
space:
mode:
authorNiklas Yann Wettengel <niyawe@niyawe.de>2020-08-24 10:32:50 +0200
committerNiklas Yann Wettengel <niyawe@niyawe.de>2020-08-24 10:32:50 +0200
commit74ca81df467c8fb24c0f6b8da333f252f0210a10 (patch)
tree7a8f0270767fd6ebd77b27341e7f14cd58abab4f /roles
parent36440dcf2ac230be781d9cb72b911552b7c4903b (diff)
munin: collect bind stats
Diffstat (limited to 'roles')
-rw-r--r--roles/install_bind/templates/named.conf.j24
-rw-r--r--roles/install_monitoring/files/munin/bind9stats.py358
-rw-r--r--roles/install_monitoring/tasks/install_munin.yml7
3 files changed, 369 insertions, 0 deletions
diff --git a/roles/install_bind/templates/named.conf.j2 b/roles/install_bind/templates/named.conf.j2
index 15af2e7..4704eee 100644
--- a/roles/install_bind/templates/named.conf.j2
+++ b/roles/install_bind/templates/named.conf.j2
@@ -29,6 +29,10 @@ options {
server-id none;
};
+statistics-channels {
+ inet 127.0.0.1 port 8053 allow { 127.0.0.1; };
+};
+
zone "localhost" IN {
type master;
file "localhost.zone";
diff --git a/roles/install_monitoring/files/munin/bind9stats.py b/roles/install_monitoring/files/munin/bind9stats.py
new file mode 100644
index 0000000..bb61716
--- /dev/null
+++ b/roles/install_monitoring/files/munin/bind9stats.py
@@ -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()
+
diff --git a/roles/install_monitoring/tasks/install_munin.yml b/roles/install_monitoring/tasks/install_munin.yml
index 7f09b2d..1a35928 100644
--- a/roles/install_monitoring/tasks/install_munin.yml
+++ b/roles/install_monitoring/tasks/install_munin.yml
@@ -80,6 +80,13 @@
mode: 0755
notify: restart munin-node
+- name: copy bind9stats plugin
+ copy:
+ src: munin/bind9stats.py
+ dest: /etc/munin/plugins/bind9stats.py
+ mode: 0755
+ notify: restart munin-node
+
- name: copy global config
copy:
src: munin/munin_global_conf