Skip to content

Commit b75adb4

Browse files
committed
api: add metrics support
The patch adds the ability to export statistics to metrics >= 0.10.0. expirationd does not require the metrics package itself and tries to use an installed one. It also adds a new API method expirationd.cfg({options}). Part of #100
1 parent 7013e8a commit b75adb4

File tree

4 files changed

+503
-0
lines changed

4 files changed

+503
-0
lines changed

expirationd.lua

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ local checks = require("checks")
1010
local fun = require("fun")
1111
local log = require("log")
1212
local fiber = require("fiber")
13+
local is_metrics_package, metrics = pcall(require, "metrics")
1314

1415
-- get fiber id function
1516
local function get_fiber_id(fiber)
@@ -21,6 +22,7 @@ local function get_fiber_id(fiber)
2122
end
2223

2324
local task_list = {}
25+
local cfg = {metrics = true}
2426

2527
local constants = {
2628
-- default value of number of tuples that will be checked by one iteration
@@ -49,6 +51,79 @@ local constants = {
4951
atomic_iteration = false,
5052
}
5153

54+
local metrics_callback = nil
55+
local metrics_collectors = nil
56+
57+
local function metrics_enable()
58+
if metrics_callback then
59+
return
60+
end
61+
62+
local supported_v0_11 = false
63+
if is_metrics_package and metrics.unregister_callback then
64+
local counter = require('metrics.collectors.counter')
65+
supported_v0_11 = counter.remove and true or false
66+
end
67+
if not supported_v0_11 then
68+
error("metrics >= 0.11.0 is required", 3)
69+
end
70+
71+
local create_collector = function(name, description)
72+
return {
73+
collector = metrics.counter(name, description),
74+
task_value = {},
75+
}
76+
end
77+
78+
metrics_collectors = {
79+
["checked_count"] = create_collector(
80+
"expirationd_checked_count",
81+
"expirationd task's a number of checked tuples"
82+
),
83+
["expired_count"] = create_collector(
84+
"expirationd_expired_count",
85+
"expirationd task's a number of expired tuples"
86+
),
87+
["restarts"] = create_collector(
88+
"expirationd_restarts",
89+
"expirationd task's a number of restarts"
90+
),
91+
["working_time"] = create_collector(
92+
"expirationd_working_time",
93+
"expirationd task's operation time"
94+
),
95+
}
96+
97+
metrics_callback = function()
98+
for task_name, task in pairs(task_list) do
99+
local stats = task:statistics()
100+
for k, v in pairs(stats) do
101+
local prev_v = metrics_collectors[k].task_value[task_name] or 0
102+
local v_inc = v - prev_v
103+
metrics_collectors[k].collector:inc(v_inc, {name = task_name})
104+
metrics_collectors[k].task_value[task_name] = v
105+
end
106+
end
107+
end
108+
metrics.register_callback(metrics_callback)
109+
end
110+
111+
if cfg.metrics then
112+
enabled, _ = pcall(metrics_enable)
113+
cfg.metrics = enabled
114+
end
115+
116+
local function metrics_disable()
117+
if metrics_callback then
118+
for _, c in pairs(metrics_collectors) do
119+
metrics.registry:unregister(c.collector)
120+
end
121+
metrics.unregister_callback(metrics_callback)
122+
metrics_callback = nil
123+
metrics_collectors = nil
124+
end
125+
end
126+
52127
-- ========================================================================= --
53128
-- Task local functions
54129
-- ========================================================================= --
@@ -281,6 +356,12 @@ local Task_methods = {
281356
-- @function task.kill
282357
kill = function (self)
283358
self:stop()
359+
if metrics_collectors then
360+
for _, c in pairs(metrics_collectors) do
361+
c.collector:remove({name = self.name})
362+
c.task_value[self.name] = nil
363+
end
364+
end
284365
task_list[self.name] = nil
285366
end,
286367

@@ -400,6 +481,63 @@ end
400481
--
401482
-- @section Functions
402483

484+
--- Configure expirationd.
485+
--
486+
-- How to set up a configuration option:
487+
--
488+
-- ```
489+
-- expirationd.cfg({metrics = true})
490+
-- ```
491+
--
492+
-- How to get an option value:
493+
--
494+
-- ```
495+
-- print(expirationd.cfg.metrics)
496+
-- true
497+
-- ```
498+
--
499+
-- @table options
500+
--
501+
-- @bool[opt] options.metrics
502+
-- Enable or disable stats collection by [metrics][1]. metrics >= 0.11.0
503+
-- is required. It is enabled by default.
504+
--
505+
-- If enabled it creates four counter collectors, see @{task.statistics}:
506+
--
507+
-- 1. `expirationd_checked_count`
508+
--
509+
-- 2. `expirationd_expired_count`
510+
--
511+
-- 3. `expirationd_restarts`
512+
--
513+
-- 4. `expirationd_working_time`
514+
--
515+
-- Labeled with `name = task_name`.
516+
--
517+
-- [1]: https://github.com/tarantool/metrics/
518+
--
519+
-- @return None
520+
--
521+
-- @function expirationd.cfg
522+
local function expirationd_cfg(self, options)
523+
checks('table', {
524+
metrics = '?boolean',
525+
})
526+
527+
if options.metrics == nil then
528+
return
529+
end
530+
531+
if cfg.metrics ~= options.metrics then
532+
if options.metrics == true then
533+
metrics_enable()
534+
else
535+
metrics_disable()
536+
end
537+
rawset(cfg, 'metrics', options.metrics)
538+
end
539+
end
540+
403541
--- Run a scheduled task to check and process (expire) tuples in a given space.
404542
--
405543
-- How expirationd works in general:
@@ -949,6 +1087,12 @@ local function show_task_list_obsolete(...)
9491087
end
9501088

9511089
return {
1090+
cfg = setmetatable({}, {
1091+
__index = cfg,
1092+
__newindex = function() error("Use expirationd.cfg{} instead", 2) end,
1093+
__call = expirationd_cfg,
1094+
__serialize = function() return cfg end,
1095+
}),
9521096
start = expirationd_run_task,
9531097
stats = expirationd_task_stats,
9541098
update = expirationd_update,

test/helper.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,16 @@ function helpers.is_expired_true()
223223
return true
224224
end
225225

226+
function helpers.is_metrics_supported()
227+
local is_package, metrics = pcall(require, "metrics")
228+
if not is_package then
229+
return false
230+
end
231+
-- metrics >= 0.11.0 is required
232+
local counter = require('metrics.collectors.counter')
233+
return metrics.unregister_callback and counter.remove
234+
end
235+
226236
function helpers.iterate_with_func(task)
227237
return task.index:pairs(task.start_key(), { iterator = task.iterator_type })
228238
:take_while(

test/unit/cfg_test.lua

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
local expirationd = require("expirationd")
2+
local t = require("luatest")
3+
local helpers = require("test.helper")
4+
local g = t.group('expirationd_cfg')
5+
6+
function g.test_cfg_default_if_installed()
7+
t.skip_if(not helpers.is_metrics_supported(), "metrics is not installed")
8+
t.assert_equals(expirationd.cfg.metrics, true)
9+
end
10+
11+
function g.test_cfg_default_if_uninstalled()
12+
t.skip_if(helpers.is_metrics_supported(), "metrics is installed")
13+
t.assert_equals(expirationd.cfg.metrics, false)
14+
end
15+
16+
function g.test_cfg_newindex()
17+
t.assert_error_msg_content_equals("Use expirationd.cfg{} instead",
18+
function()
19+
expirationd.cfg.any_key = false
20+
end)
21+
end
22+
23+
function g.test_cfg_metrics_set_unset()
24+
t.skip_if(not helpers.is_metrics_supported(), "metrics is not installed")
25+
26+
expirationd.cfg({metrics = true})
27+
t.assert_equals(expirationd.cfg.metrics, true)
28+
expirationd.cfg({metrics = false})
29+
t.assert_equals(expirationd.cfg.metrics, false)
30+
end
31+
32+
function g.test_cfg_metrics_multiple_set_unset()
33+
t.skip_if(not helpers.is_metrics_supported(), "metrics is not installed")
34+
35+
expirationd.cfg({metrics = true})
36+
expirationd.cfg({metrics = true})
37+
t.assert_equals(expirationd.cfg.metrics, true)
38+
expirationd.cfg({metrics = false})
39+
expirationd.cfg({metrics = false})
40+
t.assert_equals(expirationd.cfg.metrics, false)
41+
end
42+
43+
function g.test_cfg_metrics_set_unsupported()
44+
t.skip_if(helpers.is_metrics_supported(), "metrics is installed")
45+
46+
t.assert_error_msg_content_equals("metrics >= 0.11.0 is required",
47+
function()
48+
expirationd.cfg({metrics = true})
49+
end)
50+
t.assert_equals(expirationd.cfg.metrics, false)
51+
end

0 commit comments

Comments
 (0)