Metric#

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

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

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

При сборке из исходного кода модуль собирается по умолчанию; его можно отключить с помощью параметра сборки --without-http_metric_module.

В пакетах и образах из наших репозиториев модуль включен в сборку.

Примечание

При перезагрузке конфигурации зона, созданная директивой metric_zone или metric_complex_zone, сохраняет накопленные значения только при неизменной структуре хранимых данных. Зона начинает работу пустой, если от предыдущей конфигурации отличаются размер зоны, количество метрик или режим, если изменился параметр count режима average mean, если параметр window режима average mean переключен в off или обратно или если изменилось количество корзин histogram; причина при этом записывается в error_log на уровне info. Остальные параметры, например factor режима average exp, интервал window режима average mean или границы корзин histogram, можно менять без потери накопленных значений.

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

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

http {
    metric_zone api_requests:1m count;

    server {
        listen 80;

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

            metric api_requests $http_user_agent on=request;
        }
    }
}

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

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

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

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

Директивы#

metric#

Синтаксис

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

По умолчанию

Контекст

http, server, location

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

Параметры:

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

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

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

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

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

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

Примечание

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

Директив metric может быть несколько. Директивы наследуются с предыдущего уровня конфигурации при условии, что на данном уровне не описаны свои директивы metric. Поэтому одна директива metric в блоке location отменяет все правила, унаследованные от server или http, на всех трех стадиях on, а не только на своей.

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

metric requests $http_user_agent=$request_time;

Примечание

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

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

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

Примечание

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

metric_complex_zone#

Синтаксис

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

По умолчанию

Контекст

http

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

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

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

metric_zone#

Синтаксис

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

По умолчанию

Контекст

http

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

Параметры:

  • 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
    }
}

Примечание

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

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

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

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

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

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

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

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

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

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

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

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

count#

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

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

Примеры:

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;
    }

}

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

$ 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;
    }

}

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

$ 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;
    }

}

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

$ 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;
    }

}

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

$ 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;
    }

}

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

$ 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;
    }

}

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

$ 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;
    }

}

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

$ 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 hist KEY=$1;
    }
}

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

$ 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. Ключ и значение могут состоять из текста, переменных или их комбинаций.

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

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=0 0 1, 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>#

Для составной метрики значение конкретной подметрики можно получить при помощи переменной $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=1, 9, 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 для отображения метрик в формате 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