./ct_report/coverage/mongoose_metrics.COVER.html

1 %%==============================================================================
2 %% Copyright 2014 Erlang Solutions Ltd.
3 %%
4 %% Licensed under the Apache License, Version 2.0 (the "License");
5 %% you may not use this file except in compliance with the License.
6 %% You may obtain a copy of the License at
7 %%
8 %% http://www.apache.org/licenses/LICENSE-2.0
9 %%
10 %% Unless required by applicable law or agreed to in writing, software
11 %% distributed under the License is distributed on an "AS IS" BASIS,
12 %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 %% See the License for the specific language governing permissions and
14 %% limitations under the License.
15 %%==============================================================================
16 -module(mongoose_metrics).
17
18 -include("mongoose.hrl").
19 -include("mongoose_metrics_definitions.hrl").
20
21 %% API
22 -export([init/0,
23 create_global_metrics/0,
24 init_predefined_host_type_metrics/1,
25 init_subscriptions/0,
26 make_host_type_name/1,
27 create_generic_hook_metric/2,
28 ensure_db_pool_metric/1,
29 update/3,
30 ensure_metric/3,
31 ensure_subscribed_metric/3,
32 get_metric_value/1,
33 get_metric_values/1,
34 get_metric_value/2,
35 get_host_type_metric_names/1,
36 get_global_metric_names/0,
37 get_aggregated_values/1,
38 increment_generic_hook_metric/2,
39 get_rdbms_data_stats/0,
40 get_rdbms_data_stats/1,
41 get_dist_data_stats/0,
42 get_up_time/0,
43 get_mnesia_running_db_nodes_count/0,
44 remove_host_type_metrics/1,
45 remove_all_metrics/0,
46 get_report_interval/0
47 ]).
48
49 -ignore_xref([create_global_metrics/0, get_dist_data_stats/0, get_mnesia_running_db_nodes_count/0,
50 get_rdbms_data_stats/0, get_rdbms_data_stats/1, get_up_time/0,
51 init_subscriptions/0, make_host_type_name/1, remove_host_type_metrics/1,
52 get_report_interval/0]).
53
54 -define(DEFAULT_REPORT_INTERVAL, 60000). %%60s
55
56 -type use_or_skip() :: use | skip.
57 -type hook_name() :: atom().
58 -type metric_name() :: atom() | list(atom() | binary()).
59 -type short_metric_type() :: spiral | histogram | counter | gauge.
60 -type metric_type() :: tuple() | short_metric_type().
61 -type host_type_or_global() :: mongooseim:host_type() | global.
62
63 %% ---------------------------------------------------------------------
64 %% API
65 %% ---------------------------------------------------------------------
66
67 -spec init() -> ok.
68 init() ->
69 80 create_global_metrics(),
70 80 lists:foreach(
71 fun(HostType) ->
72 409 mongoose_metrics:init_predefined_host_type_metrics(HostType)
73 end, ?ALL_HOST_TYPES),
74 80 init_subscriptions().
75
76 create_global_metrics() ->
77 80 lists:foreach(fun({Metric, FunSpec, DataPoints}) ->
78 160 FunSpecTuple = list_to_tuple(FunSpec ++ [DataPoints]),
79 160 catch ensure_metric(global, Metric, FunSpecTuple)
80 end, ?VM_STATS),
81 80 lists:foreach(fun({Metric, Spec}) -> ensure_metric(global, Metric, Spec) end,
82 ?GLOBAL_COUNTERS),
83 80 create_data_metrics().
84
85 -spec init_predefined_host_type_metrics(mongooseim:host_type()) -> ok.
86 init_predefined_host_type_metrics(HostType) ->
87 409 create_metrics(HostType),
88 409 Hooks = mongoose_metrics_hooks:get_hooks(HostType),
89 409 ejabberd_hooks:add(Hooks),
90 409 ok.
91
92 init_subscriptions() ->
93 80 Reporters = exometer_report:list_reporters(),
94 80 lists:foreach(
95 fun({Name, _ReporterPid}) ->
96
:-(
Interval = get_report_interval(),
97
:-(
subscribe_to_all(Name, Interval)
98 end, Reporters).
99
100 -spec create_generic_hook_metric(mongooseim:host_type(), atom()) ->
101 ok | {ok, already_present} | {error, any()}.
102 create_generic_hook_metric(HostType, Hook) ->
103 15775 UseOrSkip = filter_hook(Hook),
104 15775 do_create_generic_hook_metric(HostType, Hook, UseOrSkip).
105
106 % TODO: change to HostType after mongoose_wpool_rdbms
107 ensure_db_pool_metric({rdbms, Host, Tag} = Name) ->
108
:-(
ensure_metric(Host,
109 [data, rdbms, Tag],
110 {function, mongoose_metrics, get_rdbms_data_stats, [[Name]], proplist,
111 [workers | ?INET_STATS]}).
112
113 -spec update(HostType :: mongooseim:host_type() | global, Name :: term() | list(),
114 Change :: term()) -> any().
115 update(HostType, Name, Change) when is_list(Name) ->
116 268861 exometer:update(name_by_all_metrics_are_global(HostType, Name), Change);
117 update(HostType, Name, Change) ->
118 160940 update(HostType, [Name], Change).
119
120 -spec ensure_metric(host_type_or_global(), metric_name(), metric_type()) ->
121 ok | {ok, already_present} | {error, any()}.
122 ensure_metric(HostType, Metric, Type) when is_tuple(Type) ->
123 845 ensure_metric(HostType, Metric, Type, element(1, Type));
124 ensure_metric(HostType, Metric, Type) ->
125 53813 ensure_metric(HostType, Metric, Type, Type).
126
127 get_metric_value(HostType, Name) when is_list(Name) ->
128 165 get_metric_value(name_by_all_metrics_are_global(HostType, Name));
129 get_metric_value(HostType, Name) ->
130 162 get_metric_value(HostType, [Name]).
131
132 get_metric_value(Metric) ->
133 292 exometer:get_value(Metric).
134
135 get_metric_values(Metric) when is_list(Metric) ->
136 58 exometer:get_values(Metric);
137 get_metric_values(HostType) ->
138 176 exometer:get_values([HostType]).
139
140 get_host_type_metric_names(HostType) ->
141 87 HostTypeName = make_host_type_name(HostType),
142 87 [MetricName || {[_HostTypeName | MetricName], _, _} <- exometer:find_entries([HostTypeName])].
143
144 get_global_metric_names() ->
145 2 get_host_type_metric_names(global).
146
147 get_aggregated_values(Metric) ->
148 6474 exometer:aggregate([{{['_', Metric], '_', '_'}, [], [true]}], [one, count, value]).
149
150 -spec increment_generic_hook_metric(mongooseim:host_type(), atom()) -> ok | {error, any()}.
151 increment_generic_hook_metric(HostType, Hook) ->
152 139942 UseOrSkip = filter_hook(Hook),
153 139942 do_increment_generic_hook_metric(HostType, Hook, UseOrSkip).
154
155 get_rdbms_data_stats() ->
156
:-(
Pools = lists:filter(fun({Type, _Host, _Tag}) -> Type == rdbms end, mongoose_wpool:get_pools()),
157
:-(
get_rdbms_data_stats(Pools).
158
159 get_rdbms_data_stats(Pools) ->
160
:-(
RDBMSWorkers =
161 lists:flatmap(
162 fun({Type, Host, Tag}) ->
163
:-(
PoolName = mongoose_wpool:make_pool_name(Type, Host, Tag),
164
:-(
Wpool = wpool_pool:find_wpool(PoolName),
165
:-(
PoolSize = wpool_pool:wpool_get(size, Wpool),
166
:-(
[whereis(wpool_pool:worker_name(PoolName, I)) || I <- lists:seq(1, PoolSize)]
167 end,
168 Pools),
169
170
:-(
get_rdbms_stats(RDBMSWorkers).
171
172 get_dist_data_stats() ->
173 94 DistStats = [inet_stats(Port) || {_, Port} <- erlang:system_info(dist_ctrl)],
174 94 [{connections, length(DistStats)} | merge_stats(DistStats)].
175
176 -spec get_up_time() -> {value, integer()}.
177 get_up_time() ->
178 94 {value, erlang:round(element(1, erlang:statistics(wall_clock))/1000)}.
179
180 -spec get_mnesia_running_db_nodes_count() -> {value, non_neg_integer()}.
181 get_mnesia_running_db_nodes_count() ->
182 96 {value, length(mnesia:system_info(running_db_nodes))}.
183
184 remove_host_type_metrics(HostType) ->
185
:-(
HostTypeName = make_host_type_name(HostType),
186
:-(
lists:foreach(fun remove_metric/1, exometer:find_entries([HostTypeName])).
187
188 remove_all_metrics() ->
189 80 lists:foreach(fun remove_metric/1, exometer:find_entries([])).
190
191 %% ---------------------------------------------------------------------
192 %% Internal functions
193 %% ---------------------------------------------------------------------
194
195 -spec all_metrics_are_global() -> boolean().
196 all_metrics_are_global() ->
197 323732 mongoose_config:get_opt(all_metrics_are_global).
198
199 pick_by_all_metrics_are_global(WhenGlobal, WhenNot) ->
200 323732 case all_metrics_are_global() of
201 28292 true -> WhenGlobal;
202 295440 false -> WhenNot
203 end.
204
205 -spec name_by_all_metrics_are_global(HostType :: mongooseim:host_type() | global,
206 Name :: list()) -> FinalName :: list().
207 name_by_all_metrics_are_global(HostType, Name) ->
208 323732 pick_by_all_metrics_are_global([global | Name], [make_host_type_name(HostType) | Name]).
209
210 get_report_interval() ->
211 48 application:get_env(exometer_core, mongooseim_report_interval,
212 ?DEFAULT_REPORT_INTERVAL).
213
214 -spec do_create_generic_hook_metric(HostType :: mongooseim:host_type() | global,
215 Hook :: hook_name(),
216 UseOrSkip :: use_or_skip()) ->
217 ok | {ok, already_present} | {error, any()}.
218 do_create_generic_hook_metric(_, _, skip) ->
219 6550 ok;
220 do_create_generic_hook_metric(HostType, Hook, use) ->
221 9225 ensure_metric(HostType, Hook, spiral).
222
223 -spec do_increment_generic_hook_metric(HostType :: mongooseim:host_type() | global,
224 Hook :: hook_name(),
225 UseOrSkip :: use_or_skip()) ->
226 ok | {error, any()}.
227 do_increment_generic_hook_metric(_, _, skip) ->
228 67873 ok;
229 do_increment_generic_hook_metric(HostType, Hook, use) ->
230 72069 update(HostType, Hook, 1).
231
232 get_rdbms_stats(RDBMSWorkers) ->
233
:-(
RDBMSConnections = [{catch mongoose_rdbms:get_db_info(Pid), Pid} || Pid <- RDBMSWorkers],
234
:-(
Ports = [get_port_from_rdbms_connection(Conn) || Conn <- RDBMSConnections],
235
:-(
PortStats = [inet_stats(Port) || Port <- lists:flatten(Ports)],
236
:-(
[{workers, length(RDBMSConnections)} | merge_stats(PortStats)].
237
238 get_port_from_rdbms_connection({{ok, DB, Pid}, _WorkerPid}) when DB =:= mysql;
239 DB =:= pgsql ->
240
:-(
ProcState = sys:get_state(Pid),
241
:-(
get_port_from_proc_state(DB, ProcState);
242 get_port_from_rdbms_connection({{ok, odbc, Pid}, WorkerPid}) ->
243
:-(
Links = element(2, erlang:process_info(Pid, links)) -- [WorkerPid],
244
:-(
[Port || Port <- Links, is_port(Port), {name, "tcp_inet"} == erlang:port_info(Port, name)];
245 get_port_from_rdbms_connection(_) ->
246
:-(
undefined.
247
248 %% @doc Gets a socket from mysql/epgsql library Gen_server state
249 get_port_from_proc_state(mysql, State) ->
250 %% -record(state, {server_version, connection_id, socket, sockmod, ssl_opts,
251 %% host, port, user, password, log_warnings,
252 %% ping_timeout,
253 %% query_timeout, query_cache_time,
254 %% affected_rows = 0, status = 0, warning_count = 0, insert_id = 0,
255 %% transaction_level = 0, ping_ref = undefined,
256 %% stmts = dict:new(), query_cache = empty, cap_found_rows = false}).
257
:-(
SockInfo = element(4, State),
258
:-(
get_port_from_sock(SockInfo);
259 get_port_from_proc_state(pgsql, State) ->
260 %% -record(state, {mod,
261 %% sock,
262 %% data = <<>>,
263 %% backend,
264 %% handler,
265 %% codec,
266 %% queue = queue:new(),
267 %% async,
268 %% parameters = [],
269 %% types = [],
270 %% columns = [],
271 %% rows = [],
272 %% results = [],
273 %% batch = [],
274 %% sync_required,
275 %% txstatus,
276 %% complete_status :: undefined | atom() | {atom(), integer()},
277 %% repl_last_received_lsn,
278 %% repl_last_flushed_lsn,
279 %% repl_last_applied_lsn,
280 %% repl_feedback_required,
281 %% repl_cbmodule,
282 %% repl_cbstate,
283 %% repl_receiver}).
284
:-(
SockInfo = element(3, State),
285
:-(
get_port_from_sock(SockInfo).
286
287 get_port_from_sock({sslsocket, {_, Port, _, _}, _}) ->
288
:-(
Port;
289 get_port_from_sock(Port) ->
290
:-(
Port.
291
292 merge_stats(Stats) ->
293 94 OrdDict = lists:foldl(fun(Stat, Acc) ->
294 280 StatDict = orddict:from_list(Stat),
295 280 orddict:merge(fun merge_stats_fun/3, Acc, StatDict)
296 end, orddict:from_list(?EMPTY_INET_STATS), Stats),
297
298 94 orddict:to_list(OrdDict).
299
300 merge_stats_fun(recv_max, V1, V2) ->
301 280 erlang:max(V1, V2);
302 merge_stats_fun(send_max, V1, V2) ->
303 280 erlang:max(V1, V2);
304 merge_stats_fun(_, V1, V2) ->
305 1400 V1 + V2.
306
307 inet_stats(Port) when is_port(Port) ->
308 280 {ok, Stats} = inet:getstat(Port, ?INET_STATS),
309 280 Stats;
310 %% TLS dist is operated by PID, not PORT directly, so we need to find the relevant port
311 inet_stats(Pid) when is_pid(Pid) ->
312 280 {links, Links} = erlang:process_info(Pid, links),
313 %% In case of TLS, controlling process of a TCP port is one of the linked proceses
314 %% so we should check all links of all processes linked to givan one
315 280 RelatedPidsAndPorts =
316 lists:map(fun(LinkedPid) ->
317 1120 {links, SubLinks} = erlang:process_info(LinkedPid, links),
318 1120 SubLinks
319 end, [Pid | Links]),
320
321 280 PortsTCP = lists:filter(
322 fun(Link) ->
323 5032 case Link of
324 Port when is_port(Port) ->
325 560 {name, "tcp_inet"} == erlang:port_info(Port, name);
326 _ ->
327 4472 false
328 end
329 end, lists:flatten(RelatedPidsAndPorts)),
330
331 280 case PortsTCP of
332 [Port | _] ->
333 280 inet_stats(Port);
334 _ ->
335
:-(
?EMPTY_INET_STATS
336 end;
337 inet_stats(_) ->
338
:-(
?EMPTY_INET_STATS.
339
340 remove_metric({Name, _, _}) ->
341 18880 exometer_admin:delete_entry(Name).
342
343 %% decided whether to use a metric for given hook or not
344 -spec filter_hook(hook_name()) -> use_or_skip().
345 3025 filter_hook(sm_register_connection_hook) -> skip;
346 3022 filter_hook(sm_remove_connection_hook) -> skip;
347 453 filter_hook(auth_failed) -> skip;
348 6740 filter_hook(user_send_packet) -> skip;
349 8674 filter_hook(user_receive_packet) -> skip;
350 438 filter_hook(xmpp_bounce_message) -> skip;
351 452 filter_hook(xmpp_stanza_dropped) -> skip;
352 22931 filter_hook(xmpp_send_element) -> skip;
353 495 filter_hook(roster_get) -> skip;
354 446 filter_hook(roster_set) -> skip;
355 641 filter_hook(roster_push) -> skip;
356 2646 filter_hook(register_user) -> skip;
357 2705 filter_hook(remove_user) -> skip;
358 475 filter_hook(privacy_iq_get) -> skip;
359 553 filter_hook(privacy_iq_set) -> skip;
360 20717 filter_hook(privacy_check_packet) -> skip;
361
:-(
filter_hook(mam_get_prefs) -> skip;
362
:-(
filter_hook(mam_set_prefs) -> skip;
363
:-(
filter_hook(mam_remove_archive) -> skip;
364
:-(
filter_hook(mam_archive_message) -> skip;
365 1 filter_hook(mam_muc_get_prefs) -> skip;
366 1 filter_hook(mam_muc_set_prefs) -> skip;
367 5 filter_hook(mam_muc_remove_archive) -> skip;
368 1 filter_hook(mam_muc_lookup_messages) -> skip;
369 1 filter_hook(mam_muc_archive_message) -> skip;
370 1 filter_hook(mam_muc_flush_messages) -> skip;
371
372 81294 filter_hook(_) -> use.
373
374 -spec create_metrics(mongooseim:host_type()) -> 'ok'.
375 create_metrics(HostType) ->
376 409 lists:foreach(fun(Name) -> ensure_metric(HostType, Name, spiral) end, ?GENERAL_SPIRALS),
377 409 lists:foreach(fun(Name) -> ensure_metric(HostType, Name, counter) end, ?TOTAL_COUNTERS).
378
379 ensure_metric(HostType, Metric, Type, ShortType) when is_atom(Metric) ->
380 23905 ensure_metric(HostType, [Metric], Type, ShortType);
381
382 ensure_metric(HostType, Metric, Type, probe = ShortType) ->
383 160 PrefixedMetric = name_by_all_metrics_are_global(HostType, Metric),
384 160 {ShortType, Opts} = Type,
385 160 case exometer:info(PrefixedMetric, type) of
386 undefined ->
387 160 ExometerOpts = [{module, mongoose_metrics_probe}, {type, ShortType}] ++ Opts,
388 160 do_create_metric(PrefixedMetric, ad_hoc, ExometerOpts);
389 _ ->
390
:-(
{ok, already_present}
391 end;
392 ensure_metric(HostType, Metric, Type, ShortType) when is_list(Metric) ->
393 %% the split into ShortType and Type is needed because function metrics are
394 %% defined as tuples (that is Type), while exometer:info returns only 'function'
395 54498 PrefixedMetric = name_by_all_metrics_are_global(HostType, Metric),
396 54498 case exometer:info(PrefixedMetric, type) of
397 undefined ->
398 18917 do_create_metric(PrefixedMetric, Type, []);
399 35581 ShortType -> {ok, already_present}
400 end.
401
402 %% @doc Creates a metric and subcribes it to the reporters
403 -spec ensure_subscribed_metric(HostType :: host_type_or_global(),
404 Metric :: metric_name(),
405 Type :: metric_type()) -> ok | term().
406 ensure_subscribed_metric(HostType, Metric, Type) ->
407 15671 case ensure_metric(HostType, Metric, Type) of
408 ok ->
409 48 PrefixedMetric = name_by_all_metrics_are_global(HostType, Metric),
410 48 Reporters = exometer_report:list_reporters(),
411 48 Interval = get_report_interval(),
412 48 lists:foreach(
413 fun({Reporter, _Pid}) ->
414
:-(
FullMetric = {PrefixedMetric, Type, []},
415
:-(
subscribe_metric(Reporter, FullMetric, Interval)
416 end,
417 Reporters);
418 {ok, already_present} ->
419 15623 ?LOG_DEBUG(#{what => metric_already_present,
420 15623 host_type => HostType, metric => Metric, type => Type}),
421 15623 ok;
422 Other ->
423
:-(
?LOG_WARNING(#{what => cannot_create_metric, reason => Other,
424
:-(
host_type => HostType, metric => Metric,type => Type}),
425
:-(
Other
426 end.
427
428 do_create_metric(PrefixedMetric, ExometerType, ExometerOpts) ->
429 19077 case catch exometer:new(PrefixedMetric, ExometerType, ExometerOpts) of
430 1 {'EXIT', {exists, _}} -> {ok, already_present};
431 19076 ok -> ok;
432
:-(
{'EXIT', Error} -> {error, Error}
433 end.
434
435 create_data_metrics() ->
436 80 lists:foreach(fun(Metric) -> ensure_metric(global, Metric, histogram) end,
437 ?GLOBAL_HISTOGRAMS),
438 80 lists:foreach(fun({Metric, Spec}) -> ensure_metric(global, Metric, Spec) end,
439 ?DATA_FUN_METRICS).
440
441 start_metrics_subscriptions(Reporter, MetricPrefix, Interval) ->
442
:-(
[subscribe_metric(Reporter, Metric, Interval)
443
:-(
|| Metric <- exometer:find_entries(MetricPrefix)].
444
445 subscribe_metric(Reporter, {Name, counter, _}, Interval) ->
446
:-(
subscribe_verbose(Reporter, Name, [value], Interval);
447 subscribe_metric(Reporter, {Name, histogram, _}, Interval) ->
448
:-(
subscribe_verbose(Reporter, Name, [min, mean, max, median, 95, 99, 999], Interval);
449 subscribe_metric(Reporter, {Name, _, _}, Interval) ->
450
:-(
subscribe_verbose(Reporter, Name, default, Interval).
451
452 subscribe_verbose(Reporter, Name, Types, Interval) ->
453
:-(
case exometer_report:subscribe(Reporter, Name, Types, Interval) of
454
:-(
ok -> ok;
455 Other ->
456
:-(
?LOG_ERROR(#{what => metrics_subscribe_failed,
457 reporter => Reporter, metric_name => Name,
458
:-(
reason => Other}),
459
:-(
Other
460 end.
461
462 subscribe_to_all(Reporter, Interval) ->
463
:-(
HostTypePrefixes = pick_by_all_metrics_are_global([], ?ALL_HOST_TYPES),
464
:-(
lists:foreach(
465 fun(Prefix) ->
466
:-(
UnspacedPrefix = make_host_type_name(Prefix),
467
:-(
start_metrics_subscriptions(Reporter, [UnspacedPrefix], Interval)
468 end, [global | HostTypePrefixes]).
469
470 make_host_type_name(HT) when is_atom(HT) ->
471 142186 HT;
472 make_host_type_name(HT) when is_binary(HT) ->
473 181644 binary:replace(HT, <<" ">>, <<"_">>, [global]).
Line Hits Source