Metric#

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

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

Подсчет запросов к API:

http {
    metric_zone api_requests:1m count;

    server {
        listen 80;

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

            metric api_requests $http_user_agent on=request;
        }
    }
}

Если с такой конфигурацией выполнить запрос на /api/:

$ curl 127.0.0.1/api/ --user-agent "Firefox"

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

{
    "status": {
        "http": {
            "metric_zones": {
                "api_requests": {
                    "discarded": 0,
                    "metrics": {
                        "Firefox": 1
                    }
                }
            }
        }
    }
}

Директивы#

metric_zone#

Синтаксис

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

По умолчанию

Контекст

http

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

Параметры:

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

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

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

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

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

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

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

metric_zone request_time:1m max;

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

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

discarded

Число; количество отброшенных метрик в зоне разделяемой памяти

metrics

Объект; его члены — метрики с установленными ключами и подсчитанными значениями

Примечание

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

metric_complex_zone#

Синтаксис

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

По умолчанию

Контекст

http

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

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

metric_complex_zone requests: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
        }
    }
}

discarded

Число; количество отброшенных метрик в зоне разделяемой памяти

metrics

Объект; его члены — составные метрики с установленными ключами. Они представляют собой объекты, содержащие набор подметрик с подсчитанными значениями

metric#

Синтаксис

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

По умолчанию

Контекст

http, server, location

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

Параметры:

  • ключ — произвольная строка (часто переменная), по которой группируются значения. Максимальная длина — 255 байт. Если ключ длиннее, он будет ограничен размером 255 байт и дополнен ... многоточием;

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

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

    • Если on=request, подсчет происходит при получении запроса;

    • Если on=response, подсчет происходит при подготовке ответа;

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

Примечание

В случае внутреннего перенаправления, метрики на стадии on=request посчитаются в исходном location. При этом метрики on=response и on=end будут подсчитаны уже в новом location.

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

metric requests $http_user_agent=$request_time;

Примечание

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

metric foo $bar;  # Вместо $bar=0

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

Примечание

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

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

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

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

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

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

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

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

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

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

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

count#

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

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

Примечание

Любое обновление метрики (с любым значением) монотонно увеличивает счетчик на 1.

Примеры:

metric_zone count:1m count;

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

server {
    listen 80;

    location /metric/ {
        metric count KEY;
    }

    location ~ ^/metric/set/(.+)$ {
        metric count KEY=1;
    }

    location /api/ {
        api /status/http/metric_zones/count/metrics/;
    }
}

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

$ curl 127.0.0.1/metric/
$ curl 127.0.0.1/metric/set/1
$ curl 127.0.0.1/metric/set/23
$ curl 127.0.0.1/metric/set/-32

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

{
    "KEY": 4
}

gauge#

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

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

Примеры:

metric_zone gauge:1m gauge;

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

server {
    listen 80;

    location /metric/ {
        metric gauge KEY;
    }

    location ~ ^/metric/set/(.+)$ {
        metric gauge KEY=$1;
    }

    location /api/ {
        api /status/http/metric_zones/gauge/metrics/;
    }
}

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

$ curl 127.0.0.1/metric/

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

{
    "KEY": 0
}

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

$ curl 127.0.0.1/metric/set/5
$ curl 127.0.0.1/metric/set/-5
$ curl 127.0.0.1/metric/set/8

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

{
    "KEY": 8
}

last#

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

Примеры:

metric_zone last:1m last;

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

server {
    listen 80;

    location /metric/ {
        metric last KEY;
    }

    location ~ ^/metric/set/(.+)$ {
        metric last KEY=$1;
    }

    location /api/ {
        api /status/http/metric_zones/last/metrics/;
    }
}

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

$ curl 127.0.0.1/metric/

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

{
    "KEY": 0
}

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

$ curl 127.0.0.1/metric/set/8000
$ curl 127.0.0.1/metric/set/37
$ curl 127.0.0.1/metric/set/-3.5

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

{
   "KEY": -3.5
}

min#

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

Примеры:

metric_zone min:1m min;

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

server {
    listen 80;

    location /metric/ {
        metric min KEY;
    }

    location ~ ^/metric/set/(.+)$ {
        metric min KEY=$1;
    }

    location /api/ {
        api /status/http/metric_zones/min/metrics/;
    }
}

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

$ curl 127.0.0.1/metric/set/42.999
$ curl 127.0.0.1/metric/set/-512
$ curl 127.0.0.1/metric/set/1
$ curl 127.0.0.1/metric/

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

{
    "KEY": -512
}

max#

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

Примеры:

metric_zone max:1m max;

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

server {
    listen 80;

    location /metric/ {
        metric max KEY;
    }

    location ~ ^/metric/set/(.+)$ {
        metric max KEY=$1;
    }

    location /api/ {
        api /status/http/metric_zones/max/metrics/;
    }
}

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

$ curl 127.0.0.1/metric/set/42.999
$ curl 127.0.0.1/metric/set/-512
$ curl 127.0.0.1/metric/set/1
$ curl 127.0.0.1/metric/

Ожидаемое значение метрики в 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 80;

    location ~ ^/metric/set/(.+)$ {
        metric avg_exp KEY=$1;
    }

    location /api/ {
        api /status/http/metric_zones/avg_exp/metrics/;
    }
}

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

$ curl 127.0.0.1/metric/set/100
$ curl 127.0.0.1/metric/set/200
$ curl 127.0.0.1/metric/set/0
$ curl 127.0.0.1/metric/set/8
$ curl 127.0.0.1/metric/set/30

Ожидаемое значение метрики в 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 80;

    location ~ ^/metric/set/(.+)$ {
        metric avg_mean KEY=$1;
    }

    location /api/ {
        api /status/http/metric_zones/avg_mean/metrics/;
    }
}

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

$ curl 127.0.0.1/metric/set/0.1
$ curl 127.0.0.1/metric/set/0.1
$ curl 127.0.0.1/metric/set/0.4
$ curl 127.0.0.1/metric/set/10
$ curl 127.0.0.1/metric/set/1
$ curl 127.0.0.1/metric/set/1

Ожидаемое значение метрики в 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 80;

    location ~ ^/metric/set/(.+)$ {
        metric histogram KEY=$1;
    }

    location /api/ {
        api /status/http/metric_zones/hist/metrics/;
    }
}

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

$ curl 127.0.0.1/metric/set/0.25

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

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

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

$ curl 127.0.0.1/metric/set/2

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

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

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

$ curl 127.0.0.1/metric/set/1000

Ожидаемое значение метрики в 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> для подсчета метрики. Подсчет метрики будет происходить в фазе Rewrite, что позволяет производить обработку метрики, например, из модуля njs.

В качестве значения для установки переменной должна использоваться конструкция ключ=значение. В качестве ключа и значения можно использовать текст, переменные и их комбинации. Ключ — произвольная строка, по которой группируются значения. Значение — число, обрабатываемое выбранным режимом. Если пропущено, считается 0. Если параметр невозможно преобразовать в число, считается 1.

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

http {
    metric_zone counter:1m count;

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

    server {
        listen 80;

        location /metric/ {
            set $metric_counter $http_user_agent;  # Вместо $http_user_agent=0
        }

        location /api/ {
            allow 127.0.0.1;
            deny all;
            api /status/http/metric_zones/counter/;
        }
    }
}

Подсчет метрики с помощью модуля njs:

http {
    js_import metrics.js;

    resolver 127.0.0.53;

    metric_complex_zone requests:1m {
        min_time        min;
        max_time        max;
        total           count;
    }

    location /metric/ {
        js_content metrics.js_request;
        js_fetch_trusted_certificate /path/to/ISRG_Root_X1.pem;
    }

    location /api/ {
        allow 127.0.0.1;
        deny all;
        api /static/http/metric_zones/requests/;
    }
}

Файл metrics.js:

async function js_request(r) {
    let start_time = Date.now();

    let results = await Promise.all([ngx.fetch('https://google.com/'),
                                     ngx.fetch('https://google.ru/')]);

    // Используем сеттер переменной $metric_requests
    r.variables.metric_requests = `google={Date.now() - start_time}`;
}

export default {js_request};

После нескольких запросов на location /metric/ значения метрики могут быть следующими:

{
    "discarded": 0,
    "metrics": {
        "google": {
            "min_time": 70,
            "max_time": 432,
            "total": 6
        }
    }
}

Примечание

После установки переменной можно получить ее значение. Оно будет равно указанной паре ключ=значение.

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

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

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

Примечание

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

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

http {
    metric_zone gauge:1m gauge;

    # В этот момент добавилась переменная $metric_gauge
    # А еще переменные $metric_gauge_key и $metric_gauge_value

    metric_complex_zone complex:1m {
        hist histogram 1 2 3;
        avg  average exp;
    }

    # В этот момент добавилась переменная $metric_complex
    # А еще переменные $metric_complex_key и $metric_complex_value

    server {
        listen 80;

        location /gauge/ {
            set $metric_gauge_key "foo";
            set $metric_gauge_value 1;

            # Либо set $metric_gauge foo=1;

            return 200 "Updated with '$metric_gauge'\nValue='$metric_gauge_value'\n";
        }

        location /complex/ {
            set $metric_complex_key "foo";
            set $metric_complex_value 3;

            # Либо set $metric_complex foo=3;

            return 200 "Updated with '$metric_complex'\nValue='$metric_complex_value'\n";
        }
    }
}

Для такой конфигурации после запроса к /gauge/ ожидается слудующий ответ:

$ curl 127.0.0.1/gauge/
Updated with 'foo=1'
Value='1'

Для /complex/:

$ curl 127.0.0.1/complex/
Updated with 'foo=3'
Value='0 0 1, 3'

Примечание

Если в качестве значения для переменной $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_<name>_key.

$metric_<name>_value_<metric>#

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

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

http {
    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 80;

        location /foo/ {
            set $metric_foo_key   bar;
            set $metric_foo_value 9;

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

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

Для такой конфигурации после запроса к /foo/ ожидается слудующий ответ:

$ curl 127.0.0.1/foo/
Updated with 'bar=9'
Values='1, 9, 9'
Count='1'

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

Мониторинг HTTP-методов#

metric_zone http_methods:1m count;

server {
    listen 80;

    location / {
        metric http_methods $request_method;
    }

    location /metrics/ {
        allow 127.0.0.1;
        deny all;
        api /status/http/metric_zones/http_methods/metrics/;
    }
}

Ответ:

{
    "GET": 65,
    "POST": 20,
    "PUT": 10,
    "DELETE": 5
}

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

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

server {
    listen 80;

    location /backend/ {
        proxy_pass http://backend;
        metric upstream_time $upstream_addr=$upstream_response_time on=end;
    }

    location /metrics/ {
        allow 127.0.0.1;
        deny all;
        api /status/http/metric_zones/upstream_time/;
    }
}

Ответ:

{
    "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
        }
    }
}

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

metric_zone active_connections:2m gauge;

server {
    listen 80;
    server_name site1.com;

    location / {
        # Увеличиваем при подключении
        metric active_connections site1=1 on=request;

        # Уменьшаем при завершении
        metric active_connections site1=-1 on=end;
    }
}

server {
    listen 80;
    server_name site2.com;

    location / {
        metric active_connections site2=1 on=request;
        metric active_connections site2=-1 on=end;
    }
}

server {
    listen 8080;

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

Ответ:

{
    "site1": 42,
    "site2": 17
}

Поддержка Prometheus#

Angie имеет встроенный модуль для отображения метрик в формате Prometheus, поддерживающий произвольные метрики.

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

http {
    # Создание метрики "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;
    }

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

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

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

    server {
        listen 80;

        # Обновление метрики
        location ~ ^/upload/(.*)$ {
            api /status/http/metric_zones/upload/metrics/angie/;
            metric upload angie=$1 on=request;
        }

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

После нескольких запросов на /upload/...:

$ curl 127.0.0.1/upload/16384
$ curl 127.0.0.1/upload/64448
$ curl 127.0.0.1/upload/64
$ curl 127.0.0.1/upload/1028
$ curl 127.0.0.1/upload/1028

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

{
    "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