From 122777cffafdf3dfe703595e07f330c7a6fa7b00 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Fri, 24 May 2019 22:38:40 +0800 Subject: [PATCH] feature: implemented plugin `prometheus`. --- COPYRIGHT | 9 + conf/config.yaml | 1 + conf/nginx.conf | 1 + lua/apisix.lua | 26 +- lua/apisix/core.lua | 15 +- lua/apisix/plugin.lua | 45 +- lua/apisix/plugins/prometheus.lua | 37 ++ .../plugins/prometheus/base_prometheus.lua | 553 ++++++++++++++++++ lua/apisix/plugins/prometheus/exporter.lua | 46 ++ 9 files changed, 711 insertions(+), 22 deletions(-) create mode 100644 lua/apisix/plugins/prometheus.lua create mode 100644 lua/apisix/plugins/prometheus/base_prometheus.lua create mode 100644 lua/apisix/plugins/prometheus/exporter.lua diff --git a/COPYRIGHT b/COPYRIGHT index e30f090d..6edefda6 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -37,3 +37,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. %%%%%%%%% + +prometheus + +https://github.com/knyar/nginx-lua-prometheus +https://github.com/knyar/nginx-lua-prometheus/blob/master/README.md + +Licensed under MIT license. + +%%%%%%%%% diff --git a/conf/config.yaml b/conf/config.yaml index 15cb1684..c9dc0c0e 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -7,3 +7,4 @@ plugins: - limit-req - limit-count - key-auth + - prometheus diff --git a/conf/nginx.conf b/conf/nginx.conf index f30661cd..7e8824ff 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -23,6 +23,7 @@ http { lua_shared_dict plugin-limit-req 10m; lua_shared_dict plugin-limit-count 10m; + lua_shared_dict prometheus_metrics 10m; lua_ssl_verify_depth 5; ssl_session_timeout 86400; diff --git a/lua/apisix.lua b/lua/apisix.lua index 3e34818a..1a4156f0 100644 --- a/lua/apisix.lua +++ b/lua/apisix.lua @@ -24,8 +24,7 @@ end function _M.init_worker() require("apisix.route").init_worker() require("apisix.balancer").init_worker() - - core.lrucache.global("/local_plugins", nil, plugin_module.load) + require("apisix.plugin").init_worker() end @@ -45,7 +44,7 @@ local function run_plugin(phase, filter_plugins, api_ctx) local phase_fun = plugin[phase] if phase_fun then local code, body = phase_fun(filter_plugins[i + 1], api_ctx) - if type(code) == "number" or body then + if phase ~= "log" and type(code) == "number" or body then core.response.exit(code, body) end end @@ -67,14 +66,27 @@ function _M.rewrite_phase() local uri = core.request.var(api_ctx, "uri") -- local host = core.request.var(api_ctx, "host") -- todo: support host + -- todo: try to run the api in + local api_routes = plugin_module.api_routes() + if api_routes then + local r3 = require("resty.r3").new(api_routes) + -- don't forget!!! + r3:compile() + + -- dispatch + local ok = r3:dispatch(method, uri, api_ctx) + if ok then + core.log.warn("finish api route") + return + end + end + local ok = router():dispatch(method, uri, api_ctx) if not ok then core.log.warn("not find any matched route") return core.response.exit(404) end - local local_plugins = core.lrucache.global("/local_plugins", nil, - plugin_module.load) if api_ctx.matched_route.service_id then error("todo: suppport to use service fetch user config") else @@ -84,11 +96,9 @@ function _M.rewrite_phase() end local filter_plugins = plugin_module.filter_plugin( - api_ctx.matched_route, local_plugins) + api_ctx.matched_route) api_ctx.filter_plugins = filter_plugins - -- todo: fetch the upstream node status, it may be stored in - -- different places. run_plugin("rewrite", filter_plugins, api_ctx) end diff --git a/lua/apisix/core.lua b/lua/apisix/core.lua index 90282828..3daa2bed 100644 --- a/lua/apisix/core.lua +++ b/lua/apisix/core.lua @@ -11,6 +11,7 @@ local function serialise_obj(data) return data end + local function tab_clone_with_serialise(data) if type(data) ~= "table" then return data @@ -45,13 +46,15 @@ return { decode = require("cjson.safe").decode, }, table = { - new = require("table.new"), - clear = require("table.clear"), - nkeys = require("table.nkeys"), + new = require("table.new"), + clear = require("table.clear"), + nkeys = require("table.nkeys"), + insert = table.insert, + concat = table.concat, }, - request = require("apisix.core.request"), + request = require("apisix.core.request"), response = require("apisix.core.response"), - typeof = require("apisix.core.typeof"), + typeof = require("apisix.core.typeof"), lrucache = require("apisix.core.lrucache"), - schema = require("apisix.core.schema"), + schema = require("apisix.core.schema"), } diff --git a/lua/apisix/plugin.lua b/lua/apisix/plugin.lua index 1d1bb37e..af545d30 100644 --- a/lua/apisix/plugin.lua +++ b/lua/apisix/plugin.lua @@ -6,9 +6,14 @@ local sort_tab = table.sort local pcall = pcall local ipairs = ipairs local type = type +local local_supported_plugins = {} -local _M = {version = 0.1} +local _M = { + version = 0.1, + load_times = 0, + plugins = local_supported_plugins, +} local function sort_plugin(l, r) @@ -16,13 +21,12 @@ local function sort_plugin(l, r) end -function _M.load() +local function load() local plugin_names = core.config.local_conf().plugins if not plugin_names then return nil, "failed to read plugin list form local file" end - local plugins = core.table.new(#plugin_names, 0) for _, name in ipairs(plugin_names) do local pkg_name = "apisix.plugins." .. name pkg_loaded[pkg_name] = nil @@ -42,7 +46,7 @@ function _M.load() else plugin.name = name - insert_tab(plugins, plugin) + insert_tab(local_supported_plugins, plugin) end if plugin.init then @@ -51,15 +55,35 @@ function _M.load() end -- sort by plugin's priority - if #plugins > 1 then - sort_tab(plugins, sort_plugin) + if #local_supported_plugins > 1 then + sort_tab(local_supported_plugins, sort_plugin) end - return plugins + _M.load_times = _M.load_times + 1 + return local_supported_plugins +end +_M.load = load + + +function _M.api_routes() + local routes = {} + for _, plugin in ipairs(_M.plugins) do + local api_fun = plugin.api + if api_fun then + local api_routes = api_fun() + for _, route in ipairs(api_routes) do + core.table.insert(routes, {route.methods, route.uri, + route.handler}) + end + end + end + + -- core.log.warn("api routes: ", core.json.encode(routes, true)) + return routes end -function _M.filter_plugin(user_routes, local_supported_plugins) +function _M.filter_plugin(user_routes) -- todo: reuse table local plugins = core.table.new(#local_supported_plugins * 2, 0) local user_plugin_conf = user_routes.value.plugin_config @@ -78,4 +102,9 @@ function _M.filter_plugin(user_routes, local_supported_plugins) end +function _M.init_worker() + load() +end + + return _M diff --git a/lua/apisix/plugins/prometheus.lua b/lua/apisix/plugins/prometheus.lua new file mode 100644 index 00000000..f0731787 --- /dev/null +++ b/lua/apisix/plugins/prometheus.lua @@ -0,0 +1,37 @@ +local exporter = require("apisix.plugins.prometheus.exporter") +local core = require("apisix.core") +local plugin_name = "prometheus" + + +local _M = { + version = 0.1, + priority = 500, + name = plugin_name, + init = exporter.init, + log = exporter.log, +} + + +function _M.check_args(conf) + return true +end + + +function _M.api() + return { + { + methods = {"GET"}, + uri = "/apisix.com/prometheus/metrics", + handler = exporter.collect + } + } +end + + +-- only for test +-- function _M.access() +-- ngx.say(exporter.metric_data()) +-- end + + +return _M diff --git a/lua/apisix/plugins/prometheus/base_prometheus.lua b/lua/apisix/plugins/prometheus/base_prometheus.lua new file mode 100644 index 00000000..54dc0fa4 --- /dev/null +++ b/lua/apisix/plugins/prometheus/base_prometheus.lua @@ -0,0 +1,553 @@ +-- This is a vendored dependency. +-- Original Source: https://github.com/knyar/nginx-lua-prometheus +-- vim: ts=2:sw=2:sts=2:expandtab +-- +-- This module uses a single dictionary shared between Nginx workers to keep +-- all metrics. Each counter is stored as a separate entry in that dictionary, +-- which allows us to increment them using built-in `incr` method. +-- +-- Prometheus requires that (a) all samples for a given metric are presented +-- as one uninterrupted group, and (b) buckets of a histogram appear in +-- increasing numerical order. We satisfy that by carefully constructing full +-- metric names (i.e. metric name along with all labels) so that they meet +-- those requirements while being sorted alphabetically. In particular: +-- +-- * all labels for a given metric are presented in reproducible order (the one +-- used when labels were declared). "le" label for histogram metrics always +-- goes last; +-- * bucket boundaries (which are exposed as values of the "le" label) are +-- presented as floating point numbers with leading and trailing zeroes. +-- Number of of zeroes is determined for each bucketer automatically based on +-- bucket boundaries; +-- * internally "+Inf" bucket is stored as "Inf" (to make it appear after +-- all numeric buckets), and gets replaced by "+Inf" just before we +-- expose the metrics. +-- +-- For example, if you define your bucket boundaries as {0.00005, 10, 1000} +-- then we will keep the following samples for a metric `m1` with label +-- `site` set to `site1`: +-- +-- m1_bucket{site="site1",le="0000.00005"} +-- m1_bucket{site="site1",le="0010.00000"} +-- m1_bucket{site="site1",le="1000.00000"} +-- m1_bucket{site="site1",le="Inf"} +-- m1_count{site="site1"} +-- m1_sum{site="site1"} +-- +-- "Inf" will be replaced by "+Inf" while publishing metrics. +-- +-- You can find the latest version and documentation at +-- https://github.com/knyar/nginx-lua-prometheus +-- Released under MIT license. + + +-- Default set of latency buckets, 5ms to 10s: +local DEFAULT_BUCKETS = {0.005, 0.01, 0.02, 0.03, 0.05, 0.075, 0.1, 0.2, 0.3, + 0.4, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5, 10} + +-- Metric is a "parent class" for all metrics. +local Metric = {} +function Metric:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o +end + +-- Checks that the right number of labels values have been passed. +-- +-- Args: +-- label_values: an array of label values. +-- +-- Returns: +-- an error message or nil +function Metric:check_label_values(label_values) + if self.label_names == nil and label_values == nil then + return + elseif self.label_names == nil and label_values ~= nil then + return "Expected no labels for " .. self.name .. ", got " .. #label_values + elseif label_values == nil and self.label_names ~= nil then + return "Expected " .. #self.label_names .. " labels for " .. + self.name .. ", got none" + elseif #self.label_names ~= #label_values then + return "Wrong number of labels for " .. self.name .. ". Expected " .. + #self.label_names .. ", got " .. #label_values + else + for i, k in ipairs(self.label_names) do + if label_values[i] == nil then + return "Unexpected nil value for label " .. k .. " of " .. self.name + end + end + end +end + +local Counter = Metric:new() +-- Increase a given counter by `value` +-- +-- Args: +-- value: (number) a value to add to the counter. Defaults to 1 if skipped. +-- label_values: an array of label values. Can be nil (i.e. not defined) for +-- metrics that have no labels. +function Counter:inc(value, label_values) + local err = self:check_label_values(label_values) + if err ~= nil then + self.prometheus:log_error(err) + return + end + self.prometheus:inc(self.name, self.label_names, label_values, value or 1) +end + +local Gauge = Metric:new() +-- Set a given gauge to `value` +-- +-- Args: +-- value: (number) a value to set the gauge to. Should be defined. +-- label_values: an array of label values. Can be nil (i.e. not defined) for +-- metrics that have no labels. +function Gauge:set(value, label_values) + if value == nil then + self.prometheus:log_error("No value passed for " .. self.name) + return + end + local err = self:check_label_values(label_values) + if err ~= nil then + self.prometheus:log_error(err) + return + end + self.prometheus:set(self.name, self.label_names, label_values, value) +end + +local Histogram = Metric:new() +-- Record a given value in a histogram. +-- +-- Args: +-- value: (number) a value to record. Should be defined. +-- label_values: an array of label values. Can be nil (i.e. not defined) for +-- metrics that have no labels. +function Histogram:observe(value, label_values) + if value == nil then + self.prometheus:log_error("No value passed for " .. self.name) + return + end + local err = self:check_label_values(label_values) + if err ~= nil then + self.prometheus:log_error(err) + return + end + self.prometheus:histogram_observe(self.name, self.label_names, label_values, value) +end + +local Prometheus = {} +Prometheus.__index = Prometheus +Prometheus.initialized = false + +-- Generate full metric name that includes all labels. +-- +-- Args: +-- name: string +-- label_names: (array) a list of label keys. +-- label_values: (array) a list of label values. +-- Returns: +-- (string) full metric name. +local function full_metric_name(name, label_names, label_values) + if not label_names then + return name + end + local label_parts = {} + for idx, key in ipairs(label_names) do + local label_value = (string.format("%s", label_values[idx]) + :gsub("[^\032-\126]", "") -- strip non-printable characters + :gsub("\\", "\\\\") + :gsub('"', '\\"')) + table.insert(label_parts, key .. '="' .. label_value .. '"') + end + return name .. "{" .. table.concat(label_parts, ",") .. "}" +end + +-- Construct bucket format for a list of buckets. +-- +-- This receives a list of buckets and returns a sprintf template that should +-- be used for bucket boundaries to make them come in increasing order when +-- sorted alphabetically. +-- +-- To re-phrase, this is where we detect how many leading and trailing zeros we +-- need. +-- +-- Args: +-- buckets: a list of buckets +-- +-- Returns: +-- (string) a sprintf template. +local function construct_bucket_format(buckets) + local max_order = 1 + local max_precision = 1 + for _, bucket in ipairs(buckets) do + assert(type(bucket) == "number", "bucket boundaries should be numeric") + -- floating point number with all trailing zeros removed + local as_string = string.format("%f", bucket):gsub("0*$", "") + local dot_idx = as_string:find(".", 1, true) + max_order = math.max(max_order, dot_idx - 1) + max_precision = math.max(max_precision, as_string:len() - dot_idx) + end + return "%0" .. (max_order + max_precision + 1) .. "." .. max_precision .. "f" +end + +-- Extract short metric name from the full one. +-- +-- Args: +-- full_name: (string) full metric name that can include labels. +-- +-- Returns: +-- (string) short metric name with no labels. For a `*_bucket` metric of +-- histogram the _bucket suffix will be removed. +local function short_metric_name(full_name) + local labels_start, _ = full_name:find("{") + if not labels_start then + -- no labels + return full_name + end + local suffix_idx, _ = full_name:find("_bucket{") + if suffix_idx and full_name:find("le=") then + -- this is a histogram metric + return full_name:sub(1, suffix_idx - 1) + end + -- this is not a histogram metric + return full_name:sub(1, labels_start - 1) +end + +-- Makes a shallow copy of a table +local function copy_table(table) + local new = {} + if table ~= nil then + for k, v in ipairs(table) do + new[k] = v + end + end + return new +end + +-- Check metric name and label names for correctness. +-- +-- Regular expressions to validate metric and label names are +-- documented in https://prometheus.io/docs/concepts/data_model/ +-- +-- Args: +-- metric_name: (string) metric name. +-- label_names: label names (array of strings). +-- +-- Returns: +-- Either an error string, or nil of no errors were found. +local function check_metric_and_label_names(metric_name, label_names) + if not metric_name:match("^[a-zA-Z_:][a-zA-Z0-9_:]*$") then + return "Metric name '" .. metric_name .. "' is invalid" + end + for _, label_name in ipairs(label_names or {}) do + if label_name == "le" then + return "Invalid label name 'le' in " .. metric_name + end + if not label_name:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then + return "Metric '" .. metric_name .. "' label name '" .. label_name .. + "' is invalid" + end + end +end + +-- Initialize the module. +-- +-- This should be called once from the `init_by_lua` section in nginx +-- configuration. +-- +-- Args: +-- dict_name: (string) name of the nginx shared dictionary which will be +-- used to store all metrics +-- prefix: (optional string) if supplied, prefix is added to all +-- metric names on output +-- +-- Returns: +-- an object that should be used to register metrics. +function Prometheus.init(dict_name, prefix) + local self = setmetatable({}, Prometheus) + dict_name = dict_name or "prometheus_metrics" + self.dict = ngx.shared[dict_name] + if self.dict == nil then + ngx.log(ngx.ERR, + "Dictionary '", dict_name, "' does not seem to exist. ", + "Please define the dictionary using `lua_shared_dict`.") + return self + end + self.help = {} + if prefix then + self.prefix = prefix + else + self.prefix = '' + end + self.type = {} + self.registered = {} + self.buckets = {} + self.bucket_format = {} + self.initialized = true + + self:counter("nginx_metric_errors_total", + "Number of nginx-lua-prometheus errors") + self.dict:set("nginx_metric_errors_total", 0) + return self +end + +function Prometheus:log_error(...) + ngx.log(ngx.ERR, ...) + self.dict:incr("nginx_metric_errors_total", 1) +end + +function Prometheus:log_error_kv(key, value, err) + self:log_error( + "Error while setting '", key, "' to '", value, "': '", err, "'") +end + +-- Register a counter. +-- +-- Args: +-- name: (string) name of the metric. Required. +-- description: (string) description of the metric. Will be used for the HELP +-- comment on the metrics page. Optional. +-- label_names: array of strings, defining a list of metrics. Optional. +-- +-- Returns: +-- a Counter object. +function Prometheus:counter(name, description, label_names) + if not self.initialized then + ngx.log(ngx.ERR, "Prometheus module has not been initialized") + return + end + + local err = check_metric_and_label_names(name, label_names) + if err ~= nil then + self:log_error(err) + return + end + + if self.registered[name] then + self:log_error("Duplicate metric " .. name) + return + end + self.registered[name] = true + self.help[name] = description + self.type[name] = "counter" + + return Counter:new{name=name, label_names=label_names, prometheus=self} +end + +-- Register a gauge. +-- +-- Args: +-- name: (string) name of the metric. Required. +-- description: (string) description of the metric. Will be used for the HELP +-- comment on the metrics page. Optional. +-- label_names: array of strings, defining a list of metrics. Optional. +-- +-- Returns: +-- a Gauge object. +function Prometheus:gauge(name, description, label_names) + if not self.initialized then + ngx.log(ngx.ERR, "Prometheus module has not been initialized") + return + end + + local err = check_metric_and_label_names(name, label_names) + if err ~= nil then + self:log_error(err) + return + end + + if self.registered[name] then + self:log_error("Duplicate metric " .. name) + return + end + self.registered[name] = true + self.help[name] = description + self.type[name] = "gauge" + + return Gauge:new{name=name, label_names=label_names, prometheus=self} +end + + +-- Register a histogram. +-- +-- Args: +-- name: (string) name of the metric. Required. +-- description: (string) description of the metric. Will be used for the HELP +-- comment on the metrics page. Optional. +-- label_names: array of strings, defining a list of metrics. Optional. +-- buckets: array if numbers, defining bucket boundaries. Optional. +-- +-- Returns: +-- a Histogram object. +function Prometheus:histogram(name, description, label_names, buckets) + if not self.initialized then + ngx.log(ngx.ERR, "Prometheus module has not been initialized") + return + end + + local err = check_metric_and_label_names(name, label_names) + if err ~= nil then + self:log_error(err) + return + end + + for _, suffix in ipairs({"", "_bucket", "_count", "_sum"}) do + if self.registered[name .. suffix] then + self:log_error("Duplicate metric " .. name .. suffix) + return + end + self.registered[name .. suffix] = true + end + self.help[name] = description + self.type[name] = "histogram" + + self.buckets[name] = buckets or DEFAULT_BUCKETS + self.bucket_format[name] = construct_bucket_format(self.buckets[name]) + + return Histogram:new{name=name, label_names=label_names, prometheus=self} +end + +-- Set a given dictionary key. +-- This overwrites existing values, so it should only be used when initializing +-- metrics or when explicitely overwriting the previous value of a metric. +function Prometheus:set_key(key, value) + local ok, err = self.dict:safe_set(key, value) + if not ok then + self:log_error_kv(key, value, err) + end +end + +-- Increment a given counter by `value`. +-- +-- Args: +-- name: (string) short metric name without any labels. +-- label_names: (array) a list of label keys. +-- label_values: (array) a list of label values. +-- value: (number) value to add. Optional, defaults to 1. +function Prometheus:inc(name, label_names, label_values, value) + local key = full_metric_name(name, label_names, label_values) + if value == nil then value = 1 end + if value < 0 then + self:log_error_kv(key, value, "Value should not be negative") + return + end + + local newval, err = self.dict:incr(key, value) + if newval then + return + end + -- Yes, this looks like a race, so I guess we might under-report some values + -- when multiple workers simultaneously try to create the same metric. + -- Hopefully this does not happen too often (shared dictionary does not get + -- reset during configuation reload). + if err == "not found" then + self:set_key(key, value) + return + end + -- Unexpected error + self:log_error_kv(key, value, err) +end + +-- Set the current value of a gauge to `value` +-- +-- Args: +-- name: (string) short metric name without any labels. +-- label_names: (array) a list of label keys. +-- label_values: (array) a list of label values. +-- value: (number) the new value for the gauge. +function Prometheus:set(name, label_names, label_values, value) + local key = full_metric_name(name, label_names, label_values) + self:set_key(key, value) +end + +-- Record a given value into a histogram metric. +-- +-- Args: +-- name: (string) short metric name without any labels. +-- label_names: (array) a list of label keys. +-- label_values: (array) a list of label values. +-- value: (number) value to observe. +function Prometheus:histogram_observe(name, label_names, label_values, value) + self:inc(name .. "_count", label_names, label_values, 1) + self:inc(name .. "_sum", label_names, label_values, value) + + -- we are going to mutate arrays of label names and values, so create a copy. + local l_names = copy_table(label_names) + local l_values = copy_table(label_values) + + -- Last bucket. Note, that the label value is "Inf" rather than "+Inf" + -- required by Prometheus. This is necessary for this bucket to be the last + -- one when all metrics are lexicographically sorted. "Inf" will get replaced + -- by "+Inf" in Prometheus:collect(). + table.insert(l_names, "le") + table.insert(l_values, "Inf") + self:inc(name .. "_bucket", l_names, l_values, 1) + + local label_count = #l_names + for _, bucket in ipairs(self.buckets[name]) do + if value <= bucket then + -- last label is now "le" + l_values[label_count] = self.bucket_format[name]:format(bucket) + self:inc(name .. "_bucket", l_names, l_values, 1) + end + end +end + +-- Prometheus compatible metric data as an array of strings. +-- +-- Returns: +-- Array of strings with all metrics in a text format compatible with +-- Prometheus. +function Prometheus:metric_data() + if not self.initialized then + ngx.log(ngx.ERR, "Prometheus module has not been initialized") + return + end + + local keys = self.dict:get_keys(0) + -- Prometheus server expects buckets of a histogram to appear in increasing + -- numerical order of their label values. + table.sort(keys) + + local seen_metrics = {} + local output = {} + for _, key in ipairs(keys) do + local value, err = self.dict:get(key) + if value then + local short_name = short_metric_name(key) + if not seen_metrics[short_name] then + if self.help[short_name] then + table.insert(output, string.format("# HELP %s%s %s\n", + self.prefix, short_name, self.help[short_name])) + end + if self.type[short_name] then + table.insert(output, string.format("# TYPE %s%s %s\n", + self.prefix, short_name, self.type[short_name])) + end + seen_metrics[short_name] = true + end + -- Replace "Inf" with "+Inf" in each metric's last bucket 'le' label. + if key:find('le="Inf"', 1, true) then + key = key:gsub('le="Inf"', 'le="+Inf"') + end + table.insert(output, string.format("%s%s %s\n", self.prefix, key, value)) + else + self:log_error("Error getting '", key, "': ", err) + end + end + return output +end + +-- Present all metrics in a text format compatible with Prometheus. +-- +-- This function should be used to expose the metrics on a separate HTTP page. +-- It will get the metrics from the dictionary, sort them, and expose them +-- aling with TYPE and HELP comments. +function Prometheus:collect() + ngx.header.content_type = "text/plain" + ngx.print(self:metric_data()) +end + +return Prometheus diff --git a/lua/apisix/plugins/prometheus/exporter.lua b/lua/apisix/plugins/prometheus/exporter.lua new file mode 100644 index 00000000..f5a776c8 --- /dev/null +++ b/lua/apisix/plugins/prometheus/exporter.lua @@ -0,0 +1,46 @@ +-- Copyright (C) Yuansheng Wang + +local base_prometheus = require("apisix.plugins.prometheus.base_prometheus") +local prometheus = base_prometheus.init("prometheus_metrics", "apisix_") +local core = require("apisix.core") +local metrics = {} + + +local _M = {version = 0.1} + + +function _M.init() + core.table.clear(metrics) + + -- across all services + metrics.connections = prometheus:gauge("nginx_http_current_connections", + "Number of HTTP connections", + {"state"}) +end + + +function _M.log(conf, ctx) + metrics.connections:set(ngx.time(), { "unix time" }) + core.log.warn("hit prometheuse plugin") +end + + +function _M.collect() + if not prometheus or not metrics then + core.log.err("prometheus: plugin is not initialized, please make sure ", + " 'prometheus_metrics' shared dict is present in nginx template") + return 500, { message = "An unexpected error occurred" } + end + + metrics.connections:set(ngx.time(), { "active" }) + + prometheus:collect() +end + + +function _M.metric_data() + return prometheus:metric_data() +end + + +return _M