Skip to content

feat(prometheus): stream active connections, termination status and bandwidth - #13796

Open
AlinsRan wants to merge 3 commits into
masterfrom
feat/stream-observability-metrics
Open

feat(prometheus): stream active connections, termination status and bandwidth#13796
AlinsRan wants to merge 3 commits into
masterfrom
feat/stream-observability-metrics

Conversation

@AlinsRan

Copy link
Copy Markdown
Contributor

Description

A TCP or UDP proxy cannot be monitored from the Stream metrics APISIX has today. apisix_stream_connection_total is a per-Route connection counter, and three things are missing behind it:

  • No live concurrency. Connection counts are only known once a session ends.
  • No way to tell a failure from a clean close. NGINX reports a Stream $status of 200 for every failure that happens after the upstream connection is established — every post-connect path ends in ngx_stream_proxy_finalize(s, NGX_STREAM_OK) — so an idle timeout or a reset is indistinguishable from a normal close.
  • No throughput. $bytes_sent and friends only exist at log time, and Lua has no handle on a live session, so a long-lived connection reports nothing until it ends.

This adds three metrics, all keyed by listen_addr rather than by Route: a session can end before any Stream Route is matched, and the byte counters come from NGINX, which only knows the listening address.

Metric Type Labels
apisix_stream_active_connections gauge listen_addr
apisix_stream_status counter code, listen_addr, node
apisix_stream_bandwidth counter listen_addr, type, side

code only ever takes values NGINX itself uses for Stream sessions — no synthetic codes. $stream_session_reason narrows 200 down to genuine closes and folds timeouts and resets onto 400/502. A plugin rejection is carried on ctx instead, because a Stream plugin rejects by closing the session (plugin.lua's run_plugin calls ngx_exit(1)), so the code it returned never reaches $status; it is mapped through the same whitelist so an operator-supplied rejected_code cannot put an arbitrary value on the metric.

apisix_stream_bandwidth and apisix_stream_active_connections are read once a second from an NGINX shared memory zone, so they keep moving while a connection is open. Zone size is nginx_config.stream.metrics_zone_size, default 1m.

Runtime dependency

apisix_stream_metrics_zone, $stream_session_reason and $stream_listen_addr come from apisix-nginx-module 1.19.9, so .requirements moves to 1.3.14, the first APISIX-Runtime carrying it.

Everything degrades on an older runtime rather than breaking: the collector logs once and stays off, and the status metric falls back to $status.

Which issue(s) this PR fixes

N/A

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible

…andwidth

A TCP or UDP proxy could not be monitored from the stream metrics we had. The
only one was a per route connection counter, nginx reports a stream $status of
200 for every failure that happens after the upstream connection is
established, and no byte counter of a live session is reachable from Lua, so a
long-lived connection produced nothing until it ended.

Add three metrics, keyed by listen_addr because a session can end before any
stream route is matched and because the byte counters come from nginx, which
only knows the listening address:

- apisix_stream_active_connections, live concurrency per listening address
- apisix_stream_status, one count per finished session. $stream_session_reason
  narrows 200 down to genuine closes and folds timeouts and resets onto the
  codes nginx itself uses; plugin rejections are carried on ctx because a
  stream plugin rejects by closing the session, which never sets $status
- apisix_stream_bandwidth, bytes per direction and side, read once a second
  from the nginx zone so it grows while a connection is still open

apisix_stream_metrics_zone, $stream_session_reason and $stream_listen_addr
come from apisix-nginx-module 1.19.9, so .requirements moves to the first
runtime carrying it. Everything degrades on an older runtime: the collector
logs once and stays off, and the status metric falls back to $status.
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. enhancement New feature or request plugin labels Aug 10, 2026
ci/linux-install-openresty.sh guards the debug deb with a hardcoded version
and sha256 per architecture, and bails out when .requirements moves without
them. Every job that installs the runtime failed on that guard.

Digests taken from the published 1.3.14 release assets.
The live-gauge case scraped the metrics endpoint while its session was open
and found no series, although the later case asserting 0 on the same series
passed -- so the collector had published, just not before this block scraped.
Whether it has ticked by then depends on when the stream plugins finish
loading, which is not what this case is about.

Read the zone directly, which is where the count lives while the session is
open; the published gauge stays covered by the case after it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request plugin size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant