Metric#
Added in version 1.12.0.
The ngx_stream_metric_module module allows creating arbitrary
real-time calculated metrics for stream (TCP and UDP) traffic. These metric
values are stored in shared memory and displayed in real-time within the
/status/stream/metric_zones/ API branch. Various data aggregation
types are supported (counters, histograms, moving averages, etc.) with
grouping by arbitrary keys.
For the response schema, see the
Stream metric zone API reference. Counting connections by client address: If a connection is made to port The Calculates the value of the metric for the specified shared memory zone name. Parameters: Maximum length is 255 bytes; longer keys are truncated. In API output,
Angie appends If omitted, it defaults to If If If Example usage: Note Metrics with an empty key or an invalid This is useful, for example, for the Note Remember that variables are evaluated in different phases. For example,
it is impossible to use Default — stream Defines a complex metric — a set of metrics with independent modes.
Each line in the block body defines a submetric name, a mode,
and optional mode parameters.
The zone size must be at least eight system memory pages. Example usage: In the API tree, such a complex metric template looks as follows: Default — stream Creates a shared memory zone of the specified size with the given name
to store metrics. The zone name serves as a node in the
Parameters: free memory for new ones; preserving existing entries. where values of discarded metrics are accumulated. By default, no such
metric is created. Reserved key cannot be updated manually. mode — data processing algorithm (see the Operation Modes section); (e.g., Example usage: In the API tree, the shared memory zone template looks as follows: Note In a 1 MB zone, with a key size of 39 bytes and a single metric mode,
approximately 8,000 unique key entries can be stored. List of available metric operation modes: The examples below use the Each metric update increments the counter by Default value — Examples: Updating the metric: Expected metric value in the API: The gauge increases or decreases its value depending on the sign of the
passed number. A positive value increases the counter, while a negative
value decreases it. A value of Default value — Examples: Updating the metric: Expected metric value in the API: Further updates: Expected metric value in the API: Stores the last received value without any aggregation. If value is
omitted, Examples: Updating the metric: Expected metric value in the API: Further updates: Expected metric value in the API: Saves the minimum of two values — the currently stored value and the new one. Examples: Updating the metric: Expected metric value in the API: Saves the maximum of two values — the currently stored value and the new one. Examples: Updating the metric: Expected metric value in the API: Calculates the average value using the
exponential smoothing algorithm. Accepts an optional parameter The higher the coefficient, the more weight new values have. If you specify
Examples: Updating the metric: Expected metric value in the API: Calculates the arithmetic mean. Accepts optional parameters
Note For example, Examples: Updating the metric: Expected metric value in the API: If you wait 5 seconds from the last update, the expected value will be: Creates a set of "buckets," incrementing the relevant counter if the new
value does not exceed the bucket threshold. Parameters are provided as
a list of numerical thresholds. Useful for analyzing distributions,
such as session durations. Mandatory parameters are numbers — the threshold values of the buckets,
typically listed in ascending order. Note The bucket value Examples: Updating the metric: Expected metric value in the API: Further updates: Expected metric value in the API: Further update: Expected metric value in the API: Variables are created for each metric: For complex metrics, an additional variable is added: Similar to the metric directive, the The setter accepts a key with an optional Example usage: After three connections to port Note Reading Additionally, the value stored in the The Note For complex metrics, the submetric values in the Example usage: With this configuration, a connection to port A connection to port Note If an empty string is assigned to Calculation occurs only after both In this case, the value stored in The value in The value in For complex metrics, the value of a specific submetric can be retrieved
using the Example usage: With this configuration, a connection to port Response: Response: Response: Angie includes a built-in module for displaying
metrics in
Prometheus format,
which supports custom metrics, including those collected on the stream side.
Since Prometheus exposition is an HTTP-only feature, the
prometheus_template and prometheus
directives are configured in an As an integration example, consider the following configuration: After five connections to port The metric values will be: In Prometheus format,
the metric is available at Configuration Example#
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 metric is updated in real-time:{
"stream": {
"metric_zones": {
"connections": {
"discarded": 0,
"metrics": {
"127.0.0.1": 1
}
}
}
}
}
Directives#
metric#
... to every 255-byte key, including one whose
original length was exactly 255 bytes. A key may itself contain
= characters — only the text after the last = is
treated as the value, so everything before it (including any embedded
=) becomes the key;0. If the parameter cannot be
converted to a number, it defaults to 1;on — an optional parameter specifying at which point of
session processing the metric is calculated:on=connect, calculation occurs during the Pre-access
phase, before any data is read from the client;on=preread, calculation occurs once per session, when the
first data from the upstream side — received from the server proxied to
with proxy_pass, or generated by
return — is sent to the client; by that point, modules
such as proxy_protocol, ssl_preread, and
mqtt_preread have already processed the client's initial data;on=end (default), calculation occurs during the Log
phase, when the session ends.metric sessions $remote_addr=$session_time on=end;
key=value pair are ignored.
An omitted value is treated as 0:metric foo $bar; # Equivalent to $bar=0
count mode, which ignores
numerical values and simply reacts to the fact that a metric was updated.$upstream_bytes_sent (bytes sent to the
upstream) with on=connect (before the connection has been
proxied anywhere).metric_complex_zone#
metric_complex_zone name:size [expire=on| off] [discard_key=name] { ... }metric_complex_zone sessions:1m expire=on discard_key="old" {
# submetric name mode parameters
min_time min;
avg_time average exp factor=60;
max_time max;
total count;
}
{
"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];/status/stream/metric_zones/ branch.
The zone size must be at least eight system memory pages.expire=<on|off> — behavior when the zone is full:on, the oldest metrics (by update time) are discarded tooff (default) — new incoming metrics are discarded,discard_key=<name> — defines a metric with the key namefactor for average exp).metric_zone session_time:1m max;
{
"discarded": 0,
"metrics": {
"key1": 123,
"key2": 10.5
}
}
Operation Modes#
count — counter;gauge — gauge (increment/decrement);last — the last received value;min — minimum value;max — maximum value;average exp — exponential moving average (EMA) (parameter factor);average mean — mean over a window (parameters window and count);histogram — distribution across "buckets" (a list of threshold values)./status/ endpoint from the
configuration example. Each result is
available under /status/stream/metric_zones/<zone>/metrics/.count#
1, regardless of the
supplied value.0.metric_zone count:1m count;
# As part of a complex metric:
#
# 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
{
"KEY": 4
}
gauge#
0 does not change the counter.0.metric_zone gauge:1m gauge;
# As part of a complex metric:
#
# 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
{
"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
{
"KEY": 8
}
last#
0 is used.metric_zone last:1m last;
# As part of a complex metric:
#
# 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
{
"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
{
"KEY": -3.5
}
min#
metric_zone min:1m min;
# As part of a complex metric:
#
# 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
{
"KEY": -512
}
max#
metric_zone max:1m max;
# As part of a complex metric:
#
# 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
{
"KEY": 42.999
}
average exp#
factor=<number> — a coefficient determining
how much the new value influences the average. Integer values from 0 to
99 are allowed. Default is 90.90, the result will be 90% of the new value and 10%
of the previous average.metric_zone avg_exp:1m average exp factor=60;
# As part of a complex metric:
#
# 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
{
"KEY": 30.16
}
average mean#
window=<off|time> and count=<number>, defining the
time interval and sample size for averaging, respectively.
Defaults: window=off (entire sample used) and count=10.window=5s will only consider events from the last 5 seconds.
The window parameter cannot be 0. The count=number
parameter controls the sample size (cached values) for a smoother mean calculation.metric_zone avg_mean:1m average mean window=5s count=8;
# As part of a complex metric:
#
# 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
{
"KEY": 2.1
}
{
"KEY": 0
}
histogram#
inf or +Inf can be used to capture all
values exceeding the highest specified bucket.metric_zone hist:1m histogram 0.1 0.2 0.5 1 2 inf;
# As part of a complex metric:
#
# 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
{
"KEY": {
"0.1": 0,
"0.2": 0,
"0.5": 1,
"1": 1,
"2": 1,
"inf": 1
}
}
$ nc 127.0.0.1 12412 </dev/null
{
"KEY": {
"0.1": 0,
"0.2": 0,
"0.5": 1,
"1": 1,
"2": 2,
"inf": 2
}
}
$ nc 127.0.0.1 12413 </dev/null
{
"KEY": {
"0.1": 0,
"0.2": 0,
"0.5": 1,
"1": 1,
"2": 2,
"inf": 3
}
}
Built-in Variables#
$metric_<name>$metric_<name>_key$metric_<name>_value$metric_<name>_value_<metric>$metric_<name>#$metric_<name>
variable setter can be used to update a metric. Calculation occurs when the
variable is set — for example, via the set directive, which is
evaluated during the Pre-access phase (the
same stage as on=connect), or programmatically from the
njs module, for example in a
js_access handler.=value suffix. The last
= separates the key from the value; without the suffix, the value is
0. The key and value can contain text, variables, or combinations of
both. These are the same parsing rules as for metric.stream {
metric_zone hits:1m count;
# At this point, the $metric_hits variable is added
server {
listen 12345;
metric hits client=1 on=connect;
}
server {
listen 12346;
set $metric_hits client=1;
return "$metric_hits\n";
}
}
12345 and one connection to port
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> back returns the key together with the
metric's current calculated value, in key=value form — not the
literal string that was assigned. In the example above, the connection
assigns the literal value client=1, but since the count
mode ignores the assigned value and simply increments on every update,
reading $metric_hits afterward returns client=4 — the
counter's new total, which already includes the three prior updates from
port 12345.$metric_<name>_key variable
changes to the specified key.$metric_<name>_key and $metric_<name>_value#$metric_<name>_key and $metric_<name>_value variables
define the key and value respectively. The metric update occurs when the
$metric_<name>_value is set, provided that the key in
$metric_<name>_key has already been defined.$metric_<name>_value
variable are joined using a ", " separator.stream {
metric_zone level:1m gauge;
# The variables $metric_level, $metric_level_key, and $metric_level_value are added here.
metric_complex_zone stats:1m {
count count;
min min;
avg average exp;
}
# $metric_stats, $metric_stats_key, and $metric_stats_value are added here.
server {
listen 12345;
metric level sensor=10 on=connect;
}
server {
listen 12346;
set $metric_level_key "sensor";
set $metric_level_value 5;
# Or: 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;
# Or: set $metric_stats bar=9;
return "Updated with '$metric_stats'\nValues='$metric_stats_value'\n";
}
}
12345 (setting the
gauge's starting value to 10) followed by a connection to port
12346 yields:$ nc 127.0.0.1 12345 </dev/null
$ nc 127.0.0.1 12346 </dev/null
Updated with 'sensor=15'
Value='15'
12347 yields:$ nc 127.0.0.1 12347 </dev/null
Updated with 'bar=1, 9, 9'
Values='1, 9, 9'
$metric_<name>_value, the value is
recognized as 0. If the string consists of characters that cannot be
converted to a number, it is recognized as 1.$metric_<name>_key and
$metric_<name>_value have been set.$metric_<name> becomes equal
to the new, calculated key=value pair, not the literal values
just assigned.$metric_<name>_key represents the last key specified
via variables.$metric_<name>_value represents the last calculated
value for the key set in $metric_<name>_key.$metric_<name>_value_<metric>#$metric_<name>_value_<metric> variable, where
<metric> is the name of the submetric.stream {
metric_complex_zone foo:1m {
count count;
min min;
avg average exp;
}
# Adds $metric_foo, $metric_foo_key, $metric_foo_value,
# and $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;
# Or: set $metric_foo bar=9;
return "Updated with '$metric_foo'\nValues='$metric_foo_value'\nCount='$metric_foo_value_count'\n";
}
}
12345 yields:$ nc 127.0.0.1 12345 </dev/null
Updated with 'bar=1, 9, 9'
Values='1, 9, 9'
Count='1'
Additional Examples#
Monitoring Connections by Protocol#
metric_zone protocols:1m count;
server {
listen 12345;
listen 12346 udp;
metric protocols $protocol;
}
{
"TCP": 340,
"UDP": 58
}
Upstream Session Time Distribution#
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
}
}
}
Active Connections#
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 Support#
http block, referencing the stream
metric zone through the same unified /status/ API tree.stream {
# Creating the "upload" metric
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;
# Updating the metric at session end with the total
# number of bytes received from the client
proxy_pass backend;
metric upload angie=$bytes_received on=end;
}
}
http {
# Describing the Prometheus template for the "upload" metric
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;
# Target for metric scraping
location /prometheus/upload_metric/ {
prometheus upload_metric;
}
}
}
12345, sending payloads of
16384, 64448, 64, 1028, and 1028
bytes:$ 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/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