1: -module(graphql_metric_SUITE).
    2: 
    3: -include_lib("eunit/include/eunit.hrl").
    4: 
    5: -compile([export_all, nowarn_export_all]).
    6: 
    7: -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]).
    8: -import(graphql_helper, [execute_command/4, get_ok_value/2, get_unauthorized/1,
    9:                          get_err_msg/1, get_err_code/1]).
   10: 
   11: suite() ->
   12:     MIM2NodeName = maps:get(node, distributed_helper:mim2()),
   13:     %% Ensure nodes are connected
   14:     mongoose_helper:successful_rpc(net_kernel, connect_node, [MIM2NodeName]),
   15:     require_rpc_nodes([mim, mim2]) ++ escalus:suite().
   16: 
   17: all() ->
   18:      [{group, metrics_http},
   19:       {group, metrics_cli},
   20:       {group, domain_admin_metrics}].
   21: 
   22: groups() ->
   23:      [{metrics_http, [], metrics_tests()},
   24:       {metrics_cli, [], metrics_tests()},
   25:       {domain_admin_metrics, [], domain_admin_metrics_tests()}].
   26: 
   27: metrics_tests() ->
   28:     [get_all_metrics,
   29:      get_all_metrics_check_by_type,
   30:      get_by_name_global_erlang_metrics,
   31:      get_metrics_by_name_empty_args,
   32:      get_metrics_by_name_empty_string,
   33:      get_metrics_by_nonexistent_name,
   34:      get_metrics_for_specific_host_type,
   35:      get_process_queue_length,
   36:      get_inet_stats,
   37:      get_vm_stats_memory,
   38:      get_cets_system,
   39:      get_all_metrics_as_dicts,
   40:      get_by_name_metrics_as_dicts,
   41:      get_metrics_as_dicts_by_nonexistent_name,
   42:      get_metrics_as_dicts_with_key_one,
   43:      get_metrics_as_dicts_with_nonexistent_key,
   44:      get_metrics_as_dicts_empty_args,
   45:      get_metrics_as_dicts_empty_strings,
   46:      get_cluster_metrics,
   47:      get_by_name_cluster_metrics_as_dicts,
   48:      get_mim2_cluster_metrics,
   49:      get_cluster_metrics_for_nonexistent_nodes,
   50:      get_cluster_metrics_by_nonexistent_name,
   51:      get_cluster_metrics_with_nonexistent_key,
   52:      get_cluster_metrics_empty_args,
   53:      get_cluster_metrics_empty_strings].
   54: 
   55: domain_admin_metrics_tests() ->
   56:     [domain_admin_get_metrics,
   57:      domain_admin_get_metrics_as_dicts,
   58:      domain_admin_get_metrics_as_dicts_by_name,
   59:      domain_admin_get_metrics_as_dicts_with_keys,
   60:      domain_admin_get_cluster_metrics_as_dicts,
   61:      domain_admin_get_cluster_metrics_as_dicts_by_name,
   62:      domain_admin_get_cluster_metrics_as_dicts_for_nodes].
   63: 
   64: init_per_suite(Config) ->
   65:     Config1 = ejabberd_node_utils:init(mim(), Config),
   66:     escalus:init_per_suite(Config1).
   67: 
   68: end_per_suite(Config) ->
   69:     escalus_fresh:clean(),
   70:     escalus:end_per_suite(Config).
   71: 
   72: init_per_group(metrics_http, Config) ->
   73:     graphql_helper:init_admin_handler(Config);
   74: init_per_group(metrics_cli, Config) ->
   75:     graphql_helper:init_admin_cli(Config);
   76: init_per_group(domain_admin_metrics, Config) ->
   77:     graphql_helper:init_domain_admin_handler(Config).
   78: 
   79: end_per_group(_GroupName, _Config) ->
   80:     graphql_helper:clean().
   81: 
   82: init_per_testcase(get_cets_system = CaseName, Config) ->
   83:      case is_cets_enabled() of
   84:          true ->
   85:              escalus:init_per_testcase(CaseName, Config);
   86:          false ->
   87:              {skip, cets_not_enabled}
   88:      end;
   89: init_per_testcase(CaseName, Config) ->
   90:      escalus:init_per_testcase(CaseName, Config).
   91: 
   92: end_per_testcase(CaseName, Config) ->
   93:      escalus:end_per_testcase(CaseName, Config).
   94: 
   95: get_all_metrics(Config) ->
   96:     Result = get_metrics(Config),
   97:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
   98:     Map = maps:from_list([{Name, X} || X = #{<<"name">> := Name} <- ParsedResult]),
   99:     ReadsKey = [<<"global">>, <<"backends">>, <<"mod_roster">>, <<"read_roster_version">>],
  100:     Reads = maps:get(ReadsKey, Map),
  101:     %% Histogram integer keys have p prefix
  102:     check_histogram_p(Reads),
  103:     %% HistogramMetric type
  104:     #{<<"type">> := <<"histogram">>} = Reads.
  105: 
  106: get_all_metrics_check_by_type(Config) ->
  107:     Result = get_metrics(Config),
  108:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
  109:     lists:foreach(fun check_metric_by_type/1, ParsedResult).
  110: 
  111: check_metric_by_type(#{<<"type">> := Type} = Map) ->
  112:     values_are_integers(Map, type_to_keys(Type)).
  113: 
  114: type_to_keys(<<"histogram">>) ->
  115:     [<<"n">>, <<"mean">>,  <<"min">>,  <<"max">>,  <<"median">>,
  116:      <<"p50">>, <<"p75">>, <<"p90">>, <<"p95">>,  <<"p99">>, <<"p999">>];
  117: type_to_keys(<<"counter">>) ->
  118:     [<<"value">>, <<"ms_since_reset">>];
  119: type_to_keys(<<"spiral">>) ->
  120:     [<<"one">>, <<"count">>];
  121: type_to_keys(<<"gauge">>) ->
  122:     [<<"value">>];
  123: type_to_keys(<<"merged_inet_stats">>) ->
  124:     [<<"connections">>, <<"recv_cnt">>, <<"recv_max">>, <<"recv_oct">>,
  125:      <<"send_cnt">>, <<"send_max">>, <<"send_oct">>, <<"send_pend">>];
  126: type_to_keys(<<"rdbms_stats">>) ->
  127:     [<<"workers">>, <<"recv_cnt">>, <<"recv_max">>, <<"recv_oct">>,
  128:      <<"send_cnt">>, <<"send_max">>, <<"send_oct">>, <<"send_pend">>];
  129: type_to_keys(<<"vm_stats_memory">>) ->
  130:     [<<"atom_used">>, <<"binary">>, <<"ets">>,
  131:      <<"processes_used">>, <<"system">>, <<"total">>];
  132: type_to_keys(<<"vm_system_info">>) ->
  133:     [<<"ets_limit">>, <<"port_count">>, <<"port_limit">>,
  134:      <<"process_count">>, <<"process_limit">>];
  135: type_to_keys(<<"probe_queues">>) ->
  136:     [<<"fsm">>, <<"regular">>, <<"total">>];
  137: type_to_keys(<<"cets_system">>) ->
  138:     [<<"available_nodes">>, <<"unavailable_nodes">>,
  139:     <<"remote_nodes_without_disco">>, <<"joined_nodes">>,
  140:     <<"remote_nodes_with_unknown_tables">>, <<"remote_unknown_tables">>,
  141:     <<"remote_nodes_with_missing_tables">>, <<"remote_missing_tables">>,
  142:     <<"conflict_nodes">>, <<"conflict_tables">>,
  143:     <<"discovered_nodes">>, <<"discovery_works">>].
  144: 
  145: get_by_name_global_erlang_metrics(Config) ->
  146:     %% Filter by name works
  147:     Result = get_metrics([<<"global">>, <<"erlang">>], Config),
  148:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
  149:     Map = maps:from_list([{Name, X} || X = #{<<"name">> := Name} <- ParsedResult]),
  150:     Info = maps:get([<<"global">>, <<"erlang">>, <<"system_info">>], Map),
  151:     %% VMSystemInfoMetric type
  152:     #{<<"type">> := <<"vm_system_info">>} = Info,
  153:     check_metric_by_type(Info),
  154:     ReadsKey = [<<"global">>, <<"backends">>, <<"mod_roster">>, <<"read_roster_version">>],
  155:     %% Other metrics are filtered out
  156:     undef = maps:get(ReadsKey, Map, undef).
  157: 
  158: get_metrics_by_name_empty_args(Config) ->
  159:     Result = get_metrics([], Config),
  160:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
  161:     lists:foreach(fun check_metric_by_type/1, ParsedResult),
  162:     [_|_] = ParsedResult.
  163: 
  164: get_metrics_by_name_empty_string(Config) ->
  165:     Result = get_metrics([<<>>], Config),
  166:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
  167:     [] = ParsedResult.
  168: 
  169: get_metrics_by_nonexistent_name(Config) ->
  170:     Result = get_metrics([<<"not_existing">>], Config),
  171:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
  172:     [] = ParsedResult.
  173: 
  174: get_metrics_for_specific_host_type(Config) ->
  175:     Result = get_metrics([<<"dummy auth">>], Config),
  176:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
  177:     lists:foreach(fun check_metric_by_type/1, ParsedResult),
  178:     [_|_] = ParsedResult.
  179: 
  180: get_process_queue_length(Config) ->
  181:     Result = get_metrics([<<"global">>, <<"processQueueLengths">>], Config),
  182:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
  183:     Map = maps:from_list([{Name, X} || X = #{<<"name">> := Name} <- ParsedResult]),
  184:     Lens = maps:get([<<"global">>, <<"processQueueLengths">>], Map),
  185:     %% ProbeQueuesMetric type
  186:     #{<<"type">> := <<"probe_queues">>} = Lens,
  187:     check_metric_by_type(Lens).
  188: 
  189: get_inet_stats(Config) ->
  190:     Result = get_metrics([<<"global">>, <<"data">>, <<"dist">>], Config),
  191:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
  192:     Map = maps:from_list([{Name, X} || X = #{<<"name">> := Name} <- ParsedResult]),
  193:     Stats = maps:get([<<"global">>, <<"data">>, <<"dist">>], Map),
  194:     %% MergedInetStatsMetric type
  195:     #{<<"type">> := <<"merged_inet_stats">>} = Stats,
  196:     check_metric_by_type(Stats).
  197: 
  198: get_vm_stats_memory(Config) ->
  199:     Result = get_metrics([<<"global">>], Config),
  200:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
  201:     Map = maps:from_list([{Name, X} || X = #{<<"name">> := Name} <- ParsedResult]),
  202:     Mem = maps:get([<<"global">>, <<"erlang">>, <<"memory">>], Map),
  203:     %% VMStatsMemoryMetric type
  204:     #{<<"type">> := <<"vm_stats_memory">>} = Mem,
  205:     check_metric_by_type(Mem).
  206: 
  207: get_cets_system(Config) ->
  208:     Result = get_metrics([<<"global">>, <<"cets">>, <<"system">>], Config),
  209:     ParsedResult = get_ok_value([data, metric, getMetrics], Result),
  210:     [#{<<"type">> := <<"cets_system">>} = Sys] = ParsedResult,
  211:     check_metric_by_type(Sys).
  212: 
  213: get_all_metrics_as_dicts(Config) ->
  214:     Result = get_metrics_as_dicts(Config),
  215:     ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result),
  216:     check_node_result_is_valid(ParsedResult, false).
  217: 
  218: get_by_name_metrics_as_dicts(Config) ->
  219:     Result = get_metrics_as_dicts_by_name([<<"_">>, <<"xmppStanzaSent">>], Config),
  220:     ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result),
  221:     [_|_] = ParsedResult,
  222:     %% Only xmppStanzaSent type
  223:     lists:foreach(fun(#{<<"dict">> := Dict, <<"name">> := [_, <<"xmppStanzaSent">>]}) ->
  224:                           check_spiral_dict(Dict)
  225:             end, ParsedResult).
  226: 
  227: get_metrics_as_dicts_by_nonexistent_name(Config) ->
  228:     Result = get_metrics_as_dicts_by_name([<<"not_existing">>], Config),
  229:     ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result),
  230:     [] = ParsedResult.
  231: 
  232: get_metrics_as_dicts_with_key_one(Config) ->
  233:     Result = get_metrics_as_dicts_with_keys([<<"one">>], Config),
  234:     ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result),
  235:     Map = dict_objects_to_map(ParsedResult),
  236:     SentName = [metric_host_type(), <<"xmppStanzaSent">>],
  237:     [#{<<"key">> := <<"one">>, <<"value">> := One}] = maps:get(SentName, Map),
  238:     ?assert(is_integer(One)).
  239: 
  240: get_metrics_as_dicts_with_nonexistent_key(Config) ->
  241:     Result = get_metrics_as_dicts_with_keys([<<"not_existing">>], Config),
  242:     ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result),
  243:     Map = dict_objects_to_map(ParsedResult),
  244:     SentName = [<<"global">>, <<"data">>, <<"xmpp">>, <<"received">>, <<"xml_stanza_size">>],
  245:     [] = maps:get(SentName, Map).
  246: 
  247: get_metrics_as_dicts_empty_args(Config) ->
  248:     %% Empty name
  249:     Result = get_metrics_as_dicts([], [<<"median">>], Config),
  250:     ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result),
  251:     Map = dict_objects_to_map(ParsedResult),
  252:     SentName = [<<"global">>, <<"data">>, <<"xmpp">>, <<"received">>, <<"xml_stanza_size">>],
  253:     [#{<<"key">> := <<"median">>, <<"value">> := Median}] = maps:get(SentName, Map),
  254:     ?assert(is_integer(Median)),
  255:     %% Empty keys
  256:     Result2 = get_metrics_as_dicts([<<"global">>, <<"erlang">>], [], Config),
  257:     ParsedResult2 = get_ok_value([data, metric, getMetricsAsDicts], Result2),
  258:     ?assertEqual(length(ParsedResult2), 2).
  259: 
  260: get_metrics_as_dicts_empty_strings(Config) ->
  261:     %% Name is an empty string
  262:     Result = get_metrics_as_dicts([<<>>], [<<"median">>], Config),
  263:     ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result),
  264:     [] = ParsedResult,
  265:     %% Key is an empty string
  266:     Result2 = get_metrics_as_dicts([<<"global">>, <<"erlang">>], [<<>>], Config),
  267:     ParsedResult2 = get_ok_value([data, metric, getMetricsAsDicts], Result2),
  268:     [_|_] = ParsedResult2.
  269: 
  270: get_cluster_metrics(Config) ->
  271:     %% We will have at least these two nodes
  272:     Node1 = atom_to_binary(maps:get(node, distributed_helper:mim())),
  273:     Node2 = atom_to_binary(maps:get(node, distributed_helper:mim2())),
  274:     Result = get_cluster_metrics_as_dicts(Config),
  275:     ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result),
  276:     #{Node1 := Res1, Node2 := Res2} = node_objects_to_map(ParsedResult),
  277:     check_node_result_is_valid(Res1, false),
  278:     check_node_result_is_valid(Res2, true).
  279: 
  280: get_by_name_cluster_metrics_as_dicts(Config) ->
  281:     Result = get_cluster_metrics_as_dicts_by_name([<<"_">>, <<"xmppStanzaSent">>], Config),
  282:     NodeResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result),
  283:     Map = node_objects_to_map(NodeResult),
  284:     %% Contains data for at least two nodes
  285:     ?assert(maps:size(Map) > 1),
  286:     %% Only xmppStanzaSent type
  287:     maps:map(fun(_Node, [_|_] = NodeRes) ->
  288:         lists:foreach(fun(#{<<"dict">> := Dict,
  289:                             <<"name">> := [_, <<"xmppStanzaSent">>]}) ->
  290:                               check_spiral_dict(Dict)
  291:                 end, NodeRes) end, Map).
  292: 
  293: get_mim2_cluster_metrics(Config) ->
  294:     Node = atom_to_binary(maps:get(node, distributed_helper:mim2())),
  295:     Result = get_cluster_metrics_as_dicts_for_nodes([Node], Config),
  296:     ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result),
  297:     [#{<<"node">> := Node, <<"result">> := ResList}] = ParsedResult,
  298:     check_node_result_is_valid(ResList, true).
  299: 
  300: get_cluster_metrics_for_nonexistent_nodes(Config) ->
  301:     Result = get_cluster_metrics_as_dicts_for_nodes([<<"nonexistent">>], Config),
  302:     ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result),
  303:     [#{<<"node">> := _, <<"result">> := ResList}] = ParsedResult,
  304:     [#{<<"dict">> := [], <<"name">> := ErrorResult}] = ResList,
  305:     ?assert(ErrorResult == [<<"error">>, <<"nodedown">>]).
  306: 
  307: get_cluster_metrics_by_nonexistent_name(Config) ->
  308:     Result = get_cluster_metrics_as_dicts_by_name([<<"nonexistent">>], Config),
  309:     ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result),
  310:     [#{<<"node">> := _, <<"result">> := []},
  311:      #{<<"node">> := _, <<"result">> := []}|_] = ParsedResult. %% two or three nodes.
  312: 
  313: get_cluster_metrics_with_nonexistent_key(Config) ->
  314:     Result = get_cluster_metrics_as_dicts_with_keys([<<"nonexistent">>], Config),
  315:     ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result),
  316:     [#{<<"node">> := _, <<"result">> := [_|_]},
  317:      #{<<"node">> := _, <<"result">> := [_|_]}|_] = ParsedResult.
  318: 
  319: get_cluster_metrics_empty_args(Config) ->
  320:     Node = atom_to_binary(maps:get(node, distributed_helper:mim2())),
  321:     %% Empty name
  322:     Result = get_cluster_metrics_as_dicts([], [<<"one">>], [Node], Config),
  323:     ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result),
  324:     [#{<<"node">> := Node, <<"result">> := ResList}] = ParsedResult,
  325:     Map = dict_objects_to_map(ResList),
  326:     SentName = [<<"global">>, <<"xmppStanzaSent">>],
  327:     [#{<<"key">> := <<"one">>, <<"value">> := One}] = maps:get(SentName, Map),
  328:     ?assert(is_integer(One)),
  329:     %% Empty keys
  330:     Result2 = get_cluster_metrics_as_dicts([<<"_">>], [], [Node], Config),
  331:     ParsedResult2 = get_ok_value([data, metric, getClusterMetricsAsDicts], Result2),
  332:     [#{<<"node">> := Node, <<"result">> := ResList2}] = ParsedResult2,
  333:     check_node_result_is_valid(ResList2, true),
  334:     %% Empty nodes
  335:     Result3 = get_cluster_metrics_as_dicts([<<"_">>, <<"erlang">>], [<<"ets_limit">>], [], Config),
  336:     ParsedResult3 = get_ok_value([data, metric, getClusterMetricsAsDicts], Result3),
  337:     NodeMap = node_objects_to_map(ParsedResult3),
  338:     ?assert(maps:size(NodeMap) > 1).
  339: 
  340: get_cluster_metrics_empty_strings(Config) ->
  341:     Node = atom_to_binary(maps:get(node, distributed_helper:mim2())),
  342:     %% Name is an empty string
  343:     Result = get_cluster_metrics_as_dicts([<<>>], [<<"median">>], [Node], Config),
  344:     ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result),
  345:     [#{<<"node">> := Node, <<"result">> := []}] = ParsedResult,
  346:     %% Key is an empty string
  347:     Result2 = get_cluster_metrics_as_dicts([<<"_">>], [<<>>], [Node], Config),
  348:     ParsedResult2 = get_ok_value([data, metric, getClusterMetricsAsDicts], Result2),
  349:     [#{<<"node">> := Node, <<"result">> := [_|_]}] = ParsedResult2,
  350:     %% Node is an empty string
  351:     Result3 = get_cluster_metrics_as_dicts([<<"_">>], [<<"median">>], [<<>>], Config),
  352:     ParsedResult3 = get_ok_value([data, metric, getClusterMetricsAsDicts], Result3),
  353:     [#{<<"node">> := _, <<"result">> := ResList}] = ParsedResult3,
  354:     [#{<<"dict">> := [], <<"name">> := ErrorResult}] = ResList,
  355:     ?assert(ErrorResult == [<<"error">>, <<"nodedown">>]).
  356: 
  357: check_node_result_is_valid(ResList, MetricsAreGlobal) ->
  358:     %% Check that result contains something
  359:     Map = dict_objects_to_map(ResList),
  360:     SentName = case MetricsAreGlobal of
  361:             true -> [<<"global">>, <<"xmppStanzaSent">>];
  362:             false -> [metric_host_type(), <<"xmppStanzaSent">>]
  363:         end,
  364:     check_spiral_dict(maps:get(SentName, Map)),
  365:     [#{<<"key">> := <<"value">>,<<"value">> := V}] =
  366:         maps:get([<<"global">>,<<"uniqueSessionCount">>], Map),
  367:     ?assert(is_integer(V)),
  368:     HistObjects = maps:get([<<"global">>, <<"data">>, <<"xmpp">>,
  369:                             <<"sent">>, <<"xml_stanza_size">>], Map),
  370:     check_histogram(kv_objects_to_map(HistObjects)).
  371: 
  372: check_histogram(Map) ->
  373:     Keys = [<<"n">>, <<"mean">>,  <<"min">>,  <<"max">>,  <<"median">>,
  374:             <<"50">>, <<"75">>, <<"90">>, <<"95">>,  <<"99">>, <<"999">>],
  375:     values_are_integers(Map, Keys).
  376: 
  377: check_histogram_p(Map) ->
  378:     Keys = type_to_keys(<<"histogram">>),
  379:     values_are_integers(Map, Keys).
  380: 
  381: dict_objects_to_map(List) ->
  382:     KV = [{Name, Dict} || #{<<"name">> := Name, <<"dict">> := Dict} <- List],
  383:     maps:from_list(KV).
  384: 
  385: node_objects_to_map(List) ->
  386:     KV = [{Name, Value} || #{<<"node">> := Name, <<"result">> := Value} <- List],
  387:     maps:from_list(KV).
  388: 
  389: kv_objects_to_map(List) ->
  390:     KV = [{Key, Value} || #{<<"key">> := Key, <<"value">> := Value} <- List],
  391:     maps:from_list(KV).
  392: 
  393: %% Domain admin test cases
  394: 
  395: domain_admin_get_metrics(Config) ->
  396:     get_unauthorized(get_metrics(Config)).
  397: 
  398: domain_admin_get_metrics_as_dicts(Config) ->
  399:     get_unauthorized(get_metrics_as_dicts(Config)).
  400: 
  401: domain_admin_get_metrics_as_dicts_by_name(Config) ->
  402:     get_unauthorized(get_metrics_as_dicts_by_name([<<"_">>], Config)).
  403: 
  404: domain_admin_get_metrics_as_dicts_with_keys(Config) ->
  405:     get_unauthorized(get_metrics_as_dicts_with_keys([<<"one">>], Config)).
  406: 
  407: domain_admin_get_cluster_metrics_as_dicts(Config) ->
  408:     get_unauthorized(get_cluster_metrics_as_dicts(Config)).
  409: 
  410: domain_admin_get_cluster_metrics_as_dicts_by_name(Config) ->
  411:     get_unauthorized(get_cluster_metrics_as_dicts_by_name([<<"_">>], Config)).
  412: 
  413: domain_admin_get_cluster_metrics_as_dicts_for_nodes(Config) ->
  414:     Node = atom_to_binary(maps:get(node, distributed_helper:mim2())),
  415:     get_unauthorized(get_cluster_metrics_as_dicts_for_nodes([Node], Config)).
  416: 
  417: %% Admin commands
  418: 
  419: get_metrics(Config) ->
  420:     execute_command(<<"metric">>, <<"getMetrics">>, #{}, Config).
  421: 
  422: get_metrics(Name, Config) ->
  423:     Vars = #{<<"name">> => Name},
  424:     execute_command(<<"metric">>, <<"getMetrics">>, Vars, Config).
  425: 
  426: get_metrics_as_dicts(Config) ->
  427:     execute_command(<<"metric">>, <<"getMetricsAsDicts">>, #{}, Config).
  428: 
  429: get_metrics_as_dicts(Name, Keys, Config) ->
  430:     Vars = #{<<"name">> => Name, <<"keys">> => Keys},
  431:     execute_command(<<"metric">>, <<"getMetricsAsDicts">>, Vars, Config).
  432: 
  433: get_metrics_as_dicts_by_name(Name, Config) ->
  434:     Vars = #{<<"name">> => Name},
  435:     execute_command(<<"metric">>, <<"getMetricsAsDicts">>, Vars, Config).
  436: 
  437: get_metrics_as_dicts_with_keys(Keys, Config) ->
  438:     Vars = #{<<"keys">> => Keys},
  439:     execute_command(<<"metric">>, <<"getMetricsAsDicts">>, Vars, Config).
  440: 
  441: get_cluster_metrics_as_dicts(Config) ->
  442:     execute_command(<<"metric">>, <<"getClusterMetricsAsDicts">>, #{}, Config).
  443: 
  444: get_cluster_metrics_as_dicts(Name, Keys, Nodes, Config) ->
  445:     Vars = #{<<"name">> => Name, <<"nodes">> => Nodes, <<"keys">> => Keys},
  446:     execute_command(<<"metric">>, <<"getClusterMetricsAsDicts">>, Vars, Config).
  447: 
  448: get_cluster_metrics_as_dicts_by_name(Name, Config) ->
  449:     Vars = #{<<"name">> => Name},
  450:     execute_command(<<"metric">>, <<"getClusterMetricsAsDicts">>, Vars, Config).
  451: 
  452: get_cluster_metrics_as_dicts_for_nodes(Nodes, Config) ->
  453:     Vars = #{<<"nodes">> => Nodes},
  454:     execute_command(<<"metric">>, <<"getClusterMetricsAsDicts">>, Vars, Config).
  455: 
  456: get_cluster_metrics_as_dicts_with_keys(Keys, Config) ->
  457:     Vars = #{<<"keys">> => Keys},
  458:     execute_command(<<"metric">>, <<"getClusterMetricsAsDicts">>, Vars, Config).
  459: 
  460: %% Helpers
  461: 
  462: check_spiral_dict(Dict) ->
  463:     [#{<<"key">> := <<"count">>, <<"value">> := Count},
  464:      #{<<"key">> := <<"one">>, <<"value">> := One}] = Dict,
  465:     ?assert(is_integer(Count)),
  466:     ?assert(is_integer(One)).
  467: 
  468: values_are_integers(Map, Keys) ->
  469:     case lists:all(fun(Key) -> is_integer(maps:get(Key, Map)) end, Keys) of
  470:         true ->
  471:             ok;
  472:         false ->
  473:             ct:fail({values_are_integers, Keys, Map})
  474:     end.
  475: 
  476: metric_host_type() ->
  477:     binary:replace(domain_helper:host_type(), <<" ">>, <<"_">>, [global]).
  478: 
  479: is_cets_enabled() ->
  480:     case rpc(mim(), mongoose_config, lookup_opt, [[internal_databases, cets]]) of
  481:         {ok, _} ->
  482:             true;
  483:         _ ->
  484:             false
  485:     end.