diff --git a/CHANGELOG.md b/CHANGELOG.md index 4036c4c4..350c10cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `tnt_clock_delta` metric to compute clock difference on instances +- set custom global labels in config and with `set_labels` function [#259](https://github.com/tarantool/metrics/issues/259) ## [0.9.0] - 2021-05-28 ### Fixed diff --git a/cartridge/roles/metrics.lua b/cartridge/roles/metrics.lua index 1b925bf5..5d982733 100644 --- a/cartridge/roles/metrics.lua +++ b/cartridge/roles/metrics.lua @@ -8,6 +8,8 @@ local metrics_vars = require('cartridge.vars').new('metrics_vars') metrics_vars:new('current_paths', {}) metrics_vars:new('default', {}) metrics_vars:new('config', {}) +metrics_vars:new('default_labels', {}) +metrics_vars:new('custom_labels', {}) local handlers = { ['json'] = function(req) @@ -24,7 +26,8 @@ local handlers = { end, } -local function set_labels() +local function set_labels(custom_labels) + custom_labels = custom_labels or {} local params, err = argparse.parse() assert(params, err) local this_instance = cartridge.admin_get_servers(box.info.uuid) @@ -32,12 +35,21 @@ local function set_labels() if this_instance and this_instance[1] then zone = this_instance[1].zone end - metrics.set_global_labels({alias = params.alias or params.instance_name, zone = zone}) + local labels = {alias = params.alias or params.instance_name, zone = zone} + for label, value in pairs(metrics_vars.default_labels) do + labels[label] = value + end + for label, value in pairs(custom_labels) do + labels[label] = value + end + metrics.set_global_labels(labels) + metrics_vars.custom_labels = custom_labels end local function check_config(_) checks({ export = 'table', + ['global-labels'] = '?table', }) end @@ -85,13 +97,22 @@ local function format_paths(export) return paths end +local function validate_global_labels(custom_labels) + custom_labels = custom_labels or {} + for label, _ in pairs(custom_labels) do + assert(type(label) == 'string', 'label name must me string') + assert(label ~= 'zone' and label ~= 'alias', 'custom label name is not allowed to be "zone" or "alias"') + end + return true +end + local function validate_config(conf_new) conf_new = conf_new.metrics if conf_new == nil then return true end check_config(conf_new) - return validate_routes(conf_new.export) + return validate_routes(conf_new.export) and validate_global_labels(conf_new['global-labels']) end local function apply_routes(paths) @@ -123,7 +144,7 @@ end local function apply_config(conf) local metrics_conf = conf.metrics or {} metrics_conf.export = metrics_conf.export or {} - set_labels() + set_labels(metrics_conf['global-labels']) local paths = format_paths(metrics_conf.export) metrics_vars.config = table.copy(paths) for path, format in pairs(metrics_vars.default) do @@ -152,8 +173,18 @@ local function set_export(export) end end +local function set_default_labels(default_labels) + local ok, err = pcall(validate_global_labels, default_labels) + if ok then + metrics_vars.default_labels = default_labels + set_labels(metrics_vars.custom_labels) + else + error(err, 0) + end +end + local function init() - set_labels() + set_labels(metrics_vars.custom_labels) metrics.enable_default_metrics() metrics.enable_cartridge_metrics() local current_paths = table.copy(metrics_vars.config) @@ -175,6 +206,7 @@ local function stop() metrics_vars.current_paths = {} metrics_vars.config = {} + metrics_vars.custom_labels = {} end return setmetatable({ @@ -185,4 +217,5 @@ return setmetatable({ validate_config = validate_config, apply_config = apply_config, set_export = set_export, + set_default_labels = set_default_labels, }, { __index = metrics }) diff --git a/doc/monitoring/getting_started.rst b/doc/monitoring/getting_started.rst index 92db1779..487cc386 100644 --- a/doc/monitoring/getting_started.rst +++ b/doc/monitoring/getting_started.rst @@ -201,3 +201,20 @@ via configuration. .. image:: images/role-config.png :align: center +#. To set custom global labels, you may use the following configuration. + + .. code-block:: yaml + + metrics: + export: + - path: '/metrics' + format: 'json' + global-labels: + my-custom-label: label-value + + **OR** use ``set_labels`` function in ``init.lua``. + + .. code-block:: lua + + local metrics = require('cartridge.roles.metrics') + metrics.set_labels({ ['my-custom-label'] = 'label-value'} ) diff --git a/test/integration/cartridge_hotreload_test.lua b/test/integration/cartridge_hotreload_test.lua index be781eb2..87c55ae1 100644 --- a/test/integration/cartridge_hotreload_test.lua +++ b/test/integration/cartridge_hotreload_test.lua @@ -132,3 +132,87 @@ g.test_cartridge_hotreload_set_export_and_config = function() resp = main_server:http_request('get', '/metrics', {raise = false}) t.assert_equals(resp.status, 200) end + +g.test_cartridge_hotreload_set_labels = function() + local main_server = g.cluster:server('main') + main_server.net_box:eval([[ + local metrics = require('cartridge.roles.metrics') + metrics.set_default_labels(...) + ]], {{ + system = 'some-system', + app_name = 'myapp', + }}) + reload_roles() + + local resp = main_server:http_request('get', '/metrics', {raise = false}) + t.assert_equals(resp.status, 200) + + for _, obs in pairs(resp.json) do + t.assert_equals(obs.label_pairs.system, 'some-system') + t.assert_equals(obs.label_pairs.app_name, 'myapp') + t.assert_equals(obs.label_pairs.alias, 'main') + end +end + +g.test_cartridge_hotreload_labels_from_config = function() + local main_server = g.cluster:server('main') + main_server:upload_config({ + metrics = { + export = { + { + path = '/metrics', + format = 'json' + }, + }, + ['global-labels'] = { + system = 'some-system', + app_name = 'myapp', + } + } + }) + + reload_roles() + + local resp = main_server:http_request('get', '/metrics', {raise = false}) + t.assert_equals(resp.status, 200) + + for _, obs in pairs(resp.json) do + t.assert_equals(obs.label_pairs.system, 'some-system') + t.assert_equals(obs.label_pairs.app_name, 'myapp') + t.assert_equals(obs.label_pairs.alias, 'main') + end +end + +g.test_cartridge_hotreload_labels_from_config_and_set_labels = function() + local main_server = g.cluster:server('main') + main_server:upload_config({ + metrics = { + export = { + { + path = '/metrics', + format = 'json' + }, + }, + ['global-labels'] = { + system = 'some-system', + } + } + }) + main_server.net_box:eval([[ + local metrics = require('cartridge.roles.metrics') + metrics.set_default_labels(...) + ]], {{ + app_name = 'myapp', + }}) + + reload_roles() + + local resp = main_server:http_request('get', '/metrics', {raise = false}) + t.assert_equals(resp.status, 200) + + for _, obs in pairs(resp.json) do + t.assert_equals(obs.label_pairs.system, 'some-system') + t.assert_equals(obs.label_pairs.app_name, 'myapp') + t.assert_equals(obs.label_pairs.alias, 'main') + end +end diff --git a/test/integration/cartridge_role_test.lua b/test/integration/cartridge_role_test.lua index d0ecf0e5..e825fb92 100644 --- a/test/integration/cartridge_role_test.lua +++ b/test/integration/cartridge_role_test.lua @@ -490,3 +490,181 @@ g.test_zone_label_changes_in_runtime = function() ) end end + +g.test_add_custom_labels = function() + local server = g.cluster.main_server + server:upload_config({ + metrics = { + export = { + { + path = '/metrics', + format = 'json' + }, + }, + ['global-labels'] = { + system = 'some-system', + app_name = 'myapp', + } + } + }) + + local resp = server:http_request('get', '/metrics', {raise = false}) + t.assert_equals(resp.status, 200) + for _, obs in pairs(resp.json) do + t.assert_equals(obs.label_pairs.system, 'some-system') + t.assert_equals(obs.label_pairs.app_name, 'myapp') + t.assert_equals(obs.label_pairs.alias, 'main') + end +end + +g.test_add_custom_labels_with_set_labels = function() + local server = g.cluster.main_server + server.net_box:eval([[ + local metrics = require('cartridge.roles.metrics') + metrics.set_default_labels(...) + ]], {{ + system = 'some-system', + app_name = 'myapp', + }}) + + local resp = server:http_request('get', '/metrics', {raise = false}) + t.assert_equals(resp.status, 200) + for _, obs in pairs(resp.json) do + t.assert_equals(obs.label_pairs.system, 'some-system') + t.assert_equals(obs.label_pairs.app_name, 'myapp') + t.assert_equals(obs.label_pairs.alias, 'main') + end +end + +g.test_add_custom_labels_with_set_labels_and_config = function() + local server = g.cluster.main_server + server.net_box:eval([[ + local metrics = require('cartridge.roles.metrics') + metrics.set_default_labels(...) + ]], {{ + system = 'some-system', + source = 'api', + }}) + server:upload_config({ + metrics = { + export = { + { + path = '/metrics', + format = 'json' + }, + }, + ['global-labels'] = { + app_name = 'myapp', + source = 'config', + } + } + }) + local resp = server:http_request('get', '/metrics', {raise = false}) + t.assert_equals(resp.status, 200) + for _, obs in pairs(resp.json) do + t.assert_equals(obs.label_pairs.system, 'some-system') + t.assert_equals(obs.label_pairs.app_name, 'myapp') + t.assert_equals(obs.label_pairs.source, 'config') + t.assert_equals(obs.label_pairs.alias, 'main') + end +end + +g.test_add_custom_labels_with_config_and_set_labels = function() + local server = g.cluster.main_server + server:upload_config({ + metrics = { + export = { + { + path = '/metrics', + format = 'json' + }, + }, + ['global-labels'] = { + app_name = 'myapp', + source = 'config', + } + } + }) + server.net_box:eval([[ + local metrics = require('cartridge.roles.metrics') + metrics.set_default_labels(...) + ]], {{ + system = 'some-system', + source = 'api', + }}) + local resp = server:http_request('get', '/metrics', {raise = false}) + t.assert_equals(resp.status, 200) + for _, obs in pairs(resp.json) do + t.assert_equals(obs.label_pairs.system, 'some-system') + t.assert_equals(obs.label_pairs.app_name, 'myapp') + t.assert_equals(obs.label_pairs.source, 'config') + t.assert_equals(obs.label_pairs.alias, 'main') + end +end + +g.test_add_custom_labels_with_set_labels_overrides_default = function() + local server = g.cluster.main_server + server.net_box:eval([[ + local metrics = require('cartridge.roles.metrics') + metrics.set_default_labels(...) + ]], {{ + system = 'some-system', + }}) + server.net_box:eval([[ + local metrics = require('cartridge.roles.metrics') + metrics.set_default_labels(...) + ]], {{ + app_name = 'myapp', + }}) + local resp = server:http_request('get', '/metrics', {raise = false}) + t.assert_equals(resp.status, 200) + for _, obs in pairs(resp.json) do + t.assert_not(obs.label_pairs.system) + t.assert_equals(obs.label_pairs.app_name, 'myapp') + t.assert_equals(obs.label_pairs.alias, 'main') + end +end + +g.test_invalig_global_labels_format = function() + assert_bad_config({ + metrics = { + export = { + { + path = '/metrics', + format = 'json' + }, + }, + ['global-labels'] = 'string', + } + }, 'bad argument') +end + +g.test_invalig_global_labels_names = function() + assert_bad_config({ + metrics = { + export = { + { + path = '/metrics', + format = 'json' + }, + }, + ['global-labels'] = { + zone = 'zone', + } + } + }, 'label name is not allowed to be "zone" or "alias"') + + assert_bad_config({ + metrics = { + export = { + { + path = '/metrics', + format = 'json' + }, + }, + ['global-labels'] = { + alias = 'alias', + } + } + }, 'label name is not allowed to be "zone" or "alias"') +end