mirror of
https://github.com/bjc/prosody.git
synced 2025-04-01 20:27:39 +03:00
279 lines
8.6 KiB
Lua
279 lines
8.6 KiB
Lua
|
|
local config = require "prosody.core.configmanager";
|
|
local log = require "prosody.util.logger".init("stats");
|
|
local timer = require "prosody.util.timer";
|
|
local fire_event = prosody.events.fire_event;
|
|
local array = require "prosody.util.array";
|
|
local timed = require "prosody.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 and stats_interval_config ~= "manual" then
|
|
log("error", "Invalid 'statistics_interval' setting, statistics will be disabled");
|
|
end
|
|
|
|
local stats_provider_name;
|
|
local stats_provider_config = config.get("*", "statistics");
|
|
local stats_provider = stats_provider_config;
|
|
|
|
if not stats_provider and stats_interval then
|
|
stats_provider = "internal";
|
|
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 = "prosody.util.statistics";
|
|
statsd = "prosody.util.statsd";
|
|
};
|
|
|
|
|
|
local stats, stats_err = false, nil;
|
|
|
|
if stats_provider then
|
|
if stats_provider:sub(1,1) == ":" then
|
|
stats_provider = stats_provider:sub(2);
|
|
stats_provider_name = "external "..stats_provider;
|
|
elseif stats_provider then
|
|
stats_provider_name = "built-in "..stats_provider;
|
|
stats_provider = builtin_providers[stats_provider];
|
|
if not stats_provider then
|
|
log("error", "Unrecognized statistics provider '%s', statistics will be disabled", stats_provider_config);
|
|
end
|
|
end
|
|
|
|
local have_stats_provider, stats_lib = pcall(require, stats_provider);
|
|
if not have_stats_provider then
|
|
stats, stats_err = nil, stats_lib;
|
|
else
|
|
local stats_config = config.get("*", "statistics_config");
|
|
stats, stats_err = stats_lib.new(stats_config);
|
|
stats_provider_name = stats_lib._NAME or stats_provider_name;
|
|
end
|
|
end
|
|
|
|
if stats == nil then
|
|
log("error", "Error loading statistics provider '%s': %s", stats_provider, stats_err);
|
|
end
|
|
|
|
local measure, collect, metric, cork, uncork;
|
|
|
|
if stats then
|
|
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 name.." "..type_
|
|
unit = extra and extra.unit or unit
|
|
return description, unit
|
|
end
|
|
|
|
-- 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");
|
|
|
|
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.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();
|
|
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, 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
|
|
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;
|
|
cork = cork;
|
|
uncork = uncork;
|
|
metric = metric;
|
|
get_metric_registry = function ()
|
|
return stats and stats.metric_registry or nil
|
|
end;
|
|
};
|