Merge branch 'master' into emqx_config
This commit is contained in:
commit
bb6d7c4e0b
|
@ -13,7 +13,7 @@
|
||||||
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
|
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
|
||||||
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}}
|
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}}
|
||||||
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
|
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
|
||||||
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.2"}}}
|
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.3"}}}
|
||||||
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
|
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
|
||||||
, {cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v4.0.1"}}} %% todo delete when plugins use hocon
|
, {cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v4.0.1"}}} %% todo delete when plugins use hocon
|
||||||
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.10.1"}}}
|
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.10.1"}}}
|
||||||
|
|
|
@ -175,7 +175,7 @@ broker_info(Node) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
get_metrics() ->
|
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() ->
|
get_metrics(Node) when Node =:= node() ->
|
||||||
emqx_metrics:all();
|
emqx_metrics:all();
|
||||||
|
@ -183,13 +183,44 @@ get_metrics(Node) ->
|
||||||
rpc_call(Node, get_metrics, [Node]).
|
rpc_call(Node, get_metrics, [Node]).
|
||||||
|
|
||||||
get_stats() ->
|
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() ->
|
get_stats(Node) when Node =:= node() ->
|
||||||
emqx_stats:getstats();
|
emqx_stats:getstats();
|
||||||
get_stats(Node) ->
|
get_stats(Node) ->
|
||||||
rpc_call(Node, 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
|
%% Clients
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -313,6 +313,7 @@ subscribe_api() ->
|
||||||
#{
|
#{
|
||||||
name => topic,
|
name => topic,
|
||||||
in => query,
|
in => query,
|
||||||
|
type => string,
|
||||||
required => true,
|
required => true,
|
||||||
default => <<"topic_1">>
|
default => <<"topic_1">>
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,27 +16,277 @@
|
||||||
|
|
||||||
-module(emqx_mgmt_api_metrics).
|
-module(emqx_mgmt_api_metrics).
|
||||||
|
|
||||||
-rest_api(#{name => list_all_metrics,
|
-behavior(minirest_api).
|
||||||
method => 'GET',
|
|
||||||
path => "/metrics",
|
|
||||||
func => list,
|
|
||||||
descr => "A list of metrics of all nodes in the cluster"}).
|
|
||||||
|
|
||||||
-rest_api(#{name => list_node_metrics,
|
-export([api_spec/0]).
|
||||||
method => 'GET',
|
|
||||||
path => "/nodes/:atom:node/metrics",
|
|
||||||
func => list,
|
|
||||||
descr => "A list of metrics of a node"}).
|
|
||||||
|
|
||||||
-export([list/2]).
|
-export([list/2]).
|
||||||
|
|
||||||
list(Bindings, _Params) when map_size(Bindings) == 0 ->
|
api_spec() ->
|
||||||
emqx_mgmt:return({ok, [#{node => Node, metrics => maps:from_list(Metrics)}
|
{[metrics_api()], [metrics_schema()]}.
|
||||||
|| {Node, Metrics} <- emqx_mgmt:get_metrics()]});
|
|
||||||
|
|
||||||
list(#{node := Node}, _Params) ->
|
metrics_schema() ->
|
||||||
case emqx_mgmt:get_metrics(Node) of
|
DefinitionName = <<"metrics">>,
|
||||||
{error, Reason} -> emqx_mgmt:return({error, Reason});
|
DefinitionProperties = #{
|
||||||
Metrics -> emqx_mgmt:return({ok, maps:from_list(Metrics)})
|
<<"actions.failure">> => #{
|
||||||
end.
|
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.received,messages.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.sent,messages.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}.
|
||||||
|
|
|
@ -20,7 +20,9 @@
|
||||||
-export([api_spec/0]).
|
-export([api_spec/0]).
|
||||||
|
|
||||||
-export([ nodes/2
|
-export([ nodes/2
|
||||||
, node/2]).
|
, node/2
|
||||||
|
, node_metrics/2
|
||||||
|
, node_stats/2]).
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
|
||||||
|
@ -29,9 +31,13 @@ api_spec() ->
|
||||||
|
|
||||||
apis() ->
|
apis() ->
|
||||||
[ nodes_api()
|
[ nodes_api()
|
||||||
, node_api()].
|
, node_api()
|
||||||
|
, node_metrics_api()
|
||||||
|
, node_stats_api()].
|
||||||
|
|
||||||
schemas() ->
|
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()].
|
||||||
|
|
||||||
node_schema() ->
|
node_schema() ->
|
||||||
|
@ -121,15 +127,60 @@ node_api() ->
|
||||||
schema => cowboy_swagger:schema(<<"node">>)}}}},
|
schema => cowboy_swagger:schema(<<"node">>)}}}},
|
||||||
{"/nodes/:node_name", Metadata, 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
|
%% parameters trans
|
||||||
nodes(get, _Request) ->
|
nodes(get, _Request) ->
|
||||||
list(#{}).
|
list(#{}).
|
||||||
|
|
||||||
node(get, Request) ->
|
node(get, Request) ->
|
||||||
NodeName = cowboy_req:binding(node_name, Request),
|
Params = node_name_path_parameter(Request),
|
||||||
Node = binary_to_atom(NodeName, utf8),
|
get_node(Params).
|
||||||
get_node(#{node => Node}).
|
|
||||||
|
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
|
%% api apply
|
||||||
|
@ -147,8 +198,29 @@ get_node(#{node := Node}) ->
|
||||||
{200, Response}
|
{200, Response}
|
||||||
end.
|
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
|
%% 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}) ->
|
format(_Node, Info = #{memory_total := Total, memory_used := Used}) ->
|
||||||
{ok, SysPathBinary} = file:get_cwd(),
|
{ok, SysPathBinary} = file:get_cwd(),
|
||||||
SysPath = list_to_binary(SysPathBinary),
|
SysPath = list_to_binary(SysPathBinary),
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_api_publish).
|
||||||
|
%% API
|
||||||
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
|
||||||
|
-behavior(minirest_api).
|
||||||
|
|
||||||
|
-export([api_spec/0]).
|
||||||
|
|
||||||
|
-export([ publish/2
|
||||||
|
, publish_batch/2]).
|
||||||
|
|
||||||
|
api_spec() ->
|
||||||
|
{
|
||||||
|
[publish_api(), publish_batch_api()],
|
||||||
|
[request_message_schema(), mqtt_message_schema()]
|
||||||
|
}.
|
||||||
|
|
||||||
|
publish_api() ->
|
||||||
|
MeteData = #{
|
||||||
|
post => #{
|
||||||
|
description => "publish",
|
||||||
|
parameters => [#{
|
||||||
|
name => message,
|
||||||
|
in => body,
|
||||||
|
required => true,
|
||||||
|
schema => minirest:ref(<<"request_message">>)
|
||||||
|
}],
|
||||||
|
responses => #{
|
||||||
|
<<"200">> => #{
|
||||||
|
description => <<"publish ok">>,
|
||||||
|
schema => minirest:ref(<<"message">>)}}}},
|
||||||
|
{"/publish", MeteData, publish}.
|
||||||
|
|
||||||
|
publish_batch_api() ->
|
||||||
|
MeteData = #{
|
||||||
|
post => #{
|
||||||
|
description => "publish",
|
||||||
|
parameters => [#{
|
||||||
|
name => message,
|
||||||
|
in => body,
|
||||||
|
required => true,
|
||||||
|
schema =>#{
|
||||||
|
type => array,
|
||||||
|
items => minirest:ref(<<"request_message">>)}
|
||||||
|
}],
|
||||||
|
responses => #{
|
||||||
|
<<"200">> => #{
|
||||||
|
description => <<"publish result">>,
|
||||||
|
schema => #{
|
||||||
|
type => array,
|
||||||
|
items => minirest:ref(<<"message">>)}}}}},
|
||||||
|
{"/publish_batch", MeteData, publish_batch}.
|
||||||
|
|
||||||
|
request_message_schema() ->
|
||||||
|
{<<"request_message">>, maps:without([<<"id">>], message_def())}.
|
||||||
|
|
||||||
|
mqtt_message_schema() ->
|
||||||
|
{<<"message">>, message_def()}.
|
||||||
|
|
||||||
|
message_def() ->
|
||||||
|
#{
|
||||||
|
<<"id">> => #{
|
||||||
|
type => <<"string">>,
|
||||||
|
description => <<"Message ID">>},
|
||||||
|
<<"topic">> => #{
|
||||||
|
type => <<"string">>,
|
||||||
|
description => <<"Topic">>},
|
||||||
|
<<"qos">> => #{
|
||||||
|
type => <<"integer">>,
|
||||||
|
enum => [0, 1, 2],
|
||||||
|
description => <<"Qos">>},
|
||||||
|
<<"payload">> => #{
|
||||||
|
type => <<"string">>,
|
||||||
|
description => <<"Topic">>},
|
||||||
|
<<"from">> => #{
|
||||||
|
type => <<"string">>,
|
||||||
|
description => <<"Message from">>},
|
||||||
|
<<"flag">> => #{
|
||||||
|
type => <<"object">>,
|
||||||
|
description => <<"Message flag">>,
|
||||||
|
properties => #{
|
||||||
|
<<"sys">> => #{
|
||||||
|
type => <<"boolean">>,
|
||||||
|
default => false,
|
||||||
|
description => <<"System message flag, nullable, default false">>},
|
||||||
|
<<"dup">> => #{
|
||||||
|
type => <<"boolean">>,
|
||||||
|
default => false,
|
||||||
|
description => <<"Dup message flag, nullable, default false">>},
|
||||||
|
<<"retain">> => #{
|
||||||
|
type => <<"boolean">>,
|
||||||
|
default => false,
|
||||||
|
description => <<"Retain message flag, nullable, default false">>}}}
|
||||||
|
}.
|
||||||
|
|
||||||
|
publish(post, Request) ->
|
||||||
|
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||||
|
Message = message(emqx_json:decode(Body, [return_maps])),
|
||||||
|
_ = emqx_mgmt:publish(Message),
|
||||||
|
{200, emqx_json:encode(format_message(Message))}.
|
||||||
|
|
||||||
|
publish_batch(post, Request) ->
|
||||||
|
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||||
|
Messages = messages(emqx_json:decode(Body, [return_maps])),
|
||||||
|
_ = [emqx_mgmt:publish(Message) || Message <- Messages],
|
||||||
|
ResponseBody = emqx_json:encode(format_message(Messages)),
|
||||||
|
{200, ResponseBody}.
|
||||||
|
|
||||||
|
message(Map) ->
|
||||||
|
From = maps:get(<<"from">>, Map, http_api),
|
||||||
|
QoS = maps:get(<<"qos">>, Map, 0),
|
||||||
|
Topic = maps:get(<<"topic">>, Map),
|
||||||
|
Payload = maps:get(<<"payload">>, Map),
|
||||||
|
Flags = flags(Map),
|
||||||
|
emqx_message:make(From, QoS, Topic, Payload, Flags, #{}).
|
||||||
|
|
||||||
|
flags(Map) ->
|
||||||
|
Flags = maps:get(<<"flags">>, Map, #{}),
|
||||||
|
Retain = maps:get(<<"retain">>, Flags, false),
|
||||||
|
Sys = maps:get(<<"sys">>, Flags, false),
|
||||||
|
Dup = maps:get(<<"dup">>, Flags, false),
|
||||||
|
#{
|
||||||
|
retain => Retain,
|
||||||
|
sys => Sys,
|
||||||
|
dup => Dup
|
||||||
|
}.
|
||||||
|
|
||||||
|
messages(List) ->
|
||||||
|
[message(MessageMap) || MessageMap <- List].
|
||||||
|
|
||||||
|
format_message(Messages) when is_list(Messages)->
|
||||||
|
[format_message(Message) || Message <- Messages];
|
||||||
|
format_message(#message{id = ID, qos = Qos, from = From, topic = Topic, payload = Payload, flags = Flags}) ->
|
||||||
|
#{
|
||||||
|
id => emqx_guid:to_hexstr(ID),
|
||||||
|
qos => Qos,
|
||||||
|
topic => Topic,
|
||||||
|
payload => Payload,
|
||||||
|
flag => Flags,
|
||||||
|
from => to_binary(From)
|
||||||
|
}.
|
||||||
|
|
||||||
|
to_binary(Data) when is_binary(Data) ->
|
||||||
|
Data;
|
||||||
|
to_binary(Data) ->
|
||||||
|
list_to_binary(io_lib:format("~p", [Data])).
|
|
@ -13,33 +13,93 @@
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_mgmt_api_stats).
|
-module(emqx_mgmt_api_stats).
|
||||||
|
|
||||||
-rest_api(#{name => list_stats,
|
-behavior(minirest_api).
|
||||||
method => 'GET',
|
|
||||||
path => "/stats/",
|
|
||||||
func => list,
|
|
||||||
descr => "A list of stats of all nodes in the cluster"}).
|
|
||||||
|
|
||||||
-rest_api(#{name => lookup_node_stats,
|
-export([api_spec/0]).
|
||||||
method => 'GET',
|
|
||||||
path => "/nodes/:atom:node/stats/",
|
|
||||||
func => lookup,
|
|
||||||
descr => "A list of stats of a node"}).
|
|
||||||
|
|
||||||
-export([ list/2
|
-export([list/2]).
|
||||||
, lookup/2
|
|
||||||
]).
|
|
||||||
|
|
||||||
%% List stats of all nodes
|
api_spec() ->
|
||||||
list(Bindings, _Params) when map_size(Bindings) == 0 ->
|
{stats_api(), stats_schema()}.
|
||||||
emqx_mgmt:return({ok, [#{node => Node, stats => maps:from_list(Stats)}
|
|
||||||
|| {Node, Stats} <- emqx_mgmt:get_stats()]}).
|
|
||||||
|
|
||||||
%% List stats of a node
|
stats_schema() ->
|
||||||
lookup(#{node := Node}, _Params) ->
|
DefinitionName = <<"stats">>,
|
||||||
case emqx_mgmt:get_stats(Node) of
|
DefinitionProperties = #{
|
||||||
{error, Reason} -> emqx_mgmt:return({error, Reason});
|
<<"connections.count">> => #{
|
||||||
Stats -> emqx_mgmt:return({ok, maps:from_list(Stats)})
|
type => <<"integer">>,
|
||||||
end.
|
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}.
|
||||||
|
|
|
@ -21,11 +21,13 @@
|
||||||
, kmg/1
|
, kmg/1
|
||||||
, ntoa/1
|
, ntoa/1
|
||||||
, merge_maps/2
|
, merge_maps/2
|
||||||
, not_found_schema/1
|
|
||||||
, not_found_schema/2
|
|
||||||
, batch_operation/3
|
, batch_operation/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([ not_found_schema/1
|
||||||
|
, not_found_schema/2
|
||||||
|
, batch_response_schema/1]).
|
||||||
|
|
||||||
-export([urldecode/1]).
|
-export([urldecode/1]).
|
||||||
|
|
||||||
-define(KB, 1024).
|
-define(KB, 1024).
|
||||||
|
@ -80,6 +82,8 @@ merge_maps(Default, New) ->
|
||||||
urldecode(S) ->
|
urldecode(S) ->
|
||||||
emqx_http_lib:uri_decode(S).
|
emqx_http_lib:uri_decode(S).
|
||||||
|
|
||||||
|
%%%==============================================================================================
|
||||||
|
%% schema util
|
||||||
not_found_schema(Description) ->
|
not_found_schema(Description) ->
|
||||||
not_found_schema(Description, ["RESOURCE_NOT_FOUND"]).
|
not_found_schema(Description, ["RESOURCE_NOT_FOUND"]).
|
||||||
|
|
||||||
|
@ -96,6 +100,34 @@ not_found_schema(Description, Enum) ->
|
||||||
type => string}}}
|
type => string}}}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
batch_response_schema(DefName) ->
|
||||||
|
#{
|
||||||
|
type => object,
|
||||||
|
properties => #{
|
||||||
|
success => #{
|
||||||
|
type => integer,
|
||||||
|
description => <<"Success count">>},
|
||||||
|
failed => #{
|
||||||
|
type => integer,
|
||||||
|
description => <<"Failed count">>},
|
||||||
|
detail => #{
|
||||||
|
type => array,
|
||||||
|
description => <<"Failed object & reason">>,
|
||||||
|
items => #{
|
||||||
|
type => object,
|
||||||
|
properties =>
|
||||||
|
#{
|
||||||
|
data => minirest:ref(DefName),
|
||||||
|
reason => #{
|
||||||
|
type => <<"string">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.
|
||||||
|
|
||||||
|
%%%==============================================================================================
|
||||||
batch_operation(Module, Function, ArgsList) ->
|
batch_operation(Module, Function, ArgsList) ->
|
||||||
Failed = batch_operation(Module, Function, ArgsList, []),
|
Failed = batch_operation(Module, Function, ArgsList, []),
|
||||||
Len = erlang:length(Failed),
|
Len = erlang:length(Failed),
|
||||||
|
|
|
@ -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)).
|
|
@ -20,11 +20,6 @@
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-define(APP, emqx_management).
|
|
||||||
|
|
||||||
-define(SERVER, "http://127.0.0.1:8081").
|
|
||||||
-define(BASE_PATH, "/api/v5").
|
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
emqx_ct:all(?MODULE).
|
emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
@ -54,5 +49,29 @@ t_nodes_api(_) ->
|
||||||
|
|
||||||
NodePath = emqx_mgmt_api_test_util:api_path(["nodes", atom_to_list(node())]),
|
NodePath = emqx_mgmt_api_test_util:api_path(["nodes", atom_to_list(node())]),
|
||||||
{ok, NodeInfo} = emqx_mgmt_api_test_util:request_api(get, NodePath),
|
{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).
|
?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)).
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_publish_api_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
-define(CLIENTID, <<"api_clientid">>).
|
||||||
|
-define(USERNAME, <<"api_username">>).
|
||||||
|
|
||||||
|
-define(TOPIC1, <<"api_topic1">>).
|
||||||
|
-define(TOPIC2, <<"api_topic2">>).
|
||||||
|
|
||||||
|
|
||||||
|
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_publish_api(_) ->
|
||||||
|
{ok, Client} = emqtt:start_link(#{username => <<"api_username">>, clientid => <<"api_clientid">>}),
|
||||||
|
{ok, _} = emqtt:connect(Client),
|
||||||
|
{ok, _, [0]} = emqtt:subscribe(Client, ?TOPIC1),
|
||||||
|
{ok, _, [0]} = emqtt:subscribe(Client, ?TOPIC2),
|
||||||
|
Payload = <<"hello">>,
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path(["publish"]),
|
||||||
|
Auth = emqx_mgmt_api_test_util:auth_header_(),
|
||||||
|
Body = #{topic => ?TOPIC1, payload => Payload},
|
||||||
|
{ok, _} = emqx_mgmt_api_test_util:request_api(post, Path, "", Auth, Body),
|
||||||
|
?assertEqual(receive_assert(?TOPIC1, 0, Payload), ok),
|
||||||
|
emqtt:disconnect(Client).
|
||||||
|
|
||||||
|
t_publish_batch_api(_) ->
|
||||||
|
{ok, Client} = emqtt:start_link(#{username => <<"api_username">>, clientid => <<"api_clientid">>}),
|
||||||
|
{ok, _} = emqtt:connect(Client),
|
||||||
|
{ok, _, [0]} = emqtt:subscribe(Client, ?TOPIC1),
|
||||||
|
{ok, _, [0]} = emqtt:subscribe(Client, ?TOPIC2),
|
||||||
|
Payload = <<"hello">>,
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path(["publish_batch"]),
|
||||||
|
Auth = emqx_mgmt_api_test_util:auth_header_(),
|
||||||
|
Body =[#{topic => ?TOPIC1, payload => Payload}, #{topic => ?TOPIC2, payload => Payload}],
|
||||||
|
{ok, Response} = emqx_mgmt_api_test_util:request_api(post, Path, "", Auth, Body),
|
||||||
|
ResponseMap = emqx_json:decode(Response, [return_maps]),
|
||||||
|
?assertEqual(2, erlang:length(ResponseMap)),
|
||||||
|
?assertEqual(receive_assert(?TOPIC1, 0, Payload), ok),
|
||||||
|
?assertEqual(receive_assert(?TOPIC2, 0, Payload), ok),
|
||||||
|
emqtt:disconnect(Client).
|
||||||
|
|
||||||
|
receive_assert(Topic, Qos, Payload) ->
|
||||||
|
receive
|
||||||
|
{publish, Message} ->
|
||||||
|
ReceiveTopic = maps:get(topic, Message),
|
||||||
|
ReceiveQos = maps:get(qos, Message),
|
||||||
|
ReceivePayload = maps:get(payload, Message),
|
||||||
|
?assertEqual(ReceiveTopic , Topic),
|
||||||
|
?assertEqual(ReceiveQos , Qos),
|
||||||
|
?assertEqual(ReceivePayload , Payload),
|
||||||
|
ok
|
||||||
|
after 5000 ->
|
||||||
|
timeout
|
||||||
|
end.
|
||||||
|
|
|
@ -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)).
|
|
@ -48,7 +48,7 @@
|
||||||
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
|
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
|
||||||
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}}
|
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}}
|
||||||
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
|
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
|
||||||
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.2"}}}
|
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.3"}}}
|
||||||
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
|
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
|
||||||
, {cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v4.0.1"}}} % TODO: delete when all apps moved to hocon
|
, {cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v4.0.1"}}} % TODO: delete when all apps moved to hocon
|
||||||
, {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.1.1"}}}
|
, {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.1.1"}}}
|
||||||
|
|
Loading…
Reference in New Issue