Skip to content

Commit

Permalink
[WIP] feat(prometheus) add wasmx metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
casimiro committed Sep 26, 2024
1 parent 4e38b96 commit c4911ca
Show file tree
Hide file tree
Showing 9 changed files with 533 additions and 5 deletions.
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=55f0dc2325545e6f571d4dc491874771043895fa
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
188 changes: 188 additions & 0 deletions kong/plugins/prometheus/wasmx.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
local buffer = require "string.buffer"
local json_safe = require "cjson.safe"
local wasm = require "kong.runloop.wasm"
local wasmx_shm = require "resty.wasmx.shm"


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


local _M = {}


local function sorted_iter(ctx)
local v = ctx.t[ctx.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, keys = sorted_keys, i = 1 }
end


local function parse_key(key)
local name = key
local labels = {}

local header = {
pw = "pw.", -- proxy-wasm metrics
lua = "lua.", -- lua land metrics
wa = "wa." -- internal ngx_wasm_module metrics
}

-- TODO: label support for wa and lua metrics
-- local is_wa = #key > #header.wa and key:sub(0, #header.wa) == header.wa

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

local hs = is_lua and #header.lua or #header.pw -- wa's size == pw's size

if is_pw then
local second_dot_pos, _ = str_find(key, "%.", hs + 1)
local ns = str_sub(key, hs + 1, second_dot_pos - 1)

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

local first_match = #key

if patterns then
for _, pair in ipairs(patterns) do
local lkv, lv = str_match(key, pair.pattern)
if lkv then
local lk = str_sub(lkv, 0, str_find(lkv, "="))
local lk_start, _ = str_find(key, lk)

first_match = (lk_start < first_match) and lk_start or first_match

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

name = str_sub(key, 0, first_match - 1)
end
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

local slabels = buf:get()

if #slabels > 0 then
return slabels:sub(0, #slabels - 1) -- discard trailing comma
end

return slabels
end


local function serialize_metric(m)
local buf = buf_new()

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

for _, pair in ipairs(m.labels) do
local labels = pair[1]
local labeled_m = pair[2]
local slabels = serialize_labels(labels)

if m.type == "counter" or m.type == "gauge" then
if #slabels > 0 then
buf:put(fmt("\n%s{%s} %s", m.name, slabels, labeled_m.value))
else
buf:put(fmt("\n%s %s", m.name, labeled_m.value))
end

elseif m.type == "histogram" then
local c = 0

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 llabels = (#slabels > 0) and (slabels .. "," .. ubl) or ubl

c = c + bin.count

buf:put(fmt("\n%s{%s} %s", m.name, llabels, c))
end
end
end

buf:put("\n")

return buf:get()
end


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

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))

i = i + 1

if i % flush_after == 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

0 comments on commit c4911ca

Please sign in to comment.