Skip to content

Commit 470414a

Browse files
Add statistics for all CRUD operations on router
Add statistics module for collecting metrics of CRUD operations on router. Wrap all CRUD operation calls in statistics collector. Statistics may be disabled and re-enabled. Some internal methods of select/pairs were reworked or extended to provide statistics info. `cursor` returned from storage on select/pairs now contains stats of tuple count and lookup count. All changes are backward-compatible and should work even with older versions of crud routers and storages. Part of #224
1 parent 76e3374 commit 470414a

16 files changed

+1289
-38
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 all CRUD operations on router (#224).
1112

1213
### Changed
1314

README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,63 @@ crud.len('customers')
586586
...
587587
```
588588

589+
### Statistics
590+
591+
`crud` routers provide statistics on called operations.
592+
```lua
593+
-- Returns table with statistics information.
594+
crud.stats()
595+
```
596+
597+
Statistics collect enabled by default on all routers.
598+
It can be disabled and re-enabled later.
599+
```lua
600+
-- Disables statistics collect and resets all collectors.
601+
crud.disable_stats()
602+
603+
-- Enable statistics collect and recreates all collectors.
604+
crud.enable_stats()
605+
```
606+
607+
Enabling stats on non-router instances is meaningless.
608+
609+
`crud.stats()` contains several sections: `insert` (for `insert` and `insert_object` calls),
610+
`get`, `replace` (for `replace` and `replace_object` calls), `update`,
611+
`upsert` (for `upsert` and `upsert_object` calls), `delete`,
612+
`select` (for `select` and `pairs` calls), `truncate`, `len` and
613+
`borders` (for `min` and `max` calls).
614+
615+
```lua
616+
crud.stats()['insert']
617+
---
618+
- ok:
619+
latency: 0.002
620+
count: 19800
621+
error:
622+
latency: 0.000001
623+
count: 4
624+
---
625+
```
626+
Each section contains different collectors for success calls
627+
and error (both error throw and `nil, err`) returns. `count`
628+
is total requests count since instance start or stats restart.
629+
`latency` is execution time of last request.
630+
631+
Additionally, `select` section contains `details` collectors.
632+
```lua
633+
crud.stats()['select']['details']
634+
---
635+
- map_reduces_planned: 4
636+
tuples_fetched: 10500
637+
tuples_lookup: 2380000
638+
...
639+
```
640+
`map_reduces_planned` is a count of planned map reduces
641+
(including those not executed successfully). `tuples_fetched`
642+
is a count of tuples fetched from storages during execution,
643+
`tuples_lookup` is a count of tuples looked up on storages
644+
while collecting response for call.
645+
589646
## Cartridge roles
590647

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

crud.lua

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ local len = require('crud.len')
1414
local borders = require('crud.borders')
1515
local sharding_key = require('crud.common.sharding_key')
1616
local utils = require('crud.common.utils')
17+
local stats = require('crud.stats.module')
1718

1819
local crud = {}
1920

@@ -22,67 +23,67 @@ local crud = {}
2223

2324
-- @refer insert.tuple
2425
-- @function insert
25-
crud.insert = insert.tuple
26+
crud.insert = stats.wrap(insert.tuple, stats.label.INSERT)
2627

2728
-- @refer insert.object
2829
-- @function insert_object
29-
crud.insert_object = insert.object
30+
crud.insert_object = stats.wrap(insert.object, stats.label.INSERT)
3031

3132
-- @refer get.call
3233
-- @function get
33-
crud.get = get.call
34+
crud.get = stats.wrap(get.call, stats.label.GET)
3435

3536
-- @refer replace.tuple
3637
-- @function replace
37-
crud.replace = replace.tuple
38+
crud.replace = stats.wrap(replace.tuple, stats.label.REPLACE)
3839

3940
-- @refer replace.object
4041
-- @function replace_object
41-
crud.replace_object = replace.object
42+
crud.replace_object = stats.wrap(replace.object, stats.label.REPLACE)
4243

4344
-- @refer update.call
4445
-- @function update
45-
crud.update = update.call
46+
crud.update = stats.wrap(update.call, stats.label.UPDATE)
4647

4748
-- @refer upsert.tuple
4849
-- @function upsert
49-
crud.upsert = upsert.tuple
50+
crud.upsert = stats.wrap(upsert.tuple, stats.label.UPSERT)
5051

5152
-- @refer upsert.object
5253
-- @function upsert
53-
crud.upsert_object = upsert.object
54+
crud.upsert_object = stats.wrap(upsert.object, stats.label.UPSERT)
5455

5556
-- @refer delete.call
5657
-- @function delete
57-
crud.delete = delete.call
58+
crud.delete = stats.wrap(delete.call, stats.label.DELETE)
5859

5960
-- @refer select.call
6061
-- @function select
61-
crud.select = select.call
62+
crud.select = stats.wrap(select.call, stats.label.SELECT)
6263

6364
-- @refer select.pairs
6465
-- @function pairs
65-
crud.pairs = select.pairs
66+
crud.pairs = stats.wrap_pairs(select.pairs, stats.label.SELECT)
6667

6768
-- @refer utils.unflatten_rows
6869
-- @function unflatten_rows
6970
crud.unflatten_rows = utils.unflatten_rows
7071

7172
-- @refer truncate.call
7273
-- @function truncate
73-
crud.truncate = truncate.call
74+
crud.truncate = stats.wrap(truncate.call, stats.label.TRUNCATE)
7475

7576
-- @refer len.call
7677
-- @function len
77-
crud.len = len.call
78+
crud.len = stats.wrap(len.call, stats.label.LEN)
7879

7980
-- @refer borders.min
8081
-- @function min
81-
crud.min = borders.min
82+
crud.min = stats.wrap(borders.min, stats.label.BORDERS)
8283

8384
-- @refer borders.max
8485
-- @function max
85-
crud.max = borders.max
86+
crud.max = stats.wrap(borders.max, stats.label.BORDERS)
8687

8788
-- @refer utils.cut_rows
8889
-- @function cut_rows
@@ -92,6 +93,18 @@ crud.cut_rows = utils.cut_rows
9293
-- @function cut_objects
9394
crud.cut_objects = utils.cut_objects
9495

96+
-- @refer stats.get
97+
-- @function stats
98+
crud.stats = stats.get
99+
100+
-- @refer stats.enable
101+
-- @function enable_stats
102+
crud.enable_stats = stats.enable
103+
104+
-- @refer stats.disable
105+
-- @function disable_stats
106+
crud.disable_stats = stats.disable
107+
95108
--- Initializes crud on node
96109
--
97110
-- Exports all functions that are used for calls
@@ -118,11 +131,13 @@ function crud.init_storage()
118131
end
119132

120133
function crud.init_router()
121-
rawset(_G, 'crud', crud)
134+
rawset(_G, 'crud', crud)
135+
stats.enable()
122136
end
123137

124138
function crud.stop_router()
125139
rawset(_G, 'crud', nil)
140+
stats.disable()
126141
end
127142

128143
function crud.stop_storage()

crud/common/utils.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,4 +606,8 @@ function utils.merge_options(opts_a, opts_b)
606606
return fun.chain(opts_a or {}, opts_b or {}):tomap()
607607
end
608608

609+
function utils.pass()
610+
-- Do nothing.
611+
end
612+
609613
return utils

crud/select.lua

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ local function select_on_storage(space_name, index_id, conditions, opts)
5959
end
6060

6161
-- execute select
62-
local tuples, err = select_executor.execute(space, index, filter_func, {
62+
local res, err = select_executor.execute(space, index, filter_func, {
6363
scan_value = opts.scan_value,
6464
after_tuple = opts.after_tuple,
6565
tarantool_iter = opts.tarantool_iter,
@@ -70,15 +70,18 @@ local function select_on_storage(space_name, index_id, conditions, opts)
7070
end
7171

7272
local cursor
73-
if #tuples < opts.limit or opts.limit == 0 then
73+
if res.stats.tuples_fetched < opts.limit or opts.limit == 0 then
7474
cursor = {is_end = true}
7575
else
76-
cursor = make_cursor(tuples)
76+
cursor = make_cursor(res.tuples)
7777
end
7878

79+
-- Pass statistics data from storage with cursor.
80+
cursor.stats = res.stats
81+
7982
-- getting tuples with user defined fields (if `fields` option is specified)
8083
-- and fields that are needed for comparison on router (primary key + scan key)
81-
return cursor, schema.filter_tuples_fields(tuples, opts.field_names)
84+
return cursor, schema.filter_tuples_fields(res.tuples, opts.field_names)
8285
end
8386

8487
function select_module.init()

crud/select/compat/select.lua

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ local dev_checks = require('crud.common.dev_checks')
88
local common = require('crud.select.compat.common')
99
local schema = require('crud.common.schema')
1010
local sharding_key_module = require('crud.common.sharding_key')
11+
local stats = require('crud.stats.module')
1112

1213
local compare_conditions = require('crud.compare.conditions')
1314
local select_plan = require('crud.select.plan')
@@ -111,6 +112,11 @@ local function build_select_iterator(space_name, user_conditions, opts)
111112
if err ~= nil then
112113
return nil, err, true
113114
end
115+
else
116+
-- Use function call to collect map reduce count instead
117+
-- of passing values outside to wrapper to not break
118+
-- pairs api with excessice context values in return.
119+
stats.inc_map_reduce_count()
114120
end
115121

116122
local tuples_limit = opts.first
@@ -142,7 +148,12 @@ local function build_select_iterator(space_name, user_conditions, opts)
142148
local merger = Merger.new(replicasets_to_select, space, plan.index_id,
143149
common.SELECT_FUNC_NAME,
144150
{space_name, plan.index_id, plan.conditions, select_opts},
145-
{tarantool_iter = plan.tarantool_iter, field_names = plan.field_names, call_opts = opts.call_opts}
151+
{
152+
tarantool_iter = plan.tarantool_iter,
153+
field_names = plan.field_names,
154+
call_opts = opts.call_opts,
155+
stats_callback = stats.get_fetch_callback(),
156+
}
146157
)
147158

148159
-- filter space format by plan.field_names (user defined fields + primary key + scan key)

crud/select/compat/select_old.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ local sharding = require('crud.common.sharding')
99
local dev_checks = require('crud.common.dev_checks')
1010
local schema = require('crud.common.schema')
1111
local sharding_key_module = require('crud.common.sharding_key')
12+
local stats = require('crud.stats.module')
1213

1314
local compare_conditions = require('crud.compare.conditions')
1415
local select_plan = require('crud.select.plan')
@@ -30,6 +31,7 @@ local function select_iteration(space_name, plan, opts)
3031
})
3132

3233
local call_opts = opts.call_opts
34+
local stats_callback = stats.get_fetch_callback()
3335

3436
-- call select on storages
3537
local storage_select_opts = {
@@ -59,6 +61,14 @@ local function select_iteration(space_name, plan, opts)
5961

6062
local tuples = {}
6163
for replicaset_uuid, replicaset_results in pairs(results) do
64+
-- Stats extracted with callback here and not passed
65+
-- outside to wrapper because fetch for pairs can be
66+
-- called even after pairs() return from generators.
67+
local cursor = replicaset_results[1]
68+
if cursor.stats ~= nil then
69+
stats_callback(cursor.stats)
70+
end
71+
6272
tuples[replicaset_uuid] = replicaset_results[2]
6373
end
6474

@@ -137,6 +147,11 @@ local function build_select_iterator(space_name, user_conditions, opts)
137147
if err ~= nil then
138148
return nil, err, true
139149
end
150+
else
151+
-- Use function call to collect map reduce count instead
152+
-- of passing values outside to wrapper to not break
153+
-- pairs api with excessice context values in return.
154+
stats.inc_map_reduce_count()
140155
end
141156

142157
-- generate tuples comparator

crud/select/executor.lua

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,13 @@ function executor.execute(space, index, filter_func, opts)
6969
opts = opts or {}
7070

7171
if opts.limit == 0 then
72-
return {}
72+
return {
73+
tuples = {},
74+
stats = {
75+
tuples_fetched = 0,
76+
tuples_lookup = 0,
77+
},
78+
}
7379
end
7480

7581
local tuples = {}
@@ -94,14 +100,21 @@ function executor.execute(space, index, filter_func, opts)
94100
end
95101

96102
if tuple == nil then
97-
return {}
103+
return {
104+
tuples = {},
105+
stats = {
106+
tuples_fetched = 0,
107+
tuples_lookup = 0,
108+
},
109+
}
98110
end
99111
end
100112

101113
if tuple == nil then
102114
gen.state, tuple = gen(gen.param, gen.state)
103115
end
104116

117+
local lookup_count = 0
105118
while true do
106119
if tuple == nil then
107120
break
@@ -121,9 +134,16 @@ function executor.execute(space, index, filter_func, opts)
121134
end
122135

123136
gen.state, tuple = gen(gen.param, gen.state)
137+
lookup_count = lookup_count + 1
124138
end
125139

126-
return tuples
140+
return {
141+
tuples = tuples,
142+
stats = {
143+
tuples_fetched = #tuples,
144+
tuples_lookup = lookup_count,
145+
},
146+
}
127147
end
128148

129149
return executor

0 commit comments

Comments
 (0)