Metric#

ngx_http_metric_module 模块允许创建任意的实时计算指标。这些指标值存储在共享内存中,并在 /status/http/metric_zones/ API 分支中实时显示。 支持多种数据聚合类型(计数器、直方图、移动平均等),并可按任意键进行分组。

配置示例#

统计 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_zone#

语法

metric_zone name:size [expire=on| off] [discard_key=name] mode [parameters];

默认值

上下文

http

创建一个指定 size 大小和给定 name 名称的共享内存区域来存储指标。该区域名称作为 /status/http/metric_zones/ 分支中的节点。

参数:

  • expire=<on|off> — 区域满时的行为:

    • 如果为 on,则丢弃最旧的指标(按更新时间)以释放内存供新指标使用;

    • 如果为 off (默认)— 丢弃新传入的指标,保留现有条目。

  • discard_key=<name> — 定义一个键为 name 的指标,用于累积被丢弃指标的值。默认情况下不创建此类指标。保留键不能手动更新。

  • mode — 数据处理算法(参见 操作模式 部分);

  • parameters — 所选模式的附加设置(例如,average expfactor)。

使用示例:

metric_zone request_time:1m max;

在 API 树中,共享内存区域模板如下所示:

{
    "discarded": 0,
    "metrics": {
        "key1": 123,
        "key2": 10.5,
    }
}

discarded

数字;共享内存区域中被丢弃的指标数量

metrics

对象;其成员是具有定义键和计算值的指标

备注

在 1 MB 区域中,键大小为 39 字节且单一指标模式时,大约可以存储 8,000 个唯一键条目。

metric_complex_zone#

语法

metric_complex_zone name:size [expire=on| off] [discard_key=name] { ... }

默认值

上下文

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": {
        "key1": {
            "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 name key=value [on=request| response| end];

默认值

上下文

http, server, location

计算指定共享内存区域 name 的指标值。

参数:

  • key — 任意字符串(通常是变量),用于对值进行分组。

    最大长度为 255 字节。如果键更长,将被截断为 255 字节并附加省略号 ...

  • value — 由所选模式处理的数字(可以是变量)。

    如果省略,默认为 0。如果参数无法转换为数字,则默认为 1

  • on — 可选参数,指定何时计算指标:

    • 如果为 on=request,在接收到请求时计算;

    • 如果为 on=response,在准备响应期间计算;

    • 如果为 on=end (默认),在发送响应后计算。

备注

在内部重定向的情况下,on=request 阶段的指标在原始 location 中计算。但是,on=responseon=end 指标将在新的 location 中计算。

使用示例:

metric requests $http_user_agent=$request_time;

备注

具有空键或无效 key=value 对的指标将被忽略。 省略的 value 被视为 0

metric foo $bar;  # 等同于 $bar=0

这对于 count 模式很有用,该模式忽略数值,只对指标被更新这一事实做出反应。

备注

请记住,变量在不同阶段进行求值。例如,无法在 on=request (接收请求时)使用 $bytes_sent (发送给客户端的字节数)。

操作模式#

可用指标操作模式列表:

  • count — 计数器;

  • gauge — 仪表(增加/减少);

  • last — 最后接收到的值;

  • min — 最小值;

  • max — 最大值;

  • average exp — 指数移动平均(EMA)(参数 factor);

  • average mean — 窗口内的平均值(参数 windowcount);

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

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#

存储最后接收到的值,不进行任何聚合。如果省略 value,则使用 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=<number> — 决定新值对平均值影响程度的系数。允许的整数值范围为 099。默认值为 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|time>count=<number>,分别定义用于平均的时间间隔和样本大小。默认值:window=off`(使用整个样本)和 :samp:`count=10

备注

例如,:samp:window=5s 将仅考虑最近 5 秒内的事件。window 参数不能为 0count=number 参数控制样本大小(缓存值),以实现更平滑的平均值计算。

示例:

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 秒,预期值将为:

{
    "KEY": 0
}

histogram#

创建一组"桶",如果新值不超过桶的阈值,则递增相关计数器。参数以数值阈值列表的形式提供。对于分析分布(如响应时间)很有用。

必需参数是 numbers — 桶的阈值,按升序列出。

备注

桶值 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 模块处理指标。

用于设置变量的值必须遵循 key=value 结构。 键和值都可以由文本、变量及其组合构成。 键是用于分组值的任意字符串。值是由所选模式处理的数字。 如果省略,则默认为 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
        }
    }
}

备注

设置变量后,可以获取其值;它将等于 指定的 key=value 对。

此外,存储在 $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> 中的值将等于 新的 key=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
}

上游响应时间分布#

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

    # 描述 "upload" 指标的 Prometheus 模板
    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