mirror of
https://github.com/Refactorio/RedMew.git
synced 2024-12-12 10:04:40 +02:00
Testing prometheus/grafana
We've tested it works on S10 but want it on a live map for a little while so we can gather some data for testing configuring Grafana. For now it just saves the number of players, will add more later.
This commit is contained in:
parent
164baaa523
commit
f1ec322481
40
features/prometheus/.gitignore
vendored
Normal file
40
features/prometheus/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# Compiled Lua sources
|
||||
luac.out
|
||||
|
||||
# luarocks build files
|
||||
*.src.rock
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.os
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
*.def
|
||||
*.exp
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
4
features/prometheus/Dockerfile
Normal file
4
features/prometheus/Dockerfile
Normal file
@ -0,0 +1,4 @@
|
||||
FROM tarantool/tarantool:1.7
|
||||
|
||||
COPY example.lua /opt/tarantool/
|
||||
CMD ["tarantool", "/opt/tarantool/example.lua"]
|
29
features/prometheus/LICENSE
Normal file
29
features/prometheus/LICENSE
Normal file
@ -0,0 +1,29 @@
|
||||
Copyright (C) 2014-2016 Tarantool AUTHORS:
|
||||
please see AUTHORS file in tarantool/tarantool repository.
|
||||
|
||||
Redistribution and use in source and binary forms, with or
|
||||
without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY AUTHORS ``AS IS'' AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
257
features/prometheus/README.md
Normal file
257
features/prometheus/README.md
Normal file
@ -0,0 +1,257 @@
|
||||
<a href="http://tarantool.org">
|
||||
<img src="https://avatars2.githubusercontent.com/u/2344919?v=2&s=250" align="right">
|
||||
</a>
|
||||
<!--a href="https://travis-ci.org/tarantool/tarantool-prometheus">
|
||||
<img src="https://travis-ci.org/tarantool/tarantool-prometheus.png?branch=master" align="right">
|
||||
</a-->
|
||||
|
||||
# Prometheus metric collector for Tarantool
|
||||
|
||||
This is a Lua library that makes it easy to collect metrics from your Tarantool
|
||||
apps and databases and expose them via the Prometheus protocol. You may use the
|
||||
library to instrument your code and get an insight into performance bottlenecks.
|
||||
|
||||
At the moment, 3 types of metrics are supported:
|
||||
* Counter: a non-decreasing numeric value, used e.g. for counting the number of
|
||||
requests
|
||||
* Gauge: an arbitrary numeric value, which can be used e.g. to report memory
|
||||
usage
|
||||
* Histogram: for counting value distribution by user-specified buckets. Can be
|
||||
used for recording request/response times.
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Limitations](#limitations)
|
||||
* [Getting started](#getting-started)
|
||||
* [Basic examples](#basic-examples)
|
||||
* [A more detailed example](#a-more-detailed-example)
|
||||
* [Usage](#usage)
|
||||
* [counter(name, help, labels)](#countername-help-labels)
|
||||
* [gauge(name, help, labels)](#gaugename-help-labels)
|
||||
* [histogram(name, help, labels, buckets)](#histogramname-help-labels-buckets)
|
||||
* [Counter:inc(value, labels)](#counterincvalue-labels)
|
||||
* [Gauge:set(value, labels)](#gaugesetvalue-labels)
|
||||
* [Gauge:inc(value, labels)](#gaugeincvalue-labels)
|
||||
* [Gauge:dec(value, labels)](#gaugedecvalue-labels)
|
||||
* [Histogram:observe(value, labels)](#histogramobservevalue-labels)
|
||||
* [collect()](#collect)
|
||||
* [collect\_http()](#collect_http)
|
||||
* [Development](#development)
|
||||
* [Credits](#credits)
|
||||
* [License](#license)
|
||||
|
||||
## Limitations
|
||||
|
||||
The Summary metric is not implemented yet. It may be implemented in future.
|
||||
|
||||
## Getting started
|
||||
|
||||
The easiest way is, of course, to use
|
||||
[one of the official Docker images](https://hub.docker.com/r/tarantool/tarantool/),
|
||||
which already contain the Prometheus collector. But if you run on a regular
|
||||
Linux distro, first install the library from
|
||||
[Tarantool Rocks server](http://rocks.tarantool.org):
|
||||
|
||||
```bash
|
||||
$ luarocks install tarantool-prometheus
|
||||
```
|
||||
### Basic examples
|
||||
|
||||
To report the arena size, you can write the following code:
|
||||
|
||||
```lua
|
||||
prometheus = require('tarantool-prometheus')
|
||||
fiber = require('fiber')
|
||||
|
||||
box.cfg{}
|
||||
httpd = http.new('0.0.0.0', 8080)
|
||||
|
||||
arena_used = prometheus.gauge("tarantool_arena_used",
|
||||
"The amount of arena used by Tarantool")
|
||||
|
||||
function monitor_arena_size()
|
||||
while true do
|
||||
arena_used:set(box.slab.info().arena_used)
|
||||
fiber.sleep(5)
|
||||
end
|
||||
end
|
||||
fiber.create(monitor_arena_size)
|
||||
|
||||
httpd:route( { path = '/metrics' }, prometheus.collect_http)
|
||||
httpd:start()
|
||||
```
|
||||
|
||||
The code will periodically measure the arena size and update the `arena_used`
|
||||
metric. Later, when Prometheus polls the instance, it will get the values of all
|
||||
metrics the instance created.
|
||||
|
||||
There are 3 important bits in the code above:
|
||||
|
||||
```lua
|
||||
arena_used = prometheus.gauge(...)
|
||||
```
|
||||
|
||||
This creates a [Gauge](https://prometheus.io/docs/concepts/metric_types/#gauge)
|
||||
object that can be set to an arbitrary numeric value. After this, the metric from
|
||||
this object will be automatically collected by Prometheus every time metrics are
|
||||
polled.
|
||||
|
||||
```lua
|
||||
arena_used:set(...)
|
||||
```
|
||||
|
||||
This sets the current value of the metric.
|
||||
|
||||
```lua
|
||||
httpd:route( { path = '/metrics' }, prometheus.collect_http)
|
||||
```
|
||||
|
||||
This exposes metrics over the text/plain HTTP protocol on
|
||||
[http://localhost:8080/metrics](http://localhost:8080/metrics) for Prometheus to
|
||||
collect. Prometheus periodically polls this endpoint and stores the results in
|
||||
its time series database.
|
||||
|
||||
### A more detailed example
|
||||
|
||||
If you want a more detailed example, there is an `example.lua` file in the root
|
||||
of this repo. It demonstrates the usage of each of the 3 metric types.
|
||||
|
||||
To run it with Docker, you can do as follows:
|
||||
|
||||
``` bash
|
||||
$ docker build -t tarantool-prometheus .
|
||||
$ docker run --rm -t -i -p8080:8080 tarantool-prometheus
|
||||
```
|
||||
|
||||
Then visit [http://localhost:8080/metrics](http://localhost:8080/metrics) and
|
||||
refresh the page a few times to see the metrics change.
|
||||
|
||||
## Usage
|
||||
|
||||
This section documents the user-facing API of the module.
|
||||
|
||||
### counter(name, help, labels)
|
||||
|
||||
Creates and registers a [Counter](https://prometheus.io/docs/concepts/metric_types/#counter).
|
||||
|
||||
* `name` is the name of the metric. Required.
|
||||
* `help` is the metric docstring. You can use newlines and quotes here. Optional.
|
||||
* `labels` is an array of label names for the metric. Optional.
|
||||
|
||||
Example:
|
||||
|
||||
```lua
|
||||
num_of_logins = prometheus.counter(
|
||||
"tarantool_number_of_logins", "Total number of user logins")
|
||||
|
||||
http_requests = prometheus:counter(
|
||||
"tarantool_http_requests_total", "Number of HTTP requests", {"host", "status"})
|
||||
```
|
||||
|
||||
### gauge(name, help, labels)
|
||||
|
||||
Creates and registers a [Gauge](https://prometheus.io/docs/concepts/metric_types/#gauge).
|
||||
|
||||
* `name` is the name of the metric. Required.
|
||||
* `help` is the metric docstring. You can use newlines and quotes here. Optional.
|
||||
* `labels` is an array of label names for the metric. Optional.
|
||||
|
||||
Example:
|
||||
|
||||
``` lua
|
||||
arena_used = prometheus.gauge(
|
||||
"tarantool_arena_used_size", "Total size of the arena used")
|
||||
|
||||
requests_inprogress = prometheus.gauge(
|
||||
"tarantool_requests_inprogress", "Number of requests in progress", {"request_type"})
|
||||
```
|
||||
|
||||
### histogram(name, help, labels, buckets)
|
||||
|
||||
Creates and registers a [Histogram](https://prometheus.io/docs/concepts/metric_types/#histogram).
|
||||
|
||||
* `name` is the name of the metric. Required.
|
||||
* `help` is the metric docstring. You can use newlines and quotes here. Optional.
|
||||
* `labels` is an array of label names for the metric. Optional.
|
||||
* `buckets` is an array of numbers defining histogram buckets. Optional. Defaults to
|
||||
`{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1.0, 2.5, 5.0, 7.5, 10.0, INF}`.
|
||||
|
||||
Example:
|
||||
|
||||
``` lua
|
||||
request_latency = prometheus.histogram(
|
||||
"tarantool_request_latency_seconds", "Incoming request latency", {"client"})
|
||||
response_size = prometheus.histogram(
|
||||
"tarantool_response_size", "Size of response, in bytes", nil, {100, 1000, 100000})
|
||||
```
|
||||
|
||||
### Counter:inc(value, labels)
|
||||
|
||||
Increments a counter created by `prometheus.counter()`.
|
||||
|
||||
* `value` specifies by how much to increment. Optional. Defaults to `1`.
|
||||
* `labels` is an array of label values. Optional.
|
||||
|
||||
### Gauge:set(value, labels)
|
||||
|
||||
Sets a value of a gauge created by `prometheus.gauge()`.
|
||||
|
||||
* `value` is the value to set. Optional. Defaults to `0`.
|
||||
* `labels` is an array of label values. Optional.
|
||||
|
||||
### Gauge:inc(value, labels)
|
||||
|
||||
Increments a gauge created by `prometheus.gauge()`.
|
||||
|
||||
* `value` specifies by how much to increment. Optional. Defaults to `1`.
|
||||
* `labels` is an array of label values. Optional.
|
||||
|
||||
### Gauge:dec(value, labels)
|
||||
|
||||
Decrements a gauge created by `prometheus.gauge()`.
|
||||
|
||||
* `value` specifies by how much to decrement. Optional. Defaults to `1`.
|
||||
* `labels` is an array of label values. Optional.
|
||||
|
||||
### Histogram:observe(value, labels)
|
||||
|
||||
Records a value to a histogram created by `prometheus.histogram()`.
|
||||
|
||||
* `value` is the value to record. Optional. Defaults to `0`.
|
||||
* `labels` is an array of label values. Optional.
|
||||
|
||||
### collect()
|
||||
|
||||
Presents all metrics in a text format compatible with Prometheus. This can be
|
||||
called either by `http.server` callback or by
|
||||
[Tarantool nginx_upstream_module](https://github.com/tarantool/nginx_upstream_module).
|
||||
|
||||
### collect_http()
|
||||
|
||||
Convenience function, specially for one-line registration in the Tarantool
|
||||
`http.server`, as follows:
|
||||
|
||||
```lua
|
||||
httpd:route( { path = '/metrics' }, prometheus.collect_http)
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Contributions are welcome. Report issues and feature requests at
|
||||
https://github.com/tarantool/tarantool-prometheus/issues
|
||||
|
||||
To run tests, do:
|
||||
|
||||
```bash
|
||||
$ tarantool test.lua
|
||||
```
|
||||
|
||||
NB: Tests require `luaunit` library.
|
||||
|
||||
## Credits
|
||||
|
||||
Loosely based on the implementation by @knyar: https://github.com/knyar/nginx-lua-prometheus
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the BSD license. See the LICENSE file.
|
52
features/prometheus/example.lua
Normal file
52
features/prometheus/example.lua
Normal file
@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env tarantool
|
||||
|
||||
http = require('http.server')
|
||||
prometheus = require('tarantool-prometheus')
|
||||
fiber = require('fiber')
|
||||
|
||||
box.cfg{}
|
||||
prometheus.init()
|
||||
|
||||
httpd = http.new('0.0.0.0', 8080)
|
||||
|
||||
space = box.schema.space.create("test_space")
|
||||
space:create_index('primary', {type = 'hash', parts = {1, 'NUM'}})
|
||||
|
||||
function random_write()
|
||||
num = math.random(10000)
|
||||
|
||||
box.space.test_space:truncate()
|
||||
for i=0,num do
|
||||
box.space.test_space:insert({i, tostring(i)})
|
||||
end
|
||||
end
|
||||
|
||||
function worker()
|
||||
exec_count = prometheus.counter("tarantool_worker_execution_count",
|
||||
"Number of times worker process has been executed")
|
||||
exec_time = prometheus.histogram("tarantool_worker_execution_time",
|
||||
"Time of each worker process execution")
|
||||
arena_used = prometheus.gauge("tarantool_arena_used",
|
||||
"The amount of arena used by Tarantool")
|
||||
|
||||
|
||||
while true do
|
||||
time_start = fiber.time()
|
||||
random_write()
|
||||
time_end = fiber.time()
|
||||
|
||||
exec_time:observe(time_end - time_start)
|
||||
exec_count:inc()
|
||||
arena_used:set(box.slab.info().arena_used)
|
||||
|
||||
fiber.sleep(1)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
httpd:route( { path = '/metrics' }, prometheus.collect_http)
|
||||
|
||||
httpd:start()
|
||||
fiber.create(worker)
|
79
features/prometheus/tarantool-metrics.lua
Normal file
79
features/prometheus/tarantool-metrics.lua
Normal file
@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env tarantool
|
||||
|
||||
local prometheus = require('tarantool-prometheus')
|
||||
|
||||
local memory_limit_bytes = prometheus.gauge(
|
||||
'tarantool_memory_limit_bytes',
|
||||
'Maximum amount of memory Tarantool can use')
|
||||
local memory_used_bytes = prometheus.gauge(
|
||||
'tarantool_memory_used_bytes',
|
||||
'Amount of memory currently used by Tarantool')
|
||||
local tuples_memory_bytes = prometheus.gauge(
|
||||
'tarantool_tuples_memory_bytes',
|
||||
'Amount of memory allocated for Tarantool tuples')
|
||||
local system_memory_bytes = prometheus.gauge(
|
||||
'tarantool_system_memory_bytes',
|
||||
'Amount of memory used by Tarantool indexes and system')
|
||||
|
||||
local requests_total = prometheus.gauge(
|
||||
'tarantool_requests_total',
|
||||
'Total number of requests by request type',
|
||||
{'request_type'})
|
||||
|
||||
local uptime_seconds = prometheus.gauge(
|
||||
'tarantool_uptime_seconds',
|
||||
'Number of seconds since the server started')
|
||||
|
||||
local tuples_total = prometheus.gauge(
|
||||
'tarantool_space_tuples_total',
|
||||
'Total number of tuples in a space',
|
||||
{'space_name'})
|
||||
|
||||
|
||||
local function measure_tarantool_memory_usage()
|
||||
local slabs = box.slab.info()
|
||||
local memory_limit = slabs.quota_size
|
||||
local memory_used = slabs.quota_used
|
||||
local tuples_memory = slabs.arena_used
|
||||
local system_memory = memory_used - tuples_memory
|
||||
|
||||
memory_limit_bytes:set(memory_limit)
|
||||
memory_used_bytes:set(memory_used)
|
||||
tuples_memory_bytes:set(tuples_memory)
|
||||
system_memory_bytes:set(system_memory)
|
||||
end
|
||||
|
||||
local function measure_tarantool_request_stats()
|
||||
local stat = box.stat()
|
||||
local request_types = {'delete', 'select', 'insert', 'eval', 'call',
|
||||
'replace', 'upsert', 'auth', 'error', 'update'}
|
||||
|
||||
for _, request_type in ipairs(request_types) do
|
||||
requests_total:set(stat[string.upper(request_type)].total,
|
||||
{request_type})
|
||||
end
|
||||
end
|
||||
|
||||
local function measure_tarantool_uptime()
|
||||
uptime_seconds:set(box.info.uptime)
|
||||
end
|
||||
|
||||
local function measure_tarantool_space_stats()
|
||||
for _, space in box.space._space:pairs() do
|
||||
local space_name = space[3]
|
||||
|
||||
if string.sub(space_name, 1,1) ~= '_' then
|
||||
tuples_total:set(box.space[space_name]:len(), {space_name})
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function measure_tarantool_metrics()
|
||||
measure_tarantool_memory_usage()
|
||||
measure_tarantool_request_stats()
|
||||
measure_tarantool_uptime()
|
||||
measure_tarantool_space_stats()
|
||||
end
|
||||
|
||||
return {measure_tarantool_metrics=measure_tarantool_metrics}
|
22
features/prometheus/tarantool-prometheus-scm-1.rockspec
Normal file
22
features/prometheus/tarantool-prometheus-scm-1.rockspec
Normal file
@ -0,0 +1,22 @@
|
||||
package = 'tarantool-prometheus'
|
||||
version = 'scm-1'
|
||||
source = {
|
||||
url = 'git://github.com/tarantool/prometheus.git',
|
||||
branch = 'master',
|
||||
}
|
||||
description = {
|
||||
summary = 'Prometheus library to collect metrics from Tarantool',
|
||||
homepage = 'https://github.com/tarantool/prometheus.git',
|
||||
license = 'MIT',
|
||||
}
|
||||
dependencies = {
|
||||
'lua >= 5.1';
|
||||
}
|
||||
build = {
|
||||
type = 'builtin',
|
||||
|
||||
modules = {
|
||||
['tarantool-prometheus.tarantool-metrics'] = 'tarantool-metrics.lua',
|
||||
['tarantool-prometheus'] = 'tarantool-prometheus.lua'
|
||||
}
|
||||
}
|
390
features/prometheus/tarantool-prometheus.lua
Normal file
390
features/prometheus/tarantool-prometheus.lua
Normal file
@ -0,0 +1,390 @@
|
||||
-- vim: ts=2:sw=2:sts=2:expandtab
|
||||
|
||||
local INF = math.huge
|
||||
local NAN = math.huge * 0
|
||||
local DEFAULT_BUCKETS = {.005, .01, .025, .05, .075, .1, .25, .5,
|
||||
.75, 1.0, 2.5, 5.0, 7.5, 10.0, INF}
|
||||
|
||||
local REGISTRY = nil
|
||||
|
||||
local Registry = {}
|
||||
Registry.__index = Registry
|
||||
|
||||
function Registry.new()
|
||||
local obj = {}
|
||||
setmetatable(obj, Registry)
|
||||
obj.collectors = {}
|
||||
obj.callbacks = {}
|
||||
return obj
|
||||
end
|
||||
|
||||
function Registry:register(collector)
|
||||
if self.collectors[collector.name]~=nil then
|
||||
return self.collectors[collector.name]
|
||||
end
|
||||
self.collectors[collector.name] = collector
|
||||
return collector
|
||||
end
|
||||
|
||||
function Registry:unregister(collector)
|
||||
if self.collectors[collector.name]~=nil then
|
||||
table.remove(self.collectors, collector.name)
|
||||
end
|
||||
end
|
||||
|
||||
function Registry:collect()
|
||||
for _, registered_callback in ipairs(self.callbacks) do
|
||||
registered_callback()
|
||||
end
|
||||
|
||||
local result = {}
|
||||
for _, collector in pairs(self.collectors) do
|
||||
for _, metric in ipairs(collector:collect()) do
|
||||
table.insert(result, metric)
|
||||
end
|
||||
table.insert(result, '')
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function Registry:register_callback(callback)
|
||||
local found = false
|
||||
for _, registered_callback in ipairs(self.callbacks) do
|
||||
if registered_callback == calback then
|
||||
found = true
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
table.insert(self.callbacks, callback)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_registry()
|
||||
if not REGISTRY then
|
||||
REGISTRY = Registry.new()
|
||||
end
|
||||
return REGISTRY
|
||||
end
|
||||
|
||||
local function register(collector)
|
||||
local registry = get_registry()
|
||||
registry:register(collector)
|
||||
|
||||
return collector
|
||||
end
|
||||
|
||||
local function register_callback(callback)
|
||||
local registry = get_registry()
|
||||
registry:register_callback(callback)
|
||||
end
|
||||
|
||||
function zip(lhs, rhs)
|
||||
if lhs == nil or rhs == nil then
|
||||
return {}
|
||||
end
|
||||
|
||||
local len = math.min(#lhs, #rhs)
|
||||
local result = {}
|
||||
for i=1,len do
|
||||
table.insert(result, {lhs[i], rhs[i]})
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function metric_to_string(value)
|
||||
if value == INF then
|
||||
return "+Inf"
|
||||
elseif value == -INF then
|
||||
return "-Inf"
|
||||
elseif value ~= value then
|
||||
return "Nan"
|
||||
else
|
||||
return tostring(value)
|
||||
end
|
||||
end
|
||||
|
||||
local function escape_string(str)
|
||||
return str
|
||||
:gsub("\\", "\\\\")
|
||||
:gsub("\n", "\\n")
|
||||
:gsub('"', '\\"')
|
||||
end
|
||||
|
||||
local function labels_to_string(label_pairs)
|
||||
if #label_pairs == 0 then
|
||||
return ""
|
||||
end
|
||||
local label_parts = {}
|
||||
for _, label in ipairs(label_pairs) do
|
||||
local label_name = label[1]
|
||||
local label_value = label[2]
|
||||
local label_value_escaped = escape_string(string.format("%s", label_value))
|
||||
table.insert(label_parts, label_name .. '="' .. label_value_escaped .. '"')
|
||||
end
|
||||
return "{" .. table.concat(label_parts, ",") .. "}"
|
||||
end
|
||||
|
||||
|
||||
local Counter = {}
|
||||
Counter.__index = Counter
|
||||
|
||||
function Counter.new(name, help, labels)
|
||||
local obj = {}
|
||||
setmetatable(obj, Counter)
|
||||
if not name then
|
||||
error("Name should be set for Counter")
|
||||
end
|
||||
obj.name = name
|
||||
obj.help = help or ""
|
||||
obj.labels = labels or {}
|
||||
obj.observations = {}
|
||||
obj.label_values = {}
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function Counter:inc(num, label_values)
|
||||
local num = num or 1
|
||||
local label_values = label_values or {}
|
||||
if num < 0 then
|
||||
error("Counter increment should not be negative")
|
||||
end
|
||||
local key = table.concat(label_values, '\0')
|
||||
local old_value = self.observations[key] or 0
|
||||
self.observations[key] = old_value + num
|
||||
self.label_values[key] = label_values
|
||||
end
|
||||
|
||||
function Counter:collect()
|
||||
local result = {}
|
||||
|
||||
if next(self.observations) == nil then
|
||||
return {}
|
||||
end
|
||||
|
||||
table.insert(result, '# HELP '..self.name..' '..escape_string(self.help))
|
||||
table.insert(result, "# TYPE "..self.name.." counter")
|
||||
|
||||
for key, observation in pairs(self.observations) do
|
||||
local label_values = self.label_values[key]
|
||||
local prefix = self.name
|
||||
local labels = zip(self.labels, label_values)
|
||||
|
||||
local str = prefix..labels_to_string(labels)..
|
||||
' '..metric_to_string(observation)
|
||||
table.insert(result, str)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
local Gauge = {}
|
||||
Gauge.__index = Gauge
|
||||
|
||||
function Gauge.new(name, help, labels)
|
||||
local obj = {}
|
||||
setmetatable(obj, Gauge)
|
||||
if not name then
|
||||
error("Name should be set for Gauge")
|
||||
end
|
||||
obj.name = name
|
||||
obj.help = help or ""
|
||||
obj.labels = labels or {}
|
||||
obj.observations = {}
|
||||
obj.label_values = {}
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function Gauge:inc(num, label_values)
|
||||
local num = num or 1
|
||||
local label_values = label_values or {}
|
||||
local key = table.concat(label_values, '\0')
|
||||
local old_value = self.observations[key] or 0
|
||||
self.observations[key] = old_value + num
|
||||
self.label_values[key] = label_values
|
||||
end
|
||||
|
||||
function Gauge:dec(num, label_values)
|
||||
local num = num or 1
|
||||
local label_values = label_values or {}
|
||||
local key = table.concat(label_values, '\0')
|
||||
local old_value = self.observations[key] or 0
|
||||
self.observations[key] = old_value - num
|
||||
self.label_values[key] = label_values
|
||||
end
|
||||
|
||||
function Gauge:set(num, label_values)
|
||||
local num = num or 0
|
||||
local label_values = label_values or {}
|
||||
local key = table.concat(label_values, '\0')
|
||||
self.observations[key] = num
|
||||
self.label_values[key] = label_values
|
||||
end
|
||||
|
||||
function Gauge:collect()
|
||||
local result = {}
|
||||
|
||||
if next(self.observations) == nil then
|
||||
return {}
|
||||
end
|
||||
|
||||
table.insert(result, '# HELP '..self.name..' '..escape_string(self.help))
|
||||
table.insert(result, "# TYPE "..self.name.." gauge")
|
||||
|
||||
for key, observation in pairs(self.observations) do
|
||||
local label_values = self.label_values[key]
|
||||
local prefix = self.name
|
||||
local labels = zip(self.labels, label_values)
|
||||
|
||||
local str = prefix..labels_to_string(labels)..
|
||||
' '..metric_to_string(observation)
|
||||
table.insert(result, str)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local Histogram = {}
|
||||
Histogram.__index = Histogram
|
||||
|
||||
function Histogram.new(name, help, labels,
|
||||
buckets)
|
||||
local obj = {}
|
||||
setmetatable(obj, Histogram)
|
||||
if not name then
|
||||
error("Name should be set for Histogram")
|
||||
end
|
||||
obj.name = name
|
||||
obj.help = help or ""
|
||||
obj.labels = labels or {}
|
||||
obj.buckets = buckets or DEFAULT_BUCKETS
|
||||
table.sort(obj.buckets)
|
||||
if obj.buckets[#obj.buckets] ~= INF then
|
||||
obj.buckets[#obj.buckets+1] = INF
|
||||
end
|
||||
obj.observations = {}
|
||||
obj.label_values = {}
|
||||
obj.counts = {}
|
||||
obj.sums = {}
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function Histogram:observe(num, label_values)
|
||||
local num = num or 0
|
||||
local label_values = label_values or {}
|
||||
local key = table.concat(label_values, '\0')
|
||||
|
||||
local obs = nil
|
||||
if self.observations[key] == nil then
|
||||
obs = {}
|
||||
for i=1, #self.buckets do
|
||||
obs[i] = 0
|
||||
end
|
||||
self.observations[key] = obs
|
||||
self.label_values[key] = label_values
|
||||
self.counts[key] = 0
|
||||
self.sums[key] = 0
|
||||
else
|
||||
obs = self.observations[key]
|
||||
end
|
||||
|
||||
self.counts[key] = self.counts[key] + 1
|
||||
self.sums[key] = self.sums[key] + num
|
||||
for i, bucket in ipairs(self.buckets) do
|
||||
if num <= bucket then
|
||||
obs[i] = obs[i] + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Histogram:collect()
|
||||
local result = {}
|
||||
|
||||
if next(self.observations) == nil then
|
||||
return {}
|
||||
end
|
||||
|
||||
table.insert(result, '# HELP '..self.name..' '..escape_string(self.help))
|
||||
table.insert(result, "# TYPE "..self.name.." histogram")
|
||||
|
||||
for key, observation in pairs(self.observations) do
|
||||
local label_values = self.label_values[key]
|
||||
local prefix = self.name
|
||||
local labels = zip(self.labels, label_values)
|
||||
labels[#labels+1] = {le="0"}
|
||||
for i, bucket in ipairs(self.buckets) do
|
||||
labels[#labels] = {"le", metric_to_string(bucket)}
|
||||
str = prefix.."_bucket"..labels_to_string(labels)..
|
||||
' '..metric_to_string(observation[i])
|
||||
table.insert(result, str)
|
||||
end
|
||||
table.remove(labels, #labels)
|
||||
|
||||
table.insert(result,
|
||||
prefix.."_sum"..labels_to_string(labels)..' '..self.sums[key])
|
||||
table.insert(result,
|
||||
prefix.."_count"..labels_to_string(labels)..' '..self.counts[key])
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
-- #################### Public API ####################
|
||||
|
||||
|
||||
local function counter(name, help, labels)
|
||||
local obj = Counter.new(name, help, labels)
|
||||
obj = register(obj)
|
||||
return obj
|
||||
end
|
||||
|
||||
local function gauge(name, help, labels)
|
||||
local obj = Gauge.new(name, help, labels)
|
||||
obj = register(obj)
|
||||
return obj
|
||||
end
|
||||
|
||||
local function histogram(name, help, labels, buckets)
|
||||
local obj = Histogram.new(name, help, labels, buckets)
|
||||
obj = register(obj)
|
||||
return obj
|
||||
end
|
||||
|
||||
local function collect()
|
||||
local registry = get_registry()
|
||||
|
||||
return table.concat(registry:collect(), '\n')..'\n'
|
||||
end
|
||||
|
||||
local function collect_http()
|
||||
return {
|
||||
status = 200,
|
||||
headers = { ['content-type'] = 'text/plain; charset=utf8' },
|
||||
body = collect()
|
||||
}
|
||||
end
|
||||
|
||||
local function clear()
|
||||
local registry = get_registry()
|
||||
registry.collectors = {}
|
||||
registry.callbacks = {}
|
||||
end
|
||||
|
||||
local function init()
|
||||
local registry = get_registry()
|
||||
local tarantool_metrics = require('tarantool-prometheus.tarantool-metrics')
|
||||
registry:register_callback(tarantool_metrics.measure_tarantool_metrics)
|
||||
end
|
||||
|
||||
return {counter=counter,
|
||||
gauge=gauge,
|
||||
histogram=histogram,
|
||||
collect=collect,
|
||||
collect_http=collect_http,
|
||||
clear=clear,
|
||||
init=init}
|
180
features/prometheus/test.lua
Normal file
180
features/prometheus/test.lua
Normal file
@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env tarantool
|
||||
|
||||
luaunit = require('luaunit')
|
||||
prometheus = require('tarantool-prometheus')
|
||||
|
||||
|
||||
TestPrometheus = {}
|
||||
|
||||
function TestPrometheus:tearDown()
|
||||
prometheus.clear()
|
||||
end
|
||||
|
||||
function TestPrometheus:testCounterNegativeValue()
|
||||
c = prometheus.counter("counter")
|
||||
luaunit.assertErrorMsgContains("should not be negative", c.inc, c, -1)
|
||||
end
|
||||
|
||||
function TestPrometheus:testLabelNames()
|
||||
c = prometheus.counter("counter", "", {'a1', 'foo', "var"})
|
||||
c:inc(1, {1, '2', 'q4'})
|
||||
|
||||
r = c:collect()
|
||||
luaunit.assertEquals(r[3], 'counter{a1="1",foo="2",var="q4"} 1')
|
||||
end
|
||||
|
||||
function TestPrometheus:testLabelEscape()
|
||||
c = prometheus.counter("counter", "", {'a1', 'foo', "var"})
|
||||
c:inc(1, {'"', '\\a', '\n'})
|
||||
|
||||
r = c:collect()
|
||||
luaunit.assertEquals(r[3], 'counter{a1="\\"",foo="\\\\a",var="\\n"} 1')
|
||||
end
|
||||
|
||||
function TestPrometheus:testHelpEscape()
|
||||
c = prometheus.counter("counter", "some\" escaped\\strings\n")
|
||||
c:inc(1, {'"', '\\a', '\n'})
|
||||
|
||||
r = c:collect()
|
||||
luaunit.assertEquals(r[1], '# HELP counter some\\" escaped\\\\strings\\n')
|
||||
end
|
||||
|
||||
function TestPrometheus:testCounters()
|
||||
first = prometheus.counter("counter1", "", {"a", "b"})
|
||||
second = prometheus.counter("counter2", "", {"a", "b"})
|
||||
|
||||
first:inc()
|
||||
first:inc(4)
|
||||
|
||||
second:inc(1, {"v1", "v2"})
|
||||
second:inc(3, {"v1", "v3"})
|
||||
second:inc(2, {"v1", "v3"})
|
||||
|
||||
r = first:collect()
|
||||
luaunit.assertEquals(r[1], "# HELP counter1 ")
|
||||
luaunit.assertEquals(r[2], "# TYPE counter1 counter")
|
||||
luaunit.assertEquals(r[3], "counter1 5")
|
||||
luaunit.assertEquals(r[4], nil)
|
||||
|
||||
|
||||
r = second:collect()
|
||||
luaunit.assertEquals(r[3], 'counter2{a="v1",b="v2"} 1')
|
||||
luaunit.assertEquals(r[4], 'counter2{a="v1",b="v3"} 5')
|
||||
luaunit.assertEquals(r[5], nil)
|
||||
|
||||
end
|
||||
|
||||
function TestPrometheus:testGauge()
|
||||
first = prometheus.gauge("gauge1", "", {"a", "b"})
|
||||
second = prometheus.gauge("gauge2", "", {"a", "b"})
|
||||
|
||||
first:inc()
|
||||
first:inc(4)
|
||||
first:set(2)
|
||||
first:dec()
|
||||
|
||||
second:set(1, {"v1", "v2"})
|
||||
second:inc(3, {"v1", "v3"})
|
||||
second:dec(1, {"v1", "v3"})
|
||||
second:inc(0, {"v1", "v3"})
|
||||
|
||||
r = first:collect()
|
||||
luaunit.assertEquals(r[1], "# HELP gauge1 ")
|
||||
luaunit.assertEquals(r[2], "# TYPE gauge1 gauge")
|
||||
luaunit.assertEquals(r[3], "gauge1 1")
|
||||
luaunit.assertEquals(r[4], nil)
|
||||
|
||||
|
||||
r = second:collect()
|
||||
luaunit.assertEquals(r[3], 'gauge2{a="v1",b="v2"} 1')
|
||||
luaunit.assertEquals(r[4], 'gauge2{a="v1",b="v3"} 2')
|
||||
luaunit.assertEquals(r[5], nil)
|
||||
|
||||
end
|
||||
|
||||
function TestPrometheus:testSpecialValues()
|
||||
gauge = prometheus.gauge("gauge")
|
||||
|
||||
gauge:set(math.huge)
|
||||
r = gauge:collect()
|
||||
luaunit.assertEquals(r[3], "gauge +Inf")
|
||||
|
||||
gauge:set(-math.huge)
|
||||
r = gauge:collect()
|
||||
luaunit.assertEquals(r[3], "gauge -Inf")
|
||||
|
||||
gauge:set(math.huge * 0)
|
||||
r = gauge:collect()
|
||||
luaunit.assertEquals(r[3], "gauge Nan")
|
||||
end
|
||||
|
||||
|
||||
|
||||
function TestPrometheus:testHistogram()
|
||||
hist1 = prometheus.histogram("l1", "Histogram 1")
|
||||
hist2 = prometheus.histogram("l2", "Histogram 2", {"var", "site"}, {0.1, 0.2})
|
||||
hist3 = prometheus.histogram("l3", "Histogram 3", {})
|
||||
|
||||
hist1:observe(0.35)
|
||||
hist1:observe(0.9)
|
||||
hist1:observe(5)
|
||||
hist1:observe(15)
|
||||
|
||||
hist2:observe(0.001, {"ok", "site1"})
|
||||
hist2:observe(0.15, {"ok", "site1"})
|
||||
hist2:observe(0.15, {"ok", "site2"})
|
||||
|
||||
r = hist1:collect()
|
||||
luaunit.assertEquals(r[1], "# HELP l1 Histogram 1")
|
||||
luaunit.assertEquals(r[2], "# TYPE l1 histogram")
|
||||
luaunit.assertEquals(r[3], 'l1_bucket{le="0.005"} 0')
|
||||
luaunit.assertEquals(r[4], 'l1_bucket{le="0.01"} 0')
|
||||
luaunit.assertEquals(r[5], 'l1_bucket{le="0.025"} 0')
|
||||
luaunit.assertEquals(r[6], 'l1_bucket{le="0.05"} 0')
|
||||
luaunit.assertEquals(r[7], 'l1_bucket{le="0.075"} 0')
|
||||
luaunit.assertEquals(r[8], 'l1_bucket{le="0.1"} 0')
|
||||
luaunit.assertEquals(r[9], 'l1_bucket{le="0.25"} 0')
|
||||
luaunit.assertEquals(r[10], 'l1_bucket{le="0.5"} 1')
|
||||
luaunit.assertEquals(r[11], 'l1_bucket{le="0.75"} 1')
|
||||
luaunit.assertEquals(r[12], 'l1_bucket{le="1"} 2')
|
||||
luaunit.assertEquals(r[13], 'l1_bucket{le="2.5"} 2')
|
||||
luaunit.assertEquals(r[14], 'l1_bucket{le="5"} 3')
|
||||
luaunit.assertEquals(r[15], 'l1_bucket{le="7.5"} 3')
|
||||
luaunit.assertEquals(r[16], 'l1_bucket{le="10"} 3')
|
||||
luaunit.assertEquals(r[17], 'l1_bucket{le="+Inf"} 4')
|
||||
luaunit.assertEquals(r[18], 'l1_sum 21.25')
|
||||
luaunit.assertEquals(r[19], 'l1_count 4')
|
||||
|
||||
r = hist2:collect()
|
||||
luaunit.assertEquals(r[3], 'l2_bucket{var="ok",site="site1",le="0.1"} 1')
|
||||
luaunit.assertEquals(r[4], 'l2_bucket{var="ok",site="site1",le="0.2"} 2')
|
||||
luaunit.assertEquals(r[5], 'l2_bucket{var="ok",site="site1",le="+Inf"} 2')
|
||||
luaunit.assertEquals(r[6], 'l2_sum{var="ok",site="site1"} 0.151')
|
||||
luaunit.assertEquals(r[7], 'l2_count{var="ok",site="site1"} 2')
|
||||
luaunit.assertEquals(r[8], 'l2_bucket{var="ok",site="site2",le="0.1"} 0')
|
||||
luaunit.assertEquals(r[9], 'l2_bucket{var="ok",site="site2",le="0.2"} 1')
|
||||
luaunit.assertEquals(r[10], 'l2_bucket{var="ok",site="site2",le="+Inf"} 1')
|
||||
luaunit.assertEquals(r[11], 'l2_sum{var="ok",site="site2"} 0.15')
|
||||
luaunit.assertEquals(r[12], 'l2_count{var="ok",site="site2"} 1')
|
||||
|
||||
r = hist3:collect()
|
||||
luaunit.assertEquals(r[3], nil)
|
||||
end
|
||||
|
||||
function TestPrometheus:testHistogramUnorderedBuckets()
|
||||
hist = prometheus.histogram("l2", "Histogram 2", {}, {0.2, 0.1, 0.5})
|
||||
|
||||
hist:observe(0.15)
|
||||
hist:observe(0.4)
|
||||
|
||||
r = hist:collect()
|
||||
luaunit.assertEquals(r[3], 'l2_bucket{le="0.1"} 0')
|
||||
luaunit.assertEquals(r[4], 'l2_bucket{le="0.2"} 1')
|
||||
luaunit.assertEquals(r[5], 'l2_bucket{le="0.5"} 2')
|
||||
luaunit.assertEquals(r[6], 'l2_bucket{le="+Inf"} 2')
|
||||
luaunit.assertEquals(r[7], 'l2_sum 0.55')
|
||||
luaunit.assertEquals(r[8], 'l2_count 2')
|
||||
luaunit.assertEquals(r[9], nil)
|
||||
end
|
||||
|
||||
os.exit(luaunit.run())
|
28
features/prometheus_event.lua
Normal file
28
features/prometheus_event.lua
Normal file
@ -0,0 +1,28 @@
|
||||
local Event = require 'utils.event'
|
||||
local event_repeat_seconds = 10
|
||||
|
||||
local prometheus = require 'features.prometheus.tarantool-prometheus'
|
||||
local gauge_players_online = prometheus.gauge("players_online", "description")
|
||||
|
||||
local function writeMetrics()
|
||||
game.write_file("metrics/game.prom", prometheus.collect(), false)
|
||||
end
|
||||
|
||||
local function on_nth_tick()
|
||||
writeMetrics()
|
||||
end
|
||||
|
||||
local function on_player_joined()
|
||||
gauge_players_online:inc(1)
|
||||
end
|
||||
|
||||
local function on_player_left()
|
||||
gauge_players_online:dec(1)
|
||||
end
|
||||
|
||||
Event.on_nth_tick(60*event_repeat_seconds, on_nth_tick)
|
||||
Event.add(defines.events.on_player_joined_game, on_player_joined)
|
||||
Event.add(defines.events.on_player_left_game, on_player_left)
|
||||
|
||||
|
||||
|
@ -2,7 +2,7 @@ require 'map_gen.maps.crash_site.blueprint_extractor'
|
||||
require 'map_gen.maps.crash_site.events'
|
||||
require 'map_gen.maps.crash_site.weapon_balance'
|
||||
require 'map_gen.maps.crash_site.features.rocket_tanks'
|
||||
|
||||
require 'features.prometheus_event'
|
||||
|
||||
local b = require 'map_gen.shared.builders'
|
||||
local Global = require('utils.global')
|
||||
|
Loading…
Reference in New Issue
Block a user