1
0
Fork 0
mirror of https://github.com/munin-monitoring/contrib.git synced 2025-07-25 10:28:36 +00:00
Munin-Contrib/plugins/knot/knot
Kim B. Heino 9759634977 knot: cache results, needed for first run after server reboot
Sometimes after reboot munin-async + munin-node runs before knot is
ready. This will result missing knot stats, as there is no static
config in plugin. Cache results and use them instead if knot's output
is empty.
2021-04-05 22:57:56 +02:00

216 lines
5.3 KiB
Python
Executable file

#!/usr/bin/env python3
# pylint: disable=invalid-name
# pylint: enable=invalid-name
"""Munin plugin to monitor Knot DNS server.
=head1 NAME
knot - monitor Knot DNS server statistics
=head1 APPLICABLE SYSTEMS
Systems with Knot DNS server installed.
=head1 CONFIGURATION
This plugin requires config:
[knot]
user root
=head1 AUTHOR
Kim B. Heino <b@bbbs.net>
=head1 LICENSE
GPLv2
=head1 MAGIC MARKERS
#%# family=auto
#%# capabilities=autoconf
=cut
"""
import os
import subprocess
import sys
import time
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:
output = subprocess.run(['knotc', '--force', 'stats'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False,
encoding='utf-8', errors='ignore').stdout
except FileNotFoundError:
return {}
# After server reboot output can be almost empty. Use cached results
# instead, needed for plugin config when using munin-async.
cachename = os.getenv('MUNIN_PLUGSTATE') + '/knot.state'
if len(output) > 2048:
with open(cachename, 'wt') as cache:
cache.write(output)
elif (
os.path.exists(cachename) and
os.stat(cachename).st_mtime > time.time() - 900
):
with open(cachename, 'rt') as cache:
output = cache.read()
# 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 (knot is not running)')
elif len(args) > 1 and args[1] == 'config':
print_config(values)
else:
print_values(values)
if __name__ == '__main__':
main(sys.argv)