Skip to content

Commit

Permalink
Add statistics for all CRUD operations on router
Browse files Browse the repository at this point in the history
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
  • Loading branch information
DifferentialOrange committed Dec 3, 2021
1 parent 1b75f4d commit 13c972b
Show file tree
Hide file tree
Showing 16 changed files with 1,322 additions and 38 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]

### Added
* Statistics for all CRUD operations on router (#224).

### Changed

Expand Down
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,67 @@ crud.len('customers')
...
```

### Statistics

`crud` routers provide statistics on called operations.
```lua
-- Returns table with statistics information.
crud.stats()
```

Statistics collect enabled by default on all routers.
It can be disabled and re-enabled later.
```lua
-- Disables statistics collect and resets all collectors.
crud.disable_stats()

-- Enable statistics collect and recreates all collectors.
crud.enable_stats()
```

Enabling stats on non-router instances is meaningless.

`crud.stats()` contains several sections: `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` and
`borders` (for `min` and `max` calls).

```lua
crud.stats()['insert']
---
- ok:
latency: 0.002
count: 19800
time: 35.98
error:
latency: 0.000001
count: 4
time: 0.000004
---
```
Each section contains different collectors for success calls
and error (both error throw and `nil, err`) returns. `count`
is total requests count since instance start or stats restart.
`latency` is execution time of last request.
`time` is total time of requests execution. You can use them
to compute average time of execution.

Additionally, `select` section contains `details` collectors.
```lua
crud.stats()['select']['details']
---
- map_reduces: 4
tuples_fetched: 10500
tuples_lookup: 2380000
...
```
`map_reduces` is a count of planned map reduces
(including those not executed successfully). `tuples_fetched`
is a count of tuples fetched from storages during execution,
`tuples_lookup` is a count of tuples looked up on storages
while collecting response for call.

## Cartridge roles

`cartridge.roles.crud-storage` is a Tarantool Cartridge role that depends on the
Expand Down
47 changes: 31 additions & 16 deletions crud.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ local len = require('crud.len')
local borders = require('crud.borders')
local sharding_key = require('crud.common.sharding_key')
local utils = require('crud.common.utils')
local stats = require('crud.stats.module')

local crud = {}

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

-- @refer insert.tuple
-- @function insert
crud.insert = insert.tuple
crud.insert = stats.wrap(insert.tuple, stats.label.INSERT)

-- @refer insert.object
-- @function insert_object
crud.insert_object = insert.object
crud.insert_object = stats.wrap(insert.object, stats.label.INSERT)

-- @refer get.call
-- @function get
crud.get = get.call
crud.get = stats.wrap(get.call, stats.label.GET)

-- @refer replace.tuple
-- @function replace
crud.replace = replace.tuple
crud.replace = stats.wrap(replace.tuple, stats.label.REPLACE)

-- @refer replace.object
-- @function replace_object
crud.replace_object = replace.object
crud.replace_object = stats.wrap(replace.object, stats.label.REPLACE)

-- @refer update.call
-- @function update
crud.update = update.call
crud.update = stats.wrap(update.call, stats.label.UPDATE)

-- @refer upsert.tuple
-- @function upsert
crud.upsert = upsert.tuple
crud.upsert = stats.wrap(upsert.tuple, stats.label.UPSERT)

-- @refer upsert.object
-- @function upsert
crud.upsert_object = upsert.object
crud.upsert_object = stats.wrap(upsert.object, stats.label.UPSERT)

-- @refer delete.call
-- @function delete
crud.delete = delete.call
crud.delete = stats.wrap(delete.call, stats.label.DELETE)

-- @refer select.call
-- @function select
crud.select = select.call
crud.select = stats.wrap(select.call, stats.label.SELECT)

-- @refer select.pairs
-- @function pairs
crud.pairs = select.pairs
crud.pairs = stats.wrap_pairs(select.pairs, stats.label.SELECT)

-- @refer utils.unflatten_rows
-- @function unflatten_rows
crud.unflatten_rows = utils.unflatten_rows

-- @refer truncate.call
-- @function truncate
crud.truncate = truncate.call
crud.truncate = stats.wrap(truncate.call, stats.label.TRUNCATE)

-- @refer len.call
-- @function len
crud.len = len.call
crud.len = stats.wrap(len.call, stats.label.LEN)

-- @refer borders.min
-- @function min
crud.min = borders.min
crud.min = stats.wrap(borders.min, stats.label.BORDERS)

-- @refer borders.max
-- @function max
crud.max = borders.max
crud.max = stats.wrap(borders.max, stats.label.BORDERS)

-- @refer utils.cut_rows
-- @function cut_rows
Expand All @@ -92,6 +93,18 @@ crud.cut_rows = utils.cut_rows
-- @function cut_objects
crud.cut_objects = utils.cut_objects

-- @refer stats.get
-- @function stats
crud.stats = stats.get

-- @refer stats.enable
-- @function enable_stats
crud.enable_stats = stats.enable

-- @refer stats.disable
-- @function disable_stats
crud.disable_stats = stats.disable

--- Initializes crud on node
--
-- Exports all functions that are used for calls
Expand All @@ -118,11 +131,13 @@ function crud.init_storage()
end

function crud.init_router()
rawset(_G, 'crud', crud)
rawset(_G, 'crud', crud)
stats.enable()
end

function crud.stop_router()
rawset(_G, 'crud', nil)
stats.disable()
end

function crud.stop_storage()
Expand Down
4 changes: 4 additions & 0 deletions crud/common/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -606,4 +606,8 @@ function utils.merge_options(opts_a, opts_b)
return fun.chain(opts_a or {}, opts_b or {}):tomap()
end

function utils.pass()
-- Do nothing.
end

return utils
11 changes: 7 additions & 4 deletions crud/select.lua
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ local function select_on_storage(space_name, index_id, conditions, opts)
end

-- execute select
local tuples, err = select_executor.execute(space, index, filter_func, {
local res, err = select_executor.execute(space, index, filter_func, {
scan_value = opts.scan_value,
after_tuple = opts.after_tuple,
tarantool_iter = opts.tarantool_iter,
Expand All @@ -70,15 +70,18 @@ local function select_on_storage(space_name, index_id, conditions, opts)
end

local cursor
if #tuples < opts.limit or opts.limit == 0 then
if res.stats.tuples_fetched < opts.limit or opts.limit == 0 then
cursor = {is_end = true}
else
cursor = make_cursor(tuples)
cursor = make_cursor(res.tuples)
end

-- Pass statistics data from storage with cursor.
cursor.stats = res.stats

-- getting tuples with user defined fields (if `fields` option is specified)
-- and fields that are needed for comparison on router (primary key + scan key)
return cursor, schema.filter_tuples_fields(tuples, opts.field_names)
return cursor, schema.filter_tuples_fields(res.tuples, opts.field_names)
end

function select_module.init()
Expand Down
13 changes: 12 additions & 1 deletion crud/select/compat/select.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ local dev_checks = require('crud.common.dev_checks')
local common = require('crud.select.compat.common')
local schema = require('crud.common.schema')
local sharding_key_module = require('crud.common.sharding_key')
local stats = require('crud.stats.module')

local compare_conditions = require('crud.compare.conditions')
local select_plan = require('crud.select.plan')
Expand Down Expand Up @@ -111,6 +112,11 @@ local function build_select_iterator(space_name, user_conditions, opts)
if err ~= nil then
return nil, err, true
end
else
-- Use function call to collect map reduce count instead
-- of passing values outside to wrapper to not break
-- pairs api with excessice context values in return.
stats.inc_map_reduce_count()
end

local tuples_limit = opts.first
Expand Down Expand Up @@ -142,7 +148,12 @@ local function build_select_iterator(space_name, user_conditions, opts)
local merger = Merger.new(replicasets_to_select, space, plan.index_id,
common.SELECT_FUNC_NAME,
{space_name, plan.index_id, plan.conditions, select_opts},
{tarantool_iter = plan.tarantool_iter, field_names = plan.field_names, call_opts = opts.call_opts}
{
tarantool_iter = plan.tarantool_iter,
field_names = plan.field_names,
call_opts = opts.call_opts,
stats_callback = stats.get_fetch_callback(),
}
)

-- filter space format by plan.field_names (user defined fields + primary key + scan key)
Expand Down
15 changes: 15 additions & 0 deletions crud/select/compat/select_old.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ local sharding = require('crud.common.sharding')
local dev_checks = require('crud.common.dev_checks')
local schema = require('crud.common.schema')
local sharding_key_module = require('crud.common.sharding_key')
local stats = require('crud.stats.module')

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

local call_opts = opts.call_opts
local stats_callback = stats.get_fetch_callback()

-- call select on storages
local storage_select_opts = {
Expand Down Expand Up @@ -59,6 +61,14 @@ local function select_iteration(space_name, plan, opts)

local tuples = {}
for replicaset_uuid, replicaset_results in pairs(results) do
-- Stats extracted with callback here and not passed
-- outside to wrapper because fetch for pairs can be
-- called even after pairs() return from generators.
local cursor = replicaset_results[1]
if cursor.stats ~= nil then
stats_callback(cursor.stats)
end

tuples[replicaset_uuid] = replicaset_results[2]
end

Expand Down Expand Up @@ -137,6 +147,11 @@ local function build_select_iterator(space_name, user_conditions, opts)
if err ~= nil then
return nil, err, true
end
else
-- Use function call to collect map reduce count instead
-- of passing values outside to wrapper to not break
-- pairs api with excessice context values in return.
stats.inc_map_reduce_count()
end

-- generate tuples comparator
Expand Down
26 changes: 23 additions & 3 deletions crud/select/executor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ function executor.execute(space, index, filter_func, opts)
opts = opts or {}

if opts.limit == 0 then
return {}
return {
tuples = {},
stats = {
tuples_fetched = 0,
tuples_lookup = 0,
},
}
end

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

if tuple == nil then
return {}
return {
tuples = {},
stats = {
tuples_fetched = 0,
tuples_lookup = 0,
},
}
end
end

if tuple == nil then
gen.state, tuple = gen(gen.param, gen.state)
end

local lookup_count = 0
while true do
if tuple == nil then
break
Expand All @@ -121,9 +134,16 @@ function executor.execute(space, index, filter_func, opts)
end

gen.state, tuple = gen(gen.param, gen.state)
lookup_count = lookup_count + 1
end

return tuples
return {
tuples = tuples,
stats = {
tuples_fetched = #tuples,
tuples_lookup = lookup_count,
},
}
end

return executor
Loading

0 comments on commit 13c972b

Please sign in to comment.