Source code for duct.sources.munin
"""
.. module:: munin
:platform: Any
:synopsis: Provides MuninNode source which can get events from the
munin-node protocol.
.. moduleauthor:: Colin Alston <colin@tamvera.com>
"""
import asyncio
from zope.interface import implementer
from duct.interfaces import IDuctSource
from duct.objects import Source
from duct.aggregators import Counter, Counter32
[docs]
class MuninClient:
"""asyncio-based munin-node client"""
def __init__(self, host, port):
self.host = host
self.port = port
self.reader = None
self.writer = None
[docs]
async def connect(self):
"""Open connection to munin-node."""
self.reader, self.writer = await asyncio.open_connection(
self.host, self.port)
# Read the banner line
await self.reader.readline()
[docs]
async def send_command(self, command, multiline=False):
"""Send a command to munin-node and return the response."""
self.writer.write((command + '\n').encode())
await self.writer.drain()
if multiline:
lines = []
while True:
line = (await self.reader.readline()).decode().rstrip('\n')
if line.startswith('#'):
continue
if line == '.':
break
lines.append(line)
return lines
else:
while True:
line = (await self.reader.readline()).decode().rstrip('\n')
if not line.startswith('#'):
return line
[docs]
async def disconnect(self):
"""Close connection to munin-node."""
if self.writer:
self.writer.close()
try:
await self.writer.wait_closed()
except Exception:
pass
[docs]
@implementer(IDuctSource)
class MuninNode(Source):
"""Connects to munin-node and retrieves all metrics
:param host: munin-node hostname (probably localhost)
:type host: str.
:param port: munin-node port (probably 4949)
:type port: int.
**Metrics:**
:(service name).(plugin name).(keys...): A dot separated tree of
munin plugin keys
"""
[docs]
async def get(self):
host = self.config.get('host', 'localhost')
port = int(self.config.get('port', 4949))
client = MuninClient(host, port)
await client.connect()
# Announce capabilities
await client.send_command('cap multigraph')
listout = await client.send_command('list')
plug_list = listout.split()
events = []
for plug in plug_list:
config = await client.send_command(f'config {plug}', True)
plugin_config = {}
for r in config:
name, val = r.split(' ', 1)
if '.' in name:
metric, key = name.split('.')
if key in ['type', 'label', 'min', 'info']:
plugin_config[f'{plug}.{metric}.{key}'] = val
else:
if name == 'graph_category':
plugin_config[f'{plug}.category'] = val
category = plugin_config.get(f'{plug}.category', 'system')
metrics = await client.send_command(f'fetch {plug}', True)
for m in metrics:
name, val = m.split(' ', 1)
if name != 'multigraph':
metric, key = name.split('.')
base = f'{plug}.{metric}'
m_type = plugin_config.get(f'{base}.type', 'GAUGE')
try:
val = float(val)
except ValueError:
continue
info = plugin_config.get(f'{base}.info', base)
prefix = f'{category}.{base}'
if m_type == 'COUNTER':
events.append(self.createEvent('ok', info, val,
prefix=prefix,
aggregation=Counter32))
elif m_type == 'DERIVE':
events.append(self.createEvent('ok', info, val,
prefix=prefix,
aggregation=Counter))
else:
events.append(self.createEvent('ok', info, val,
prefix=prefix))
await client.disconnect()
return events