core/statsmanager.lua
changeset 11527 5f15ab7c6ae5
parent 11519 10d13e0554f9
child 11579 76d32b2ca5eb
equal deleted inserted replaced
11526:5bd38d9197e1 11527:5f15ab7c6ae5
     1 
     1 
     2 local config = require "core.configmanager";
     2 local config = require "core.configmanager";
     3 local log = require "util.logger".init("stats");
     3 local log = require "util.logger".init("stats");
     4 local timer = require "util.timer";
     4 local timer = require "util.timer";
     5 local fire_event = prosody.events.fire_event;
     5 local fire_event = prosody.events.fire_event;
       
     6 local array = require "util.array";
       
     7 local timed = require "util.openmetrics".timed;
     6 
     8 
     7 local stats_interval_config = config.get("*", "statistics_interval");
     9 local stats_interval_config = config.get("*", "statistics_interval");
     8 local stats_interval = tonumber(stats_interval_config);
    10 local stats_interval = tonumber(stats_interval_config);
     9 if stats_interval_config and not stats_interval and stats_interval_config ~= "manual" then
    11 if stats_interval_config and not stats_interval and stats_interval_config ~= "manual" then
    10 	log("error", "Invalid 'statistics_interval' setting, statistics will be disabled");
    12 	log("error", "Invalid 'statistics_interval' setting, statistics will be disabled");
    55 
    57 
    56 if stats == nil then
    58 if stats == nil then
    57 	log("error", "Error loading statistics provider '%s': %s", stats_provider, stats_err);
    59 	log("error", "Error loading statistics provider '%s': %s", stats_provider, stats_err);
    58 end
    60 end
    59 
    61 
    60 local measure, collect;
    62 local measure, collect, metric, cork, uncork;
    61 local latest_stats = {};
       
    62 local changed_stats = {};
       
    63 local stats_extra = {};
       
    64 
    63 
    65 if stats then
    64 if stats then
    66 	function measure(type, name, conf)
    65 	function metric(type_, name, unit, description, labels, extra)
    67 		local f = assert(stats[type], "unknown stat type: "..type);
    66 		local registry = stats.metric_registry
    68 		return f(name, conf);
    67 		local f = assert(registry[type_], "unknown metric family type: "..type_);
       
    68 		return f(registry, name, unit or "", description or "", labels, extra);
       
    69 	end
       
    70 
       
    71 	local function new_legacy_metric(stat_type, name, unit, description, fixed_label_key, fixed_label_value, extra)
       
    72 		local label_keys = array()
       
    73 		local conf = extra or {}
       
    74 		if fixed_label_key then
       
    75 			label_keys:push(fixed_label_key)
       
    76 		end
       
    77 		unit = unit or ""
       
    78 		local mf = metric(stat_type, "prosody_" .. name, unit, description, label_keys, conf);
       
    79 		if fixed_label_key then
       
    80 			mf = mf:with_partial_label(fixed_label_value)
       
    81 		end
       
    82 		return mf:with_labels()
       
    83 	end
       
    84 
       
    85 	local function unwrap_legacy_extra(extra, type_, name, unit)
       
    86 		local description = extra and extra.description or "Legacy "..type_.." metric "..name
       
    87 		unit = extra and extra.unit or unit
       
    88 		return description, unit
       
    89 	end
       
    90 
       
    91 	-- These wrappers provide the pre-OpenMetrics interface of statsmanager
       
    92 	-- and moduleapi (module:measure).
       
    93 	local legacy_metric_wrappers = {
       
    94 		amount = function(name, fixed_label_key, fixed_label_value, extra)
       
    95 			local initial = 0
       
    96 			if type(extra) == "number" then
       
    97 				initial = extra
       
    98 			else
       
    99 				initial = extra and extra.initial or initial
       
   100 			end
       
   101 			local description, unit = unwrap_legacy_extra(extra, "amount", name)
       
   102 
       
   103 			local m = new_legacy_metric("gauge", name, unit, description, fixed_label_key, fixed_label_value)
       
   104 			m:set(initial or 0)
       
   105 			return function(v)
       
   106 				m:set(v)
       
   107 			end
       
   108 		end;
       
   109 
       
   110 		counter = function(name, fixed_label_key, fixed_label_value, extra)
       
   111 			if type(extra) == "number" then
       
   112 				-- previous versions of the API allowed passing an initial
       
   113 				-- value here; we do not allow that anymore, it is not a thing
       
   114 				-- which makes sense with counters
       
   115 				extra = nil
       
   116 			end
       
   117 
       
   118 			local description, unit = unwrap_legacy_extra(extra, "counter", name)
       
   119 
       
   120 			local m = new_legacy_metric("counter", name, unit, description, fixed_label_key, fixed_label_value)
       
   121 			m:set(0)
       
   122 			return function(v)
       
   123 				m:add(v)
       
   124 			end
       
   125 		end;
       
   126 
       
   127 		rate = function(name, fixed_label_key, fixed_label_value, extra)
       
   128 			if type(extra) == "number" then
       
   129 				-- previous versions of the API allowed passing an initial
       
   130 				-- value here; we do not allow that anymore, it is not a thing
       
   131 				-- which makes sense with counters
       
   132 				extra = nil
       
   133 			end
       
   134 
       
   135 			local description, unit = unwrap_legacy_extra(extra, "counter", name)
       
   136 
       
   137 			local m = new_legacy_metric("counter", name, unit, description, fixed_label_key, fixed_label_value)
       
   138 			m:set(0)
       
   139 			return function()
       
   140 				m:add(1)
       
   141 			end
       
   142 		end;
       
   143 
       
   144 		times = function(name, fixed_label_key, fixed_label_value, extra)
       
   145 			local conf = {}
       
   146 			if extra and extra.buckets then
       
   147 				conf.buckets = extra.buckets
       
   148 			else
       
   149 				conf.buckets = { 0.001, 0.01, 0.1, 1.0, 10.0, 100.0 }
       
   150 			end
       
   151 			local description, _ = unwrap_legacy_extra(extra, "times", name)
       
   152 
       
   153 			local m = new_legacy_metric("histogram", name, "seconds", description, fixed_label_key, fixed_label_value, conf)
       
   154 			return function()
       
   155 				return timed(m)
       
   156 			end
       
   157 		end;
       
   158 
       
   159 		sizes = function(name, fixed_label_key, fixed_label_value, extra)
       
   160 			local conf = {}
       
   161 			if extra and extra.buckets then
       
   162 				conf.buckets = extra.buckets
       
   163 			else
       
   164 				conf.buckets = { 1024, 4096, 32768, 131072, 1048576, 4194304, 33554432, 134217728, 1073741824 }
       
   165 			end
       
   166 			local description, _ = unwrap_legacy_extra(extra, "sizes", name)
       
   167 
       
   168 			local m = new_legacy_metric("histogram", name, "bytes", description, fixed_label_key, fixed_label_value, conf)
       
   169 			return function(v)
       
   170 				m:sample(v)
       
   171 			end
       
   172 		end;
       
   173 
       
   174 		distribution = function(name, fixed_label_key, fixed_label_value, extra)
       
   175 			if type(extra) == "string" then
       
   176 				-- compat with previous API
       
   177 				extra = { unit = extra }
       
   178 			end
       
   179 			local description, unit = unwrap_legacy_extra(extra, "distribution", name, "")
       
   180 			local m = new_legacy_metric("summary", name, unit, description, fixed_label_key, fixed_label_value)
       
   181 			return function(v)
       
   182 				m:sample(v)
       
   183 			end
       
   184 		end;
       
   185 	};
       
   186 
       
   187 	-- Argument order switched here to support the legacy statsmanager.measure
       
   188 	-- interface.
       
   189 	function measure(stat_type, name, extra, fixed_label_key, fixed_label_value)
       
   190 		local wrapper = assert(legacy_metric_wrappers[stat_type], "unknown legacy metric type "..stat_type)
       
   191 		return wrapper(name, fixed_label_key, fixed_label_value, extra)
       
   192 	end
       
   193 
       
   194 	if stats.cork then
       
   195 		function cork()
       
   196 			return stats:cork()
       
   197 		end
       
   198 
       
   199 		function uncork()
       
   200 			return stats:uncork()
       
   201 		end
       
   202 	else
       
   203 		function cork() end
       
   204 		function uncork() end
    69 	end
   205 	end
    70 
   206 
    71 	if stats_interval or stats_interval_config == "manual" then
   207 	if stats_interval or stats_interval_config == "manual" then
    72 
   208 
    73 		local mark_collection_start = measure("times", "stats.collection");
   209 		local mark_collection_start = measure("times", "stats.collection");
    74 		local mark_processing_start = measure("times", "stats.processing");
   210 		local mark_processing_start = measure("times", "stats.processing");
    75 
   211 
    76 		function collect()
   212 		function collect()
    77 			local mark_collection_done = mark_collection_start();
   213 			local mark_collection_done = mark_collection_start();
    78 			fire_event("stats-update");
   214 			fire_event("stats-update");
       
   215 			-- ensure that the backend is uncorked, in case it got stuck at
       
   216 			-- some point, to avoid infinite resource use
       
   217 			uncork()
    79 			mark_collection_done();
   218 			mark_collection_done();
    80 
   219 			local manual_result = nil
    81 			if stats.get_stats then
   220 
       
   221 			if stats.metric_registry then
       
   222 				-- only if supported by the backend, we fire the event which
       
   223 				-- provides the current metric values
    82 				local mark_processing_done = mark_processing_start();
   224 				local mark_processing_done = mark_processing_start();
    83 				changed_stats, stats_extra = {}, {};
   225 				local metric_registry = stats.metric_registry;
    84 				for stat_name, getter in pairs(stats.get_stats()) do
   226 				fire_event("openmetrics-updated", { metric_registry = metric_registry })
    85 					-- luacheck: ignore 211/type
       
    86 					local type, value, extra = getter();
       
    87 					local old_value = latest_stats[stat_name];
       
    88 					latest_stats[stat_name] = value;
       
    89 					if value ~= old_value then
       
    90 						changed_stats[stat_name] = value;
       
    91 					end
       
    92 					if extra then
       
    93 						stats_extra[stat_name] = extra;
       
    94 					end
       
    95 				end
       
    96 				fire_event("stats-updated", { stats = latest_stats, changed_stats = changed_stats, stats_extra = stats_extra });
       
    97 				mark_processing_done();
   227 				mark_processing_done();
    98 			end
   228 				manual_result = metric_registry;
    99 			return stats_interval;
   229 			end
       
   230 
       
   231 			return stats_interval, manual_result;
   100 		end
   232 		end
   101 		if stats_interval then
   233 		if stats_interval then
   102 			log("debug", "Statistics enabled using %s provider, collecting every %d seconds", stats_provider_name, stats_interval);
   234 			log("debug", "Statistics enabled using %s provider, collecting every %d seconds", stats_provider_name, stats_interval);
   103 			timer.add_task(stats_interval, collect);
   235 			timer.add_task(stats_interval, collect);
   104 			prosody.events.add_handler("server-started", function () collect() end, -1);
   236 			prosody.events.add_handler("server-started", function () collect() end, -1);
   110 		log("debug", "Statistics enabled using %s provider, collection is disabled", stats_provider_name);
   242 		log("debug", "Statistics enabled using %s provider, collection is disabled", stats_provider_name);
   111 	end
   243 	end
   112 else
   244 else
   113 	log("debug", "Statistics disabled");
   245 	log("debug", "Statistics disabled");
   114 	function measure() return measure; end
   246 	function measure() return measure; end
       
   247 
       
   248 	local dummy_mt = {}
       
   249 	function dummy_mt.__newindex()
       
   250 	end
       
   251 	function dummy_mt:__index()
       
   252 		return self
       
   253 	end
       
   254 	function dummy_mt:__call()
       
   255 		return self
       
   256 	end
       
   257 	local dummy = {}
       
   258 	setmetatable(dummy, dummy_mt)
       
   259 
       
   260 	function metric() return dummy; end
       
   261 	function cork() end
       
   262 	function uncork() end
   115 end
   263 end
   116 
   264 
   117 local exported_collect = nil;
   265 local exported_collect = nil;
   118 if stats_interval_config == "manual" then
   266 if stats_interval_config == "manual" then
   119 	exported_collect = collect;
   267 	exported_collect = collect;
   120 end
   268 end
   121 
   269 
   122 return {
   270 return {
   123 	collect = exported_collect;
   271 	collect = exported_collect;
   124 	measure = measure;
   272 	measure = measure;
   125 	get_stats = function ()
   273 	cork = cork;
   126 		return latest_stats, changed_stats, stats_extra;
   274 	uncork = uncork;
   127 	end;
   275 	metric = metric;
   128 	get = function (name)
   276 	get_metric_registry = function ()
   129 		return latest_stats[name], stats_extra[name];
   277 		return stats and stats.metric_registry or nil
   130 	end;
   278 	end;
   131 };
   279 };