Skip to content

Commit

Permalink
feat(prometheus) add wasmx metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
casimiro committed Sep 16, 2024
1 parent 4e38b96 commit 3973b56
Show file tree
Hide file tree
Showing 6 changed files with 495 additions and 4 deletions.
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
162 changes: 162 additions & 0 deletions kong/plugins/prometheus/wasmx.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
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 pretty = require("pl.pretty").write


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 _M = {}


local function parse_key(key)
-- [pw,lua,wa].namespace.metric_name_label1=v1_label2=v2
-- e.g. pw.filter_name.metric_name_label1=v1
-- e.g. lua.plugin_name.metric_name_label1=v1
-- e.g. wa.wasm_socket.metric_name_label1=v1
ngx.log(ngx.INFO, "attemtping to parse key: ", key)
local name
local labels = {}

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

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

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

if is_lua or is_wa then
name = key
-- label support NYI

elseif 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 = json_safe.decode(wasm.filters_by_name[ns].config)
local patterns = filter_config and filter_config.metrics.label_patterns

local first_match = #key

if patterns then
for i, pair in ipairs(patterns) do
local lkv, lv = str_match(key, pair.pattern)
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

labels[pair.label] = lv
end

name = str_sub(key, 0, first_match - 1)

else
name = key
end
else
-- unreachable
end

return name, labels
end


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

for l, v in pairs(labels) do
buf:put(fmt('%s="%s",', l, v))
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 labels, labeled_m in pairs(m.labels) do
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
for _, bin in ipairs(labeled_m.value) do
local ubl = fmt('le="%s"', bin.ub)

slabels = (#slabels > 0) and (slabels .. "," .. ubl) or ubl

buf:put(fmt("\n%s{%s} %s", m.name, slabels, bin.count))
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
metrics[key] = wasmx_shm.metrics:get_by_name(key, { prefix = false })
end

wasmx_shm.metrics:unlock()

for key, m in pairs(metrics) do
m.name, m.labels = parse_key(key)

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

for _, metric_by_label in 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
16 changes: 16 additions & 0 deletions kong/runloop/wasm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ 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

_M.filters_by_name[filter.name].config = filter.config
end
end

Expand Down Expand Up @@ -778,6 +780,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 +798,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 3973b56

Please sign in to comment.