aboutsummaryrefslogtreecommitdiffstats
path: root/core/statsmanager.lua
diff options
context:
space:
mode:
Diffstat (limited to 'core/statsmanager.lua')
-rw-r--r--core/statsmanager.lua226
1 files changed, 194 insertions, 32 deletions
diff --git a/core/statsmanager.lua b/core/statsmanager.lua
index 237b1dd5..8323c160 100644
--- a/core/statsmanager.lua
+++ b/core/statsmanager.lua
@@ -3,10 +3,12 @@ local config = require "core.configmanager";
local log = require "util.logger".init("stats");
local timer = require "util.timer";
local fire_event = prosody.events.fire_event;
+local array = require "util.array";
+local timed = require "util.openmetrics".timed;
local stats_interval_config = config.get("*", "statistics_interval");
local stats_interval = tonumber(stats_interval_config);
-if stats_interval_config and not stats_interval then
+if stats_interval_config and not stats_interval and stats_interval_config ~= "manual" then
log("error", "Invalid 'statistics_interval' setting, statistics will be disabled");
end
@@ -19,6 +21,9 @@ if not stats_provider and stats_interval then
elseif stats_provider and not stats_interval then
stats_interval = 60;
end
+if stats_interval_config == "manual" then
+ stats_interval = nil;
+end
local builtin_providers = {
internal = "util.statistics";
@@ -54,19 +59,152 @@ if stats == nil then
log("error", "Error loading statistics provider '%s': %s", stats_provider, stats_err);
end
-local measure, collect;
-local latest_stats = {};
-local changed_stats = {};
-local stats_extra = {};
+local measure, collect, metric, cork, uncork;
if stats then
- function measure(type, name)
- local f = assert(stats[type], "unknown stat type: "..type);
- return f(name);
+ function metric(type_, name, unit, description, labels, extra)
+ local registry = stats.metric_registry
+ local f = assert(registry[type_], "unknown metric family type: "..type_);
+ return f(registry, name, unit or "", description or "", labels, extra);
+ end
+
+ local function new_legacy_metric(stat_type, name, unit, description, fixed_label_key, fixed_label_value, extra)
+ local label_keys = array()
+ local conf = extra or {}
+ if fixed_label_key then
+ label_keys:push(fixed_label_key)
+ end
+ unit = unit or ""
+ local mf = metric(stat_type, "prosody_" .. name, unit, description, label_keys, conf);
+ if fixed_label_key then
+ mf = mf:with_partial_label(fixed_label_value)
+ end
+ return mf:with_labels()
+ end
+
+ local function unwrap_legacy_extra(extra, type_, name, unit)
+ local description = extra and extra.description or "Legacy "..type_.." metric "..name
+ unit = extra and extra.unit or unit
+ return description, unit
end
- if stats_interval then
- log("debug", "Statistics enabled using %s provider, collecting every %d seconds", stats_provider_name, stats_interval);
+ -- These wrappers provide the pre-OpenMetrics interface of statsmanager
+ -- and moduleapi (module:measure).
+ local legacy_metric_wrappers = {
+ amount = function(name, fixed_label_key, fixed_label_value, extra)
+ local initial = 0
+ if type(extra) == "number" then
+ initial = extra
+ else
+ initial = extra and extra.initial or initial
+ end
+ local description, unit = unwrap_legacy_extra(extra, "amount", name)
+
+ local m = new_legacy_metric("gauge", name, unit, description, fixed_label_key, fixed_label_value)
+ m:set(initial or 0)
+ return function(v)
+ m:set(v)
+ end
+ end;
+
+ counter = function(name, fixed_label_key, fixed_label_value, extra)
+ if type(extra) == "number" then
+ -- previous versions of the API allowed passing an initial
+ -- value here; we do not allow that anymore, it is not a thing
+ -- which makes sense with counters
+ extra = nil
+ end
+
+ local description, unit = unwrap_legacy_extra(extra, "counter", name)
+
+ local m = new_legacy_metric("counter", name, unit, description, fixed_label_key, fixed_label_value)
+ m:set(0)
+ return function(v)
+ m:add(v)
+ end
+ end;
+
+ rate = function(name, fixed_label_key, fixed_label_value, extra)
+ if type(extra) == "number" then
+ -- previous versions of the API allowed passing an initial
+ -- value here; we do not allow that anymore, it is not a thing
+ -- which makes sense with counters
+ extra = nil
+ end
+
+ local description, unit = unwrap_legacy_extra(extra, "counter", name)
+
+ local m = new_legacy_metric("counter", name, unit, description, fixed_label_key, fixed_label_value)
+ m:set(0)
+ return function()
+ m:add(1)
+ end
+ end;
+
+ times = function(name, fixed_label_key, fixed_label_value, extra)
+ local conf = {}
+ if extra and extra.buckets then
+ conf.buckets = extra.buckets
+ else
+ conf.buckets = { 0.001, 0.01, 0.1, 1.0, 10.0, 100.0 }
+ end
+ local description, _ = unwrap_legacy_extra(extra, "times", name)
+
+ local m = new_legacy_metric("histogram", name, "seconds", description, fixed_label_key, fixed_label_value, conf)
+ return function()
+ return timed(m)
+ end
+ end;
+
+ sizes = function(name, fixed_label_key, fixed_label_value, extra)
+ local conf = {}
+ if extra and extra.buckets then
+ conf.buckets = extra.buckets
+ else
+ conf.buckets = { 1024, 4096, 32768, 131072, 1048576, 4194304, 33554432, 134217728, 1073741824 }
+ end
+ local description, _ = unwrap_legacy_extra(extra, "sizes", name)
+
+ local m = new_legacy_metric("histogram", name, "bytes", description, fixed_label_key, fixed_label_value, conf)
+ return function(v)
+ m:sample(v)
+ end
+ end;
+
+ distribution = function(name, fixed_label_key, fixed_label_value, extra)
+ if type(extra) == "string" then
+ -- compat with previous API
+ extra = { unit = extra }
+ end
+ local description, unit = unwrap_legacy_extra(extra, "distribution", name, "")
+ local m = new_legacy_metric("summary", name, unit, description, fixed_label_key, fixed_label_value)
+ return function(v)
+ m:sample(v)
+ end
+ end;
+ };
+
+ -- Argument order switched here to support the legacy statsmanager.measure
+ -- interface.
+ function measure(stat_type, name, extra, fixed_label_key, fixed_label_value)
+ local wrapper = assert(legacy_metric_wrappers[stat_type], "unknown legacy metric type "..stat_type)
+ return wrapper(name, fixed_label_key, fixed_label_value, extra)
+ end
+
+ if stats.cork then
+ function cork()
+ return stats:cork()
+ end
+
+ function uncork()
+ return stats:uncork()
+ end
+ else
+ function cork() end
+ function uncork() end
+ end
+
+ if stats_interval or stats_interval_config == "manual" then
local mark_collection_start = measure("times", "stats.collection");
local mark_processing_start = measure("times", "stats.processing");
@@ -74,44 +212,68 @@ if stats then
function collect()
local mark_collection_done = mark_collection_start();
fire_event("stats-update");
+ -- ensure that the backend is uncorked, in case it got stuck at
+ -- some point, to avoid infinite resource use
+ uncork()
mark_collection_done();
+ local manual_result = nil
- if stats.get_stats then
- changed_stats, stats_extra = {}, {};
- for stat_name, getter in pairs(stats.get_stats()) do
- local type, value, extra = getter();
- local old_value = latest_stats[stat_name];
- latest_stats[stat_name] = value;
- if value ~= old_value then
- changed_stats[stat_name] = value;
- end
- if extra then
- stats_extra[stat_name] = extra;
- end
- end
+ if stats.metric_registry then
+ -- only if supported by the backend, we fire the event which
+ -- provides the current metric values
local mark_processing_done = mark_processing_start();
- fire_event("stats-updated", { stats = latest_stats, changed_stats = changed_stats, stats_extra = stats_extra });
+ local metric_registry = stats.metric_registry;
+ fire_event("openmetrics-updated", { metric_registry = metric_registry })
mark_processing_done();
+ manual_result = metric_registry;
end
- return stats_interval;
+
+ return stats_interval, manual_result;
+ end
+ if stats_interval then
+ log("debug", "Statistics enabled using %s provider, collecting every %d seconds", stats_provider_name, stats_interval);
+ timer.add_task(stats_interval, collect);
+ prosody.events.add_handler("server-started", function () collect() end, -1);
+ prosody.events.add_handler("server-stopped", function () collect() end, -1);
+ else
+ log("debug", "Statistics enabled using %s provider, no scheduled collection", stats_provider_name);
end
- timer.add_task(stats_interval, collect);
- prosody.events.add_handler("server-started", function () collect() end, -1);
else
log("debug", "Statistics enabled using %s provider, collection is disabled", stats_provider_name);
end
else
log("debug", "Statistics disabled");
function measure() return measure; end
+
+ local dummy_mt = {}
+ function dummy_mt.__newindex()
+ end
+ function dummy_mt:__index()
+ return self
+ end
+ function dummy_mt:__call()
+ return self
+ end
+ local dummy = {}
+ setmetatable(dummy, dummy_mt)
+
+ function metric() return dummy; end
+ function cork() end
+ function uncork() end
end
+local exported_collect = nil;
+if stats_interval_config == "manual" then
+ exported_collect = collect;
+end
return {
+ collect = exported_collect;
measure = measure;
- get_stats = function ()
- return latest_stats, changed_stats, stats_extra;
- end;
- get = function (name)
- return latest_stats[name], stats_extra[name];
+ cork = cork;
+ uncork = uncork;
+ metric = metric;
+ get_metric_registry = function ()
+ return stats and stats.metric_registry or nil
end;
};