Skip to content

Commit

Permalink
api: compute min and max for gauges
Browse files Browse the repository at this point in the history
This patch introduces tool to compute counters per second rate. No
additional deepcopies are performed, same as in collect.

Part of tarantool/tarantool#7725
Part of tarantool/tarantool#7728
  • Loading branch information
DifferentialOrange committed Feb 15, 2023
1 parent d87459c commit a651756
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `metrics.collect()`;
- tools to compute metrics aggregates:
- per second rate for counters;
- min and max for gauges;

### Changed
- Setup cartridge hotreload inside the role
Expand Down
4 changes: 3 additions & 1 deletion doc/monitoring/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,9 @@ Metrics functions
Supported aggregates:

* ``rate`` for counter collectors: per second rate of value change for the last
two observations.
two observations;
* ``min`` for gauge collectors: minimal value for the history of observations;
* ``max`` for gauge collectors: maximal value for the history of observations.

:param table output_with_aggregates_prev: a previous result of this method call.
Use ``nil`` if this is the first invokation. You may use
Expand Down
75 changes: 75 additions & 0 deletions metrics/aggregates.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ local Gauge = require('metrics.collectors.gauge')
local mksec_in_sec = 1e6

local RATE_SUFFIX = 'per_second'
local MIN_SUFFIX = 'min'
local MAX_SUFFIX = 'max'

local function compute_rate_value(time_delta, obs_prev, obs)
if obs_prev == nil then
Expand Down Expand Up @@ -70,13 +72,86 @@ local function compute_counter_rate(output_with_aggregates_prev, output, coll_ke
return output
end

local function compute_extremum_value(obs_prev, obs, method)
if obs_prev == nil then
return {
label_pairs = obs.label_pairs,
value = obs.value,
}
end

return {
label_pairs = obs.label_pairs,
-- math.min and math.max doesn't work with cdata.
value = method(tonumber(obs_prev.value), tonumber(obs.value))
}
end

local function compute_gauge_extremum(output_with_aggregates_prev, output, coll_key, coll_obs,
extremum_method, extremum_suffix, extremum_help_line)
local name = string_utils.build_name(coll_obs.name_prefix, extremum_suffix)
local kind = coll_obs.kind
local registry_key = string_utils.build_registry_key(name, kind)

if output[registry_key] ~= nil then
-- If, for any reason, registry collision had happenned,
-- we assume that there is already an aggregate metric with the
-- similar meaning.
return output
end

local known_extremum
if output_with_aggregates_prev[registry_key] then -- previous extremum
known_extremum = output_with_aggregates_prev[registry_key]
elseif output_with_aggregates_prev[coll_key] then -- previous value
known_extremum = output_with_aggregates_prev[coll_key]
else -- only current observation
known_extremum = coll_obs
end

local values = {}

for key, obs in pairs(coll_obs.observations['']) do
local obs_prev = known_extremum.observations[''][key]
values[key] = compute_extremum_value(obs_prev, obs, extremum_method)
end

local metainfo = table.deepcopy(coll_obs.metainfo)
metainfo.aggregate = true

output[registry_key] = {
name = name,
name_prefix = coll_obs.name_prefix,
help = extremum_help_line .. coll_obs.name,
kind = kind,
metainfo = metainfo,
timestamp = coll_obs.timestamp,
observations = {[''] = values}
}

return output
end

local function compute_gauge_min(output_with_aggregates_prev, output, coll_key, coll_obs)
return compute_gauge_extremum(output_with_aggregates_prev, output, coll_key, coll_obs,
math.min, MIN_SUFFIX, "Minimum of ")
end

local function compute_gauge_max(output_with_aggregates_prev, output, coll_key, coll_obs)
return compute_gauge_extremum(output_with_aggregates_prev, output, coll_key, coll_obs,
math.max, MAX_SUFFIX, "Maximum of ")
end


local default_kind_rules = {
[Counter.kind] = { 'rate' },
[Gauge.kind] = { 'min', 'max' },
}

local rule_processors = {
rate = compute_counter_rate,
min = compute_gauge_min,
max = compute_gauge_max,
}

local function compute(output_with_aggregates_prev, output, kind_rules)
Expand Down
175 changes: 175 additions & 0 deletions test/aggregates_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,36 @@ local function get_counter_example(timestamp, value1, value2)
return res
end

local function get_gauge_example(timestamp, value1, value2)
local res = {
lj_gc_memorygauge = {
name = 'lj_gc_memory',
name_prefix = 'lj_gc_memory',
kind = 'gauge',
help = 'Memory currently allocated',
metainfo = { default = true },
timestamp = timestamp,
observations = { [''] = {} }
}
}

if value1 ~= nil then
res['lj_gc_memorygauge'].observations[''][''] = {
label_pairs = { alias = 'router' },
value = value1,
}
end

if value2 ~= nil then
res['lj_gc_memorygauge'].observations['']['source\tvinyl_procedures'] = {
label_pairs = { alias = 'router', source = 'vinyl_procedures' },
value = value2,
}
end

return res
end

g.test_unknown_rule = function()
local output = get_counter_example(1676364616294847ULL, 14148, 3204)

Expand Down Expand Up @@ -130,3 +160,148 @@ g.test_counter_rate_disabled = function()
t.assert_equals(utils.len(output_with_aggregates_2), 1,
"No rate computed due to options")
end

g.test_gauge_extremum_no_previous_data = function()
local output = get_gauge_example(1676364616294847ULL, 2020047, 327203)

local output_with_aggregates = metrics.compute_aggregates(nil, output)
t.assert_equals(utils.len(output_with_aggregates), 3,
"Min and max computed for a single observation")

local min_obs = output_with_aggregates['lj_gc_memory_mingauge']
t.assert_not_equals(min_obs, nil, "min computed")
t.assert_equals(min_obs.name, 'lj_gc_memory_min')
t.assert_equals(min_obs.name_prefix, 'lj_gc_memory')
t.assert_equals(min_obs.kind, 'gauge')
t.assert_equals(min_obs.help, 'Minimum of lj_gc_memory')
t.assert_equals(min_obs.metainfo.default, true)
t.assert_equals(min_obs.metainfo.aggregate, true)
t.assert_equals(min_obs.timestamp, 1676364616294847ULL)
t.assert_equals(min_obs.observations[''][''].label_pairs, { alias = 'router' })
t.assert_almost_equals(min_obs.observations[''][''].value, 2020047)
t.assert_equals(min_obs.observations['']['source\tvinyl_procedures'].label_pairs,
{ alias = 'router', source = 'vinyl_procedures' })
t.assert_almost_equals(min_obs.observations['']['source\tvinyl_procedures'].value, 327203)

local max_obs = output_with_aggregates['lj_gc_memory_maxgauge']
t.assert_not_equals(max_obs, nil, "max computed")
t.assert_equals(max_obs.name, 'lj_gc_memory_max')
t.assert_equals(max_obs.name_prefix, 'lj_gc_memory')
t.assert_equals(max_obs.kind, 'gauge')
t.assert_equals(max_obs.help, 'Maximum of lj_gc_memory')
t.assert_equals(max_obs.metainfo.default, true)
t.assert_equals(max_obs.metainfo.aggregate, true)
t.assert_equals(max_obs.timestamp, 1676364616294847ULL)
t.assert_equals(max_obs.observations[''][''].label_pairs, { alias = 'router' })
t.assert_almost_equals(max_obs.observations[''][''].value, 2020047)
t.assert_equals(max_obs.observations['']['source\tvinyl_procedures'].label_pairs,
{ alias = 'router', source = 'vinyl_procedures' })
t.assert_almost_equals(max_obs.observations['']['source\tvinyl_procedures'].value, 327203)
end

g.test_gauge_extremum_prev_aggregates = function()
local output_1 = get_gauge_example(1676364616294847ULL, 2020047, 327203)
local output_2 = get_gauge_example(1676364616294847ULL, 1920047, 429203)

local output_with_aggregates_1 = metrics.compute_aggregates(nil, output_1)
local output_with_aggregates_2 = metrics.compute_aggregates(output_with_aggregates_1, output_2)
t.assert_equals(utils.len(output_with_aggregates_2), 3,
"Min and max computed for a single observation")

local min_obs = output_with_aggregates_2['lj_gc_memory_mingauge']
t.assert_not_equals(min_obs, nil, "min computed")
t.assert_equals(min_obs.name, 'lj_gc_memory_min')
t.assert_equals(min_obs.name_prefix, 'lj_gc_memory')
t.assert_equals(min_obs.kind, 'gauge')
t.assert_equals(min_obs.help, 'Minimum of lj_gc_memory')
t.assert_equals(min_obs.metainfo.default, true)
t.assert_equals(min_obs.metainfo.aggregate, true)
t.assert_equals(min_obs.timestamp, 1676364616294847ULL)
t.assert_equals(min_obs.observations[''][''].label_pairs, { alias = 'router' })
t.assert_almost_equals(min_obs.observations[''][''].value, 1920047)
t.assert_equals(min_obs.observations['']['source\tvinyl_procedures'].label_pairs,
{ alias = 'router', source = 'vinyl_procedures' })
t.assert_almost_equals(min_obs.observations['']['source\tvinyl_procedures'].value, 327203)

local max_obs = output_with_aggregates_2['lj_gc_memory_maxgauge']
t.assert_not_equals(max_obs, nil, "max computed")
t.assert_equals(max_obs.name, 'lj_gc_memory_max')
t.assert_equals(max_obs.name_prefix, 'lj_gc_memory')
t.assert_equals(max_obs.kind, 'gauge')
t.assert_equals(max_obs.help, 'Maximum of lj_gc_memory')
t.assert_equals(max_obs.metainfo.default, true)
t.assert_equals(max_obs.metainfo.aggregate, true)
t.assert_equals(max_obs.timestamp, 1676364616294847ULL)
t.assert_equals(max_obs.observations[''][''].label_pairs, { alias = 'router' })
t.assert_almost_equals(max_obs.observations[''][''].value, 2020047)
t.assert_equals(max_obs.observations['']['source\tvinyl_procedures'].label_pairs,
{ alias = 'router', source = 'vinyl_procedures' })
t.assert_almost_equals(max_obs.observations['']['source\tvinyl_procedures'].value, 429203)
end

g.test_gauge_extremum_prev_raw = function()
local output_1 = get_gauge_example(1676364616294847ULL, 2020047, 327203)
local output_2 = get_gauge_example(1676364616294847ULL, 1920047, 429203)

local output_with_aggregates_2 = metrics.compute_aggregates(output_1, output_2)
t.assert_equals(utils.len(output_with_aggregates_2), 3,
"Min and max computed for a single observation")

local min_obs = output_with_aggregates_2['lj_gc_memory_mingauge']
t.assert_not_equals(min_obs, nil, "min computed")
t.assert_equals(min_obs.name, 'lj_gc_memory_min')
t.assert_equals(min_obs.name_prefix, 'lj_gc_memory')
t.assert_equals(min_obs.kind, 'gauge')
t.assert_equals(min_obs.help, 'Minimum of lj_gc_memory')
t.assert_equals(min_obs.metainfo.default, true)
t.assert_equals(min_obs.metainfo.aggregate, true)
t.assert_equals(min_obs.timestamp, 1676364616294847ULL)
t.assert_equals(min_obs.observations[''][''].label_pairs, { alias = 'router' })
t.assert_almost_equals(min_obs.observations[''][''].value, 1920047)
t.assert_equals(min_obs.observations['']['source\tvinyl_procedures'].label_pairs,
{ alias = 'router', source = 'vinyl_procedures' })
t.assert_almost_equals(min_obs.observations['']['source\tvinyl_procedures'].value, 327203)

local max_obs = output_with_aggregates_2['lj_gc_memory_maxgauge']
t.assert_not_equals(max_obs, nil, "max computed")
t.assert_equals(max_obs.name, 'lj_gc_memory_max')
t.assert_equals(max_obs.name_prefix, 'lj_gc_memory')
t.assert_equals(max_obs.kind, 'gauge')
t.assert_equals(max_obs.help, 'Maximum of lj_gc_memory')
t.assert_equals(max_obs.metainfo.default, true)
t.assert_equals(max_obs.metainfo.aggregate, true)
t.assert_equals(max_obs.timestamp, 1676364616294847ULL)
t.assert_equals(max_obs.observations[''][''].label_pairs, { alias = 'router' })
t.assert_almost_equals(max_obs.observations[''][''].value, 2020047)
t.assert_equals(max_obs.observations['']['source\tvinyl_procedures'].label_pairs,
{ alias = 'router', source = 'vinyl_procedures' })
t.assert_almost_equals(max_obs.observations['']['source\tvinyl_procedures'].value, 429203)
end

g.test_gauge_extremum_new_label = function()
local output_1 = get_gauge_example(1676364616294847ULL, 2020047, nil)
local output_2 = get_gauge_example(1676364616294847ULL, 1920047, 429203)

local output_with_aggregates_1 = metrics.compute_aggregates(nil, output_1)
local output_with_aggregates_2 = metrics.compute_aggregates(output_with_aggregates_1, output_2)
t.assert_equals(utils.len(output_with_aggregates_2), 3,
"Min and max computed for a single observation")

local min_obs = output_with_aggregates_2['lj_gc_memory_mingauge']
t.assert_almost_equals(min_obs.observations['']['source\tvinyl_procedures'].value, 429203)

local max_obs = output_with_aggregates_2['lj_gc_memory_maxgauge']
t.assert_almost_equals(max_obs.observations['']['source\tvinyl_procedures'].value, 429203)
end

g.test_gauge_min_max_disabled = function()
local output_1 = get_counter_example(1676364616294847ULL, 14148, 3204)
local output_2 = get_counter_example(1676364616294847ULL + 100 * 1e6, 14148 + 200, 3204 + 50)

local opts = { gauge = {} }
local output_with_aggregates_1 = metrics.compute_aggregates(nil, output_1, opts)
local output_with_aggregates_2 = metrics.compute_aggregates(output_with_aggregates_1, output_2, opts)

t.assert_equals(utils.len(output_with_aggregates_2), 1,
"No min or max computed due to options")
end

0 comments on commit a651756

Please sign in to comment.