Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(prometheus) add wasmx metrics #13681

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .requirements
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ ATC_ROUTER=ffd11db657115769bf94f0c4f915f98300bc26b6 # 1.6.2
SNAPPY=23b3286820105438c5dbb9bc22f1bb85c5812c8a # 1.2.0

KONG_MANAGER=nightly
NGX_WASM_MODULE=96b4e27e10c63b07ed40ea88a91c22f23981db35
NGX_WASM_MODULE=ad1e5c7a83b6c356a25df95f6ed6bf265c1943ba
WASMER=3.1.1
WASMTIME=23.0.2
V8=12.0.267.17
Expand Down
3 changes: 3 additions & 0 deletions changelog/unreleased/kong/prometheus-wasmx-metrics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
message: Expose WasmX metrics as part of the Prometheus plugin
type: feature
scope: Plugin
1 change: 1 addition & 0 deletions kong-3.9.0-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ build = {
["kong.plugins.prometheus.prometheus"] = "kong/plugins/prometheus/prometheus.lua",
["kong.plugins.prometheus.serve"] = "kong/plugins/prometheus/serve.lua",
["kong.plugins.prometheus.schema"] = "kong/plugins/prometheus/schema.lua",
["kong.plugins.prometheus.wasmx"] = "kong/plugins/prometheus/wasmx.lua",

["kong.plugins.session.handler"] = "kong/plugins/session/handler.lua",
["kong.plugins.session.schema"] = "kong/plugins/session/schema.lua",
Expand Down
8 changes: 6 additions & 2 deletions kong/plugins/prometheus/exporter.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
local balancer = require "kong.runloop.balancer"
local yield = require("kong.tools.yield").yield
local wasm = require "kong.plugins.prometheus.wasmx"


local kong = kong
local ngx = ngx
local get_phase = ngx.get_phase
local lower = string.lower
local ngx_timer_pending_count = ngx.timer.pending_count
local ngx_timer_running_count = ngx.timer.running_count
local balancer = require("kong.runloop.balancer")
local yield = require("kong.tools.yield").yield
local get_all_upstreams = balancer.get_all_upstreams
if not balancer.get_all_upstreams then -- API changed since after Kong 2.5
get_all_upstreams = require("kong.runloop.balancer.upstreams").get_all_upstreams
Expand Down Expand Up @@ -517,6 +520,7 @@ local function metric_data(write_fn)
-- notify the function if prometheus plugin is enabled,
-- so that it can avoid exporting unnecessary metrics if not
prometheus:metric_data(write_fn, not IS_PROMETHEUS_ENABLED)
wasm.metric_data()
end

local function collect()
Expand Down
204 changes: 204 additions & 0 deletions kong/plugins/prometheus/wasmx.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
local buffer = require "string.buffer"
local wasm = require "kong.runloop.wasm"
local wasmx_shm


local fmt = string.format
local str_find = string.find
local str_match = string.match
local str_sub = string.sub
local table_insert = table.insert
local table_sort = table.sort
local buf_new = buffer.new
local ngx_say = ngx.say


local _M = {}


local FLUSH_EVERY = 100


local function sorted_iter(ctx)
local v = ctx.t[ctx.sorted_keys[ctx.i]]
ctx.i = ctx.i + 1

return v
end


local function sorted_pairs(t)
local sorted_keys = {}

for k, _ in pairs(t) do
table_insert(sorted_keys, k)
end

table_sort(sorted_keys)

return sorted_iter, { t = t, sorted_keys = sorted_keys, i = 1 }
end


local function parse_pw_key(key)
local name = key
local labels = {}
local header_size = 3 -- pw.
local first_label = #key

local second_dot_pos, _ = str_find(key, "%.", header_size + 1)
local filter_name = str_sub(key, header_size + 1, second_dot_pos - 1)

local filter_config = wasm.filters_by_name[filter_name].config or {}
local patterns = filter_config.pw_metrics
and filter_config.pw_metrics.label_patterns or {}

for _, pair in ipairs(patterns) do
local label_kv, label_v = str_match(key, pair.pattern)
if label_kv then
local label_k = str_sub(label_kv, 0, str_find(label_kv, "="))
local label_k_start, _ = str_find(key, label_k)

first_label = (label_k_start < first_label) and label_k_start or first_label

table_insert(labels, { pair.label, label_v })
end
end

if first_label ~= #key then
name = str_sub(key, 0, first_label - 1)
end

return name, labels
end


local function parse_key(key)
-- TODO: parse wa. (WasmX metrics) and lua. (metrics defined in Lua land)
local header = { pw = "pw." }

local name = key
local labels = {}

local is_pw = #key > #header.pw and key:sub(0, #header.pw) == header.pw

if is_pw then
name, labels = parse_pw_key(key)
end

name = name:gsub("%.", "_")

return name, labels
end


local function serialize_labels(labels)
local buf = buf_new()

for _, pair in ipairs(labels) do
buf:put(fmt(',%s="%s"', pair[1], pair[2]))
end

buf:get(1) -- discard trailing comma

return "{" .. buf:get() .. "}"
end


local function serialize_metric(m, buf)
buf:put(fmt("# HELP %s\n# TYPE %s %s", m.name, m.name, m.type))

if m.type == "histogram" then
local h_count, sum = 0, 0

for _, pair in ipairs(m.labels) do
local labels, labeled_m = pair[1], pair[2]
local slabels = (#labels > 0) and serialize_labels(labels) or ""
local l_count = 0

slabels = (#labels > 0) and (slabels:sub(1, #slabels - 1) .. ",") or "{"

for _, bin in ipairs(labeled_m.value) do
local ub = (bin.ub ~= 4294967295) and bin.ub or "+Inf"
local ubl = fmt('le="%s"', ub)
local hlabels = slabels .. ubl .. "}"

h_count = h_count + bin.count
l_count = l_count + bin.count

buf:put(fmt("\n%s%s %s", m.name, hlabels, l_count))
end

sum = sum + labeled_m.sum
end

buf:put(fmt("\n%s_sum %s", m.name, sum))
buf:put(fmt("\n%s_count %s", m.name, h_count))

else
for _, pair in ipairs(m.labels) do
local labels, labeled_m = pair[1], pair[2]
local slabels = (#labels > 0) and serialize_labels(labels) or ""

buf:put(fmt("\n%s%s %s", m.name, slabels, labeled_m.value))
end
end

buf:put("\n")
end


_M.metric_data = function()
local i = 0
local metrics = {}
local parsed = {}
local buf = buf_new()

-- delayed require of the WasmX module, to ensure it is loaded
-- after ngx_wasm_module.so is loaded.
if not wasmx_shm then
local ok, _wasmx_shm = pcall(require, "resty.wasmx.shm")
if ok then
wasmx_shm = _wasmx_shm
end
end

if not wasmx_shm then
return
end

wasmx_shm.metrics:lock()

for key in wasmx_shm.metrics:iterate_keys() do
table_insert(metrics, { key, wasmx_shm.metrics:get_by_name(key, { prefix = false })})
end

wasmx_shm.metrics:unlock()

-- in WasmX the different labels of a metric are stored as separate metrics
-- aggregate those separate metrics into a single one
for _, pair in ipairs(metrics) do
local key = pair[1]
local m = pair[2]
local name, labels = parse_key(key)

parsed[name] = parsed[name] or { name = name, type = m.type, labels = {} }

table_insert(parsed[name].labels, { labels, m })
end

for metric_by_label in sorted_pairs(parsed) do
buf:put(serialize_metric(metric_by_label, buf))

i = i + 1

if i % FLUSH_EVERY == 0 then
ngx_say(buf:get())
end
end

ngx_say(buf:get())
end


return _M
17 changes: 17 additions & 0 deletions kong/runloop/wasm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,8 @@ local function rebuild_state(db, version, old_state)

for _, filter in ipairs(chain.filters) do
if filter.enabled then
_M.filters_by_name[filter.name].config = cjson_decode(filter.config) or filter.config

-- Serialize all JSON configurations up front
--
-- NOTE: there is a subtle difference between a raw, non-JSON filter
Expand All @@ -415,6 +417,7 @@ local function rebuild_state(db, version, old_state)
if filter.config ~= nil and type(filter.config) ~= "string" then
filter.config = cjson_encode(filter.config)
end

end
end

Expand Down Expand Up @@ -778,6 +781,13 @@ local function register_property_handlers()
return ok, value, const
end)

properties.add_getter("kong.route_name", function(_, _, ctx)
local value = ctx.route and ctx.route.name
local ok = value ~= nil
local const = ok
return ok, value, const
end)

properties.add_getter("kong.service.response.status", function(kong)
return true, kong.service.response.get_status(), false
end)
Expand All @@ -789,6 +799,13 @@ local function register_property_handlers()
return ok, value, const
end)

properties.add_getter("kong.service_name", function(_, _, ctx)
local value = ctx.service and ctx.service.name
local ok = value ~= nil
local const = ok
return ok, value, const
end)

properties.add_getter("kong.version", function(kong)
return true, kong.version, true
end)
Expand Down
Loading
Loading