Skip to content

Commit 11be8ca

Browse files
committed
Update script to work with Bind 9.10 V3 stats
Bind 9.10 has new XML stats Version 3. This update detects this and uses the new XML layout for stats collection. It also adds memory usage and cache data for Version 3 and Memory for Version 2. The Zabbix Template for Zabbix 3.x is updates with the new data fields for memory and cache, also adding graphs for those. The discovory rules is disabled, as it seems to create too much noise and would need some regex detect update.
1 parent 7f79a76 commit 11be8ca

File tree

3 files changed

+3216
-552
lines changed

3 files changed

+3216
-552
lines changed

bind-stats.py

Lines changed: 109 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import sys
99
import time
10+
import re
1011

1112

1213
JSONFILE = '/tmp/bindstats.json'
@@ -43,6 +44,22 @@
4344

4445
import xml.etree.ElementTree as ElementTree
4546
root = ElementTree.fromstring(content)
47+
# first, we need to see what statistics version we are. 2.x or 3.x
48+
# if root tag is isc = we have probably stats version 2, if it stats with statisics we have version 3 or newer
49+
if root.tag == 'isc':
50+
# get statistics version from isc/bind/statistics attrib
51+
version = root.find('./bind/statistics').attrib['version']
52+
elif root.tag == 'statistics':
53+
version = root.attrib['version']
54+
else:
55+
print("Unknown root tag: {}".format(root.ag), file=sys.stderr)
56+
print("ZBX_NOTSUPPORTED")
57+
# check the statistics version here
58+
v = re.match('^(\d{1})\.', version)
59+
version = int(v.group(1))
60+
if version < 0 or version > 3:
61+
print("Unsupported bind statistics version: {}".format(root.attrib), file=sys.stderr)
62+
print("ZBX_NOTSUPPORTED")
4663

4764
# Build the JSON cache
4865
j = {
@@ -53,30 +70,100 @@
5370
'socketcounter': {},
5471
'incounter': {},
5572
'outcounter': {},
56-
}
57-
for view in root.iterfind('./bind/statistics/views/view'):
58-
if view.findtext('./name') in ('_default',):
59-
for zone in view.iterfind('./zones/zone'):
60-
if zone.find('./counters') is not None:
73+
'cache': {},
74+
'memory': {}
75+
}
76+
# this is for version 2
77+
if version == 2:
78+
for view in root.iterfind('./bind/statistics/views/view'):
79+
if view.findtext('./name') in ('_default',):
80+
for zone in view.iterfind('./zones/zone'):
81+
if zone.find('./counters') is not None:
82+
counters = {}
83+
for counter in zone.iterfind('./counters/*'):
84+
counters[counter.tag] = counter.text
85+
j['zones'][zone.findtext('./name')] = counters
86+
for stat in root.iterfind('./bind/statistics/server/nsstat'):
87+
j['counter'][stat.findtext('./name')] = stat.findtext('./counter')
88+
for stat in root.iterfind('./bind/statistics/server/zonestat'):
89+
j['zonemaintenancecounter'][stat.findtext('./name')] = stat.findtext('./counter')
90+
for view in root.iterfind('./bind/statistics/views/view'):
91+
if view.findtext('./name') in ('_default',):
92+
for stat in view.iterfind('./resstat'):
93+
j['resolvercounter'][stat.findtext('./name')] = stat.findtext('./counter')
94+
for stat in root.iterfind('./bind/statistics/server/sockstat'):
95+
j['socketcounter'][stat.findtext('./name')] = stat.findtext('./counter')
96+
for stat in root.iterfind('./bind/statistics/server/queries-in/rdtype'):
97+
j['incounter'][stat.findtext('./name')] = stat.findtext('./counter')
98+
for stat in root.iterfind('./bind/statistics/views/view/rdtype'):
99+
j['outcounter'][stat.findtext('./name')] = stat.findtext('./counter')
100+
# Memory
101+
for child in root.iterfind('./bind/statistics/memory/summary/*'):
102+
j['memory'][child.tag] = child.text
103+
# Cache for local
104+
for child in root.iterfind('./bind/statistics/views/view/cache'):
105+
if child.attrib['name'] == 'localhost_resolver':
106+
for stat in child.iterfind('./rrset'):
107+
j['cache'][stat.findtext('./name')] = stat.findtext('./counter')
108+
109+
# this is for newer version 3
110+
if version == 3:
111+
for child in root.iterfind('./server/counters'):
112+
# V2 ./bind/statistics/server/nsstat
113+
if child.attrib['type'] == 'nsstat':
114+
for stat in child.iterfind('./counter'):
115+
j['counter'][stat.attrib['name']] = stat.text
116+
# V2 ./bind/statistics/server/sockstat
117+
if child.attrib['type'] == 'sockstat':
118+
for stat in child.iterfind('./counter'):
119+
j['socketcounter'][stat.attrib['name']] = stat.text
120+
# V2 ./bind/statistics/server/zonestat
121+
if child.attrib['type'] == 'zonestat':
122+
for stat in child.iterfind('./counter'):
123+
j['zonemaintenancecounter'][stat.attrib['name']] = stat.text
124+
# V2 ./bind/statistics/server/queries-in/rdtype
125+
if child.attrib['type'] == 'qtype':
126+
for stat in child.iterfind('./counter'):
127+
j['incounter'][stat.attrib['name']] = stat.text
128+
# they are only for block _default
129+
for child in root.iterfind('./views/view/counters'):
130+
# V2 ./bind/statistics/views/view/rdtype
131+
if child.attrib['type'] == 'resqtype':
132+
for stat in child.iterfind('./counter'):
133+
j['outcounter'][stat.attrib['name']] = stat.text
134+
# V2 ./bind/statistics/views/view => _default name only
135+
if child.attrib['type'] == 'resstats':
136+
for stat in child.iterfind('./counter'):
137+
j['resolvercounter'][stat.attrib['name']] = stat.text
138+
# V2: no (only in memory detail stats)
139+
if child.attrib['type'] == 'cachestats':
140+
for stat in child.iterfind('./counter'):
141+
j['cache'][stat.attrib['name']] = stat.text
142+
# V2 has @name = localhost_resolver, interal, external
143+
for child in root.iterfind('./views/view/cache'):
144+
if (child.attrib['name'] == '_default'):
145+
for stat in child.iterfind('./rrset'):
146+
j['cache'][stat.findtext('./name')] = stat.findtext('./counter')
147+
# for sets stating with !, we replace that with an _ (! is not allowed in zabbix)
148+
if re.match('^!', stat.findtext('./name')):
149+
j['cache'][stat.findtext('./name').replace('!', '_')] = stat.findtext('./counter')
150+
# for all the Zone stats only
151+
for child in root.iterfind('./views/view'):
152+
# only for default
153+
if (child.attrib['name'] == '_default'):
154+
# V2 ./bind/statistics/views/view -> ./zones/zone => _default name only
155+
for zone in child.iterfind('./zones/zone'):
61156
counters = {}
62-
for counter in zone.iterfind('./counters/*'):
63-
counters[counter.tag] = counter.text
64-
j['zones'][zone.findtext('./name')] = counters
65-
for stat in root.iterfind('./bind/statistics/server/nsstat'):
66-
j['counter'][stat.findtext('./name')] = stat.findtext('./counter')
67-
for stat in root.iterfind('./bind/statistics/server/zonestat'):
68-
j['zonemaintenancecounter'][stat.findtext('./name')] = stat.findtext('./counter')
69-
for view in root.iterfind('./bind/statistics/views/view'):
70-
if view.findtext('./name') in ('_default',):
71-
for stat in view.iterfind('./resstat'):
72-
j['resolvercounter'][stat.findtext('./name')] = stat.findtext('./counter')
73-
for stat in root.iterfind('./bind/statistics/server/sockstat'):
74-
j['socketcounter'][stat.findtext('./name')] = stat.findtext('./counter')
75-
for stat in root.iterfind('./bind/statistics/server/queries-in/rdtype'):
76-
j['incounter'][stat.findtext('./name')] = stat.findtext('./counter')
77-
for stat in root.iterfind('./bind/statistics/views/view/rdtype'):
78-
j['outcounter'][stat.findtext('./name')] = stat.findtext('./counter')
157+
for stat in zone.iterfind('./counters'):
158+
if stat.attrib['type'] == 'rcode' or stat.attrib['type'] == 'qtype':
159+
for counter in stat.iterfind('./counter'):
160+
counters[counter.attrib['name']] = counter.text
161+
j['zones'][zone.attrib['name']] = counters
162+
# V2 ./bind/statistics/memory/summary/*
163+
for child in root.iterfind('./memory/summary/*'):
164+
j['memory'][child.tag] = child.text
79165

166+
# write to cache is the same in both version
80167
with open(JSONFILE, 'w') as f:
81168
json.dump(j, f)
82169

userparameter_bind.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ UserParameter=bind.resolvercounter[*],/usr/local/bin/bind-stats.py resolvercount
66
UserParameter=bind.socketcounter[*],/usr/local/bin/bind-stats.py socketcounter -c $1
77
UserParameter=bind.incounter[*],/usr/local/bin/bind-stats.py incounter -c $1
88
UserParameter=bind.outcounter[*],/usr/local/bin/bind-stats.py outcounter -c $1
9+
UserParameter=bind.memory[*],/usr/local/bin/bind-stats.py memory -c $1
10+
UserParameter=bind.cache[*],/usr/local/bin/bind-stats.py cache -c $1

0 commit comments

Comments
 (0)