Skip to content

Commit 7ee2d7d

Browse files
stats: add statistics for CRUD router operations
Add statistics module for collecting metrics of CRUD operations on router. Wrap all CRUD operation calls in the statistics collector. Statistics must be enabled manually with `crud.cfg`. They can be disabled, restarted or re-enabled later. This patch introduces `crud.cfg`. `crud.cfg` is a tool to set module configuration. It is similar to Tarantool `box.cfg`, although we don't need to call it to bootstrap the module -- it is used only to change configuration. `crud.cfg` is a callable table. To change configuration, call it: `crud.cfg{ stats = true }`. You can check table contents as with ordinary table, but do not change them directly -- use call instead. Table contents is immutable and use proxy approach (see [1, 2]). Iterating through `crud.cfg` with pairs is not supported yet, refer to #265. `crud.stats()` returns --- - spaces: my_space: insert: ok: latency: 0.002 count: 19800 time: 39.6 error: latency: 0.000001 count: 4 time: 0.000004 ... `spaces` section contains statistics for each observed space. If operation has never been called for a space, the corresponding field will be empty. If no requests has been called for a space, it will not be represented. Space data is based on client requests rather than storages schema, so requests for non-existing spaces are also collected. Possible statistics operation labels are `insert` (for `insert` and `insert_object` calls), `get`, `replace` (for `replace` and `replace_object` calls), `update`, `upsert` (for `upsert` and `upsert_object` calls), `delete`, `select` (for `select` and `pairs` calls), `truncate`, `len`, `count` and `borders` (for `min` and `max` calls). Each operation section consists of different collectors for success calls and error (both error throw and `nil, err`) returns. `count` is the total requests count since instance start or stats restart. `latency` is the average time of requests execution, `time` is the total time of requests execution. Since `pairs` request behavior differs from any other crud request, its statistics collection also has specific behavior. Statistics (`select` section) are updated after `pairs` cycle is finished: you either have iterated through all records or an error was thrown. If your pairs cycle was interrupted with `break`, statistics will be collected when pairs objects are cleaned up with Lua garbage collector. Statistics are preserved between package reloads. Statistics are preserved between Tarantool Cartridge role reloads [3] if CRUD Cartridge roles are used. 1. http://lua-users.org/wiki/ReadOnlyTables 2. tarantool/tarantool#2867 3. https://www.tarantool.io/en/doc/latest/book/cartridge/cartridge_api/modules/cartridge.roles/#reload Part of #224
1 parent 2c234cf commit 7ee2d7d

18 files changed

+2023
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
88
## [Unreleased]
99

1010
### Added
11+
* Statistics for CRUD operations on router (#224).
1112

1213
### Changed
1314

README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,97 @@ Combinations of `mode`, `prefer_replica` and `balance` options lead to:
674674
* prefer_replica, balance -
675675
[vshard call `callbre`](https://www.tarantool.io/en/doc/latest/reference/reference_rock/vshard/vshard_api/#router-api-callbre)
676676

677+
### Statistics
678+
679+
`crud` routers can provide statistics on called operations.
680+
```lua
681+
-- Enable statistics collect.
682+
crud.cfg{ stats = true }
683+
684+
-- Returns table with statistics information.
685+
crud.stats()
686+
687+
-- Returns table with statistics information for specific space.
688+
crud.stats('my_space')
689+
690+
-- Disables statistics collect and destroys all collectors.
691+
crud.cfg{ stats = false }
692+
693+
-- Destroys all statistics collectors and creates them again.
694+
crud.reset_stats()
695+
```
696+
697+
You can use `crud.cfg` to check current stats state.
698+
```lua
699+
crud.cfg
700+
---
701+
- stats: true
702+
...
703+
```
704+
Beware that iterating through `crud.cfg` with pairs is not supported yet,
705+
refer to [tarantool/crud#265](https://github.com/tarantool/crud/issues/265).
706+
707+
Format is as follows.
708+
```lua
709+
crud.stats()
710+
---
711+
- spaces:
712+
my_space:
713+
insert:
714+
ok:
715+
latency: 0.002
716+
count: 19800
717+
time: 39.6
718+
error:
719+
latency: 0.000001
720+
count: 4
721+
time: 0.000004
722+
...
723+
crud.stats('my_space')
724+
---
725+
- insert:
726+
ok:
727+
latency: 0.002
728+
count: 19800
729+
time: 39.6
730+
error:
731+
latency: 0.000001
732+
count: 4
733+
time: 0.000004
734+
...
735+
```
736+
`spaces` section contains statistics for each observed space.
737+
If operation has never been called for a space, the corresponding
738+
field will be empty. If no requests has been called for a
739+
space, it will not be represented. Space data is based on
740+
client requests rather than storages schema, so requests
741+
for non-existing spaces are also collected.
742+
743+
Possible statistics operation labels are
744+
`insert` (for `insert` and `insert_object` calls),
745+
`get`, `replace` (for `replace` and `replace_object` calls), `update`,
746+
`upsert` (for `upsert` and `upsert_object` calls), `delete`,
747+
`select` (for `select` and `pairs` calls), `truncate`, `len`, `count`
748+
and `borders` (for `min` and `max` calls).
749+
750+
Each operation section contains of different collectors
751+
for success calls and error (both error throw and `nil, err`)
752+
returns. `count` is total requests count since instance start
753+
or stats restart. `latency` is average time of requests execution,
754+
`time` is the total time of requests execution.
755+
756+
Since `pairs` request behavior differs from any other crud request, its
757+
statistics collection also has specific behavior. Statistics (`select`
758+
section) are updated after `pairs` cycle is finished: you
759+
either have iterated through all records or an error was thrown.
760+
If your pairs cycle was interrupted with `break`, statistics will
761+
be collected when pairs objects are cleaned up with Lua garbage
762+
collector.
763+
764+
Statistics are preserved between package reloads. Statistics are preserved
765+
between [Tarantool Cartridge role reloads](https://www.tarantool.io/en/doc/latest/book/cartridge/cartridge_api/modules/cartridge.roles/#reload)
766+
if you use CRUD Cartridge roles.
767+
677768
## Cartridge roles
678769

679770
`cartridge.roles.crud-storage` is a Tarantool Cartridge role that depends on the

cartridge/roles/crud-router.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
local crud = require('crud')
2+
local stash = require('crud.common.stash')
23

34
-- removes routes that changed in config and adds new routes
45
local function init()
56
crud.init_router()
7+
stash.setup_cartridge_reload()
68
end
79

810
local function stop()

cartridge/roles/crud-storage.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
local crud = require('crud')
2+
local stash = require('crud.common.stash')
23

34
local function init()
45
crud.init_storage()
6+
stash.setup_cartridge_reload()
57
end
68

79
local function stop()

crud.lua

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
--
33
-- @module crud
44

5+
local cfg = require('crud.cfg')
56
local insert = require('crud.insert')
67
local replace = require('crud.replace')
78
local get = require('crud.get')
@@ -15,6 +16,7 @@ local count = require('crud.count')
1516
local borders = require('crud.borders')
1617
local sharding_metadata = require('crud.common.sharding.sharding_metadata')
1718
local utils = require('crud.common.utils')
19+
local stats = require('crud.stats')
1820

1921
local crud = {}
2022

@@ -23,71 +25,71 @@ local crud = {}
2325

2426
-- @refer insert.tuple
2527
-- @function insert
26-
crud.insert = insert.tuple
28+
crud.insert = stats.wrap(insert.tuple, stats.op.INSERT)
2729

2830
-- @refer insert.object
2931
-- @function insert_object
30-
crud.insert_object = insert.object
32+
crud.insert_object = stats.wrap(insert.object, stats.op.INSERT)
3133

3234
-- @refer get.call
3335
-- @function get
34-
crud.get = get.call
36+
crud.get = stats.wrap(get.call, stats.op.GET)
3537

3638
-- @refer replace.tuple
3739
-- @function replace
38-
crud.replace = replace.tuple
40+
crud.replace = stats.wrap(replace.tuple, stats.op.REPLACE)
3941

4042
-- @refer replace.object
4143
-- @function replace_object
42-
crud.replace_object = replace.object
44+
crud.replace_object = stats.wrap(replace.object, stats.op.REPLACE)
4345

4446
-- @refer update.call
4547
-- @function update
46-
crud.update = update.call
48+
crud.update = stats.wrap(update.call, stats.op.UPDATE)
4749

4850
-- @refer upsert.tuple
4951
-- @function upsert
50-
crud.upsert = upsert.tuple
52+
crud.upsert = stats.wrap(upsert.tuple, stats.op.UPSERT)
5153

5254
-- @refer upsert.object
5355
-- @function upsert
54-
crud.upsert_object = upsert.object
56+
crud.upsert_object = stats.wrap(upsert.object, stats.op.UPSERT)
5557

5658
-- @refer delete.call
5759
-- @function delete
58-
crud.delete = delete.call
60+
crud.delete = stats.wrap(delete.call, stats.op.DELETE)
5961

6062
-- @refer select.call
6163
-- @function select
62-
crud.select = select.call
64+
crud.select = stats.wrap(select.call, stats.op.SELECT)
6365

6466
-- @refer select.pairs
6567
-- @function pairs
66-
crud.pairs = select.pairs
68+
crud.pairs = stats.wrap(select.pairs, stats.op.SELECT, { pairs = true })
6769

6870
-- @refer utils.unflatten_rows
6971
-- @function unflatten_rows
7072
crud.unflatten_rows = utils.unflatten_rows
7173

7274
-- @refer truncate.call
7375
-- @function truncate
74-
crud.truncate = truncate.call
76+
crud.truncate = stats.wrap(truncate.call, stats.op.TRUNCATE)
7577

7678
-- @refer len.call
7779
-- @function len
78-
crud.len = len.call
80+
crud.len = stats.wrap(len.call, stats.op.LEN)
7981

8082
-- @refer count.call
8183
-- @function count
82-
crud.count = count.call
84+
crud.count = stats.wrap(count.call, stats.op.COUNT)
8385

8486
-- @refer borders.min
8587
-- @function min
86-
crud.min = borders.min
88+
crud.min = stats.wrap(borders.min, stats.op.BORDERS)
8789

8890
-- @refer borders.max
8991
-- @function max
90-
crud.max = borders.max
92+
crud.max = stats.wrap(borders.max, stats.op.BORDERS)
9193

9294
-- @refer utils.cut_rows
9395
-- @function cut_rows
@@ -97,6 +99,18 @@ crud.cut_rows = utils.cut_rows
9799
-- @function cut_objects
98100
crud.cut_objects = utils.cut_objects
99101

102+
-- @refer cfg.cfg
103+
-- @function cfg
104+
crud.cfg = cfg.cfg
105+
106+
-- @refer stats.get
107+
-- @function stats
108+
crud.stats = stats.get
109+
110+
-- @refer stats.reset
111+
-- @function reset_stats
112+
crud.reset_stats = stats.reset
113+
100114
--- Initializes crud on node
101115
--
102116
-- Exports all functions that are used for calls

crud/cfg.lua

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---- Module for CRUD configuration.
2+
-- @module crud.cfg
3+
--
4+
5+
local checks = require('checks')
6+
local errors = require('errors')
7+
8+
local stash = require('crud.common.stash')
9+
local stats = require('crud.stats')
10+
11+
local CfgError = errors.new_class('CfgError', {capture_stack = false})
12+
13+
local cfg_module = {}
14+
15+
local function set_defaults_if_empty(cfg)
16+
if cfg.stats == nil then
17+
cfg.stats = false
18+
end
19+
20+
return cfg
21+
end
22+
23+
local cfg = set_defaults_if_empty(stash.get(stash.name.cfg))
24+
25+
--- Configure CRUD module.
26+
--
27+
-- @function __call
28+
--
29+
-- @tab self
30+
--
31+
-- @tab[opt] opts
32+
--
33+
-- @bool[opt] opts.stats
34+
-- Enable or disable statistics collect.
35+
-- Statistics are observed only on router instances.
36+
--
37+
-- @return Configuration table.
38+
--
39+
local function __call(self, opts)
40+
checks('table', { stats = '?boolean' })
41+
42+
opts = opts or {}
43+
44+
if opts.stats ~= nil then
45+
if opts.stats == true then
46+
stats.enable()
47+
else
48+
stats.disable()
49+
end
50+
51+
rawset(cfg, 'stats', opts.stats)
52+
end
53+
54+
return self
55+
end
56+
57+
local function __newindex()
58+
CfgError:assert(false, 'Use crud.cfg{} instead')
59+
end
60+
61+
-- Iterating through `crud.cfg` with pairs is not supported
62+
-- yet, refer to tarantool/crud#265.
63+
cfg_module.cfg = setmetatable({}, {
64+
__index = cfg,
65+
__newindex = __newindex,
66+
__call = __call,
67+
__serialize = function() return cfg end
68+
})
69+
70+
return cfg_module

crud/common/stash.lua

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---- Module for preserving data between reloads.
2+
-- @module crud.common.stash
3+
--
4+
local dev_checks = require('crud.common.dev_checks')
5+
6+
local stash = {}
7+
8+
--- Available stashes list.
9+
--
10+
-- @tfield string cfg
11+
-- Stash for CRUD module configuration.
12+
--
13+
-- @tfield string stats_internal
14+
-- Stash for main stats module.
15+
--
16+
-- @tfield string stats_local_registry
17+
-- Stash for local metrics registry.
18+
--
19+
stash.name = {
20+
cfg = '__crud_cfg',
21+
stats_internal = '__crud_stats_internal',
22+
stats_local_registry = '__crud_stats_local_registry'
23+
}
24+
25+
--- Setup Tarantool Cartridge reload.
26+
--
27+
-- Call on Tarantool Cartridge roles that are expected
28+
-- to use stashes.
29+
--
30+
-- @function setup_cartridge_reload
31+
--
32+
-- @return Returns
33+
--
34+
function stash.setup_cartridge_reload()
35+
local hotreload = require('cartridge.hotreload')
36+
for _, name in pairs(stash.name) do
37+
hotreload.whitelist_globals({ name })
38+
end
39+
end
40+
41+
--- Get a stash instance, initialize if needed.
42+
--
43+
-- Stashes are persistent to package reload.
44+
-- To use them with Cartridge roles reload,
45+
-- call `stash.setup_cartridge_reload` in role.
46+
--
47+
-- @function get
48+
--
49+
-- @string name
50+
-- Stash identifier. Use one from `stash.name` table.
51+
--
52+
-- @treturn table A stash instance.
53+
--
54+
function stash.get(name)
55+
dev_checks('string')
56+
57+
local instance = rawget(_G, name) or {}
58+
rawset(_G, name, instance)
59+
60+
return instance
61+
end
62+
63+
return stash

0 commit comments

Comments
 (0)