Software /
code /
prosody
File
core/hostmanager.lua @ 11523:5f15ab7c6ae5
Statistics: Rewrite statistics backends to use OpenMetrics
The metric subsystem of Prosody has had some shortcomings from
the perspective of the current state-of-the-art in metric
observability.
The OpenMetrics standard [0] is a formalization of the data
model (and serialization format) of the well-known and
widely-used Prometheus [1] software stack.
The previous stats subsystem of Prosody did not map well to that
format (see e.g. [2] and [3]); the key reason is that it was
trying to do too much math on its own ([2]) while lacking
first-class support for "families" of metrics ([3]) and
structured metric metadata (despite the `extra` argument to
metrics, there was no standard way of representing common things
like "tags" or "labels").
Even though OpenMetrics has grown from the Prometheus world of
monitoring, it maps well to other popular monitoring stacks
such as:
- InfluxDB (labels can be mapped to tags and fields as necessary)
- Carbon/Graphite (labels can be attached to the metric name with
dot-separation)
- StatsD (see graphite when assuming that graphite is used as
backend, which is the default)
The util.statsd module has been ported to use the OpenMetrics
model as a proof of concept. An implementation which exposes
the util.statistics backend data as Prometheus metrics is
ready for publishing in prosody-modules (most likely as
mod_openmetrics_prometheus to avoid breaking existing 0.11
deployments).
At the same time, the previous measure()-based API had one major
advantage: It is really simple and easy to use without requiring
lots of knowledge about OpenMetrics or similar concepts. For that
reason as well as compatibility with existing code, it is preserved
and may even be extended in the future.
However, code relying on the `stats-updated` event as well as
`get_stats` from `statsmanager` will break because the data
model has changed completely; in case of `stats-updated`, the
code will simply not run (as the event was renamed in order
to avoid conflicts); the `get_stats` function has been removed
completely (so it will cause a traceback when it is attempted
to be used).
Note that the measure_*_event methods have been removed from
the module API. I was unable to find any uses or documentation
and thus deemed they should not be ported. Re-implementation is
possible when necessary.
[0]: https://openmetrics.io/
[1]: https://prometheus.io/
[2]: #959
[3]: #960
author | Jonas Schäfer <jonas@wielicki.name> |
---|---|
date | Sun, 18 Apr 2021 11:47:41 +0200 |
parent | 11505:61d38c4b749d |
child | 12972:ead41e25ebc0 |
line wrap: on
line source
-- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local configmanager = require "core.configmanager"; local modulemanager = require "core.modulemanager"; local events_new = require "util.events".new; local disco_items = require "util.multitable".new(); local NULL = {}; local log = require "util.logger".init("hostmanager"); local hosts = prosody.hosts; local prosody_events = prosody.events; if not _G.prosody.incoming_s2s then require "core.s2smanager"; end local incoming_s2s = _G.prosody.incoming_s2s; local core_route_stanza = _G.prosody.core_route_stanza; local pairs, rawget = pairs, rawget; local tostring, type = tostring, type; local setmetatable = setmetatable; local _ENV = nil; -- luacheck: std none local host_mt = { } function host_mt:__tostring() if self.type == "component" then local typ = configmanager.get(self.host, "component_module"); if typ == "component" then return ("Component %q"):format(self.host); end return ("Component %q %q"):format(self.host, typ); elseif self.type == "local" then return ("VirtualHost %q"):format(self.host); end end local hosts_loaded_once; local activate, deactivate; local function load_enabled_hosts(config) local defined_hosts = config or configmanager.getconfig(); local activated_any_host; for host, host_config in pairs(defined_hosts) do if host ~= "*" and host_config.enabled ~= false then if not host_config.component_module then activated_any_host = true; end activate(host, host_config); end end if not activated_any_host then log("error", "No active VirtualHost entries in the config file. This may cause unexpected behaviour as no modules will be loaded."); end prosody_events.fire_event("hosts-activated", defined_hosts); hosts_loaded_once = true; end prosody_events.add_handler("server-starting", load_enabled_hosts); local function host_send(stanza) core_route_stanza(nil, stanza); end function activate(host, host_config) if rawget(hosts, host) then return nil, "The host "..host.." is already activated"; end host_config = host_config or configmanager.getconfig()[host]; if not host_config then return nil, "Couldn't find the host "..tostring(host).." defined in the current config"; end local host_session = { host = host; s2sout = {}; events = events_new(); send = host_send; modules = {}; }; function host_session:close(reason) log("debug", "Attempt to close host session %s with reason: %s", self.host, reason); end setmetatable(host_session, host_mt); if not host_config.component_module then -- host host_session.type = "local"; host_session.sessions = {}; else -- component host_session.type = "component"; end hosts[host] = host_session; if not host_config.disco_hidden and not host:match("[@/]") then disco_items:set(host:match("%.(.*)") or "*", host, host_config.name or true); end for option_name in pairs(host_config) do if option_name:match("_ports$") or option_name:match("_interface$") then log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in the server-wide section instead", host, option_name); end end log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host); prosody_events.fire_event("host-activated", host); return true; end function deactivate(host, reason) local host_session = hosts[host]; if not host_session then return nil, "The host "..tostring(host).." is not activated"; end log("info", "Deactivating host: %s", host); prosody_events.fire_event("host-deactivating", { host = host, host_session = host_session, reason = reason }); if type(reason) ~= "table" then reason = { condition = "host-gone", text = tostring(reason or "This server has stopped serving "..host) }; end -- Disconnect local users, s2s connections -- TODO: These should move to mod_c2s and mod_s2s (how do they know they're being unloaded and not reloaded?) if host_session.sessions then for username, user in pairs(host_session.sessions) do for resource, session in pairs(user.sessions) do log("debug", "Closing connection for %s@%s/%s", username, host, resource); session:close(reason); end end end if host_session.s2sout then for remotehost, session in pairs(host_session.s2sout) do if session.close then log("debug", "Closing outgoing connection to %s", remotehost); session:close(reason); end end end for remote_session in pairs(incoming_s2s) do if remote_session.to_host == host then log("debug", "Closing incoming connection from %s", remote_session.from_host or "<unknown>"); remote_session:close(reason); end end -- TODO: This should be done in modulemanager if host_session.modules then for module in pairs(host_session.modules) do modulemanager.unload(host, module); end end hosts[host] = nil; if not host:match("[@/]") then disco_items:remove(host:match("%.(.*)") or "*", host); end prosody_events.fire_event("host-deactivated", host); log("info", "Deactivated host: %s", host); return true; end local function get_children(host) return disco_items:get(host) or NULL; end return { activate = activate; deactivate = deactivate; get_children = get_children; }