создание collectd python freeradius плагина


Все мы любим мониторинг, особенно, когда он предоставляет полную информацию о сервисах. Так получилось, что на работе мы используем Graphite, а метрики в него засылаем с помощью collectd.

Иногда предоставленной функциональности collectd не хватает, и тогда на выручку приходят готовые плагины с гитхаба или самописные костыли. Сейчас я на примере простого плагина для сбора метрик freeradius расскажу как написать python плагин для collectd.

Для начала, определим конфиг плагина, например, в конфиге мы укажем, какой тип статистики мы будем собирать (не забудьте настроить радиус отображать статус), порт freeradius и пароль для подключения. Предполагается, что freeradius сервер запущен локально, т.е. там же где и collectd.

Вот пример конфига collectd

<LoadPlugin python>
    Globals true
</LoadPlugin>
<Plugin python>
    ModulePath "/usr/lib/collectd/plugins/python"
    Import "freeradius"
    <Module freeradius>
        Statistics_Type 1
        Host localhost
        Port 18121
        Secret adminsecret
    </Module>
</Plugin>

Теперь создадим сам модуль в /usr/lib/collectd/plugins/python/freeradius.py

Импортируем нужные нам модули

import signal
import re
import subprocess

Создадим функцию, которая прочитает конфиг

config = {
    'port': 18121,
    'secret': 'adminsecret',
    'host': 'localhost',
    'timeout': 1,
    'stat_type': 1
}


def configer(conf):
    for node in conf.children:
        if node.key == 'Host':
            config['host'] = node.values[0]
        if node.key == 'Port':
            try:
                config['port'] = int(node.values[0])
            except:
                collectd.warning("can not use provided port {0}"
                                 .format(node.values[0]))
        if node.key == 'Secret':
            config['secret'] = node.values[0]
        if node.key == 'Statistics_Type':
            try:
                config['stat_type'] = int(node.values[0])
            except:
                collectd.warning("can not use provided stats type {0}"
                                 .format(node.values[0]))

Напишем основную функцию, которая собирает метрики с помощью radclient

def get_metrics():

    # изменяем имена метрик
    def _convert_name(name):
        return name.replace('FreeRADIUS-Total-', '').replace('-', '_').lower()

    # выполняем команду для получения метрик
    cmd = ('echo "Message-Authenticator = 0x00, '
           'FreeRADIUS-Statistics-Type = {stat_type}, '
           'Response-Packet-Type = Access-Accept" | '
           'radclient -t {timeout} -x {host}:{port} status {secret}'
           .format(
                timeout=config['timeout'],
                port=config['port'],
                secret=config['secret'],
                host=config['host'],
                stat_type=config['stat_type']))
    child = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
    stdout, stderr = child.communicate()
    metrics = {}
    # метрики находятся не во всех строках, выберем нужные
    found_metrics = re.findall(r'(FreeRADIUS-Total.*) = (\d*)', stdout)
    for metric_couple in found_metrics:
        try:
            metrics[_convert_name(metric_couple[0])] = int(metric_couple[1])
        except:
            collectd.warning(
            "can not collect couple: {0}".format(', '.join(metric_couple)))
    return metrics

Обвязки для основной функции и отправки метрик в collectd. Все метрики у нас будут типа “gauge”.

def reader():
    metrics = get_metrics()
    for type, value in metrics.items():
        dispatch_value(type, value)


def dispatch_value(val_type, value, metric_type='gauge'):
    val = collectd.Values(plugin='freeradius')
    val.type = metric_type
    val.type_instance = val_type
    val.values = [value]
    val.dispatch()

Ну и вызов всего этого хозяйства. Если скрипт запущен вручную, то просто выведем информацию на stdout.

if __name__ == '__main__':
    print(get_metrics())
else:
    import collectd
    # это нужно, чтоб collectd правильно обрабатывал запуск radclient
    # http://giovannitorres.me/using-collectd-python-and-graphite-to-graph-slurm-partitions.html
    collectd.register_init(signal.signal(signal.SIGCHLD, signal.SIG_DFL))
    collectd.register_config(configer)
    collectd.register_read(reader)

Перезапускаем collectd для применения изменений:

sudo service collectd restart

Смотрим на метрики:

Стоит заметить, что все метрики — это счетчики, после перезапуска freeradius они обнуляются.

Исходники плагина вы сможете найти здесь: https://github.com/kshcherban/collectd-freeradius