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

perf(plugin/prometheus): generate metrics output data with string.buffer #11065

Merged
merged 15 commits into from Jun 25, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -54,6 +54,7 @@
- The Prometheus plugin has been optimized to reduce proxy latency impacts during scraping.
[#10949](https://github.com/Kong/kong/pull/10949)
[#11040](https://github.com/Kong/kong/pull/11040)
[#11065](https://github.com/Kong/kong/pull/11065)

### Fixes

Expand Down
13 changes: 8 additions & 5 deletions kong/plugins/prometheus/api.lua
@@ -1,17 +1,20 @@
local buffer = require("string.buffer")
local exporter = require "kong.plugins.prometheus.exporter"
local tbl_insert = table.insert
local tbl_concat = table.concat


local printable_metric_data = function(_)
local buffer = {}
local buf = buffer.new(4096)
-- override write_fn, since stream_api expect response to returned
-- instead of ngx.print'ed
exporter.metric_data(function(new_metric_data)
tbl_insert(buffer, tbl_concat(new_metric_data, ""))
buf:put(new_metric_data)
end)

return tbl_concat(buffer, "")
local str = buf:get()

buf:free()

return str
end


Expand Down
2 changes: 1 addition & 1 deletion kong/plugins/prometheus/exporter.lua
Expand Up @@ -90,7 +90,7 @@ end

local function init()
local shm = "prometheus_metrics"
if not ngx.shared.prometheus_metrics then
if not ngx.shared[shm] then
kong.log.err("prometheus: ngx shared dict 'prometheus_metrics' not found")
return
end
Expand Down
46 changes: 27 additions & 19 deletions kong/plugins/prometheus/prometheus.lua
Expand Up @@ -68,9 +68,8 @@ local ipairs = ipairs
local pairs = pairs
local tostring = tostring
local tonumber = tonumber
local st_format = string.format
local table_sort = table.sort
local tb_clear = require("table.clear")
local tb_new = require("table.new")
local yield = require("kong.tools.utils").yield


Expand All @@ -90,6 +89,9 @@ local TYPE_LITERAL = {
-- Default metric name size for string.buffer.new()
local NAME_BUFFER_SIZE_HINT = 256

-- Default metric data size for string.buffer.new()
local DATA_BUFFER_SIZE_HINT = 4096

-- Default name for error metric incremented by this library.
local DEFAULT_ERROR_METRIC_NAME = "nginx_metric_errors_total"

Expand Down Expand Up @@ -399,7 +401,7 @@ local function lookup_or_create(self, label_values)
local bucket_pref
if self.label_count > 0 then
-- strip last }
bucket_pref = self.name .. "_bucket" .. string.sub(labels, 1, #labels-1) .. ","
bucket_pref = self.name .. "_bucket" .. string.sub(labels, 1, -2) .. ","
else
bucket_pref = self.name .. "_bucket{"
end
Expand Down Expand Up @@ -701,8 +703,9 @@ end
-- Returns:
-- an object that should be used to register metrics.
function Prometheus.init(dict_name, options_or_prefix)
if ngx.get_phase() ~= 'init' and ngx.get_phase() ~= 'init_worker' and
ngx.get_phase() ~= 'timer' then
local phase = ngx.get_phase()
if phase ~= 'init' and phase ~= 'init_worker' and
phase ~= 'timer' then
error('Prometheus.init can only be called from ' ..
'init_by_lua_block, init_worker_by_lua_block or timer' , 2)
end
Expand Down Expand Up @@ -912,26 +915,29 @@ function Prometheus:metric_data(write_fn, local_only)
-- numerical order of their label values.
table_sort(keys)

local seen_metrics = {}
local output = {}
local seen_metrics = tb_new(0, count)

-- the output is an integral string, not an array any more
local output = buffer.new(DATA_BUFFER_SIZE_HINT)
local output_count = 0

local function buffered_print(data)
if data then
local function buffered_print(fmt, ...)
if fmt then
output_count = output_count + 1
output[output_count] = data
output:putf(fmt, ...)
end

if output_count >= 100 or not data then
write_fn(output)
if output_count >= 100 or not fmt then
write_fn(output:get()) -- consume the whole buffer
output_count = 0
tb_clear(output)
end
end

for _, key in ipairs(keys) do
for i = 1, count do
yield()

local key = keys[i]

local value, err
local is_local_metrics = true
value = self.local_metrics[key]
Expand All @@ -950,26 +956,28 @@ function Prometheus:metric_data(write_fn, local_only)
local m = self.registry[short_name]
if m then
if m.help then
buffered_print(st_format("# HELP %s%s %s\n",
self.prefix, short_name, m.help))
buffered_print("# HELP %s%s %s\n",
self.prefix, short_name, m.help)
end
if m.typ then
buffered_print(st_format("# TYPE %s%s %s\n",
self.prefix, short_name, TYPE_LITERAL[m.typ]))
buffered_print("# TYPE %s%s %s\n",
self.prefix, short_name, TYPE_LITERAL[m.typ])
end
end
seen_metrics[short_name] = true
end
if not is_local_metrics then -- local metrics is always a gauge
key = fix_histogram_bucket_labels(key)
end
buffered_print(st_format("%s%s %s\n", self.prefix, key, value))
buffered_print("%s%s %s\n", self.prefix, key, value)

::continue::

end

buffered_print(nil)

output:free()
end

-- Present all metrics in a text format compatible with Prometheus.
Expand Down