You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2022/06/01 09:19:16 UTC
[apisix] branch master updated: feat(stream): add prometheus plugin (#7174)
This is an automated email from the ASF dual-hosted git repository.
spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new a8afdf2b8 feat(stream): add prometheus plugin (#7174)
a8afdf2b8 is described below
commit a8afdf2b83be9728fd1c486265c2b6eb299463ce
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Wed Jun 1 17:19:08 2022 +0800
feat(stream): add prometheus plugin (#7174)
---
apisix/cli/ngx_tpl.lua | 44 ++++++++-
apisix/cli/ops.lua | 12 +++
apisix/plugin.lua | 7 +-
apisix/plugins/prometheus.lua | 54 +----------
apisix/plugins/prometheus/exporter.lua | 111 +++++++++++++++++++++-
apisix/stream/plugins/prometheus.lua | 48 ++++++++++
conf/config-default.yaml | 5 +
docs/en/latest/plugins/prometheus.md | 59 +++++++++++-
docs/zh/latest/plugins/prometheus.md | 57 +++++++++++-
t/APISIX.pm | 15 ++-
t/cli/common.sh | 1 +
t/cli/test_etcd_mtls.sh | 2 +-
t/cli/test_prometheus_stream.sh | 93 +++++++++++++++++++
t/stream-plugin/prometheus.t | 162 +++++++++++++++++++++++++++++++++
14 files changed, 601 insertions(+), 69 deletions(-)
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index e92b8092a..4709362e5 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -57,6 +57,44 @@ env {*name*};
{% end %}
{% end %}
+{% if use_apisix_openresty then %}
+lua {
+ {% if enabled_stream_plugins["prometheus"] then %}
+ lua_shared_dict prometheus-metrics {* meta.lua_shared_dict["prometheus-metrics"] *};
+ {% end %}
+}
+
+{% if enabled_stream_plugins["prometheus"] and not enable_http then %}
+http {
+ init_worker_by_lua_block {
+ require("apisix.plugins.prometheus.exporter").http_init(true)
+ }
+
+ server {
+ listen {* prometheus_server_addr *};
+
+ access_log off;
+
+ location / {
+ content_by_lua_block {
+ local prometheus = require("apisix.plugins.prometheus.exporter")
+ prometheus.export_metrics(true)
+ }
+ }
+
+ {% if with_module_status then %}
+ location = /apisix/nginx_status {
+ allow 127.0.0.0/24;
+ deny all;
+ stub_status;
+ }
+ {% end %}
+ }
+}
+{% end %}
+
+{% end %}
+
{% if stream_proxy then %}
stream {
lua_package_path "{*extra_lua_path*}$prefix/deps/share/lua/5.1/?.lua;$prefix/deps/share/lua/5.1/?/init.lua;]=]
@@ -164,7 +202,7 @@ stream {
}
{% end %}
-{% if enable_admin or not (stream_proxy and stream_proxy.only ~= false) then %}
+{% if enable_http then %}
http {
# put extra_lua_path in front of the builtin path
# so user can override the source code
@@ -211,7 +249,7 @@ http {
lua_shared_dict plugin-limit-count-redis-cluster-slot-lock {* http.lua_shared_dict["plugin-limit-count-redis-cluster-slot-lock"] *};
{% end %}
- {% if enabled_plugins["prometheus"] then %}
+ {% if enabled_plugins["prometheus"] and not enabled_stream_plugins["prometheus"] then %}
lua_shared_dict prometheus-metrics {* http.lua_shared_dict["prometheus-metrics"] *};
{% end %}
@@ -460,7 +498,7 @@ http {
location / {
content_by_lua_block {
- local prometheus = require("apisix.plugins.prometheus")
+ local prometheus = require("apisix.plugins.prometheus.exporter")
prometheus.export_metrics()
}
}
diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua
index 384f2aa1c..937d74106 100644
--- a/apisix/cli/ops.lua
+++ b/apisix/cli/ops.lua
@@ -257,6 +257,13 @@ Please modify "admin_key" in conf/config.yaml .
use_apisix_openresty = false
end
+ local enable_http = true
+ if not yaml_conf.apisix.enable_admin and yaml_conf.apisix.stream_proxy and
+ yaml_conf.apisix.stream_proxy.only ~= false
+ then
+ enable_http = false
+ end
+
local enabled_discoveries = {}
for name in pairs(yaml_conf.discovery or {}) do
enabled_discoveries[name] = true
@@ -346,6 +353,10 @@ Please modify "admin_key" in conf/config.yaml .
end
end
+ if enabled_stream_plugins["prometheus"] and not prometheus_server_addr then
+ util.die("L4 prometheus metric should be exposed via export server\n")
+ end
+
local ip_port_to_check = {}
local function listen_table_insert(listen_table, scheme, ip, port, enable_http2, enable_ipv6)
@@ -540,6 +551,7 @@ Please modify "admin_key" in conf/config.yaml .
with_module_status = with_module_status,
use_apisix_openresty = use_apisix_openresty,
error_log = {level = "warn"},
+ enable_http = enable_http,
enabled_discoveries = enabled_discoveries,
enabled_plugins = enabled_plugins,
enabled_stream_plugins = enabled_stream_plugins,
diff --git a/apisix/plugin.lua b/apisix/plugin.lua
index fc76b2546..5aad12e89 100644
--- a/apisix/plugin.lua
+++ b/apisix/plugin.lua
@@ -567,8 +567,11 @@ function _M.init_worker()
_M.load()
-- some plugins need to be initialized in init* phases
- if ngx.config.subsystem == "http" and local_plugins_hash["prometheus"] then
- require("apisix.plugins.prometheus.exporter").init()
+ if is_http and local_plugins_hash["prometheus"] then
+ local prometheus_enabled_in_stream = stream_local_plugins_hash["prometheus"]
+ require("apisix.plugins.prometheus.exporter").http_init(prometheus_enabled_in_stream)
+ elseif not is_http and stream_local_plugins_hash["prometheus"] then
+ require("apisix.plugins.prometheus.exporter").stream_init()
end
if local_conf and not local_conf.apisix.enable_admin then
diff --git a/apisix/plugins/prometheus.lua b/apisix/plugins/prometheus.lua
index bce99625f..b15469754 100644
--- a/apisix/plugins/prometheus.lua
+++ b/apisix/plugins/prometheus.lua
@@ -14,14 +14,11 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-local ngx = ngx
local core = require("apisix.core")
-local plugin = require("apisix.plugin")
local exporter = require("apisix.plugins.prometheus.exporter")
local plugin_name = "prometheus"
-local default_export_uri = "/apisix/prometheus/metrics"
local schema = {
type = "object",
properties = {
@@ -37,7 +34,7 @@ local _M = {
version = 0.2,
priority = 500,
name = plugin_name,
- log = exporter.log,
+ log = exporter.http_log,
schema = schema,
run_policy = "prefer_route",
}
@@ -53,56 +50,9 @@ function _M.check_schema(conf)
end
-local function get_api(called_by_api_router)
- local export_uri = default_export_uri
- local attr = plugin.plugin_attr(plugin_name)
- if attr and attr.export_uri then
- export_uri = attr.export_uri
- end
-
- local api = {
- methods = {"GET"},
- uri = export_uri,
- handler = exporter.collect
- }
-
- if not called_by_api_router then
- return api
- end
-
- if attr.enable_export_server then
- return {}
- end
-
- return {api}
-end
-
-
function _M.api()
- return get_api(true)
+ return exporter.get_api(true)
end
-function _M.export_metrics()
- local api = get_api(false)
- local uri = ngx.var.uri
- local method = ngx.req.get_method()
-
- if uri == api.uri and method == api.methods[1] then
- local code, body = api.handler()
- if code or body then
- core.response.exit(code, body)
- end
- end
-
- return core.response.exit(404)
-end
-
-
--- only for test
--- function _M.access()
--- ngx.say(exporter.metric_data())
--- end
-
-
return _M
diff --git a/apisix/plugins/prometheus/exporter.lua b/apisix/plugins/prometheus/exporter.lua
index 30534d567..e9295e7d8 100644
--- a/apisix/plugins/prometheus/exporter.lua
+++ b/apisix/plugins/prometheus/exporter.lua
@@ -19,8 +19,10 @@ local core = require("apisix.core")
local plugin = require("apisix.plugin")
local ipairs = ipairs
local ngx = ngx
-local ngx_capture = ngx.location.capture
local re_gmatch = ngx.re.gmatch
+local ffi = require("ffi")
+local C = ffi.C
+local pcall = pcall
local select = select
local type = type
local prometheus
@@ -36,8 +38,14 @@ local get_protos = require("apisix.plugins.grpc-transcode.proto").protos
local service_fetch = require("apisix.http.service").get
local latency_details = require("apisix.utils.log-util").latency_details_in_ms
+local ngx_capture
+if ngx.config.subsystem == "http" then
+ ngx_capture = ngx.location.capture
+end
+local plugin_name = "prometheus"
+local default_export_uri = "/apisix/prometheus/metrics"
-- Default set of latency buckets, 1ms to 60s:
local DEFAULT_BUCKETS = {1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 30000, 60000}
@@ -58,7 +66,14 @@ end
local _M = {}
-function _M.init()
+local function init_stream_metrics()
+ metrics.stream_connection_total = prometheus:counter("stream_connection_total",
+ "Total number of connections handled per stream route in APISIX",
+ {"route"})
+end
+
+
+function _M.http_init(prometheus_enabled_in_stream)
-- todo: support hot reload, we may need to update the lua-prometheus
-- library
if ngx.get_phase() ~= "init" and ngx.get_phase() ~= "init_worker" then
@@ -83,6 +98,7 @@ function _M.init()
end
prometheus = base_prometheus.init("prometheus-metrics", metric_prefix)
+
metrics.connections = prometheus:gauge("nginx_http_current_connections",
"Number of HTTP connections",
{"state"})
@@ -119,10 +135,37 @@ function _M.init()
"Total bandwidth in bytes consumed per service in APISIX",
{"type", "route", "service", "consumer", "node"})
+ if prometheus_enabled_in_stream then
+ init_stream_metrics()
+ end
+end
+
+
+function _M.stream_init()
+ if ngx.get_phase() ~= "init" and ngx.get_phase() ~= "init_worker" then
+ return
+ end
+
+ if not pcall(function() return C.ngx_meta_lua_ffi_shdict_udata_to_zone end) then
+ core.log.error("need to build APISIX-Base to support L4 metrics")
+ return
+ end
+
+ clear_tab(metrics)
+
+ local metric_prefix = "apisix_"
+ local attr = plugin.plugin_attr("prometheus")
+ if attr and attr.metric_prefix then
+ metric_prefix = attr.metric_prefix
+ end
+
+ prometheus = base_prometheus.init("prometheus-metrics", metric_prefix)
+
+ init_stream_metrics()
end
-function _M.log(conf, ctx)
+function _M.http_log(conf, ctx)
local vars = ctx.var
local route_id = ""
@@ -174,6 +217,20 @@ function _M.log(conf, ctx)
end
+function _M.stream_log(conf, ctx)
+ local route_id = ""
+ local matched_route = ctx.matched_route and ctx.matched_route.value
+ if matched_route then
+ route_id = matched_route.id
+ if conf.prefer_name == true then
+ route_id = matched_route.name or route_id
+ end
+ end
+
+ metrics.stream_connection_total:inc(1, gen_arr(route_id))
+end
+
+
local ngx_status_items = {"active", "accepted", "handled", "total",
"reading", "writing", "waiting"}
local label_values = {}
@@ -291,7 +348,7 @@ local function etcd_modify_index()
end
-function _M.collect()
+local function collect(ctx, stream_only)
if not prometheus or not metrics then
core.log.error("prometheus: plugin is not initialized, please make sure ",
" 'prometheus_metrics' shared dict is present in nginx template")
@@ -307,7 +364,8 @@ function _M.collect()
local vars = ngx.var or {}
local hostname = vars.hostname or ""
- if config.type == "etcd" then
+ -- we can't get etcd index in metric server if only stream subsystem is enabled
+ if config.type == "etcd" and not stream_only then
-- etcd modify index
etcd_modify_index()
@@ -338,6 +396,49 @@ function _M.collect()
core.response.set_header("content_type", "text/plain")
return 200, core.table.concat(prometheus:metric_data())
end
+_M.collect = collect
+
+
+local function get_api(called_by_api_router)
+ local export_uri = default_export_uri
+ local attr = plugin.plugin_attr(plugin_name)
+ if attr and attr.export_uri then
+ export_uri = attr.export_uri
+ end
+
+ local api = {
+ methods = {"GET"},
+ uri = export_uri,
+ handler = collect
+ }
+
+ if not called_by_api_router then
+ return api
+ end
+
+ if attr.enable_export_server then
+ return {}
+ end
+
+ return {api}
+end
+_M.get_api = get_api
+
+
+function _M.export_metrics(stream_only)
+ local api = get_api(false)
+ local uri = ngx.var.uri
+ local method = ngx.req.get_method()
+
+ if uri == api.uri and method == api.methods[1] then
+ local code, body = api.handler(nil, stream_only)
+ if code or body then
+ core.response.exit(code, body)
+ end
+ end
+
+ return core.response.exit(404)
+end
function _M.metric_data()
diff --git a/apisix/stream/plugins/prometheus.lua b/apisix/stream/plugins/prometheus.lua
new file mode 100644
index 000000000..46222eca2
--- /dev/null
+++ b/apisix/stream/plugins/prometheus.lua
@@ -0,0 +1,48 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local exporter = require("apisix.plugins.prometheus.exporter")
+
+
+local plugin_name = "prometheus"
+local schema = {
+ type = "object",
+ properties = {
+ prefer_name = {
+ type = "boolean",
+ default = false -- stream route doesn't have name yet
+ }
+ },
+}
+
+
+local _M = {
+ version = 0.1,
+ priority = 500,
+ name = plugin_name,
+ log = exporter.stream_log,
+ schema = schema,
+ run_policy = "prefer_route",
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+return _M
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index f60c6577f..8f9e58e5d 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -177,6 +177,10 @@ nginx_config: # config for render the template to generate n
#envs: # allow to get a list of environment variables
# - TEST_ENV
+ meta:
+ lua_shared_dict:
+ prometheus-metrics: 15m
+
stream:
enable_access_log: false # enable access log or not, default false
access_log: logs/access_stream.log
@@ -409,6 +413,7 @@ stream_plugins: # sorted by priority
- ip-restriction # priority: 3000
- limit-conn # priority: 1003
- mqtt-proxy # priority: 1000
+ #- prometheus # priority: 500
- syslog # priority: 401
# <- recommend to use priority (0, 100) for your custom plugins
diff --git a/docs/en/latest/plugins/prometheus.md b/docs/en/latest/plugins/prometheus.md
index f083fecd5..5722733ae 100644
--- a/docs/en/latest/plugins/prometheus.md
+++ b/docs/en/latest/plugins/prometheus.md
@@ -135,7 +135,7 @@ plugin_attr:
export_uri: /apisix/metrics
```
-### Grafana dashboard
+## Grafana dashboard
Metrics exported by the plugin can be graphed in Grafana using a drop in dashboard.
@@ -151,7 +151,7 @@ Or you can goto [Grafana official](https://grafana.com/grafana/dashboards/11719)
![Grafana chart-4](../../../assets/images/plugin/grafana-4.png)
-### Available metrics
+## Available metrics
* `Status codes`: HTTP status code returned from upstream services. These status code available per service and across all services.
@@ -282,3 +282,58 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1
}
}'
```
+
+## Gather L4 metrics
+
+:::info IMPORTANT
+
+This feature requires APISIX to run on [APISIX-Base](../FAQ.md#how-do-i-build-the-apisix-base-environment?).
+
+:::
+
+We can also enable `prometheus` on the stream route:
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "plugins": {
+ "prometheus":{}
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:80": 1
+ }
+ }
+}'
+```
+
+## L4 available metrics
+
+The following metrics are available when using APISIX as an L4 proxy.
+
+* `Stream Connections`: The number of processed connections at the route level.
+
+ Attributes:
+
+ | Name | Description |
+ | ------------- | -------------------- |
+ | route | matched stream route ID |
+* `Connections`: Various Nginx connection metrics like active, reading, writing, and number of accepted connections.
+* `Info`: Information about the current APISIX node.
+
+Here are examples of APISIX metrics:
+
+```shell
+$ curl http://127.0.0.1:9091/apisix/prometheus/metrics
+```
+
+```
+...
+# HELP apisix_node_info Info of APISIX node
+# TYPE apisix_node_info gauge
+apisix_node_info{hostname="desktop-2022q8f-wsl"} 1
+# HELP apisix_stream_connection_total Total number of connections handled per stream route in APISIX
+# TYPE apisix_stream_connection_total counter
+apisix_stream_connection_total{route="1"} 1
+```
diff --git a/docs/zh/latest/plugins/prometheus.md b/docs/zh/latest/plugins/prometheus.md
index 7755514bc..2d126727a 100644
--- a/docs/zh/latest/plugins/prometheus.md
+++ b/docs/zh/latest/plugins/prometheus.md
@@ -134,7 +134,7 @@ plugin_attr:
export_uri: /apisix/metrics
```
-### Grafana 面板
+## Grafana 面板
插件导出的指标可以在 Grafana 进行图形化绘制显示。
@@ -150,7 +150,7 @@ plugin_attr:
![Grafana chart-4](../../../assets/images/plugin/grafana-4.png)
-### 可有的指标
+## 可用的指标
* `Status codes`: upstream 服务返回的 HTTP 状态码,可以统计到每个服务或所有服务的响应状态码的次数总和。具有的维度:
@@ -274,3 +274,56 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1
}
}'
```
+
+## 采集 L4 指标
+
+:::info IMPORTANT
+
+该功能要求 Apache APISIX 运行在 [APISIX-Base](../FAQ.md#如何构建-APISIX-Base-环境?) 上。
+
+:::
+
+我们也可以在 stream route 上开启 `prometheus`:
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "plugins": {
+ "prometheus":{}
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:80": 1
+ }
+ }
+}'
+```
+
+## L4 可用的指标
+
+以下是把 APISIX 作为 L4 代理时可用的指标:
+
+* `Stream Connections`: 路由级别的已处理连接数。具有的维度:
+
+ | 名称 | 描述 |
+ | -------------| --------------------|
+ | route | 匹配的 stream route ID|
+* `Connections`: 各种的 Nginx 连接指标,如 active,reading,writing,已建立的连接数。
+* `Info`: 当前 APISIX 节点信息。
+
+这里是 APISIX 指标的范例:
+
+```shell
+$ curl http://127.0.0.1:9091/apisix/prometheus/metrics
+```
+
+```
+...
+# HELP apisix_node_info Info of APISIX node
+# TYPE apisix_node_info gauge
+apisix_node_info{hostname="desktop-2022q8f-wsl"} 1
+# HELP apisix_stream_connection_total Total number of connections handled per stream route in APISIX
+# TYPE apisix_stream_connection_total counter
+apisix_stream_connection_total{route="1"} 1
+```
diff --git a/t/APISIX.pm b/t/APISIX.pm
index b8e87a859..0143aa9d8 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -238,6 +238,15 @@ env PATH; # for searching external plugin runner's binary
env TEST_NGINX_HTML_DIR;
_EOC_
+
+ if ($version =~ m/\/apisix-nginx-module/) {
+ $main_config .= <<_EOC_;
+lua {
+ lua_shared_dict prometheus-metrics 15m;
+}
+_EOC_
+ }
+
# set default `timeout` to 5sec
my $timeout = $block->timeout // 5;
$block->set_value("timeout", $timeout);
@@ -480,7 +489,6 @@ _EOC_
lua_shared_dict plugin-limit-req 10m;
lua_shared_dict plugin-limit-count 10m;
lua_shared_dict plugin-limit-conn 10m;
- lua_shared_dict prometheus-metrics 10m;
lua_shared_dict internal-status 10m;
lua_shared_dict upstream-healthcheck 32m;
lua_shared_dict worker-events 10m;
@@ -526,6 +534,7 @@ _EOC_
balancer_by_lua_block {
apisix.http_balancer_phase()
}
+ }
_EOC_
} else {
$http_config .= <<_EOC_;
@@ -534,11 +543,13 @@ _EOC_
}
keepalive 32;
+ }
+
+ lua_shared_dict prometheus-metrics 10m;
_EOC_
}
$http_config .= <<_EOC_;
- }
$dubbo_upstream
diff --git a/t/cli/common.sh b/t/cli/common.sh
index 9da89a022..a4e2dea54 100644
--- a/t/cli/common.sh
+++ b/t/cli/common.sh
@@ -39,4 +39,5 @@ exit_if_not_customed_nginx() {
openresty -V 2>&1 | grep apisix-nginx-module || exit 0
}
+rm logs/error.log || true # clear previous error log
unset APISIX_PROFILE
diff --git a/t/cli/test_etcd_mtls.sh b/t/cli/test_etcd_mtls.sh
index 6741ea178..371330e93 100755
--- a/t/cli/test_etcd_mtls.sh
+++ b/t/cli/test_etcd_mtls.sh
@@ -115,7 +115,7 @@ if echo "$out" | grep "ouch"; then
exit 1
fi
-rm logs/error.log
+rm logs/error.log || true
make run
sleep 1
make stop
diff --git a/t/cli/test_prometheus_stream.sh b/t/cli/test_prometheus_stream.sh
new file mode 100755
index 000000000..347774b27
--- /dev/null
+++ b/t/cli/test_prometheus_stream.sh
@@ -0,0 +1,93 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+. ./t/cli/common.sh
+
+exit_if_not_customed_nginx
+
+echo "
+apisix:
+ enable_admin: true
+ stream_proxy:
+ tcp:
+ - addr: 9100
+stream_plugins:
+ - prometheus
+" > conf/config.yaml
+
+make run
+sleep 0.5
+
+curl -v -k -i -m 20 -o /dev/null -s -X PUT http://127.0.0.1:9080/apisix/admin/stream_routes/1 \
+ -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" \
+ -d '{
+ "plugins": {
+ "prometheus": {}
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": [{
+ "host": "127.0.0.1",
+ "port": 1995,
+ "weight": 1
+ }]
+ }
+ }'
+
+curl http://127.0.0.1:9100 || true
+sleep 1 # wait for sync
+
+out="$(curl http://127.0.0.1:9091/apisix/prometheus/metrics)"
+if ! echo "$out" | grep "apisix_stream_connection_total{route=\"1\"} 1" > /dev/null; then
+ echo "failed: prometheus can't work in stream subsystem"
+ exit 1
+fi
+
+make stop
+
+echo "passed: prometheus works when both http & stream are enabled"
+
+echo "
+apisix:
+ enable_admin: false
+ stream_proxy:
+ tcp:
+ - addr: 9100
+stream_plugins:
+ - prometheus
+" > conf/config.yaml
+
+make run
+sleep 0.5
+
+curl http://127.0.0.1:9100 || true
+sleep 1 # wait for sync
+
+out="$(curl http://127.0.0.1:9091/apisix/prometheus/metrics)"
+if ! echo "$out" | grep "apisix_stream_connection_total{route=\"1\"} 1" > /dev/null; then
+ echo "failed: prometheus can't work in stream subsystem"
+ exit 1
+fi
+
+if ! echo "$out" | grep "apisix_node_info{hostname=" > /dev/null; then
+ echo "failed: prometheus can't work in stream subsystem"
+ exit 1
+fi
+
+echo "passed: prometheus works when only stream is enabled"
diff --git a/t/stream-plugin/prometheus.t b/t/stream-plugin/prometheus.t
new file mode 100644
index 000000000..796b22028
--- /dev/null
+++ b/t/stream-plugin/prometheus.t
@@ -0,0 +1,162 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+BEGIN {
+ if ($ENV{TEST_NGINX_CHECK_LEAK}) {
+ $SkipReason = "unavailable for the hup tests";
+
+ } else {
+ $ENV{TEST_NGINX_USE_HUP} = 1;
+ undef $ENV{TEST_NGINX_USE_STAP};
+ }
+}
+
+use t::APISIX;
+
+my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
+my $version = eval { `$nginx_binary -V 2>&1` };
+
+if ($version !~ m/\/apisix-nginx-module/) {
+ plan(skip_all => "apisix-nginx-module not installed");
+} else {
+ plan('no_plan');
+}
+
+repeat_each(1);
+no_long_string();
+no_shuffle();
+no_root_location();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ my $extra_yaml_config = <<_EOC_;
+stream_plugins:
+ - mqtt-proxy
+ - prometheus
+_EOC_
+
+ $block->set_value("extra_yaml_config", $extra_yaml_config);
+
+ if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+ $block->set_value("no_error_log", "[error]");
+ }
+
+ if (!defined $block->request) {
+ $block->set_value("request", "GET /t");
+ }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: pre-create public API route
+--- config
+ location /t {
+ content_by_lua_block {
+ local data = {
+ {
+ url = "/apisix/admin/routes/metrics",
+ data = [[{
+ "plugins": {
+ "public-api": {}
+ },
+ "uri": "/apisix/prometheus/metrics"
+ }]]
+ },
+ {
+ url = "/apisix/admin/stream_routes/mqtt",
+ data = [[{
+ "plugins": {
+ "mqtt-proxy": {
+ "protocol_name": "MQTT",
+ "protocol_level": 4
+ },
+ "prometheus": {}
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": [{
+ "host": "127.0.0.1",
+ "port": 1995,
+ "weight": 1
+ }]
+ }
+ }]]
+ }
+ }
+
+ local t = require("lib.test_admin").test
+
+ for _, data in ipairs(data) do
+ local code, body = t(data.url, ngx.HTTP_PUT, data.data)
+ if code > 300 then
+ ngx.say(body)
+ return
+ end
+ end
+ }
+ }
+--- response_body
+
+
+
+=== TEST 2: hit
+--- stream_request eval
+"\x10\x0f\x00\x04\x4d\x51\x54\x54\x04\x02\x00\x3c\x00\x03\x66\x6f\x6f"
+--- stream_response
+hello world
+
+
+
+=== TEST 3: fetch the prometheus metric data
+--- request
+GET /apisix/prometheus/metrics
+--- response_body eval
+qr/apisix_stream_connection_total\{route="mqtt"\} 1/
+
+
+
+=== TEST 4: hit, error
+--- stream_request eval
+mmm
+--- error_log
+Received unexpected MQTT packet type+flags
+
+
+
+=== TEST 5: fetch the prometheus metric data
+--- request
+GET /apisix/prometheus/metrics
+--- response_body eval
+qr/apisix_stream_connection_total\{route="mqtt"\} 2/
+
+
+
+=== TEST 6: contains metrics from stub_status
+--- request
+GET /apisix/prometheus/metrics
+--- response_body eval
+qr/apisix_nginx_http_current_connections\{state="active"\} 1/
+
+
+
+=== TEST 7: contains basic metrics
+--- request
+GET /apisix/prometheus/metrics
+--- response_body eval
+qr/apisix_node_info\{hostname="[^"]+"\}/