diff --git a/CHANGELOG.md b/CHANGELOG.md index 2618cad4..2ac5dfa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - values and effect (like default metrics callbacks) are preserved between reloads; - does not deal with external features like cartridge HTTP setup +- name prefixes for collectors +- exhaustive output for export and aggregating with `extended_format` option: + - `shared_collector_obj:collect()`; + - `counter_obj:collect()`; + - `gauge_obj:collect()`; + - `histogram_obj:collect()`; + - `summary_obj:collect()`; + - `metrics.collect()`; ### Changed - Setup cartridge hotreload inside the role @@ -26,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Passing nonexistent metrics to `enable_default_metrics()` - Using `{}` as `include` in `enable_default_metrics()` to enable all metrics +- Control characters in collector kind, name, observation and global labels ## [0.16.0] - 2023-01-27 ### Added diff --git a/doc/monitoring/api_reference.rst b/doc/monitoring/api_reference.rst index cbab48ce..bc60d434 100644 --- a/doc/monitoring/api_reference.rst +++ b/doc/monitoring/api_reference.rst @@ -51,19 +51,55 @@ counter .. _metrics-api_reference-counter_collect: - .. method:: collect() + .. method:: collect(opts) - :return: Array of ``observation`` objects for a given counter. + :param table opts: table of collect options: + + * ``extended_format`` -- use extended format in the output. + + :return: Array of ``observation`` objects for a given counter (by default or if + ``extended format`` is ``false``). Otherwise returns a map with collector + description and structured observations. + + .. code-block:: lua + + counter_obj:collect{extended_format = false} + + { + -- an observation + { + label_pairs: table, -- `label_pairs` key-value table + timestamp: ctype, -- observation time (in microseconds) + value: number, -- current value + metric_name: string, -- metric name + }, + -- another observation... + } .. code-block:: lua + counter_obj:collect{extended_format = true} + { - label_pairs: table, -- `label_pairs` key-value table - timestamp: ctype, -- current system time (in microseconds) - value: number, -- current value - metric_name: string, -- collector + name: string, -- collector name + name_prefix: string, -- collector name prefix + kind: string, -- collector kind + help: string, -- collector help + metainfo: table, -- collector metainfo + timestamp: ctype, -- observation time (in microseconds) + observations = { + [''] = { -- map of observations + -- an observation + key: string = { + label_pairs: table, -- `label_pairs` key-value table + value: number, -- current value + }, + -- another observation... + } + } } + :rtype: table .. _metrics-api_reference-counter_remove: @@ -113,10 +149,15 @@ gauge Sets the observation for ``label_pairs`` to ``num``. - .. method:: collect() + .. _metrics-api_reference-gauge_collect: + + .. method:: collect(opts) + + :param table opts: table of collect options: - Returns an array of ``observation`` objects for a given gauge. - For the description of ``observation``, see + * ``extended_format`` -- use extended format in the output. + + Returns format is the same as for :ref:`counter_obj:collect() `. .. method:: remove(label_pairs) @@ -170,11 +211,59 @@ histogram Note that both label names and values in ``label_pairs`` are treated as strings. - .. method:: collect() + .. _metrics-api_reference-histogram_collect: + + .. method:: collect(opts) + + :param table opts: table of collect options: - Return a concatenation of ``counter_obj:collect()`` across all internal - counters of ``histogram_obj``. For the description of ``observation``, - see :ref:`counter_obj:collect() `. + * ``extended_format`` -- use extended format in the output. + + :return: A concatenation of ``observation`` objects for a given counter (by default or if + ``extended format`` is ``false``). Otherwise returns a map with collector + description and structured observations. + + .. code-block:: lua + + histogram_obj:collect{extended_format = false} + + { + -- an observation + { + label_pairs: table, -- `label_pairs` key-value table + timestamp: ctype, -- observation time (in microseconds) + value: number, -- current value + metric_name: string, -- metric name + }, + -- another observation... + } + + .. code-block:: lua + + histogram_obj:collect{extended_format = true} + + { + name: string, -- collector name + name_prefix: string, -- collector name_prefix + kind: string, -- collector kind + help: string, -- collector help + metainfo: table, -- collector metainfo + timestamp: ctype, -- observation time (in microseconds) + observations = { + count = { -- map of count observations + -- an observation + key: string = { + label_pairs: table, -- `label_pairs` key-value table + value: number, -- current value + }, + -- another observation... + }, + sum = { -- map of sum observations + }, + bucket = { -- map of bucket observations + }, + } + } .. method:: remove(label_pairs) @@ -255,11 +344,61 @@ summary Note that both label names and values in ``label_pairs`` are treated as strings. - .. method:: collect() + .. _metrics-api_reference-summary_collect: + + .. method:: collect(opts) + + :param table opts: table of collect options: + + * ``extended_format`` -- use extended format in the output. + + :return: A concatenation of ``observation`` objects for a given counter (by default or if + ``extended format`` is ``false``). Otherwise returns a map with collector + description and structured observations. + + .. code-block:: lua + + summary_obj:collect{extended_format = false} + + { + -- an observation + { + label_pairs: table, -- `label_pairs` key-value table + timestamp: ctype, -- observation time (in microseconds) + value: number, -- current value + metric_name: string, -- metric name + }, + -- another observation... + } + + .. code-block:: lua + + summary_obj:collect{extended_format = true} + + { + name: string, -- collector name + name_suffix: string, -- collector name suffix + kind: string, -- collector kind + help: string, -- collector name + metainfo: table, -- collector metainfo + timestamp: ctype, -- observation time (in microseconds) + observations = { + count = { -- map of count observations + -- an observation + key: string = { + label_pairs: table, -- `label_pairs` key-value table + value: number, -- current value + }, + -- another observation... + }, + sum = { -- map of sum observations + }, + [''] = { -- map of quantile observations + }, + } + } + - Return a concatenation of ``counter_obj:collect()`` across all internal - counters of ``summary_obj``. For the description of ``observation``, - see :ref:`counter_obj:collect() `. If ``max_age_time`` and ``age_buckets_count`` are set, quantile observations are collected only from the head bucket in the sliding time window, not from every bucket. If no observations were recorded, @@ -375,6 +514,12 @@ Metrics functions * ``invoke_callbacks`` -- if ``true``, ``invoke_callbacks()`` is triggerred before actual collect. * ``default_only`` -- if ``true``, observations contain only default metrics (``metainfo.default = true``). + * ``extended_format`` -- if ``true``, collects extended format output from all collectors + (see :ref:`counter_obj:collect{extended_format = true} `, + :ref:`gauge_obj:collect{extended_format = true} `, + :ref:`histogram_obj:collect{extended_format = true} `, + :ref:`summary_obj:collect{extended_format = true} `) + and returns a map instead of an array. .. class:: registry diff --git a/doc/monitoring/plugins.rst b/doc/monitoring/plugins.rst index 8a7dcd6c..6083a48c 100644 --- a/doc/monitoring/plugins.rst +++ b/doc/monitoring/plugins.rst @@ -199,58 +199,6 @@ Use the JSON plugin with Tarantool ``http.server`` as follows: end ) -.. _metrics-plugins-plugin-specific_api: - -Plugin-specific API -------------------- - -Use the following methods **only when developing a new plugin**. - -.. module:: metrics - -.. function:: invoke_callbacks() - - Invoke a function registered via - ``metrics.register_callback()``. - Used in exporters. - -.. function:: collectors() - - List all collectors in the registry. Designed to be used in exporters. - - :return: A list of created collectors. - -.. class:: collector_object - - .. method:: collect() - - .. note:: - - You'll probably want to use ``metrics.collectors()`` instead. - - Equivalent to: - - .. code-block:: lua - - for _, c in pairs(metrics.collectors()) do - for _, obs in ipairs(c:collect()) do - ... -- handle observation - end - end - - :return: A concatenation of ``observation`` objects across all created collectors. - - .. code-block:: lua - - { - label_pairs: table, -- `label_pairs` key-value table - timestamp: ctype, -- current system time (in microseconds) - value: number, -- current value - metric_name: string, -- collector - } - - :rtype: table - .. _metrics-plugins-custom: Creating custom plugins @@ -260,17 +208,23 @@ Include the following in your main export function: .. code-block:: lua - -- Invoke all callbacks registered via `metrics.register_callback()` - metrics.invoke_callbacks() + local metrics = require('metrics') + local string_utils = require('metrics.string_utils') - -- Loop over collectors - for _, c in pairs(metrics.collectors()) do - ... + -- Collect up-to-date metrics with extended format. + local output = metrics.collect{invoke_callbacks = true, extended_format = true} - -- Loop over instant observations in the collector - for _, obs in pairs(c:collect()) do - -- Export observation `obs` - ... - end + for _, coll_obs in pairs(output) do + -- Serialize collector info like coll_obs.name, coll_obs.help, + -- coll_obs.kind and coll_obs.timestamp + + for group_name, obs_group in pairs(coll_obs.observations) do + -- Common way to build metric name. + local metric_name = string_utils.build_name(coll_obs.name, group_name) + for _, obs in pairs(obs_group) do + -- Serialize observation info: obs.value and obs.label_pairs + + end + end end diff --git a/metrics-scm-1.rockspec b/metrics-scm-1.rockspec index 029e6a06..25fa0bc4 100644 --- a/metrics-scm-1.rockspec +++ b/metrics-scm-1.rockspec @@ -60,6 +60,7 @@ build = { ['metrics.utils'] = 'metrics/utils.lua', ['metrics.cfg'] = 'metrics/cfg.lua', ['metrics.stash'] = 'metrics/stash.lua', + ['metrics.string_utils'] = 'metrics/string_utils.lua', ['cartridge.roles.metrics'] = 'cartridge/roles/metrics.lua', ['cartridge.health'] = 'cartridge/health.lua', } diff --git a/metrics/api.lua b/metrics/api.lua index d36f1c9a..9a9f8ad0 100644 --- a/metrics/api.lua +++ b/metrics/api.lua @@ -3,6 +3,7 @@ local checks = require('checks') local Registry = require('metrics.registry') +local string_utils = require('metrics.string_utils') local Counter = require('metrics.collectors.counter') local Gauge = require('metrics.collectors.gauge') @@ -33,29 +34,36 @@ local function invoke_callbacks() return registry:invoke_callbacks() end -local function get_collector_values(collector, result) - for _, obs in ipairs(collector:collect()) do - table.insert(result, obs) - end -end - local function collect(opts) - checks({invoke_callbacks = '?boolean', default_only = '?boolean'}) + checks({ + invoke_callbacks = '?boolean', + default_only = '?boolean', + extended_format = '?boolean', + }) + opts = opts or {} + local collector_opts = { extended_format = opts.extended_format } if opts.invoke_callbacks then registry:invoke_callbacks() end local result = {} - for _, collector in pairs(registry.collectors) do - if opts.default_only then - if collector.metainfo.default then - get_collector_values(collector, result) - end + for key, collector in pairs(registry.collectors) do + if opts.default_only and not collector.metainfo.default then + goto continue + end + + local collect_result = collector:collect(collector_opts) + if collector_opts.extended_format then + result[key] = collect_result else - get_collector_values(collector, result) + for _, obs in ipairs(collect_result) do + table.insert(result, obs) + end end + + :: continue :: end return result @@ -116,10 +124,12 @@ local function set_global_labels(label_pairs) label_pairs = label_pairs or {} -- Verify label table - for k, _ in pairs(label_pairs) do + for k, v in pairs(label_pairs) do if type(k) ~= 'string' then error(("bad label key (string expected, got %s)"):format(type(k))) end + string_utils.check_symbols(k) + string_utils.check_symbols(v) end registry:set_labels(label_pairs) diff --git a/metrics/collectors/histogram.lua b/metrics/collectors/histogram.lua index 2e4b7575..92ec0afe 100644 --- a/metrics/collectors/histogram.lua +++ b/metrics/collectors/histogram.lua @@ -1,3 +1,7 @@ +local fiber = require('fiber') + +local string_utils = require('metrics.string_utils') + local Shared = require('metrics.collectors.shared') local Counter = require('metrics.collectors.counter') @@ -6,6 +10,9 @@ local DEFAULT_BUCKETS = {.005, .01, .025, .05, .075, .1, .25, .5, .75, 1.0, 2.5, 5.0, 7.5, 10.0, INF} local Histogram = Shared:new_class('histogram', {'observe_latency'}) +Histogram.COUNT_SUFFIX = 'count' +Histogram.SUM_SUFFIX = 'sum' +Histogram.BUCKET_SUFFIX = 'bucket' function Histogram.check_buckets(buckets) local prev = -math.huge @@ -28,9 +35,15 @@ function Histogram:new(name, help, buckets, metainfo) obj.buckets[#obj.buckets+1] = INF end - obj.count_collector = Counter:new(name .. '_count', help, metainfo) - obj.sum_collector = Counter:new(name .. '_sum', help, metainfo) - obj.bucket_collector = Counter:new(name .. '_bucket', help, metainfo) + obj.count_collector = Counter:new( + string_utils.build_name(name, Histogram.COUNT_SUFFIX), + help, metainfo) + obj.sum_collector = Counter:new( + string_utils.build_name(name, Histogram.SUM_SUFFIX), + help, metainfo) + obj.bucket_collector = Counter:new( + string_utils.build_name(name, Histogram.BUCKET_SUFFIX), + help, metainfo) return obj end @@ -77,7 +90,7 @@ function Histogram:remove(label_pairs) end end -function Histogram:collect() +function Histogram:_collect_v1_implementation() local result = {} for _, obs in ipairs(self.count_collector:collect()) do table.insert(result, obs) @@ -91,4 +104,24 @@ function Histogram:collect() return result end +function Histogram._collect_v2_observations() + error("Not supported for complex collectors") +end + +function Histogram:_collect_v2_implementation() + return { + name = self.name, + name_prefix = self.name_prefix, + kind = self.kind, + help = self.help, + metainfo = self.metainfo, + timestamp = fiber.time64(), + observations = { + [Histogram.COUNT_SUFFIX] = self.count_collector:_collect_v2_observations(), + [Histogram.SUM_SUFFIX] = self.sum_collector:_collect_v2_observations(), + [Histogram.BUCKET_SUFFIX] = self.bucket_collector:_collect_v2_observations(), + } + } +end + return Histogram diff --git a/metrics/collectors/shared.lua b/metrics/collectors/shared.lua index 2cb195b4..1d64f522 100644 --- a/metrics/collectors/shared.lua +++ b/metrics/collectors/shared.lua @@ -2,6 +2,8 @@ local clock = require('clock') local fiber = require('fiber') local log = require('log') +local string_utils = require('metrics.string_utils') + local Shared = {} -- Create collector class with the list of instance methods copied from @@ -14,11 +16,16 @@ function Shared:new_class(kind, method_names) table.insert(method_names, 'make_key') table.insert(method_names, 'append_global_labels') table.insert(method_names, 'collect') + table.insert(method_names, '_collect_v1_implementation') + table.insert(method_names, '_collect_v2_implementation') + table.insert(method_names, '_collect_v2_observations') table.insert(method_names, 'remove') local methods = {} for _, name in pairs(method_names) do methods[name] = self[name] end + + string_utils.check_symbols(kind) local class = {kind = kind} class.__index = class return setmetatable(class, {__index = methods}) @@ -30,8 +37,11 @@ function Shared:new(name, help, metainfo) if not name then error("Name should be set for %s") end + string_utils.check_symbols(name) + return setmetatable({ name = name, + name_prefix = name:gsub('_total$', ''):gsub('_current$', ''), help = help or "", observations = {}, label_pairs = {}, @@ -49,6 +59,8 @@ function Shared.make_key(label_pairs) end local parts = {} for k, v in pairs(label_pairs) do + string_utils.check_symbols(k) + string_utils.check_symbols(v) table.insert(parts, k .. '\t' .. v) end table.sort(parts) @@ -142,7 +154,7 @@ function Shared:append_global_labels(label_pairs) return extended_label_pairs end -function Shared:collect() +function Shared:_collect_v1_implementation() if next(self.observations) == nil then return {} end @@ -159,4 +171,41 @@ function Shared:collect() return result end +function Shared:_collect_v2_observations() + local observations = {} + + for key, value in pairs(self.observations) do + local obs = { + label_pairs = self:append_global_labels(self.label_pairs[key]), + value = value, + } + observations[key] = obs + end + return observations +end + +function Shared:_collect_v2_implementation() + return { + name = self.name, + name_prefix = self.name_prefix, + kind = self.kind, + help = self.help, + metainfo = self.metainfo, + timestamp = fiber.time64(), + observations = { + [''] = self:_collect_v2_observations(), + } + } +end + +function Shared:collect(opts) + opts = opts or {} + + if opts.extended_format then + return self:_collect_v2_implementation() + else + return self:_collect_v1_implementation() + end +end + return Shared diff --git a/metrics/collectors/summary.lua b/metrics/collectors/summary.lua index 30d37f75..0522b05c 100644 --- a/metrics/collectors/summary.lua +++ b/metrics/collectors/summary.lua @@ -1,3 +1,4 @@ +local string_utils = require('metrics.string_utils') local Shared = require('metrics.collectors.shared') local Counter = require('metrics.collectors.counter') local Quantile = require('metrics.quantile') @@ -5,14 +6,21 @@ local Quantile = require('metrics.quantile') local fiber = require('fiber') local Summary = Shared:new_class('summary', {'observe_latency'}) +Summary.COUNT_SUFFIX = 'count' +Summary.SUM_SUFFIX = 'sum' +Summary.QUANTILE_SUFFIX = '' function Summary:new(name, help, objectives, params, metainfo) params = params or {} metainfo = table.copy(metainfo) or {} local obj = Shared.new(self, name, help, metainfo) - obj.count_collector = Counter:new(name .. '_count', help, metainfo) - obj.sum_collector = Counter:new(name .. '_sum', help, metainfo) + obj.count_collector = Counter:new( + string_utils.build_name(name, Summary.COUNT_SUFFIX), + help, metainfo) + obj.sum_collector = Counter:new( + string_utils.build_name(name, Summary.SUM_SUFFIX), + help, metainfo) obj.objectives = objectives obj.max_age_time = params.max_age_time obj.age_buckets_count = params.age_buckets_count or 1 @@ -100,7 +108,9 @@ function Summary:remove(label_pairs) end end -function Summary:collect_quantiles() +function Summary:collect_quantiles(opts) + opts = opts or {} + if not self.objectives or next(self.observations) == nil then return {} end @@ -114,19 +124,28 @@ function Summary:collect_quantiles() for _, objective in ipairs(self.quantiles) do local label_pairs = table.deepcopy(self:append_global_labels(observation.label_pairs)) label_pairs.quantile = objective - local obs = { - metric_name = self.name, - label_pairs = label_pairs, - value = Quantile.Query(observation.buckets[observation.head_bucket_index], objective), - timestamp = fiber.time64(), - } - table.insert(result, obs) + + local value = Quantile.Query(observation.buckets[observation.head_bucket_index], objective) + + if opts.extended_format then + result[self.make_key(label_pairs)] = { + label_pairs = label_pairs, + value = value, + } + else + table.insert(result, { + metric_name = self.name, + label_pairs = label_pairs, + value = value, + timestamp = fiber.time64(), + }) + end end end return result end -function Summary:collect() +function Summary:_collect_v1_implementation() local result = {} for _, obs in ipairs(self.count_collector:collect()) do table.insert(result, obs) @@ -140,6 +159,30 @@ function Summary:collect() return result end +function Summary._collect_v2_observations() + error("Not supported for complex collectors") +end + +function Summary:_collect_v2_implementation() + return { + name = self.name, + name_prefix = self.name_prefix, + kind = self.kind, + help = self.help, + metainfo = self.metainfo, + timestamp = fiber.time64(), + observations = { + [Summary.COUNT_SUFFIX] = self.count_collector:_collect_v2_observations(), + [Summary.SUM_SUFFIX] = self.sum_collector:_collect_v2_observations(), + [Summary.QUANTILE_SUFFIX] = self:collect_quantiles{extended_format = true}, + } + } +end + +function Summary.collect_observations() + error('Not supported for complex collectors') +end + -- debug function to get observation quantiles from summary -- returns array of quantile objects or -- single quantile object if summary has only one bucket diff --git a/metrics/plugins/graphite.lua b/metrics/plugins/graphite.lua index e75355c8..faa78b08 100644 --- a/metrics/plugins/graphite.lua +++ b/metrics/plugins/graphite.lua @@ -1,6 +1,7 @@ local socket = require('socket') local fiber = require('fiber') local metrics = require('metrics') +local string_utils = require('metrics.string_utils') local checks = require('checks') local log = require('log') local fun = require('fun') @@ -36,22 +37,63 @@ function graphite.format_observation(prefix, obs) return graph end -local function graphite_worker(opts) - fiber.name('metrics_graphite_worker') +graphite.internal = {} +function graphite.internal.collect_and_push_v1(opts) + metrics.invoke_callbacks() + for _, c in pairs(metrics.collectors()) do + for _, obs in ipairs(c:collect()) do + local data = graphite.format_observation(opts.prefix, obs) + local numbytes = opts.sock:sendto(opts.host, opts.port, data) + if numbytes == nil then + log.error('Error while sending to host %s port %s data %s', + opts.host, opts.port, data) + end + end + end +end - while true do - metrics.invoke_callbacks() - for _, c in pairs(metrics.collectors()) do - for _, obs in ipairs(c:collect()) do - local data = graphite.format_observation(opts.prefix, obs) - local numbytes = opts.sock:sendto(opts.host, opts.port, data) - if numbytes == nil then - log.error('Error while sending to host %s port %s data %s', - opts.host, opts.port, data) - end +function graphite.format_output(output, opts) + local result = {} + for _, coll_obs in pairs(output) do + for group_name, obs_group in pairs(coll_obs.observations) do + local metric_name = string_utils.build_name(coll_obs.name, group_name) + for _, obs in pairs(obs_group) do + local formatted_obs = graphite.format_observation(opts.prefix, + { + metric_name = metric_name, + label_pairs = obs.label_pairs, + timestamp = coll_obs.timestamp, + value = obs.value + }) + table.insert(result, formatted_obs) end end + end + + return result +end + +function graphite.send_formatted(formatted_output, opts) + for _, data in ipairs(formatted_output) do + local numbytes = opts.sock:sendto(opts.host, opts.port, data) + if numbytes == nil then + log.error('Error while sending to host %s port %s data %s', + opts.host, opts.port, data) + end + end +end + +function graphite.internal.collect_and_push_v2(opts) + local output = metrics.collect{invoke_callbacks = true, extended_format = true} + local formatted_output = graphite.format_output(output, opts) + graphite.send_formatted(formatted_output, opts) +end +local function graphite_worker(opts) + fiber.name('metrics_graphite_worker') + + while true do + graphite.internal.collect_and_push_v2(opts) fiber.sleep(opts.send_interval) end end diff --git a/metrics/plugins/json.lua b/metrics/plugins/json.lua index 7e2f9ce0..8d311788 100644 --- a/metrics/plugins/json.lua +++ b/metrics/plugins/json.lua @@ -1,4 +1,5 @@ local metrics = require('metrics') +local string_utils = require('metrics.string_utils') local json = require('json') local json_exporter = {} @@ -38,7 +39,8 @@ local function format_observation(obs) return part end -function json_exporter.export() +json_exporter.internal = {} -- For test purposes. +function json_exporter.internal.collect_and_serialize_v1() metrics.invoke_callbacks() local stat = {} @@ -51,4 +53,30 @@ function json_exporter.export() return json.encode(stat) end +function json_exporter.format_output(output) + local result = {} + for _, coll_obs in pairs(output) do + for group_name, obs_group in pairs(coll_obs.observations) do + local metric_name = string_utils.build_name(coll_obs.name, group_name) + for _, obs in pairs(obs_group) do + table.insert(result, { + metric_name = metric_name, + label_pairs = format_label_pairs(obs.label_pairs), + timestamp = coll_obs.timestamp, + value = format_value(obs.value), + }) + end + end + end + + return json.encode(result) +end + +function json_exporter.internal.collect_and_serialize_v2() + local output = metrics.collect{invoke_callbacks = true, extended_format = true} + return json_exporter.format_output(output) +end + +json_exporter.export = json_exporter.internal.collect_and_serialize_v2 + return json_exporter diff --git a/metrics/plugins/prometheus.lua b/metrics/plugins/prometheus.lua index 97c62c72..f1303eed 100644 --- a/metrics/plugins/prometheus.lua +++ b/metrics/plugins/prometheus.lua @@ -1,4 +1,5 @@ local metrics = require('metrics') +local string_utils = require('metrics.string_utils') require('checks') local prometheus = {} @@ -48,7 +49,8 @@ local function serialize_label_pairs(label_pairs) return string.format('{%s}', enumerated_via_comma) end -local function collect_and_serialize() +prometheus.internal = {} -- For test purposes. +function prometheus.internal.collect_and_serialize_v1() metrics.invoke_callbacks() local parts = {} for _, c in pairs(metrics.collectors()) do @@ -66,11 +68,36 @@ local function collect_and_serialize() return table.concat(parts, '\n') .. '\n' end +function prometheus.format_output(output) + local result = {} + for _, coll_obs in pairs(output) do + table.insert(result, string.format("# HELP %s %s", coll_obs.name, coll_obs.help)) + table.insert(result, string.format("# TYPE %s %s", coll_obs.name, coll_obs.kind)) + for group_name, obs_group in pairs(coll_obs.observations) do + local metric_name = string_utils.build_name(coll_obs.name, group_name) + for _, obs in pairs(obs_group) do + table.insert(result, string.format('%s%s %s', + serialize_name(metric_name), + serialize_label_pairs(obs.label_pairs), + serialize_value(obs.value) + )) + end + end + end + + return table.concat(result, '\n') .. '\n' +end + +function prometheus.internal.collect_and_serialize_v2() + local output = metrics.collect{invoke_callbacks = true, extended_format = true} + return prometheus.format_output(output) +end + function prometheus.collect_http() return { status = 200, headers = { ['content-type'] = 'text/plain; charset=utf8' }, - body = collect_and_serialize(), + body = prometheus.internal.collect_and_serialize_v2(), } end diff --git a/metrics/string_utils.lua b/metrics/string_utils.lua new file mode 100644 index 00000000..f3fed45e --- /dev/null +++ b/metrics/string_utils.lua @@ -0,0 +1,20 @@ +local log = require('log') + +local function check_symbols(s) + if string.find(s, '%c') ~= nil then + log.error('Do not use control characters, this will raise an error in the future.') + end +end + +local function build_name(prefix, suffix) + if #suffix == 0 then + return prefix + end + + return prefix .. '_' .. suffix +end + +return { + check_symbols = check_symbols, + build_name = build_name, +} diff --git a/test/collectors/counter_test.lua b/test/collectors/counter_test.lua index 9bd77403..d5e415b1 100644 --- a/test/collectors/counter_test.lua +++ b/test/collectors/counter_test.lua @@ -1,6 +1,8 @@ local t = require('luatest') local g = t.group() +local luatest_capture = require('luatest.capture') + local metrics = require('metrics') local utils = require('test.utils') @@ -103,3 +105,56 @@ g.test_metainfo_immutable = function() metainfo['my_useful_info'] = 'there' t.assert_equals(c.metainfo, {my_useful_info = 'here'}) end + +local control_characters_cases = { + in_name = function() + metrics.counter('cnt\tlab') + end, + in_observation_label_key = function() + local collector = metrics.counter('cnt') + collector:inc(1, {['lab\tval\tlab2'] = 'val2'}) + end, + in_observation_label_value = function() + local collector = metrics.counter('cnt') + collector:inc(1, {lab = 'val\tlab2\tval2'}) + end, +} + +for name, case in pairs(control_characters_cases) do + g['test_control_characters_' .. name .. 'are_not_expected'] = function() + local capture = luatest_capture:new() + capture:enable() + + case() + + local stdout = utils.fflush_main_server_output(nil, capture) + capture:disable() + + t.assert_str_contains( + stdout, + 'Do not use control characters, this will raise an error in the future.') + end +end + +g.test_collect_extended = function() + local c = metrics.counter('cnt', nil, {my_useful_info = 'here'}) + c:inc(3, {mylabel = 'myvalue1'}) + c:inc(2, {mylabel = 'myvalue2'}) + + local res = c:collect{extended_format = true} + t.assert_type(res, 'table') + t.assert_equals(res.name, c.name) + t.assert_equals(res.name_prefix, c.name_prefix) + t.assert_equals(res.kind, c.kind) + t.assert_equals(res.help, c.help) + t.assert_equals(res.metainfo, c.metainfo) + t.assert_gt(res.timestamp, 0) + t.assert_type(res.observations, 'table') + t.assert_type(res.observations[''], 'table') + t.assert_equals(utils.len(res.observations['']), 2) + for _, v in pairs(res.observations['']) do + t.assert_type(v.value, 'number') + t.assert_type(v.label_pairs, 'table') + t.assert_type(v.label_pairs['mylabel'], 'string') + end +end diff --git a/test/collectors/gauge_test.lua b/test/collectors/gauge_test.lua index e089ae31..5b1c895a 100644 --- a/test/collectors/gauge_test.lua +++ b/test/collectors/gauge_test.lua @@ -1,6 +1,8 @@ local t = require('luatest') local g = t.group() +local luatest_capture = require('luatest.capture') + local metrics = require('metrics') local utils = require('test.utils') @@ -90,3 +92,56 @@ g.test_metainfo_immutable = function() metainfo['my_useful_info'] = 'there' t.assert_equals(c.metainfo, {my_useful_info = 'here'}) end + +local control_characters_cases = { + in_name = function() + metrics.gauge('gauge\tlab') + end, + in_observation_label_key = function() + local collector = metrics.gauge('gauge') + collector:set(1, {['lab\tval\tlab2'] = 'val2'}) + end, + in_observation_label_value = function() + local collector = metrics.gauge('gauge') + collector:set(1, {lab = 'val\tlab2\tval2'}) + end, +} + +for name, case in pairs(control_characters_cases) do + g['test_control_characters_' .. name .. 'are_not_expected'] = function() + local capture = luatest_capture:new() + capture:enable() + + case() + + local stdout = utils.fflush_main_server_output(nil, capture) + capture:disable() + + t.assert_str_contains( + stdout, + 'Do not use control characters, this will raise an error in the future.') + end +end + +g.test_collect_extended = function() + local c = metrics.gauge('gauge', nil, {my_useful_info = 'here'}) + c:set(3, {mylabel = 'myvalue1'}) + c:set(2, {mylabel = 'myvalue2'}) + + local res = c:collect{extended_format = true} + t.assert_type(res, 'table') + t.assert_equals(res.name, c.name) + t.assert_equals(res.name_prefix, c.name_prefix) + t.assert_equals(res.kind, c.kind) + t.assert_equals(res.help, c.help) + t.assert_equals(res.metainfo, c.metainfo) + t.assert_gt(res.timestamp, 0) + t.assert_type(res.observations, 'table') + t.assert_type(res.observations[''], 'table') + t.assert_equals(utils.len(res.observations['']), 2) + for _, v in pairs(res.observations['']) do + t.assert_type(v.value, 'number') + t.assert_type(v.label_pairs, 'table') + t.assert_type(v.label_pairs['mylabel'], 'string') + end +end diff --git a/test/collectors/histogram_test.lua b/test/collectors/histogram_test.lua index d765ec13..908b9805 100644 --- a/test/collectors/histogram_test.lua +++ b/test/collectors/histogram_test.lua @@ -1,6 +1,8 @@ local t = require('luatest') local g = t.group() +local luatest_capture = require('luatest.capture') + local metrics = require('metrics') local Histogram = require('metrics.collectors.histogram') local utils = require('test.utils') @@ -116,3 +118,72 @@ g.test_metainfo_immutable = function() t.assert_equals(h.count_collector.metainfo, {my_useful_info = 'here'}) t.assert_equals(h.bucket_collector.metainfo, {my_useful_info = 'here'}) end + +local control_characters_cases = { + in_name = function() + metrics.histogram('hist\tlab', nil, {2, 4}) + end, + in_observation_label_key = function() + local collector = metrics.histogram('hist', nil, {2, 4}) + collector:observe(1, {['lab\tval\tlab2'] = 'val2'}) + end, + in_observation_label_value = function() + local collector = metrics.histogram('hist', nil, {2, 4}) + collector:observe(1, {lab = 'val\tlab2\tval2'}) + end, +} + +for name, case in pairs(control_characters_cases) do + g['test_control_characters_' .. name .. 'are_not_expected'] = function() + local capture = luatest_capture:new() + capture:enable() + + case() + + local stdout = utils.fflush_main_server_output(nil, capture) + capture:disable() + + t.assert_str_contains( + stdout, + 'Do not use control characters, this will raise an error in the future.') + end +end + +g.test_collect_extended = function() + local buckets = {2, 4} + local bucket_count = #buckets + 1 + local c = metrics.histogram('histogram', nil, buckets, {my_useful_info = 'here'}) + c:observe(3, {mylabel = 'myvalue1'}) + c:observe(2, {mylabel = 'myvalue2'}) + + local res = c:collect{extended_format = true} + t.assert_type(res, 'table') + t.assert_equals(res.name, c.name) + t.assert_equals(res.name_suffix, c.name_suffix) + t.assert_equals(res.kind, c.kind) + t.assert_equals(res.help, c.help) + t.assert_equals(res.metainfo, c.metainfo) + t.assert_gt(res.timestamp, 0) + t.assert_type(res.observations, 'table') + + t.assert_equals(utils.len(res.observations.bucket), 2 * bucket_count) + t.assert_equals(utils.len(res.observations.sum), 2) + t.assert_equals(utils.len(res.observations.count), 2) + + for k, _ in pairs(res.observations.sum) do + t.assert_type(res.observations.count[k], 'table', "Each sum observation has corresponding count") + end + + for _, section in ipairs({'count', 'sum', 'bucket'}) do + for _, v in pairs(res.observations[section]) do + t.assert_type(v.value, 'number') + t.assert_type(v.label_pairs, 'table') + t.assert_type(v.label_pairs['mylabel'], 'string') + end + end +end + +g.test_internal_collect_observations = function() + local c = metrics.histogram('histogram', nil, {2, 4}, {my_useful_info = 'here'}) + t.assert_error_msg_contains('Not supported', function() c:_collect_v2_observations() end) +end diff --git a/test/collectors/shared_test.lua b/test/collectors/shared_test.lua index e76c02d2..9f67fae0 100644 --- a/test/collectors/shared_test.lua +++ b/test/collectors/shared_test.lua @@ -1,6 +1,8 @@ local t = require('luatest') local g = t.group() +local luatest_capture = require('luatest.capture') + local utils = require('test.utils') local Shared = require('metrics.collectors.shared') @@ -44,3 +46,94 @@ g.test_metainfo_immutable = function() metainfo['my_useful_info'] = 'there' t.assert_equals(c.metainfo, {my_useful_info = 'here'}) end + +local control_characters_cases = { + in_kind = function() + Shared:new_class('test_class\tlab', {'inc'}) + end, + in_name = function() + local class = Shared:new_class('test_class', {'inc'}) + class:new('test\tlab') + end, + in_observation_label_key = function() + local class = Shared:new_class('test_class', {'inc'}) + local collector = class:new('test') + collector:inc(1, {['lab\tval\tlab2'] = 'val2'}) + end, + in_observation_label_value = function() + local class = Shared:new_class('test_class', {'inc'}) + local collector = class:new('test') + collector:inc(1, {lab = 'val\tlab2\tval2'}) + end, +} + +for name, case in pairs(control_characters_cases) do + g['test_control_characters_' .. name .. 'are_not_expected'] = function() + local capture = luatest_capture:new() + capture:enable() + + case() + + local stdout = utils.fflush_main_server_output(nil, capture) + capture:disable() + + t.assert_str_contains( + stdout, + 'Do not use control characters, this will raise an error in the future.') + end +end + +local name_prefix_cases = { + without_suffix = { + name = 'my_metric', + name_prefix = 'my_metric', + }, + with_current_suffix = { + name = 'my_metric_current', + name_prefix = 'my_metric', + }, + with_total_suffix = { + name = 'my_metric_total', + name_prefix = 'my_metric', + }, + with_current_not_as_suffix = { + name = 'electrical_current_value', + name_prefix = 'electrical_current_value', + }, + with_total_not_as_suffix = { + name = 'sold_total_overdose_dvds', + name_prefix = 'sold_total_overdose_dvds', + }, +} + +for name, case in pairs(name_prefix_cases) do + g['test_name_prefix_if_' .. name] = function() + local class = Shared:new_class(case.name .. '_class', {'inc'}) + local c = class:new(case.name) + t.assert_equals(c.name_prefix, case.name_prefix) + end +end + +g.test_collect_extended = function() + local class = Shared:new_class('test_class', {'inc'}) + local c = class:new('test', nil, {my_useful_info = 'here'}) + c:inc(3, {mylabel = 'myvalue1'}) + c:inc(2, {mylabel = 'myvalue2'}) + + local res = c:collect{extended_format = true} + t.assert_type(res, 'table') + t.assert_equals(res.name, c.name) + t.assert_equals(res.name_prefix, c.name_prefix) + t.assert_equals(res.kind, c.kind) + t.assert_equals(res.help, c.help) + t.assert_equals(res.metainfo, c.metainfo) + t.assert_gt(res.timestamp, 0) + t.assert_type(res.observations, 'table') + t.assert_type(res.observations[''], 'table') + t.assert_equals(utils.len(res.observations['']), 2) + for _, v in pairs(res.observations['']) do + t.assert_type(v.value, 'number') + t.assert_type(v.label_pairs, 'table') + t.assert_type(v.label_pairs['mylabel'], 'string') + end +end diff --git a/test/collectors/summary_test.lua b/test/collectors/summary_test.lua index cdade065..a108ed84 100644 --- a/test/collectors/summary_test.lua +++ b/test/collectors/summary_test.lua @@ -1,6 +1,8 @@ local t = require('luatest') local g = t.group() +local luatest_capture = require('luatest.capture') + local utils = require('test.utils') local Summary = require('metrics.collectors.summary') @@ -252,3 +254,72 @@ g.test_metainfo_immutable = function() t.assert_equals(c.sum_collector.metainfo, {my_useful_info = 'here'}) t.assert_equals(c.count_collector.metainfo, {my_useful_info = 'here'}) end + +local control_characters_cases = { + in_name = function() + Summary:new('latency\tlab', nil, {[0.5]=0.01, [0.9]=0.01, [0.99]=0.01}) + end, + in_observation_label_key = function() + local collector = Summary:new('latency', nil, {[0.5]=0.01, [0.9]=0.01, [0.99]=0.01}) + collector:observe(1, {['lab\tval\tlab2'] = 'val2'}) + end, + in_observation_label_value = function() + local collector = Summary:new('latency', nil, {[0.5]=0.01, [0.9]=0.01, [0.99]=0.01}) + collector:observe(1, {lab = 'val\tlab2\tval2'}) + end, +} + +for name, case in pairs(control_characters_cases) do + g['test_control_characters_' .. name .. 'are_not_expected'] = function() + local capture = luatest_capture:new() + capture:enable() + + case() + + local stdout = utils.fflush_main_server_output(nil, capture) + capture:disable() + + t.assert_str_contains( + stdout, + 'Do not use control characters, this will raise an error in the future.') + end +end + +g.test_collect_extended = function() + local quantiles = {[0.5]=0.01, [0.9]=0.01, [0.99]=0.01} + local quantile_count = utils.len(quantiles) + local c = metrics.summary('summary', nil, quantiles, nil, {my_useful_info = 'here'}) + c:observe(3, {mylabel = 'myvalue1'}) + c:observe(2, {mylabel = 'myvalue2'}) + + local res = c:collect{extended_format = true} + t.assert_type(res, 'table') + t.assert_equals(res.name, c.name) + t.assert_equals(res.kind, c.kind) + t.assert_equals(res.help, c.help) + t.assert_equals(res.metainfo, c.metainfo) + t.assert_gt(res.timestamp, 0) + t.assert_type(res.observations, 'table') + + t.assert_equals(utils.len(res.observations['']), 2 * quantile_count) + t.assert_equals(utils.len(res.observations.sum), 2) + t.assert_equals(utils.len(res.observations.count), 2) + + for k, _ in pairs(res.observations.sum) do + t.assert_type(res.observations.count[k], 'table', "Each sum observation has corresponding count") + end + + for _, section in ipairs({'count', 'sum', ''}) do + for _, v in pairs(res.observations[section]) do + t.assert_type(v.value, 'number') + t.assert_type(v.label_pairs, 'table') + t.assert_type(v.label_pairs['mylabel'], 'string') + end + end +end + +g.test_internal_collect_observations = function() + local quantiles = {[0.5]=0.01, [0.9]=0.01, [0.99]=0.01} + local c = metrics.summary('summary', nil, quantiles, nil, {my_useful_info = 'here'}) + t.assert_error_msg_contains('Not supported', function() c:_collect_v2_observations() end) +end diff --git a/test/metrics_test.lua b/test/metrics_test.lua index 1a58cb9e..aefaddd5 100755 --- a/test/metrics_test.lua +++ b/test/metrics_test.lua @@ -3,11 +3,22 @@ local t = require('luatest') local g = t.group('collectors') +local luatest_capture = require('luatest.capture') + local metrics = require('metrics') local utils = require('test.utils') g.before_all(utils.init) +g.before_each(function() + -- Reset to defaults. + metrics.cfg{ + include = 'all', + exclude = {}, + labels = {}, + } +end) + g.after_each(function() -- Delete all collectors and global labels metrics.clear() @@ -234,3 +245,77 @@ for name, case in pairs(collect_default_only_cases) do end end end + +local control_characters_cases = { + in_global_label_key = function() + metrics.cfg{labels = {['lab\tval\tlab2'] = 'val2'}} + end, + in_global_label_value = function() + metrics.cfg{labels = {lab = 'val\tlab2\tval2'}} + end, +} + +for name, case in pairs(control_characters_cases) do + g['test_control_characters_' .. name .. 'are_not_expected'] = function() + local capture = luatest_capture:new() + capture:enable() + + case() + + local stdout = utils.fflush_main_server_output(nil, capture) + capture:disable() + + t.assert_str_contains( + stdout, + 'Do not use control characters, this will raise an error in the future.') + end +end + +g.test_collect_extended = function() + local res_plain = metrics.collect{invoke_callbacks = true} + local res_extended = metrics.collect{invoke_callbacks = true, extended_format=true} + + local count = 0 + for _, collector_obs in pairs(res_extended) do + t.assert_type(collector_obs.name, 'string') + t.assert_type(collector_obs.name_prefix, 'string') + t.assert_type(collector_obs.kind, 'string') + t.assert_type(collector_obs.help, 'string') + t.assert_type(collector_obs.metainfo, 'table') + t.assert_gt(collector_obs.timestamp, 0) + t.assert_type(collector_obs.observations, 'table') + + for _, obs_group in pairs(collector_obs.observations) do + count = count + utils.len(obs_group) + end + end + + t.assert_equals(count, #res_plain, "No observations lost in extended format") +end + +local function observations_covers_by_key(res_1, res_2) + for coll_key, coll_obs_1 in pairs(res_1) do + t.assert_type(res_2[coll_key], 'table', "Collector info found") + local coll_obs_2 = res_2[coll_key] + t.assert_equals(coll_obs_1.name, coll_obs_2.name) + t.assert_equals(coll_obs_1.kind, coll_obs_2.kind) + + for group_name, obs_group_1 in pairs(coll_obs_1.observations) do + local obs_group_2 = coll_obs_2.observations[group_name] + t.assert_type(obs_group_2, 'table', "Observation group found") + for key, obs_1 in pairs(obs_group_1) do + local obs_2 = obs_group_2[key] + t.assert_type(obs_2, 'table', "Observation found") + t.assert_equals(obs_1.label_pairs, obs_2.label_pairs) + end + end + end +end + +g.test_collect_extended_keys = function() + local res_1 = metrics.collect{invoke_callbacks = true, extended_format=true} + local res_2 = metrics.collect{invoke_callbacks = true, extended_format=true} + + observations_covers_by_key(res_1, res_2) + observations_covers_by_key(res_2, res_1) +end diff --git a/test/plugins/graphite_test.lua b/test/plugins/graphite_test.lua index 8891dc06..136ece06 100755 --- a/test/plugins/graphite_test.lua +++ b/test/plugins/graphite_test.lua @@ -153,3 +153,69 @@ g.test_graphite_kills_previous_fibers_on_init = function() fiber.yield() -- let cancelled fibers disappear from fiber.info() t.assert_equals(count_workers(), 1) end + +g.test_collect_and_push_preseves_format = function(group) + -- Prepare some data for all collector types. + metrics.cfg{include = 'all', exclude = {}, labels = {alias = 'router-3'}} + + local c = metrics.counter('cnt', nil, {my_useful_info = 'here'}) + c:inc(3, {mylabel = 'myvalue1'}) + c:inc(2, {mylabel = 'myvalue2'}) + + c = metrics.gauge('gauge', nil, {my_useful_info = 'here'}) + c:set(3, {mylabel = 'myvalue1'}) + c:set(2, {mylabel = 'myvalue2'}) + + c = metrics.histogram('histogram', nil, {2, 4}, {my_useful_info = 'here'}) + c:observe(3, {mylabel = 'myvalue1'}) + c:observe(2, {mylabel = 'myvalue2'}) + + local port_v1 = 22003 + group.sock_v1 = socket('AF_INET', 'SOCK_DGRAM', 'udp') + group.sock_v1:bind('127.0.0.1', port_v1) + local port_v2 = 22004 + group.sock_v2 = socket('AF_INET', 'SOCK_DGRAM', 'udp') + group.sock_v2:bind('127.0.0.1', port_v2) + + graphite.internal.collect_and_push_v1({ + prefix = 'tarantool', + host = '127.0.0.1', + port = port_v1, + sock = group.sock_v1, + }) + graphite.internal.collect_and_push_v2({ + prefix = 'tarantool', + host = '127.0.0.1', + port = port_v2, + sock = group.sock_v2, + }) + + local output_v2 = '' + while true do + local output_v2_part = group.sock_v2:recvfrom(200) + if output_v2_part == nil or output_v2_part == '' then + break + end + + output_v2 = output_v2 .. ' ' .. output_v2_part + end + + while true do + local output_v1_part = group.sock_v1:recvfrom(200) + if output_v1_part == nil or output_v1_part == '' then + break + end + + t.assert_str_contains(output_v2, output_v1_part:split(' ')[1]) + end +end + +g.after_test('test_collect_and_push_preseves_format', function(group) + if group.sock_v1 then + group.sock_v1:close() + end + + if group.sock_v2 then + group.sock_v2:close() + end +end) diff --git a/test/plugins/json_test.lua b/test/plugins/json_test.lua index 3997faac..1902a049 100755 --- a/test/plugins/json_test.lua +++ b/test/plugins/json_test.lua @@ -78,3 +78,27 @@ g.test_number64_ull_value_parses_to_json_number = function() t.assert_not_equals(type(obs_ull.value), 'string', 'number64 is not casted to string on export') t.assert_equals(obs_ull.value, 9007199254740992ULL, 'number64 ULL parsed to corrent number value') end + +g.test_collect_and_serialize_preserves_format = function() + -- Prepare some data for all collector types. + metrics.cfg{include = 'all', exclude = {}, labels = {alias = 'router-3'}} + + local c = metrics.counter('cnt', nil, {my_useful_info = 'here'}) + c:inc(3, {mylabel = 'myvalue1'}) + c:inc(2, {mylabel = 'myvalue2'}) + + c = metrics.gauge('gauge', nil, {my_useful_info = 'here'}) + c:set(3, {mylabel = 'myvalue1'}) + c:set(2, {mylabel = 'myvalue2'}) + + c = metrics.histogram('histogram', nil, {2, 4}, {my_useful_info = 'here'}) + c:observe(3, {mylabel = 'myvalue1'}) + c:observe(2, {mylabel = 'myvalue2'}) + + local output_v1 = json.decode(json_exporter.internal.collect_and_serialize_v1()) + local output_v2 = json.decode(json_exporter.internal.collect_and_serialize_v2()) + + for _, obs in ipairs(output_v1) do + utils.find_obs(obs.metric_name, obs.label_pairs, output_v2) + end +end diff --git a/test/plugins/prometheus_test.lua b/test/plugins/prometheus_test.lua index 27b9c12c..67c04a43 100755 --- a/test/plugins/prometheus_test.lua +++ b/test/plugins/prometheus_test.lua @@ -6,7 +6,7 @@ local t = require('luatest') local g = t.group('prometheus_plugin') local metrics = require('metrics') -local http_handler = require('metrics.plugins.prometheus').collect_http +local prometheus = require('metrics.plugins.prometheus') local utils = require('test.utils') g.before_all(function() @@ -24,7 +24,7 @@ g.before_all(function() -- Enable default metrics collections metrics.enable_default_metrics() - g.prometheus_metrics = http_handler().body + g.prometheus_metrics = prometheus.collect_http().body end) g.after_each(function() @@ -42,3 +42,33 @@ g.test_cdata_handling = function() t.assert_str_contains(g.prometheus_metrics, 'tnt_space_bsize{name="random_space_for_prometheus",engine="memtx"} 0', 'Plugin output serialize 0ULL as +Inf') end + +g.test_collect_and_serialize_preserves_format = function() + -- Prepare some data for all collector types. + metrics.cfg{include = 'all', exclude = {}, labels = {alias = 'router-3'}} + + local c = metrics.counter('cnt', nil, {my_useful_info = 'here'}) + c:inc(3, {mylabel = 'myvalue1'}) + c:inc(2, {mylabel = 'myvalue2'}) + + c = metrics.gauge('gauge', nil, {my_useful_info = 'here'}) + c:set(3, {mylabel = 'myvalue1'}) + c:set(2, {mylabel = 'myvalue2'}) + + c = metrics.histogram('histogram', nil, {2, 4}, {my_useful_info = 'here'}) + c:observe(3, {mylabel = 'myvalue1'}) + c:observe(2, {mylabel = 'myvalue2'}) + + local output_v1 = prometheus.internal.collect_and_serialize_v1() + local output_v2 = prometheus.internal.collect_and_serialize_v2() + + for line in output_v1:gmatch('[^\r\n]+') do + if line:startswith('#') then + t.assert_str_contains(output_v2, line) + else + local _, _, value_line_header = line:find('^(.*)%s') + -- Values of default metrics will be different for two different observations. + t.assert_str_contains(output_v2, value_line_header) + end + end +end