3
# Author: Austin S. Hemmelgarn (Ferroin)
4
5
import os
6
+import sys
7
8
from copy import deepcopy
9
63
}
64
}
65
66
+# This is used as a templates for the per-thread charts.
67
+PER_THREAD_CHARTS = {
68
+ '_queries': {
69
+ 'options': [None, '{0} Queries Processed', 'queries', 'Queries Processed', 'unbound.threads.queries', 'line'],
70
+ 'lines': [
71
+ ['{0}_ratelimit', 'Ratelimited', 'absolute', 1, 1],
72
+ ['{0}_cachemiss', 'Cache Miss', 'absolute', 1, 1],
73
+ ['{0}_cachehit', 'Cache Hit', 'absolute', 1, 1],
74
+ ['{0}_expired', 'Expired', 'absolute', 1, 1],
75
+ ['{0}_prefetch', 'Prefetched', 'absolute', 1, 1],
76
+ ['{0}_recursive', 'Recursive', 'absolute', 1, 1]
77
+ ]
78
+ },
79
+ '_reqlist': {
80
+ 'options': [None, '{0} Request List', 'items', 'Request List', 'unbound.threads.reqlist', 'line'],
81
+ 'lines': [
82
+ ['{0}_reqlist_avg', 'Average Size', 'absolute', 1, 1],
83
+ ['{0}_reqlist_max', 'Maximum Size', 'absolute', 1, 1],
84
+ ['{0}_reqlist_overwritten', 'Overwritten Requests', 'absolute', 1, 1],
85
+ ['{0}_reqlist_exceeded', 'Overruns', 'absolute', 1, 1],
86
+ ['{0}_reqlist_current', 'Current Size', 'absolute', 1, 1],
87
+ ['{0}_reqlist_user', 'User Requests', 'absolute', 1, 1]
88
+ ]
89
+ },
90
+ '_recursion': {
91
+ 'options': [None, '{0} Recursion Timings', 'seconds', 'Recursive Timings', 'unbound.threads.recursion', 'line'],
92
+ 'lines': [
93
+ ['{0}_recursive_avg', 'Average', 'absolute', 1, PRECISION],
94
+ ['{0}_recursive_med', 'Median', 'absolute', 1, PRECISION]
95
+ ]
96
+ }
97
+}
98
+
99
+
100
# This maps the Unbound stat names to our names and precision requiremnets.
101
STAT_MAP = {
102
'total.num.queries_ip_ratelimited': ('ratelimit', 1),
121
'dnscrypt_nonce.cache.count': ('cache_dnscn', 1)
122
}
123
124
+# Same as above, but for per-thread stats.
125
+PER_THREAD_STAT_MAP = {
126
+ '{0}.num.queries_ip_ratelimited': ('{0}_ratelimit', 1),
127
+ '{0}.num.cachehits': ('{0}_cachehit', 1),
128
+ '{0}.num.cachemiss': ('{0}_cachemiss', 1),
129
+ '{0}.num.zero_ttl': ('{0}_expired', 1),
130
+ '{0}.num.prefetch': ('{0}_prefetch', 1),
131
+ '{0}.num.recursivereplies': ('{0}_recursive', 1),
132
+ '{0}.requestlist.avg': ('{0}_reqlist_avg', 1),
133
+ '{0}.requestlist.max': ('{0}_reqlist_max', 1),
134
+ '{0}.requestlist.overwritten': ('{0}_reqlist_overwritten', 1),
135
+ '{0}.requestlist.exceeded': ('{0}_reqlist_exceeded', 1),
136
+ '{0}.requestlist.current.all': ('{0}_reqlist_current', 1),
137
+ '{0}.requestlist.current.user': ('{0}_reqlist_user', 1),
138
+ '{0}.recursion.time.avg': ('{0}_recursive_avg', PRECISION),
139
+ '{0}.recursion.time.median': ('{0}_recursive_med', PRECISION)
140
+}
141
+
142
143
class Service(SocketService):
144
def __init__(self, configuration=None, name=None):
150
SocketService.__init__(self, configuration, name)
151
self.ext = self.configuration.get('extended', None)
152
self.ubconf = self.configuration.get('ubconf', None)
153
+ self.perthread = self.configuration.get('per_thread', False)
154
+ self.threads = None
155
self.order = deepcopy(ORDER)
156
self.definitions = deepcopy(CHARTS)
157
self.request = 'UBCT1 stats\n'
158
self._parse_config()
159
self._auto_config()
160
self.debug('Extended stats: {0}'.format(self.ext))
161
+ self.debug('Per-thread stats: {0}'.format(self.perthread))
162
if self.ext:
163
self.order = self.order + EXTENDED_ORDER
164
self.definitions.update(EXTENDED_CHARTS)
165
if self.unix_socket:
166
self.debug('Using unix socket: {0}'.format(self.unix_socket))
111
- for key in self.definitions:
112
- self.definitions[key]['options'][4] = 'Local'
167
else:
168
self.debug('Connecting to: {0}:{1}'.format(self.host, self.port))
169
self.debug('Using key: {0}'.format(self.key))
170
self.debug('Using certificate: {0}'.format(self.cert))
117
- for key in self.definitions:
118
- self.definitions[key]['options'][4] = self.host
171
172
def _auto_config(self):
173
if self.ubconf and os.access(self.ubconf, os.R_OK):
192
if not self.port:
193
self.port = 8953
194
195
+ def _generate_perthread_charts(self):
196
+ # TODO: THis could probably be more efficient, but it's only
197
+ # run once, so it probably doesn't really matter.
198
+ for thread in range(0, self.threads):
199
+ shortname = 'thread{0}'.format(thread)
200
+ longname = 'Thread {0}'.format(thread)
201
+ charts = dict()
202
+ order = [[], [], []]
203
+ statmap = dict()
204
+ count = 0
205
+ for item in PER_THREAD_CHARTS:
206
+ chartname = '{0}{1}'.format(shortname, item)
207
+ order[count].append(chartname)
208
+ count += 1
209
+ charts[chartname] = deepcopy(PER_THREAD_CHARTS[item])
210
+ charts[chartname]['options'][1] = charts[chartname]['options'][1].format(longname)
211
+ for line in range(0, len(charts[chartname]['lines'])):
212
+ charts[chartname]['lines'][line][0] = charts[chartname]['lines'][line][0].format(shortname)
213
+ order = order[0] + order[1] + order[2]
214
+ for chart in order:
215
+ params = [chart] + charts[chart]['options']
216
+ dimensions = charts[chart]['lines']
217
+ new_chart = self.charts.add_chart(params)
218
+ for dimension in dimensions:
219
+ new_chart.add_dimension(dimension)
220
+ for key, value in PER_THREAD_STAT_MAP.items():
221
+ STAT_MAP[key.format(shortname)] = (value[0].format(shortname), value[1])
222
+
223
def check(self):
144
- # We need to check that auth works, otherwise there's no point.
224
+ # Check if authentication is working.
225
self._connect()
226
result = bool(self._sock)
227
self._disconnect()
228
+ # If auth works, and we need per-thread charts, query the server
229
+ # to see how many threads it's using. This somewhat abuses the
230
+ # SocketService API to get the data we need.
231
+ if result and self.perthread:
232
+ tmp = self.request
233
+ if sys.version_info[0] < 3:
234
+ self.request = 'UBCT1 status\n'
235
+ else:
236
+ self.request = b'UBCT1 status\n'
237
+ raw = self._get_raw_data()
238
+ for line in raw.splitlines():
239
+ if line.startswith('threads'):
240
+ self.threads = int(line.split()[1])
241
+ self._generate_perthread_charts()
242
+ break
243
+ if self.threads is None:
244
+ self.info('Unable to auto-detect thread counts, disabling per-thread stats.')
245
+ self.perthread = False
246
+ self.request = tmp
247
return result
248
249
@staticmethod