Add more service modules for MQTT Version 5.0

This commit is contained in:
Feng Lee 2018-04-18 16:34:23 +08:00
parent bbb66ff92e
commit 2a4ffc6645
69 changed files with 2428 additions and 2040 deletions

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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.
%%-------------------------------------------------------------------- %%%===================================================================
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Banner %% Banner
@ -154,7 +154,10 @@
%% Route %% Route
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(route, { topic :: topic(), dest }). -record(route,
{ topic :: topic(),
dest :: node() | {binary(), node()}
}).
-type(route() :: #route{}). -type(route() :: #route{}).

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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). -module(emqx).
@ -29,7 +29,7 @@
-export([topics/0, subscriptions/1, subscribers/1, subscribed/2]). -export([topics/0, subscriptions/1, subscribers/1, subscribed/2]).
%% Get/Set suboptions %% Get/Set suboptions
-export([getopts/2, setopts/3]). -export([get_subopts/2, set_subopts/3]).
%% Hooks API %% Hooks API
-export([hook/4, hook/3, unhook/2, run_hooks/2, run_hooks/3]). -export([hook/4, hook/3, unhook/2, run_hooks/2, run_hooks/3]).
@ -47,12 +47,17 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Start emqx application %% @doc Start emqx application
-spec(start() -> ok | {error, term()}). -spec(start() -> {ok, list(atom())} | {error, term()}).
start() -> application:start(?APP). start() ->
%% Check OS
%% Check VM
%% Check Mnesia
application:ensure_all_started(?APP).
%% @doc Stop emqx application. %% @doc Stop emqx application.
-spec(stop() -> ok | {error, term()}). -spec(stop() -> ok | {error, term()}).
stop() -> application:stop(?APP). stop() ->
application:stop(?APP).
%% @doc Is emqx running? %% @doc Is emqx running?
-spec(is_running(node()) -> boolean()). -spec(is_running(node()) -> boolean()).
@ -96,13 +101,13 @@ unsubscribe(Topic, Subscriber) ->
%% PubSub management API %% PubSub management API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(getopts(topic() | string(), subscriber()) -> [suboption()]). -spec(get_subopts(topic() | string(), subscriber()) -> [suboption()]).
getopts(Topic, Subscriber) -> get_subopts(Topic, Subscriber) ->
emqx_broker:getopts(iolist_to_binary(Topic), list_to_subid(Subscriber)). emqx_broker:get_subopts(iolist_to_binary(Topic), list_to_subid(Subscriber)).
-spec(setopts(topic() | string(), subscriber(), [suboption()]) -> ok). -spec(set_subopts(topic() | string(), subscriber(), [suboption()]) -> ok).
setopts(Topic, Subscriber, Options) when is_list(Options) -> set_subopts(Topic, Subscriber, Options) when is_list(Options) ->
emqx_broker:setopts(iolist_to_binary(Topic), list_to_subid(Subscriber), Options). emqx_broker:set_subopts(iolist_to_binary(Topic), list_to_subid(Subscriber), Options).
-spec(topics() -> list(topic())). -spec(topics() -> list(topic())).
topics() -> emqx_router:topics(). topics() -> emqx_router:topics().
@ -165,7 +170,7 @@ shutdown() ->
shutdown(normal). shutdown(normal).
shutdown(Reason) -> shutdown(Reason) ->
emqx_log:error("EMQ shutdown for ~s", [Reason]), emqx_logger:error("EMQ shutdown for ~s", [Reason]),
emqx_plugins:unload(), emqx_plugins:unload(),
lists:foreach(fun application:stop/1, [emqx, ekka, mochiweb, esockd, gproc]). lists:foreach(fun application:stop/1, [emqx, ekka, mochiweb, esockd, gproc]).

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_access_control). -module(emqx_access_control).
@ -28,10 +28,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-define(TAB, ?MODULE).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
-define(TAB, access_control).
-type(password() :: undefined | binary()). -type(password() :: undefined | binary()).
-record(state, {}). -record(state, {}).
@ -122,7 +121,7 @@ stop() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
_ = emqx_tables:create(?TAB, [set, protected, {read_concurrency, true}]), _ = emqx_tables:new(?TAB, [set, protected, {read_concurrency, true}]),
{ok, #state{}}. {ok, #state{}}.
handle_call({register_mod, Type, Mod, Opts, Seq}, _From, State) -> handle_call({register_mod, Type, Mod, Opts, Seq}, _From, State) ->
@ -157,15 +156,15 @@ handle_call(stop, _From, State) ->
{stop, normal, ok, State}; {stop, normal, ok, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[AccessControl] Unexpected request: ~p", [Req]), emqx_logger:error("[AccessControl] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[AccessControl] Unexpected msg: ~p", [Msg]), emqx_logger:error("[AccessControl] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[AccessControl] Unexpected info: ~p", [Info]), emqx_logger:error("[AccessControl] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_acl_internal). -module(emqx_acl_internal).
@ -25,7 +25,7 @@
%% ACL callbacks %% ACL callbacks
-export([init/1, check_acl/2, reload_acl/1, description/0]). -export([init/1, check_acl/2, reload_acl/1, description/0]).
-define(ACL_RULE_TAB, mqtt_acl_rule). -define(ACL_RULE_TAB, emqx_acl_rule).
-record(state, {config}). -record(state, {config}).
@ -48,7 +48,7 @@ all_rules() ->
%% @doc Init internal ACL %% @doc Init internal ACL
-spec(init([File :: string()]) -> {ok, State :: any()}). -spec(init([File :: string()]) -> {ok, State :: any()}).
init([File]) -> init([File]) ->
_ = emqx_tables:create(?ACL_RULE_TAB, [set, public, {read_concurrency, true}]), _ = emqx_tables:new(?ACL_RULE_TAB, [set, public, {read_concurrency, true}]),
{ok, load_rules_from_file(#state{config = File})}. {ok, load_rules_from_file(#state{config = File})}.
load_rules_from_file(State = #state{config = AclFile}) -> load_rules_from_file(State = #state{config = AclFile}) ->

View File

@ -92,7 +92,7 @@ handle_event({set_alarm, Alarm = #alarm{id = AlarmId,
{summary, iolist_to_binary(Summary)}, {summary, iolist_to_binary(Summary)},
{ts, emqx_time:now_secs(TS)}]) of {ts, emqx_time:now_secs(TS)}]) of
{'EXIT', Reason} -> {'EXIT', Reason} ->
emqx_log:error("[Alarm] Failed to encode set_alarm: ~p", [Reason]); emqx_logger:error("[Alarm] Failed to encode set_alarm: ~p", [Reason]);
JSON -> JSON ->
emqx_broker:publish(alarm_msg(alert, AlarmId, JSON)) emqx_broker:publish(alarm_msg(alert, AlarmId, JSON))
end, end,
@ -101,7 +101,7 @@ handle_event({set_alarm, Alarm = #alarm{id = AlarmId,
handle_event({clear_alarm, AlarmId}, Alarms) -> handle_event({clear_alarm, AlarmId}, Alarms) ->
case catch emqx_json:encode([{id, AlarmId}, {ts, emqx_time:now_secs()}]) of case catch emqx_json:encode([{id, AlarmId}, {ts, emqx_time:now_secs()}]) of
{'EXIT', Reason} -> {'EXIT', Reason} ->
emqx_log:error("[Alarm] Failed to encode clear_alarm: ~p", [Reason]); emqx_logger:error("[Alarm] Failed to encode clear_alarm: ~p", [Reason]);
JSON -> JSON ->
emqx_broker:publish(alarm_msg(clear, AlarmId, JSON)) emqx_broker:publish(alarm_msg(clear, AlarmId, JSON))
end, end,

View File

@ -34,7 +34,6 @@ start(_Type, _Args) ->
ekka:start(), ekka:start(),
{ok, Sup} = emqx_sup:start_link(), {ok, Sup} = emqx_sup:start_link(),
%%TODO: fixme later %%TODO: fixme later
emqx_mqtt_metrics:init(),
ok = register_acl_mod(), ok = register_acl_mod(),
emqx_modules:load(), emqx_modules:load(),
start_autocluster(), start_autocluster(),

View File

@ -62,13 +62,13 @@ passwd_hash(pbkdf2, {Salt, Password, Macfun, Iterations, Dklen}) ->
case pbkdf2:pbkdf2(Macfun, Password, Salt, Iterations, Dklen) of case pbkdf2:pbkdf2(Macfun, Password, Salt, Iterations, Dklen) of
{ok, Hexstring} -> pbkdf2:to_hex(Hexstring); {ok, Hexstring} -> pbkdf2:to_hex(Hexstring);
{error, Error} -> {error, Error} ->
emqx_log:error("[AuthMod] PasswdHash with pbkdf2 error:~p", [Error]), <<>> emqx_logger:error("[AuthMod] PasswdHash with pbkdf2 error:~p", [Error]), <<>>
end; end;
passwd_hash(bcrypt, {Salt, Password}) -> passwd_hash(bcrypt, {Salt, Password}) ->
case bcrypt:hashpw(Password, Salt) of case bcrypt:hashpw(Password, Salt) of
{ok, HashPassword} -> list_to_binary(HashPassword); {ok, HashPassword} -> list_to_binary(HashPassword);
{error, Error}-> {error, Error}->
emqx_log:error("[AuthMod] PasswdHash with bcrypt error:~p", [Error]), <<>> emqx_logger:error("[AuthMod] PasswdHash with bcrypt error:~p", [Error]), <<>>
end. end.
hexstring(<<X:128/big-unsigned-integer>>) -> hexstring(<<X:128/big-unsigned-integer>>) ->

View File

@ -1,72 +1,138 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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.
%%-------------------------------------------------------------------- %%%===================================================================
%% Banned an IP Address, ClientId?
-module(emqx_banned). -module(emqx_banned).
-behaviour(gen_server). -behaviour(gen_server).
-include("emqx.hrl").
%% Mnesia bootstrap
-export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}).
%% API %% API
-export([start_link/0]). -export([start_link/0]).
-export([check/1]).
-export([add/1, del/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-define(TAB, ?MODULE).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
-record(state, {}). -type(key() :: {client_id, client_id()} |
{ipaddr, inet:ip_address()} |
{username, username()}).
%%%=================================================================== -record(state, {expiry_timer}).
%%% API
%%%===================================================================
%% @doc Starts the server -record(banned, {key :: key(), reason, by, desc, until}).
%%--------------------------------------------------------------------
%% Mnesia bootstrap
%%--------------------------------------------------------------------
mnesia(boot) ->
ok = ekka_mnesia:create_table(?TAB, [
{type, ordered_set},
{disc_copies, [node()]},
{record_name, banned},
{attributes, record_info(fields, banned)}]);
mnesia(copy) ->
ok = ekka_mnesia:copy_table(?TAB).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
%% @doc Start the banned server
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}). -spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link() -> start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%=================================================================== -spec(check(client()) -> boolean()).
%%% gen_server callbacks check(#client{client_id = ClientId,
%%%=================================================================== username = Username,
peername = {IPAddr, _}}) ->
ets:member(?TAB, {client_id, ClientId})
orelse ets:member(?TAB, {username, Username})
orelse ets:member(?TAB, {ipaddr, IPAddr}).
add(Record) when is_record(Record, banned) ->
mnesia:dirty_write(?TAB, Record).
del(Key) ->
mnesia:dirty_delete(?TAB, Key).
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
init([]) -> init([]) ->
{ok, #state{}}. emqx_timer:seed(),
{ok, ensure_expiry_timer(#state{})}.
handle_call(_Request, _From, State) -> handle_call(Req, _From, State) ->
Reply = ok, emqx_logger:error("[BANNED] Unexpected request: ~p", [Req]),
{reply, Reply, State}. {reply, ignore, State}.
handle_cast(_Msg, State) -> handle_cast(Msg, State) ->
emqx_logger:error("[BANNED] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(_Info, State) -> handle_info({timeout, Ref, expire}, State = #state{expiry_timer = Ref}) ->
mnesia:async_dirty(fun expire_banned_items/1, [erlang:timestamp()]),
{noreply, ensure_expiry_timer(State), hibernate};
handle_info(Info, State) ->
emqx_logger:error("[BANNED] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, #state{expiry_timer = Timer}) ->
ok. emqx_misc:cancel_timer(Timer).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%%=================================================================== %%--------------------------------------------------------------------
%%% Internal functions %% Internal functions
%%%=================================================================== %%--------------------------------------------------------------------
ensure_expiry_timer(State) ->
Interval = emqx_config:get_env(banned_expiry_interval, timer:minutes(5)),
State#state{expiry_timer = emqx_misc:start_timer(
Interval + rand:uniform(Interval), expire)}.
expire_banned_items(Now) ->
expire_banned_item(mnesia:first(?TAB), Now).
expire_banned_item('$end_of_table', _Now) ->
ok;
expire_banned_item(Key, Now) ->
case mnesia:read(?TAB, Key) of
[#banned{until = undefined}] -> ok;
[B = #banned{until = Until}] when Until < Now ->
mnesia:delete_object(?TAB, B, sticky_write);
[] -> ok
end,
expire_banned_item(mnesia:next(Key), Now).

View File

@ -103,11 +103,11 @@ qname(Node, Topic) ->
iolist_to_binary(["Bridge:", Node, ":", Topic]). iolist_to_binary(["Bridge:", Node, ":", Topic]).
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[Bridge] Unexpected request: ~p", [Req]), emqx_logger:error("[Bridge] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[Bridge] Unexpected msg: ~p", [Msg]), emqx_logger:error("[Bridge] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({dispatch, _Topic, Msg}, State = #state{mqueue = MQ, status = down}) -> handle_info({dispatch, _Topic, Msg}, State = #state{mqueue = MQ, status = down}) ->
@ -118,7 +118,7 @@ handle_info({dispatch, _Topic, Msg}, State = #state{node = Node, status = up}) -
{noreply, State, hibernate}; {noreply, State, hibernate};
handle_info({nodedown, Node}, State = #state{node = Node, ping_down_interval = Interval}) -> handle_info({nodedown, Node}, State = #state{node = Node, ping_down_interval = Interval}) ->
emqx_log:warning("[Bridge] Node Down: ~s", [Node]), emqx_logger:warning("[Bridge] Node Down: ~s", [Node]),
erlang:send_after(Interval, self(), ping_down_node), erlang:send_after(Interval, self(), ping_down_node),
{noreply, State#state{status = down}, hibernate}; {noreply, State#state{status = down}, hibernate};
@ -126,7 +126,7 @@ handle_info({nodeup, Node}, State = #state{node = Node}) ->
%% TODO: Really fast?? %% TODO: Really fast??
case emqx:is_running(Node) of case emqx:is_running(Node) of
true -> true ->
emqx_log:warning("[Bridge] Node up: ~s", [Node]), emqx_logger:warning("[Bridge] Node up: ~s", [Node]),
{noreply, dequeue(State#state{status = up})}; {noreply, dequeue(State#state{status = up})};
false -> false ->
self() ! {nodedown, Node}, self() ! {nodedown, Node},
@ -149,7 +149,7 @@ handle_info({'EXIT', _Pid, normal}, State) ->
{noreply, State}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[Bridge] Unexpected info: ~p", [Info]), emqx_logger:error("[Bridge] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{pool = Pool, id = Id}) -> terminate(_Reason, #state{pool = Pool, id = Id}) ->

View File

@ -1,24 +1,27 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_bridge_sup_sup). -module(emqx_bridge_sup_sup).
-behavior(supervisor). -behavior(supervisor).
-export([start_link/0, bridges/0, start_bridge/2, start_bridge/3, stop_bridge/2]). -include("emqx.hrl").
-export([start_link/0, bridges/0]).
-export([start_bridge/2, start_bridge/3, stop_bridge/2]).
-export([init/1]). -export([init/1]).
@ -32,27 +35,28 @@ start_link() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc List all bridges %% @doc List all bridges
-spec(bridges() -> [{node(), binary(), pid()}]). -spec(bridges() -> [{node(), topic(), pid()}]).
bridges() -> bridges() ->
[{Node, Topic, Pid} || {?CHILD_ID(Node, Topic), Pid, supervisor, _} [{Node, Topic, Pid} || {?CHILD_ID(Node, Topic), Pid, supervisor, _}
<- supervisor:which_children(?MODULE)]. <- supervisor:which_children(?MODULE)].
%% @doc Start a bridge %% @doc Start a bridge
-spec(start_bridge(atom(), binary()) -> {ok, pid()} | {error, term()}). -spec(start_bridge(node(), topic()) -> {ok, pid()} | {error, term()}).
start_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) -> start_bridge(Node, Topic) when is_atom(Node), is_binary(Topic) ->
start_bridge(Node, Topic, []). start_bridge(Node, Topic, []).
-spec(start_bridge(atom(), binary(), [emqx_bridge:option()]) -> {ok, pid()} | {error, term()}). -spec(start_bridge(node(), topic(), [emqx_bridge:option()])
-> {ok, pid()} | {error, term()}).
start_bridge(Node, _Topic, _Options) when Node =:= node() -> start_bridge(Node, _Topic, _Options) when Node =:= node() ->
{error, bridge_to_self}; {error, bridge_to_self};
start_bridge(Node, Topic, Options) when is_atom(Node) andalso is_binary(Topic) -> start_bridge(Node, Topic, Options) when is_atom(Node), is_binary(Topic) ->
{ok, BridgeEnv} = emqx_config:get_env(bridge), {ok, BridgeEnv} = emqx_config:get_env(bridge),
Options1 = emqx_misc:merge_opts(BridgeEnv, Options), Options1 = emqx_misc:merge_opts(BridgeEnv, Options),
supervisor:start_child(?MODULE, bridge_spec(Node, Topic, Options1)). supervisor:start_child(?MODULE, bridge_spec(Node, Topic, Options1)).
%% @doc Stop a bridge %% @doc Stop a bridge
-spec(stop_bridge(atom(), binary()) -> {ok, pid()} | ok). -spec(stop_bridge(node(), topic()) -> ok | {error, term()}).
stop_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) -> stop_bridge(Node, Topic) when is_atom(Node), is_binary(Topic) ->
ChildId = ?CHILD_ID(Node, Topic), ChildId = ?CHILD_ID(Node, Topic),
case supervisor:terminate_child(?MODULE, ChildId) of case supervisor:terminate_child(?MODULE, ChildId) of
ok -> supervisor:delete_child(?MODULE, ChildId); ok -> supervisor:delete_child(?MODULE, ChildId);

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_broker). -module(emqx_broker).
@ -53,9 +53,11 @@
%% Start a broker %% Start a broker
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(start_link(atom(), pos_integer()) -> {ok, pid()} | ignore | {error, term()}). -spec(start_link(atom(), pos_integer())
-> {ok, pid()} | ignore | {error, term()}).
start_link(Pool, Id) -> start_link(Pool, Id) ->
gen_server:start_link(?MODULE, [Pool, Id], [{hibernate_after, 2000}]). gen_server:start_link(emqx_misc:proc_name(?MODULE, Id),
?MODULE, [Pool, Id], [{hibernate_after, 2000}]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Subscriber/Unsubscribe %% Subscriber/Unsubscribe
@ -101,15 +103,32 @@ unsubscribe(Topic, Subscriber, Timeout) ->
-spec(publish(message()) -> delivery() | stopped). -spec(publish(message()) -> delivery() | stopped).
publish(Msg = #message{from = From}) -> publish(Msg = #message{from = From}) ->
emqx_tracer:trace(publish, From, Msg), %% Hook to trace?
trace(public, From, Msg),
case emqx_hooks:run('message.publish', [], Msg) of case emqx_hooks:run('message.publish', [], Msg) of
{ok, Msg1 = #message{topic = Topic}} -> {ok, Msg1 = #message{topic = Topic}} ->
publish(Topic, Msg1); publish(Topic, Msg1);
{stop, Msg1} -> {stop, Msg1} ->
emqx_log:warning("Stop publishing: ~s", [emqx_message:format(Msg1)]), emqx_logger:warning("Stop publishing: ~s", [emqx_message:format(Msg1)]),
stopped stopped
end. end.
%%--------------------------------------------------------------------
%% Trace
%%--------------------------------------------------------------------
trace(publish, From, _Msg) when is_atom(From) ->
%% Dont' trace '$SYS' publish
ignore;
trace(public, #client{client_id = ClientId, username = Username},
#message{topic = Topic, payload = Payload}) ->
emqx_logger:info([{client, ClientId}, {topic, Topic}],
"~s/~s PUBLISH to ~s: ~p", [ClientId, Username, Topic, Payload]);
trace(public, From, #message{topic = Topic, payload = Payload})
when is_binary(From); is_list(From) ->
emqx_logger:info([{client, From}, {topic, Topic}],
"~s PUBLISH to ~s: ~p", [From, Topic, Payload]).
publish(Topic, Msg) -> publish(Topic, Msg) ->
route(aggre(emqx_router:match_routes(Topic)), delivery(Msg)). route(aggre(emqx_router:match_routes(Topic)), delivery(Msg)).
@ -131,16 +150,14 @@ route(Routes, Delivery) ->
aggre([]) -> aggre([]) ->
[]; [];
aggre([{To, Dest}]) -> aggre([#route{topic = To, dest = Dest}]) ->
[{To, Dest}]; [{To, Dest}];
aggre(Routes) -> aggre(Routes) ->
lists:foldl( lists:foldl(
fun({To, Node}, Acc) when is_atom(Node) -> fun(#route{topic = To, dest = Node}, Acc) when is_atom(Node) ->
[{To, Node} | Acc]; [{To, Node} | Acc];
({To, {Group, _Node}}, Acc) -> (#route{topic = To, dest = {Group, _Node}}, Acc) ->
lists:usort([{To, Group} | Acc]); lists:usort([{To, Group} | Acc])
({To, {Cluster, Group, _Node}}, Acc) ->
lists:usort([{To, {Cluster, Group}} | Acc])
end, [], Routes). end, [], Routes).
%% @doc Forward message to another node. %% @doc Forward message to another node.
@ -148,7 +165,7 @@ forward(Node, To, Delivery) ->
%% rpc:call to ensure the delivery, but the latency:( %% rpc:call to ensure the delivery, but the latency:(
case emqx_rpc:call(Node, ?BROKER, dispatch, [To, Delivery]) of case emqx_rpc:call(Node, ?BROKER, dispatch, [To, Delivery]) of
{badrpc, Reason} -> {badrpc, Reason} ->
emqx_log:error("[Broker] Failed to forward msg to ~s: ~p", [Node, Reason]), emqx_logger:error("[Broker] Failed to forward msg to ~s: ~p", [Node, Reason]),
Delivery; Delivery;
Delivery1 -> Delivery1 Delivery1 -> Delivery1
end. end.
@ -261,7 +278,7 @@ handle_call({set_subopts, Topic, Subscriber, Opts}, _From, State) ->
end; end;
handle_call(Request, _From, State) -> handle_call(Request, _From, State) ->
emqx_log:error("[Broker] Unexpected request: ~p", [Request]), emqx_logger:error("[Broker] Unexpected request: ~p", [Request]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast({From, {subscribe, Topic, Subscriber, Options}}, State) -> handle_cast({From, {subscribe, Topic, Subscriber, Options}}, State) ->
@ -292,7 +309,7 @@ handle_cast({From, {unsubscribe, Topic, Subscriber}}, State) ->
{noreply, State}; {noreply, State};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[Broker] Unexpected msg: ~p", [Msg]), emqx_logger:error("[Broker] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({'DOWN', _MRef, process, SubPid, _Reason}, handle_info({'DOWN', _MRef, process, SubPid, _Reason},
@ -321,7 +338,7 @@ handle_info({'DOWN', _MRef, process, SubPid, _Reason},
{noreply, demonitor_subscriber(SubPid, State)}; {noreply, demonitor_subscriber(SubPid, State)};
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[Broker] Unexpected info: ~p", [Info]), emqx_logger:error("[Broker] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{pool = Pool, id = Id}) -> terminate(_Reason, #state{pool = Pool, id = Id}) ->

View File

@ -1,66 +1,79 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_broker_helper). -module(emqx_broker_helper).
-behaviour(gen_server). -behaviour(gen_server).
-export([start_link/1]). -export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-define(SERVER, ?MODULE). -define(HELPER, ?MODULE).
-record(state, {stats_fun, stats_timer}). -record(state, {}).
%%-------------------------------------------------------------------- -spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
%% API start_link() ->
%%-------------------------------------------------------------------- gen_server:start_link({local, ?HELPER}, ?MODULE, [], []).
-spec(start_link(fun()) -> {ok, pid()} | ignore | {error, any()}).
start_link(StatsFun) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [StatsFun], []).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([StatsFun]) -> init([]) ->
{ok, TRef} = timer:send_interval(timer:seconds(1), stats), emqx_stats:update_interval(broker_stats, stats_fun()),
{ok, #state{stats_fun = StatsFun, stats_timer = TRef}}. {ok, #state{}, hibernate}.
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[BrokerHelper] Unexpected request: ~p", [Req]), emqx_logger:error("[BrokerHelper] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[BrokerHelper] Unexpected msg: ~p", [Msg]), emqx_logger:error("[BrokerHelper] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(stats, State = #state{stats_fun = StatsFun}) ->
StatsFun(), {noreply, State, hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[BrokerHelper] Unexpected info: ~p", [Info]), emqx_logger:error("[BrokerHelper] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{stats_timer = TRef}) -> terminate(_Reason, #state{}) ->
timer:cancel(TRef). emqx_stats:cancel_update(broker_stats).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
stats_fun() ->
fun() ->
safe_update_stats(emqx_subscriber,
'subscribers/count', 'subscribers/max'),
safe_update_stats(emqx_subscription,
'subscriptions/count', 'subscriptions/max'),
safe_update_stats(emqx_suboptions,
'suboptions/count', 'suboptions/max')
end.
safe_update_stats(Tab, Stat, MaxStat) ->
case ets:info(Tab, size) of
undefined -> ok;
Size -> emqx_stats:setstat(Stat, MaxStat, Size)
end.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_broker_sup). -module(emqx_broker_sup).
@ -22,7 +22,11 @@
-export([init/1]). -export([init/1]).
-define(CONCURRENCY_OPTS, [{read_concurrency, true}, {write_concurrency, true}]). -import(lists, [foreach/2]).
-define(TAB_OPTS, [public,
{read_concurrency, true},
{write_concurrency, true}]).
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
@ -33,24 +37,23 @@ start_link() ->
init([]) -> init([]) ->
%% Create the pubsub tables %% Create the pubsub tables
lists:foreach(fun create_tab/1, foreach(fun create_tab/1, [subscription, subscriber, suboption]),
[subscription, subscriber, suboption]),
%% Shared subscription %% Shared subscription
Shared = {shared_sub, {emqx_shared_sub, start_link, []}, SharedSub = {shared_sub, {emqx_shared_sub, start_link, []},
permanent, 5000, worker, [emqx_shared_sub]}, permanent, 5000, worker, [emqx_shared_sub]},
%% Broker helper %% Broker helper
Helper = {broker_helper, {emqx_broker_helper, start_link, [stats_fun()]}, Helper = {broker_helper, {emqx_broker_helper, start_link, []},
permanent, 5000, worker, [emqx_broker_helper]}, permanent, 5000, worker, [emqx_broker_helper]},
%% Broker pool %% Broker pool
PoolArgs = [broker, hash, emqx_sys:schedulers() * 2, PoolArgs = [broker, hash, emqx_vm:schedulers() * 2,
{emqx_broker, start_link, []}], {emqx_broker, start_link, []}],
PoolSup = emqx_pool_sup:spec(broker_pool, PoolArgs), PoolSup = emqx_pool_sup:spec(eqmx_broker_pool, PoolArgs),
{ok, {{one_for_all, 0, 3600}, [Shared, Helper, PoolSup]}}. {ok, {{one_for_all, 0, 1}, [SharedSub, Helper, PoolSup]}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Create tables %% Create tables
@ -58,27 +61,15 @@ init([]) ->
create_tab(suboption) -> create_tab(suboption) ->
%% Suboption: {Topic, Sub} -> [{qos, 1}] %% Suboption: {Topic, Sub} -> [{qos, 1}]
emqx_tables:create(emqx_suboption, [public, set | ?CONCURRENCY_OPTS]); emqx_tables:new(emqx_suboption, [set | ?TAB_OPTS]);
create_tab(subscriber) -> create_tab(subscriber) ->
%% Subscriber: Topic -> Sub1, Sub2, Sub3, ..., SubN %% Subscriber: Topic -> Sub1, Sub2, Sub3, ..., SubN
%% duplicate_bag: o(1) insert %% duplicate_bag: o(1) insert
emqx_tables:create(emqx_subscriber, [public, duplicate_bag | ?CONCURRENCY_OPTS]); emqx_tables:new(emqx_subscriber, [duplicate_bag | ?TAB_OPTS]);
create_tab(subscription) -> create_tab(subscription) ->
%% Subscription: Sub -> Topic1, Topic2, Topic3, ..., TopicN %% Subscription: Sub -> Topic1, Topic2, Topic3, ..., TopicN
%% bag: o(n) insert %% bag: o(n) insert
emqx_tables:create(emqx_subscription, [public, bag | ?CONCURRENCY_OPTS]). emqx_tables:new(emqx_subscription, [bag | ?TAB_OPTS]).
%%--------------------------------------------------------------------
%% Stats function
%%--------------------------------------------------------------------
stats_fun() ->
fun() ->
emqx_stats:setstat('subscribers/count', 'subscribers/max',
ets:info(emqx_subscriber, size)),
emqx_stats:setstat('subscriptions/count', 'subscriptions/max',
ets:info(emqx_subscription, size))
end.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_cli). -module(emqx_cli).

70
src/emqx_client.erl Normal file
View File

@ -0,0 +1,70 @@
%%%===================================================================
%%% Copyright (c) 2013-2018 EMQ Inc. 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_client).
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {}).
%%%===================================================================
%%% API
%%%===================================================================
%% @doc Starts the server
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_cm). -module(emqx_cm).
@ -22,92 +22,145 @@
-export([start_link/0]). -export([start_link/0]).
-export([lookup/1, reg/1, unreg/1]). -export([lookup_client/1, register_client/1, register_client/2,
unregister_client/1]).
-export([get_client_attrs/1, lookup_client_pid/1]).
-export([get_client_stats/1, set_client_stats/2]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-record(state, {stats_fun, stats_timer, monitors}). -record(state, {client_pmon}).
-define(SERVER, ?MODULE). -define(CM, ?MODULE).
%%-------------------------------------------------------------------- %% ETS Tables.
%% API -define(CLIENT, emqx_client).
%%-------------------------------------------------------------------- -define(CLIENT_ATTRS, emqx_client_attrs).
-define(CLIENT_STATS, emqx_client_stats).
%% @doc Start the client manager %% @doc Start the client manager.
-spec(start_link() -> {ok, pid()} | ignore | {error, term()}). -spec(start_link() -> {ok, pid()} | ignore | {error, term()}).
start_link() -> start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?CM}, ?MODULE, [], []).
%% @doc Lookup ClientPid by ClientId %% @doc Lookup a client.
-spec(lookup(client_id()) -> pid() | undefined). -spec(lookup_client(client_id()) -> list({client_id(), pid()})).
lookup(ClientId) when is_binary(ClientId) -> lookup_client(ClientId) when is_binary(ClientId) ->
try ets:lookup_element(client, ClientId, 2) ets:lookup(?CLIENT, ClientId).
%% @doc Register a client.
-spec(register_client(client_id() | {client_id(), pid()}) -> ok).
register_client(ClientId) when is_binary(ClientId) ->
register_client({ClientId, self()});
register_client({ClientId, ClientPid}) when is_binary(ClientId),
is_pid(ClientPid) ->
register_client({ClientId, ClientPid}, []).
-spec(register_client({client_id(), pid()}, list()) -> ok).
register_client({ClientId, ClientPid}, Attrs) when is_binary(ClientId),
is_pid(ClientPid) ->
ets:insert(?CLIENT, {ClientId, ClientPid}),
ets:insert(?CLIENT_ATTRS, {{ClientId, ClientPid}, Attrs}),
notify({registered, ClientId, ClientPid}).
%% @doc Get client attrs
-spec(get_client_attrs({client_id(), pid()}) -> list()).
get_client_attrs({ClientId, ClientPid}) when is_binary(ClientId),
is_pid(ClientPid) ->
try ets:lookup_element(?CLIENT_ATTRS, {ClientId, ClientPid}, 2)
catch catch
error:badarg -> undefined error:badarg -> []
end. end.
%% @doc Register a clientId %% @doc Unregister a client.
-spec(reg(client_id()) -> ok). -spec(unregister_client(client_id() | {client_id(), pid()}) -> ok).
reg(ClientId) -> unregister_client(ClientId) when is_binary(ClientId) ->
gen_server:cast(?SERVER, {reg, ClientId, self()}). unregister_client({ClientId, self()});
%% @doc Unregister clientId with pid. unregister_client({ClientId, ClientPid}) when is_binary(ClientId),
-spec(unreg(client_id()) -> ok). is_pid(ClientPid) ->
unreg(ClientId) -> ets:delete(?CLIENT_STATS, {ClientId, ClientPid}),
gen_server:cast(?SERVER, {unreg, ClientId, self()}). ets:delete(?CLIENT_ATTRS, {ClientId, ClientPid}),
ets:delete_object(?CLIENT, {ClientId, ClientPid}),
notify({unregistered, ClientId, ClientPid}).
%% @doc Lookup client pid
-spec(lookup_client_pid(client_id()) -> pid() | undefined).
lookup_client_pid(ClientId) when is_binary(ClientId) ->
case lookup_client_pid(ClientId) of
[] -> undefined;
[{_, Pid}] -> Pid
end.
%% @doc Get client stats
-spec(get_client_stats({client_id(), pid()}) -> list(emqx_stats:stats())).
get_client_stats({ClientId, ClientPid}) when is_binary(ClientId),
is_pid(ClientPid) ->
try ets:lookup_element(?CLIENT_STATS, {ClientId, ClientPid}, 2)
catch
error:badarg -> []
end.
%% @doc Set client stats.
-spec(set_client_stats(client_id(), list(emqx_stats:stats())) -> boolean()).
set_client_stats(ClientId, Stats) when is_binary(ClientId) ->
set_client_stats({ClientId, self()}, Stats);
set_client_stats({ClientId, ClientPid}, Stats) when is_binary(ClientId),
is_pid(ClientPid) ->
ets:insert(?CLIENT_STATS, {{ClientId, ClientPid}, Stats}).
notify(Msg) ->
gen_server:cast(?CM, {notify, Msg}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
_ = emqx_tables:create(client, [public, set, {keypos, 2}, TabOpts = [public, set, {write_concurrency, true}],
{read_concurrency, true}, _ = emqx_tables:new(?CLIENT, [{read_concurrency, true} | TabOpts]),
{write_concurrency, true}]), _ = emqx_tables:new(?CLIENT_ATTRS, TabOpts),
_ = emqx_tables:create(client_attrs, [public, set, _ = emqx_tables:new(?CLIENT_STATS, TabOpts),
{write_concurrency, true}]), ok = emqx_stats:update_interval(cm_stats, fun update_client_stats/0),
{ok, #state{monitors = dict:new()}}. {ok, #state{client_pmon = emqx_pmon:new()}}.
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[CM] Unexpected request: ~p", [Req]), emqx_logger:error("[CM] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast({reg, ClientId, Pid}, State) -> handle_cast({notify, {registered, ClientId, Pid}},
_ = ets:insert(client, {ClientId, Pid}), State = #state{client_pmon = PMon}) ->
{noreply, monitor_client(ClientId, Pid, State)}; {noreply, State#state{client_pmon = PMon:monitor(Pid, ClientId)}};
handle_cast({unreg, ClientId, Pid}, State) -> handle_cast({notify, {unregistered, _ClientId, Pid}},
case lookup(ClientId) of State = #state{client_pmon = PMon}) ->
Pid -> remove_client({ClientId, Pid}); {noreply, State#state{client_pmon = PMon:demonitor(Pid)}};
_ -> ok
end,
{noreply, State};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[CM] Unexpected msg: ~p", [Msg]), emqx_logger:error("[CM] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) -> handle_info({'DOWN', _MRef, process, DownPid, _Reason},
case dict:find(MRef, State#state.monitors) of State = #state{client_pmon = PMon}) ->
{ok, ClientId} -> case PMon:find(DownPid) of
case lookup(ClientId) of undefined ->
DownPid -> remove_client({ClientId, DownPid}); {noreply, State};
_ -> ok ClientId ->
end, unregister_client({ClientId, DownPid}),
{noreply, erase_monitor(MRef, State)}; {noreply, State#state{client_pmon = PMon:erase(DownPid)}}
error ->
emqx_log:error("[CM] down client ~p not found", [DownPid]),
{noreply, State}
end; end;
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[CM] Unexpected info: ~p", [Info]), emqx_logger:error("[CM] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State = #state{stats_timer = TRef}) -> terminate(_Reason, _State = #state{}) ->
timer:cancel(TRef). emqx_stats:cancel_update(cm_stats).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -116,16 +169,10 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
remove_client(Client) -> update_client_stats() ->
ets:delete_object(client, Client), case ets:info(?CLIENT, size) of
ets:delete(client_stats, Client), undefined -> ok;
ets:delete(client_attrs, Client). Size ->
emqx_stats:setstat('clients/count', 'clients/max', Size)
monitor_client(ClientId, Pid, State = #state{monitors = Monitors}) -> end.
MRef = erlang:monitor(process, Pid),
State#state{monitors = dict:store(MRef, ClientId, Monitors)}.
erase_monitor(MRef, State = #state{monitors = Monitors}) ->
erlang:demonitor(MRef),
State#state{monitors = dict:erase(MRef, Monitors)}.

View File

@ -1,72 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Inc. 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_cm_stats).
-behaviour(gen_statem).
-include("emqx.hrl").
%% API
-export([start_link/0]).
-export([set_client_stats/2, get_client_stats/1, del_client_stats/1]).
%% gen_statem callbacks
-export([init/1, callback_mode/0, handle_event/4, terminate/3, code_change/4]).
-define(TAB, client_stats).
-record(state, {statsfun}).
start_link() ->
gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec(set_client_stats(client_id(), emqx_stats:stats()) -> true).
set_client_stats(ClientId, Stats) ->
ets:insert(?TAB, {ClientId, [{'$ts', emqx_time:now_secs()}|Stats]}).
-spec(get_client_stats(client_id()) -> emqx_stats:stats()).
get_client_stats(ClientId) ->
case ets:lookup(?TAB, ClientId) of
[{_, Stats}] -> Stats;
[] -> []
end.
-spec(del_client_stats(client_id()) -> true).
del_client_stats(ClientId) ->
ets:delete(?TAB, ClientId).
init([]) ->
_ = emqx_tables:create(?TAB, [public, {write_concurrency, true}]),
StatsFun = emqx_stats:statsfun('clients/count', 'clients/max'),
{ok, idle, #state{statsfun = StatsFun}, timer:seconds(1)}.
callback_mode() -> handle_event_function.
handle_event(timeout, _Timeout, idle, State = #state{statsfun = StatsFun}) ->
case ets:info(client, size) of
undefined -> ok;
Size -> StatsFun(Size)
end,
{next_state, idle, State, timer:seconds(1)}.
terminate(_Reason, _StateName, _State) ->
ok.
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_cm_sup). -module(emqx_cm_sup).
@ -26,9 +26,7 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) -> init([]) ->
Stats = {emqx_cm_stats, {emqx_cm_stats, start_link, []},
permanent, 5000, worker, [emqx_cm_stats]},
CM = {emqx_cm, {emqx_cm, start_link, []}, CM = {emqx_cm, {emqx_cm, start_link, []},
permanent, 5000, worker, [emqx_cm]}, permanent, 5000, worker, [emqx_cm]},
{ok, {{one_for_all, 10, 3600}, [Stats, CM]}}. {ok, {{one_for_all, 10, 3600}, [CM]}}.

View File

@ -57,7 +57,7 @@
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]).
-define(LOG(Level, Format, Args, State), -define(LOG(Level, Format, Args, State),
emqx_log:Level("Client(~s): " ++ Format, emqx_logger:Level("Client(~s): " ++ Format,
[esockd_net:format(State#state.peername) | Args])). [esockd_net:format(State#state.peername) | Args])).
start_link(Conn, Env) -> start_link(Conn, Env) ->
@ -316,7 +316,7 @@ received(Bytes, State = #state{parser = Parser,
{more, NewParser} -> {more, NewParser} ->
{noreply, run_socket(State#state{parser = NewParser}), IdleTimeout}; {noreply, run_socket(State#state{parser = NewParser}), IdleTimeout};
{ok, Packet, Rest} -> {ok, Packet, Rest} ->
emqx_mqtt_metrics:received(Packet), emqx_metrics:received(Packet),
case emqx_protocol:received(Packet, ProtoState) of case emqx_protocol:received(Packet, ProtoState) of
{ok, ProtoState1} -> {ok, ProtoState1} ->
received(Rest, State#state{parser = emqx_parser:initial_state(PacketSize), received(Rest, State#state{parser = emqx_parser:initial_state(PacketSize),
@ -371,7 +371,7 @@ emit_stats(undefined, State) ->
State; State;
emit_stats(ClientId, State) -> emit_stats(ClientId, State) ->
{reply, Stats, _, _} = handle_call(stats, undefined, State), {reply, Stats, _, _} = handle_call(stats, undefined, State),
emqx_stats:set_client_stats(ClientId, Stats), emqx_cm:set_client_stats(ClientId, Stats),
State. State.
sock_stats(#state{connection = Conn}) -> sock_stats(#state{connection = Conn}) ->

View File

@ -1,26 +1,26 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_ctl). -module(emqx_ctl).
-behaviour(gen_server). -behaviour(gen_server).
%% API Function Exports -export([start_link/0]).
-export([start_link/0, register_cmd/2, register_cmd/3, unregister_cmd/1, -export([register_command/2, register_command/3, unregister_command/1]).
lookup/1, run/1]). -export([run_command/2, lookup_command/1]).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@ -28,9 +28,10 @@
-record(state, {seq = 0}). -record(state, {seq = 0}).
-define(SERVER, ?MODULE). -type(cmd() :: atom()).
-define(TAB, ?MODULE). -define(SERVER, ?MODULE).
-define(TAB, emqx_command).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
@ -40,47 +41,44 @@ start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% @doc Register a command %% @doc Register a command
-spec(register_cmd(atom(), {module(), atom()}) -> ok). -spec(register_command(cmd(), {module(), atom()}) -> ok).
register_cmd(Cmd, MF) -> register_command(Cmd, MF) when is_atom(Cmd) ->
register_cmd(Cmd, MF, []). register_command(Cmd, MF, []).
%% @doc Register a command with opts %% @doc Register a command with options
-spec(register_cmd(atom(), {module(), atom()}, list()) -> ok). -spec(register_command(cmd(), {module(), atom()}, list()) -> ok).
register_cmd(Cmd, MF, Opts) -> register_command(Cmd, MF, Opts) when is_atom(Cmd) ->
cast({register_cmd, Cmd, MF, Opts}). cast({register_command, Cmd, MF, Opts}).
%% @doc Unregister a command %% @doc Unregister a command
-spec(unregister_cmd(atom()) -> ok). -spec(unregister_command(cmd()) -> ok).
unregister_cmd(Cmd) -> unregister_command(Cmd) when is_atom(Cmd) ->
cast({unregister_cmd, Cmd}). cast({unregister_command, Cmd}).
cast(Msg) -> gen_server:cast(?SERVER, Msg). cast(Msg) -> gen_server:cast(?SERVER, Msg).
%% @doc Run a command %% @doc Run a command
-spec(run([string()]) -> any()). -spec(run_command(cmd(), [string()]) -> ok | {error, term()}).
run([]) -> usage(), ok; run_command(help, []) ->
usage();
run(["help"]) -> usage(), ok; run_command(Cmd, Args) when is_atom(Cmd) ->
case lookup_command(Cmd) of
run([CmdS|Args]) ->
case lookup(list_to_atom(CmdS)) of
[{Mod, Fun}] -> [{Mod, Fun}] ->
try Mod:Fun(Args) of try Mod:Fun(Args) of
_ -> ok _ -> ok
catch catch
_:Reason -> _:Reason ->
io:format("Reason:~p, get_stacktrace:~p~n", emqx_logger:error("[CTL] Cmd error:~p, stacktrace:~p",
[Reason, erlang:get_stacktrace()]), [Reason, erlang:get_stacktrace()]),
{error, Reason} {error, Reason}
end; end;
[] -> [] ->
usage(), usage(), {error, cmd_not_found}
{error, cmd_not_found}
end. end.
%% @doc Lookup a command %% @doc Lookup a command
-spec(lookup(atom()) -> [{module(), atom()}]). -spec(lookup_command(cmd()) -> [{module(), atom()}]).
lookup(Cmd) -> lookup_command(Cmd) when is_atom(Cmd) ->
case ets:match(?TAB, {{'_', Cmd}, '$1', '_'}) of case ets:match(?TAB, {{'_', Cmd}, '$1', '_'}) of
[El] -> El; [El] -> El;
[] -> [] [] -> []
@ -97,30 +95,32 @@ usage() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
ets:new(?TAB, [ordered_set, named_table, protected]), _ = emqx_tables:new(?TAB, [ordered_set, protected]),
{ok, #state{seq = 0}}. {ok, #state{seq = 0}}.
handle_call(_Request, _From, State) -> handle_call(Req, _From, State) ->
{reply, ok, State}. emqx_logger:error("Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast({register_cmd, Cmd, MF, Opts}, State = #state{seq = Seq}) -> handle_cast({register_command, Cmd, MF, Opts}, State = #state{seq = Seq}) ->
case ets:match(?TAB, {{'$1', Cmd}, '_', '_'}) of case ets:match(?TAB, {{'$1', Cmd}, '_', '_'}) of
[] -> [] -> ets:insert(?TAB, {{Seq, Cmd}, MF, Opts});
ets:insert(?TAB, {{Seq, Cmd}, MF, Opts});
[[OriginSeq] | _] -> [[OriginSeq] | _] ->
emqx_log:warning("[CLI] ~s is overidden by ~p", [Cmd, MF]), emqx_logger:warning("[CTL] cmd ~s is overidden by ~p", [Cmd, MF]),
ets:insert(?TAB, {{OriginSeq, Cmd}, MF, Opts}) ets:insert(?TAB, {{OriginSeq, Cmd}, MF, Opts})
end, end,
noreply(next_seq(State)); noreply(next_seq(State));
handle_cast({unregister_cmd, Cmd}, State) -> handle_cast({unregister_command, Cmd}, State) ->
ets:match_delete(?TAB, {{'_', Cmd}, '_', '_'}), ets:match_delete(?TAB, {{'_', Cmd}, '_', '_'}),
noreply(State); noreply(State);
handle_cast(_Msg, State) -> handle_cast(Msg, State) ->
emqx_logger:error("Unexpected msg: ~p", [Msg]),
noreply(State). noreply(State).
handle_info(_Info, State) -> handle_info(Info, State) ->
emqx_logger:error("Unexpected info: ~p", [Info]),
noreply(State). noreply(State).
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
@ -143,7 +143,7 @@ next_seq(State = #state{seq = Seq}) ->
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
register_cmd_test_() -> register_command_test_() ->
{setup, {setup,
fun() -> fun() ->
{ok, InitState} = emqx_ctl:init([]), {ok, InitState} = emqx_ctl:init([]),
@ -153,7 +153,7 @@ register_cmd_test_() ->
ok = emqx_ctl:terminate(shutdown, State) ok = emqx_ctl:terminate(shutdown, State)
end, end,
fun(State = #state{seq = Seq}) -> fun(State = #state{seq = Seq}) ->
emqx_ctl:handle_cast({register_cmd, test0, {?MODULE, test0}, []}, State), emqx_ctl:handle_cast({register_command, test0, {?MODULE, test0}, []}, State),
[?_assertMatch([{{0,test0},{?MODULE, test0}, []}], ets:lookup(?TAB, {Seq,test0}))] [?_assertMatch([{{0,test0},{?MODULE, test0}, []}], ets:lookup(?TAB, {Seq,test0}))]
end end
}. }.

70
src/emqx_flow_control.erl Normal file
View File

@ -0,0 +1,70 @@
%%%===================================================================
%%% Copyright (c) 2013-2018 EMQ Inc. 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_flow_control).
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {}).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
%% @doc Starts the server
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
init([]) ->
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------

View File

@ -1,24 +1,23 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_hooks). -module(emqx_hooks).
-behaviour(gen_server). -behaviour(gen_server).
%% Start
-export([start_link/0]). -export([start_link/0]).
%% Hooks API %% Hooks API
@ -41,7 +40,7 @@
-record(hook, {name :: atom(), callbacks = [] :: list(#callback{})}). -record(hook, {name :: atom(), callbacks = [] :: list(#callback{})}).
-define(HOOK_TAB, mqtt_hook). -define(TAB, ?MODULE).
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
@ -104,24 +103,25 @@ run_([], _Args, Acc) ->
-spec(lookup(atom()) -> [#callback{}]). -spec(lookup(atom()) -> [#callback{}]).
lookup(HookPoint) -> lookup(HookPoint) ->
case ets:lookup(?HOOK_TAB, HookPoint) of case ets:lookup(?TAB, HookPoint) of
[#hook{callbacks = Callbacks}] -> Callbacks; [#hook{callbacks = Callbacks}] -> Callbacks;
[] -> [] [] -> []
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server Callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
ets:new(?HOOK_TAB, [set, protected, named_table, {keypos, #hook.name}]), _ = emqx_tables:new(?TAB, [set, protected, {keypos, #hook.name},
{read_concurrency, true}]),
{ok, #state{}}. {ok, #state{}}.
handle_call({add, HookPoint, {Tag, Function}, InitArgs, Priority}, _From, State) -> handle_call({add, HookPoint, {Tag, Function}, InitArgs, Priority}, _From, State) ->
Callback = #callback{tag = Tag, function = Function, Callback = #callback{tag = Tag, function = Function,
init_args = InitArgs, priority = Priority}, init_args = InitArgs, priority = Priority},
{reply, {reply,
case ets:lookup(?HOOK_TAB, HookPoint) of case ets:lookup(?TAB, HookPoint) of
[#hook{callbacks = Callbacks}] -> [#hook{callbacks = Callbacks}] ->
case contain_(Tag, Function, Callbacks) of case contain_(Tag, Function, Callbacks) of
false -> false ->
@ -135,7 +135,7 @@ handle_call({add, HookPoint, {Tag, Function}, InitArgs, Priority}, _From, State)
handle_call({delete, HookPoint, {Tag, Function}}, _From, State) -> handle_call({delete, HookPoint, {Tag, Function}}, _From, State) ->
{reply, {reply,
case ets:lookup(?HOOK_TAB, HookPoint) of case ets:lookup(?TAB, HookPoint) of
[#hook{callbacks = Callbacks}] -> [#hook{callbacks = Callbacks}] ->
case contain_(Tag, Function, Callbacks) of case contain_(Tag, Function, Callbacks) of
true -> true ->
@ -167,7 +167,7 @@ code_change(_OldVsn, State, _Extra) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
insert_hook_(HookPoint, Callbacks) -> insert_hook_(HookPoint, Callbacks) ->
ets:insert(?HOOK_TAB, #hook{name = HookPoint, callbacks = Callbacks}), ok. ets:insert(?TAB, #hook{name = HookPoint, callbacks = Callbacks}), ok.
add_callback_(Callback, Callbacks) -> add_callback_(Callback, Callbacks) ->
lists:keymerge(#callback.priority, Callbacks, [Callback]). lists:keymerge(#callback.priority, Callbacks, [Callback]).

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_inflight). -module(emqx_inflight).

View File

@ -1,22 +1,23 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_json). -module(emqx_json).
-export([encode/1, encode/2, decode/1, decode/2]). -export([encode/1, encode/2, safe_encode/1, safe_encode/2]).
-export([decode/1, decode/2, safe_decode/1, safe_decode/2]).
-spec(encode(jsx:json_term()) -> jsx:json_text()). -spec(encode(jsx:json_term()) -> jsx:json_text()).
encode(Term) -> encode(Term) ->
@ -26,11 +27,41 @@ encode(Term) ->
encode(Term, Opts) -> encode(Term, Opts) ->
jsx:encode(Term, Opts). jsx:encode(Term, Opts).
-spec(safe_encode(jsx:json_term())
-> {ok, jsx:json_text()} | {error, term()}).
safe_encode(Term) ->
safe_encode(Term, []).
-spec(safe_encode(jsx:json_term(), jsx_to_json:config())
-> {ok, jsx:json_text()} | {error, term()}).
safe_encode(Term, Opts) ->
try encode(Term, Opts) of
Json -> {ok, Json}
catch
error:Reason ->
{error, Reason}
end.
-spec(decode(jsx:json_text()) -> jsx:json_term()). -spec(decode(jsx:json_text()) -> jsx:json_term()).
decode(JSON) -> decode(Json) ->
jsx:decode(JSON). jsx:decode(Json).
-spec(decode(jsx:json_text(), jsx_to_json:config()) -> jsx:json_term()). -spec(decode(jsx:json_text(), jsx_to_json:config()) -> jsx:json_term()).
decode(JSON, Opts) -> decode(Json, Opts) ->
jsx:decode(JSON, Opts). jsx:decode(Json, Opts).
-spec(safe_decode(jsx:json_text())
-> {ok, jsx:json_term()} | {error, term()}).
safe_decode(Json) ->
safe_decode(Json, []).
-spec(safe_decode(jsx:json_text(), jsx_to_json:config())
-> {ok, jsx:json_term()} | {error, term()}).
safe_decode(Json, Opts) ->
try decode(Json, Opts) of
Term -> {ok, Term}
catch
error:Reason ->
{error, Reason}
end.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_keepalive). -module(emqx_keepalive).
@ -29,19 +29,21 @@
start(_, 0, _) -> start(_, 0, _) ->
{ok, #keepalive{}}; {ok, #keepalive{}};
start(StatFun, TimeoutSec, TimeoutMsg) -> start(StatFun, TimeoutSec, TimeoutMsg) ->
case StatFun() of case catch StatFun() of
{ok, StatVal} -> {ok, StatVal} ->
{ok, #keepalive{statfun = StatFun, statval = StatVal, {ok, #keepalive{statfun = StatFun, statval = StatVal,
tsec = TimeoutSec, tmsg = TimeoutMsg, tsec = TimeoutSec, tmsg = TimeoutMsg,
tref = timer(TimeoutSec, TimeoutMsg)}}; tref = timer(TimeoutSec, TimeoutMsg)}};
{error, Error} -> {error, Error} ->
{error, Error} {error, Error};
{'EXIT', Reason} ->
{error, Reason}
end. end.
%% @doc Check keepalive, called when timeout. %% @doc Check keepalive, called when timeout...
-spec(check(keepalive()) -> {ok, keepalive()} | {error, term()}). -spec(check(keepalive()) -> {ok, keepalive()} | {error, term()}).
check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repeat}) -> check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repeat}) ->
case StatFun() of case catch StatFun() of
{ok, NewVal} -> {ok, NewVal} ->
if NewVal =/= LastVal -> if NewVal =/= LastVal ->
{ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = 0})}; {ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = 0})};
@ -51,9 +53,12 @@ check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repe
{error, timeout} {error, timeout}
end; end;
{error, Error} -> {error, Error} ->
{error, Error} {error, Error};
{'EXIT', Reason} ->
{error, Reason}
end. end.
-spec(resume(keepalive()) -> keepalive()).
resume(KeepAlive = #keepalive{tsec = TimeoutSec, tmsg = TimeoutMsg}) -> resume(KeepAlive = #keepalive{tsec = TimeoutSec, tmsg = TimeoutMsg}) ->
KeepAlive#keepalive{tref = timer(TimeoutSec, TimeoutMsg)}. KeepAlive#keepalive{tref = timer(TimeoutSec, TimeoutMsg)}.
@ -64,6 +69,6 @@ cancel(#keepalive{tref = TRef}) when is_reference(TRef) ->
cancel(_) -> cancel(_) ->
ok. ok.
timer(Sec, Msg) -> timer(Secs, Msg) ->
erlang:send_after(timer:seconds(Sec), self(), Msg). erlang:send_after(timer:seconds(Secs), self(), Msg).

43
src/emqx_kernel_sup.erl Normal file
View File

@ -0,0 +1,43 @@
%%%===================================================================
%%% Copyright (c) 2013-2018 EMQ Inc. 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_kernel_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
{ok, {{one_for_one, 10, 100},
[child_spec(emqx_pool, supervisor),
child_spec(emqx_alarm, worker),
child_spec(emqx_hooks, worker),
child_spec(emqx_stats, worker),
child_spec(emqx_metrics, worker),
child_spec(emqx_ctl, worker),
child_spec(emqx_tracer, worker)]}}.
child_spec(M, worker) ->
{M, {M, start_link, []}, permanent, 5000, worker, [M]};
child_spec(M, supervisor) ->
{M, {M, start_link, []},
permanent, infinity, supervisor, [M]}.

View File

@ -1,20 +1,20 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_log). -module(emqx_logger).
-compile({no_auto_import,[error/1]}). -compile({no_auto_import,[error/1]}).

View File

@ -50,7 +50,7 @@ get_flag(Flag, #message{flags = Flags}, Default) ->
%% @doc Set flag %% @doc Set flag
-spec(set_flag(message_flag(), message()) -> message()). -spec(set_flag(message_flag(), message()) -> message()).
set_flag(Flag, Msg = #message{flags = Flags}) -> set_flag(Flag, Msg = #message{flags = Flags}) when is_atom(Flag) ->
Msg#message{flags = maps:put(Flag, true, Flags)}. Msg#message{flags = maps:put(Flag, true, Flags)}.
%% @doc Unset flag %% @doc Unset flag

View File

@ -1,55 +1,110 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_metrics). -module(emqx_metrics).
-behaviour(gen_server). -include("emqx_mqtt.hrl").
%% API Function Exports -export([start_link/0]).
-export([start_link/0, create/1]).
-export([all/0, value/1, inc/1, inc/2, inc/3, dec/2, dec/3, set/2]). -export([new/1, all/0]).
-export([val/1, inc/1, inc/2, inc/3, dec/2, dec/3, set/2]).
%% Received/sent metrics
-export([received/1, sent/1]).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-record(state, {tick}). -record(state, {}).
-define(TAB, metrics). %% Bytes sent and received of Broker
-define(BYTES_METRICS, [
{counter, 'bytes/received'}, % Total bytes received
{counter, 'bytes/sent'} % Total bytes sent
]).
%% Packets sent and received of broker
-define(PACKET_METRICS, [
{counter, 'packets/received'}, % All Packets received
{counter, 'packets/sent'}, % All Packets sent
{counter, 'packets/connect'}, % CONNECT Packets received
{counter, 'packets/connack'}, % CONNACK Packets sent
{counter, 'packets/publish/received'}, % PUBLISH packets received
{counter, 'packets/publish/sent'}, % PUBLISH packets sent
{counter, 'packets/puback/received'}, % PUBACK packets received
{counter, 'packets/puback/sent'}, % PUBACK packets sent
{counter, 'packets/puback/missed'}, % PUBACK packets missed
{counter, 'packets/pubrec/received'}, % PUBREC packets received
{counter, 'packets/pubrec/sent'}, % PUBREC packets sent
{counter, 'packets/pubrec/missed'}, % PUBREC packets missed
{counter, 'packets/pubrel/received'}, % PUBREL packets received
{counter, 'packets/pubrel/sent'}, % PUBREL packets sent
{counter, 'packets/pubrel/missed'}, % PUBREL packets missed
{counter, 'packets/pubcomp/received'}, % PUBCOMP packets received
{counter, 'packets/pubcomp/sent'}, % PUBCOMP packets sent
{counter, 'packets/pubcomp/missed'}, % PUBCOMP packets missed
{counter, 'packets/subscribe'}, % SUBSCRIBE Packets received
{counter, 'packets/suback'}, % SUBACK packets sent
{counter, 'packets/unsubscribe'}, % UNSUBSCRIBE Packets received
{counter, 'packets/unsuback'}, % UNSUBACK Packets sent
{counter, 'packets/pingreq'}, % PINGREQ packets received
{counter, 'packets/pingresp'}, % PINGRESP Packets sent
{counter, 'packets/disconnect'}, % DISCONNECT Packets received
{counter, 'packets/auth'} % Auth Packets received
]).
%% Messages sent and received of broker
-define(MESSAGE_METRICS, [
{counter, 'messages/received'}, % All Messages received
{counter, 'messages/sent'}, % All Messages sent
{counter, 'messages/qos0/received'}, % QoS0 Messages received
{counter, 'messages/qos0/sent'}, % QoS0 Messages sent
{counter, 'messages/qos1/received'}, % QoS1 Messages received
{counter, 'messages/qos1/sent'}, % QoS1 Messages sent
{counter, 'messages/qos2/received'}, % QoS2 Messages received
{counter, 'messages/qos2/sent'}, % QoS2 Messages sent
{counter, 'messages/qos2/dropped'}, % QoS2 Messages dropped
{gauge, 'messages/retained'}, % Messagea retained
{counter, 'messages/dropped'}, % Messages dropped
{counter, 'messages/forward'} % Messages forward
]).
-define(TAB, ?MODULE).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
%% @doc Start the metrics server %% @doc Start the metrics server
-spec(start_link() -> {ok, pid()} | ignore | {error, term()}). -spec(start_link() -> {ok, pid()} | ignore | {error, term()}).
start_link() -> start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
create({gauge, Name}) -> %%--------------------------------------------------------------------
%% Metrics API
%%--------------------------------------------------------------------
new({gauge, Name}) ->
ets:insert(?TAB, {{Name, 0}, 0}); ets:insert(?TAB, {{Name, 0}, 0});
create({counter, Name}) -> new({counter, Name}) ->
Schedulers = lists:seq(1, erlang:system_info(schedulers)), Schedulers = lists:seq(1, emqx_vm:schedulers()),
ets:insert(?TAB, [{{Name, I}, 0} || I <- Schedulers]). ets:insert(?TAB, [{{Name, I}, 0} || I <- Schedulers]).
%% @doc Get all metrics %% @doc Get all metrics
-spec(all() -> [{atom(), non_neg_integer()}]). -spec(all() -> [{atom(), non_neg_integer()}]).
all() -> all() ->
@ -63,8 +118,8 @@ all() ->
end, #{}, ?TAB)). end, #{}, ?TAB)).
%% @doc Get metric value %% @doc Get metric value
-spec(value(atom()) -> non_neg_integer()). -spec(val(atom()) -> non_neg_integer()).
value(Metric) -> val(Metric) ->
lists:sum(ets:select(?TAB, [{{{Metric, '_'}, '$1'}, [], ['$1']}])). lists:sum(ets:select(?TAB, [{{{Metric, '_'}, '$1'}, [], ['$1']}])).
%% @doc Increase counter %% @doc Increase counter
@ -84,9 +139,9 @@ inc(Metric, Val) when is_atom(Metric) ->
%% @doc Increase metric value %% @doc Increase metric value
-spec(inc(counter | gauge, atom(), pos_integer()) -> pos_integer()). -spec(inc(counter | gauge, atom(), pos_integer()) -> pos_integer()).
inc(gauge, Metric, Val) -> inc(gauge, Metric, Val) ->
ets:update_counter(?TAB, key(gauge, Metric), {2, Val}); update_counter(key(gauge, Metric), {2, Val});
inc(counter, Metric, Val) -> inc(counter, Metric, Val) ->
ets:update_counter(?TAB, key(counter, Metric), {2, Val}). update_counter(key(counter, Metric), {2, Val}).
%% @doc Decrease metric value %% @doc Decrease metric value
-spec(dec(gauge, atom()) -> integer()). -spec(dec(gauge, atom()) -> integer()).
@ -96,7 +151,7 @@ dec(gauge, Metric) ->
%% @doc Decrease metric value %% @doc Decrease metric value
-spec(dec(gauge, atom(), pos_integer()) -> integer()). -spec(dec(gauge, atom(), pos_integer()) -> integer()).
dec(gauge, Metric, Val) -> dec(gauge, Metric, Val) ->
ets:update_counter(?TAB, key(gauge, Metric), {2, -Val}). update_counter(key(gauge, Metric), {2, -Val}).
%% @doc Set metric value %% @doc Set metric value
set(Metric, Val) when is_atom(Metric) -> set(Metric, Val) when is_atom(Metric) ->
@ -104,57 +159,120 @@ set(Metric, Val) when is_atom(Metric) ->
set(gauge, Metric, Val) -> set(gauge, Metric, Val) ->
ets:insert(?TAB, {key(gauge, Metric), Val}). ets:insert(?TAB, {key(gauge, Metric), Val}).
%% @doc Metric Key %% @doc Metric key
key(gauge, Metric) -> key(gauge, Metric) ->
{Metric, 0}; {Metric, 0};
key(counter, Metric) -> key(counter, Metric) ->
{Metric, erlang:system_info(scheduler_id)}. {Metric, erlang:system_info(scheduler_id)}.
update_counter(Key, UpOp) ->
ets:update_counter(?TAB, Key, UpOp).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server Callbacks %% Receive/Sent metrics
%%--------------------------------------------------------------------
%% @doc Count packets received.
-spec(received(mqtt_packet()) -> ok).
received(Packet) ->
inc('packets/received'),
received1(Packet).
received1(?PUBLISH_PACKET(Qos, _PktId)) ->
inc('packets/publish/received'),
inc('messages/received'),
qos_received(Qos);
received1(?PACKET(Type)) ->
received2(Type).
received2(?CONNECT) ->
inc('packets/connect');
received2(?PUBACK) ->
inc('packets/puback/received');
received2(?PUBREC) ->
inc('packets/pubrec/received');
received2(?PUBREL) ->
inc('packets/pubrel/received');
received2(?PUBCOMP) ->
inc('packets/pubcomp/received');
received2(?SUBSCRIBE) ->
inc('packets/subscribe');
received2(?UNSUBSCRIBE) ->
inc('packets/unsubscribe');
received2(?PINGREQ) ->
inc('packets/pingreq');
received2(?DISCONNECT) ->
inc('packets/disconnect');
received2(_) ->
ignore.
qos_received(?QOS_0) ->
inc('messages/qos0/received');
qos_received(?QOS_1) ->
inc('messages/qos1/received');
qos_received(?QOS_2) ->
inc('messages/qos2/received').
%% @doc Count packets received. Will not count $SYS PUBLISH.
-spec(sent(mqtt_packet()) -> ignore | non_neg_integer()).
sent(?PUBLISH_PACKET(_Qos, <<"$SYS/", _/binary>>, _, _)) ->
ignore;
sent(Packet) ->
inc('packets/sent'),
sent1(Packet).
sent1(?PUBLISH_PACKET(Qos, _PktId)) ->
inc('packets/publish/sent'),
inc('messages/sent'),
qos_sent(Qos);
sent1(?PACKET(Type)) ->
sent2(Type).
sent2(?CONNACK) ->
inc('packets/connack');
sent2(?PUBACK) ->
inc('packets/puback/sent');
sent2(?PUBREC) ->
inc('packets/pubrec/sent');
sent2(?PUBREL) ->
inc('packets/pubrel/sent');
sent2(?PUBCOMP) ->
inc('packets/pubcomp/sent');
sent2(?SUBACK) ->
inc('packets/suback');
sent2(?UNSUBACK) ->
inc('packets/unsuback');
sent2(?PINGRESP) ->
inc('packets/pingresp');
sent2(_Type) ->
ignore.
qos_sent(?QOS_0) ->
inc('messages/qos0/sent');
qos_sent(?QOS_1) ->
inc('messages/qos1/sent');
qos_sent(?QOS_2) ->
inc('messages/qos2/sent').
%%--------------------------------------------------------------------
%% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
emqx_time:seed(),
% Create metrics table % Create metrics table
_ = ets:new(?TAB, [set, public, named_table, {write_concurrency, true}]), _ = emqx_tables:new(?TAB, [set, public, {write_concurrency, true}]),
% Tick to publish metrics lists:foreach(fun new/1, ?BYTES_METRICS ++ ?PACKET_METRICS ++ ?MESSAGE_METRICS),
{ok, TRef} = timer:send_after(emqx_sys:sys_interval(), tick), {ok, #state{}, hibernate}.
{ok, #state{tick = TRef}, hibernate}.
handle_call(_Req, _From, State) -> handle_call(Req, _From, State) ->
{reply, error, State}. emqx_logger:error("[METRICS] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast(_Msg, State) -> handle_cast(Msg, State) ->
emqx_logger:error("[METRICS] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(tick, State) -> handle_info(Info, State) ->
% publish metric message emqx_logger:error("[METRICS] Unexpected info: ~p", [Info]),
[publish(Metric, Val) || {Metric, Val} <- all()],
{noreply, State, hibernate};
handle_info(_Info, State) ->
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{tick = TRef}) -> terminate(_Reason, #state{}) ->
%%TODO: ok.
timer:cancel(TRef).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
%% TODO: the depencies are not right
publish(Metric, Val) ->
Msg = emqx_message:make(metrics, metric_topic(Metric), bin(Val)),
emqx_broker:publish(emqx_message:set_flag(sys, Msg)).
metric_topic(Metric) ->
emqx_topic:systop(list_to_binary(lists:concat(['metrics/', Metric]))).
bin(I) when is_integer(I) -> list_to_binary(integer_to_list(I)).

View File

@ -1,25 +1,26 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_misc). -module(emqx_misc).
-export([merge_opts/2, start_timer/2, start_timer/3, cancel_timer/1, -export([merge_opts/2, start_timer/2, start_timer/3, cancel_timer/1,
proc_stats/0, proc_stats/1]). proc_name/2, proc_stats/0, proc_stats/1]).
%% @doc Merge Options %% @doc Merge Options
-spec(merge_opts(list(), list()) -> list()).
merge_opts(Defaults, Options) -> merge_opts(Defaults, Options) ->
lists:foldl( lists:foldl(
fun({Opt, Val}, Acc) -> fun({Opt, Val}, Acc) ->
@ -51,6 +52,10 @@ cancel_timer(Timer) ->
_ -> ok _ -> ok
end. end.
-spec(proc_name(atom(), pos_integer()) -> atom()).
proc_name(Mod, Id) ->
list_to_atom(lists:concat([Mod, "_", Id])).
-spec(proc_stats() -> list()). -spec(proc_stats() -> list()).
proc_stats() -> proc_stats() ->
proc_stats(self()). proc_stats(self()).

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_mod_presence). -module(emqx_mod_presence).
@ -34,32 +34,32 @@ on_client_connected(ConnAck, Client = #client{client_id = ClientId,
%%clean_sess = CleanSess, %%clean_sess = CleanSess,
%%proto_ver = ProtoVer %%proto_ver = ProtoVer
}, Env) -> }, Env) ->
case catch emqx_json:encode([{clientid, ClientId}, case emqx_json:safe_encode([{clientid, ClientId},
{username, Username}, {username, Username},
{ipaddress, iolist_to_binary(emqx_net:ntoa(IpAddr))}, {ipaddress, iolist_to_binary(emqx_net:ntoa(IpAddr))},
%%{clean_sess, CleanSess}, %%TODO:: fixme later %%{clean_sess, CleanSess}, %%TODO:: fixme later
%%{protocol, ProtoVer}, %%{protocol, ProtoVer},
{connack, ConnAck}, {connack, ConnAck},
{ts, emqx_time:now_secs()}]) of {ts, emqx_time:now_secs()}]) of
Payload when is_binary(Payload) -> {ok, Payload} ->
Msg = message(qos(Env), topic(connected, ClientId), Payload), Msg = message(qos(Env), topic(connected, ClientId), Payload),
emqx:publish(emqx_message:set_flag(sys, Msg)); emqx:publish(emqx_message:set_flag(sys, Msg));
{'EXIT', Reason} -> {error, Reason} ->
emqx_log:error("[Presence Module] json error: ~p", [Reason]) emqx_logger:error("[Presence Module] Json error: ~p", [Reason])
end, end,
{ok, Client}. {ok, Client}.
on_client_disconnected(Reason, #client{client_id = ClientId, on_client_disconnected(Reason, #client{client_id = ClientId,
username = Username}, Env) -> username = Username}, Env) ->
case catch emqx_json:encode([{clientid, ClientId}, case emqx_json:safe_encode([{clientid, ClientId},
{username, Username}, {username, Username},
{reason, reason(Reason)}, {reason, reason(Reason)},
{ts, emqx_time:now_secs()}]) of {ts, emqx_time:now_secs()}]) of
Payload when is_binary(Payload) -> {ok, Payload} ->
Msg = message(qos(Env), topic(disconnected, ClientId), Payload), Msg = message(qos(Env), topic(disconnected, ClientId), Payload),
emqx:publish(emqx_message:set_flag(sys, Msg)); emqx:publish(emqx_message:set_flag(sys, Msg));
{'EXIT', Reason} -> {error, Reason} ->
emqx_log:error("[Presence Module] json error: ~p", [Reason]) emqx_logger:error("[Presence Module] Json error: ~p", [Reason])
end, ok. end, ok.
unload(_Env) -> unload(_Env) ->
@ -67,13 +67,13 @@ unload(_Env) ->
emqx:unhook('client.disconnected', fun ?MODULE:on_client_disconnected/3). emqx:unhook('client.disconnected', fun ?MODULE:on_client_disconnected/3).
message(Qos, Topic, Payload) -> message(Qos, Topic, Payload) ->
Msg = emqx_message:make(presence, Topic, iolist_to_binary(Payload)), Msg = emqx_message:make(?MODULE, Topic, iolist_to_binary(Payload)),
emqx_message:set_header(qos, Qos, Msg). emqx_message:set_header(qos, Qos, Msg).
topic(connected, ClientId) -> topic(connected, ClientId) ->
emqx_topic:systop(list_to_binary(["clients/", ClientId, "/connected"])); emqx_topic:systop(iolist_to_binary(["clients/", ClientId, "/connected"]));
topic(disconnected, ClientId) -> topic(disconnected, ClientId) ->
emqx_topic:systop(list_to_binary(["clients/", ClientId, "/disconnected"])). emqx_topic:systop(iolist_to_binary(["clients/", ClientId, "/disconnected"])).
qos(Env) -> qos(Env) ->
proplists:get_value(qos, Env, 0). proplists:get_value(qos, Env, 0).

View File

@ -35,11 +35,11 @@ load(Rules0) ->
emqx:hook('message.publish', fun ?MODULE:rewrite_publish/2, [Rules]). emqx:hook('message.publish', fun ?MODULE:rewrite_publish/2, [Rules]).
rewrite_subscribe(_ClientId, _Username, TopicTable, Rules) -> rewrite_subscribe(_ClientId, _Username, TopicTable, Rules) ->
emqx_log:info("Rewrite subscribe: ~p", [TopicTable]), emqx_logger:info("Rewrite subscribe: ~p", [TopicTable]),
{ok, [{match_rule(Topic, Rules), Opts} || {Topic, Opts} <- TopicTable]}. {ok, [{match_rule(Topic, Rules), Opts} || {Topic, Opts} <- TopicTable]}.
rewrite_unsubscribe(_ClientId, _Username, TopicTable, Rules) -> rewrite_unsubscribe(_ClientId, _Username, TopicTable, Rules) ->
emqx_log:info("Rewrite unsubscribe: ~p", [TopicTable]), emqx_logger:info("Rewrite unsubscribe: ~p", [TopicTable]),
{ok, [{match_rule(Topic, Rules), Opts} || {Topic, Opts} <- TopicTable]}. {ok, [{match_rule(Topic, Rules), Opts} || {Topic, Opts} <- TopicTable]}.
rewrite_publish(Message = #message{topic = Topic}, Rules) -> rewrite_publish(Message = #message{topic = Topic}, Rules) ->

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_mod_subscription). -module(emqx_mod_subscription).

View File

@ -1,23 +1,24 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_modules). -module(emqx_modules).
-export([load/0, unload/0]). -export([load/0, unload/0]).
-spec(load() -> ok).
load() -> load() ->
lists:foreach( lists:foreach(
fun({Mod, Env}) -> fun({Mod, Env}) ->
@ -25,6 +26,7 @@ load() ->
io:format("Load ~s module successfully.~n", [Mod]) io:format("Load ~s module successfully.~n", [Mod])
end, emqx_config:get_env(modules, [])). end, emqx_config:get_env(modules, [])).
-spec(unload() -> ok).
unload() -> unload() ->
lists:foreach( lists:foreach(
fun({Mod, Env}) -> fun({Mod, Env}) ->

View File

@ -1,159 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Inc. 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_mqtt_metrics).
-include("emqx_mqtt.hrl").
-import(emqx_metrics, [inc/1]).
-export([init/0]).
%% Received/Sent Metrics
-export([received/1, sent/1]).
%% Bytes sent and received of Broker
-define(SYSTOP_BYTES, [
{counter, 'bytes/received'}, % Total bytes received
{counter, 'bytes/sent'} % Total bytes sent
]).
%% Packets sent and received of Broker
-define(SYSTOP_PACKETS, [
{counter, 'packets/received'}, % All Packets received
{counter, 'packets/sent'}, % All Packets sent
{counter, 'packets/connect'}, % CONNECT Packets received
{counter, 'packets/connack'}, % CONNACK Packets sent
{counter, 'packets/publish/received'}, % PUBLISH packets received
{counter, 'packets/publish/sent'}, % PUBLISH packets sent
{counter, 'packets/puback/received'}, % PUBACK packets received
{counter, 'packets/puback/sent'}, % PUBACK packets sent
{counter, 'packets/puback/missed'}, % PUBACK packets missed
{counter, 'packets/pubrec/received'}, % PUBREC packets received
{counter, 'packets/pubrec/sent'}, % PUBREC packets sent
{counter, 'packets/pubrec/missed'}, % PUBREC packets missed
{counter, 'packets/pubrel/received'}, % PUBREL packets received
{counter, 'packets/pubrel/sent'}, % PUBREL packets sent
{counter, 'packets/pubrel/missed'}, % PUBREL packets missed
{counter, 'packets/pubcomp/received'}, % PUBCOMP packets received
{counter, 'packets/pubcomp/sent'}, % PUBCOMP packets sent
{counter, 'packets/pubcomp/missed'}, % PUBCOMP packets missed
{counter, 'packets/subscribe'}, % SUBSCRIBE Packets received
{counter, 'packets/suback'}, % SUBACK packets sent
{counter, 'packets/unsubscribe'}, % UNSUBSCRIBE Packets received
{counter, 'packets/unsuback'}, % UNSUBACK Packets sent
{counter, 'packets/pingreq'}, % PINGREQ packets received
{counter, 'packets/pingresp'}, % PINGRESP Packets sent
{counter, 'packets/disconnect'} % DISCONNECT Packets received
]).
%% Messages sent and received of broker
-define(SYSTOP_MESSAGES, [
{counter, 'messages/received'}, % All Messages received
{counter, 'messages/sent'}, % All Messages sent
{counter, 'messages/qos0/received'}, % QoS0 Messages received
{counter, 'messages/qos0/sent'}, % QoS0 Messages sent
{counter, 'messages/qos1/received'}, % QoS1 Messages received
{counter, 'messages/qos1/sent'}, % QoS1 Messages sent
{counter, 'messages/qos2/received'}, % QoS2 Messages received
{counter, 'messages/qos2/sent'}, % QoS2 Messages sent
{counter, 'messages/qos2/dropped'}, % QoS2 Messages dropped
{gauge, 'messages/retained'}, % Messagea retained
{counter, 'messages/dropped'}, % Messages dropped
{counter, 'messages/forward'} % Messages forward
]).
% Init metrics
init() ->
lists:foreach(fun emqx_metrics:create/1,
?SYSTOP_BYTES ++ ?SYSTOP_PACKETS ++ ?SYSTOP_MESSAGES).
%% @doc Count packets received.
-spec(received(mqtt_packet()) -> ok).
received(Packet) ->
inc('packets/received'),
received1(Packet).
received1(?PUBLISH_PACKET(Qos, _PktId)) ->
inc('packets/publish/received'),
inc('messages/received'),
qos_received(Qos);
received1(?PACKET(Type)) ->
received2(Type).
received2(?CONNECT) ->
inc('packets/connect');
received2(?PUBACK) ->
inc('packets/puback/received');
received2(?PUBREC) ->
inc('packets/pubrec/received');
received2(?PUBREL) ->
inc('packets/pubrel/received');
received2(?PUBCOMP) ->
inc('packets/pubcomp/received');
received2(?SUBSCRIBE) ->
inc('packets/subscribe');
received2(?UNSUBSCRIBE) ->
inc('packets/unsubscribe');
received2(?PINGREQ) ->
inc('packets/pingreq');
received2(?DISCONNECT) ->
inc('packets/disconnect');
received2(_) ->
ignore.
qos_received(?QOS_0) ->
inc('messages/qos0/received');
qos_received(?QOS_1) ->
inc('messages/qos1/received');
qos_received(?QOS_2) ->
inc('messages/qos2/received').
%% @doc Count packets received. Will not count $SYS PUBLISH.
-spec(sent(mqtt_packet()) -> ignore | non_neg_integer()).
sent(?PUBLISH_PACKET(_Qos, <<"$SYS/", _/binary>>, _, _)) ->
ignore;
sent(Packet) ->
inc('packets/sent'),
sent1(Packet).
sent1(?PUBLISH_PACKET(Qos, _PktId)) ->
inc('packets/publish/sent'),
inc('messages/sent'),
qos_sent(Qos);
sent1(?PACKET(Type)) ->
sent2(Type).
sent2(?CONNACK) ->
inc('packets/connack');
sent2(?PUBACK) ->
inc('packets/puback/sent');
sent2(?PUBREC) ->
inc('packets/pubrec/sent');
sent2(?PUBREL) ->
inc('packets/pubrel/sent');
sent2(?PUBCOMP) ->
inc('packets/pubcomp/sent');
sent2(?SUBACK) ->
inc('packets/suback');
sent2(?UNSUBACK) ->
inc('packets/unsuback');
sent2(?PINGRESP) ->
inc('packets/pingresp');
sent2(_Type) ->
ignore.
qos_sent(?QOS_0) ->
inc('messages/qos0/sent');
qos_sent(?QOS_1) ->
inc('messages/qos1/sent');
qos_sent(?QOS_2) ->
inc('messages/qos2/sent').

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_parser). -module(emqx_parser).

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_plugins). -module(emqx_plugins).
@ -83,7 +83,7 @@ load_expand_plugin(PluginDir) ->
end, Modules), end, Modules),
case filelib:wildcard(Ebin ++ "/*.app") of case filelib:wildcard(Ebin ++ "/*.app") of
[App|_] -> application:load(list_to_atom(filename:basename(App, ".app"))); [App|_] -> application:load(list_to_atom(filename:basename(App, ".app")));
_ -> emqx_log:error("App file cannot be found."), _ -> emqx_logger:error("App file cannot be found."),
{error, load_app_fail} {error, load_app_fail}
end. end.
@ -128,7 +128,7 @@ with_loaded_file(File, SuccFun) ->
{ok, Names} -> {ok, Names} ->
SuccFun(Names); SuccFun(Names);
{error, Error} -> {error, Error} ->
emqx_log:error("[Plugins] Failed to read: ~p, error: ~p", [File, Error]), emqx_logger:error("[Plugins] Failed to read: ~p, error: ~p", [File, Error]),
{error, Error} {error, Error}
end. end.
@ -136,7 +136,7 @@ load_plugins(Names, Persistent) ->
Plugins = list(), NotFound = Names -- names(Plugins), Plugins = list(), NotFound = Names -- names(Plugins),
case NotFound of case NotFound of
[] -> ok; [] -> ok;
NotFound -> emqx_log:error("[Plugins] Cannot find plugins: ~p", [NotFound]) NotFound -> emqx_logger:error("[Plugins] Cannot find plugins: ~p", [NotFound])
end, end,
NeedToLoad = Names -- NotFound -- names(started_app), NeedToLoad = Names -- NotFound -- names(started_app),
[load_plugin(find_plugin(Name, Plugins), Persistent) || Name <- NeedToLoad]. [load_plugin(find_plugin(Name, Plugins), Persistent) || Name <- NeedToLoad].
@ -185,12 +185,12 @@ plugin(CfgFile) ->
load(PluginName) when is_atom(PluginName) -> load(PluginName) when is_atom(PluginName) ->
case lists:member(PluginName, names(started_app)) of case lists:member(PluginName, names(started_app)) of
true -> true ->
emqx_log:error("[Plugins] Plugin ~s is already started", [PluginName]), emqx_logger:error("[Plugins] Plugin ~s is already started", [PluginName]),
{error, already_started}; {error, already_started};
false -> false ->
case find_plugin(PluginName) of case find_plugin(PluginName) of
false -> false ->
emqx_log:error("[Plugins] Plugin ~s not found", [PluginName]), emqx_logger:error("[Plugins] Plugin ~s not found", [PluginName]),
{error, not_found}; {error, not_found};
Plugin -> Plugin ->
load_plugin(Plugin, true) load_plugin(Plugin, true)
@ -218,12 +218,12 @@ load_app(App) ->
start_app(App, SuccFun) -> start_app(App, SuccFun) ->
case application:ensure_all_started(App) of case application:ensure_all_started(App) of
{ok, Started} -> {ok, Started} ->
emqx_log:info("Started Apps: ~p", [Started]), emqx_logger:info("Started Apps: ~p", [Started]),
emqx_log:info("Load plugin ~s successfully", [App]), emqx_logger:info("Load plugin ~s successfully", [App]),
SuccFun(App), SuccFun(App),
{ok, Started}; {ok, Started};
{error, {ErrApp, Reason}} -> {error, {ErrApp, Reason}} ->
emqx_log:error("Load plugin ~s error, cannot start app ~s for ~p", [App, ErrApp, Reason]), emqx_logger:error("Load plugin ~s error, cannot start app ~s for ~p", [App, ErrApp, Reason]),
{error, {ErrApp, Reason}} {error, {ErrApp, Reason}}
end. end.
@ -240,10 +240,10 @@ unload(PluginName) when is_atom(PluginName) ->
{true, true} -> {true, true} ->
unload_plugin(PluginName, true); unload_plugin(PluginName, true);
{false, _} -> {false, _} ->
emqx_log:error("Plugin ~s is not started", [PluginName]), emqx_logger:error("Plugin ~s is not started", [PluginName]),
{error, not_started}; {error, not_started};
{true, false} -> {true, false} ->
emqx_log:error("~s is not a plugin, cannot unload it", [PluginName]), emqx_logger:error("~s is not a plugin, cannot unload it", [PluginName]),
{error, not_found} {error, not_found}
end. end.
@ -258,11 +258,11 @@ unload_plugin(App, Persistent) ->
stop_app(App) -> stop_app(App) ->
case application:stop(App) of case application:stop(App) of
ok -> ok ->
emqx_log:info("Stop plugin ~s successfully", [App]), ok; emqx_logger:info("Stop plugin ~s successfully", [App]), ok;
{error, {not_started, App}} -> {error, {not_started, App}} ->
emqx_log:error("Plugin ~s is not started", [App]), ok; emqx_logger:error("Plugin ~s is not started", [App]), ok;
{error, Reason} -> {error, Reason} ->
emqx_log:error("Stop plugin ~s error: ~p", [App]), {error, Reason} emqx_logger:error("Stop plugin ~s error: ~p", [App]), {error, Reason}
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -294,7 +294,7 @@ plugin_loaded(Name, true) ->
ignore ignore
end; end;
{error, Error} -> {error, Error} ->
emqx_log:error("Cannot read loaded plugins: ~p", [Error]) emqx_logger:error("Cannot read loaded plugins: ~p", [Error])
end. end.
plugin_unloaded(_Name, false) -> plugin_unloaded(_Name, false) ->
@ -306,10 +306,10 @@ plugin_unloaded(Name, true) ->
true -> true ->
write_loaded(lists:delete(Name, Names)); write_loaded(lists:delete(Name, Names));
false -> false ->
emqx_log:error("Cannot find ~s in loaded_file", [Name]) emqx_logger:error("Cannot find ~s in loaded_file", [Name])
end; end;
{error, Error} -> {error, Error} ->
emqx_log:error("Cannot read loaded_plugins: ~p", [Error]) emqx_logger:error("Cannot read loaded_plugins: ~p", [Error])
end. end.
read_loaded() -> read_loaded() ->
@ -328,7 +328,7 @@ write_loaded(AppNames) ->
file:write(Fd, iolist_to_binary(io_lib:format("~s.~n", [Name]))) file:write(Fd, iolist_to_binary(io_lib:format("~s.~n", [Name])))
end, AppNames); end, AppNames);
{error, Error} -> {error, Error} ->
emqx_log:error("Open File ~p Error: ~p", [File, Error]), emqx_logger:error("Open File ~p Error: ~p", [File, Error]),
{error, Error} {error, Error}
end. end.

View File

@ -1,22 +1,24 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_pmon). -module(emqx_pmon).
-export([new/0, monitor/2, monitor/3, demonitor/2, find/2, erase/2]). -export([new/0]).
-export([monitor/2, monitor/3, demonitor/2, find/2, erase/2]).
-compile({no_auto_import,[monitor/3]}). -compile({no_auto_import,[monitor/3]}).
@ -43,7 +45,8 @@ monitor(Pid, Val, PM = {?MODULE, [M]}) ->
demonitor(Pid, PM = {?MODULE, [M]}) -> demonitor(Pid, PM = {?MODULE, [M]}) ->
case maps:find(Pid, M) of case maps:find(Pid, M) of
{ok, {Ref, _Val}} -> {ok, {Ref, _Val}} ->
erlang:demonitor(Ref, [flush]), %% Don't flush
_ = erlang:demonitor(Ref),
{?MODULE, [maps:remove(Pid, M)]}; {?MODULE, [maps:remove(Pid, M)]};
error -> error ->
PM PM

106
src/emqx_pool.erl Normal file
View File

@ -0,0 +1,106 @@
%%%===================================================================
%%% Copyright (c) 2013-2018 EMQ Inc. 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_pool).
-behaviour(gen_server).
%% Start the pool supervisor
-export([start_link/0]).
%% API Exports
-export([start_link/2, submit/1, async_submit/1]).
%% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {pool, id}).
-define(POOL, ?MODULE).
%% @doc Start Pooler Supervisor.
start_link() ->
emqx_pool_sup:start_link(?POOL, random, {?MODULE, start_link, []}).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
-spec(start_link(atom(), pos_integer())
-> {ok, pid()} | ignore | {error, term()}).
start_link(Pool, Id) ->
gen_server:start_link({local, emqx_misc:proc_name(?MODULE, Id)},
?MODULE, [Pool, Id], []).
%% @doc Submit work to the pool
-spec(submit(fun()) -> any()).
submit(Fun) ->
gen_server:call(worker(), {submit, Fun}, infinity).
%% @doc Submit work to the pool asynchronously
-spec(async_submit(fun()) -> ok).
async_submit(Fun) ->
gen_server:cast(worker(), {async_submit, Fun}).
worker() ->
gproc_pool:pick_worker(pool).
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
init([Pool, Id]) ->
gproc_pool:connect_worker(Pool, {Pool, Id}),
{ok, #state{pool = Pool, id = Id}}.
handle_call({submit, Fun}, _From, State) ->
{reply, catch run(Fun), State};
handle_call(Req, _From, State) ->
emqx_logger:error("[POOL] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast({async_submit, Fun}, State) ->
try run(Fun)
catch _:Error ->
emqx_logger:error("[POOL] Error: ~p, ~p", [Error, erlang:get_stacktrace()])
end,
{noreply, State};
handle_cast(Msg, State) ->
emqx_logger:error("[POOL] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info(Info, State) ->
emqx_logger:error("[POOL] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{pool = Pool, id = Id}) ->
gproc_pool:disconnect_worker(Pool, {Pool, Id}).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
run({M, F, A}) ->
erlang:apply(M, F, A);
run(Fun) when is_function(Fun) ->
Fun().

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_pool_sup). -module(emqx_pool_sup).
@ -39,12 +39,11 @@ start_link(Pool, Type, MFA) ->
start_link(Pool, Type, Schedulers, MFA). start_link(Pool, Type, Schedulers, MFA).
-spec(start_link(atom() | tuple(), atom(), pos_integer(), mfa()) -> {ok, pid()} | {error, term()}). -spec(start_link(atom() | tuple(), atom(), pos_integer(), mfa()) -> {ok, pid()} | {error, term()}).
start_link(Pool, Type, Size, MFA) when is_atom(Pool) ->
supervisor:start_link({local, Pool}, ?MODULE, [Pool, Type, Size, MFA]);
start_link(Pool, Type, Size, MFA) -> start_link(Pool, Type, Size, MFA) ->
supervisor:start_link(?MODULE, [Pool, Type, Size, MFA]). supervisor:start_link(?MODULE, [Pool, Type, Size, MFA]).
%% sup_name(Pool) when is_atom(Pool) ->
%% list_to_atom(atom_to_list(Pool) ++ "_pool_sup").
init([Pool, Type, Size, {M, F, Args}]) -> init([Pool, Type, Size, {M, F, Args}]) ->
ensure_pool(Pool, Type, [{size, Size}]), ensure_pool(Pool, Type, [{size, Size}]),
{ok, {{one_for_one, 10, 3600}, [ {ok, {{one_for_one, 10, 3600}, [

View File

@ -1,98 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Inc. 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_pooler).
-behaviour(gen_server).
%% Start the pool supervisor
-export([start_link/0]).
%% API Exports
-export([start_link/2, submit/1, async_submit/1]).
%% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {pool, id}).
%% @doc Start Pooler Supervisor.
start_link() ->
emqx_pool_sup:start_link(pooler, random, {?MODULE, start_link, []}).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
-spec(start_link(atom(), pos_integer()) -> {ok, pid()} | ignore | {error, term()}).
start_link(Pool, Id) ->
gen_server:start_link({local, name(Id)}, ?MODULE, [Pool, Id], []).
name(Id) -> list_to_atom(lists:concat([?MODULE, "_", Id])).
%% @doc Submit work to pooler
submit(Fun) -> gen_server:call(worker(), {submit, Fun}, infinity).
%% @doc Submit work to pooler asynchronously
async_submit(Fun) ->
gen_server:cast(worker(), {async_submit, Fun}).
worker() ->
gproc_pool:pick_worker(pooler).
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
init([Pool, Id]) ->
gproc_pool:connect_worker(Pool, {Pool, Id}),
{ok, #state{pool = Pool, id = Id}}.
handle_call({submit, Fun}, _From, State) ->
{reply, catch run(Fun), State};
handle_call(_Req, _From, State) ->
{reply, ok, State}.
handle_cast({async_submit, Fun}, State) ->
try run(Fun)
catch _:Error ->
emqx_log:error("Pooler Error: ~p, ~p", [Error, erlang:get_stacktrace()])
end,
{noreply, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, #state{pool = Pool, id = Id}) ->
gproc_pool:disconnect_worker(Pool, {Pool, Id}).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
run({M, F, A}) ->
erlang:apply(M, F, A);
run(Fun) when is_function(Fun) ->
Fun().

View File

@ -57,7 +57,7 @@
-define(STATS_KEYS, [recv_pkt, recv_msg, send_pkt, send_msg]). -define(STATS_KEYS, [recv_pkt, recv_msg, send_pkt, send_msg]).
-define(LOG(Level, Format, Args, State), -define(LOG(Level, Format, Args, State),
emqx_log:Level([{client, State#proto_state.client_id}], "Client(~s@~s): " ++ Format, emqx_logger:Level([{client, State#proto_state.client_id}], "Client(~s@~s): " ++ Format,
[State#proto_state.client_id, esockd_net:format(State#proto_state.peername) | Args])). [State#proto_state.client_id, esockd_net:format(State#proto_state.peername) | Args])).
%% @doc Init protocol %% @doc Init protocol
@ -220,7 +220,7 @@ process(?CONNECT_PACKET(Var), State0) ->
{ok, Session} -> %% TODO:... {ok, Session} -> %% TODO:...
SP = true, %% TODO:... SP = true, %% TODO:...
%% TODO: Register the client %% TODO: Register the client
emqx_cm:reg(clientid(State2)), emqx_cm:register_client(clientid(State2)),
%%emqx_cm:reg(client(State2)), %%emqx_cm:reg(client(State2)),
%% Start keepalive %% Start keepalive
start_keepalive(KeepAlive, State2), start_keepalive(KeepAlive, State2),
@ -362,7 +362,7 @@ send(Msg, State = #proto_state{client_id = ClientId,
send(Packet = ?PACKET(Type), State = #proto_state{sendfun = SendFun, stats_data = Stats}) -> send(Packet = ?PACKET(Type), State = #proto_state{sendfun = SendFun, stats_data = Stats}) ->
trace(send, Packet, State), trace(send, Packet, State),
emqx_mqtt_metrics:sent(Packet), emqx_metrics:sent(Packet),
SendFun(Packet), SendFun(Packet),
{ok, State#proto_state{stats_data = inc_stats(send, Type, Stats)}}. {ok, State#proto_state{stats_data = inc_stats(send, Type, Stats)}}.
@ -398,15 +398,14 @@ stop_if_auth_failure(_RC, State) ->
shutdown(_Error, #proto_state{client_id = undefined}) -> shutdown(_Error, #proto_state{client_id = undefined}) ->
ignore; ignore;
shutdown(conflict, _State) -> shutdown(conflict, _State = #proto_state{client_id = ClientId}) ->
%% let it down emqx_cm:unregister_client(ClientId),
ignore; ignore;
shutdown(mnesia_conflict, _State) -> shutdown(mnesia_conflict, _State = #proto_state{client_id = ClientId}) ->
%% let it down emqx_cm:unregister_client(ClientId),
%% emqx_cm:unreg(ClientId);
ignore; ignore;
shutdown(Error, State = #proto_state{client_id = ClientId,
shutdown(Error, State = #proto_state{will_msg = WillMsg}) -> will_msg = WillMsg}) ->
?LOG(info, "Shutdown for ~p", [Error], State), ?LOG(info, "Shutdown for ~p", [Error], State),
Client = client(State), Client = client(State),
%% Auth failure not publish the will message %% Auth failure not publish the will message
@ -415,11 +414,11 @@ shutdown(Error, State = #proto_state{will_msg = WillMsg}) ->
false -> send_willmsg(Client, WillMsg) false -> send_willmsg(Client, WillMsg)
end, end,
emqx_hooks:run('client.disconnected', [Error], Client), emqx_hooks:run('client.disconnected', [Error], Client),
%% let it down emqx_cm:unregister_client(ClientId),
%% emqx_cm:unreg(ClientId).
ok. ok.
willmsg(Packet, State = #proto_state{mountpoint = MountPoint}) when is_record(Packet, mqtt_packet_connect) -> willmsg(Packet, State = #proto_state{mountpoint = MountPoint})
when is_record(Packet, mqtt_packet_connect) ->
case emqx_packet:to_message(Packet) of case emqx_packet:to_message(Packet) of
undefined -> undefined; undefined -> undefined;
Msg -> mount(replvar(MountPoint, State), Msg) Msg -> mount(replvar(MountPoint, State), Msg)

67
src/emqx_rate_limiter.erl Normal file
View File

@ -0,0 +1,67 @@
%%%-------------------------------------------------------------------
%%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io)
%%%
%%% 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_rate_limiter).
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {}).
%%%===================================================================
%%% API
%%%===================================================================
%% @doc Starts the server
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -1,59 +1,53 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_router). -module(emqx_router).
-behaviour(gen_server). -behaviour(gen_server).
-include("emqx.hrl"). -include("emqx.hrl").
-include_lib("ekka/include/ekka.hrl"). -include_lib("ekka/include/ekka.hrl").
%% Mnesia Bootstrap %% Mnesia bootstrap
-export([mnesia/1]). -export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}). -copy_mnesia({mnesia, [copy]}).
%% Start
-export([start_link/2]). -export([start_link/2]).
%% Route APIs
-export([add_route/2, add_route/3, get_routes/1, del_route/2, del_route/3]).
-export([has_routes/1, match_routes/1, print_routes/1]).
%% Topics %% Topics
-export([topics/0]). -export([topics/0]).
%% Route management APIs
-export([add_route/2, add_route/3, get_routes/1, del_route/2, del_route/3]).
-export([has_routes/1, match_routes/1, print_routes/1]).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-type(group() :: binary()). -type(destination() :: node() | {binary(), node()}).
-type(destination() :: node() | {group(), node()}
| {cluster(), group(), node()}).
-record(state, {pool, id}). -record(state, {pool, id}).
-define(ROUTE, emqx_route). -define(ROUTE, emqx_route).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Mnesia Bootstrap %% Mnesia bootstrap
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
mnesia(boot) -> mnesia(boot) ->
@ -67,14 +61,17 @@ mnesia(copy) ->
ok = ekka_mnesia:copy_table(?ROUTE). ok = ekka_mnesia:copy_table(?ROUTE).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Start a router %% Strat a Router
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(start_link(atom(), pos_integer())
-> {ok, pid()} | ignore | {error, term()}).
start_link(Pool, Id) -> start_link(Pool, Id) ->
gen_server:start_link(?MODULE, [Pool, Id], [{hibernate_after, 10000}]). gen_server:start_link(emqx_misc:proc_name(?MODULE, Id),
?MODULE, [Pool, Id], [{hibernate_after, 10000}]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Add/Del Routes %% Route APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Add a route %% @doc Add a route
@ -105,40 +102,24 @@ del_route(From, Topic, Dest) when is_binary(Topic) ->
has_routes(Topic) when is_binary(Topic) -> has_routes(Topic) when is_binary(Topic) ->
ets:member(?ROUTE, Topic). ets:member(?ROUTE, Topic).
%%-------------------------------------------------------------------- %% @doc Get topics
%% Topics -spec(topics() -> list(topic())).
%%--------------------------------------------------------------------
-spec(topics() -> list(binary())).
topics() -> mnesia:dirty_all_keys(?ROUTE). topics() -> mnesia:dirty_all_keys(?ROUTE).
%%--------------------------------------------------------------------
%% Match routes
%%--------------------------------------------------------------------
%% @doc Match routes %% @doc Match routes
%% Optimize: routing table will be replicated to all router nodes. %% Optimize: routing table will be replicated to all router nodes.
-spec(match_routes(Topic:: topic()) -> [{topic(), binary() | node()}]). -spec(match_routes(topic()) -> [route()]).
match_routes(Topic) when is_binary(Topic) -> match_routes(Topic) when is_binary(Topic) ->
Matched = mnesia:ets(fun emqx_trie:match/1, [Topic]), Matched = mnesia:ets(fun emqx_trie:match/1, [Topic]),
Routes = [ets:lookup(?ROUTE, To) || To <- [Topic | Matched]], lists:append([get_routes(To) || To <- [Topic | Matched]]).
[{To, Dest} || #route{topic = To, dest = Dest} <- lists:append(Routes)].
%%--------------------------------------------------------------------
%% Print routes
%%--------------------------------------------------------------------
%% @doc Print routes to a topic %% @doc Print routes to a topic
-spec(print_routes(topic()) -> ok). -spec(print_routes(topic()) -> ok).
print_routes(Topic) -> print_routes(Topic) ->
lists:foreach(fun({To, Dest}) -> lists:foreach(fun(#route{topic = To, dest = Dest}) ->
io:format("~s -> ~s~n", [To, Dest]) io:format("~s -> ~s~n", [To, Dest])
end, match_routes(Topic)). end, match_routes(Topic)).
%%--------------------------------------------------------------------
%% Utility functions
%%--------------------------------------------------------------------
cast(Router, Msg) -> cast(Router, Msg) ->
gen_server:cast(Router, Msg). gen_server:cast(Router, Msg).
@ -154,7 +135,7 @@ init([Pool, Id]) ->
{ok, #state{pool = Pool, id = Id}}. {ok, #state{pool = Pool, id = Id}}.
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[Router] Unexpected request: ~p", [Req]), emqx_logger:error("[Router] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast({add_route, From, Route}, State) -> handle_cast({add_route, From, Route}, State) ->
@ -163,12 +144,12 @@ handle_cast({add_route, From, Route}, State) ->
{noreply, State}; {noreply, State};
handle_cast({add_route, Route = #route{topic = Topic, dest = Dest}}, State) -> handle_cast({add_route, Route = #route{topic = Topic, dest = Dest}}, State) ->
case lists:member(Route, ets:lookup(?ROUTE, Topic)) of case lists:member(Route, get_routes(Topic)) of
true -> ok; true -> ok;
false -> false ->
ok = emqx_router_helper:monitor(Dest), ok = emqx_router_helper:monitor(Dest),
case emqx_topic:wildcard(Topic) of case emqx_topic:wildcard(Topic) of
true -> trans(fun add_trie_route/1, [Route]); true -> log(trans(fun add_trie_route/1, [Route]));
false -> add_direct_route(Route) false -> add_direct_route(Route)
end end
end, end,
@ -185,18 +166,18 @@ handle_cast({del_route, Route = #route{topic = Topic}}, State) ->
true -> ok; true -> ok;
false -> false ->
case emqx_topic:wildcard(Topic) of case emqx_topic:wildcard(Topic) of
true -> trans(fun del_trie_route/1, [Route]); true -> log(trans(fun del_trie_route/1, [Route]));
false -> del_direct_route(Route) false -> del_direct_route(Route)
end end
end, end,
{noreply, State}; {noreply, State};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[Router] Unexpected msg: ~p", [Msg]), emqx_logger:error("[Router] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[Router] Unexpected info: ~p", [Info]), emqx_logger:error("[Router] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{pool = Pool, id = Id}) -> terminate(_Reason, #state{pool = Pool, id = Id}) ->
@ -237,8 +218,10 @@ del_trie_route(Route = #route{topic = Topic}) ->
trans(Fun, Args) -> trans(Fun, Args) ->
case mnesia:transaction(Fun, Args) of case mnesia:transaction(Fun, Args) of
{atomic, _} -> ok; {atomic, _} -> ok;
{aborted, Error} -> {aborted, Error} -> {error, Error}
emqx_log:error("[Router] Mnesia aborted: ~p", [Error]),
{error, Error}
end. end.
log(ok) -> ok;
log({error, Error}) ->
emqx_logger:error("[Router] Mnesia aborted: ~p", [Error]).

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_router_helper). -module(emqx_router_helper).
@ -20,72 +20,73 @@
-include("emqx.hrl"). -include("emqx.hrl").
%% Mnesia Bootstrap %% Mnesia bootstrap
-export([mnesia/1]). -export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}). -copy_mnesia({mnesia, [copy]}).
%% API %% API
-export([start_link/1, monitor/1]). -export([start_link/0, monitor/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-record(routing_node, {name, ts}). -record(routing_node, {name, const = unused}).
-record(state, {nodes = [], stats_fun, stats_timer}). -record(state, {nodes = []}).
-compile({no_auto_import, [monitor/1]}). -compile({no_auto_import, [monitor/1]}).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
-define(TAB, emqx_routing_node). -define(ROUTE, emqx_route).
-define(ROUTING_NODE, emqx_routing_node).
-define(LOCK, {?MODULE, clean_routes}). -define(LOCK, {?MODULE, cleanup_routes}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Mnesia bootstrap %% Mnesia bootstrap
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
mnesia(boot) -> mnesia(boot) ->
ok = ekka_mnesia:create_table(?TAB, [ ok = ekka_mnesia:create_table(?ROUTING_NODE, [
{type, set}, {type, set},
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, routing_node}, {record_name, routing_node},
{attributes, record_info(fields, routing_node)}]); {attributes, record_info(fields, routing_node)}]);
mnesia(copy) -> mnesia(copy) ->
ok = ekka_mnesia:copy_table(?TAB). ok = ekka_mnesia:copy_table(?ROUTING_NODE).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Starts the router helper %% @doc Starts the router helper
-spec(start_link(fun()) -> {ok, pid()} | ignore | {error, any()}). -spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link(StatsFun) -> start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [StatsFun], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% @doc Monitor routing node %% @doc Monitor routing node
-spec(monitor(node()) -> ok). -spec(monitor(node() | {binary(), node()}) -> ok).
monitor({_Group, Node}) -> monitor({_Group, Node}) ->
monitor(Node); monitor(Node);
monitor(Node) when is_atom(Node) -> monitor(Node) when is_atom(Node) ->
case ekka:is_member(Node) orelse ets:member(?TAB, Node) of case ekka:is_member(Node)
orelse ets:member(?ROUTING_NODE, Node) of
true -> ok; true -> ok;
false -> false -> mnesia:dirty_write(?ROUTING_NODE, #routing_node{name = Node})
mnesia:dirty_write(?TAB, #routing_node{name = Node, ts = os:timestamp()})
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([StatsFun]) -> init([]) ->
ekka:monitor(membership), ekka:monitor(membership),
mnesia:subscribe({table, ?TAB, simple}), mnesia:subscribe({table, ?ROUTING_NODE, simple}),
Nodes = lists:foldl( Nodes = lists:foldl(
fun(Node, Acc) -> fun(Node, Acc) ->
case ekka:is_member(Node) of case ekka:is_member(Node) of
@ -93,21 +94,21 @@ init([StatsFun]) ->
false -> _ = erlang:monitor_node(Node, true), false -> _ = erlang:monitor_node(Node, true),
[Node | Acc] [Node | Acc]
end end
end, [], mnesia:dirty_all_keys(?TAB)), end, [], mnesia:dirty_all_keys(?ROUTING_NODE)),
{ok, TRef} = timer:send_interval(timer:seconds(1), stats), emqx_stats:update_interval(route_stats, stats_fun()),
{ok, #state{nodes = Nodes, stats_fun = StatsFun, stats_timer = TRef}}. {ok, #state{nodes = Nodes}}.
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[RouterHelper] Unexpected request: ~p", [Req]), emqx_logger:error("[RouterHelper] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[RouterHelper] Unexpected msg: ~p", [Msg]), emqx_logger:error("[RouterHelper] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({mnesia_table_event, {write, #routing_node{name = Node}, _}}, handle_info({mnesia_table_event, {write, #routing_node{name = Node}, _}},
State = #state{nodes = Nodes}) -> State = #state{nodes = Nodes}) ->
emqx_log:info("[RouterHelper] New routing node: ~s", [Node]), emqx_logger:info("[RouterHelper] New routing node: ~s", [Node]),
case ekka:is_member(Node) orelse lists:member(Node, Nodes) of case ekka:is_member(Node) orelse lists:member(Node, Nodes) of
true -> {noreply, State}; true -> {noreply, State};
false -> _ = erlang:monitor_node(Node, true), false -> _ = erlang:monitor_node(Node, true),
@ -122,8 +123,8 @@ handle_info({nodedown, Node}, State = #state{nodes = Nodes}) ->
fun() -> fun() ->
mnesia:transaction(fun cleanup_routes/1, [Node]) mnesia:transaction(fun cleanup_routes/1, [Node])
end), end),
mnesia:dirty_delete(?TAB, Node), mnesia:dirty_delete(?ROUTING_NODE, Node),
handle_info(stats, State#state{nodes = lists:delete(Node, Nodes)}); {noreply, State#state{nodes = lists:delete(Node, Nodes)}};
handle_info({membership, {mnesia, down, Node}}, State) -> handle_info({membership, {mnesia, down, Node}}, State) ->
handle_info({nodedown, Node}, State); handle_info({nodedown, Node}, State);
@ -131,18 +132,14 @@ handle_info({membership, {mnesia, down, Node}}, State) ->
handle_info({membership, _Event}, State) -> handle_info({membership, _Event}, State) ->
{noreply, State}; {noreply, State};
handle_info(stats, State = #state{stats_fun = StatsFun}) ->
ok = StatsFun(mnesia:table_info(emqx_route, size)),
{noreply, State, hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[RouteHelper] Unexpected info: ~p", [Info]), emqx_logger:error("[RouteHelper] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{stats_timer = TRef}) -> terminate(_Reason, #state{}) ->
timer:cancel(TRef),
ekka:unmonitor(membership), ekka:unmonitor(membership),
mnesia:unsubscribe({table, ?TAB, simple}). emqx_stats:cancel_update(route_stats),
mnesia:unsubscribe({table, ?ROUTING_NODE, simple}).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -151,9 +148,19 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
stats_fun() ->
fun() ->
case ets:info(?ROUTE, size) of
undefined -> ok;
Size ->
emqx_stats:setstat('routes/count', 'routes/max', Size),
emqx_stats:setstat('topics/count', 'topics/max', Size)
end
end.
cleanup_routes(Node) -> cleanup_routes(Node) ->
Patterns = [#route{_ = '_', dest = Node}, Patterns = [#route{_ = '_', dest = Node},
#route{_ = '_', dest = {'_', Node}}], #route{_ = '_', dest = {'_', Node}}],
[mnesia:delete_object(?TAB, R, write) [mnesia:delete_object(?ROUTE, Route, write)
|| Pat <- Patterns, R <- mnesia:match_object(?TAB, Pat, write)]. || Pat <- Patterns, Route <- mnesia:match_object(?ROUTE, Pat, write)].

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_router_sup). -module(emqx_router_sup).
@ -26,16 +26,14 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) -> init([]) ->
StatsFun = emqx_stats:statsfun('routes/count', 'routes/max'),
%% Router helper %% Router helper
Helper = {router_helper, {emqx_router_helper, start_link, [StatsFun]}, Helper = {router_helper, {emqx_router_helper, start_link, []},
permanent, 5000, worker, [emqx_router_helper]}, permanent, 5000, worker, [emqx_router_helper]},
%% Router pool %% Router pool
PoolSup = emqx_pool_sup:spec(router_pool, RouterPool = emqx_pool_sup:spec(emqx_router_pool,
[router, hash, emqx_sys:schedulers(), [router, hash, emqx_vm:schedulers(),
{emqx_router, start_link, []}]), {emqx_router, start_link, []}]),
{ok, {{one_for_all, 0, 3600}, [Helper, PoolSup]}}. {ok, {{one_for_all, 0, 1}, [Helper, RouterPool]}}.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_rpc). -module(emqx_rpc).
@ -20,12 +20,14 @@
-export([multicall/4]). -export([multicall/4]).
-define(RPC, rpc).
call(Node, Mod, Fun, Args) -> call(Node, Mod, Fun, Args) ->
rpc:call(Node, Mod, Fun, Args). ?RPC:call(Node, Mod, Fun, Args).
multicall(Nodes, Mod, Fun, Args) -> multicall(Nodes, Mod, Fun, Args) ->
rpc:multicall(Nodes, Mod, Fun, Args). ?RPC:multicall(Nodes, Mod, Fun, Args).
cast(Node, Mod, Fun, Args) -> cast(Node, Mod, Fun, Args) ->
rpc:cast(Node, Mod, Fun, Args). ?RPC:cast(Node, Mod, Fun, Args).

View File

@ -155,7 +155,7 @@
created_at]). created_at]).
-define(LOG(Level, Format, Args, State), -define(LOG(Level, Format, Args, State),
emqx_log:Level([{client, State#state.client_id}], emqx_logger:Level([{client, State#state.client_id}],
"Session(~s): " ++ Format, [State#state.client_id | Args])). "Session(~s): " ++ Format, [State#state.client_id | Args])).
%% @doc Start a Session %% @doc Start a Session
@ -296,7 +296,7 @@ init(#{clean_start := CleanStart,
enable_stats = EnableStats, enable_stats = EnableStats,
ignore_loop_deliver = IgnoreLoopDeliver, ignore_loop_deliver = IgnoreLoopDeliver,
created_at = os:timestamp()}, created_at = os:timestamp()},
emqx_sm:register_session(#session{sid = ClientId, pid = self()}, info(State)), emqx_sm:register_session(ClientId, info(State)),
emqx_hooks:run('session.created', [ClientId, Username]), emqx_hooks:run('session.created', [ClientId, Username]),
io:format("Session started: ~p~n", [self()]), io:format("Session started: ~p~n", [self()]),
{ok, emit_stats(State), hibernate}. {ok, emit_stats(State), hibernate}.
@ -342,7 +342,7 @@ handle_call(state, _From, State) ->
reply(?record_to_proplist(state, State, ?STATE_KEYS), State); reply(?record_to_proplist(state, State, ?STATE_KEYS), State);
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[Session] Unexpected request: ~p", [Req]), emqx_logger:error("[Session] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast({subscribe, From, TopicTable, AckFun}, handle_cast({subscribe, From, TopicTable, AckFun},
@ -501,7 +501,7 @@ handle_cast({resume, ClientPid},
{noreply, emit_stats(dequeue(retry_delivery(true, State1)))}; {noreply, emit_stats(dequeue(retry_delivery(true, State1)))};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[Session] Unexpected msg: ~p", [Msg]), emqx_logger:error("[Session] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
%% Ignore Messages delivered by self %% Ignore Messages delivered by self
@ -551,13 +551,13 @@ handle_info({'EXIT', Pid, Reason}, State = #state{client_pid = ClientPid}) ->
{noreply, State, hibernate}; {noreply, State, hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[Session] Unexpected info: ~p", [Info]), emqx_logger:error("[Session] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(Reason, #state{client_id = ClientId, username = Username}) -> terminate(Reason, #state{client_id = ClientId, username = Username}) ->
emqx_hooks:run('session.terminated', [ClientId, Username, Reason]), emqx_hooks:run('session.terminated', [ClientId, Username, Reason]),
emqx_sm:unregister_session(#session{sid = ClientId, pid = self()}). emqx_sm:unregister_session(ClientId).
code_change(_OldVsn, Session, _Extra) -> code_change(_OldVsn, Session, _Extra) ->
{ok, Session}. {ok, Session}.
@ -812,8 +812,7 @@ next_msg_id(State = #state{next_msg_id = Id}) ->
emit_stats(State = #state{enable_stats = false}) -> emit_stats(State = #state{enable_stats = false}) ->
State; State;
emit_stats(State = #state{client_id = ClientId}) -> emit_stats(State = #state{client_id = ClientId}) ->
Session = #session{sid = ClientId, pid = self()}, emqx_sm:set_session_stats(ClientId, stats(State)),
emqx_sm_stats:set_session_stats(Session, stats(State)),
State. State.
inc_stats(Key) -> put(Key, get(Key) + 1). inc_stats(Key) -> put(Key, get(Key) + 1).

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_session_sup). -module(emqx_session_sup).
@ -24,13 +24,12 @@
-export([init/1]). -export([init/1]).
-spec(start_link() -> {ok, pid()}).
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec(start_session(session()) -> {ok, pid()}). -spec(start_session(map()) -> {ok, pid()}).
start_session(Session) -> start_session(Attrs) ->
supervisor:start_child(?MODULE, [Session]). supervisor:start_child(?MODULE, [Attrs]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Supervisor callbacks %% Supervisor callbacks

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_shared_sub). -module(emqx_shared_sub).
@ -20,13 +20,16 @@
-include("emqx.hrl"). -include("emqx.hrl").
%% API %% Mnesia bootstrap
-export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}).
-export([start_link/0]). -export([start_link/0]).
-export([strategy/0]). -export([strategy/0]).
-export([subscribe/3, unsubscribe/3]). -export([subscribe/3, unsubscribe/3]).
-export([dispatch/3]). -export([dispatch/3]).
%% gen_server callbacks %% gen_server callbacks
@ -41,18 +44,26 @@
-record(shared_subscription, {group, topic, subpid}). -record(shared_subscription, {group, topic, subpid}).
%%--------------------------------------------------------------------
%% Mnesia bootstrap
%%--------------------------------------------------------------------
mnesia(boot) ->
ok = ekka_mnesia:create_table(?TAB, [
{type, bag},
{ram_copies, [node()]},
{record_name, shared_subscription},
{attributes, record_info(fields, shared_subscription)}]);
mnesia(copy) ->
ok = ekka_mnesia:copy_table(?TAB).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}). -spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link() -> start_link() ->
ok = ekka_mnesia:create_table(?TAB, [
{type, bag},
{ram_copies, [node()]},
{record_name, shared_subscription},
{attributes, record_info(fields, shared_subscription)}]),
ok = ekka_mnesia:copy_table(?TAB),
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec(strategy() -> random | hash). -spec(strategy() -> random | hash).
@ -62,24 +73,17 @@ strategy() ->
subscribe(undefined, _Topic, _SubPid) -> subscribe(undefined, _Topic, _SubPid) ->
ok; ok;
subscribe(Group, Topic, SubPid) when is_pid(SubPid) -> subscribe(Group, Topic, SubPid) when is_pid(SubPid) ->
mnesia:dirty_write(?TAB, r(Group, Topic, SubPid)), mnesia:dirty_write(?TAB, record(Group, Topic, SubPid)),
gen_server:cast(?SERVER, {monitor, SubPid}). gen_server:cast(?SERVER, {monitor, SubPid}).
unsubscribe(undefined, _Topic, _SubPid) -> unsubscribe(undefined, _Topic, _SubPid) ->
ok; ok;
unsubscribe(Group, Topic, SubPid) when is_pid(SubPid) -> unsubscribe(Group, Topic, SubPid) when is_pid(SubPid) ->
mnesia:dirty_delete_object(?TAB, r(Group, Topic, SubPid)). mnesia:dirty_delete_object(?TAB, record(Group, Topic, SubPid)).
r(Group, Topic, SubPid) -> record(Group, Topic, SubPid) ->
#shared_subscription{group = Group, topic = Topic, subpid = SubPid}. #shared_subscription{group = Group, topic = Topic, subpid = SubPid}.
dispatch({Cluster, Group}, Topic, Delivery) ->
case ekka:cluster_name() of
Cluster ->
dispatch(Group, Topic, Delivery);
_ -> Delivery
end;
%% TODO: ensure the delivery... %% TODO: ensure the delivery...
dispatch(Group, Topic, Delivery = #delivery{message = Msg, flows = Flows}) -> dispatch(Group, Topic, Delivery = #delivery{message = Msg, flows = Flows}) ->
case pick(subscribers(Group, Topic)) of case pick(subscribers(Group, Topic)) of
@ -107,7 +111,7 @@ subscribers(Group, Topic) ->
init([]) -> init([]) ->
{atomic, PMon} = mnesia:transaction(fun init_monitors/0), {atomic, PMon} = mnesia:transaction(fun init_monitors/0),
mnesia:subscribe({table, ?TAB, simple}), mnesia:subscribe({table, ?TAB, simple}),
{ok, #state{pmon = PMon}}. {ok, update_stats(#state{pmon = PMon})}.
init_monitors() -> init_monitors() ->
mnesia:foldl( mnesia:foldl(
@ -116,36 +120,34 @@ init_monitors() ->
end, emqx_pmon:new(), ?TAB). end, emqx_pmon:new(), ?TAB).
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[Shared] Unexpected request: ~p", [Req]), emqx_logger:error("[Shared] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast({monitor, SubPid}, State= #state{pmon = PMon}) -> handle_cast({monitor, SubPid}, State= #state{pmon = PMon}) ->
{noreply, State#state{pmon = PMon:monitor(SubPid)}}; {noreply, update_stats(State#state{pmon = PMon:monitor(SubPid)})};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[Shared] Unexpected msg: ~p", [Msg]), emqx_logger:error("[Shared] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = PMon}) -> handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = PMon}) ->
emqx_log:info("Shared subscription created: ~p", [NewRecord]),
#shared_subscription{subpid = SubPid} = NewRecord, #shared_subscription{subpid = SubPid} = NewRecord,
{noreply, State#state{pmon = PMon:monitor(SubPid)}}; {noreply, update_stats(State#state{pmon = PMon:monitor(SubPid)})};
handle_info({mnesia_table_event, {delete_object, OldRecord, _}}, State = #state{pmon = PMon}) -> handle_info({mnesia_table_event, {delete_object, OldRecord, _}}, State = #state{pmon = PMon}) ->
emqx_log:info("Shared subscription deleted: ~p", [OldRecord]),
#shared_subscription{subpid = SubPid} = OldRecord, #shared_subscription{subpid = SubPid} = OldRecord,
{noreply, State#state{pmon = PMon:demonitor(SubPid)}}; {noreply, update_stats(State#state{pmon = PMon:demonitor(SubPid)})};
handle_info({mnesia_table_event, _Event}, State) -> handle_info({mnesia_table_event, _Event}, State) ->
{noreply, State}; {noreply, State};
handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #state{pmon = PMon}) -> handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #state{pmon = PMon}) ->
emqx_log:info("Shared subscription down: ~p", [SubPid]), emqx_logger:info("Shared subscription down: ~p", [SubPid]),
mnesia:async_dirty(fun cleanup_down/1, [SubPid]), mnesia:async_dirty(fun cleanup_down/1, [SubPid]),
{noreply, State#state{pmon = PMon:erase(SubPid)}}; {noreply, update_stats(State#state{pmon = PMon:erase(SubPid)})};
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[Shared] Unexpected info: ~p", [Info]), emqx_logger:error("[Shared] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
@ -164,3 +166,8 @@ cleanup_down(SubPid) ->
mnesia:delete_object(?TAB, Record) mnesia:delete_object(?TAB, Record)
end, mnesia:match_object(Pat)). end, mnesia:match_object(Pat)).
update_stats(State) ->
emqx_stats:setstat('subscriptions/shared/count',
'subscriptions/shared/max',
ets:info(?TAB, size)), State.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_sm). -module(emqx_sm).
@ -24,7 +24,8 @@
-export([open_session/1, lookup_session/1, close_session/1]). -export([open_session/1, lookup_session/1, close_session/1]).
-export([resume_session/1, resume_session/2, discard_session/1, discard_session/2]). -export([resume_session/1, resume_session/2, discard_session/1, discard_session/2]).
-export([register_session/2, unregister_session/1]). -export([register_session/2, get_session_attrs/1, unregister_session/1]).
-export([get_session_stats/1, set_session_stats/2]).
%% Internal functions for rpc %% Internal functions for rpc
-export([dispatch/3]). -export([dispatch/3]).
@ -32,18 +33,22 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-record(state, {pmon}). -record(state, {session_pmon}).
-define(SM, ?MODULE). -define(SM, ?MODULE).
%% ETS Tables
-define(SESSION, emqx_session).
-define(SESSION_P, emqx_persistent_session).
-define(SESSION_ATTRS, emqx_session_attrs).
-define(SESSION_STATS, emqx_session_stats).
-spec(start_link() -> {ok, pid()} | ignore | {error, term()}). -spec(start_link() -> {ok, pid()} | ignore | {error, term()}).
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?SM}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% Open Session
%%--------------------------------------------------------------------
%% @doc Open a session.
-spec(open_session(map()) -> {ok, pid()} | {error, term()}).
open_session(Attrs = #{clean_start := true, open_session(Attrs = #{clean_start := true,
client_id := ClientId, client_pid := ClientPid}) -> client_id := ClientId, client_pid := ClientPid}) ->
CleanStart = fun(_) -> CleanStart = fun(_) ->
@ -66,81 +71,113 @@ open_session(Attrs = #{clean_start := false,
end, end,
emqx_sm_locker:trans(ClientId, ResumeStart). emqx_sm_locker:trans(ClientId, ResumeStart).
%%-------------------------------------------------------------------- %% @doc Discard all the sessions identified by the ClientId.
%% Discard Session -spec(discard_session(map()) -> ok).
%%--------------------------------------------------------------------
discard_session(ClientId) when is_binary(ClientId) -> discard_session(ClientId) when is_binary(ClientId) ->
discard_session(ClientId, self()). discard_session(ClientId, self()).
discard_session(ClientId, ClientPid) when is_binary(ClientId) -> discard_session(ClientId, ClientPid) when is_binary(ClientId) ->
lists:foreach( lists:foreach(
fun(#session{pid = SessionPid}) -> fun({_ClientId, SessionPid}) ->
case catch emqx_session:discard(SessionPid, ClientPid) of case catch emqx_session:discard(SessionPid, ClientPid) of
{'EXIT', Error} -> {'EXIT', Error} ->
emqx_log:error("[SM] Failed to discard ~p: ~p", [SessionPid, Error]); emqx_logger:error("[SM] Failed to discard ~p: ~p", [SessionPid, Error]);
ok -> ok ok -> ok
end end
end, lookup_session(ClientId)). end, lookup_session(ClientId)).
%%-------------------------------------------------------------------- %% @doc Try to resume a session.
%% Resume Session -spec(resume_session(client_id()) -> {ok, pid()} | {error, term()}).
%%--------------------------------------------------------------------
resume_session(ClientId) -> resume_session(ClientId) ->
resume_session(ClientId, self()). resume_session(ClientId, self()).
resume_session(ClientId, ClientPid) -> resume_session(ClientId, ClientPid) ->
case lookup_session(ClientId) of case lookup_session(ClientId) of
[] -> {error, not_found}; [] -> {error, not_found};
[#session{pid = SessionPid}] -> [{_ClientId, SessionPid}] ->
ok = emqx_session:resume(SessionPid, ClientPid), ok = emqx_session:resume(SessionPid, ClientPid),
{ok, SessionPid}; {ok, SessionPid};
Sessions -> Sessions ->
[#session{pid = SessionPid}|StaleSessions] = lists:reverse(Sessions), [{_, SessionPid}|StaleSessions] = lists:reverse(Sessions),
emqx_log:error("[SM] More than one session found: ~p", [Sessions]), emqx_logger:error("[SM] More than one session found: ~p", [Sessions]),
lists:foreach(fun(#session{pid = Pid}) -> lists:foreach(fun({_, StalePid}) ->
catch emqx_session:discard(Pid, ClientPid) catch emqx_session:discard(StalePid, ClientPid)
end, StaleSessions), end, StaleSessions),
ok = emqx_session:resume(SessionPid, ClientPid), ok = emqx_session:resume(SessionPid, ClientPid),
{ok, SessionPid} {ok, SessionPid}
end. end.
%%-------------------------------------------------------------------- %% @doc Close a session.
%% Close a session -spec(close_session({client_id(), pid()} | pid()) -> ok).
%%-------------------------------------------------------------------- close_session({_ClientId, SessionPid}) ->
emqx_session:close(SessionPid);
close_session(#session{pid = SessionPid}) -> close_session(SessionPid) when is_pid(SessionPid) ->
emqx_session:close(SessionPid). emqx_session:close(SessionPid).
%%-------------------------------------------------------------------- %% @doc Register a session with attributes.
%% Create/Delete a session -spec(register_session(client_id() | {client_id(), pid()},
%%-------------------------------------------------------------------- list(emqx_session:attribute())) -> ok).
register_session(ClientId, Attrs) when is_binary(ClientId) ->
register_session({ClientId, self()}, Attrs);
register_session(Session, Attrs) when is_record(Session, session) -> register_session(Session = {ClientId, SessionPid}, Attrs)
ets:insert(session, Session), when is_binary(ClientId), is_pid(SessionPid) ->
ets:insert(session_attrs, {Session, Attrs}), ets:insert(?SESSION, Session),
ets:insert(?SESSION_ATTRS, {Session, Attrs}),
case proplists:get_value(clean_start, Attrs, true) of
true -> ok;
false -> ets:insert(?SESSION_P, Session)
end,
emqx_sm_registry:register_session(Session), emqx_sm_registry:register_session(Session),
gen_server:cast(?MODULE, {registered, Session}). notify({registered, ClientId, SessionPid}).
unregister_session(Session) when is_record(Session, session) -> %% @doc Get session attrs
-spec(get_session_attrs({client_id(), pid()})
-> list(emqx_session:attribute())).
get_session_attrs(Session = {ClientId, SessionPid})
when is_binary(ClientId), is_pid(SessionPid) ->
safe_lookup_element(?SESSION_ATTRS, Session, []).
%% @doc Unregister a session
-spec(unregister_session(client_id() | {client_id(), pid()}) -> ok).
unregister_session(ClientId) when is_binary(ClientId) ->
unregister_session({ClientId, self()});
unregister_session(Session = {ClientId, SessionPid})
when is_binary(ClientId), is_pid(SessionPid) ->
emqx_sm_registry:unregister_session(Session), emqx_sm_registry:unregister_session(Session),
emqx_sm_stats:del_session_stats(Session), ets:delete(?SESSION_STATS, Session),
ets:delete(session_attrs, Session), ets:delete(?SESSION_ATTRS, Session),
ets:delete_object(session, Session), ets:delete_object(?SESSION_P, Session),
gen_server:cast(?MODULE, {unregistered, Session}). ets:delete_object(?SESSION, Session),
notify({unregistered, ClientId, SessionPid}).
%%-------------------------------------------------------------------- %% @doc Get session stats
%% Lookup a session from registry -spec(get_session_stats({client_id(), pid()}) -> list(emqx_stats:stats())).
%%-------------------------------------------------------------------- get_session_stats(Session = {ClientId, SessionPid})
when is_binary(ClientId), is_pid(SessionPid) ->
safe_lookup_element(?SESSION_STATS, Session, []).
%% @doc Set session stats
-spec(set_session_stats(client_id() | {client_id(), pid()},
emqx_stats:stats()) -> ok).
set_session_stats(ClientId, Stats) when is_binary(ClientId) ->
set_session_stats({ClientId, self()}, Stats);
set_session_stats(Session = {ClientId, SessionPid}, Stats)
when is_binary(ClientId), is_pid(SessionPid) ->
ets:insert(?SESSION_STATS, {Session, Stats}).
%% @doc Lookup a session from registry
-spec(lookup_session(client_id()) -> list({client_id(), pid()})).
lookup_session(ClientId) -> lookup_session(ClientId) ->
emqx_sm_registry:lookup_session(ClientId). case emqx_sm_registry:is_enabled() of
true -> emqx_sm_registry:lookup_session(ClientId);
%%-------------------------------------------------------------------- false -> ets:lookup(?SESSION, ClientId)
%% Dispatch by client Id end.
%%--------------------------------------------------------------------
%% @doc Dispatch a message to the session.
-spec(dispatch(client_id(), topic(), message()) -> any()).
dispatch(ClientId, Topic, Msg) -> dispatch(ClientId, Topic, Msg) ->
case lookup_session_pid(ClientId) of case lookup_session_pid(ClientId) of
Pid when is_pid(Pid) -> Pid when is_pid(Pid) ->
@ -149,60 +186,82 @@ dispatch(ClientId, Topic, Msg) ->
emqx_hooks:run('message.dropped', [ClientId, Msg]) emqx_hooks:run('message.dropped', [ClientId, Msg])
end. end.
%% @doc Lookup session pid.
-spec(lookup_session_pid(client_id()) -> pid() | undefined).
lookup_session_pid(ClientId) -> lookup_session_pid(ClientId) ->
try ets:lookup_element(session, ClientId, #session.pid) safe_lookup_element(?SESSION, ClientId, undefined).
catch error:badarg ->
undefined safe_lookup_element(Tab, Key, Default) ->
try ets:lookup_element(Tab, Key, 2)
catch
error:badarg -> Default
end. end.
notify(Event) -> gen_server:cast(?SM, {notify, Event}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
_ = emqx_tables:create(session, [public, set, {keypos, 2}, TabOpts = [public, set, {write_concurrency, true}],
{read_concurrency, true}, _ = emqx_tables:new(?SESSION, [{read_concurrency, true} | TabOpts]),
{write_concurrency, true}]), _ = emqx_tables:new(?SESSION_P, TabOpts),
_ = emqx_tables:create(session_attrs, [public, set, _ = emqx_tables:new(?SESSION_ATTRS, TabOpts),
{write_concurrency, true}]), _ = emqx_tables:new(?SESSION_STATS, TabOpts),
{ok, #state{pmon = emqx_pmon:new()}}. emqx_stats:update_interval(sm_stats, stats_fun()),
{ok, #state{session_pmon = emqx_pmon:new()}}.
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[SM] Unexpected request: ~p", [Req]), emqx_logger:error("[SM] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast({registered, #session{sid = ClientId, pid = SessionPid}}, handle_cast({notify, {registered, ClientId, SessionPid}},
State = #state{pmon = PMon}) -> State = #state{session_pmon = PMon}) ->
{noreply, State#state{pmon = PMon:monitor(SessionPid, ClientId)}}; {noreply, State#state{session_pmon = PMon:monitor(SessionPid, ClientId)}};
handle_cast({unregistered, #session{sid = _ClientId, pid = SessionPid}}, handle_cast({notify, {unregistered, _ClientId, SessionPid}},
State = #state{pmon = PMon}) -> State = #state{session_pmon = PMon}) ->
{noreply, State#state{pmon = PMon:erase(SessionPid)}}; {noreply, State#state{session_pmon = PMon:demonitor(SessionPid)}};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[SM] Unexpected msg: ~p", [Msg]), emqx_logger:error("[SM] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({'DOWN', _MRef, process, DownPid, _Reason}, handle_info({'DOWN', _MRef, process, DownPid, _Reason},
State = #state{pmon = PMon}) -> State = #state{session_pmon = PMon}) ->
case PMon:find(DownPid) of case PMon:find(DownPid) of
{ok, ClientId} ->
case ets:lookup(session, ClientId) of
[] -> ok;
_ -> unregister_session(#session{sid = ClientId, pid = DownPid})
end,
{noreply, State};
undefined -> undefined ->
{noreply, State} {noreply, State};
ClientId ->
unregister_session({ClientId, DownPid}),
{noreply, State#state{session_pmon = PMon:erase(DownPid)}}
end; end;
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[SM] Unexpected info: ~p", [Info]), emqx_logger:error("[SM] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ok. emqx_stats:cancel_update(cm_stats).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
stats_fun() ->
fun () ->
safe_update_stats(?SESSION, 'sessions/count', 'sessions/max'),
safe_update_stats(?SESSION_P, 'sessions/persistent/count',
'sessions/persistent/max')
end.
safe_update_stats(Tab, Stat, MaxStat) ->
case ets:info(Tab, size) of
undefined -> ok;
Size -> emqx_stats:setstat(Stat, MaxStat, Size)
end.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_sm_locker). -module(emqx_sm_locker).
@ -32,8 +32,11 @@ start_link() ->
trans(ClientId, Fun) -> trans(ClientId, Fun) ->
trans(ClientId, Fun, undefined). trans(ClientId, Fun, undefined).
-spec(trans(client_id(), fun(([node()]) -> any()), -spec(trans(client_id() | undefined,
fun(([node()]) -> any()),
ekka_locker:piggyback()) -> any()). ekka_locker:piggyback()) -> any()).
trans(undefined, Fun, _Piggyback) ->
Fun([]);
trans(ClientId, Fun, Piggyback) -> trans(ClientId, Fun, Piggyback) ->
case lock(ClientId, Piggyback) of case lock(ClientId, Piggyback) of
{true, Nodes} -> {true, Nodes} ->

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_sm_registry). -module(emqx_sm_registry).
@ -23,58 +23,73 @@
%% API %% API
-export([start_link/0]). -export([start_link/0]).
-export([is_enabled/0]).
-export([register_session/1, lookup_session/1, unregister_session/1]). -export([register_session/1, lookup_session/1, unregister_session/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-define(SERVER, ?MODULE). -define(REGISTRY, ?MODULE).
-define(TAB, session_registry). -define(TAB, emqx_session_registry).
-define(LOCK, {?MODULE, cleanup_sessions}). -define(LOCK, {?MODULE, cleanup_sessions}).
-record(global_session, {sid, pid}).
-record(state, {}). -record(state, {}).
-type(session_pid() :: pid()).
%% @doc Start the session manager.
-spec(start_link() -> {ok, pid()} | ignore | {error, term()}).
start_link() ->
gen_server:start_link({local, ?REGISTRY}, ?MODULE, [], []).
-spec(is_enabled() -> boolean()).
is_enabled() ->
ets:info(?TAB, name) =/= undefined.
-spec(lookup_session(client_id()) -> list({client_id(), session_pid()})).
lookup_session(ClientId) ->
[{ClientId, SessionPid} || #global_session{pid = SessionPid}
<- mnesia:dirty_read(?TAB, ClientId)].
-spec(register_session({client_id(), session_pid()}) -> ok).
register_session({ClientId, SessionPid}) when is_binary(ClientId),
is_pid(SessionPid) ->
mnesia:dirty_write(?TAB, record(ClientId, SessionPid)).
-spec(unregister_session({client_id(), session_pid()}) -> ok).
unregister_session({ClientId, SessionPid}) when is_binary(ClientId),
is_pid(SessionPid) ->
mnesia:dirty_delete_object(?TAB, record(ClientId, SessionPid)).
record(ClientId, SessionPid) ->
#global_session{sid = ClientId, pid = SessionPid}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link() -> init([]) ->
ok = ekka_mnesia:create_table(?TAB, [ ok = ekka_mnesia:create_table(?TAB, [
{type, bag}, {type, bag},
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, session}, {record_name, global_session},
{attributes, record_info(fields, session)}]), {attributes, record_info(fields, global_session)}]),
ok = ekka_mnesia:copy_table(?TAB), ok = ekka_mnesia:copy_table(?TAB),
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec(lookup_session(client_id()) -> list(session())).
lookup_session(ClientId) ->
mnesia:dirty_read(?TAB, ClientId).
-spec(register_session(session()) -> ok).
register_session(Session) when is_record(Session, session) ->
mnesia:dirty_write(?TAB, Session).
-spec(unregister_session(session()) -> ok).
unregister_session(Session) when is_record(Session, session) ->
mnesia:dirty_delete_object(?TAB, Session).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
ekka:monitor(membership), ekka:monitor(membership),
{ok, #state{}}. {ok, #state{}}.
handle_call(_Request, _From, State) -> handle_call(Req, _From, State) ->
Reply = ok, emqx_logger:error("[Registry] Unexpected request: ~p", [Req]),
{reply, Reply, State}. {reply, ignore, State}.
handle_cast(_Msg, State) -> handle_cast(Msg, State) ->
emqx_logger:error("[Registry] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({membership, {mnesia, down, Node}}, State) -> handle_info({membership, {mnesia, down, Node}}, State) ->
@ -87,7 +102,8 @@ handle_info({membership, {mnesia, down, Node}}, State) ->
handle_info({membership, _Event}, State) -> handle_info({membership, _Event}, State) ->
{noreply, State}; {noreply, State};
handle_info(_Info, State) -> handle_info(Info, State) ->
emqx_logger:error("[Registry] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
@ -101,7 +117,7 @@ code_change(_OldVsn, State, _Extra) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
cleanup_sessions(Node) -> cleanup_sessions(Node) ->
Pat = [{#session{pid = '$1', _ = '_'}, Pat = [{#global_session{pid = '$1', _ = '_'},
[{'==', {node, '$1'}, Node}], ['$_']}], [{'==', {node, '$1'}, Node}], ['$_']}],
lists:foreach(fun(Session) -> lists:foreach(fun(Session) ->
mnesia:delete_object(?TAB, Session) mnesia:delete_object(?TAB, Session)

View File

@ -1,72 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Inc. 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_sm_stats).
-behaviour(gen_statem).
-include("emqx.hrl").
%% API
-export([start_link/0]).
-export([set_session_stats/2, get_session_stats/1, del_session_stats/1]).
%% gen_statem callbacks
-export([init/1, callback_mode/0, handle_event/4, terminate/3, code_change/4]).
-define(TAB, session_stats).
-record(state, {statsfun}).
start_link() ->
gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec(set_session_stats(session(), emqx_stats:stats()) -> true).
set_session_stats(Session, Stats) when is_record(Session, session) ->
ets:insert(?TAB, {Session, [{'$ts', emqx_time:now_secs()}|Stats]}).
-spec(get_session_stats(session()) -> emqx_stats:stats()).
get_session_stats(Session) ->
case ets:lookup(?TAB, Session) of
[{_, Stats}] -> Stats;
[] -> []
end.
-spec(del_session_stats(session()) -> true).
del_session_stats(Session) ->
ets:delete(?TAB, Session).
init([]) ->
_ = emqx_tables:create(?TAB, [public, {write_concurrency, true}]),
StatsFun = emqx_stats:statsfun('sessions/count', 'sessions/max'),
{ok, idle, #state{statsfun = StatsFun}, timer:seconds(1)}.
callback_mode() -> handle_event_function.
handle_event(timeout, _Timeout, idle, State = #state{statsfun = StatsFun}) ->
case ets:info(session, size) of
undefined -> ok;
Size -> StatsFun(Size)
end,
{next_state, idle, State, timer:seconds(1)}.
terminate(_Reason, _StateName, _State) ->
ok.
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_sm_sup). -module(emqx_sm_sup).
@ -26,12 +26,17 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) -> init([]) ->
Childs = [child(M) || M <- [emqx_sm_locker, %% Session Locker
emqx_sm_registry, Locker = {locker, {emqx_sm_locker, start_link, []},
emqx_sm_stats, permanent, 5000, worker, [emqx_sm_locker]},
emqx_sm]],
{ok, {{one_for_all, 10, 3600}, Childs}}.
child(M) -> %% Session Registry
{M, {M, start_link, []}, permanent, 5000, worker, [M]}. Registry = {registry, {emqx_sm_registry, start_link, []},
permanent, 5000, worker, [emqx_sm_registry]},
%% Session Manager
Manager = {manager, {emqx_sm, start_link, []},
permanent, 5000, worker, [emqx_sm]},
{ok, {{one_for_rest, 10, 3600}, [Locker, Registry, Manager]}}.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_stats). -module(emqx_stats).
@ -20,69 +20,78 @@
-include("emqx.hrl"). -include("emqx.hrl").
-export([start_link/0, stop/0]). -export([start_link/0]).
%% Get all Stats %% Get all stats
-export([all/0]). -export([all/0]).
%% Statistics API. %% Stats API.
-export([statsfun/1, statsfun/2, getstats/0, getstat/1, setstat/2, setstat/3]). -export([statsfun/1, statsfun/2, getstats/0, getstat/1, setstat/2, setstat/3]).
-export([update_interval/2, update_interval/3, cancel_update/1]).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-record(state, {tick}). -record(update, {name, countdown, interval, func}).
-record(state, {timer, updates :: #update{}}).
-type(stats() :: list({atom(), non_neg_integer()})). -type(stats() :: list({atom(), non_neg_integer()})).
-export_type([stats/0]). -export_type([stats/0]).
-define(STATS_TAB, stats). %% Client stats
-define(CLIENT_STATS, [
%% $SYS Topics for Clients
-define(SYSTOP_CLIENTS, [
'clients/count', % clients connected current 'clients/count', % clients connected current
'clients/max' % max clients connected 'clients/max' % maximum clients connected
]). ]).
%% $SYS Topics for Sessions %% Session stats
-define(SYSTOP_SESSIONS, [ -define(SESSION_STATS, [
'sessions/count', 'sessions/count',
'sessions/max' 'sessions/max',
'sessions/persistent/count',
'sessions/persistent/max'
]). ]).
%% $SYS Topics for Subscribers %% Subscribers, Subscriptions stats
-define(SYSTOP_PUBSUB, [ -define(PUBSUB_STATS, [
'topics/count', % ... 'topics/count',
'topics/max', % ... 'topics/max',
'subscribers/count', % ... 'subscribers/count',
'subscribers/max', % ... 'subscribers/max',
'subscriptions/count', % ... 'subscriptions/count',
'subscriptions/max', % ... 'subscriptions/max'
'routes/count', % ...
'routes/max' % ...
]). ]).
%% $SYS Topic for retained -define(ROUTE_STATS, [
-define(SYSTOP_RETAINED, [ 'routes/count',
'routes/max'
]).
%% Retained stats
-define(RETAINED_STATS, [
'retained/count', 'retained/count',
'retained/max' 'retained/max'
]). ]).
-define(TAB, ?MODULE).
-define(SERVER, ?MODULE).
%% @doc Start stats server
-spec(start_link() -> {ok, pid()} | ignore | {error, term()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Start stats server %% Get all stats.
-spec(start_link() -> {ok, pid()} | ignore | {error, term()}). -spec(all() -> stats()).
start_link() -> all() -> getstats().
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() ->
gen_server:call(?MODULE, stop).
all() -> ets:tab2list(?STATS_TAB).
%% @doc Generate stats fun %% @doc Generate stats fun
-spec(statsfun(Stat :: atom()) -> fun()). -spec(statsfun(Stat :: atom()) -> fun()).
@ -94,71 +103,117 @@ statsfun(Stat, MaxStat) ->
fun(Val) -> setstat(Stat, MaxStat, Val) end. fun(Val) -> setstat(Stat, MaxStat, Val) end.
%% @doc Get all statistics %% @doc Get all statistics
-spec(getstats() -> [{atom(), non_neg_integer()}]). -spec(getstats() -> stats()).
getstats() -> getstats() ->
lists:sort(ets:tab2list(?STATS_TAB)). case ets:info(?TAB, name) of
undefined -> [];
_ -> ets:tab2list(?TAB)
end.
%% @doc Get stats by name %% @doc Get stats by name
-spec(getstat(atom()) -> non_neg_integer() | undefined). -spec(getstat(atom()) -> non_neg_integer() | undefined).
getstat(Name) -> getstat(Name) ->
case ets:lookup(?STATS_TAB, Name) of case ets:lookup(?TAB, Name) of
[{Name, Val}] -> Val; [{Name, Val}] -> Val;
[] -> undefined [] -> undefined
end. end.
%% @doc Set broker stats %% @doc Set stats
-spec(setstat(Stat :: atom(), Val :: pos_integer()) -> boolean()). -spec(setstat(Stat :: atom(), Val :: pos_integer()) -> boolean()).
setstat(Stat, Val) -> setstat(Stat, Val) when is_integer(Val) ->
ets:update_element(?STATS_TAB, Stat, {2, Val}). safe_update_element(Stat, Val).
%% @doc Set stats with max %% @doc Set stats with max value.
-spec(setstat(Stat :: atom(), MaxStat :: atom(), Val :: pos_integer()) -> boolean()). -spec(setstat(Stat :: atom(), MaxStat :: atom(),
setstat(Stat, MaxStat, Val) -> Val :: pos_integer()) -> boolean()).
gen_server:cast(?MODULE, {setstat, Stat, MaxStat, Val}). setstat(Stat, MaxStat, Val) when is_integer(Val) ->
cast({setstat, Stat, MaxStat, Val}).
-spec(update_interval(atom(), fun()) -> ok).
update_interval(Name, UpFun) ->
update_interval(Name, 1, UpFun).
-spec(update_interval(atom(), pos_integer(), fun()) -> ok).
update_interval(Name, Secs, UpFun) when is_integer(Secs), Secs >= 1 ->
cast({update_interval, rec(Name, Secs, UpFun)}).
-spec(cancel_update(atom()) -> ok).
cancel_update(Name) ->
cast({cancel_update, Name}).
rec(Name, Secs, UpFun) ->
#update{name = Name, countdown = Secs, interval = Secs, func = UpFun}.
cast(Msg) ->
gen_server:cast(?SERVER, Msg).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
emqx_time:seed(), _ = emqx_tables:new(?TAB, [set, public, {write_concurrency, true}]),
_ = emqx_tables:create(?STATS_TAB, [set, public, named_table, Stats = lists:append([?CLIENT_STATS, ?SESSION_STATS, ?PUBSUB_STATS,
{write_concurrency, true}]), ?ROUTE_STATS, ?RETAINED_STATS]),
Topics = ?SYSTOP_CLIENTS ++ ?SYSTOP_SESSIONS ++ ?SYSTOP_PUBSUB ++ ?SYSTOP_RETAINED, ets:insert(?TAB, [{Name, 0} || Name <- Stats]),
ets:insert(?STATS_TAB, [{Topic, 0} || Topic <- Topics]), {ok, start_timer(#state{updates = []}), hibernate}.
% Tick to publish stats
{ok, TRef} = timer:send_after(emqx_sys:sys_interval(), tick),
{ok, #state{tick = TRef}, hibernate}.
handle_call(stop, _From, State) -> start_timer(State) ->
{stop, normal, ok, State}; State#state{timer = emqx_misc:start_timer(timer:seconds(1), tick)}.
handle_call(_Request, _From, State) -> handle_call(Req, _From, State) ->
{reply, error, State}. emqx_logger:error("[STATS] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
%% atomic
handle_cast({setstat, Stat, MaxStat, Val}, State) -> handle_cast({setstat, Stat, MaxStat, Val}, State) ->
MaxVal = ets:lookup_element(?STATS_TAB, MaxStat, 2), try ets:lookup_element(?TAB, MaxStat, 2) of
if MaxVal when Val > MaxVal ->
Val > MaxVal -> ets:update_element(?TAB, MaxStat, {2, Val});
ets:update_element(?STATS_TAB, MaxStat, {2, Val}); _ -> ok
true -> ok catch
error:badarg ->
ets:insert(?TAB, {MaxStat, Val})
end, end,
ets:update_element(?STATS_TAB, Stat, {2, Val}), safe_update_element(Stat, Val),
{noreply, State}; {noreply, State};
handle_cast(_Msg, State) -> handle_cast({update_interval, Update = #update{name = Name}},
State = #state{updates = Updates}) ->
case lists:keyfind(Name, #update.name, Updates) of
#update{} ->
emqx_logger:error("[STATS]: Duplicated update: ~s", [Name]),
{noreply, State};
false ->
{noreply, State#state{updates = [Update | Updates]}}
end;
handle_cast({cancel_update, Name}, State = #state{updates = Updates}) ->
{noreply, State#state{updates = lists:keydelete(Name, #update.name, Updates)}};
handle_cast(Msg, State) ->
emqx_logger:error("[STATS] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
%% Interval Tick. handle_info({timeout, TRef, tick}, State = #state{timer = TRef,
handle_info(tick, State) -> updates = Updates}) ->
[publish(Stat, Val) || {Stat, Val} <- ets:tab2list(?STATS_TAB)], lists:foldl(
{noreply, State, hibernate}; fun(Update = #update{name = Name, countdown = C, interval = I,
func = UpFun}, Acc) when C =< 0 ->
try UpFun()
catch _:Error ->
emqx_logger:error("[STATS] Update ~s error: ~p", [Name, Error])
end,
[Update#update{countdown = I} | Acc];
(Update = #update{countdown = C}, Acc) ->
[Update#update{countdown = C - 1} | Acc]
end, [], Updates),
{noreply, start_timer(State), hibernate};
handle_info(_Info, State) -> handle_info(Info, State) ->
emqx_logger:error("[STATS] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{tick = TRef}) -> terminate(_Reason, #state{timer = TRef}) ->
timer:cancel(TRef). timer:cancel(TRef).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
@ -168,12 +223,10 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
publish(Stat, Val) -> safe_update_element(Key, Val) ->
Msg = emqx_message:make(stats, stats_topic(Stat), bin(Val)), try ets:update_element(?TAB, Key, {2, Val})
emqx:publish(emqx_message:set_flag(sys, Msg)). catch
error:badarg ->
stats_topic(Stat) -> ets:insert_new(?TAB, {Key, Val})
emqx_topic:systop(list_to_binary(lists:concat(['stats/', Stat]))). end.
bin(I) when is_integer(I) -> list_to_binary(integer_to_list(I)).

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_sup). -module(emqx_sup).
@ -20,7 +20,6 @@
-export([start_link/0, start_child/1, start_child/2, stop_child/1]). -export([start_link/0, start_child/1, start_child/2, stop_child/1]).
%% Supervisor callbacks
-export([init/1]). -export([init/1]).
-type(startchild_ret() :: {ok, supervisor:child()} -type(startchild_ret() :: {ok, supervisor:child()}
@ -29,20 +28,24 @@
-define(SUPERVISOR, ?MODULE). -define(SUPERVISOR, ?MODULE).
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
start_link() -> start_link() ->
supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
-spec(start_child(supervisor:child_spec()) -> startchild_ret()). -spec(start_child(supervisor:child_spec()) -> startchild_ret()).
start_child(ChildSpec) when is_tuple(ChildSpec) -> start_child(ChildSpec) when is_tuple(ChildSpec) ->
supervisor:start_child(?SUPERVISOR, ChildSpec). supervisor:start_child(?SUPERVISOR, ChildSpec).
-spec(start_child(atom(), worker | supervisor) -> startchild_ret()). -spec(start_child(module(), worker | supervisor) -> startchild_ret()).
start_child(Mod, Type) when Type == worker orelse Type == supervisor -> start_child(Mod, worker) ->
start_child(?CHILD(Mod, Type)). start_child(worker_spec(Mod));
start_child(Mod, supervisor) ->
start_child(supervisor_spec(Mod)).
-spec(stop_child(supervisor:child_id()) -> ok | {error, any()}). -spec(stop_child(supervisor:child_id()) -> ok | {error, term()}).
stop_child(ChildId) -> stop_child(ChildId) ->
case supervisor:terminate_child(?SUPERVISOR, ChildId) of case supervisor:terminate_child(?SUPERVISOR, ChildId) of
ok -> supervisor:delete_child(?SUPERVISOR, ChildId); ok -> supervisor:delete_child(?SUPERVISOR, ChildId);
@ -54,24 +57,44 @@ stop_child(ChildId) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
{ok, {{one_for_all, 10, 3600}, %% Kernel Sup
[?CHILD(emqx_ctl, worker), KernelSup = supervisor_spec(emqx_kernel_sup),
?CHILD(emqx_hooks, worker), %% Router Sup
?CHILD(emqx_stats, worker), RouterSup = supervisor_spec(emqx_router_sup),
?CHILD(emqx_metrics, worker), %% Broker Sup
?CHILD(emqx_sys, worker), BrokerSup = supervisor_spec(emqx_broker_sup),
?CHILD(emqx_router_sup, supervisor), %% BridgeSup
?CHILD(emqx_broker_sup, supervisor), BridgeSup = supervisor_spec(emqx_bridge_sup_sup),
?CHILD(emqx_pooler, supervisor), %% AccessControl
?CHILD(emqx_tracer_sup, supervisor), AccessControl = worker_spec(emqx_access_control),
?CHILD(emqx_cm_sup, supervisor), %% Session Manager
?CHILD(emqx_sm_sup, supervisor), SMSup = supervisor_spec(emqx_sm_sup),
?CHILD(emqx_session_sup, supervisor), %% Session Sup
?CHILD(emqx_ws_connection_sup, supervisor), SessionSup = supervisor_spec(emqx_session_sup),
?CHILD(emqx_alarm, worker), %% Connection Manager
?CHILD(emqx_mod_sup, supervisor), CMSup = supervisor_spec(emqx_cm_sup),
?CHILD(emqx_bridge_sup_sup, supervisor), %% WebSocket Connection Sup
?CHILD(emqx_access_control, worker), WSConnSup = supervisor_spec(emqx_ws_connection_sup),
?CHILD(emqx_sysmon_sup, supervisor)] %% Sys Sup
}}. SysSup = supervisor_spec(emqx_sys_sup),
{ok, {{one_for_all, 0, 1},
[KernelSup,
RouterSup,
BrokerSup,
BridgeSup,
AccessControl,
SMSup,
SessionSup,
CMSup,
WSConnSup,
SysSup]}}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
worker_spec(M) ->
{M, {M, start_link, []}, permanent, 30000, worker, [M]}.
supervisor_spec(M) ->
{M, {M, start_link, []}, permanent, infinity, supervisor, [M]}.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_sys). -module(emqx_sys).
@ -22,8 +22,6 @@
-export([start_link/0]). -export([start_link/0]).
-export([schedulers/0]).
-export([version/0, uptime/0, datetime/0, sysdescr/0, sys_interval/0]). -export([version/0, uptime/0, datetime/0, sysdescr/0, sys_interval/0]).
-export([info/0]). -export([info/0]).
@ -31,33 +29,29 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-record(state, {started_at, heartbeat, sys_ticker, version, sysdescr}). -import(emqx_topic, [systop/1]).
-import(emqx_misc, [start_timer/2]).
-record(state, {start_time, heartbeat, ticker, version, sysdescr}).
-define(APP, emqx). -define(APP, emqx).
-define(SYS, ?MODULE).
-define(SERVER, ?MODULE). -define(INFO_KEYS, [
%% $SYS Topics of Broker
-define(SYSTOP_BROKERS, [
version, % Broker version version, % Broker version
uptime, % Broker uptime uptime, % Broker uptime
datetime, % Broker local datetime datetime, % Broker local datetime
sysdescr % Broker description sysdescr % Broker description
]). ]).
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link() ->
gen_server:start_link({local, ?SYS}, ?MODULE, [], []).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% @doc Get schedulers
-spec(schedulers() -> pos_integer()).
schedulers() ->
erlang:system_info(schedulers).
%% @doc Get sys version %% @doc Get sys version
-spec(version() -> string()). -spec(version() -> string()).
version() -> version() ->
@ -70,7 +64,7 @@ sysdescr() ->
%% @doc Get sys uptime %% @doc Get sys uptime
-spec(uptime() -> string()). -spec(uptime() -> string()).
uptime() -> gen_server:call(?SERVER, uptime). uptime() -> gen_server:call(?SYS, uptime).
%% @doc Get sys datetime %% @doc Get sys datetime
-spec(datetime() -> string()). -spec(datetime() -> string()).
@ -80,6 +74,8 @@ datetime() ->
io_lib:format( io_lib:format(
"~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S])). "~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S])).
%% @doc Get sys interval
-spec(sys_interval() -> pos_integer()).
sys_interval() -> sys_interval() ->
application:get_env(?APP, sys_interval, 60000). application:get_env(?APP, sys_interval, 60000).
@ -96,44 +92,49 @@ info() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
Tick = fun(I, M) -> State = #state{start_time = erlang:timestamp(),
{ok, TRef} = timer:send_interval(I, M), TRef
end,
{ok, #state{started_at = os:timestamp(),
heartbeat = Tick(1000, heartbeat),
sys_ticker = Tick(sys_interval(), tick),
version = iolist_to_binary(version()), version = iolist_to_binary(version()),
sysdescr = iolist_to_binary(sysdescr())}, hibernate}. sysdescr = iolist_to_binary(sysdescr())},
{ok, heartbeat(tick(State))}.
heartbeat(State) ->
State#state{heartbeat = start_timer(timer:seconds(1), heartbeat)}.
tick(State) ->
State#state{ticker = start_timer(sys_interval(), tick)}.
handle_call(uptime, _From, State) -> handle_call(uptime, _From, State) ->
{reply, uptime(State), State}; {reply, uptime(State), State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[SYS] Unexpected request: ~p", [Req]), emqx_logger:error("[SYS] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[SYS] Unexpected msg: ~p", [Msg]), emqx_logger:error("[SYS] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(heartbeat, State) -> handle_info({timeout, TRef, heartbeat}, State = #state{heartbeat = TRef}) ->
publish(uptime, iolist_to_binary(uptime(State))), publish(uptime, iolist_to_binary(uptime(State))),
publish(datetime, iolist_to_binary(datetime())), publish(datetime, iolist_to_binary(datetime())),
{noreply, State, hibernate}; {noreply, heartbeat(State)};
handle_info(tick, State = #state{version = Version, sysdescr = Descr}) -> handle_info({timeout, TRef, tick}, State = #state{ticker = TRef,
retain(brokers), version = Version,
retain(version, Version), sysdescr = Descr}) ->
retain(sysdescr, Descr), publish(version, Version),
{noreply, State, hibernate}; publish(sysdescr, Descr),
publish(brokers, ekka_mnesia:running_nodes()),
publish(stats, emqx_stats:all()),
publish(metrics, emqx_metrics:all()),
{noreply, tick(State), hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[SYS] Unexpected info: ~p", [Info]), emqx_logger:error("[SYS] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{heartbeat = Hb, sys_ticker = TRef}) -> terminate(_Reason, #state{heartbeat = HBRef, ticker = TRef}) ->
timer:cancel(Hb), emqx_misc:cancel_timer(HBRef),
timer:cancel(TRef). emqx_misc:cancel_timer(TRef).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -142,24 +143,9 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
retain(brokers) -> uptime(#state{start_time = Ts}) ->
Payload = list_to_binary(string:join([atom_to_list(N) || Secs = timer:now_diff(erlang:timestamp(), Ts) div 1000000,
N <- ekka_mnesia:running_nodes()], ",")),
Msg = emqx_message:make(broker, <<"$SYS/brokers">>, Payload),
emqx:publish(emqx_message:set_flag(sys, emqx_message:set_flag(retain, Msg))).
retain(Topic, Payload) when is_binary(Payload) ->
Msg = emqx_message:make(broker, emqx_topic:systop(Topic), Payload),
emqx:publish(emqx_message:set_flag(sys, emqx_message:set_flag(retain, Msg))).
publish(Topic, Payload) when is_binary(Payload) ->
Msg = emqx_message:make(broker, emqx_topic:systop(Topic), Payload),
emqx:publish(emqx_message:set_flag(sys, Msg)).
uptime(#state{started_at = Ts}) ->
Secs = timer:now_diff(os:timestamp(), Ts) div 1000000,
lists:flatten(uptime(seconds, Secs)). lists:flatten(uptime(seconds, Secs)).
uptime(seconds, Secs) when Secs < 60 -> uptime(seconds, Secs) when Secs < 60 ->
[integer_to_list(Secs), " seconds"]; [integer_to_list(Secs), " seconds"];
uptime(seconds, Secs) -> uptime(seconds, Secs) ->
@ -175,3 +161,38 @@ uptime(hours, H) ->
uptime(days, D) -> uptime(days, D) ->
[integer_to_list(D), " days,"]. [integer_to_list(D), " days,"].
publish(uptime, Uptime) ->
safe_publish(systop(uptime), [sys], Uptime);
publish(datetime, Datetime) ->
safe_publish(systop(datatype), [sys], Datetime);
publish(version, Version) ->
safe_publish(systop(version), [sys, retain], Version);
publish(sysdescr, Descr) ->
safe_publish(systop(sysdescr), [sys, retain], Descr);
publish(brokers, Nodes) ->
Payload = string:join([atom_to_list(N) || N <- Nodes], ","),
safe_publish(<<"$SYS/brokers">>, [sys, retain], Payload);
publish(stats, Stats) ->
[begin
Topic = systop(lists:concat(['stats/', Stat])),
safe_publish(Topic, [sys], integer_to_binary(Val))
end || {Stat, Val} <- Stats, is_atom(Stat), is_integer(Val)];
publish(metrics, Metrics) ->
[begin
Topic = systop(lists:concat(['metrics/', Metric])),
safe_publish(Topic, [sys], integer_to_binary(Val))
end || {Metric, Val} <- Metrics, is_atom(Metric), is_integer(Val)].
safe_publish(Topic, Flags, Payload) ->
try do_publish(Topic, Flags, Payload)
catch
_:Error ->
emqx_logger:error("[SYS] Publish error: ~p", [Error])
end.
do_publish(Topic, Flags, Payload) ->
Msg0 = emqx_message:make(?SYS, Topic, iolist_to_binary(Payload)),
emqx_broker:publish(lists:foldl(fun(Flag, Msg) ->
emqx_message:set_flag(Flag, Msg)
end, Msg0, Flags)).

View File

@ -1,20 +1,20 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_sysmon). -module(emqx_sys_mon).
-behavior(gen_server). -behavior(gen_server).
@ -23,31 +23,34 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-record(state, {tickref, events = [], tracelog}). -record(state, {timer, events}).
%%-define(LOG_FMT, [{formatter_config, [time, " ", message, "\n"]}]).
-define(LOG(Msg, ProcInfo), -define(LOG(Msg, ProcInfo),
emqx_log:warning([{sysmon, true}], "[SYSMON] ~s~n~p", [WarnMsg, ProcInfo])). emqx_logger:warning([{sysmon, true}],
"[SYSMON] ~s~n~p", [WarnMsg, ProcInfo])).
-define(LOG(Msg, ProcInfo, PortInfo), -define(LOG(Msg, ProcInfo, PortInfo),
emqx_log:warning([{sysmon, true}], "[SYSMON] ~s~n~p~n~p", [WarnMsg, ProcInfo, PortInfo])). emqx_logger:warning([{sysmon, true}],
"[SYSMON] ~s~n~p~n~p", [WarnMsg, ProcInfo, PortInfo])).
-define(SYSMON, ?MODULE).
%% @doc Start system monitor %% @doc Start system monitor
-spec(start_link(Opts :: list(tuple())) -> {ok, pid()} | ignore | {error, term()}). -spec(start_link(Opts :: list(tuple()))
-> {ok, pid()} | ignore | {error, term()}).
start_link(Opts) -> start_link(Opts) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []). gen_server:start_link({local, ?SYSMON}, ?MODULE, [Opts], []).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server Callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([Opts]) -> init([Opts]) ->
erlang:system_monitor(self(), parse_opt(Opts)), erlang:system_monitor(self(), parse_opt(Opts)),
{ok, TRef} = timer:send_interval(timer:seconds(1), reset), {ok, start_timer(#state{events = []})}.
%%TODO: don't trace for performance issue.
%%{ok, TraceLog} = start_tracelog(proplists:get_value(logfile, Opts)), start_timer(State) ->
{ok, #state{tickref = TRef}}. State#state{timer = emqx_misc:start_timer(timer:seconds(2), tick)}.
parse_opt(Opts) -> parse_opt(Opts) ->
parse_opt(Opts, []). parse_opt(Opts, []).
@ -75,53 +78,53 @@ parse_opt([_Opt|Opts], Acc) ->
parse_opt(Opts, Acc). parse_opt(Opts, Acc).
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[SYSMON] Unexpected request: ~p", [Req]), emqx_logger:error("[SYSMON] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[SYSMON] Unexpected msg: ~p", [Msg]), emqx_logger:error("[SYSMON] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({monitor, Pid, long_gc, Info}, State) -> handle_info({monitor, Pid, long_gc, Info}, State) ->
suppress({long_gc, Pid}, fun() -> suppress({long_gc, Pid}, fun() ->
WarnMsg = io_lib:format("long_gc warning: pid = ~p, info: ~p", [Pid, Info]), WarnMsg = io_lib:format("long_gc warning: pid = ~p, info: ~p", [Pid, Info]),
?LOG(WarnMsg, procinfo(Pid)), ?LOG(WarnMsg, procinfo(Pid)),
publish(long_gc, WarnMsg) safe_publish(long_gc, WarnMsg)
end, State); end, State);
handle_info({monitor, Pid, long_schedule, Info}, State) when is_pid(Pid) -> handle_info({monitor, Pid, long_schedule, Info}, State) when is_pid(Pid) ->
suppress({long_schedule, Pid}, fun() -> suppress({long_schedule, Pid}, fun() ->
WarnMsg = io_lib:format("long_schedule warning: pid = ~p, info: ~p", [Pid, Info]), WarnMsg = io_lib:format("long_schedule warning: pid = ~p, info: ~p", [Pid, Info]),
?LOG(WarnMsg, procinfo(Pid)), ?LOG(WarnMsg, procinfo(Pid)),
publish(long_schedule, WarnMsg) safe_publish(long_schedule, WarnMsg)
end, State); end, State);
handle_info({monitor, Port, long_schedule, Info}, State) when is_port(Port) -> handle_info({monitor, Port, long_schedule, Info}, State) when is_port(Port) ->
suppress({long_schedule, Port}, fun() -> suppress({long_schedule, Port}, fun() ->
WarnMsg = io_lib:format("long_schedule warning: port = ~p, info: ~p", [Port, Info]), WarnMsg = io_lib:format("long_schedule warning: port = ~p, info: ~p", [Port, Info]),
?LOG(WarnMsg, erlang:port_info(Port)), ?LOG(WarnMsg, erlang:port_info(Port)),
publish(long_schedule, WarnMsg) safe_publish(long_schedule, WarnMsg)
end, State); end, State);
handle_info({monitor, Pid, large_heap, Info}, State) -> handle_info({monitor, Pid, large_heap, Info}, State) ->
suppress({large_heap, Pid}, fun() -> suppress({large_heap, Pid}, fun() ->
WarnMsg = io_lib:format("large_heap warning: pid = ~p, info: ~p", [Pid, Info]), WarnMsg = io_lib:format("large_heap warning: pid = ~p, info: ~p", [Pid, Info]),
?LOG(WarnMsg, procinfo(Pid)), ?LOG(WarnMsg, procinfo(Pid)),
publish(large_heap, WarnMsg) safe_publish(large_heap, WarnMsg)
end, State); end, State);
handle_info({monitor, SusPid, busy_port, Port}, State) -> handle_info({monitor, SusPid, busy_port, Port}, State) ->
suppress({busy_port, Port}, fun() -> suppress({busy_port, Port}, fun() ->
WarnMsg = io_lib:format("busy_port warning: suspid = ~p, port = ~p", [SusPid, Port]), WarnMsg = io_lib:format("busy_port warning: suspid = ~p, port = ~p", [SusPid, Port]),
?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)), ?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)),
publish(busy_port, WarnMsg) safe_publish(busy_port, WarnMsg)
end, State); end, State);
handle_info({monitor, SusPid, busy_dist_port, Port}, State) -> handle_info({monitor, SusPid, busy_dist_port, Port}, State) ->
suppress({busy_dist_port, Port}, fun() -> suppress({busy_dist_port, Port}, fun() ->
WarnMsg = io_lib:format("busy_dist_port warning: suspid = ~p, port = ~p", [SusPid, Port]), WarnMsg = io_lib:format("busy_dist_port warning: suspid = ~p, port = ~p", [SusPid, Port]),
?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)), ?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)),
publish(busy_dist_port, WarnMsg) safe_publish(busy_dist_port, WarnMsg)
end, State); end, State);
handle_info(reset, State) -> handle_info(reset, State) ->
@ -131,9 +134,8 @@ handle_info(Info, State) ->
lager:error("[SYSMON] Unexpected Info: ~p", [Info]), lager:error("[SYSMON] Unexpected Info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{tickref = TRef, tracelog = TraceLog}) -> terminate(_Reason, #state{timer = TRef}) ->
timer:cancel(TRef), emqx_misc:cancel_timer(TRef).
cancel_tracelog(TraceLog).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -152,20 +154,13 @@ procinfo(Pid) ->
{Info, GcInfo} -> Info ++ GcInfo {Info, GcInfo} -> Info ++ GcInfo
end. end.
publish(Sysmon, WarnMsg) -> safe_publish(Event, WarnMsg) ->
Msg = emqx_message:make(sysmon, topic(Sysmon), iolist_to_binary(WarnMsg)), try
emqx:publish(emqx_message:set_flag(sys, Msg)). Topic = emqx_topic:systop(lists:concat(['sysmon/', Event])),
Msg = emqx_message:make(?SYSMON, Topic, iolist_to_binary(WarnMsg)),
topic(Sysmon) -> emqx_broker:publish(emqx_message:set_flag(sys, Msg))
emqx_topic:systop(list_to_binary(lists:concat(['sysmon/', Sysmon]))). catch
_:Error ->
%% start_tracelog(undefined) -> emqx_logger:error("[SYSMON] Publish error: ~p", [Error])
%% {ok, undefined}; end.
%% start_tracelog(LogFile) ->
%% lager:trace_file(LogFile, [{sysmon, true}], info, ?LOG_FMT).
cancel_tracelog(undefined) ->
ok;
cancel_tracelog(TraceLog) ->
lager:stop_trace(TraceLog).

36
src/emqx_sys_sup.erl Normal file
View File

@ -0,0 +1,36 @@
%%%===================================================================
%%% Copyright (c) 2013-2018 EMQ Inc. 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_sys_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
Sys = {sys, {emqx_sys, start_link, []},
permanent, 5000, worker, [emqx_sys]},
{ok, Env} = emqx_config:get_env(sysmon),
Sysmon = {sys_mon, {emqx_sysmon, start_link, [Env]},
permanent, 5000, worker, [emqx_sys_mon]},
{ok, {{one_for_one, 10, 100}, [Sys, Sysmon]}}.

View File

@ -1,35 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Inc. 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_sysmon_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
{ok, Env} = emqx_config:get_env(sysmon),
Sysmon = {sysmon, {emqx_sysmon, start_link, [Env]},
permanent, 5000, worker, [emqx_sysmon]},
{ok, {{one_for_one, 10, 100}, [Sysmon]}}.

View File

@ -1,24 +1,25 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_tables). -module(emqx_tables).
-export([create/2]). -export([new/2]).
create(Tab, Opts) -> %% Create a named_table ets.
new(Tab, Opts) ->
case ets:info(Tab, name) of case ets:info(Tab, name) of
undefined -> undefined ->
ets:new(Tab, lists:usort([named_table | Opts])); ets:new(Tab, lists:usort([named_table | Opts]));

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_time). -module(emqx_time).

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_topic). -module(emqx_topic).
@ -143,11 +143,10 @@ word(<<"#">>) -> '#';
word(Bin) -> Bin. word(Bin) -> Bin.
%% @doc '$SYS' Topic. %% @doc '$SYS' Topic.
systop(Name) when is_atom(Name) -> systop(Name) when is_atom(Name); is_list(Name) ->
list_to_binary(lists:concat(["$SYS/brokers/", node(), "/", Name])); iolist_to_binary(lists:concat(["$SYS/brokers/", node(), "/", Name]));
systop(Name) when is_binary(Name) -> systop(Name) when is_binary(Name) ->
list_to_binary(["$SYS/brokers/", atom_to_list(node()), "/", Name]). iolist_to_binary(["$SYS/brokers/", atom_to_list(node()), "/", Name]).
-spec(feed_var(binary(), binary(), binary()) -> binary()). -spec(feed_var(binary(), binary(), binary()) -> binary()).
feed_var(Var, Val, Topic) -> feed_var(Var, Val, Topic) ->

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_tracer). -module(emqx_tracer).
@ -22,9 +22,7 @@
-export([start_link/0]). -export([start_link/0]).
-export([trace/3]). -export([start_trace/2, lookup_traces/0, stop_trace/1]).
-export([start_trace/2, stop_trace/1, all_traces/0]).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@ -36,29 +34,15 @@
-define(OPTIONS, [{formatter_config, [time, " [",severity,"] ", message, "\n"]}]). -define(OPTIONS, [{formatter_config, [time, " [",severity,"] ", message, "\n"]}]).
-define(TRACER, ?MODULE).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Start the tracer %% Start the tracer
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(start_link() -> {ok, pid()}). -spec(start_link() -> {ok, pid()} | ignore | {error, term()}).
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?TRACER}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% Trace
%%--------------------------------------------------------------------
trace(publish, From, _Msg) when is_atom(From) ->
%% Dont' trace '$SYS' publish
ignore;
trace(publish, #client{client_id = ClientId, username = Username},
#message{topic = Topic, payload = Payload}) ->
emqx_log:info([{client, ClientId}, {topic, Topic}],
"~s/~s PUBLISH to ~s: ~p", [ClientId, Username, Topic, Payload]);
trace(publish, From, #message{topic = Topic, payload = Payload})
when is_binary(From); is_list(From) ->
emqx_log:info([{client, From}, {topic, Topic}],
"~s PUBLISH to ~s: ~p", [From, Topic, Payload]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Start/Stop Trace %% Start/Stop Trace
@ -68,11 +52,11 @@ trace(publish, From, #message{topic = Topic, payload = Payload})
-spec(start_trace(trace_who(), string()) -> ok | {error, term()}). -spec(start_trace(trace_who(), string()) -> ok | {error, term()}).
start_trace({client, ClientId}, LogFile) -> start_trace({client, ClientId}, LogFile) ->
start_trace({start_trace, {client, ClientId}, LogFile}); start_trace({start_trace, {client, ClientId}, LogFile});
start_trace({topic, Topic}, LogFile) -> start_trace({topic, Topic}, LogFile) ->
start_trace({start_trace, {topic, Topic}, LogFile}). start_trace({start_trace, {topic, Topic}, LogFile}).
start_trace(Req) -> gen_server:call(?MODULE, Req, infinity). start_trace(Req) ->
gen_server:call(?MODULE, Req, infinity).
%% @doc Stop tracing client or topic. %% @doc Stop tracing client or topic.
-spec(stop_trace(trace_who()) -> ok | {error, term()}). -spec(stop_trace(trace_who()) -> ok | {error, term()}).
@ -81,25 +65,31 @@ stop_trace({client, ClientId}) ->
stop_trace({topic, Topic}) -> stop_trace({topic, Topic}) ->
gen_server:call(?MODULE, {stop_trace, {topic, Topic}}). gen_server:call(?MODULE, {stop_trace, {topic, Topic}}).
%% @doc Lookup all traces. %% @doc Lookup all traces
-spec(all_traces() -> [{Who :: trace_who(), LogFile :: string()}]). -spec(lookup_traces() -> [{Who :: trace_who(), LogFile :: string()}]).
all_traces() -> lookup_traces() ->
gen_server:call(?MODULE, all_traces). gen_server:call(?TRACER, lookup_traces).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
{ok, #state{level = emqx_config:get_env(trace_level, debug), traces = #{}}}. Level = emqx_config:get_env(trace_level, debug),
{ok, #state{level = Level, traces = #{}}}.
handle_call({start_trace, Who, LogFile}, _From, State = #state{level = Level, traces = Traces}) -> handle_call({start_trace, Who, LogFile}, _From,
case lager:trace_file(LogFile, [Who], Level, ?OPTIONS) of State = #state{level = Level, traces = Traces}) ->
case catch lager:trace_file(LogFile, [Who], Level, ?OPTIONS) of
{ok, exists} -> {ok, exists} ->
{reply, {error, existed}, State}; {reply, {error, alread_existed}, State};
{ok, Trace} -> {ok, Trace} ->
{reply, ok, State#state{traces = maps:put(Who, {Trace, LogFile}, Traces)}}; {reply, ok, State#state{traces = maps:put(Who, {Trace, LogFile}, Traces)}};
{error, Error} -> {error, Reason} ->
emqx_logger:error("[TRACER] trace error: ~p", [Reason]),
{reply, {error, Reason}, State};
{'EXIT', Error} ->
emqx_logger:error("[TRACER] trace exit: ~p", [Error]),
{reply, {error, Error}, State} {reply, {error, Error}, State}
end; end;
@ -112,23 +102,23 @@ handle_call({stop_trace, Who}, _From, State = #state{traces = Traces}) ->
end, end,
{reply, ok, State#state{traces = maps:remove(Who, Traces)}}; {reply, ok, State#state{traces = maps:remove(Who, Traces)}};
error -> error ->
{reply, {error, not_found}, State} {reply, {error, trance_not_found}, State}
end; end;
handle_call(all_traces, _From, State = #state{traces = Traces}) -> handle_call(lookup_traces, _From, State = #state{traces = Traces}) ->
{reply, [{Who, LogFile} || {Who, {_Trace, LogFile}} {reply, [{Who, LogFile} || {Who, {_Trace, LogFile}}
<- maps:to_list(Traces)], State}; <- maps:to_list(Traces)], State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
emqx_log:error("[TRACE] Unexpected Call: ~p", [Req]), emqx_logger:error("[TRACER] Unexpected request: ~p", [Req]),
{reply, ignore, State}. {reply, ignore, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
emqx_log:error("[TRACE] Unexpected Cast: ~p", [Msg]), emqx_logger:error("[TRACER] Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info(Info, State) -> handle_info(Info, State) ->
emqx_log:error("[TRACE] Unexpected Info: ~p", [Info]), emqx_logger:error("[TRACER] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->

View File

@ -1,32 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Inc. 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_tracer_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
Tracer = {tracer, {emqx_tracer, start_link, []},
permanent, 5000, worker, [emqx_tracer]},
{ok, {{one_for_one, 10, 3600}, [Tracer]}}.

View File

@ -1,18 +1,18 @@
%%-------------------------------------------------------------------- %%%===================================================================
%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. %%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved.
%% %%%
%% Licensed under the Apache License, Version 2.0 (the "License"); %%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at %%% You may obtain a copy of the License at
%% %%%
%% http://www.apache.org/licenses/LICENSE-2.0 %%% http://www.apache.org/licenses/LICENSE-2.0
%% %%%
%% Unless required by applicable law or agreed to in writing, software %%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, %%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% 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_trie). -module(emqx_trie).
@ -24,39 +24,39 @@
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}). -copy_mnesia({mnesia, [copy]}).
%% Trie API %% Trie APIs
-export([insert/1, match/1, lookup/1, delete/1]). -export([insert/1, match/1, lookup/1, delete/1]).
%% Tables %% Mnesia tables
-define(TRIE, emqx_trie). -define(TRIE, emqx_trie).
-define(TRIE_NODE, emqx_trie_node). -define(TRIE_NODE, emqx_trie_node).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Mnesia Bootstrap %% Mnesia bootstrap
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Create or replicate trie tables. %% @doc Create or replicate trie tables.
-spec(mnesia(boot | copy) -> ok). -spec(mnesia(boot | copy) -> ok).
mnesia(boot) -> mnesia(boot) ->
%% Trie Table %% Trie table
ok = ekka_mnesia:create_table(?TRIE, [ ok = ekka_mnesia:create_table(?TRIE, [
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, trie}, {record_name, trie},
{attributes, record_info(fields, trie)}]), {attributes, record_info(fields, trie)}]),
%% Trie Node Table %% Trie node table
ok = ekka_mnesia:create_table(?TRIE_NODE, [ ok = ekka_mnesia:create_table(?TRIE_NODE, [
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, trie_node}, {record_name, trie_node},
{attributes, record_info(fields, trie_node)}]); {attributes, record_info(fields, trie_node)}]);
mnesia(copy) -> mnesia(copy) ->
%% Copy Trie Table %% Copy trie table
ok = ekka_mnesia:copy_table(?TRIE), ok = ekka_mnesia:copy_table(?TRIE),
%% Copy Trie Node Table %% Copy trie_node table
ok = ekka_mnesia:copy_table(?TRIE_NODE). ok = ekka_mnesia:copy_table(?TRIE_NODE).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Trie API %% Trie APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Insert a topic into the trie %% @doc Insert a topic into the trie

View File

@ -26,7 +26,7 @@
-record(wsocket_state, {peername, client_pid, max_packet_size, parser}). -record(wsocket_state, {peername, client_pid, max_packet_size, parser}).
-define(WSLOG(Level, Format, Args, State), -define(WSLOG(Level, Format, Args, State),
emqx_log:Level("WsClient(~s): " ++ Format, emqx_logger:Level("WsClient(~s): " ++ Format,
[esockd_net:format(State#wsocket_state.peername) | Args])). [esockd_net:format(State#wsocket_state.peername) | Args])).
@ -38,7 +38,7 @@ handle_request(Req) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_request('GET', "/mqtt", Req) -> handle_request('GET', "/mqtt", Req) ->
emqx_log:debug("WebSocket Connection from: ~s", [Req:get(peer)]), emqx_logger:debug("WebSocket Connection from: ~s", [Req:get(peer)]),
Upgrade = Req:get_header_value("Upgrade"), Upgrade = Req:get_header_value("Upgrade"),
Proto = check_protocol_header(Req), Proto = check_protocol_header(Req),
case {is_websocket(Upgrade), Proto} of case {is_websocket(Upgrade), Proto} of
@ -56,19 +56,19 @@ handle_request('GET', "/mqtt", Req) ->
max_packet_size = PacketSize, max_packet_size = PacketSize,
client_pid = ClientPid}); client_pid = ClientPid});
{error, Reason} -> {error, Reason} ->
emqx_log:error("Get peername with error ~s", [Reason]), emqx_logger:error("Get peername with error ~s", [Reason]),
Req:respond({400, [], <<"Bad Request">>}) Req:respond({400, [], <<"Bad Request">>})
end; end;
{false, _} -> {false, _} ->
emqx_log:error("Not WebSocket: Upgrade = ~s", [Upgrade]), emqx_logger:error("Not WebSocket: Upgrade = ~s", [Upgrade]),
Req:respond({400, [], <<"Bad Request">>}); Req:respond({400, [], <<"Bad Request">>});
{_, Proto} -> {_, Proto} ->
emqx_log:error("WebSocket with error Protocol: ~s", [Proto]), emqx_logger:error("WebSocket with error Protocol: ~s", [Proto]),
Req:respond({400, [], <<"Bad WebSocket Protocol">>}) Req:respond({400, [], <<"Bad WebSocket Protocol">>})
end; end;
handle_request(Method, Path, Req) -> handle_request(Method, Path, Req) ->
emqx_log:error("Unexpected WS Request: ~s ~s", [Method, Path]), emqx_logger:error("Unexpected WS Request: ~s ~s", [Method, Path]),
Req:not_found(). Req:not_found().
is_websocket(Upgrade) -> is_websocket(Upgrade) ->

View File

@ -50,7 +50,7 @@
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]).
-define(WSLOG(Level, Format, Args, State), -define(WSLOG(Level, Format, Args, State),
emqx_log:Level("WsClient(~s): " ++ Format, emqx_logger:Level("WsClient(~s): " ++ Format,
[esockd_net:format(State#wsclient_state.peername) | Args])). [esockd_net:format(State#wsclient_state.peername) | Args])).
%% @doc Start WebSocket Client. %% @doc Start WebSocket Client.
@ -140,7 +140,7 @@ handle_call(Req, _From, State) ->
reply({error, unexpected_request}, State). reply({error, unexpected_request}, State).
handle_cast({received, Packet}, State = #wsclient_state{proto_state = ProtoState}) -> handle_cast({received, Packet}, State = #wsclient_state{proto_state = ProtoState}) ->
emqx_mqtt_metrics:received(Packet), emqx_metrics:received(Packet),
case emqx_protocol:received(Packet, ProtoState) of case emqx_protocol:received(Packet, ProtoState) of
{ok, ProtoState1} -> {ok, ProtoState1} ->
{noreply, gc(State#wsclient_state{proto_state = ProtoState1}), hibernate}; {noreply, gc(State#wsclient_state{proto_state = ProtoState1}), hibernate};
@ -290,7 +290,7 @@ emit_stats(undefined, State) ->
State; State;
emit_stats(ClientId, State) -> emit_stats(ClientId, State) ->
{reply, Stats, _, _} = handle_call(stats, undefined, State), {reply, Stats, _, _} = handle_call(stats, undefined, State),
emqx_stats:set_client_stats(ClientId, Stats), emqx_cm:set_client_stats(ClientId, Stats),
State. State.
wsock_stats(#wsclient_state{connection = Conn}) -> wsock_stats(#wsclient_state{connection = Conn}) ->