mirror of
https://github.com/bjc/prosody.git
synced 2025-04-03 21:27:38 +03:00
269 lines
6.4 KiB
Lua
269 lines
6.4 KiB
Lua
local socket = require "socket";
|
|
local time = require "util.time".now;
|
|
local array = require "util.array";
|
|
local t_concat = table.concat;
|
|
|
|
local new_metric_registry = require "util.openmetrics".new_metric_registry;
|
|
local render_histogram_le = require "util.openmetrics".render_histogram_le;
|
|
|
|
-- BEGIN of Metric implementations
|
|
|
|
-- Gauges
|
|
local gauge_metric_mt = {}
|
|
gauge_metric_mt.__index = gauge_metric_mt
|
|
|
|
local function new_gauge_metric(full_name, impl)
|
|
local metric = {
|
|
_full_name = full_name;
|
|
_impl = impl;
|
|
value = 0;
|
|
}
|
|
setmetatable(metric, gauge_metric_mt)
|
|
return metric
|
|
end
|
|
|
|
function gauge_metric_mt:set(value)
|
|
self.value = value
|
|
self._impl:push_gauge(self._full_name, value)
|
|
end
|
|
|
|
function gauge_metric_mt:add(delta)
|
|
self.value = self.value + delta
|
|
self._impl:push_gauge(self._full_name, self.value)
|
|
end
|
|
|
|
function gauge_metric_mt:reset()
|
|
self.value = 0
|
|
self._impl:push_gauge(self._full_name, 0)
|
|
end
|
|
|
|
function gauge_metric_mt.iter_samples()
|
|
-- statsd backend does not support iteration.
|
|
return function()
|
|
return nil
|
|
end
|
|
end
|
|
|
|
-- Counters
|
|
local counter_metric_mt = {}
|
|
counter_metric_mt.__index = counter_metric_mt
|
|
|
|
local function new_counter_metric(full_name, impl)
|
|
local metric = {
|
|
_full_name = full_name,
|
|
_impl = impl,
|
|
value = 0,
|
|
}
|
|
setmetatable(metric, counter_metric_mt)
|
|
return metric
|
|
end
|
|
|
|
function counter_metric_mt:set(value)
|
|
local delta = value - self.value
|
|
self.value = value
|
|
self._impl:push_counter_delta(self._full_name, delta)
|
|
end
|
|
|
|
function counter_metric_mt:add(value)
|
|
self.value = (self.value or 0) + value
|
|
self._impl:push_counter_delta(self._full_name, value)
|
|
end
|
|
|
|
function counter_metric_mt.iter_samples()
|
|
-- statsd backend does not support iteration.
|
|
return function()
|
|
return nil
|
|
end
|
|
end
|
|
|
|
function counter_metric_mt:reset()
|
|
self.value = 0
|
|
end
|
|
|
|
-- Histograms
|
|
local histogram_metric_mt = {}
|
|
histogram_metric_mt.__index = histogram_metric_mt
|
|
|
|
local function new_histogram_metric(buckets, full_name, impl)
|
|
-- NOTE: even though the more or less proprietary dogstatsd has Its own
|
|
-- histogram implementation, we push the individual buckets in this statsd
|
|
-- backend for both consistency and compatibility across statsd
|
|
-- implementations.
|
|
local metric = {
|
|
_sum_name = full_name..".sum",
|
|
_count_name = full_name..".count",
|
|
_impl = impl,
|
|
_created = time(),
|
|
_sum = 0,
|
|
_count = 0,
|
|
}
|
|
-- the order of buckets matters unfortunately, so we cannot directly use
|
|
-- the thresholds as table keys
|
|
for i, threshold in ipairs(buckets) do
|
|
local threshold_s = render_histogram_le(threshold)
|
|
metric[i] = {
|
|
threshold = threshold,
|
|
threshold_s = threshold_s,
|
|
count = 0,
|
|
_full_name = full_name..".bucket."..(threshold_s:gsub("%.", "_")),
|
|
}
|
|
end
|
|
setmetatable(metric, histogram_metric_mt)
|
|
return metric
|
|
end
|
|
|
|
function histogram_metric_mt:sample(value)
|
|
-- According to the I-D, values must be part of all buckets
|
|
for i, bucket in pairs(self) do
|
|
if "number" == type(i) and value <= bucket.threshold then
|
|
bucket.count = bucket.count + 1
|
|
self._impl:push_counter_delta(bucket._full_name, 1)
|
|
end
|
|
end
|
|
self._sum = self._sum + value
|
|
self._count = self._count + 1
|
|
self._impl:push_gauge(self._sum_name, self._sum)
|
|
self._impl:push_counter_delta(self._count_name, 1)
|
|
end
|
|
|
|
function histogram_metric_mt.iter_samples()
|
|
-- statsd backend does not support iteration.
|
|
return function()
|
|
return nil
|
|
end
|
|
end
|
|
|
|
function histogram_metric_mt:reset()
|
|
self._created = time()
|
|
self._count = 0
|
|
self._sum = 0
|
|
for i, bucket in pairs(self) do
|
|
if "number" == type(i) then
|
|
bucket.count = 0
|
|
end
|
|
end
|
|
self._impl:push_gauge(self._sum_name, self._sum)
|
|
end
|
|
|
|
-- Summaries
|
|
local summary_metric_mt = {}
|
|
summary_metric_mt.__index = summary_metric_mt
|
|
|
|
local function new_summary_metric(full_name, impl)
|
|
local metric = {
|
|
_sum_name = full_name..".sum",
|
|
_count_name = full_name..".count",
|
|
_impl = impl,
|
|
}
|
|
setmetatable(metric, summary_metric_mt)
|
|
return metric
|
|
end
|
|
|
|
function summary_metric_mt:sample(value)
|
|
self._impl:push_counter_delta(self._sum_name, value)
|
|
self._impl:push_counter_delta(self._count_name, 1)
|
|
end
|
|
|
|
function summary_metric_mt.iter_samples()
|
|
-- statsd backend does not support iteration.
|
|
return function()
|
|
return nil
|
|
end
|
|
end
|
|
|
|
function summary_metric_mt.reset()
|
|
end
|
|
|
|
-- BEGIN of statsd client implementation
|
|
|
|
local statsd_mt = {}
|
|
statsd_mt.__index = statsd_mt
|
|
|
|
function statsd_mt:cork()
|
|
self.corked = true
|
|
self.cork_buffer = self.cork_buffer or {}
|
|
end
|
|
|
|
function statsd_mt:uncork()
|
|
self.corked = false
|
|
self:_flush_cork_buffer()
|
|
end
|
|
|
|
function statsd_mt:_flush_cork_buffer()
|
|
local buffer = self.cork_buffer
|
|
for metric_name, value in pairs(buffer) do
|
|
self:_send_gauge(metric_name, value)
|
|
buffer[metric_name] = nil
|
|
end
|
|
end
|
|
|
|
function statsd_mt:push_gauge(metric_name, value)
|
|
if self.corked then
|
|
self.cork_buffer[metric_name] = value
|
|
else
|
|
self:_send_gauge(metric_name, value)
|
|
end
|
|
end
|
|
|
|
function statsd_mt:_send_gauge(metric_name, value)
|
|
self:_send(self.prefix..metric_name..":"..tostring(value).."|g")
|
|
end
|
|
|
|
function statsd_mt:push_counter_delta(metric_name, delta)
|
|
self:_send(self.prefix..metric_name..":"..tostring(delta).."|c")
|
|
end
|
|
|
|
function statsd_mt:_send(s)
|
|
return self.sock:send(s)
|
|
end
|
|
|
|
-- END of statsd client implementation
|
|
|
|
local function build_metric_name(family_name, labels)
|
|
local parts = array { family_name }
|
|
if labels then
|
|
parts:append(labels)
|
|
end
|
|
return t_concat(parts, "/"):gsub("%.", "_"):gsub("/", ".")
|
|
end
|
|
|
|
local function new(config)
|
|
if not config or not config.statsd_server then
|
|
return nil, "No statsd server specified in the config, please see https://prosody.im/doc/statistics";
|
|
end
|
|
|
|
local sock = socket.udp();
|
|
sock:setpeername(config.statsd_server, config.statsd_port or 8125);
|
|
|
|
local prefix = (config.prefix or "prosody")..".";
|
|
|
|
local impl = {
|
|
metric_registry = nil;
|
|
sock = sock;
|
|
prefix = prefix;
|
|
};
|
|
setmetatable(impl, statsd_mt)
|
|
|
|
local backend = {
|
|
gauge = function(family_name, labels)
|
|
return new_gauge_metric(build_metric_name(family_name, labels), impl)
|
|
end;
|
|
counter = function(family_name, labels)
|
|
return new_counter_metric(build_metric_name(family_name, labels), impl)
|
|
end;
|
|
histogram = function(buckets, family_name, labels)
|
|
return new_histogram_metric(buckets, build_metric_name(family_name, labels), impl)
|
|
end;
|
|
summary = function(family_name, labels, extra)
|
|
return new_summary_metric(build_metric_name(family_name, labels), impl, extra)
|
|
end;
|
|
};
|
|
|
|
impl.metric_registry = new_metric_registry(backend);
|
|
|
|
return impl;
|
|
end
|
|
|
|
return {
|
|
new = new;
|
|
}
|