aboutsummaryrefslogtreecommitdiffstats
path: root/util/statsd.lua
diff options
context:
space:
mode:
Diffstat (limited to 'util/statsd.lua')
-rw-r--r--util/statsd.lua297
1 files changed, 241 insertions, 56 deletions
diff --git a/util/statsd.lua b/util/statsd.lua
index 67481c36..6ae85c31 100644
--- a/util/statsd.lua
+++ b/util/statsd.lua
@@ -1,82 +1,267 @@
local socket = require "socket";
+local time = require "util.time".now;
+local array = require "util.array";
+local t_concat = table.concat;
-local time = require "util.time".now
+local new_metric_registry = require "util.openmetrics".new_metric_registry;
+local render_histogram_le = require "util.openmetrics".render_histogram_le;
-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";
+-- 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
- local sock = socket.udp();
- sock:setpeername(config.statsd_server, config.statsd_port or 8125);
+-- Counters
+local counter_metric_mt = {}
+counter_metric_mt.__index = counter_metric_mt
- local prefix = (config.prefix or "prosody")..".";
+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
- local function send_metric(s)
- return sock:send(prefix..s);
+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 proprietrary 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
- local function send_gauge(name, amount, relative)
- local s_amount = tostring(amount);
- if relative and amount > 0 then
- s_amount = "+"..s_amount;
+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
- return send_metric(name..":"..s_amount.."|g");
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
- local function send_counter(name, amount)
- return send_metric(name..":"..tostring(amount).."|c");
+function histogram_metric_mt.iter_samples()
+ -- statsd backend does not support iteration.
+ return function()
+ return nil
end
+end
- local function send_duration(name, duration)
- return send_metric(name..":"..tostring(duration).."|ms");
+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
- local function send_histogram_sample(name, sample)
- return send_metric(name..":"..tostring(sample).."|h");
+function summary_metric_mt.iter_samples()
+ -- statsd backend does not support iteration.
+ return function()
+ return nil
end
+end
- local methods;
- methods = {
- amount = function (name, initial)
- if initial then
- send_gauge(name, initial);
- end
- return function (new_v) send_gauge(name, new_v); end
- end;
- counter = function (name, initial) --luacheck: ignore 212/initial
- return function (delta)
- send_gauge(name, delta, true);
- end;
- end;
- rate = function (name)
- return function ()
- send_counter(name, 1);
- 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;
- distribution = function (name, unit, type) --luacheck: ignore 212/unit 212/type
- return function (value)
- send_histogram_sample(name, value);
- end;
+ counter = function(family_name, labels)
+ return new_counter_metric(build_metric_name(family_name, labels), impl)
end;
- sizes = function (name)
- name = name.."_size";
- return function (value)
- send_histogram_sample(name, value);
- end;
+ histogram = function(buckets, family_name, labels)
+ return new_histogram_metric(buckets, build_metric_name(family_name, labels), impl)
end;
- times = function (name)
- return function ()
- local start_time = time();
- return function ()
- local end_time = time();
- local duration = end_time - start_time;
- send_duration(name, duration*1000);
- end
- end;
+ summary = function(family_name, labels, extra)
+ return new_summary_metric(build_metric_name(family_name, labels), impl, extra)
end;
};
- return methods;
+
+ impl.metric_registry = new_metric_registry(backend);
+
+ return impl;
end
return {