Skip to content

Commit

Permalink
Summary collector (#101)
Browse files Browse the repository at this point in the history
Add new Summary collector to compute summaries and percentiles
  • Loading branch information
yngvar-antonsson authored Sep 9, 2020
1 parent f567492 commit 3ad4c5c
Show file tree
Hide file tree
Showing 15 changed files with 638 additions and 29 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Summary collector

### Deprecated
- Average collector

## [0.4.0] - 2020-07-14
### Added
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ rpm:
else \
tarantoolctl rocks install cartridge $(CARTRIDGE_VERSION); \
fi
gcc -c -o metrics/quantile.o metrics/quantile.c
gcc -shared -o metrics/libquantile.so metrics/quantile.o
rm metrics/quantile.o

.PHONY: lint
lint: .rocks
Expand All @@ -32,3 +35,4 @@ test_with_coverage_report: .rocks
.PHONY: clean
clean:
rm -rf .rocks
rm metrics/libquantile.so
58 changes: 43 additions & 15 deletions doc/monitoring/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Histogram

* ``name .. "_sum"`` - A counter holding the sum of added observations.
Contains only an empty label set.
* ``name .. "_count"`` - A counter holding number of added observations.
* ``name .. "_count"`` - A counter holding the number of added observations.
Contains only an empty label set.
* ``name .. "_bucket"`` - A counter holding all bucket sizes under the label
``le`` (low or equal). So to access a specific bucket ``x`` (``x`` is a number),
Expand All @@ -157,26 +157,54 @@ Histogram
counters of ``histogram_obj``. For ``observation`` description,
see :ref:`counter_obj:collect() <counter-collect>`.
.. _average:
.. _summary:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Average
Summary
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Can be used only as a collector for HTTP statistics (described below)
and cannot be built explicitly.
.. module:: metrics

.. class:: histogram_obj
.. function:: summary(name [, help, objectives])

.. method: collect()
Registers a new summary. Quantile computation is based on the algorithm `"Effective computation of biased quantiles over data streams" <https://ieeexplore.ieee.org/document/1410103>`_

:param string name: Collector name. Must be unique.
:param string help: Help description.
:param table objectives: Quantiles to observe in the form ``{quantile = error, ... }``.
For example: ``{[0.5]=0.01, [0.9]=0.01, [0.99]=0.01}``

:return: Summary object

:rtype: summary_obj

.. NOTE::

The summary is just a set of collectors:

* ``name .. "_sum"`` - A counter holding the sum of added observations.
* ``name .. "_count"`` - A counter holding the number of added observations.
* ``name`` - It's holding all quantiles under observation under the label
``quantile`` (low or equal). So to access a specific quantile ``x`` (``x`` is a number),
you should specify the value ``x`` for the label ``quantile``.

:return: A list of two observations:
.. class:: summary_obj

* ``name .. "_avg"`` - An average value of observations for
the observing period (time from the previous collect call till now),
* ``name .. "_count"`` - The observation count for the same period.
For ``observation`` description, see
:ref:`counter_obj:collect() <counter-collect>`.
.. method: observe(num, label_pairs)
Records a new value in a summary.
:param number num: Value to put in the data stream.
:param table label_pairs: Table containing label names as keys,
label values as values (table).
A new value is observed by all internal counters
with these labels specified.
.. method: collect()
Returns a concatenation of ``counter_obj:collect()`` across all internal
counters of ``summary_obj``. For ``observation`` description,
see :ref:`counter_obj:collect() <counter-collect>`.
.. _labels:

Expand Down Expand Up @@ -287,7 +315,7 @@ latency statistics.

Registers a collector for the middleware and sets it as default.

:param string type_name: Collector type: "histogram" or "average". Default is "histogram".
:param string type_name: Collector type: "histogram" or "summary". Default is "histogram".
:param string name: Collector name. Default is "http_server_request_latency".
:param string help: Help description. Default is "HTTP Server Request Latency".

Expand All @@ -298,7 +326,7 @@ latency statistics.

Registers a collector for the middleware and returns it.

:param string type_name: Collector type: "histogram" or "average". Default is "histogram".
:param string type_name: Collector type: "histogram" or "summary". Default is "histogram".
:param string name: Collector name. Default is "http_server_request_latency".
:param string help: Help description. Default is "HTTP Server Request Latency".

Expand Down
40 changes: 34 additions & 6 deletions example/HTTP/latency_v1.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package.path = package.path .. ";../?.lua"
local json = require('json')
local fiber = require('fiber')
local metrics = require('metrics')
local log = require('log')
local http_middleware = metrics.http_middleware

-- Configure HTTP routing
Expand All @@ -21,13 +22,13 @@ local handler = function(req)
return { status = 200, body = req.body }
end

-- Configure average latency collector
-- Configure summary latency collector
local collector = http_middleware.build_default_collector(
'average', 'path_latency',
'summary', 'path_latency',
'My collector for /path requests latency'
)

-- Set route handler with average latency collection
-- Set route handler with summary latency collection
httpd:route(route, http_middleware.v1(handler, collector))
-- Start HTTP routing
httpd:start()
Expand All @@ -37,7 +38,7 @@ local http_client = require("http.client") -- HTTP ver. 1.x.x
http_client.post('http://' .. ip .. ':' .. port .. route.path, json.encode({ body = 'text' }))

-- Collect the metrics
metrics.collect()
log.info(metrics.collect())
--[[
- label_pairs:
Expand All @@ -53,8 +54,35 @@ metrics.collect()
method: POST
status: 200
timestamp: 1588951616500768
value: 1.0038734949776
metric_name: path_latency_avg
value: 1.0240110000595
metric_name: path_latency_sum
- label_pairs:
path: /path
method: POST
status: 200
quantile: 0.5
timestamp: 1588951616500768
value: 1.0240110000595
metric_name: path_latency
- label_pairs:
path: /path
method: POST
status: 200
quantile: 0.9
timestamp: 1588951616500768
value: 1.0240110000595
metric_name: path_latency
- label_pairs:
path: /path
method: POST
status: 200
quantile: 0.99
timestamp: 1588951616500768
value: 1.0240110000595
metric_name: path_latency
--]]

Expand Down
41 changes: 35 additions & 6 deletions example/HTTP/latency_v2.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package.path = package.path .. ";../?.lua"
local json = require('json')
local fiber = require('fiber')
local metrics = require('metrics')
local log = require('log')
local http_middleware = metrics.http_middleware

-- Configure HTTP routing
Expand All @@ -23,13 +24,13 @@ local handler = function(req)
end
router:route(route, handler)

-- Configure average latency collector
-- Configure summary latency collector
local collector = http_middleware.build_default_collector(
'average', 'path_latency',
'summary', 'path_latency',
'My collector for /path requests latency'
)

-- Set router average latency collection middleware
-- Set router summary latency collection middleware
router:use(http_middleware.v2(collector), { name = 'latency_instrumentation' })

-- Start HTTP routing using configured router
Expand All @@ -41,7 +42,7 @@ local http_client = require("http.client").new() -- HTTP ver. 2.x.x
http_client:request(route.method, 'http://' .. ip .. ':' .. port .. route.path, json.encode({ body = 'text' }))

-- Collect the metrics
metrics.collect()
log.info(metrics.collect())
--[[
- label_pairs:
Expand All @@ -57,8 +58,36 @@ metrics.collect()
method: POST
status: 200
timestamp: 1588951616500768
value: 1.0038734949776
metric_name: path_latency_avg
value: 1.0240110000595
metric_name: path_latency_sum
- label_pairs:
path: /path
method: POST
status: 200
quantile: 0.5
timestamp: 1588951616500768
value: 1.0240110000595
metric_name: path_latency
- label_pairs:
path: /path
method: POST
status: 200
quantile: 0.9
timestamp: 1588951616500768
value: 1.0240110000595
metric_name: path_latency
- label_pairs:
path: /path
method: POST
status: 200
quantile: 0.99
timestamp: 1588951616500768
value: 1.0240110000595
metric_name: path_latency
--]]

Expand Down
3 changes: 3 additions & 0 deletions metrics-scm-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ build = {
modules = {
['metrics'] = 'metrics/init.lua',
['metrics.registry'] = 'metrics/registry.lua',
['metrics.quantile'] = 'metrics/quantile.lua',
['metrics.http_middleware'] = 'metrics/http_middleware.lua',
['metrics.collectors.shared'] = 'metrics/collectors/shared.lua',
['metrics.collectors.average'] = 'metrics/collectors/average.lua',
['metrics.collectors.summary'] = 'metrics/collectors/summary.lua',
['metrics.collectors.counter'] = 'metrics/collectors/counter.lua',
['metrics.collectors.gauge'] = 'metrics/collectors/gauge.lua',
['metrics.collectors.histogram'] = 'metrics/collectors/histogram.lua',
Expand All @@ -48,6 +50,7 @@ build = {
['metrics.psutils.psutils_linux'] = 'metrics/psutils/psutils_linux.lua',
['metrics.utils'] = 'metrics/utils.lua',
['cartridge.roles.metrics'] = 'cartridge/roles/metrics.lua',
['libquantile'] = 'metrics/quantile.c',
}
}

Expand Down
85 changes: 85 additions & 0 deletions metrics/collectors/summary.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
local Shared = require('metrics.collectors.shared')
local Counter = require('metrics.collectors.counter')
local Quantile = require('metrics.quantile')

local fiber = require('fiber')

local Summary = Shared:new_class('summary', {'observe_latency'})

function Summary:new(name, help, objectives)
local obj = Shared.new(self, name, help)

obj.count_collector = Counter:new(name .. '_count', help)
obj.sum_collector = Counter:new(name .. '_sum', help)
obj.objectives = objectives

return obj
end

function Summary.check_quantiles(objectives)
for k, v in pairs(objectives) do
if type(k) ~= 'number' then return false end
if k >= 1 or k < 0 then return false end
if type(v) ~= 'number' then return false end
end
return true
end

function Summary:set_registry(registry)
Shared.set_registry(self, registry)
self.count_collector:set_registry(registry)
self.sum_collector:set_registry(registry)
end

function Summary:observe(num, label_pairs)
label_pairs = label_pairs or {}

self.count_collector:inc(1, label_pairs)
self.sum_collector:inc(num, label_pairs)
if self.objectives then
local key = self.make_key(label_pairs)
if not self.observations[key] then
self.observations[key] = Quantile.NewTargeted(self.objectives)
self.label_pairs[key] = label_pairs
end
Quantile.Insert(self.observations[key], num)
end
end


function Summary:collect_quantiles()
if next(self.observations) == nil then
return {}
end
local result = {}
for key, observation in pairs(self.observations) do
for objective, _ in pairs(self.objectives) do
local label_pairs = table.deepcopy(self:append_global_labels(self.label_pairs[key]))
label_pairs.quantile = objective
local obs = {
metric_name = self.name,
label_pairs = label_pairs,
value = Quantile.Query(observation, objective),
timestamp = fiber.time64(),
}
table.insert(result, obs)
end
end
return result
end

function Summary:collect()
local result = {}
for _, obs in pairs(self.count_collector:collect()) do
table.insert(result, obs)
end
for _, obs in pairs(self.sum_collector:collect()) do
table.insert(result, obs)
end
for _, obs in pairs(self:collect_quantiles()) do
table.insert(result, obs)
end
return result
end

return Summary
Loading

0 comments on commit 3ad4c5c

Please sign in to comment.