1 |
|
-module(mongoose_system_metrics_collector). |
2 |
|
|
3 |
|
-include("mongoose.hrl"). |
4 |
|
|
5 |
|
-type report_struct() :: |
6 |
|
#{ |
7 |
|
report_name := term(), |
8 |
|
key := term(), |
9 |
|
value := term() |
10 |
|
}. |
11 |
|
|
12 |
|
-export_type([report_struct/0]). |
13 |
|
|
14 |
|
-export([collect/1]). |
15 |
|
|
16 |
|
collect(PrevReport) -> |
17 |
33 |
ReportResults = [ get_reports(RGetter) || RGetter <- report_getters()], |
18 |
33 |
StanzasCount = get_xmpp_stanzas_count(PrevReport), |
19 |
33 |
lists:flatten(ReportResults ++ StanzasCount). |
20 |
|
|
21 |
|
-spec get_reports(fun(() -> [report_struct()])) -> [report_struct()]. |
22 |
|
get_reports(Fun) -> |
23 |
429 |
Fun(). |
24 |
|
|
25 |
|
-spec report_getters() -> [fun(() -> [report_struct()])]. |
26 |
|
report_getters() -> |
27 |
33 |
[ |
28 |
|
fun get_hosts_count/0, |
29 |
|
fun get_domains_count/0, |
30 |
|
fun get_modules/0, |
31 |
|
fun get_number_of_custom_modules/0, |
32 |
|
fun get_uptime/0, |
33 |
|
fun get_cluster_size/0, |
34 |
|
fun get_version/0, |
35 |
|
fun get_components/0, |
36 |
|
fun get_api/0, |
37 |
|
fun get_transport_mechanisms/0, |
38 |
|
fun get_tls_options/0, |
39 |
|
fun get_outgoing_pools/0, |
40 |
|
fun get_config_type/0 |
41 |
|
]. |
42 |
|
|
43 |
|
get_hosts_count() -> |
44 |
33 |
HostTypes = ?ALL_HOST_TYPES, |
45 |
33 |
NumberOfHosts = length(HostTypes), |
46 |
33 |
[#{report_name => hosts, key => count, value => NumberOfHosts}]. |
47 |
|
|
48 |
|
get_domains_count() -> |
49 |
33 |
DomainsCount = mongoose_domain_core:domains_count(), |
50 |
33 |
[#{report_name => domains, key => count, value => DomainsCount}]. |
51 |
|
|
52 |
|
get_modules() -> |
53 |
33 |
HostTypes = ?ALL_HOST_TYPES, |
54 |
33 |
AllModules = lists:flatten([gen_mod:loaded_modules(H) || H <- HostTypes]), |
55 |
33 |
ModulesToReport = filter_behaviour_implementations(lists:usort(AllModules), |
56 |
|
mongoose_module_metrics), |
57 |
33 |
ModsWithOpts = [get_modules_metrics(Host, ModulesToReport) || Host <- HostTypes], |
58 |
33 |
[report_module_with_opts(Mod, Opt) || {Mod, Opt} <- lists:flatten(ModsWithOpts)]. |
59 |
|
|
60 |
|
filter_behaviour_implementations(Modules, Behaviour) -> |
61 |
99 |
lists:filter( |
62 |
|
fun(M) -> |
63 |
4095 |
try lists:keyfind([Behaviour], 2, M:module_info(attributes)) of |
64 |
116 |
{behavior, _} -> true; |
65 |
3834 |
{behaviour, _} -> true; |
66 |
145 |
_ -> false |
67 |
|
catch |
68 |
:-( |
_:_ -> false |
69 |
|
end |
70 |
|
end, Modules). |
71 |
|
|
72 |
|
get_modules_metrics(Host, Modules) -> |
73 |
188 |
lists:map( |
74 |
|
fun(M) -> |
75 |
2645 |
case erlang:function_exported(M, config_metrics, 1) of |
76 |
779 |
true -> {M, M:config_metrics(Host)}; |
77 |
1866 |
false -> {M, [{none, none}]} |
78 |
|
end |
79 |
|
end, Modules). |
80 |
|
|
81 |
|
report_module_with_opts(Module, Opts) -> |
82 |
2645 |
lists:map( |
83 |
|
fun({OptKey, OptValue}) -> |
84 |
2645 |
#{report_name => Module, key => OptKey, value => OptValue} |
85 |
|
end,Opts). |
86 |
|
|
87 |
|
get_number_of_custom_modules() -> |
88 |
33 |
HostTypes = ?ALL_HOST_TYPES, |
89 |
33 |
AllModules = lists:flatten( |
90 |
|
lists:map(fun gen_mod:loaded_modules/1, HostTypes)), |
91 |
33 |
GenMods = filter_behaviour_implementations(AllModules, gen_mod), |
92 |
33 |
GenModsSet = sets:from_list(GenMods), |
93 |
33 |
MetricsModule = filter_behaviour_implementations(AllModules, |
94 |
|
mongoose_module_metrics), |
95 |
33 |
MetricsModuleSet = sets:from_list(MetricsModule), |
96 |
33 |
CountCustomMods= sets:size(sets:subtract(GenModsSet, MetricsModuleSet)), |
97 |
33 |
#{report_name => custom_modules, key => count, value => CountCustomMods}. |
98 |
|
|
99 |
|
get_uptime() -> |
100 |
33 |
{Uptime, _} = statistics(wall_clock), |
101 |
33 |
UptimeSeconds = Uptime div 1000, |
102 |
33 |
{D, {H, M, S}} = calendar:seconds_to_daystime(UptimeSeconds), |
103 |
33 |
Formatted = io_lib:format("~4..0B-~2..0B:~2..0B:~2..0B", [D,H,M,S]), |
104 |
33 |
[#{report_name => cluster, key => uptime, value => list_to_binary(Formatted)}]. |
105 |
|
|
106 |
|
get_cluster_size() -> |
107 |
33 |
NodesNo = length(nodes()) + 1, |
108 |
33 |
[#{report_name => cluster, key => number_of_nodes, value => NodesNo}]. |
109 |
|
|
110 |
|
get_version() -> |
111 |
33 |
case lists:keyfind(mongooseim, 1, application:which_applications()) of |
112 |
|
{_, _, Version} -> |
113 |
33 |
#{report_name => cluster, key => mim_version, value => list_to_binary(Version)}; |
114 |
|
_ -> |
115 |
:-( |
[] |
116 |
|
end. |
117 |
|
|
118 |
|
get_components() -> |
119 |
33 |
Domains = mongoose_router:get_all_domains() ++ ejabberd_router:dirty_get_all_components(all), |
120 |
33 |
Components = [ejabberd_router:lookup_component(D, node()) || D <- Domains], |
121 |
33 |
LenComponents = length(lists:flatten(Components)), |
122 |
33 |
#{report_name => cluster, key => number_of_components, value => LenComponents}. |
123 |
|
|
124 |
|
get_api() -> |
125 |
33 |
ApiList = filter_unknown_api(get_http_handler_modules()), |
126 |
33 |
[#{report_name => http_api, key => Api, value => enabled} || Api <- ApiList]. |
127 |
|
|
128 |
|
filter_unknown_api(ApiList) -> |
129 |
33 |
AllowedToReport = [ mongoose_api, mongoose_client_api_rooms_messages, |
130 |
|
mongoose_client_api_rooms_users, mongoose_client_api_rooms_config, |
131 |
|
mongoose_client_api_rooms, mongoose_client_api_contacts, |
132 |
|
mongoose_client_api_messages, lasse_handler, mongoose_api_admin, |
133 |
|
mod_bosh, mod_websockets], |
134 |
33 |
[Api || Api <- ApiList, lists:member(Api, AllowedToReport)]. |
135 |
|
|
136 |
|
get_transport_mechanisms() -> |
137 |
33 |
HTTP = [Mod || Mod <- get_http_handler_modules(), |
138 |
528 |
Mod =:= mod_bosh orelse Mod =:= mod_websockets], |
139 |
33 |
TCP = lists:usort([tcp || #{proto := tcp} <- get_listeners(ejabberd_c2s)]), |
140 |
33 |
[#{report_name => transport_mechanism, |
141 |
|
key => Transport, |
142 |
33 |
value => enabled} || Transport <- HTTP ++ TCP]. |
143 |
|
|
144 |
|
get_http_handler_modules() -> |
145 |
66 |
Listeners = get_listeners(ejabberd_cowboy), |
146 |
66 |
Modules = lists:flatten([Modules || #{handlers := Modules} <- Listeners]), |
147 |
|
% Modules Option can have variable number of elements. To be more |
148 |
|
% error-proof, extracting 3rd element instead of pattern matching. |
149 |
66 |
lists:usort(lists:map(fun(Module) -> element(3, Module) end, Modules)). |
150 |
|
|
151 |
|
get_listeners(Module) -> |
152 |
132 |
Listeners = mongoose_config:get_opt(listen), |
153 |
132 |
lists:filter(fun(#{module := Mod}) -> Mod =:= Module end, Listeners). |
154 |
|
|
155 |
|
get_tls_options() -> |
156 |
33 |
TLSOptions = lists:flatmap(fun extract_tls_options/1, get_listeners(ejabberd_c2s)), |
157 |
33 |
[#{report_name => tls_option, key => TLSMode, value => TLSModule} || |
158 |
33 |
{TLSMode, TLSModule} <- lists:usort(TLSOptions)]. |
159 |
|
|
160 |
|
extract_tls_options(#{tls := Opts}) -> |
161 |
36 |
Modes = [starttls, starttls_required, tls], |
162 |
36 |
case [Opt || Opt <- Opts, lists:member(Opt, Modes)] of |
163 |
|
[TLSMode] -> |
164 |
36 |
TLSModule = proplists:get_value(tls_module, Opts, fast_tls), |
165 |
36 |
[{TLSMode, TLSModule}]; |
166 |
|
_ -> |
167 |
:-( |
[] |
168 |
|
end; |
169 |
29 |
extract_tls_options(_) -> []. |
170 |
|
|
171 |
|
get_outgoing_pools() -> |
172 |
33 |
OutgoingPools = mongoose_config:get_opt(outgoing_pools, []), |
173 |
33 |
[#{report_name => outgoing_pools, |
174 |
|
key => type, |
175 |
33 |
value => Type} || #{type := Type} <- OutgoingPools]. |
176 |
|
|
177 |
|
get_xmpp_stanzas_count(PrevReport) -> |
178 |
33 |
StanzaTypes = [xmppMessageSent, xmppMessageReceived, xmppIqSent, |
179 |
|
xmppIqReceived, xmppPresenceSent, xmppPresenceReceived], |
180 |
33 |
NewCount = [count_stanzas(StanzaType) || StanzaType <- StanzaTypes], |
181 |
33 |
StanzasCount = calculate_stanza_rate(PrevReport, NewCount), |
182 |
33 |
[#{report_name => StanzaType, |
183 |
|
key => Total, |
184 |
33 |
value => Increment} || {StanzaType, Total, Increment} <- StanzasCount]. |
185 |
|
|
186 |
|
count_stanzas(StanzaType) -> |
187 |
198 |
ExometerResults = exometer:get_values(['_', StanzaType]), |
188 |
198 |
StanzaCount = lists:foldl(fun({ _, [{count,Count}, {one, _}]}, Sum) -> |
189 |
1080 |
Count + Sum end, 0, ExometerResults), |
190 |
198 |
{StanzaType, StanzaCount}. |
191 |
|
|
192 |
|
calculate_stanza_rate([], NewCount) -> |
193 |
19 |
[{Type, Count, Count} || {Type, Count} <- NewCount]; |
194 |
|
calculate_stanza_rate(PrevReport, NewCount) -> |
195 |
14 |
ReportProplist = [{Name, Key} || |
196 |
14 |
#{report_name := Name, key := Key} <- PrevReport], |
197 |
14 |
[{Type, Count, |
198 |
|
case proplists:get_value(Type, ReportProplist) of |
199 |
:-( |
undefined -> Count; |
200 |
84 |
Total -> Count-Total |
201 |
14 |
end} || {Type, Count} <- NewCount]. |
202 |
|
|
203 |
|
get_config_type() -> |
204 |
33 |
ConfigPath = mongoose_config:get_config_path(), |
205 |
33 |
ConfigType = case filename:extension(ConfigPath) of |
206 |
33 |
".toml" -> toml; |
207 |
:-( |
".cfg" -> cfg; |
208 |
:-( |
_ -> unknown_config_type |
209 |
|
end, |
210 |
33 |
[#{report_name => cluster, key => config_type, value => ConfigType}]. |