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