Описание

Подсистема Performance Management (PM) в ноке отвечает за сбор метрик с оборудования (дискавери) и контроль их значения (пороги, threshold).

Умеет из коробки:

Умеет после использования напильника:

Следует признать, что PM находится в стадии развития. Заложены широкие возможности, но много требует доработки или реализации. Посмотрим на то, что есть сейчас.

Архитектура и компоненты

Архитектурно PM состоит из источника данных, сборщика (коллектора), транспорта, писателя и хранилища. Туда же, можно добавить интерфейс для отображения графиков и интерфейс для аналитики (отчётов).

Пройдёмся по компонентам подробнее:

Активатор

Запускает скрипты для сбора метрик.

Дискавери

За сбор метрик отвечает Periodic дискавери. Его задачи:

Центральная штука, в общем.

NSQD aka транспорт

ПО очереди для работы в реальном времени с гарантированной первой доставкой. Занимается передачей сообщений из одного конца очереди в другой. NSQD необходим в одном экземпляре на каждую ноду, NSQLOOKUPD минимум 1 (2 для отказоустойчивости).

Архитектурно NSQD состоит из 3 компонент: 2 обязательных и 1 опционального. 

  • процесс NSQD отвечает за работу очередей: принимает сообщения и передаёт их к месту назначения. Должен присутствовать на каждой НОДе, где есть процесс, пишущий в очередь. В случае невыполнения требования возможны потери сообщений.
  • процесс NSQLOOKUPD отвечает за поиск демонов NSQD с необходимыми топиками (очередями с сообщениями). Необходим по крайней мере в 1 экземпляре.
  • процесс NSQADMIND Это админка, к NSQD не обязательна, но позволяет просматривать различную статистику и управлять очередями (очищать, удалять и т.д.)

CHWRITER

Слушает приходящие сообщения (канал metrics) и, по мере их накопления, записывает в БД. Интервал записи поддаётся настройке. Рекомендуется держать его поближе к БД.

ClikcHouse (CH)

В качестве хранилища для метрик используется БД Clickhouse от Yandex


Сущности

К основным понятиям PM в НОКе добавляет несколько своих:

Байки от разработчика:
Представим, что у вас есть нетрезвый электрик, совковый такой, прям небритый, в кепке и с перегаром. По имени Петрович. И у него есть щиток, в котором 3 фазы.

А вы – начальник электрика Петровича со щитком, в котором 3 фазы. И вольтметром.
И, раз в 5 минут, посылаете его за щитком, мерять напряжение. Которое он пишет на бумажке и отдаёт вам. А вы рисуете по бумажке график. Чтобы все видели, вот.

Прибор у Петровича выдаёт метрику, допустим, Environment | Voltage, а фаз 3. Петрович добивает 0.33, поправляет кепку, идет, и пишет карандашиком на бумажке:

  • Environment | Voltage [“фаза1”] 222.3
  • Environment | Voltage [“фаза2”] 220.7
  • Environment | Voltage [“фаза3”] 222.7

Итого, имеем:

  1. Небритый электрик
  2. 3 фазы
  3. 1 вольтметр
  4. Environment | Voltage [“фаза1”] 222.3Environment | Voltage [“фаза2”] 220.7Environment | Voltage [“фаза3”] 222.7

Тепрерь представьте, что по пути он встретил Прасковью… и она говорит, что у нее тоже напряжение. Петрович поправляет кепку и пишет Environment | Voltage [“Прасковья”] 218.4.

Итого:

  1. Небритый электрик
  2. 3 фазы
  3. 1 вотльтметр
  4. Прасковья
  5. Environment | Voltage [“фаза1”] 222.3Environment | Voltage [“фаза2”] 220.7Environment | Voltage [“фаза3”] 222.7Environment | Voltage [“Прасковья”] 218.4

Но тут вы, находите в запасниках стола температуру, которую меряют градусником! и будет у вас вторая метрика Environment | Temperature. Петровича послали мерять её тоже. Он так и запишет - [“Щитовая”][“Прасковья подмышка”][“Прасковья ad-rectum"]

Резюме: Metric Type на вольтаж у тебя один, на температуру - еще один, где бы Петрович их не замерял: Environment | Wodka [“Петрович”] 0.33 или Environment | Drink [“Водка”, “Петрович”], а путь (Path) до них разный…


Настройка сбора метрик

Прервём поток букв(smile) и попробуем снабдить их картинками. В текущей реализации настройку сбора метрик очень упростили. Фактически, необходимо:

  1. Необходимо добавить метрики для сбора:
  2. Для интерфейсных метрик необходим работающий дискавери интерфейсов и назначить профили на интерфейсы, с которых необходимо собираться метрики. Автоматизации этого процесса посвящена статья
  3. Для SLA метрик необходим SLA дискавери и нзначить профиль SLA на необходимую пробу (пробы собираются SLA дискавери).
  4. Для снятия метрик по SNMP, необходим прописанный и рабочий SNMP_RO community в настройках объекта или назначенный профиль аутентификации с ним.

В случае, если назначен профиль аутентификации (Auth Profile) на устройство, то будет использован он, даже если поле SNMP RO в нём пустое (в этом случае система будет писать, что отсутствует community)

  1. Активировать Periodic Discovery и включить в нём сбор метрик: Service Activation -> Setup -> Object Profile, Periodic -> Metric
  2. Настроить пороги (опционально)

Пройдёмся по этим шагам подробнее:


Настрока Managed Object

Необходимо проверить ManagedObject:

В случае, если назначен профиль аутентификации (Auth Profile) на устройство, то будет использован он, даже если поле SNMP RO в нём пустое (в этом случае система будет писать, что отсутствует community)

Настройка Managed Object Profile

Идём в "Service Activation" -> Setup -> "Managed Object Profile". Выбираем существующий или создаём новый профиль. В нём, на вкладке Metrics нажимаем Add и выбираем метрику, которую необходимо собирать. Для этого достаточно заполнить 3 поля:


После, переходим на вкладку "Periodic Discovery" и активируем сбор метрик (устанавливаем галочка Enable и Metric). По необходимости, настраиваем интервал обхода.


Настройка сбора метрик с интерфейсов

Метрики для интерфейсов настраиваются в Inventory -> Setup -> "Interface Profiles". В нём необходимо аналогично Managed Object Profile добавить метрики.

После этого привязать профиль к интерфейсам. Это можно сделать в меню Inventory -> Interfaces. Необходимо в выпадающем списке выбрать MO и назначить профили. В это же меню можно попасть по кнопке Interfaces из "Service Activation" -> "Managed Object" -> <MO>.

Настройка Interfaces Classification Rules

Делается опционально-обязательно. Предназначены для автоматического назначения профила интерфейса на интерфейс. Конечно, можно вручную устанавливать профили интерфейсов, но это не продуктивно(smile) Поэтому можно расписать правила классификации. Делается это  из меню Inventory -> Setup -> "Interface Classification Rule".

Подробно работа с правилами расписана тут: Классификация интерфейсов.

Настройка Dashboard

Просмотр метрик идёт через Dashboard. Он доступен из интерфейса MO: в этом случае, он, автоматически, настраивается на отображение всех метрик, собираемых с данного MO или по адресу https://<AddressNOCWeb>/ui/grafana:

Если в интерфейсе Графаны вместо в значениях метрик выводится случайная величина: необходимо проверить настройки источника данных (DataSource) в графане.

Для настройки Источника данных (DataSource) необходим админский доступ к Графане (по умолчанию, он у пользователя admin). В левом верхнем углу кликнуть по значку графаны, далее опция "Data Sources". После этого нажать кнопку Add в верхнем меню.

При добавлении указываем следующие опции:

Проверить настройки можно кнопкой "Test Connection". После нажимаем Save. Наслаждаемся эффектом.

Добавление своих метрик

Для добавления метрики необходимо выполнить 2 шага:

  1. Создать метрику (Metric Type) (выполняется в веб-интерфейсе)
  2. Написать её конфигурацию (выполняется на активаторе)

Перед добавлением метрики есть 2  пути. Если метрика более менее стандартная (температура, загрузка процессора, интерфейсная) то можно клонировать существующую. В остальных случаях необходимо будет ответить на несколько вопросов:

  1. Необходимо понять, какие ключевые поля будут участвовать в сборе метрики. На этот вопрос можно не отвечать когда:
  2. В случае SNMP - всё просто. Открываем её в SNMP браузере и ключевые поля перечислены там. Ищем подходящий MetricScope или создаём свой.
  3. В случае CLI свободы больше. Надо подумать, какие имена/значения будут отличать метрику для данного объекта от других.

Если метрика относится к параметру объекта (н-р показатель какого-нибудь датчика) - используем Environment, если метрика для интерфейса (н-р размер входящего буфера) - то scope interface. Имя задаётся через прямую черту: | это позвляет отстраивать иерархию метрик. Рекомендуется пользоваться данным соглашением


Добавлени MetricTypes

После выбора Metric Scope идём в интерфейс создания метрики: PM -> Setup -> Metric Type. И нажимаем на кнопку Добавить, заполняем поля:

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

Настройка метрики на сборщике (активаторе)

В задаче активатору приходит список метрик (в виде имён), в зависимости от способа их получения: SNMP или не SNMP дальнейший путь расходится.

За формирование списка OIDов для съёма отвечает генератор. Для объяснения ему что к чему необходимо написать конфигурацию.
Конфигурация метрики:

{
  "$metric": "CPU | Usage",
  "$type": "oid",
  "oid": "1.3.6.1.4.1.2011.2.23.1.18.1.3.0",
  "type": "gauge",
  "scale": 1
}

Формулы для Scale можно написать в "<noc>/core/script/metrics.py".

def percent(value, total):
    """
    Convert absolute and total values to percent
    """
    if total:
        return float(value) * 100.0 / float(total)
    else:
        return 100.0

def percent_one(value, total):
    """
    Convert absolute and total values to percent
    """
    if total:
        return float(value) * 100.0 / (float(total) + float(value))
    else:
        return 100.0


Генераторы

Отвечают за формирование списка SNMP OIDов для сбора устройства на основе файла конфигурации. Указываются в директиве "$type" конфига.

В базовой системы реализованы следующие генераторы:

OID

Считывает OID, заданный в конфиг файле. Доступны параметры type и scale.

{
  "$metric": "CPU | Usage",
  "$type": "oid",
  "oid": "1.3.6.1.4.1.8886.1.1.1.4.1.0"
}

Может указать больше 1 OID’a, но должна быть задана функция, для рассчёта значения метрики:

{
  "$metric": "Memory | Usage",
  "$type": "oid",
  "oid": [
    "1.3.6.1.4.1.2021.4.5.0",
    "1.3.6.1.4.1.2021.4.6.0"
  ],
  "scale": "percent_usage"
}


Сами функции находят в файле core/script/metrics.py
percent_usage(value, total):
percent_invert(value, total):
sum(*args):
subtract(*args):
is1(x):

OIDS

Позволяет перечислять OID’ы списком с указанием path. Полезен, если метрика векторная, но сами значения наперёд известны и неизменны.

Пример конфиг файла:

{
  "$metric": "Environment | Sensor Status",
  "$type": "oids",
  "oids": [{
    "oid": "1.3.6.1.4.1.41752.5.15.1.1.0",
    "path": ["","","", "Door"],
    "$type": "oid",
    "type": "gauge",
    "scale": 1
  },{
    "oid": "1.3.6.1.4.1.41752.5.15.1.7.0",
    "path": ["","","", "Key_State"],
    "$type": "oid",
    "type": "gauge",
    "scale": 1
  }
  ]}

Здесь появляется дополнительное свойство - path. Его формат можно уточнить в MetricScope. Например, для Environment будут:




CAPS

Специфичный генератор. Проверяет Capabilities, если присутствует - отрабатывает соответствующее правило
! для платформы и версии

CAPINDEX и CAPLIST

При налачии capabilities в специальном формате, позволяет сгенерировать SNMP OID по шаблону.
Capindex - Caps в виде числа, Caplist в виде чисел, разделённых символом.

MATCHERS

Реализует возможность выбора SNMP OIDов на основании платформы/версии ПО оборудования через конфигурацию. Конфигурация для метрик распологается в файликах из папки snmp_metrics профиля. Теперь в них можно применять генератор match:

{
   "$metric": "XXXXX",
   "$type": "match",
   "$matchers": {
     // Локальные матчеры
     // По возможности стоит выносить в платформы
     "is_3328": {
      "platform": {
        "$regex": "3328"
            }
        },
      "is_2328": {
      "platform": {
        "$regex": "2328"
            }
        }
    }
    },
   "$match": [
        {
            "$match": "is_3328",  // Попадает только если сработал матчер из $matchers или из профиля (if getattr(self, name))
            "$type": "oid",
            "..."
        },
        {
           // Нет матчера, срабатывает всегда
            "$type": "oid",
            ...
        }
   ]
   }
}

Блок matchers описывает структуру по которой будет проходить проверку версия и платформа оборудования. На выходе будет совпало или нет. Сами OIDы описываются в разделе $match в виде списка. При работе проходим последовательно список и производим проверку соотв матчера из блока matchers.

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

Н-р для профиля Huawei.VRP есть стековая серия коммутаторов - 5328, коммутаторы доступа 2328 и остальные. Для стэков есть отдельные OID на каждый коммутатор в стеке и процессор в нём. Для 2328 используюется устаревшая ветка OID. Поэтому, получаем:

{
  "$metric": "CPU | Usage",
  "$type": "match",
  "$matchers": {
    "is_5328": {
      "platform": {
        "$regex": "5328"
            }
        },
    "is_2328": {
      "platform": {
        "$regex": "2328"
            }
        }
    },
  "$match": [{
    "$match": "is_2328",
    "oid": "1.3.6.1.4.1.2011.2.23.1.18.1.3.0",
    "path": ["","","", "Current_Load"],
    "$type": "oid",
    "type": "gauge",
    "scale": 1
  },{
    "$match": "is_5328",
    "$type": "slot",
    "oid": "1.3.6.1.4.1.2011.6.3.4.1.2.{{ hwSlotIndex }}"
   },{
    "$type": "oid",
    "oid": "1.3.6.1.4.1.2011.6.3.4.1.3.0.0.0",
    "type": "gauge",
    "scale": 1
  }]
}

Генератор профиля

Есть возможность написать свой генератор, если не подходит какой-либо из стандартных. Конфиг, при этом выглядит так:

{
  "$metric": "CPU | Load | 1min",
  "$type": "slot",
  "oid": "1.3.6.1.4.1.2011.6.3.4.1.3.{{ hwSlotIndex }}"
}

В данном случае в качестве OID’a записывается шаблон. Заместо переменной {{ hwSlotIndex }} будет подставлено значение из генератора. В поле $type записывается имя созданного генератора (уникально в пределах профиля). Сам генератор помещается в скрипт get_metrics соответствующего профиля. Н-р:

from noc.sa.profiles.Generic.get_metrics import Script as GetMetricsScript
from noc.sa.profiles.Generic.get_metrics import OIDRule
from noc.core.mib import mib

class SlotRule(OIDRule):
    # Имя генератора, должно совпадать с параметром $type в конфиге
    name = "slot"

    def iter_oids(self, script, metric):
        """
        Основной метод, должен вернуть переменные для подстановки в шаблон и путь (path)
        """
        sysSlotIndex = [0]
        i = 1
        r = {}
        # Здесь мы используем значение CAPS для определения правильного числа коммутаторов в стэке
        if script.has_capability("Stack | Member Ids"):
            sysSlotIndex = [int(index) for index in script.capabilities["Stack | Member Ids"].split(" | ")]
        elif script.has_capability("Stack | Members"):
            # Индексы коммутаторов в стеке делаем списком
            sysSlotIndex = range(1, script.capabilities["Stack | Members"] + 1)
        else:
            sysSlotIndex = [1]

        for ms in sysSlotIndex:
            r[str(i)] = "%d" % ms
            # r[str(i)] = {"healthModuleSlot": ms}
            i += 1

        for i in r:
            """
            В цикле обходим индексы и формируем переменную `hwSlotIndex`, для получения нормального OID'a используем метод `self.expand` передавая ему словарь с переменными и шаблон OID (`self.oid`)
            """
            if self.is_complex:
                # Это проверка на наличие нескольких OID (если их необходимо передавать в функцию для расчёта)
                gen = [mib[self.expand(o, {"hwSlotIndex": r[i]})] for o in self.oid]
                path = ["0", "0", i, ""] if "CPU" in metric.metric else ["0", i, "0"]
                if gen:
                    # self.type и self.scale - берутся из конфига. При необходимости, можно задавать другие параметры и получать к ним доступ здесь
                    yield tuple(gen), self.type, self.scale, path
            else:
                oid = mib[self.expand(self.oid, {"hwSlotIndex": r[i]})]
                path = ["0", "0", i, ""] if "CPU" in metric.metric else ["0", i, "0"]
                if oid:
                    yield oid, self.type, self.scale, path


Сбор метрик по CLI

В случае сбора метрик по CLI необходимо писать реализацию полноценного скрипта get_metrics для работы по CLI.

Для примера, возьмём скрипт сбора сессий пользователей для профиля Juniper.JunOSe

from noc.sa.profiles.Generic.get_metrics import Script as GetMetricsScript
from noc.lib.text import parse_table

class Script(GetMetricsScript):
    name = "Juniper.JUNOSe.get_metrics"

    CLI_METRICS = set(["Subscribers | Summary"])
    # Полезно указывать, какие метрики скрипт может собирать. Это исключит его запуск при сборе других метрик.

    def collect_profile_metrics(self, metrics):
        # Основной метод. При вызывается при старте скрипта 
        if self.has_capability("BRAS | PPTP"):
            self.logger.debug("Merics %s" % metrics)
            # Проверяем, что есть соответствующий CAPS и метрики, которые мы можем собрать
            if self.CLI_METRICS.intersection(set(m.metric for m in metrics)):
                # Запускаем функцию сбора. Делить скрипт на функции необязательно, но полезно для удобочитаемости
                self.collect_subscribers_metrics(metrics)

    def collect_subscribers_metrics(self, metrics):
        # if not (self.ALL_SLA_METRICS & set(metrics)):
        #     return  # NO SLA metrics requested
        ts = self.get_ts()
        # Получаем время (timestamp) и запускаем функцию сбора значений метрик с устройства
        m = self.get_subscribers_metrics()
        for bv in metrics:
            # обходим метрики (в данной переменной содержатся все метрики переданные для сбора)
            if bv.metric not in self.CLI_METRICS:
                # Если мы не можем собирать метрику - то пропускаем
                continue
            for slot in m:
                # set_metrics передаёт собранную метрику для отдачи дискавери.
                self.set_metric(
                    id=bv.id,
                    metric=bv.metric,
                    value=m[slot],
                    ts=ts,
                    path=["0", slot, ""]
                )

    def get_subscribers_metrics(self):
        """
        Returns collected subscribers metric in form
        slot id -> {
            rtt: RTT in seconds
        }
        :return:
        """
        v = self.cli("show subscribers summary slot")
        v = v.splitlines()[:-2]
        v = "\n".join(v)
        r_v = parse_table(v)
        if len(r_v) < 3:
            return {}
        # r = defaultdict(dict)
        r = dict(r_v)
        return r


Что-то пошло не так (Отладка)

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

Для выяснение причин отсутствия графиков имеет смысл повторно пройти все этапы настройки и проверить, не пропущена ли где запятая. Если таковую найти не удалось - идём проверять по порядку участия в PM:

Проверка выполненных настроек и дискавери.

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

|metrics]  Collecting metrics
|metrics]  SLA not configured, skipping SLA metrics
|metrics]  SLA metrics are not configured. Skipping
|metrics]  Spooling 14 metrics

Если при этом метрик мы не видим (на графиках), то смотрим в сторону писателя (chwriter) и интерфейса графаны.

Если мы видим что-то, навроде этого:

|metrics]  Collecting metrics
|metrics]  SLA not configured, skipping SLA metrics
|metrics]  SLA metrics are not configured. Skipping
|metrics]  No metrics found

No metrics found - То имеет смысл пойти разбираться что не так в плане доступа к устройству по SNMP.

Если же мы видим слова:

Проверка реквизитов доступа.

Необходимо проверить, что устройство доступно с активатора по SNMP. Это делается путём запуска скрипта

#./noc script get_uptime MONAME

Если с устройства успешно считался Uptime, то доступ по SNMP есть. И необходимо проверить capabilities на страничке с устройством.

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

Проверка работы дискавери

Проверка работы дискавери осуществляется командой: 


#./noc discovery --debug run periodic MONAME


У неё достаточно длинный вывод, в нём необходимо зафиксировать несколько моментов:

Collected CH data:
Fields: %s interface.date.ts.managed_object.path.discards_out.errors_in.load_in.load_out.status_oper
2017-10-30      2017-10-30 04:03:51     6119069133381749519     ['','','','Ethernet1/16']       0       0       56209   2822339 1
Fields: %s memory.date.ts.managed_object.usage
2017-10-30      2017-10-30 04:03:51     6119069133381749519     95

Проверка писателя.

Достаточно заглянуть в его лог. Там должны быть сообщения о записи метрик:

[chwriter] [interface.date.ts.managed_object.path.discards_in.errors_out.load_in.load_out.packets_in.packets_out.status_admin.status_oper] 58 records sent in 11.94ms
[chwriter] [ping.date.ts.managed_object.rtt.attempts] 86 records sent in 2.75ms
[chwriter] [interface.date.ts.managed_object.path.discards_out.errors_in.load_in.load_out.status_oper] 215 records sent in 9.37ms
[nsq.client] [stage-wrk:4150:chwriter:chwriter] received heartbeat
[chwriter] Feeding speed: 38.10records/sec, active channels: 11, buffered records: 284
[chwriter] [memory.date.ts.managed_object.path.usage_5s] 1 records sent in 5.35ms
[chwriter] [cpu.date.ts.managed_object.path.load_5s] 1 records sent in 2.81ms

Если есть какие-то проблемы с записью в БД в логе будут ругательные сообщения.

Проверка CH

Для этого необходимо подключиться к БД и повыполнять запросы к данным.
Команда для подключения: 

clickhouse-client -h 0.0.0.0, если не настрены права, или clickhouse-client -u USERNAME --password PASSWORD -h 0.0.0.0
Далее:
:) use noc
:) show tables
:) select * from interface order by ts desc limit 10


Посмотреть отображаются ли данные и время последних записей. Если в данных пусто, а лог писателя говорит что они записаны… То необходимо подсмотреть в логе сервера ClickHouse

Отладка скрипта сбора метрик

Чтобы проверить, нет ли ошибки в скриптах, можно воспользоваться механизмом отладки, только, дополнительно передать в скрипт парметры. Делается это так:

# ./noc script --debug Cisco.IOS.get_metrics "xxx-sw61" metrics:='[{"metric": "Environment | Voltage", "id": 0}]'
.....
 Result: [{'scale': 1.0, 'name': 'Memory | Usage', 'tags': {'object': None}, 'ts': 1469865495320386816, 'value': 49.583421519590374, 'type': 'gauge'}]


Уточнения