From 780f0290cb1da5dec716076e89ca37751a209bbc Mon Sep 17 00:00:00 2001 From: Michal Cichra Date: Tue, 27 Feb 2018 17:22:28 +0100 Subject: [PATCH] [prometheus] create prometheus exporter skeleton --- CHANGELOG.md | 4 +++ gateway/Roverfile.lock | 1 + gateway/apicast-scm-1.rockspec | 1 + gateway/conf/nginx.conf.liquid | 18 +++++++++++++ gateway/config/development.lua | 3 ++- gateway/src/apicast/executor.lua | 8 ++++++ gateway/src/apicast/policy.lua | 2 +- gateway/src/apicast/prometheus.lua | 23 +++++++++++++++++ spec/policy_spec.lua | 2 +- spec/prometheus_spec.lua | 41 ++++++++++++++++++++++++++++++ 10 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 gateway/src/apicast/prometheus.lua create mode 100644 spec/prometheus_spec.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d223e16e..8d4b54efc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Error loading policy chain configuration JSON with null value [PR #626](https://github.com/3scale/apicast/pull/626) - Splitted `resolv.conf` in lines,to avoid commented lines [PR #618](https://github.com/3scale/apicast/pull/618) +## Added + +- New `metrics` phase that runs when prometheus is collecting metrics [PR #629](https://github.com/3scale/apicast/pull/629) + ## [3.2.0-beta1] - 2018-02-20 ## Added diff --git a/gateway/Roverfile.lock b/gateway/Roverfile.lock index 6422e08d6..25256f64a 100644 --- a/gateway/Roverfile.lock +++ b/gateway/Roverfile.lock @@ -18,6 +18,7 @@ luassert 1.7.10-0||testing luasystem 0.2.1-0||testing markdown 0.33-1||development mediator_lua 1.1.2-0||testing +nginx-lua-prometheus 0.20171117-4||production penlight 1.5.4-1||production,development,testing router 2.1-0||production say 1.3-1||testing diff --git a/gateway/apicast-scm-1.rockspec b/gateway/apicast-scm-1.rockspec index 71935665b..e4e0d896e 100644 --- a/gateway/apicast-scm-1.rockspec +++ b/gateway/apicast-scm-1.rockspec @@ -20,6 +20,7 @@ dependencies = { 'liquid', 'argparse', 'penlight', + 'nginx-lua-prometheus', } build = { type = "make", diff --git a/gateway/conf/nginx.conf.liquid b/gateway/conf/nginx.conf.liquid index 88aff6a47..be83eed4f 100644 --- a/gateway/conf/nginx.conf.liquid +++ b/gateway/conf/nginx.conf.liquid @@ -106,6 +106,24 @@ http { {% include "conf.d/apicast.conf" %} } + {% if port.metrics %} + lua_shared_dict prometheus_metrics 16M; + server { + access_log off; + listen {{ port.metrics }}; + server_name metrics prometheus _; + + location /metrics { + content_by_lua_block { require('apicast.executor'):metrics() } + } + + location /nginx_status { + internal; + stub_status; + } + } + {% endif %} + {% for file in "sites.d/*.conf" | filesystem %} {% include file %} {% endfor %} diff --git a/gateway/config/development.lua b/gateway/config/development.lua index c19900050..459faa972 100644 --- a/gateway/config/development.lua +++ b/gateway/config/development.lua @@ -28,5 +28,6 @@ return { lua_code_cache = 'on', configuration_loader = 'lazy', configuration_cache = 0, - configuration = data_url('application/json', configuration) + configuration = data_url('application/json', configuration), + port = { metrics = 9421 }, -- see https://github.com/prometheus/prometheus/wiki/Default-port-allocations } diff --git a/gateway/src/apicast/executor.lua b/gateway/src/apicast/executor.lua index 15e8d3b1d..be7b11fd0 100644 --- a/gateway/src/apicast/executor.lua +++ b/gateway/src/apicast/executor.lua @@ -9,6 +9,7 @@ require('apicast.loader') -- to load code from deprecated paths local PolicyChain = require('apicast.policy_chain') local policy = require('apicast.policy') local linked_list = require('apicast.linked_list') +local prometheus = require('apicast.prometheus') local setmetatable = setmetatable @@ -58,4 +59,11 @@ function _M:context(phase) return shared_build_context(self) end +local metrics = _M.metrics +--- Render metrics from all policies. +function _M:metrics(...) + metrics(self, ...) + return prometheus:collect() +end + return _M.new(PolicyChain.default()) diff --git a/gateway/src/apicast/policy.lua b/gateway/src/apicast/policy.lua index ae02dc439..0e0bfbf4d 100644 --- a/gateway/src/apicast/policy.lua +++ b/gateway/src/apicast/policy.lua @@ -13,7 +13,7 @@ local PHASES = { 'rewrite', 'access', 'content', 'balancer', 'header_filter', 'body_filter', - 'post_action', 'log' + 'post_action', 'log', 'metrics', } local setmetatable = setmetatable diff --git a/gateway/src/apicast/prometheus.lua b/gateway/src/apicast/prometheus.lua new file mode 100644 index 000000000..a41756e64 --- /dev/null +++ b/gateway/src/apicast/prometheus.lua @@ -0,0 +1,23 @@ +local prometheus = require('nginx.prometheus') +local assert = assert +local dict = 'prometheus_metrics' + +if ngx.shared[dict] then + local init = prometheus.init(dict) + + local metrics = { } + local __call = function(_, type, name, ...) + local metric_name = assert(name, 'missing metric name') + + if not metrics[metric_name] then + metrics[metric_name] = init[assert(type, 'missing metric type')](init, metric_name, ...) + end + + return metrics[metric_name] + end + + return setmetatable({ }, { __call = __call, __index = init }) +else + local noop = function() end + return setmetatable({ collect = noop }, { __call = noop }) +end diff --git a/spec/policy_spec.lua b/spec/policy_spec.lua index ff15deac3..1a293b5f5 100644 --- a/spec/policy_spec.lua +++ b/spec/policy_spec.lua @@ -6,7 +6,7 @@ describe('policy', function() 'rewrite', 'access', 'content', 'balancer', 'header_filter', 'body_filter', - 'post_action', 'log' + 'post_action', 'log', 'metrics' } describe('.new', function() diff --git a/spec/prometheus_spec.lua b/spec/prometheus_spec.lua new file mode 100644 index 000000000..95819cb15 --- /dev/null +++ b/spec/prometheus_spec.lua @@ -0,0 +1,41 @@ + +describe('prometheus', function() + before_each(function() package.loaded['apicast.prometheus'] = nil end) + + describe('shared dictionary is missing', function() + before_each(function() ngx.shared.prometheus_metrics = nil end) + + it('can be called', function() + assert.is_nil(require('apicast.prometheus')()) + end) + + it('can be collected', function() + assert.is_nil(require('apicast.prometheus'):collect()) + end) + end) + + describe('shared dictionary is there', function() + before_each(function() + ngx.shared.prometheus_metrics = { + set = function() end, + get_keys = function() return {} end + } + end) + + local prometheus + + before_each(function() + prometheus = assert(require('apicast.prometheus')) + end) + + it('can be called', function() + assert.is_table(prometheus('counter', 'some_metric')) + end) + + it('can be collected', function() + ngx.header = { } + assert.is_nil(require('apicast.prometheus'):collect()) + end) + end) + +end)