Skip to content

Commit 5b47ee4

Browse files
committed
Add unbound plugin
1 parent 4221f6b commit 5b47ee4

3 files changed

Lines changed: 311 additions & 0 deletions

File tree

unbound/README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# unbound
2+
3+
Pythond module for ganglia. Reads stats from `unbound-control stats`.
4+
5+
http://unbound.net/documentation/unbound-control.html
6+
7+
## privileges
8+
9+
The ganglia user needs to execute the unbound-control stats command, so it's
10+
probably necessary to add this to your sudoers file:
11+
12+
ganglia ALL=(root) NOPASSWD: /usr/sbin/unbound-control stats
13+
14+
## stats
15+
16+
* unbound_queries
17+
18+
number of queries received
19+
20+
* unbound_cachehits
21+
22+
number of queries that were successfully answered using a cache lookup
23+
24+
* unbound_cachemiss
25+
26+
number of queries that needed recursive processing
27+
28+
* unbound_prefetch
29+
30+
number of cache prefetches performed. This number is included
31+
in cachehits, as the original query had the unprefetched answer
32+
from cache, and resulted in recursive processing, taking a slot
33+
in the requestlist. Not part of the recursivereplies (or the
34+
histogram thereof) or cachemiss, as a cache response was sent.
35+
36+
* unbound_recursivereplies
37+
38+
The number of replies sent to queries that needed recursive pro-
39+
cessing. Could be smaller than threadX.num.cachemiss if due to
40+
timeouts no replies were sent for some queries.
41+
42+
* unbound_requestlist_avg
43+
44+
The average number of requests in the internal recursive pro-
45+
cessing request list on insert of a new incoming recursive pro-
46+
cessing query.
47+
48+
* unbound_requestlist_max
49+
50+
Maximum size attained by the internal recursive processing
51+
request list.
52+
53+
* unbound_requestlist_overwritten
54+
55+
Number of requests in the request list that were overwritten by
56+
newer entries. This happens if there is a flood of queries that
57+
recursive processing and the server has a hard time.
58+
59+
* unbound_requestlist_exceeded
60+
61+
Queries that were dropped because the request list was full.
62+
This happens if a flood of queries need recursive processing,
63+
and the server can not keep up.
64+
65+
* unbound_requestlist_current_all
66+
67+
Current size of the request list, includes internally generated
68+
queries (such as priming queries and glue lookups).
69+
70+
* unbound_requestlist_current_user
71+
72+
Current size of the request list, only the requests from client
73+
queries.
74+
75+
* unbound_recursion_time_avg
76+
77+
Average time it took to answer queries that needed recursive
78+
processing. Note that queries that were answered from the cache
79+
are not in this average.
80+
81+
* unbound_recursion_time_median
82+
83+
The median of the time it took to answer queries that needed
84+
recursive processing. The median means that 50% of the user
85+
queries were answered in less than this time. Because of big
86+
outliers (usually queries to non responsive servers), the aver-
87+
age can be bigger than the median. This median has been calcu-
88+
lated by interpolation from a histogram.

unbound/conf.d/unbound.pyconf

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
modules {
2+
module {
3+
name = "unbound"
4+
language = "python"
5+
param stats_command {
6+
value = "sudo /usr/sbin/unbound-control stats"
7+
}
8+
}
9+
}
10+
11+
collection_group {
12+
collect_every = 30
13+
time_threshold = 60
14+
15+
metric {
16+
name_match = "unbound_(.+)"
17+
}
18+
}

unbound/python_modules/unbound.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# unbound gmond module for Ganglia
5+
#
6+
# Copyright (C) 2014 by Tobias Schmidt <[email protected]>, SoundCloud Inc.
7+
# All rights reserved.
8+
#
9+
# Permission is hereby granted, free of charge, to any person obtaining a copy
10+
# of this software and associated documentation files (the "Software"), to deal
11+
# in the Software without restriction, including without limitation the rights
12+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
# copies of the Software, and to permit persons to whom the Software is
14+
# furnished to do so, subject to the following conditions:
15+
#
16+
# The above copyright notice and this permission notice shall be included in
17+
# all copies or substantial portions of the Software.
18+
#
19+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
# THE SOFTWARE.
26+
#
27+
28+
import os
29+
import time
30+
31+
NAME_PREFIX = 'unbound_'
32+
PARAMS = {
33+
'stats_command': 'sudo /usr/sbin/unbound-control stats'
34+
}
35+
METRICS = {
36+
'time': 0,
37+
'data': {}
38+
}
39+
METRICS_CACHE_MAX = 5
40+
41+
42+
def create_desc(skel, prop):
43+
d = skel.copy()
44+
for k, v in prop.iteritems():
45+
d[k] = v
46+
return d
47+
48+
49+
def get_metrics():
50+
"""Return all metrics"""
51+
52+
global METRICS
53+
54+
if (time.time() - METRICS['time']) > METRICS_CACHE_MAX:
55+
# get raw metric data
56+
io = os.popen(PARAMS['stats_command'])
57+
58+
# convert to dict
59+
metrics = {}
60+
for line in io.readlines():
61+
key, value = line.split('=')[:2]
62+
metrics[key] = float(value)
63+
64+
# update cache
65+
METRICS = {
66+
'time': time.time(),
67+
'data': metrics
68+
}
69+
70+
return METRICS
71+
72+
73+
def get_value(name):
74+
"""Return a value for the requested metric"""
75+
76+
metrics = get_metrics()
77+
name = 'total.' + name[len(NAME_PREFIX):].replace('_', '.')
78+
79+
try:
80+
result = metrics['data'][name]
81+
except StandardError:
82+
result = 0.0
83+
84+
return result
85+
86+
87+
def metric_init(lparams):
88+
"""Initialize metric descriptors"""
89+
90+
global PARAMS, Desc_Skel
91+
92+
# set parameters
93+
for key in lparams:
94+
PARAMS[key] = lparams[key]
95+
96+
Desc_Skel = {
97+
'name': 'XXX',
98+
'call_back': get_value,
99+
'time_max': 60,
100+
'value_type': 'float',
101+
'format': '%f',
102+
'units': 'XXX',
103+
'slope': 'both',
104+
'description': 'XXX',
105+
'groups': 'unbound',
106+
}
107+
108+
descriptors = []
109+
110+
descriptors.append(create_desc(Desc_Skel, {
111+
'name': NAME_PREFIX + 'num_queries',
112+
'units': 'Queries',
113+
'description': 'Unbound queries',
114+
}))
115+
116+
descriptors.append(create_desc(Desc_Skel, {
117+
'name': NAME_PREFIX + 'num_cachehits',
118+
'units': 'Queries',
119+
'description': 'Unbound cachehits',
120+
}))
121+
122+
descriptors.append(create_desc(Desc_Skel, {
123+
'name': NAME_PREFIX + 'num_cachemiss',
124+
'units': 'Queries',
125+
'description': 'Unbound cachemiss',
126+
}))
127+
128+
descriptors.append(create_desc(Desc_Skel, {
129+
'name': NAME_PREFIX + 'num_prefetch',
130+
'units': 'Prefetches',
131+
'description': 'Unbound cache prefetches',
132+
}))
133+
134+
descriptors.append(create_desc(Desc_Skel, {
135+
'name': NAME_PREFIX + 'num_recursivereplies',
136+
'units': 'Replies',
137+
'description': 'Replies to recursive queries',
138+
}))
139+
140+
descriptors.append(create_desc(Desc_Skel, {
141+
'name': NAME_PREFIX + 'requestlist_avg',
142+
'units': 'Requests',
143+
'description': 'Number of requests (avg.)',
144+
}))
145+
146+
descriptors.append(create_desc(Desc_Skel, {
147+
'name': NAME_PREFIX + 'requestlist_max',
148+
'units': 'Requests',
149+
'description': 'Number of requests (max.)',
150+
}))
151+
152+
descriptors.append(create_desc(Desc_Skel, {
153+
'name': NAME_PREFIX + 'requestlist_overwritten',
154+
'units': 'Requests',
155+
'description': 'Overwritten number of requests',
156+
}))
157+
158+
descriptors.append(create_desc(Desc_Skel, {
159+
'name': NAME_PREFIX + 'requestlist_exceeded',
160+
'units': 'Requests',
161+
'description': 'Dropped number of requests',
162+
}))
163+
164+
descriptors.append(create_desc(Desc_Skel, {
165+
'name': NAME_PREFIX + 'requestlist_current_all',
166+
'units': 'Requests',
167+
'description': 'Unbound requestlist size (all)',
168+
}))
169+
170+
descriptors.append(create_desc(Desc_Skel, {
171+
'name': NAME_PREFIX + 'requestlist_current_user',
172+
'units': 'Requests',
173+
'description': 'Unbound requestlist size (user)',
174+
}))
175+
176+
descriptors.append(create_desc(Desc_Skel, {
177+
'name': NAME_PREFIX + 'recursion_time_avg',
178+
'units': 'Seconds',
179+
'description': 'Unbound recursion latency (avg.)',
180+
}))
181+
182+
descriptors.append(create_desc(Desc_Skel, {
183+
'name': NAME_PREFIX + 'recursion_time_median',
184+
'units': 'Seconds',
185+
'description': 'Unbound recursion latency (50th)',
186+
}))
187+
188+
return descriptors
189+
190+
191+
def metric_cleanup():
192+
"""Cleanup"""
193+
194+
pass
195+
196+
197+
# the following code is for debugging and testing
198+
if __name__ == '__main__':
199+
descriptors = metric_init(PARAMS)
200+
while True:
201+
for d in descriptors:
202+
fmt = (('%s = %s') % (d['name'], d['format']))
203+
print fmt % (d['call_back'](d['name']))
204+
print 'Sleeping 15 seconds'
205+
time.sleep(15)

0 commit comments

Comments
 (0)