Metric#

Добавлено в версии 1.12.0.

Модуль ngx_stream_metric_module позволяет создавать вычисляемые в реальном времени произвольные метрики для потокового (TCP и UDP) трафика. Значения таких метрик сохраняются в разделяемой памяти и отображаются в реальном времени в ветке API /status/stream/metric_zones/. Поддерживаются различные типы агрегации данных (счетчики, гистограммы, скользящие средние и др.) с группировкой по произвольным ключам.

Схема ответа приведена в справочнике API зон потоковых метрик.

Пример конфигурации#

Подсчет соединений по адресу клиента:

stream {
    metric_zone connections:1m count;

    server {
        listen 12345;

        metric connections $remote_addr on=connect;
    }
}

http {
    server {
        listen 80;

        location /status/ {
            allow 127.0.0.1;
            deny all;
            api /status/;
        }
    }
}

Если выполнить соединение с портом 12345:

$ nc 127.0.0.1 12345 </dev/null

В реальном времени происходит обновление метрики connections:

{
    "stream": {
       "metric_zones": {
           "connections": {
               "discarded": 0,
               "metrics": {
                   "127.0.0.1": 1
               }
           }
       }
    }
}

Директивы#

metric#

Синтаксис

metric название ключ=значение [on=connect| preread| end];

По умолчанию

Контекст

stream, server

Подсчитывает значение метрики с указанным названием зоны разделяемой памяти.

Параметры:

  • ключ — произвольная строка (часто переменная), по которой группируются значения. Максимальная длина — 255 байт; более длинные ключи обрезаются. В выводе API Angie добавляет ... к любому ключу длиной 255 байт, даже если до сохранения его длина составляла ровно 255 байт. Ключ может сам содержать символы = — значением считается только текст после последнего =, поэтому все, что стоит перед ним (включая вложенные =), становится ключом;

  • значение — число (может быть переменной), обрабатываемое выбранным режимом. Если пропущено, считается 0. Если параметр невозможно преобразовать в число, считается 1;

  • on — необязательный параметр, указывающий, в какой момент обработки сессии происходит подсчет метрики:

    • Если on=connect, подсчет происходит на фазе Pre-access, до чтения каких-либо данных от клиента;

    • Если on=preread, подсчет происходит один раз за сессию — в момент, когда клиенту отправляются первые данные, полученные от проксируемого с помощью proxy_pass сервера или сформированные директивой return; к этому моменту такие модули, как proxy_protocol, ssl_preread и mqtt_preread, уже обработали начальные данные клиента;

    • Если on=end (по умолчанию), подсчет происходит на фазе Log, когда сессия завершается.

Пример использования:

metric sessions $remote_addr=$session_time on=end;

Примечание

Метрики с пустым ключом или неправильной парой ключ=значение не учитываются. Пропущенное значение учитывается как 0:

metric foo $bar;  # Эквивалентно $bar=0

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

Примечание

Важно помнить, что переменные вычисляются в разных фазах. Например, невозможно использовать $upstream_bytes_sent (количество байт, отправленных апстриму) при on=connect (до того, как соединение куда-либо проксировано).

metric_complex_zone#

Синтаксис

metric_complex_zone название:размер [expire=on| off] [discard_key=название] { ... }

По умолчанию

Контекст

stream

Определяет составную метрику — набор метрик с независимыми режимами. Каждая строка в теле блока задает название подметрики, режим и необязательные параметры режима. Размер зоны должен составлять не менее восьми системных страниц памяти.

Пример использования:

metric_complex_zone sessions:1m expire=on discard_key="old" {
    # название подметрики   режим работы    параметры
    min_time                min;
    avg_time                average exp     factor=60;
    max_time                max;
    total                   count;
}

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

{
    "discarded": 3,
    "metrics": {
        "ключ1": {
            "min_time": 20,
            "avg_time": 50,
            "max_time": 80,
            "total": 2
        },
        "old": {
             "min_time": 3,
             "avg_time": 40,
             "max_time": 152,
             "total": 80
        }
    }
}

metric_zone#

Синтаксис

metric_zone название:размер [expire=on| off] [discard_key=название] режим [параметры];

По умолчанию

Контекст

stream

Создает зону разделяемой памяти указанного размера с именем название, в которой хранятся метрики. Имя зоны является узлом в ветке /status/stream/metric_zones/. Размер зоны должен составлять не менее восьми системных страниц памяти.

Параметры:

  • expire=<on|off> — поведение при переполнении зоны:

    • Если on, самые старые метрики по времени обновления отбрасываются, освобождая память под поступающие;

    • Если off (по умолчанию) — отбрасываются поступающие метрики, сохраняя информацию об установленных.

  • discard_key=<название> — задает метрику с ключом название, в которой сохраняются значения отброшенных метрик. По умолчанию такая метрика не создается. Зарезервированный ключ нельзя обновлять вручную.

  • режим — алгоритм обработки значений (см. раздел Режимы работы);

  • параметры — дополнительная настройка выбранного режима (например, factor для average exp).

Пример использования:

metric_zone session_time:1m max;

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

{
    "discarded": 0,
    "metrics": {
        "ключ1": 123,
        "ключ2": 10.5
    }
}

Примечание

В зоне размером 1 мегабайт при размере ключа 39 байт и с одним режимом метрики может разместиться около 8 тысяч записей с уникальным ключом.

Режимы работы#

Список доступных режимов работы метрик:

  • count — счетчик обращений;

  • gauge — стрелка (инкремент/декремент);

  • last — последнее поступившее значение;

  • min — минимальное значение;

  • max — максимальное значение;

  • average exp — скользящее среднее (параметр factor);

  • average mean — среднее за окно (параметры window и count);

  • histogram — распределение по "бакетам" (перечень пороговых значений).

В примерах ниже используется конечная точка /status/ из примера конфигурации. Результат каждого примера доступен по адресу /status/stream/metric_zones/<зона>/metrics/.

count#

Каждое обновление метрики увеличивает счетчик на 1 независимо от переданного значения.

Значение по умолчанию — 0.

Примеры:

metric_zone count:1m count;

# В качестве составной метрики:
#
# metric_complex_zone count:1m {
#     some_metric_name  count;
# }

server {
    listen 12345;

    metric count KEY;
}

Обновление метрики:

$ nc 127.0.0.1 12345 </dev/null
$ nc 127.0.0.1 12345 </dev/null
$ nc 127.0.0.1 12345 </dev/null
$ nc 127.0.0.1 12345 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": 4
}

gauge#

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

Значение по умолчанию — 0.

Примеры:

metric_zone gauge:1m gauge;

# В качестве составной метрики:
#
# metric_complex_zone gauge:1m {
#     some_metric_name  gauge;
# }

server {
    listen 12351;
    metric gauge KEY;
}

server {
    listen 12352;
    metric gauge KEY=5;
}

server {
    listen 12353;
    metric gauge KEY=-5;
}

server {
    listen 12354;
    metric gauge KEY=8;
}

Обновление метрики:

$ nc 127.0.0.1 12351 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": 0
}

Дальнейшие обновления:

$ nc 127.0.0.1 12352 </dev/null
$ nc 127.0.0.1 12353 </dev/null
$ nc 127.0.0.1 12354 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": 8
}

last#

Хранит последнее полученное значение без какой-либо агрегации. Если значение опущено, используется 0.

Примеры:

metric_zone last:1m last;

# В качестве составной метрики:
#
# metric_complex_zone last:1m {
#     some_metric_name  last;
# }

server {
    listen 12361;
    metric last KEY;
}

server {
    listen 12362;
    metric last KEY=8000;
}

server {
    listen 12363;
    metric last KEY=37;
}

server {
    listen 12364;
    metric last KEY=-3.5;
}

Обновление метрики:

$ nc 127.0.0.1 12361 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": 0
}

Дальнейшие обновления:

$ nc 127.0.0.1 12362 </dev/null
$ nc 127.0.0.1 12363 </dev/null
$ nc 127.0.0.1 12364 </dev/null

Ожидаемое значение метрики в API:

{
   "KEY": -3.5
}

min#

Сохраняет минимальное из двух значений — уже сохраненного и нового.

Примеры:

metric_zone min:1m min;

# В качестве составной метрики:
#
# metric_complex_zone min:1m {
#     some_metric_name  min;
# }

server {
    listen 12371;
    metric min KEY=42.999;
}

server {
    listen 12372;
    metric min KEY=-512;
}

server {
    listen 12373;
    metric min KEY=1;
}

Обновление метрики:

$ nc 127.0.0.1 12371 </dev/null
$ nc 127.0.0.1 12372 </dev/null
$ nc 127.0.0.1 12373 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": -512
}

max#

Сохраняет наибольшее из двух значений — уже сохраненного и нового.

Примеры:

metric_zone max:1m max;

# В качестве составной метрики:
#
# metric_complex_zone max:1m {
#     some_metric_name  max;
# }

server {
    listen 12381;
    metric max KEY=42.999;
}

server {
    listen 12382;
    metric max KEY=-512;
}

server {
    listen 12383;
    metric max KEY=1;
}

Обновление метрики:

$ nc 127.0.0.1 12381 </dev/null
$ nc 127.0.0.1 12382 </dev/null
$ nc 127.0.0.1 12383 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": 42.999
}

average exp#

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

Принимает необязательный параметр factor=<число> — коэффициент, по которому установленное значение учитывается при расчете среднего. Допустимы целые числа от 0 до 99. По умолчанию — 90.

Чем больше коэффициент, тем сильнее новые значения влияют на среднее. Если указать 90, то будет взято 90% от нового значения и лишь 10% от предыдущего.

Примеры:

metric_zone avg_exp:1m average exp factor=60;

# В качестве составной метрики:
#
# metric_complex_zone avg_exp:1m {
#     some_metric_name  average exp  factor=60;
# }

server {
    listen 12391;
    metric avg_exp KEY=100;
}

server {
    listen 12392;
    metric avg_exp KEY=200;
}

server {
    listen 12393;
    metric avg_exp KEY=0;
}

server {
    listen 12394;
    metric avg_exp KEY=8;
}

server {
    listen 12395;
    metric avg_exp KEY=30;
}

Обновление метрики:

$ nc 127.0.0.1 12391 </dev/null
$ nc 127.0.0.1 12392 </dev/null
$ nc 127.0.0.1 12393 </dev/null
$ nc 127.0.0.1 12394 </dev/null
$ nc 127.0.0.1 12395 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": 30.16
}

average mean#

Вычисляет среднее арифметическое. Принимает необязательные параметры window=<off|время> и count=<число>, задающие соответственно интервал времени и объем выборки для усреднения. По умолчанию: window=off (учитывается вся выборка) и count=10.

Примечание

Например, window=5s будет учитывать только события последних 5 секунд. Параметр window не может принимать значение 0. Параметр count=число управляет размером выборки (закэшированных значений) для более плавного подсчета среднего.

Примеры:

metric_zone avg_mean:1m average mean window=5s count=8;

# В качестве составной метрики:
#
# metric_complex_zone avg_mean:1m {
#     some_metric_name  average mean  window=5s count=8;
# }

server {
    listen 12401;
    metric avg_mean KEY=0.1;
}

server {
    listen 12402;
    metric avg_mean KEY=0.4;
}

server {
    listen 12403;
    metric avg_mean KEY=10;
}

server {
    listen 12404;
    metric avg_mean KEY=1;
}

Обновление метрики:

$ nc 127.0.0.1 12401 </dev/null
$ nc 127.0.0.1 12401 </dev/null
$ nc 127.0.0.1 12402 </dev/null
$ nc 127.0.0.1 12403 </dev/null
$ nc 127.0.0.1 12404 </dev/null
$ nc 127.0.0.1 12404 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": 2.1
}

Если подождать 5 секунд с момента последнего обновления метрики, то ожидаемое значение метрики в API:

{
    "KEY": 0
}

histogram#

Создает набор "бакетов", увеличивая соответствующий счетчик, если новое значение не превосходит порога бакета. Формат параметров — список числовых порогов. Полезен для анализа распределения, например длительности сессий.

В качестве обязательных параметров передаются числа — предельные значения бакетов, обычно перечисляемые в порядке возрастания.

Примечание

Значение бакета inf или +Inf можно использовать для захвата всех значений, превышающих максимальный бакет.

Примеры:

metric_zone hist:1m histogram 0.1 0.2 0.5 1 2 inf;

# В качестве составной метрики:
#
# metric_complex_zone hist:1m {
#     some_metric_name  histogram  0.1 0.2 0.5 1 2 inf;
# }

server {
    listen 12411;
    metric hist KEY=0.25;
}

server {
    listen 12412;
    metric hist KEY=2;
}

server {
    listen 12413;
    metric hist KEY=1000;
}

Обновление метрики:

$ nc 127.0.0.1 12411 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": {
        "0.1": 0,
        "0.2": 0,
        "0.5": 1,
        "1": 1,
        "2": 1,
        "inf": 1
    }
}

Дальнейшее обновление:

$ nc 127.0.0.1 12412 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": {
        "0.1": 0,
        "0.2": 0,
        "0.5": 1,
        "1": 1,
        "2": 2,
        "inf": 2
    }
}

Дальнейшее обновление:

$ nc 127.0.0.1 12413 </dev/null

Ожидаемое значение метрики в API:

{
    "KEY": {
       "0.1": 0,
       "0.2": 0,
       "0.5": 1,
       "1": 1,
       "2": 2,
       "inf": 3
    }
}

Встроенные переменные#

Для каждой метрики создаются переменные:

  • $metric_<name>

  • $metric_<name>_key

  • $metric_<name>_value

Для составной метрики добавляется переменная:

  • $metric_<name>_value_<metric>

$metric_<name>#

Аналогично директиве metric, можно использовать сеттер переменной $metric_<name> для подсчета метрики. Подсчет происходит в момент установки переменной — например, через директиву set, которая вычисляется на фазе Pre-access (той же, что и on=connect), либо программно из модуля njs, например в обработчике js_access.

Сеттер принимает ключ с необязательным суффиксом =значение. Ключ от значения отделяет последний символ =; без суффикса используется значение 0. Ключ и значение могут состоять из текста, переменных или их комбинаций. Такие же правила разбора использует директива metric.

Пример использования:

stream {
    metric_zone hits:1m count;

    # В этот момент добавилась переменная $metric_hits

    server {
        listen 12345;

        metric hits client=1 on=connect;
    }

    server {
        listen 12346;

        set $metric_hits client=1;

        return "$metric_hits\n";
    }
}

После трех соединений с портом 12345 и одного соединения с портом 12346:

$ nc 127.0.0.1 12345 </dev/null
$ nc 127.0.0.1 12345 </dev/null
$ nc 127.0.0.1 12345 </dev/null
$ nc 127.0.0.1 12346 </dev/null
client=4

Примечание

Чтение переменной $metric_<name> возвращает ключ вместе с текущим подсчитанным значением метрики, в виде ключ=значение, а не ту строку, которая была присвоена буквально. В примере выше буквально присваивается значение client=1, но поскольку режим count игнорирует присвоенное значение и просто увеличивает счетчик при каждом обновлении, чтение переменной $metric_hits после этого возвращает client=4 — новое значение счетчика, уже учитывающее три предыдущих обновления с порта 12345.

Также изменится значение, хранящееся в переменной $metric_<name>_key, на указанный ключ.

$metric_<name>_key и $metric_<name>_value#

Переменные $metric_<name>_key и $metric_<name>_value задают соответственно ключ и значение. Обновление метрики происходит в момент установки значения $metric_<name>_value, если ключ в $metric_<name>_key уже задан.

Примечание

Для составной метрики значения подметрик в переменной $metric_<name>_value объединены при помощи разделяющего символа ", ".

Пример использования:

stream {
    metric_zone level:1m gauge;

    # В этот момент добавились переменные $metric_level, $metric_level_key
    # и $metric_level_value

    metric_complex_zone stats:1m {
        count count;
        min   min;
        avg   average exp;
    }

    # В этот момент добавились переменные $metric_stats, $metric_stats_key
    # и $metric_stats_value

    server {
        listen 12345;

        metric level sensor=10 on=connect;
    }

    server {
        listen 12346;

        set $metric_level_key   "sensor";
        set $metric_level_value 5;

        # Либо: set $metric_level sensor=5;

        return "Updated with '$metric_level'\nValue='$metric_level_value'\n";
    }

    server {
        listen 12347;

        set $metric_stats_key   bar;
        set $metric_stats_value 9;

        # Либо: set $metric_stats bar=9;

        return "Updated with '$metric_stats'\nValues='$metric_stats_value'\n";
    }
}

Для такой конфигурации соединение с портом 12345 (устанавливающее начальное значение стрелки в 10), за которым следует соединение с портом 12346, дает:

$ nc 127.0.0.1 12345 </dev/null
$ nc 127.0.0.1 12346 </dev/null
Updated with 'sensor=15'
Value='15'

Соединение с портом 12347 дает:

$ nc 127.0.0.1 12347 </dev/null
Updated with 'bar=1, 9, 9'
Values='1, 9, 9'

Примечание

Если в качестве значения для переменной $metric_<name>_value указать пустую строку, значение будет распознано как 0. Если строка состоит из символов, которые невозможно преобразовать в число, она будет распознана как 1.

Подсчет метрики произойдет только после того, как заданы значения переменных $metric_<name>_key и $metric_<name>_value.

В таком случае значение, сохраненное в $metric_<name>, станет равным новой, подсчитанной паре ключ=значение, а не буквально присвоенным значениям.

Значение, которое хранится в $metric_<name>_key, является последним указанным через переменные ключом метрики.

Значение, которое хранится в $metric_<name>_value, является последним подсчитанным значением метрики с ключом, установленным в $metric_<name>_key.

$metric_<name>_value_<metric>#

Для составной метрики значение конкретной подметрики можно получить при помощи переменной $metric_<name>_value_<metric>, указав имя подметрики в качестве <metric>.

Пример использования:

stream {
    metric_complex_zone foo:1m {
        count count;
        min   min;
        avg   average exp;
    }

    # В этот момент добавилась переменная $metric_foo, а также
    # $metric_foo_key, $metric_foo_value и $metric_foo_value_count,
    # $metric_foo_value_min, $metric_foo_value_avg

    server {
        listen 12345;

        set $metric_foo_key   bar;
        set $metric_foo_value 9;

        # Либо: set $metric_foo bar=9;

        return "Updated with '$metric_foo'\nValues='$metric_foo_value'\nCount='$metric_foo_value_count'\n";
    }
}

Для такой конфигурации соединение с портом 12345 дает:

$ nc 127.0.0.1 12345 </dev/null
Updated with 'bar=1, 9, 9'
Values='1, 9, 9'
Count='1'

Дополнительные примеры#

Мониторинг соединений по протоколу#

metric_zone protocols:1m count;

server {
    listen 12345;
    listen 12346 udp;

    metric protocols $protocol;
}

Ответ:

{
    "TCP": 340,
    "UDP": 58
}

Распределение времени сессии upstream#

metric_zone upstream_time:10m expire=on histogram
    0.05 0.1 0.3 0.5 1 2 5 10 inf;

server {
    listen 12345;

    proxy_pass backend;
    metric upstream_time $upstream_addr=$upstream_session_time on=end;
}

Ответ:

{
    "discarded": 0,
    "metrics": {
        "backend1:8080": {
            "0.05": 12,
            "0.1": 28,
            "0.3": 56,
            "0.5": 78,
            "1": 92,
            "2": 97,
            "5": 99,
            "10": 100,
            "inf": 100
        }
    }
}

Активные подключения#

stream {
    metric_zone active_connections:2m gauge;

    server {
        listen 12345;

        metric active_connections service_a=1 on=connect;
        metric active_connections service_a=-1 on=end;
    }

    server {
        listen 12346;

        metric active_connections service_b=1 on=connect;
        metric active_connections service_b=-1 on=end;
    }
}

http {
    server {
        listen 8080;

        location /connections/ {
            allow 127.0.0.1;
            deny all;
            api /status/stream/metric_zones/active_connections/metrics/;
        }
    }
}

Ответ:

{
    "service_a": 42,
    "service_b": 17
}

Поддержка Prometheus#

В Angie есть встроенный модуль для отображения метрик в формате Prometheus, который поддерживает произвольные метрики, включая метрики, собранные на потоковой стороне. Поскольку экспорт в формате Prometheus — HTTP-функция, директивы prometheus_template и prometheus задаются в блоке http, обращаясь к потоковой зоне метрики через единое дерево API /status/.

В качестве примера интеграции рассмотрим следующую конфигурацию:

stream {
    # Создание метрики "upload"
    metric_complex_zone upload:1m discard_key="other" {
        stats    histogram 64 256 1024 4096 16384 +Inf;
        sum      gauge;
        count    count;
        avg_size average exp;
    }

    upstream backend {
        server 127.0.0.1:15001;
    }

    server {
        listen 12345;

        # Обновление метрики по завершении сессии общим количеством
        # байт, полученных от клиента
        proxy_pass backend;
        metric upload angie=$bytes_received on=end;
    }
}

http {
    # Описание шаблона Prometheus для метрики "upload"
    prometheus_template upload_metric {
        'stats{le="$1"}' $p8s_value
                         path=~^/stream/metric_zones/upload/metrics/angie/stats/(.+)$
                         type=histogram;

        'stats_sum'      $p8s_value
                         path=/stream/metric_zones/upload/metrics/angie/sum;
        'stats_count'    $p8s_value
                         path=/stream/metric_zones/upload/metrics/angie/count;

        'avg_size'       $p8s_value
                         path=/stream/metric_zones/upload/metrics/angie/avg_size;
    }

    server {
        listen 80;

        # Таргет для сбора метрик
        location /prometheus/upload_metric/ {
            prometheus upload_metric;
        }
    }
}

После пяти соединений с портом 12345, при которых были отправлены данные размером 16384, 64448, 64, 1028 и 1028 байт:

$ head -c 16384 /dev/urandom | nc 127.0.0.1 12345
$ head -c 64448 /dev/urandom | nc 127.0.0.1 12345
$ head -c 64 /dev/urandom | nc 127.0.0.1 12345
$ head -c 1028 /dev/urandom | nc 127.0.0.1 12345
$ head -c 1028 /dev/urandom | nc 127.0.0.1 12345

Значения метрики будут следующими:

{
    "stats": {
        "64": 1,
        "256": 1,
        "1024": 1,
        "4096": 3,
        "16384": 4,
        "+Inf": 5
    },

    "sum": 82952,
    "count": 5,
    "avg_size": 1077.9376
}

В формате Prometheus метрика доступна на /prometheus/upload_metric/:

# Angie Prometheus template "upload_metric"
# TYPE stats histogram
stats{le="64"} 1
stats{le="256"} 1
stats{le="1024"} 1
stats{le="4096"} 3
stats{le="16384"} 4
stats{le="+Inf"} 5
stats_sum 82952
stats_count 5
avg_size 1077.9376