1: -module(mongooseim_metrics_SUITE).
    2: 
    3: -include_lib("exml/include/exml.hrl").
    4: -include_lib("proper/include/proper.hrl").
    5: -include_lib("eunit/include/eunit.hrl").
    6: -include("jlib.hrl").
    7: -include_lib("common_test/include/ct.hrl").
    8: 
    9: -compile([export_all, nowarn_export_all]).
   10: 
   11: all() ->
   12:     [
   13:      {group, ordinary_mode},
   14:      {group, all_metrics_are_global}
   15:     ].
   16: 
   17: groups() ->
   18:     [
   19:      {ordinary_mode, [], all_metrics_list()},
   20:      {all_metrics_are_global, [], all_metrics_list()}
   21:     ].
   22: 
   23: all_metrics_list() ->
   24:     [
   25:      no_skip_metric,
   26:      subscriptions_initialised,
   27:      tcp_connections_detected,
   28:      tcp_metric_varies_with_tcp_variations,
   29:      up_time_positive,
   30:      queued_messages_increase,
   31:      function_ensure_subscribed_metric_subscribes
   32:     ].
   33: 
   34: init_per_suite(C) ->
   35:     application:load(exometer_core),
   36:     application:set_env(exometer_core, mongooseim_report_interval, 1000),
   37:     {Port, Socket} = carbon_cache_server:start(),
   38:     Sup = spawn(fun() ->
   39:         mim_ct_sup:start_link(ejabberd_sup),
   40:         Hooks = {gen_hook,
   41:                  {mongooseim_helper, start_link_loaded_hooks, []},
   42:                  permanent,
   43:                  brutal_kill,
   44:                  worker,
   45:                  [gen_hook]},
   46:         supervisor:start_child(ejabberd_sup, Hooks),
   47:         receive
   48:             stop ->
   49:                 ok
   50:         end
   51:     end),
   52:     Reporters = get_reporters_cfg(Port),
   53:     application:set_env(exometer_core, report, Reporters),
   54:     PortServer = carbon_cache_server:wait_for_accepting(),
   55:     gen_tcp:controlling_process(Socket, PortServer),
   56:     {ok, _Apps} = application:ensure_all_started(exometer_core),
   57:     exometer:new([carbon, packets], spiral),
   58:     [{carbon_port, Port}, {test_sup, Sup}, {carbon_server, PortServer}, {carbon_socket, Socket} | C].
   59: 
   60: end_per_suite(C) ->
   61:     Sup = ?config(test_sup, C),
   62:     Sup ! stop,
   63:     CarbonServer = ?config(carbon_server, C),
   64:     erlang:exit(CarbonServer, kill),
   65:     CarbonSocket = ?config(carbon_socket, C),
   66:     gen_tcp:close(CarbonSocket),
   67:     application:stop(exometer_core),
   68:     C.
   69: 
   70: init_per_group(Group, C) ->
   71:     mongoose_config:set_opts(opts(Group)),
   72:     mongoose_metrics:init(),
   73:     mongoose_metrics:init_mongooseim_metrics(),
   74:     C.
   75: 
   76: end_per_group(_Group, _C) ->
   77:     mongoose_metrics:remove_host_type_metrics(<<"localhost">>),
   78:     mongoose_metrics:remove_host_type_metrics(global),
   79:     mongoose_config:erase_opts().
   80: 
   81: init_per_testcase(CN, C) when tcp_connections_detected =:= CN;
   82:                               tcp_metric_varies_with_tcp_variations =:= CN ->
   83:     exometer:setopts([global, tcpPortsUsed], [{sample_interval, 50}]),
   84:     exometer:repair([global, tcpPortsUsed]),
   85:     C;
   86: init_per_testcase(queued_messages_increase, C) ->
   87:     exometer:setopts([global, processQueueLengths], [{sample_interval, 50}]),
   88:     exometer:repair([global, processQueueLengths]),
   89:     PidsFun = fun() -> put('$internal_queue_len', 1),
   90:                        receive die -> ok
   91:                        end
   92:               end,
   93:     Pids = [spawn(PidsFun) || _ <- lists:seq(1,5)],
   94:     lists:foreach(fun(Pid) -> Pid ! undefined end, Pids),
   95:     [{pids, Pids} | C];
   96: init_per_testcase(_N, C) ->
   97:     C.
   98: 
   99: end_per_testcase(queued_messages_increase, C) ->
  100:     [Pid ! die || Pid <- ?config(pids, C)],
  101:     C;
  102: end_per_testcase(_N, C) ->
  103:     C.
  104: 
  105: up_time_positive(_C) ->
  106:     {ok, [{value, X}]} = mongoose_metrics:get_metric_value(global, nodeUpTime),
  107:     ?assert(X > 0).
  108: 
  109: function_ensure_subscribed_metric_subscribes(_C) ->
  110:     SubMetric = [happy_metric],
  111:     UnsubMetric = [sad_metric],
  112:     mongoose_metrics:ensure_subscribed_metric(global, SubMetric, spiral),
  113:     mongoose_metrics:ensure_metric(global, UnsubMetric, spiral),
  114:     Subs = exometer_report:list_subscriptions(exometer_report_graphite),
  115:     try
  116:         true = lists:keymember([global|SubMetric], 1, Subs),
  117:         false = lists:keymember([global|UnsubMetric], 1, Subs)
  118:     catch C:E:S ->
  119:               ct:pal("Subs ~p", [Subs]),
  120:               erlang:raise(C, E, S)
  121:     end.
  122: 
  123: get_new_tcp_metric_value(OldValue) ->
  124:     Validator = fun(NewValue) -> OldValue =/= NewValue end,
  125:     {ok, {ok, [{value, X}]}} = async_helper:wait_until(
  126:       fun() -> mongoose_metrics:get_metric_value(global, tcpPortsUsed) end,
  127:       Validator, #{sleep_time => 30, time_left => 500}
  128:      ),
  129:     X.
  130: 
  131: tcp_connections_detected(_C) ->
  132:     get_new_tcp_metric_value({ok, []}).
  133: 
  134: tcp_metric_varies_with_tcp_variations(_C) ->
  135:     X = get_new_tcp_metric_value({ok, []}),
  136:     {ok, Socket} = gen_tcp:listen(0, []),
  137:     Y = get_new_tcp_metric_value({ok, [{value, X}]}),
  138:     ?assert(Y == X + 1),
  139:     gen_tcp:close(Socket),
  140:     X = get_new_tcp_metric_value({ok, [{value, Y}]}).
  141: 
  142: queued_messages_increase(_C) ->
  143:     Fun = fun(Value) ->
  144:         case Value of
  145:             [{fsm, 5}, {regular, 5}, {total, 10}] -> true;
  146: 
  147:             %% Sometimes there is an additional unprocessed message
  148:             %% in the standard I/O ('user') process
  149:             [{fsm, 5}, {regular, 6}, {total, 11}] -> true;
  150:             _ -> false
  151:         end
  152:     end,
  153:     async_helper:wait_until(
  154:       fun() ->
  155:               {ok, L} = mongoose_metrics:get_metric_value(global, processQueueLengths),
  156:               lists:sort(L)
  157:       end, Fun).
  158: 
  159: no_skip_metric(_C) ->
  160:     ok = mongoose_metrics:create_generic_hook_metric(<<"localhost">>, sm_register_connection_hook),
  161:     undefined = exometer:info([<<"localhost">>, sm_register_connection_hook]).
  162: 
  163: subscriptions_initialised(_C) ->
  164:     true = wait_for_update(exometer:get_value([carbon, packets], count), 60).
  165: 
  166: wait_for_update({ok, [{count,X}]}, 0) ->
  167:     X > 0;
  168: wait_for_update({ok, [{count,X}]}, _N) when X > 0 ->
  169:     true;
  170: wait_for_update({ok, [{count,0}]}, N) ->
  171:     timer:sleep(1000),
  172:     wait_for_update(exometer:get_value([carbon, packets], count), N-1).
  173: 
  174: opts(Group) ->
  175:     #{hosts => [<<"localhost">>],
  176:       host_types => [],
  177:       all_metrics_are_global => Group =:= all_metrics_are_global}.
  178: 
  179: get_reporters_cfg(Port) ->
  180:     [{reporters, [
  181:                  {exometer_report_graphite, [
  182:                                              {prefix, "mongooseim"},
  183:                                              {connect_timeout, 10000},
  184:                                              {host, "127.0.0.1"},
  185:                                              {port, Port},
  186:                                              {api_key, ""}
  187:                                             ]}
  188:                 ]}].