Metric#

Added in version 1.12.0.

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

有关响应模式,请参见 Stream 指标区域 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 name key=value [on=connect| preread| end];

默认值

上下文

stream, server

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

参数:

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

    最大长度为 255 字节;更长的键会被截断。在 API 输出中,Angie 会为每个 255 字节的键(包括原始长度恰好为 255 字节的键)附加 ...。键本身可以包含 = 字符 — 只有 最后一个 = 之后的文本才被视为值,因此它之前的所有内容(包括任何嵌入的 =)都成为键;

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

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

  • on — 可选参数,指定在会话处理的哪个阶段计算指标:

    • 如果为 on=connect,计算发生在 Pre-access 阶段,即在从客户端读取任何数据之前;

    • 如果为 on=preread,每个会话计算一次,即当来自上游侧的第一批数据 — 从使用 proxy_pass 代理到的服务器接收,或由 return 生成 — 被发送给客户端时;此时,proxy_protocolssl_prereadmqtt_preread 等模块已经处理了客户端的初始数据;

    • 如果为 on=end(默认),计算发生在 Log 阶段,即会话结束时。

使用示例:

metric sessions $remote_addr=$session_time on=end;

备注

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

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

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

备注

请记住,变量在不同阶段进行求值。例如,无法将 $upstream_bytes_sent(发送到上游的字节数)与 on=connect(在连接被代理到任何地方之前)一起使用。

metric_complex_zone#

语法

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

默认值

上下文

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": {
        "key1": {
            "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 name:size [expire=on| off] [discard_key=name] mode [parameters];

默认值

上下文

stream

创建一个指定 size 大小和给定 name 名称的共享内存区域来存储指标。该区域名称作为 /status/stream/metric_zones/ 分支中的节点。区域大小必须至少为八个系统内存页。

参数:

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

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

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

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

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

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

使用示例:

metric_zone session_time:1m max;

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

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

备注

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

操作模式#

可用指标操作模式列表:

  • count — 计数器;

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

  • last — 最后接收到的值;

  • min — 最小值;

  • max — 最大值;

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

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

  • histogram — 跨"桶"的分布(阈值列表)。

以下示例使用配置示例中的 /status/ 端点。每个结果都可在 /status/stream/metric_zones/<zone>/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#

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#

存储最后接收到的值,不进行任何聚合。如果省略 value,则使用 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=<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 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|time>count=<number>,分别定义用于平均的时间间隔和样本大小。默认为 window=off(使用整个样本)和 count=10

备注

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

{
    "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 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 处理程序中)进行。

该设置器接受一个键,并可带有可选的 =value 后缀。最后一个 = 将键与值分开;如果没有该后缀,值为 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> 会以 key=value 形式返回该键以及指标当前的 计算后 值 — 而不是所赋的字面字符串。在上面的示例中,连接赋予的字面值为 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(将 gauge 的起始值设置为 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> 中的值将等于新的 计算后 key=value 对,而不是刚刚赋予的字面值。

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

上游会话时间分布#

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 格式 显示指标,该模块支持自定义指标,包括在 stream 侧收集的指标。由于 Prometheus 暴露是仅 HTTP 的功能,因此 prometheus_templateprometheus 指令在 http 块中配置,并通过同一个统一的 /status/ API 树引用 stream 指标区域。

作为集成示例,请考虑以下配置:

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 {
    # 描述 "upload" 指标的 Prometheus 模板
    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 发起五次连接,分别发送 16384644486410281028 字节的负载后:

$ 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