Skip to content

Commit 2ebe150

Browse files
committed
api: add metrics support
The patch adds the ability to export statistics to metrics >= 0.11.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 bdff788 commit 2ebe150

File tree

6 files changed

+682
-0
lines changed

6 files changed

+682
-0
lines changed

expirationd.lua

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ 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")
14+
local is_hotreload_package, hotreload = pcall(require, "cartridge.hotreload")
1315

1416
-- get fiber id function
1517
local function get_fiber_id(fiber)
@@ -20,7 +22,30 @@ local function get_fiber_id(fiber)
2022
return fid
2123
end
2224

25+
local stash_names = {
26+
cfg = '__expirationd_cfg',
27+
metrics_stats = '__expirationd_metrics_stats',
28+
}
29+
30+
if is_hotreload_package then
31+
for _, name in pairs(stash_names) do
32+
hotreload.whitelist_globals({ name })
33+
end
34+
end
35+
36+
-- get a stash instance, initialize if needed
37+
local function stash_get(name)
38+
local instance = rawget(_G, name) or {}
39+
rawset(_G, name, instance)
40+
return instance
41+
end
42+
2343
local task_list = {}
44+
local cfg = stash_get(stash_names.cfg)
45+
if cfg.metrics == nil then
46+
cfg.metrics = true
47+
end
48+
local metrics_stats = stash_get(stash_names.metrics_stats)
2449

2550
local constants = {
2651
-- default value of number of tuples that will be checked by one iteration
@@ -49,6 +74,92 @@ local constants = {
4974
atomic_iteration = false,
5075
}
5176

77+
local function is_metrics_v_0_11_installed()
78+
if not is_metrics_package or metrics.unregister_callback == nil then
79+
return false
80+
end
81+
local counter = require('metrics.collectors.counter')
82+
return counter.remove and true or false
83+
end
84+
85+
local function metrics_enable()
86+
if not is_metrics_v_0_11_installed() then
87+
error("metrics >= 0.11.0 is required", 3)
88+
end
89+
90+
-- Workaround for a cartridge role reload:
91+
--
92+
-- Metrics package does not lose observation after a cartridge reload since
93+
-- 0.13.0. expirationd does not yet support the cartridge reload (it
94+
-- requires saving and restarting tasks at least). So, we are acting here
95+
-- as if all expirationd tasks have been killed: reset all collectors and
96+
-- the callback.
97+
if metrics_stats.callback then
98+
metrics.unregister_callback(metrics_stats.callback)
99+
end
100+
metrics_stats.callback = nil
101+
if metrics_stats.collectors then
102+
for _, c in pairs(metrics_stats.collectors) do
103+
metrics.registry:unregister(c.collector)
104+
end
105+
end
106+
metrics_stats.collectors = nil
107+
108+
local create_collector = function(name, description)
109+
return {
110+
collector = metrics.counter(name, description),
111+
task_value = {},
112+
}
113+
end
114+
115+
metrics_stats.collectors = {
116+
["checked_count"] = create_collector(
117+
"expirationd_checked_count",
118+
"expirationd task's a number of checked tuples"
119+
),
120+
["expired_count"] = create_collector(
121+
"expirationd_expired_count",
122+
"expirationd task's a number of expired tuples"
123+
),
124+
["restarts"] = create_collector(
125+
"expirationd_restarts",
126+
"expirationd task's a number of restarts"
127+
),
128+
["working_time"] = create_collector(
129+
"expirationd_working_time",
130+
"expirationd task's operation time"
131+
),
132+
}
133+
134+
local callback = function()
135+
for task_name, task in pairs(task_list) do
136+
local stats = task:statistics()
137+
for k, v in pairs(stats) do
138+
local prev_v = metrics_stats.collectors[k].task_value[task_name] or 0
139+
local v_inc = v - prev_v
140+
metrics_stats.collectors[k].collector:inc(v_inc, {name = task_name})
141+
metrics_stats.collectors[k].task_value[task_name] = v
142+
end
143+
end
144+
end
145+
metrics.register_callback(callback)
146+
metrics_stats.callback = callback
147+
end
148+
149+
local function metrics_disable()
150+
for _, c in pairs(metrics_stats.collectors) do
151+
metrics.registry:unregister(c.collector)
152+
end
153+
metrics_stats.collectors = nil
154+
metrics.unregister_callback(metrics_stats.callback)
155+
metrics_stats.callback = nil
156+
end
157+
158+
if cfg.metrics then
159+
local enabled, _ = pcall(metrics_enable)
160+
cfg.metrics = enabled
161+
end
162+
52163
-- ========================================================================= --
53164
-- Task local functions
54165
-- ========================================================================= --
@@ -281,6 +392,12 @@ local Task_methods = {
281392
-- @function task.kill
282393
kill = function (self)
283394
self:stop()
395+
if metrics_stats.collectors then
396+
for _, c in pairs(metrics_stats.collectors) do
397+
c.collector:remove({name = self.name})
398+
c.task_value[self.name] = nil
399+
end
400+
end
284401
task_list[self.name] = nil
285402
end,
286403

@@ -400,6 +517,65 @@ end
400517
--
401518
-- @section Functions
402519

520+
--- Configure expirationd.
521+
--
522+
-- Since version 1.2.0.
523+
--
524+
-- How to set up a configuration option:
525+
--
526+
-- ```
527+
-- expirationd.cfg({metrics = true})
528+
-- ```
529+
--
530+
-- How to get an option value:
531+
--
532+
-- ```
533+
-- print(expirationd.cfg.metrics)
534+
-- true
535+
-- ```
536+
--
537+
-- @table options
538+
--
539+
-- @bool[opt] options.metrics
540+
-- Enable or disable stats collection by [metrics][1]. metrics >= 0.11.0
541+
-- is required. It is enabled by default.
542+
--
543+
-- If enabled it creates four counter collectors, see @{task.statistics}:
544+
--
545+
-- 1. `expirationd_checked_count`
546+
--
547+
-- 2. `expirationd_expired_count`
548+
--
549+
-- 3. `expirationd_restarts`
550+
--
551+
-- 4. `expirationd_working_time`
552+
--
553+
-- Labeled with `name = task_name`.
554+
--
555+
-- [1]: https://github.com/tarantool/metrics/
556+
--
557+
-- @return None
558+
--
559+
-- @function expirationd.cfg
560+
local function expirationd_cfg(self, options)
561+
checks('table', {
562+
metrics = '?boolean',
563+
})
564+
565+
if options.metrics == nil then
566+
return
567+
end
568+
569+
if cfg.metrics ~= options.metrics then
570+
if options.metrics == true then
571+
metrics_enable()
572+
else
573+
metrics_disable()
574+
end
575+
rawset(cfg, 'metrics', options.metrics)
576+
end
577+
end
578+
403579
--- Run a scheduled task to check and process (expire) tuples in a given space.
404580
--
405581
-- How expirationd works in general:
@@ -949,6 +1125,12 @@ local function show_task_list_obsolete(...)
9491125
end
9501126

9511127
return {
1128+
cfg = setmetatable({}, {
1129+
__index = cfg,
1130+
__newindex = function() error("Use expirationd.cfg{} instead", 2) end,
1131+
__call = expirationd_cfg,
1132+
__serialize = function() return cfg end,
1133+
}),
9521134
start = expirationd_run_task,
9531135
stats = expirationd_task_stats,
9541136
update = expirationd_update,

test/entrypoint/srv_reload.lua

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env tarantool
2+
3+
require('strict').on()
4+
_G.is_initialized = function() return false end
5+
6+
local log = require('log')
7+
local errors = require('errors')
8+
local cartridge = require('cartridge')
9+
10+
package.preload['customers-storage'] = function()
11+
return {
12+
role_name = 'customers-storage',
13+
init = function()
14+
local customers_space = box.schema.space.create('customers', {
15+
format = {
16+
{name = 'id', type = 'unsigned'},
17+
},
18+
if_not_exists = true,
19+
engine = 'memtx',
20+
})
21+
22+
customers_space:create_index('id', {
23+
parts = { {field = 'id'} },
24+
unique = true,
25+
type = 'TREE',
26+
if_not_exists = true,
27+
})
28+
end,
29+
}
30+
end
31+
32+
local ok, err = errors.pcall('CartridgeCfgError', cartridge.cfg, {
33+
advertise_uri = 'localhost:3301',
34+
http_port = 8081,
35+
bucket_count = 3000,
36+
roles = {
37+
'customers-storage',
38+
'cartridge.roles.vshard-router',
39+
'cartridge.roles.vshard-storage',
40+
},
41+
roles_reload_allowed = true
42+
})
43+
44+
if not ok then
45+
log.error('%s', err)
46+
os.exit(1)
47+
end
48+
49+
_G.is_initialized = cartridge.is_healthy

test/helper.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ local fio = require("fio")
33

44
local helpers = require("luatest.helpers")
55

6+
helpers.project_root = fio.dirname(debug.sourcedir())
7+
68
local function create_space(space_name, engine)
79
local space_format = {
810
{
@@ -223,6 +225,16 @@ function helpers.is_expired_true()
223225
return true
224226
end
225227

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

0 commit comments

Comments
 (0)