./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 init_mongooseim_metrics/0,
24 create_generic_hook_metric/2,
25 create_probe_metric/3,
26 ensure_db_pool_metric/1,
27 update/3,
28 ensure_metric/3,
29 ensure_subscribed_metric/3,
30 get_metric_value/1,
31 get_metric_values/1,
32 get_metric_value/2,
33 sample_metric/1,
34 get_host_type_metric_names/1,
35 get_global_metric_names/0,
36 get_aggregated_values/1,
37 increment_generic_hook_metric/2,
38 get_rdbms_data_stats/0,
39 get_rdbms_data_stats/1,
40 get_dist_data_stats/0,
41 get_up_time/0,
42 get_mnesia_running_db_nodes_count/0,
43 remove_host_type_metrics/1,
44 remove_all_metrics/0,
45 get_report_interval/0,
46 get_host_type_prefix/1
47 ]).
48
49 -ignore_xref([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 remove_host_type_metrics/1, get_report_interval/0,
52 sample_metric/1, get_metric_value/1]).
53
54 -define(PREFIXES, mongoose_metrics_prefixes).
55 -define(DEFAULT_REPORT_INTERVAL, 60000). %%60s
56
57 -type use_or_skip() :: use | skip.
58 -type hook_name() :: atom().
59 -type metric_name() :: atom() | list(atom() | binary()).
60 -type short_metric_type() :: spiral | histogram | counter | gauge.
61 -type metric_type() :: tuple() | short_metric_type().
62
63 %% ---------------------------------------------------------------------
64 %% API
65 %% ---------------------------------------------------------------------
66
67 -spec init() -> ok.
68 init() ->
69 104 prepare_prefixes(),
70 104 create_vm_metrics(),
71 104 create_global_metrics(?GLOBAL_COUNTERS),
72 104 create_data_metrics(),
73 104 create_host_type_metrics().
74
75 -spec init_mongooseim_metrics() -> ok.
76 init_mongooseim_metrics() ->
77 104 create_host_type_hook_metrics(),
78 104 create_global_metrics(?MNESIA_COUNTERS),
79 104 init_subscriptions().
80
81 init_subscriptions() ->
82 104 Reporters = exometer_report:list_reporters(),
83 104 lists:foreach(
84 fun({Name, _ReporterPid}) ->
85
:-(
Interval = get_report_interval(),
86
:-(
subscribe_to_all(Name, Interval)
87 end, Reporters).
88
89 -spec create_generic_hook_metric(mongooseim:host_type(), atom()) ->
90 ok | {ok, already_present} | {error, any()}.
91 create_generic_hook_metric(HostType, Hook) ->
92 32056 UseOrSkip = filter_hook(Hook),
93 32056 do_create_generic_hook_metric(HostType, Hook, UseOrSkip).
94
95 -spec create_probe_metric(mongooseim:host_type_or_global(), atom(), module()) ->
96 ok | {ok, already_present} | {error, any()}.
97 create_probe_metric(HostType, Name, Module) ->
98 312 {Metric, Spec} = ?PROBE(Name, Module),
99 312 ensure_metric(HostType, Metric, Spec).
100
101 % TODO: change to HostType after mongoose_wpool_rdbms
102 ensure_db_pool_metric({rdbms, Host, Tag} = Name) ->
103 106 ensure_metric(Host,
104 [data, rdbms, Tag],
105 {function, mongoose_metrics, get_rdbms_data_stats, [[Name]], proplist,
106 [workers | ?INET_STATS]}).
107
108 -spec update(HostType :: mongooseim:host_type_or_global(), Name :: term() | list(),
109 Change :: term()) -> any().
110 update(HostType, Name, Change) when is_list(Name) ->
111 1289753 exometer:update(name_by_all_metrics_are_global(HostType, Name), Change);
112 update(HostType, Name, Change) ->
113 451696 update(HostType, [Name], Change).
114
115 -spec ensure_metric(mongooseim:host_type_or_global(), metric_name(), metric_type()) ->
116 ok | {ok, already_present} | {error, any()}.
117 ensure_metric(HostType, Metric, Type) when is_tuple(Type) ->
118 1249 ensure_metric(HostType, Metric, Type, element(1, Type));
119 ensure_metric(HostType, Metric, Type) ->
120 69790 ensure_metric(HostType, Metric, Type, Type).
121
122 get_metric_value(HostType, Name) when is_list(Name) ->
123 261 get_metric_value(name_by_all_metrics_are_global(HostType, Name));
124 get_metric_value(HostType, Name) ->
125 256 get_metric_value(HostType, [Name]).
126
127 get_metric_value(Metric) ->
128 347 exometer:get_value(Metric).
129
130 get_metric_values(Metric) when is_list(Metric) ->
131 246 exometer:get_values(Metric);
132 get_metric_values(HostType) ->
133 184 exometer:get_values([HostType]).
134
135 %% Force update a probe metric
136 sample_metric(Metric) ->
137 6 exometer:sample(Metric).
138
139 get_host_type_metric_names(HostType) ->
140 91 HostTypeName = get_host_type_prefix(HostType),
141 91 [MetricName || {[_HostTypeName | MetricName], _, _} <- exometer:find_entries([HostTypeName])].
142
143 get_global_metric_names() ->
144 2 get_host_type_metric_names(global).
145
146 get_aggregated_values(Metric) ->
147 8875 exometer:aggregate([{{['_', Metric], '_', '_'}, [], [true]}], [one, count, value]).
148
149 -spec increment_generic_hook_metric(mongooseim:host_type_or_global(), atom()) -> ok | {error, any()}.
150 increment_generic_hook_metric(HostType, Hook) ->
151 418614 UseOrSkip = filter_hook(Hook),
152 418614 do_increment_generic_hook_metric(HostType, Hook, UseOrSkip).
153
154 get_rdbms_data_stats() ->
155
:-(
Pools = lists:filter(fun({Type, _Host, _Tag}) -> Type == rdbms end, mongoose_wpool:get_pools()),
156
:-(
get_rdbms_data_stats(Pools).
157
158 get_rdbms_data_stats(Pools) ->
159 130 RDBMSWorkers =
160 lists:flatmap(
161 fun({Type, Host, Tag}) ->
162 130 PoolName = mongoose_wpool:make_pool_name(Type, Host, Tag),
163 130 Wpool = wpool_pool:find_wpool(PoolName),
164 130 PoolSize = wpool_pool:wpool_get(size, Wpool),
165 130 [whereis(wpool_pool:worker_name(PoolName, I)) || I <- lists:seq(1, PoolSize)]
166 end,
167 Pools),
168
169 130 get_rdbms_stats(RDBMSWorkers).
170
171 get_dist_data_stats() ->
172 132 DistStats = [dist_inet_stats(PortOrPid) || {_, PortOrPid} <- erlang:system_info(dist_ctrl)],
173 132 [{connections, length(DistStats)} | merge_stats(DistStats)].
174
175 -spec get_up_time() -> {value, integer()}.
176 get_up_time() ->
177 130 {value, erlang:round(element(1, erlang:statistics(wall_clock))/1000)}.
178
179 -spec get_mnesia_running_db_nodes_count() -> {value, non_neg_integer()}.
180 get_mnesia_running_db_nodes_count() ->
181 132 {value, length(mnesia:system_info(running_db_nodes))}.
182
183 remove_host_type_metrics(HostType) ->
184
:-(
HostTypeName = get_host_type_prefix(HostType),
185
:-(
lists:foreach(fun remove_metric/1, exometer:find_entries([HostTypeName])).
186
187 remove_all_metrics() ->
188 104 persistent_term:erase(?PREFIXES),
189 104 lists:foreach(fun remove_metric/1, exometer:find_entries([])).
190
191 %% ---------------------------------------------------------------------
192 %% Internal functions
193 %% ---------------------------------------------------------------------
194
195 prepare_prefixes() ->
196 104 Prebuilt = maps:from_list([begin
197 558 Prefix = make_host_type_prefix(HT),
198 558 {Prefix, Prefix}
199 104 end || HT <- ?ALL_HOST_TYPES ]),
200 104 Prefixes = maps:from_list([ {HT, make_host_type_prefix(HT)}
201 104 || HT <- ?ALL_HOST_TYPES ]),
202 104 persistent_term:put(?PREFIXES, maps:merge(Prebuilt, Prefixes)).
203
204 -spec all_metrics_are_global() -> boolean().
205 all_metrics_are_global() ->
206 1361101 mongoose_config:get_opt(all_metrics_are_global).
207
208 get_host_type_prefix(global) ->
209 780575 global;
210 get_host_type_prefix(HostType) when is_binary(HostType) ->
211 578367 case persistent_term:get(?PREFIXES, #{}) of
212 578358 #{HostType := HostTypePrefix} -> HostTypePrefix;
213 9 #{} -> make_host_type_prefix(HostType)
214 end.
215
216 make_host_type_prefix(HT) when is_binary(HT) ->
217 1125 binary:replace(HT, <<" ">>, <<"_">>, [global]).
218
219 pick_prefix_by_all_metrics_are_global(HostType) ->
220 1361101 case all_metrics_are_global() of
221 34977 true -> global;
222 1326124 false -> get_host_type_prefix(HostType)
223 end.
224
225 pick_by_all_metrics_are_global(WhenGlobal, WhenNot) ->
226
:-(
case all_metrics_are_global() of
227
:-(
true -> WhenGlobal;
228
:-(
false -> WhenNot
229 end.
230
231 -spec name_by_all_metrics_are_global(HostType :: mongooseim:host_type_or_global(),
232 Name :: list()) -> FinalName :: list().
233 name_by_all_metrics_are_global(HostType, Name) ->
234 1361101 [pick_prefix_by_all_metrics_are_global(HostType) | Name].
235
236 get_report_interval() ->
237 612 application:get_env(exometer_core, mongooseim_report_interval,
238 ?DEFAULT_REPORT_INTERVAL).
239
240 -spec do_create_generic_hook_metric(HostType :: mongooseim:host_type_or_global(),
241 Hook :: hook_name(),
242 UseOrSkip :: use_or_skip()) ->
243 ok | {ok, already_present} | {error, any()}.
244 do_create_generic_hook_metric(_, _, skip) ->
245 11278 ok;
246 do_create_generic_hook_metric(HostType, Hook, use) ->
247 20778 ensure_metric(HostType, Hook, spiral).
248
249 -spec do_increment_generic_hook_metric(HostType :: mongooseim:host_type_or_global(),
250 Hook :: hook_name(),
251 UseOrSkip :: use_or_skip()) ->
252 ok | {error, any()}.
253 do_increment_generic_hook_metric(_, _, skip) ->
254 226615 ok;
255 do_increment_generic_hook_metric(HostType, Hook, use) ->
256 191999 update(HostType, Hook, 1).
257
258 get_rdbms_stats(RDBMSWorkers) ->
259 130 RDBMSConnections = [{catch mongoose_rdbms:get_db_info(Pid), Pid} || Pid <- RDBMSWorkers],
260 130 Ports = [get_port_from_rdbms_connection(Conn) || Conn <- RDBMSConnections],
261 130 PortStats = [inet_stats(Port) || Port <- lists:flatten(Ports)],
262 130 [{workers, length(RDBMSConnections)} | merge_stats(PortStats)].
263
264 get_port_from_rdbms_connection({{ok, DB, Pid}, _WorkerPid}) when DB =:= mysql;
265 DB =:= pgsql ->
266
:-(
ProcState = sys:get_state(Pid),
267
:-(
get_port_from_proc_state(DB, ProcState);
268 get_port_from_rdbms_connection({{ok, odbc, Pid}, WorkerPid}) ->
269 650 Links = element(2, erlang:process_info(Pid, links)) -- [WorkerPid],
270 650 [Port || Port <- Links, is_port(Port), {name, "tcp_inet"} == erlang:port_info(Port, name)];
271 get_port_from_rdbms_connection(_) ->
272
:-(
undefined.
273
274 %% @doc Gets a socket from mysql/epgsql library Gen_server state
275 get_port_from_proc_state(mysql, State) ->
276 %% -record(state, {server_version, connection_id, socket, sockmod, ssl_opts,
277 %% host, port, user, password, log_warnings,
278 %% ping_timeout,
279 %% query_timeout, query_cache_time,
280 %% affected_rows = 0, status = 0, warning_count = 0, insert_id = 0,
281 %% transaction_level = 0, ping_ref = undefined,
282 %% stmts = dict:new(), query_cache = empty, cap_found_rows = false}).
283
:-(
SockInfo = element(4, State),
284
:-(
get_port_from_sock(SockInfo);
285 get_port_from_proc_state(pgsql, State) ->
286 %% -record(state, {mod,
287 %% sock,
288 %% data = <<>>,
289 %% backend,
290 %% handler,
291 %% codec,
292 %% queue = queue:new(),
293 %% async,
294 %% parameters = [],
295 %% types = [],
296 %% columns = [],
297 %% rows = [],
298 %% results = [],
299 %% batch = [],
300 %% sync_required,
301 %% txstatus,
302 %% complete_status :: undefined | atom() | {atom(), integer()},
303 %% repl_last_received_lsn,
304 %% repl_last_flushed_lsn,
305 %% repl_last_applied_lsn,
306 %% repl_feedback_required,
307 %% repl_cbmodule,
308 %% repl_cbstate,
309 %% repl_receiver}).
310
:-(
SockInfo = element(3, State),
311
:-(
get_port_from_sock(SockInfo).
312
313 get_port_from_sock({sslsocket, {_, Port, _, _}, _}) ->
314
:-(
Port;
315 get_port_from_sock(Port) ->
316
:-(
Port.
317
318 merge_stats(Stats) ->
319 262 OrdDict = lists:foldl(fun(Stat, Acc) ->
320 1575 StatDict = orddict:from_list(Stat),
321 1575 orddict:merge(fun merge_stats_fun/3, Acc, StatDict)
322 end, orddict:from_list(?EMPTY_INET_STATS), Stats),
323
324 262 orddict:to_list(OrdDict).
325
326 merge_stats_fun(recv_max, V1, V2) ->
327 1575 erlang:max(V1, V2);
328 merge_stats_fun(send_max, V1, V2) ->
329 1575 erlang:max(V1, V2);
330 merge_stats_fun(_, V1, V2) ->
331 7875 V1 + V2.
332
333 dist_inet_stats(Pid) when is_pid(Pid) ->
334
:-(
try
335
:-(
{ok, {sslsocket, FD, _Pids}} = tls_sender:dist_tls_socket(Pid),
336
:-(
gen_tcp = element(1, FD),
337
:-(
inet_stats(element(2, FD))
338 catch C:R:S ->
339
:-(
?LOG_INFO(#{what => dist_inet_stats_failed, class => C, reason => R, stacktrace => S}),
340
:-(
?EMPTY_INET_STATS
341 end;
342 dist_inet_stats(Port) ->
343 275 inet_stats(Port).
344
345 inet_stats(Port) ->
346 1575 try
347 1575 {ok, Stats} = inet:getstat(Port, ?INET_STATS),
348 1575 Stats
349 catch C:R:S ->
350
:-(
?LOG_INFO(#{what => inet_stats_failed, class => C, reason => R, stacktrace => S}),
351
:-(
?EMPTY_INET_STATS
352 end.
353
354 remove_metric({Name, _, _}) ->
355 30885 exometer_admin:delete_entry(Name).
356
357 %% decided whether to use a metric for given hook or not
358 -spec filter_hook(hook_name()) -> use_or_skip().
359 7146 filter_hook(sm_register_connection_hook) -> skip;
360 7144 filter_hook(sm_remove_connection_hook) -> skip;
361 627 filter_hook(auth_failed) -> skip;
362 24186 filter_hook(user_send_packet) -> skip;
363 4610 filter_hook(user_send_message) -> skip;
364 7544 filter_hook(user_send_presence) -> skip;
365 8284 filter_hook(user_send_iq) -> skip;
366 41473 filter_hook(user_receive_packet) -> skip;
367 12758 filter_hook(user_receive_message) -> skip;
368 15617 filter_hook(user_receive_presence) -> skip;
369 10135 filter_hook(user_receive_iq) -> skip;
370 607 filter_hook(xmpp_bounce_message) -> skip;
371 632 filter_hook(xmpp_stanza_dropped) -> skip;
372 63087 filter_hook(xmpp_send_element) -> skip;
373 749 filter_hook(roster_get) -> skip;
374 589 filter_hook(roster_set) -> skip;
375 1395 filter_hook(roster_push) -> skip;
376 6648 filter_hook(register_user) -> skip;
377 6622 filter_hook(remove_user) -> skip;
378 620 filter_hook(privacy_iq_get) -> skip;
379 697 filter_hook(privacy_iq_set) -> skip;
380 4127 filter_hook(privacy_check_packet) -> skip;
381 217 filter_hook(mam_get_prefs) -> skip;
382 364 filter_hook(mam_set_prefs) -> skip;
383 593 filter_hook(mam_remove_archive) -> skip;
384 7663 filter_hook(mam_archive_message) -> skip;
385 14 filter_hook(mam_muc_get_prefs) -> skip;
386 14 filter_hook(mam_muc_set_prefs) -> skip;
387 607 filter_hook(mam_muc_remove_archive) -> skip;
388 552 filter_hook(mam_muc_lookup_messages) -> skip;
389 2572 filter_hook(mam_muc_archive_message) -> skip;
390
:-(
filter_hook(mam_muc_flush_messages) -> skip;
391
392 212777 filter_hook(_) -> use.
393
394 create_global_metrics(Metrics) ->
395 208 lists:foreach(fun({Metric, Spec}) -> ensure_metric(global, Metric, Spec) end, Metrics).
396
397 create_vm_metrics() ->
398 104 lists:foreach(fun({Metric, FunSpec, DataPoints}) ->
399 208 FunSpecTuple = list_to_tuple(FunSpec ++ [DataPoints]),
400 208 catch ensure_metric(global, Metric, FunSpecTuple)
401 end, ?VM_STATS).
402
403 -spec create_host_type_metrics() -> ok.
404 create_host_type_metrics() ->
405 104 lists:foreach(fun create_host_type_metrics/1, ?ALL_HOST_TYPES).
406
407 -spec create_host_type_metrics(mongooseim:host_type()) -> 'ok'.
408 create_host_type_metrics(HostType) ->
409 558 lists:foreach(fun(Name) -> ensure_metric(HostType, Name, spiral) end, ?GENERAL_SPIRALS),
410 558 lists:foreach(fun(Name) -> ensure_metric(HostType, Name, histogram) end, ?GENERAL_HISTOGRAMS),
411 558 lists:foreach(fun(Name) -> ensure_metric(HostType, Name, counter) end, ?TOTAL_COUNTERS).
412
413 -spec create_host_type_hook_metrics() -> ok.
414 create_host_type_hook_metrics() ->
415 104 lists:foreach(fun create_host_type_hook_metrics/1, ?ALL_HOST_TYPES).
416
417 -spec create_host_type_hook_metrics(mongooseim:host_type()) -> 'ok'.
418 create_host_type_hook_metrics(HostType) ->
419 558 Hooks = mongoose_metrics_hooks:get_hooks(HostType),
420 558 gen_hook:add_handlers(Hooks).
421
422 ensure_metric(HostType, Metric, Type, ShortType) when is_atom(Metric) ->
423 40686 ensure_metric(HostType, [Metric], Type, ShortType);
424
425 ensure_metric(HostType, Metric, Type, probe = ShortType) ->
426 602 PrefixedMetric = name_by_all_metrics_are_global(HostType, Metric),
427 602 {ShortType, Opts} = Type,
428 602 case exometer:info(PrefixedMetric, type) of
429 undefined ->
430 532 ExometerOpts = [{module, mongoose_metrics_probe}, {type, ShortType}] ++ Opts,
431 532 do_create_metric(PrefixedMetric, ad_hoc, ExometerOpts);
432 _ ->
433 70 {ok, already_present}
434 end;
435 ensure_metric(HostType, Metric, Type, ShortType) when is_list(Metric) ->
436 %% the split into ShortType and Type is needed because function metrics are
437 %% defined as tuples (that is Type), while exometer:info returns only 'function'
438 70437 PrefixedMetric = name_by_all_metrics_are_global(HostType, Metric),
439 70437 case exometer:info(PrefixedMetric, type) of
440 undefined ->
441 30575 do_create_metric(PrefixedMetric, Type, []);
442 39862 ShortType -> {ok, already_present}
443 end.
444
445 %% @doc Creates a metric and subcribes it to the reporters
446 -spec ensure_subscribed_metric(HostType :: mongooseim:host_type_or_global(),
447 Metric :: metric_name(),
448 Type :: metric_type()) -> ok | term().
449 ensure_subscribed_metric(HostType, Metric, Type) ->
450 5636 case ensure_metric(HostType, Metric, Type) of
451 ok ->
452 48 PrefixedMetric = name_by_all_metrics_are_global(HostType, Metric),
453 48 Reporters = exometer_report:list_reporters(),
454 48 Interval = get_report_interval(),
455 48 lists:foreach(
456 fun({Reporter, _Pid}) ->
457
:-(
FullMetric = {PrefixedMetric, Type, []},
458
:-(
subscribe_metric(Reporter, FullMetric, Interval)
459 end,
460 Reporters);
461 {ok, already_present} ->
462 5588 ?LOG_DEBUG(#{what => metric_already_present,
463 5588 host_type => HostType, metric => Metric, type => Type}),
464 5588 ok;
465 Other ->
466
:-(
?LOG_WARNING(#{what => cannot_create_metric, reason => Other,
467
:-(
host_type => HostType, metric => Metric,type => Type}),
468
:-(
Other
469 end.
470
471 do_create_metric(PrefixedMetric, ExometerType, ExometerOpts) ->
472 31107 case catch exometer:new(PrefixedMetric, ExometerType, ExometerOpts) of
473 1 {'EXIT', {exists, _}} -> {ok, already_present};
474 31106 ok -> ok;
475
:-(
{'EXIT', Error} -> {error, Error}
476 end.
477
478 create_data_metrics() ->
479 104 lists:foreach(fun(Metric) -> ensure_metric(global, Metric, histogram) end,
480 ?GLOBAL_HISTOGRAMS),
481 104 lists:foreach(fun(Metric) -> ensure_metric(global, Metric, spiral) end,
482 ?GLOBAL_SPIRALS),
483 104 lists:foreach(fun({Metric, Spec}) -> ensure_metric(global, Metric, Spec) end,
484 ?DATA_FUN_METRICS).
485
486 start_metrics_subscriptions(Reporter, MetricPrefix, Interval) ->
487
:-(
[subscribe_metric(Reporter, Metric, Interval)
488
:-(
|| Metric <- exometer:find_entries(MetricPrefix)].
489
490 subscribe_metric(Reporter, {Name, counter, _}, Interval) ->
491
:-(
subscribe_verbose(Reporter, Name, [value], Interval);
492 subscribe_metric(Reporter, {Name, histogram, _}, Interval) ->
493
:-(
subscribe_verbose(Reporter, Name, [min, mean, max, median, 95, 99, 999], Interval);
494 subscribe_metric(Reporter, {Name, _, _}, Interval) ->
495
:-(
subscribe_verbose(Reporter, Name, default, Interval).
496
497 subscribe_verbose(Reporter, Name, Types, Interval) ->
498
:-(
case exometer_report:subscribe(Reporter, Name, Types, Interval) of
499
:-(
ok -> ok;
500 Other ->
501
:-(
?LOG_ERROR(#{what => metrics_subscribe_failed,
502 reporter => Reporter, metric_name => Name,
503
:-(
reason => Other}),
504
:-(
Other
505 end.
506
507 subscribe_to_all(Reporter, Interval) ->
508
:-(
HostTypePrefixes = pick_by_all_metrics_are_global([], ?ALL_HOST_TYPES),
509
:-(
lists:foreach(
510 fun(Prefix) ->
511
:-(
UnspacedPrefix = get_host_type_prefix(Prefix),
512
:-(
start_metrics_subscriptions(Reporter, [UnspacedPrefix], Interval)
513 end, [global | HostTypePrefixes]).
Line Hits Source