mirror of
https://github.com/munin-monitoring/contrib.git
synced 2025-07-21 18:41:03 +00:00
knot: new plugin to monitor knot DNS server statistics
This commit is contained in:
parent
301d13b111
commit
cc6f29fb6c
1 changed files with 186 additions and 0 deletions
186
plugins/knot/knot
Executable file
186
plugins/knot/knot
Executable file
|
@ -0,0 +1,186 @@
|
|||
#!/usr/bin/python3 -tt
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=invalid-name
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
"""Munin plugin to monitor Knot DNS server.
|
||||
|
||||
Copyright 2017, Kim B. Heino, b@bbbs.net, Foobar Oy
|
||||
License GPLv2+
|
||||
|
||||
This plugin requires Munin config /etc/munin/plugin-conf.d/knot:
|
||||
|
||||
[knot]
|
||||
user root
|
||||
|
||||
|
||||
#%# capabilities=autoconf
|
||||
#%# family=auto
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
CONFIG = {
|
||||
# 'edns-presence': {},
|
||||
# 'flag-presence': {},
|
||||
'query-size': {
|
||||
'title': 'query counts grouped by size',
|
||||
'vlabel': 'queries / second',
|
||||
'info': '',
|
||||
},
|
||||
'query-type': {
|
||||
'title': 'query types',
|
||||
'vlabel': 'queries / second',
|
||||
'info': '',
|
||||
},
|
||||
'reply-nodata': {
|
||||
'title': 'no-data replies',
|
||||
'vlabel': 'replies / second',
|
||||
'info': '',
|
||||
},
|
||||
'reply-size': {
|
||||
'title': 'reply counts grouped by size',
|
||||
'vlabel': 'replies / second',
|
||||
'info': '',
|
||||
},
|
||||
'request-bytes': {
|
||||
'title': 'request bytes',
|
||||
'vlabel': 'bytes / second',
|
||||
'info': '',
|
||||
},
|
||||
'request-protocol': {
|
||||
'title': 'request protocols',
|
||||
'vlabel': 'requests / second',
|
||||
'info': '',
|
||||
},
|
||||
'response-bytes': {
|
||||
'title': 'response bytes',
|
||||
'vlabel': 'bytes / second',
|
||||
'info': '',
|
||||
},
|
||||
'response-code': {
|
||||
'title': 'response codes',
|
||||
'vlabel': 'responses / second',
|
||||
'info': '',
|
||||
},
|
||||
'server-operation': {
|
||||
'title': 'operations',
|
||||
'vlabel': 'operations / second',
|
||||
'info': '',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _merge_replysize(values):
|
||||
"""Merge reply-size 512..65535 stats."""
|
||||
if 'reply-size' not in values:
|
||||
return
|
||||
|
||||
total = 0
|
||||
todel = []
|
||||
for key in values['reply-size']:
|
||||
if int(key.split('-')[0]) >= 512:
|
||||
total += values['reply-size'][key]
|
||||
todel.append(key)
|
||||
for key in todel:
|
||||
del values['reply-size'][key]
|
||||
values['reply-size']['512-65535'] = total
|
||||
|
||||
|
||||
def get_stats():
|
||||
"""Get statistics."""
|
||||
# Get status output
|
||||
try:
|
||||
pipe = subprocess.Popen(
|
||||
['/usr/sbin/knotc', '--force', 'stats'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
output = pipe.communicate()[0].decode('utf-8', 'ignore')
|
||||
except OSError:
|
||||
return {}
|
||||
|
||||
# Parse output. Keep graph labels in knotc-order.
|
||||
values = defaultdict(dict)
|
||||
for line in output.splitlines():
|
||||
if not line.startswith('mod-stats.') or ' = ' not in line:
|
||||
continue
|
||||
|
||||
# Parse key
|
||||
key, value = line.split(' = ', 1)
|
||||
key = key[10:-1]
|
||||
key1, key2 = key.split('[', 1)
|
||||
|
||||
# Parse value
|
||||
try:
|
||||
values[key1][key2] = int(value)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
_merge_replysize(values)
|
||||
return values
|
||||
|
||||
|
||||
def _clean_key(key):
|
||||
"""Convert knotc key to Munin label."""
|
||||
key = key.lower().replace('-', '_')
|
||||
if key[0].isdigit():
|
||||
key = '_' + key
|
||||
return key
|
||||
|
||||
|
||||
def print_config(values):
|
||||
"""Print plugin config."""
|
||||
for key_graph in sorted(CONFIG):
|
||||
if key_graph not in values:
|
||||
continue
|
||||
|
||||
# Basic data
|
||||
print('multigraph knot_{}'.format(key_graph.replace('-', '')))
|
||||
print('graph_title Knot {}'.format(CONFIG[key_graph]['title']))
|
||||
print('graph_vlabel {}'.format(CONFIG[key_graph]['vlabel']))
|
||||
info = CONFIG[key_graph]['info']
|
||||
if info:
|
||||
print('graph_info {}'.format(info))
|
||||
print('graph_category dns')
|
||||
print('graph_args --base 1000 --lower-limit 0')
|
||||
|
||||
# Keys
|
||||
for key_raw in values[key_graph]:
|
||||
key_clean = _clean_key(key_raw)
|
||||
print('{}.label {}'.format(key_clean, key_raw))
|
||||
print('{}.type DERIVE'.format(key_clean))
|
||||
print('{}.min 0'.format(key_clean))
|
||||
|
||||
if os.environ.get('MUNIN_CAP_DIRTYCONFIG') == '1':
|
||||
print_values(values)
|
||||
|
||||
|
||||
def print_values(values):
|
||||
"""Print plugin values."""
|
||||
for key_graph in sorted(CONFIG):
|
||||
if key_graph not in values:
|
||||
continue
|
||||
|
||||
print('multigraph knot_{}'.format(key_graph.replace('-', '')))
|
||||
for key_raw in values[key_graph]:
|
||||
key_clean = _clean_key(key_raw)
|
||||
print('{}.value {}'.format(key_clean, values[key_graph][key_raw]))
|
||||
|
||||
|
||||
def main(args):
|
||||
"""Do it all main program."""
|
||||
values = get_stats()
|
||||
if len(args) > 1 and args[1] == 'autoconf':
|
||||
print('yes' if values else 'no')
|
||||
elif len(args) > 1 and args[1] == 'config':
|
||||
print_config(values)
|
||||
else:
|
||||
print_values(values)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
Loading…
Add table
Add a link
Reference in a new issue