refactor: stats api & metrics api; fix: clients api parameter type lose

This commit is contained in:
DDDHuang 2021-07-13 14:32:56 +08:00 committed by turtleDeng
parent 4ed14dfdfd
commit 187d200cb7
8 changed files with 592 additions and 55 deletions

View File

@ -175,7 +175,7 @@ broker_info(Node) ->
%%--------------------------------------------------------------------
get_metrics() ->
[{Node, get_metrics(Node)} || Node <- ekka_mnesia:running_nodes()].
nodes_info_count([get_metrics(Node) || Node <- ekka_mnesia:running_nodes()]).
get_metrics(Node) when Node =:= node() ->
emqx_metrics:all();
@ -183,13 +183,44 @@ get_metrics(Node) ->
rpc_call(Node, get_metrics, [Node]).
get_stats() ->
[{Node, get_stats(Node)} || Node <- ekka_mnesia:running_nodes()].
GlobalStatsKeys =
[ 'retained.count'
, 'retained.max'
, 'routes.count'
, 'routes.max'
, 'subscriptions.shared.count'
, 'subscriptions.shared.max'
],
CountStats = nodes_info_count([
begin
Stats = get_stats(Node),
delete_keys(Stats, GlobalStatsKeys)
end || Node <- ekka_mnesia:running_nodes()]),
GlobalStats = maps:with(GlobalStatsKeys, maps:from_list(get_stats(node()))),
maps:merge(CountStats, GlobalStats).
delete_keys(List, []) ->
List;
delete_keys(List, [Key | Keys]) ->
delete_keys(proplists:delete(Key, List), Keys).
get_stats(Node) when Node =:= node() ->
emqx_stats:getstats();
get_stats(Node) ->
rpc_call(Node, get_stats, [Node]).
nodes_info_count(PropList) ->
NodeCount =
fun({Key, Value}, Result) ->
Count = maps:get(Key, Result, 0),
Result#{Key => Count + Value}
end,
AllCount =
fun(StatsMap, Result) ->
lists:foldl(NodeCount, Result, StatsMap)
end,
lists:foldl(AllCount, #{}, PropList).
%%--------------------------------------------------------------------
%% Clients
%%--------------------------------------------------------------------

View File

@ -313,6 +313,7 @@ subscribe_api() ->
#{
name => topic,
in => query,
type => string,
required => true,
default => <<"topic_1">>
}

View File

@ -16,27 +16,277 @@
-module(emqx_mgmt_api_metrics).
-rest_api(#{name => list_all_metrics,
method => 'GET',
path => "/metrics",
func => list,
descr => "A list of metrics of all nodes in the cluster"}).
-behavior(minirest_api).
-rest_api(#{name => list_node_metrics,
method => 'GET',
path => "/nodes/:atom:node/metrics",
func => list,
descr => "A list of metrics of a node"}).
-export([api_spec/0]).
-export([list/2]).
list(Bindings, _Params) when map_size(Bindings) == 0 ->
emqx_mgmt:return({ok, [#{node => Node, metrics => maps:from_list(Metrics)}
|| {Node, Metrics} <- emqx_mgmt:get_metrics()]});
api_spec() ->
{[metrics_api()], [metrics_schema()]}.
list(#{node := Node}, _Params) ->
case emqx_mgmt:get_metrics(Node) of
{error, Reason} -> emqx_mgmt:return({error, Reason});
Metrics -> emqx_mgmt:return({ok, maps:from_list(Metrics)})
end.
metrics_schema() ->
DefinitionName = <<"metrics">>,
DefinitionProperties = #{
<<"actions.failure">> => #{
type => <<"integer">>,
description => <<"Number of failure executions of the rule engine action">>},
<<"actions.success">> => #{
type => <<"integer">>,
description => <<"Number of successful executions of the rule engine action">>},
<<"bytes.received">> => #{
type => <<"integer">>,
description => <<"Number of bytes received by EMQ X Broker">>},
<<"bytes.sent">> => #{
type => <<"integer">>,
description => <<"Number of bytes sent by EMQ X Broker on this connection">>},
<<"client.authenticate">> => #{
type => <<"integer">>,
description => <<"Number of client authentications">>},
<<"client.auth.anonymous">> => #{
type => <<"integer">>,
description => <<"Number of clients who log in anonymously">>},
<<"client.connect">> => #{
type => <<"integer">>,
description => <<"Number of client connections">>},
<<"client.connack">> => #{
type => <<"integer">>,
description => <<"Number of CONNACK packet sent">>},
<<"client.connected">> => #{
type => <<"integer">>,
description => <<"Number of successful client connections">>},
<<"client.disconnected">> => #{
type => <<"integer">>,
description => <<"Number of client disconnects">>},
<<"client.check_acl">> => #{
type => <<"integer">>,
description => <<"Number of ACL rule checks">>},
<<"client.subscribe">> => #{
type => <<"integer">>,
description => <<"Number of client subscriptions">>},
<<"client.unsubscribe">> => #{
type => <<"integer">>,
description => <<"Number of client unsubscriptions">>},
<<"delivery.dropped.too_large">> => #{
type => <<"integer">>,
description => <<"The number of messages that were dropped because the length exceeded the limit when sending">>},
<<"delivery.dropped.queue_full">> => #{
type => <<"integer">>,
description => <<"Number of messages with a non-zero QoS that were dropped because the message queue was full when sending">>},
<<"delivery.dropped.qos0_msg">> => #{
type => <<"integer">>,
description => <<"Number of messages with QoS 0 that were dropped because the message queue was full when sending">>},
<<"delivery.dropped.expired">> => #{
type => <<"integer">>,
description => <<"Number of messages dropped due to message expiration on sending">>},
<<"delivery.dropped.no_local">> => #{
type => <<"integer">>,
description => <<"Number of messages that were dropped due to the No Local subscription option when sending">>},
<<"delivery.dropped">> => #{
type => <<"integer">>,
description => <<"Total number of discarded messages when sending">>},
<<"messages.delayed">> => #{
type => <<"integer">>,
description => <<"Number of delay- published messages stored by EMQ X Broker">>},
<<"messages.delivered">> => #{
type => <<"integer">>,
description => <<"Number of messages forwarded to the subscription process internally by EMQ X Broker">>},
<<"messages.dropped">> => #{
type => <<"integer">>,
description => <<"Total number of messages dropped by EMQ X Broker before forwarding to the subscription process">>},
<<"messages.dropped.expired">> => #{
type => <<"integer">>,
description => <<"Number of messages dropped due to message expiration when receiving">>},
<<"messages.dropped.no_subscribers">> => #{
type => <<"integer">>,
description => <<"Number of messages dropped due to no subscribers">>},
<<"messages.forward">> => #{
type => <<"integer">>,
description => <<"Number of messages forwarded to other nodes">>},
<<"messages.publish">> => #{
type => <<"integer">>,
description => <<"Number of messages published in addition to system messages">>},
<<"messages.qos0.received">> => #{
type => <<"integer">>,
description => <<"Number of QoS 0 messages received from clients">>},
<<"messages.qos1.received">> => #{
type => <<"integer">>,
description => <<"Number of QoS 1 messages received from clients">>},
<<"messages.qos2.received">> => #{
type => <<"integer">>,
description => <<"Number of QoS 2 messages received from clients">>},
<<"messages.qos0.sent">> => #{
type => <<"integer">>,
description => <<"Number of QoS 0 messages sent to clients">>},
<<"messages.qos1.sent">> => #{
type => <<"integer">>,
description => <<"Number of QoS 1 messages sent to clients">>},
<<"messages.qos2.sent">> => #{
type => <<"integer">>,
description => <<"Number of QoS 2 messages sent to clients">>},
<<"messages.received">> => #{
type => <<"integer">>,
description => <<"Number of messages received from the client, equal to the sum of messages.qos0.receivedmessages.qos1.received and messages.qos2.received">>},
<<"messages.sent">> => #{
type => <<"integer">>,
description => <<"Number of messages sent to the client, equal to the sum of messages.qos0.sentmessages.qos1.sent and messages.qos2.sent">>},
<<"messages.retained">> => #{
type => <<"integer">>,
description => <<"Number of retained messages stored by EMQ X Broker">>},
<<"messages.acked">> => #{
type => <<"integer">>,
description => <<"Number of received PUBACK and PUBREC packet">>},
<<"packets.received">> => #{
type => <<"integer">>,
description => <<"Number of received packet">>},
<<"packets.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent packet">>},
<<"packets.connect.received">> => #{
type => <<"integer">>,
description => <<"Number of received CONNECT packet">>},
<<"packets.connack.auth_error">> => #{
type => <<"integer">>,
description => <<"Number of received CONNECT packet with failed authentication">>},
<<"packets.connack.error">> => #{
type => <<"integer">>,
description => <<"Number of received CONNECT packet with unsuccessful connections">>},
<<"packets.connack.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent CONNACK packet">>},
<<"packets.publish.received">> => #{
type => <<"integer">>,
description => <<"Number of received PUBLISH packet">>},
<<"packets.publish.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent PUBLISH packet">>},
<<"packets.publish.inuse">> => #{
type => <<"integer">>,
description => <<"Number of received PUBLISH packet with occupied identifiers">>},
<<"packets.publish.auth_error">> => #{
type => <<"integer">>,
description => <<"Number of received PUBLISH packets with failed the ACL check">>},
<<"packets.publish.error">> => #{
type => <<"integer">>,
description => <<"Number of received PUBLISH packet that cannot be published">>},
<<"packets.publish.dropped">> => #{
type => <<"integer">>,
description => <<"Number of messages discarded due to the receiving limit">>},
<<"packets.puback.received">> => #{
type => <<"integer">>,
description => <<"Number of received PUBACK packet">>},
<<"packets.puback.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent PUBACK packet">>},
<<"packets.puback.inuse">> => #{
type => <<"integer">>,
description => <<"Number of received PUBACK packet with occupied identifiers">>},
<<"packets.puback.missed">> => #{
type => <<"integer">>,
description => <<"Number of received packet with identifiers.">>},
<<"packets.pubrec.received">> => #{
type => <<"integer">>,
description => <<"Number of received PUBREC packet">>},
<<"packets.pubrec.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent PUBREC packet">>},
<<"packets.pubrec.inuse">> => #{
type => <<"integer">>,
description => <<"Number of received PUBREC packet with occupied identifiers">>},
<<"packets.pubrec.missed">> => #{
type => <<"integer">>,
description => <<"Number of received PUBREC packet with unknown identifiers">>},
<<"packets.pubrel.received">> => #{
type => <<"integer">>,
description => <<"Number of received PUBREL packet">>},
<<"packets.pubrel.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent PUBREL packet">>},
<<"packets.pubrel.missed">> => #{
type => <<"integer">>,
description => <<"Number of received PUBREC packet with unknown identifiers">>},
<<"packets.pubcomp.received">> => #{
type => <<"integer">>,
description => <<"Number of received PUBCOMP packet">>},
<<"packets.pubcomp.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent PUBCOMP packet">>},
<<"packets.pubcomp.inuse">> => #{
type => <<"integer">>,
description => <<"Number of received PUBCOMP packet with occupied identifiers">>},
<<"packets.pubcomp.missed">> => #{
type => <<"integer">>,
description => <<"Number of missed PUBCOMP packet">>},
<<"packets.subscribe.received">> => #{
type => <<"integer">>,
description => <<"Number of received SUBSCRIBE packet">>},
<<"packets.subscribe.error">> => #{
type => <<"integer">>,
description => <<"Number of received SUBSCRIBE packet with failed subscriptions">>},
<<"packets.subscribe.auth_error">> => #{
type => <<"integer">>,
description => <<"Number of received SUBACK packet with failed ACL check">>},
<<"packets.suback.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent SUBACK packet">>},
<<"packets.unsubscribe.received">> => #{
type => <<"integer">>,
description => <<"Number of received UNSUBSCRIBE packet">>},
<<"packets.unsubscribe.error">> => #{
type => <<"integer">>,
description => <<"Number of received UNSUBSCRIBE packet with failed unsubscriptions">>},
<<"packets.unsuback.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent UNSUBACK packet">>},
<<"packets.pingreq.received">> => #{
type => <<"integer">>,
description => <<"Number of received PINGREQ packet">>},
<<"packets.pingresp.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent PUBRESP packet">>},
<<"packets.disconnect.received">> => #{
type => <<"integer">>,
description => <<"Number of received DISCONNECT packet">>},
<<"packets.disconnect.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent DISCONNECT packet">>},
<<"packets.auth.received">> => #{
type => <<"integer">>,
description => <<"Number of received AUTH packet">>},
<<"packets.auth.sent">> => #{
type => <<"integer">>,
description => <<"Number of sent AUTH packet">>},
<<"rules.matched">> => #{
type => <<"integer">>,
description => <<"Number of rule matched">>},
<<"session.created">> => #{
type => <<"integer">>,
description => <<"Number of sessions created">>},
<<"session.discarded">> => #{
type => <<"integer">>,
description => <<"Number of sessions dropped because Clean Session or Clean Start is true">>},
<<"session.resumed">> => #{
type => <<"integer">>,
description => <<"Number of sessions resumed because Clean Session or Clean Start is false">>},
<<"session.takeovered">> => #{
type => <<"integer">>,
description => <<"Number of sessions takeovered because Clean Session or Clean Start is false">>},
<<"session.terminated">> => #{
type => <<"integer">>,
description => <<"Number of terminated sessions">>}},
{DefinitionName, DefinitionProperties}.
metrics_api() ->
Metadata = #{
get => #{
description => "EMQ X metrics",
responses => #{
<<"200">> => #{
schema => cowboy_swagger:schema(<<"metrics">>)}}}},
{"/metrics", Metadata, list}.
%%%==============================================================================================
%% api apply
list(get, _) ->
Response = emqx_json:encode(emqx_mgmt:get_metrics()),
{200, Response}.

View File

@ -20,7 +20,9 @@
-export([api_spec/0]).
-export([ nodes/2
, node/2]).
, node/2
, node_metrics/2
, node_stats/2]).
-include_lib("emqx/include/emqx.hrl").
@ -29,9 +31,13 @@ api_spec() ->
apis() ->
[ nodes_api()
, node_api()].
, node_api()
, node_metrics_api()
, node_stats_api()].
schemas() ->
%% notice: node api used schema metrics and stats
%% see these schema in emqx_mgmt_api_metrics emqx_mgmt_api_status
[node_schema()].
node_schema() ->
@ -121,15 +127,60 @@ node_api() ->
schema => cowboy_swagger:schema(<<"node">>)}}}},
{"/nodes/:node_name", Metadata, node}.
node_metrics_api() ->
Metadata = #{
get => #{
description => "Get node metrics",
parameters => [#{
name => node_name,
in => path,
description => "node name",
type => string,
required => true,
default => node()}],
responses => #{
<<"400">> =>
emqx_mgmt_util:not_found_schema(<<"Node error">>, [<<"SOURCE_ERROR">>]),
<<"200">> => #{
description => <<"Get EMQ X Node Metrics">>,
schema => cowboy_swagger:schema(<<"metrics">>)}}}},
{"/nodes/:node_name/metrics", Metadata, node_metrics}.
node_stats_api() ->
Metadata = #{
get => #{
description => "Get node stats",
parameters => [#{
name => node_name,
in => path,
description => "node name",
type => string,
required => true,
default => node()}],
responses => #{
<<"400">> =>
emqx_mgmt_util:not_found_schema(<<"Node error">>, [<<"SOURCE_ERROR">>]),
<<"200">> => #{
description => <<"Get EMQ X Node Stats">>,
schema => cowboy_swagger:schema(<<"stats">>)}}}},
{"/nodes/:node_name/stats", Metadata, node_metrics}.
%%%==============================================================================================
%% parameters trans
nodes(get, _Request) ->
list(#{}).
node(get, Request) ->
NodeName = cowboy_req:binding(node_name, Request),
Node = binary_to_atom(NodeName, utf8),
get_node(#{node => Node}).
Params = node_name_path_parameter(Request),
get_node(Params).
node_metrics(get, Request) ->
Params = node_name_path_parameter(Request),
get_metrics(Params).
node_stats(get, Request) ->
Params = node_name_path_parameter(Request),
get_stats(Params).
%%%==============================================================================================
%% api apply
@ -147,8 +198,29 @@ get_node(#{node := Node}) ->
{200, Response}
end.
get_metrics(#{node := Node}) ->
case emqx_mgmt:get_metrics(Node) of
{error, _} ->
{400, emqx_json:encode(#{code => 'SOURCE_ERROR', reason => <<"rpc_failed">>})};
Metrics ->
{200, emqx_json:encode(Metrics)}
end.
get_stats(#{node := Node}) ->
case emqx_mgmt:get_stats(Node) of
{error, _} ->
{400, emqx_json:encode(#{code => 'SOURCE_ERROR', reason => <<"rpc_failed">>})};
Stats ->
{200, emqx_json:encode(Stats)}
end.
%%============================================================================================================
%% internal function
node_name_path_parameter(Request) ->
NodeName = cowboy_req:binding(node_name, Request),
Node = binary_to_atom(NodeName, utf8),
#{node => Node}.
format(_Node, Info = #{memory_total := Total, memory_used := Used}) ->
{ok, SysPathBinary} = file:get_cwd(),
SysPath = list_to_binary(SysPathBinary),

View File

@ -13,33 +13,93 @@
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_mgmt_api_stats).
-rest_api(#{name => list_stats,
method => 'GET',
path => "/stats/",
func => list,
descr => "A list of stats of all nodes in the cluster"}).
-behavior(minirest_api).
-rest_api(#{name => lookup_node_stats,
method => 'GET',
path => "/nodes/:atom:node/stats/",
func => lookup,
descr => "A list of stats of a node"}).
-export([api_spec/0]).
-export([ list/2
, lookup/2
]).
-export([list/2]).
%% List stats of all nodes
list(Bindings, _Params) when map_size(Bindings) == 0 ->
emqx_mgmt:return({ok, [#{node => Node, stats => maps:from_list(Stats)}
|| {Node, Stats} <- emqx_mgmt:get_stats()]}).
api_spec() ->
{stats_api(), stats_schema()}.
%% List stats of a node
lookup(#{node := Node}, _Params) ->
case emqx_mgmt:get_stats(Node) of
{error, Reason} -> emqx_mgmt:return({error, Reason});
Stats -> emqx_mgmt:return({ok, maps:from_list(Stats)})
end.
stats_schema() ->
DefinitionName = <<"stats">>,
DefinitionProperties = #{
<<"connections.count">> => #{
type => <<"integer">>,
description => <<"Number of current connections">>},
<<"connections.max">> => #{
type => <<"integer">>,
description => <<"Historical maximum number of connections">>},
<<"channels.count">> => #{
type => <<"integer">>,
description => <<"sessions.count">>},
<<"channels.max">> => #{
type => <<"integer">>,
description => <<"session.max">>},
<<"sessions.count">> => #{
type => <<"integer">>,
description => <<"Number of current sessions">>},
<<"sessions.max">> => #{
type => <<"integer">>,
description => <<"Historical maximum number of sessions">>},
<<"topics.count">> => #{
type => <<"integer">>,
description => <<"Number of current topics">>},
<<"topics.max">> => #{
type => <<"integer">>,
description => <<"Historical maximum number of topics">>},
<<"suboptions.count">> => #{
type => <<"integer">>,
description => <<"subscriptions.count">>},
<<"suboptions.max">> => #{
type => <<"integer">>,
description => <<"subscriptions.max">>},
<<"subscribers.count">> => #{
type => <<"integer">>,
description => <<"Number of current subscribers">>},
<<"subscribers.max">> => #{
type => <<"integer">>,
description => <<"Historical maximum number of subscribers">>},
<<"subscriptions.count">> => #{
type => <<"integer">>,
description => <<"Number of current subscriptions, including shared subscriptions">>},
<<"subscriptions.max">> => #{
type => <<"integer">>,
description => <<"Historical maximum number of subscriptions">>},
<<"subscriptions.shared.count">> => #{
type => <<"integer">>,
description => <<"Number of current shared subscriptions">>},
<<"subscriptions.shared.max">> => #{
type => <<"integer">>,
description => <<"Historical maximum number of shared subscriptions">>},
<<"routes.count">> => #{
type => <<"integer">>,
description => <<"Number of current routes">>},
<<"routes.max">> => #{
type => <<"integer">>,
description => <<"Historical maximum number of routes">>},
<<"retained.count">> => #{
type => <<"integer">>,
description => <<"Number of currently retained messages">>},
<<"retained.max">> => #{
type => <<"integer">>,
description => <<"Historical maximum number of retained messages">>}},
[{DefinitionName, DefinitionProperties}].
stats_api() ->
Metadata = #{
get => #{
description => "EMQ X stats",
responses => #{
<<"200">> => #{
schema => cowboy_swagger:schema(<<"stats">>)}}}},
[{"/stats", Metadata, list}].
%%%==============================================================================================
%% api apply
list(get, _Request) ->
Response = emqx_json:encode(emqx_mgmt:get_stats()),
{200, Response}.

View File

@ -0,0 +1,52 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_mgmt_metrics_api_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
all() ->
emqx_ct:all(?MODULE).
init_per_suite(Config) ->
ekka_mnesia:start(),
emqx_mgmt_auth:mnesia(boot),
emqx_ct_helpers:start_apps([emqx_management], fun set_special_configs/1),
Config.
end_per_suite(_) ->
emqx_ct_helpers:stop_apps([emqx_management]).
set_special_configs(emqx_management) ->
emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}],
applications =>[#{id => "admin", secret => "public"}]}),
ok;
set_special_configs(_App) ->
ok.
t_metrics_api(_) ->
MetricsPath = emqx_mgmt_api_test_util:api_path(["metrics"]),
SystemMetrics = emqx_mgmt:get_metrics(),
{ok, MetricsResponse} = emqx_mgmt_api_test_util:request_api(get, MetricsPath),
Metrics = emqx_json:decode(MetricsResponse, [return_maps]),
?assertEqual(erlang:length(maps:keys(SystemMetrics)), erlang:length(maps:keys(Metrics))),
Fun =
fun(Key) ->
?assertEqual(maps:get(Key, SystemMetrics), maps:get(atom_to_binary(Key, utf8), Metrics))
end,
lists:foreach(Fun, maps:keys(SystemMetrics)).

View File

@ -20,11 +20,6 @@
-include_lib("eunit/include/eunit.hrl").
-define(APP, emqx_management).
-define(SERVER, "http://127.0.0.1:8081").
-define(BASE_PATH, "/api/v5").
all() ->
emqx_ct:all(?MODULE).
@ -54,5 +49,29 @@ t_nodes_api(_) ->
NodePath = emqx_mgmt_api_test_util:api_path(["nodes", atom_to_list(node())]),
{ok, NodeInfo} = emqx_mgmt_api_test_util:request_api(get, NodePath),
NodeNameResponse = binary_to_atom(maps:get(<<"node">>, emqx_json:decode(NodeInfo, [return_maps])), utf8),
NodeNameResponse =
binary_to_atom(maps:get(<<"node">>, emqx_json:decode(NodeInfo, [return_maps])), utf8),
?assertEqual(node(), NodeNameResponse).
t_node_stats_api() ->
StatsPath = emqx_mgmt_api_test_util:api_path(["nodes", atom_to_binary(node(), utf8), "stats"]),
SystemStats= emqx_mgmt:get_stats(),
{ok, StatsResponse} = emqx_mgmt_api_test_util:request_api(get, StatsPath),
Stats = emqx_json:decode(StatsResponse, [return_maps]),
Fun =
fun(Key) ->
?assertEqual(maps:get(Key, SystemStats), maps:get(atom_to_binary(Key, utf8), Stats))
end,
lists:foreach(Fun, maps:keys(SystemStats)).
t_node_metrics_api() ->
MetricsPath =
emqx_mgmt_api_test_util:api_path(["nodes", atom_to_binary(node(), utf8), "metrics"]),
SystemMetrics= emqx_mgmt:get_metrics(),
{ok, MetricsResponse} = emqx_mgmt_api_test_util:request_api(get, MetricsPath),
Metrics = emqx_json:decode(MetricsResponse, [return_maps]),
Fun =
fun(Key) ->
?assertEqual(maps:get(Key, SystemMetrics), maps:get(atom_to_binary(Key, utf8), Metrics))
end,
lists:foreach(Fun, maps:keys(SystemMetrics)).

View File

@ -0,0 +1,52 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_mgmt_stats_api_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
all() ->
emqx_ct:all(?MODULE).
init_per_suite(Config) ->
ekka_mnesia:start(),
emqx_mgmt_auth:mnesia(boot),
emqx_ct_helpers:start_apps([emqx_management], fun set_special_configs/1),
Config.
end_per_suite(_) ->
emqx_ct_helpers:stop_apps([emqx_management]).
set_special_configs(emqx_management) ->
emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}],
applications =>[#{id => "admin", secret => "public"}]}),
ok;
set_special_configs(_App) ->
ok.
t_stats_api(_) ->
StatsPath = emqx_mgmt_api_test_util:api_path(["stats"]),
SystemStats = emqx_mgmt:get_stats(),
{ok, StatsResponse} = emqx_mgmt_api_test_util:request_api(get, StatsPath),
Stats = emqx_json:decode(StatsResponse, [return_maps]),
?assertEqual(erlang:length(maps:keys(SystemStats)), erlang:length(maps:keys(Stats))),
Fun =
fun(Key) ->
?assertEqual(maps:get(Key, SystemStats), maps:get(atom_to_binary(Key, utf8), Stats))
end,
lists:foreach(Fun, maps:keys(SystemStats)).