Настройка мониторинга CPU для роутера Netcraze Ultra в Cacti




Когда домашняя сетевая лаборатория разрастается, а в качестве основного шлюза трудится многоядерный аппарат (в моем случае — трёхъядерный боевой Netcraze Ultra NC-1812), хочется видеть детальную картину загрузки процессора: какое именно ядро принимает на себя основной удар во время пиковых нагрузок (например, при шифровании VPN), а какое простаивает.

В этой статье я разберу полный путь: как всё же заставить Cacti собирать метрики по каждому отдельному процессорному потоку маршрутизатора, если производитель закрыл стандартные ветки OID.

Шаг 1. Глухая стена SNMP и диагностика поллеров

При развертывании системы мониторинга Cacti на микрокомпьютере Raspberry Pi 3 стандартный быстрый поллер Spine отлично отрабатывает и собирает дефолтные метрики. Однако возникла проблема с отрисовкой графика загрузки процессора (CPU) для моего роутера Netcraze Ultra. Как выяснилось, ветка Enterprise/Enchant OID на роутере оказалась полностью закрыта производителем, из-за чего Cacti не мог получить данные стандартными методами.

Проверка через терминал:

snmpwalk -v 2c -c public 192.168.1.1 .1.3.6.1.4.1.2021.11.9

Ответила коротко и ясно: No Such Object available on this agent at this OID. Полный срез дерева SNMP показал, что роутер на прошивке NDMS 5.1.3 охотно отдает статистику по интерфейсам, VLAN'ам и мостам, но напрочь отказывается делиться загрузкой CPU (как, в принципе, и оперативной памятью, но это уже другая история) — Enterprise/Enchant OID оказались полностью закрыты производителем.

В процессе диагностики была предпринята попытка переключить основной поллер Cacti со Spine на встроенный cmd.php. Однако это переключение не принесло результатов. Было принято решение вернуть высокопроизводительный Spine обратно для обслуживания дефолтных графиков, а проблему со статистикой от процессора решать кастомным путем.

Шаг 2. Подготовка роутера: OPKG и сырые данные (Jiffies)

Чтобы обойти аппаратные ограничения, потребовалось расширить возможности встроенной ОС роутера. На Netcraze Ultra NC-1812 был развернут и настроен менеджер пакетов OPKG (по подробной инструкции из базы знаний), после чего появилась возможность использовать легковесные утилиты.

Установленный SNMP из репозиториев также наотрез отказался считывать статистику из закрытых OID.

Заглянув в виртуальную файловую систему ядра (cat /proc/stat | grep '^cpu'), я и обнаружил нужные счетчики:

cpu  199171 123 2630346 46072841 250223 183381 1061205 0 0 0
cpu0 85838 60 704311 15381916 94996 35125 434311 0 0 0
cpu1 67436 42 967604 15344121 28097 40014 388076 0 0 0
cpu2 45897 20 958430 15346803 127128 108240 238817 0 0 0

/proc/stat отдает не готовые проценты, а накопительные тики процессора (jiffies). Чтобы вычислить реальную нагрузку, пришлось писать два скрипта /opt/bin/get_cpu.sh и /opt/bin/get_cpu_cores.sh (для снятия общей "по больнице" и для снятия статистики с каждого ядра), которые делают два замера с паузой в 2 секунды, высчитывают дельту активного времени и времени простоя (idle), после чего переводят результат в проценты:

Скрипты на роутере:

Общая загрузка (/opt/bin/get_cpu.sh):

#!/bin/sh

stat1=$(grep '^cpu ' /proc/stat)
sleep 2
stat2=$(grep '^cpu ' /proc/stat)

printf "%s\n%s\n" "$stat1" "$stat2" | awk '
{
    if (!seen) {
        prev_tot = $2+$3+$4+$5+$6+$7+$8
        prev_idl = $5
        seen = 1
    } else {
        tot = $2+$3+$4+$5+$6+$7+$8
        idl = $5
        d_tot = tot - prev_tot
        d_idl = idl - prev_idl
        
        usage = (d_tot > 0) ? 100 - int((d_idl * 100) / d_tot) : 0
        print usage
    }
}'

Поядерная статистика (/opt/bin/get_cpu_cores.sh):

#!/bin/sh

# Делаем два замера напрямую через grep, без cat
stat1=$(grep '^cpu[0-2] ' /proc/stat)
sleep 2
stat2=$(grep '^cpu[0-2] ' /proc/stat)

# Отдаем оба замера в один процесс awk, который сам посчитает дельту для каждого ядра
printf "%s\n%s\n" "$stat1" "$stat2" | awk '
{
    # Если видим ядро первый раз, сохраняем значения
    if (!seen[$1]++) {
        prev_tot[$1] = $2+$3+$4+$5+$6+$7+$8
        prev_idl[$1] = $5
    } else {
        # Если видим ядро второй раз (второй замер), считаем разницу
        tot = $2+$3+$4+$5+$6+$7+$8
        idl = $5
        d_tot = tot - prev_tot[$1]
        d_idl = idl - prev_idl[$1]
        
        usage = (d_tot > 0) ? int((d_tot - d_idl) * 100 / d_tot) : 0
        printf "%s=%d ", $1, usage
    }
}
END { print "" }'

Шаг 3. Архитектура передачи: сетевой демон через netcat и FIFO

Чтобы не зависеть от Cron и забирать данные «по требованию», на роутере был поднят сетевой слушатель через системный init.d. Для общей загрузки использовался порт 81, а для поядерной статистики был выделен порт 82.

В автозагрузку роутера (/opt/etc/init.d/S99local) прописал отказоустойчивую конструкцию с именованным каналом (FIFO), которая удерживает порты открытыми и мгновенно отдает данные по запросу:

#!/bin/sh

# Сервис общей статистики CPU на порту 81
rm -f /tmp/f_cpu && mkfifo /tmp/f_cpu
while true; do
    cat /tmp/f_cpu | /opt/bin/get_cpu.sh 2>&1 | netcat -l -p 81 >/tmp/f_cpu
done &

# Сервис поядерной статистики CPU на порту 82
rm -f /tmp/f_cores && mkfifo /tmp/f_cores
while true; do
    cat /tmp/f_cores | /opt/bin/get_cpu_cores.sh 2>&1 | netcat -l -p 82 >/tmp/f_cores
done &

На этом конфигурация роутера оказалась вполне достаточной, ведь одной из целей было не нагружать его CPU снятием загрузки своей же работы.

Шаг 4. Приемник на Raspberry Pi

На стороне Raspberry Pi (/usr/share/cacti/site/scripts/) были созданы скрипты-обертки get_netcraze_cpu.sh и get_netcraze_cores.sh, каждый из которых забирает данные с роутера со своего порта и форматирует их под требования Cacti:

cat /usr/share/cacti/site/scripts/get_netcraze_cpu.sh

#!/bin/bash

HOST_IP="192.168.1.1"
HOST_PORT="81"

if RESULT=$(exec 3<>/dev/tcp/"$HOST_IP"/"$HOST_PORT" && head -n 1 <&3) 2>/dev/null; then
    if [[ "$RESULT" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
        # Если роутер вернул ровно 0 или 0.0, подменяем на 0.0001 для Spine
        if (( $(echo "$RESULT == 0" | bc -l 2>/dev/null || [ "$RESULT" = "0" ]) )); then
            echo "cpu:0.0347"
        else
            echo "cpu:$RESULT"
        fi
    else
        echo "cpu:U"
    fi
else
    echo "cpu:U"
fi

exec 3>&-
exec 3<&-

cat /usr/share/cacti/site/scripts/get_netcraze_cores.sh

#!/bin/bash

HOST_IP="192.168.1.1"
HOST_PORT="82"

if RESULT=$(exec 3<>/dev/tcp/"$HOST_IP"/"$HOST_PORT" && head -n 1 <&3) 2>/dev/null; then
    FORMATTED=$(echo "$RESULT" | sed 's/=/:/g')
    
    if [[ -n "$FORMATTED" ]]; then
        echo "$FORMATTED"
    else
        echo "cpu0:U cpu1:U cpu2:U"
    fi
else
    echo "cpu0:U cpu1:U cpu2:U"
fi

exec 3>&-
exec 3<&-

Обязательно необходимо сделать их исполняемыми:

sudo chmod +x /usr/share/cacti/site/scripts/get_netcraze_cpu.sh
sudo chmod +x /usr/share/cacti/site/scripts/get_netcraze_cores.sh

Шаг 5. Настройка в интерфейсе Cacti: от скрипта к графику

Чтобы Cacti поняла, как работать с нашим скриптом-приемником на Raspberry Pi, и начала рисовать метрики, необходимо выстроить правильную логическую цепочку внутри веб-интерфейса. Весь процесс делится на четыре последовательных этапа (переводы на русский язык взяты из версии 1.2.30, в некоторые моменты эта локализация вызывает просто смех).

5.1. Создание Data Input Method (Метода ввода данных)

Сначала надо объяснить системе, откуда забирать цифры.

  • Перехожу в раздел Data Input Methods (Коллекции данных -> Метод Ввода Данных) и создаю новый метод +.
  • Задаю понятное имя: Netcraze Router Core Stats.
  • Выбираю Input Type (Тип ввода): Script/Command (Скрипт/Команда).
  • В поле пути к скрипту прописываю абсолютный путь: /usr/share/cacti/site/scripts/get_netcraze_cores.sh.
  • Сохраняю метод. В появившемся внизу блоке Output Fields (Поля вывода) поочередно добавляю три выходных поля: cpu0, cpu1 и cpu2.
  • Важный нюанс: при добавлении каждого поля обязательно надо активировать чекбокс Update RRD File (Обновить RRD файл). Это ключевой параметр, который разрешает Cacti писать полученные данные в базу.

5.2. Настройка шаблона Data Templates (Источник данных)

Поскольку я хочу видеть три независимых графика по каждому из ядер, мне необходимо создать три отдельных источника данных.

  • Перехожу в Шаблоны в Data Templates (Шаблоны -> Источник данных) и создаю шаблон для нулевого ядра с именем: Netcraze - Core 0 Usage.
  • В блоке привязки источника (Метод Ввода Данных) выбираю созданный ранее метод Netcraze Router Core Stats.
  • В Output Field (Источник данных -> Поле Вывода) привязываю этот шаблон жестко к переменной cpu0.
  • Тип данных источника выставляю в GAUGE (так как скрипт уже отдает готовые проценты) и жестко задаю лимиты: минимум 0, максимум 100.
  • Повторяю эту процедуру для ядер Core 1 и Core 2, привязывая их к полям cpu1 и cpu2 соответственно.

5.3. Создание Graph Templates (Шаблонов графиков) и настройка легенды

Теперь настраиваю визуальную часть.

  • Иду в Graph Templates (Шаблоны -> График) и создаю первый шаблон: Netcraze - CPU Core 0.
  • В секции элементов Graph Template Items (График Элементы шаблона) добавляю линию: выбираю мой источник cpu0, задаю приятный цвет (например, розовый) и выбираю тип графического элемента LINE1 или AREA.
    • Чтобы график был по-настоящему информативным, под ним нужно вывести точные цифры. Для этого добавляю элементы типа GPRINT:
    • 1. Вывод текущей загрузки: выбираю функцию консолидации LAST и задаю формат текста Current:
    • 2. Вывод средней загрузки: функция AVERAGE и формат текста Average:
    • 3. Вывод пиковой загрузки: функция MAX и формат текста Maximum:
  • Секрет красивой верстки легенды: в настройках последнего элемента (MAX) обязательно надо перевести тумблер Insert Hard Return (Вставить жесткий возврат) во включенное положение. Это перенесет строку и не даст тексту слипнуться.
  • Для более крупного отображения линии на графиках можно включить тумблер: Использование - автоматическое масштабирование.
  • Клонирую эту логику для создания шаблонов графиков Core 1 и Core 2 (мне приятно видеть одинаковый цвет для этих трех разных графиков, но настроить можно любой по желанию).

5.4. Финальная привязка к роутеру Devices (Устройства)

Остается только связать созданные шаблоны с моим физическим устройством.

  • Открываю раздел Devices (Управление -> Устройства) и нахожу в списке мой роутер Netcraze Ultra.
  • В блоке Associated Graph Templates (Имя шаблона графика - Добавить Шаблон Графика) выбираю из выпадающего списка три своих новых графических шаблона и нажимаю Add (Сохранить).
  • Справа вверху перехожу к блоку Create Graphs for this Host (Создание графиков для данного устройства), отмечаю чекбоксами появившиеся строки для каждого из трех ядер и нажимаю кнопку Create (Создать).

Именно в этот момент Cacti формирует задания для пуллера, и при следующем цикле опроса на диске генерируются RRD-файлы баз данных.

Шаг 6. Траблшутинг Cacti: магия кэша

Но в моем случае самостоятельно графики отрисовываться не захотели.
После привязки графиков к устройству, RRDtool упорно выдавал ошибку отсутствия .rrd файлов (Ошибка «No such file or directory»).

Проблема решилась в два шага:

  • Включение созданных источников данных в разделе Data Sources (Управление -> Источники) (как оказалось, по умолчанию они создаются в статусе Выключено).
  • Сброс кэша пуллера через веб-интерфейс (Утилиты -> Восстановление кэша Поллера) и принудительный запуск сборщика через консоль в Raspberry Pi:
sudo -u www-data php /usr/share/cacti/site/poller.php --force

После этого файлы успешно сгенерировались в /var/lib/cacti/rra/, а через время начала отрисовываться и долгожданная статистика.

Шаг 7. Оптимизация Load Average

Как только графики заработали, вылезла классическая проблема embedded-систем. Базовая линия графика Load Average на роутере (который к этому времени у меня уже рисовался стандартными методами) заметно поползла вверх.
Проблема крылась в архитектуре первоначальных скриптов (в данной статье не стал приводить их примеры), но:

# Фрагмент старого скрипта
IDLE=$(top -b -n 1 | grep "CPU:" | awk '{print $8}' | cut -d% -f1)

Использование тяжеловесной утилиты top в связке с циклами, cat и множественными пайпами (| grep | awk | cut) означало, что каждые пару минут роутер был вынужден порождать десятки новых процессов (fork/exec). Для слабых процессоров это серьезный удар по очереди выполнения, и она просто забивалась ожиданием ввода-вывода (I/O Wait) что и вызывало рост Load Average даже при фактическом простое CPU.

Переход на единый процесс awk, считывающий /proc/stat напрямую, кардинально разгрузил процессор маршрутизатора. Базовая линия Load Average снизилась в два раза и вернулась к нормальным спокойным значениям.

Заключение

Результатом всех манипуляций стало успешное добавление и стабильная отрисовка графика мониторинга CPU для роутера Netcraze Ultra NC-1812 в интерфейсе Cacti. Несмотря на ограничения прошивки оборудования и закрытые ветки OID, кастомный метод сбора дельт из /proc/stat позволил получить точную и своевременную картину общей утилизации процессора без лишней нагрузки на систему.

Тонкая настройка мониторинга сетевого оборудования — процесс медитативный. Зато теперь у меня в лаборатории есть полный контроль над утилизацией каждого процессорного потока трёхъядерного флагмана. Метрики пишутся корректно, графики не ломаются, а база знаний пополнилась еще одним отличным кейсом.


Мониторинг CPU для роутера Netcraze Ultra