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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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.
%%%===================================================================
%%--------------------------------------------------------------------
%% Banner
@ -154,7 +154,10 @@
%% Route
%%--------------------------------------------------------------------
-record(route, { topic :: topic(), dest }).
-record(route,
{ topic :: topic(),
dest :: node() | {binary(), node()}
}).
-type(route() :: #route{}).

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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).
@ -29,7 +29,7 @@
-export([topics/0, subscriptions/1, subscribers/1, subscribed/2]).
%% Get/Set suboptions
-export([getopts/2, setopts/3]).
-export([get_subopts/2, set_subopts/3]).
%% Hooks API
-export([hook/4, hook/3, unhook/2, run_hooks/2, run_hooks/3]).
@ -47,12 +47,17 @@
%%--------------------------------------------------------------------
%% @doc Start emqx application
-spec(start() -> ok | {error, term()}).
start() -> application:start(?APP).
-spec(start() -> {ok, list(atom())} | {error, term()}).
start() ->
%% Check OS
%% Check VM
%% Check Mnesia
application:ensure_all_started(?APP).
%% @doc Stop emqx application.
-spec(stop() -> ok | {error, term()}).
stop() -> application:stop(?APP).
stop() ->
application:stop(?APP).
%% @doc Is emqx running?
-spec(is_running(node()) -> boolean()).
@ -96,13 +101,13 @@ unsubscribe(Topic, Subscriber) ->
%% PubSub management API
%%--------------------------------------------------------------------
-spec(getopts(topic() | string(), subscriber()) -> [suboption()]).
getopts(Topic, Subscriber) ->
emqx_broker:getopts(iolist_to_binary(Topic), list_to_subid(Subscriber)).
-spec(get_subopts(topic() | string(), subscriber()) -> [suboption()]).
get_subopts(Topic, Subscriber) ->
emqx_broker:get_subopts(iolist_to_binary(Topic), list_to_subid(Subscriber)).
-spec(setopts(topic() | string(), subscriber(), [suboption()]) -> ok).
setopts(Topic, Subscriber, Options) when is_list(Options) ->
emqx_broker:setopts(iolist_to_binary(Topic), list_to_subid(Subscriber), Options).
-spec(set_subopts(topic() | string(), subscriber(), [suboption()]) -> ok).
set_subopts(Topic, Subscriber, Options) when is_list(Options) ->
emqx_broker:set_subopts(iolist_to_binary(Topic), list_to_subid(Subscriber), Options).
-spec(topics() -> list(topic())).
topics() -> emqx_router:topics().
@ -165,7 +170,7 @@ shutdown() ->
shutdown(normal).
shutdown(Reason) ->
emqx_log:error("EMQ shutdown for ~s", [Reason]),
emqx_logger:error("EMQ shutdown for ~s", [Reason]),
emqx_plugins:unload(),
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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_access_control).
@ -28,10 +28,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(TAB, ?MODULE).
-define(SERVER, ?MODULE).
-define(TAB, access_control).
-type(password() :: undefined | binary()).
-record(state, {}).
@ -122,7 +121,7 @@ stop() ->
%%--------------------------------------------------------------------
init([]) ->
_ = emqx_tables:create(?TAB, [set, protected, {read_concurrency, true}]),
_ = emqx_tables:new(?TAB, [set, protected, {read_concurrency, true}]),
{ok, #state{}}.
handle_call({register_mod, Type, Mod, Opts, Seq}, _From, State) ->
@ -157,15 +156,15 @@ handle_call(stop, _From, State) ->
{stop, normal, ok, 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}.
handle_cast(Msg, State) ->
emqx_log:error("[AccessControl] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[AccessControl] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info(Info, State) ->
emqx_log:error("[AccessControl] Unexpected info: ~p", [Info]),
emqx_logger:error("[AccessControl] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_acl_internal).
@ -25,7 +25,7 @@
%% ACL callbacks
-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}).
@ -48,7 +48,7 @@ all_rules() ->
%% @doc Init internal ACL
-spec(init([File :: string()]) -> {ok, State :: any()}).
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})}.
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)},
{ts, emqx_time:now_secs(TS)}]) of
{'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 ->
emqx_broker:publish(alarm_msg(alert, AlarmId, JSON))
end,
@ -101,7 +101,7 @@ handle_event({set_alarm, Alarm = #alarm{id = AlarmId,
handle_event({clear_alarm, AlarmId}, Alarms) ->
case catch emqx_json:encode([{id, AlarmId}, {ts, emqx_time:now_secs()}]) of
{'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 ->
emqx_broker:publish(alarm_msg(clear, AlarmId, JSON))
end,

View File

@ -34,7 +34,6 @@ start(_Type, _Args) ->
ekka:start(),
{ok, Sup} = emqx_sup:start_link(),
%%TODO: fixme later
emqx_mqtt_metrics:init(),
ok = register_acl_mod(),
emqx_modules:load(),
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
{ok, Hexstring} -> pbkdf2:to_hex(Hexstring);
{error, Error} ->
emqx_log:error("[AuthMod] PasswdHash with pbkdf2 error:~p", [Error]), <<>>
emqx_logger:error("[AuthMod] PasswdHash with pbkdf2 error:~p", [Error]), <<>>
end;
passwd_hash(bcrypt, {Salt, Password}) ->
case bcrypt:hashpw(Password, Salt) of
{ok, HashPassword} -> list_to_binary(HashPassword);
{error, Error}->
emqx_log:error("[AuthMod] PasswdHash with bcrypt error:~p", [Error]), <<>>
emqx_logger:error("[AuthMod] PasswdHash with bcrypt error:~p", [Error]), <<>>
end.
hexstring(<<X:128/big-unsigned-integer>>) ->

View File

@ -1,72 +1,138 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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.
%%%===================================================================
%% Banned an IP Address, ClientId?
-module(emqx_banned).
-behaviour(gen_server).
-include("emqx.hrl").
%% Mnesia bootstrap
-export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}).
%% API
-export([start_link/0]).
-export([check/1]).
-export([add/1, del/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(TAB, ?MODULE).
-define(SERVER, ?MODULE).
-record(state, {}).
-type(key() :: {client_id, client_id()} |
{ipaddr, inet:ip_address()} |
{username, username()}).
%%%===================================================================
%%% API
%%%===================================================================
-record(state, {expiry_timer}).
%% @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()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
-spec(check(client()) -> boolean()).
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([]) ->
{ok, #state{}}.
emqx_timer:seed(),
{ok, ensure_expiry_timer(#state{})}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_call(Req, _From, State) ->
emqx_logger:error("[BANNED] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast(_Msg, State) ->
handle_cast(Msg, State) ->
emqx_logger:error("[BANNED] Unexpected msg: ~p", [Msg]),
{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}.
terminate(_Reason, _State) ->
ok.
terminate(_Reason, #state{expiry_timer = Timer}) ->
emqx_misc:cancel_timer(Timer).
code_change(_OldVsn, State, _Extra) ->
{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]).
handle_call(Req, _From, State) ->
emqx_log:error("[Bridge] Unexpected request: ~p", [Req]),
emqx_logger:error("[Bridge] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast(Msg, State) ->
emqx_log:error("[Bridge] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[Bridge] Unexpected msg: ~p", [Msg]),
{noreply, State}.
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};
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),
{noreply, State#state{status = down}, hibernate};
@ -126,7 +126,7 @@ handle_info({nodeup, Node}, State = #state{node = Node}) ->
%% TODO: Really fast??
case emqx:is_running(Node) of
true ->
emqx_log:warning("[Bridge] Node up: ~s", [Node]),
emqx_logger:warning("[Bridge] Node up: ~s", [Node]),
{noreply, dequeue(State#state{status = up})};
false ->
self() ! {nodedown, Node},
@ -149,7 +149,7 @@ handle_info({'EXIT', _Pid, normal}, State) ->
{noreply, State};
handle_info(Info, State) ->
emqx_log:error("[Bridge] Unexpected info: ~p", [Info]),
emqx_logger:error("[Bridge] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{pool = Pool, id = Id}) ->

View File

@ -1,24 +1,27 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_bridge_sup_sup).
-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]).
@ -32,27 +35,28 @@ start_link() ->
%%--------------------------------------------------------------------
%% @doc List all bridges
-spec(bridges() -> [{node(), binary(), pid()}]).
-spec(bridges() -> [{node(), topic(), pid()}]).
bridges() ->
[{Node, Topic, Pid} || {?CHILD_ID(Node, Topic), Pid, supervisor, _}
<- supervisor:which_children(?MODULE)].
%% @doc Start a bridge
-spec(start_bridge(atom(), binary()) -> {ok, pid()} | {error, term()}).
start_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) ->
-spec(start_bridge(node(), topic()) -> {ok, pid()} | {error, term()}).
start_bridge(Node, Topic) when is_atom(Node), is_binary(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() ->
{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),
Options1 = emqx_misc:merge_opts(BridgeEnv, Options),
supervisor:start_child(?MODULE, bridge_spec(Node, Topic, Options1)).
%% @doc Stop a bridge
-spec(stop_bridge(atom(), binary()) -> {ok, pid()} | ok).
stop_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) ->
-spec(stop_bridge(node(), topic()) -> ok | {error, term()}).
stop_bridge(Node, Topic) when is_atom(Node), is_binary(Topic) ->
ChildId = ?CHILD_ID(Node, Topic),
case supervisor:terminate_child(?MODULE, ChildId) of
ok -> supervisor:delete_child(?MODULE, ChildId);

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_broker).
@ -53,9 +53,11 @@
%% 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) ->
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
@ -101,15 +103,32 @@ unsubscribe(Topic, Subscriber, Timeout) ->
-spec(publish(message()) -> delivery() | stopped).
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
{ok, Msg1 = #message{topic = Topic}} ->
publish(Topic, Msg1);
{stop, Msg1} ->
emqx_log:warning("Stop publishing: ~s", [emqx_message:format(Msg1)]),
emqx_logger:warning("Stop publishing: ~s", [emqx_message:format(Msg1)]),
stopped
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) ->
route(aggre(emqx_router:match_routes(Topic)), delivery(Msg)).
@ -131,16 +150,14 @@ route(Routes, Delivery) ->
aggre([]) ->
[];
aggre([{To, Dest}]) ->
aggre([#route{topic = To, dest = Dest}]) ->
[{To, Dest}];
aggre(Routes) ->
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, {Group, _Node}}, Acc) ->
lists:usort([{To, Group} | Acc]);
({To, {Cluster, Group, _Node}}, Acc) ->
lists:usort([{To, {Cluster, Group}} | Acc])
(#route{topic = To, dest = {Group, _Node}}, Acc) ->
lists:usort([{To, Group} | Acc])
end, [], Routes).
%% @doc Forward message to another node.
@ -148,7 +165,7 @@ forward(Node, To, Delivery) ->
%% rpc:call to ensure the delivery, but the latency:(
case emqx_rpc:call(Node, ?BROKER, dispatch, [To, Delivery]) of
{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;
Delivery1 -> Delivery1
end.
@ -261,7 +278,7 @@ handle_call({set_subopts, Topic, Subscriber, Opts}, _From, State) ->
end;
handle_call(Request, _From, State) ->
emqx_log:error("[Broker] Unexpected request: ~p", [Request]),
emqx_logger:error("[Broker] Unexpected request: ~p", [Request]),
{reply, ignore, State}.
handle_cast({From, {subscribe, Topic, Subscriber, Options}}, State) ->
@ -292,7 +309,7 @@ handle_cast({From, {unsubscribe, Topic, Subscriber}}, State) ->
{noreply, State};
handle_cast(Msg, State) ->
emqx_log:error("[Broker] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[Broker] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info({'DOWN', _MRef, process, SubPid, _Reason},
@ -321,7 +338,7 @@ handle_info({'DOWN', _MRef, process, SubPid, _Reason},
{noreply, demonitor_subscriber(SubPid, State)};
handle_info(Info, State) ->
emqx_log:error("[Broker] Unexpected info: ~p", [Info]),
emqx_logger:error("[Broker] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{pool = Pool, id = Id}) ->

View File

@ -1,66 +1,79 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_broker_helper).
-behaviour(gen_server).
-export([start_link/1]).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-define(HELPER, ?MODULE).
-record(state, {stats_fun, stats_timer}).
-record(state, {}).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
-spec(start_link(fun()) -> {ok, pid()} | ignore | {error, any()}).
start_link(StatsFun) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [StatsFun], []).
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link() ->
gen_server:start_link({local, ?HELPER}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
init([StatsFun]) ->
{ok, TRef} = timer:send_interval(timer:seconds(1), stats),
{ok, #state{stats_fun = StatsFun, stats_timer = TRef}}.
init([]) ->
emqx_stats:update_interval(broker_stats, stats_fun()),
{ok, #state{}, hibernate}.
handle_call(Req, _From, State) ->
emqx_log:error("[BrokerHelper] Unexpected request: ~p", [Req]),
emqx_logger:error("[BrokerHelper] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast(Msg, State) ->
emqx_log:error("[BrokerHelper] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[BrokerHelper] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info(stats, State = #state{stats_fun = StatsFun}) ->
StatsFun(), {noreply, State, hibernate};
handle_info(Info, State) ->
emqx_log:error("[BrokerHelper] Unexpected info: ~p", [Info]),
emqx_logger:error("[BrokerHelper] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{stats_timer = TRef}) ->
timer:cancel(TRef).
terminate(_Reason, #state{}) ->
emqx_stats:cancel_update(broker_stats).
code_change(_OldVsn, State, _Extra) ->
{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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_broker_sup).
@ -22,7 +22,11 @@
-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() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
@ -33,24 +37,23 @@ start_link() ->
init([]) ->
%% Create the pubsub tables
lists:foreach(fun create_tab/1,
[subscription, subscriber, suboption]),
foreach(fun create_tab/1, [subscription, subscriber, suboption]),
%% Shared subscription
Shared = {shared_sub, {emqx_shared_sub, start_link, []},
SharedSub = {shared_sub, {emqx_shared_sub, start_link, []},
permanent, 5000, worker, [emqx_shared_sub]},
%% 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]},
%% Broker pool
PoolArgs = [broker, hash, emqx_sys:schedulers() * 2,
PoolArgs = [broker, hash, emqx_vm:schedulers() * 2,
{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
@ -58,27 +61,15 @@ init([]) ->
create_tab(suboption) ->
%% 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) ->
%% Subscriber: Topic -> Sub1, Sub2, Sub3, ..., SubN
%% 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) ->
%% Subscription: Sub -> Topic1, Topic2, Topic3, ..., TopicN
%% bag: o(n) insert
emqx_tables:create(emqx_subscription, [public, bag | ?CONCURRENCY_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.
emqx_tables:new(emqx_subscription, [bag | ?TAB_OPTS]).

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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).
@ -22,92 +22,145 @@
-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,
terminate/2, code_change/3]).
-record(state, {stats_fun, stats_timer, monitors}).
-record(state, {client_pmon}).
-define(SERVER, ?MODULE).
-define(CM, ?MODULE).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
%% ETS Tables.
-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()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
gen_server:start_link({local, ?CM}, ?MODULE, [], []).
%% @doc Lookup ClientPid by ClientId
-spec(lookup(client_id()) -> pid() | undefined).
lookup(ClientId) when is_binary(ClientId) ->
try ets:lookup_element(client, ClientId, 2)
%% @doc Lookup a client.
-spec(lookup_client(client_id()) -> list({client_id(), pid()})).
lookup_client(ClientId) when is_binary(ClientId) ->
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
error:badarg -> undefined
error:badarg -> []
end.
%% @doc Register a clientId
-spec(reg(client_id()) -> ok).
reg(ClientId) ->
gen_server:cast(?SERVER, {reg, ClientId, self()}).
%% @doc Unregister a client.
-spec(unregister_client(client_id() | {client_id(), pid()}) -> ok).
unregister_client(ClientId) when is_binary(ClientId) ->
unregister_client({ClientId, self()});
%% @doc Unregister clientId with pid.
-spec(unreg(client_id()) -> ok).
unreg(ClientId) ->
gen_server:cast(?SERVER, {unreg, ClientId, self()}).
unregister_client({ClientId, ClientPid}) when is_binary(ClientId),
is_pid(ClientPid) ->
ets:delete(?CLIENT_STATS, {ClientId, ClientPid}),
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
%%--------------------------------------------------------------------
init([]) ->
_ = emqx_tables:create(client, [public, set, {keypos, 2},
{read_concurrency, true},
{write_concurrency, true}]),
_ = emqx_tables:create(client_attrs, [public, set,
{write_concurrency, true}]),
{ok, #state{monitors = dict:new()}}.
TabOpts = [public, set, {write_concurrency, true}],
_ = emqx_tables:new(?CLIENT, [{read_concurrency, true} | TabOpts]),
_ = emqx_tables:new(?CLIENT_ATTRS, TabOpts),
_ = emqx_tables:new(?CLIENT_STATS, TabOpts),
ok = emqx_stats:update_interval(cm_stats, fun update_client_stats/0),
{ok, #state{client_pmon = emqx_pmon:new()}}.
handle_call(Req, _From, State) ->
emqx_log:error("[CM] Unexpected request: ~p", [Req]),
emqx_logger:error("[CM] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast({reg, ClientId, Pid}, State) ->
_ = ets:insert(client, {ClientId, Pid}),
{noreply, monitor_client(ClientId, Pid, State)};
handle_cast({notify, {registered, ClientId, Pid}},
State = #state{client_pmon = PMon}) ->
{noreply, State#state{client_pmon = PMon:monitor(Pid, ClientId)}};
handle_cast({unreg, ClientId, Pid}, State) ->
case lookup(ClientId) of
Pid -> remove_client({ClientId, Pid});
_ -> ok
end,
{noreply, State};
handle_cast({notify, {unregistered, _ClientId, Pid}},
State = #state{client_pmon = PMon}) ->
{noreply, State#state{client_pmon = PMon:demonitor(Pid)}};
handle_cast(Msg, State) ->
emqx_log:error("[CM] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[CM] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
case dict:find(MRef, State#state.monitors) of
{ok, ClientId} ->
case lookup(ClientId) of
DownPid -> remove_client({ClientId, DownPid});
_ -> ok
end,
{noreply, erase_monitor(MRef, State)};
error ->
emqx_log:error("[CM] down client ~p not found", [DownPid]),
{noreply, State}
handle_info({'DOWN', _MRef, process, DownPid, _Reason},
State = #state{client_pmon = PMon}) ->
case PMon:find(DownPid) of
undefined ->
{noreply, State};
ClientId ->
unregister_client({ClientId, DownPid}),
{noreply, State#state{client_pmon = PMon:erase(DownPid)}}
end;
handle_info(Info, State) ->
emqx_log:error("[CM] Unexpected info: ~p", [Info]),
emqx_logger:error("[CM] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, _State = #state{stats_timer = TRef}) ->
timer:cancel(TRef).
terminate(_Reason, _State = #state{}) ->
emqx_stats:cancel_update(cm_stats).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
@ -116,16 +169,10 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions
%%--------------------------------------------------------------------
remove_client(Client) ->
ets:delete_object(client, Client),
ets:delete(client_stats, Client),
ets:delete(client_attrs, Client).
monitor_client(ClientId, Pid, State = #state{monitors = Monitors}) ->
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)}.
update_client_stats() ->
case ets:info(?CLIENT, size) of
undefined -> ok;
Size ->
emqx_stats:setstat('clients/count', 'clients/max', Size)
end.

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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_sup).
@ -26,9 +26,7 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
Stats = {emqx_cm_stats, {emqx_cm_stats, start_link, []},
permanent, 5000, worker, [emqx_cm_stats]},
CM = {emqx_cm, {emqx_cm, start_link, []},
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(LOG(Level, Format, Args, State),
emqx_log:Level("Client(~s): " ++ Format,
emqx_logger:Level("Client(~s): " ++ Format,
[esockd_net:format(State#state.peername) | Args])).
start_link(Conn, Env) ->
@ -316,7 +316,7 @@ received(Bytes, State = #state{parser = Parser,
{more, NewParser} ->
{noreply, run_socket(State#state{parser = NewParser}), IdleTimeout};
{ok, Packet, Rest} ->
emqx_mqtt_metrics:received(Packet),
emqx_metrics:received(Packet),
case emqx_protocol:received(Packet, ProtoState) of
{ok, ProtoState1} ->
received(Rest, State#state{parser = emqx_parser:initial_state(PacketSize),
@ -371,7 +371,7 @@ emit_stats(undefined, State) ->
State;
emit_stats(ClientId, State) ->
{reply, Stats, _, _} = handle_call(stats, undefined, State),
emqx_stats:set_client_stats(ClientId, Stats),
emqx_cm:set_client_stats(ClientId, Stats),
State.
sock_stats(#state{connection = Conn}) ->

View File

@ -1,26 +1,26 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_ctl).
-behaviour(gen_server).
%% API Function Exports
-export([start_link/0, register_cmd/2, register_cmd/3, unregister_cmd/1,
lookup/1, run/1]).
-export([start_link/0]).
-export([register_command/2, register_command/3, unregister_command/1]).
-export([run_command/2, lookup_command/1]).
%% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@ -28,9 +28,10 @@
-record(state, {seq = 0}).
-define(SERVER, ?MODULE).
-type(cmd() :: atom()).
-define(TAB, ?MODULE).
-define(SERVER, ?MODULE).
-define(TAB, emqx_command).
%%--------------------------------------------------------------------
%% API
@ -40,47 +41,44 @@ start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% @doc Register a command
-spec(register_cmd(atom(), {module(), atom()}) -> ok).
register_cmd(Cmd, MF) ->
register_cmd(Cmd, MF, []).
-spec(register_command(cmd(), {module(), atom()}) -> ok).
register_command(Cmd, MF) when is_atom(Cmd) ->
register_command(Cmd, MF, []).
%% @doc Register a command with opts
-spec(register_cmd(atom(), {module(), atom()}, list()) -> ok).
register_cmd(Cmd, MF, Opts) ->
cast({register_cmd, Cmd, MF, Opts}).
%% @doc Register a command with options
-spec(register_command(cmd(), {module(), atom()}, list()) -> ok).
register_command(Cmd, MF, Opts) when is_atom(Cmd) ->
cast({register_command, Cmd, MF, Opts}).
%% @doc Unregister a command
-spec(unregister_cmd(atom()) -> ok).
unregister_cmd(Cmd) ->
cast({unregister_cmd, Cmd}).
-spec(unregister_command(cmd()) -> ok).
unregister_command(Cmd) when is_atom(Cmd) ->
cast({unregister_command, Cmd}).
cast(Msg) -> gen_server:cast(?SERVER, Msg).
%% @doc Run a command
-spec(run([string()]) -> any()).
run([]) -> usage(), ok;
run(["help"]) -> usage(), ok;
run([CmdS|Args]) ->
case lookup(list_to_atom(CmdS)) of
-spec(run_command(cmd(), [string()]) -> ok | {error, term()}).
run_command(help, []) ->
usage();
run_command(Cmd, Args) when is_atom(Cmd) ->
case lookup_command(Cmd) of
[{Mod, Fun}] ->
try Mod:Fun(Args) of
_ -> ok
catch
_:Reason ->
io:format("Reason:~p, get_stacktrace:~p~n",
emqx_logger:error("[CTL] Cmd error:~p, stacktrace:~p",
[Reason, erlang:get_stacktrace()]),
{error, Reason}
end;
[] ->
usage(),
{error, cmd_not_found}
usage(), {error, cmd_not_found}
end.
%% @doc Lookup a command
-spec(lookup(atom()) -> [{module(), atom()}]).
lookup(Cmd) ->
-spec(lookup_command(cmd()) -> [{module(), atom()}]).
lookup_command(Cmd) when is_atom(Cmd) ->
case ets:match(?TAB, {{'_', Cmd}, '$1', '_'}) of
[El] -> El;
[] -> []
@ -97,30 +95,32 @@ usage() ->
%%--------------------------------------------------------------------
init([]) ->
ets:new(?TAB, [ordered_set, named_table, protected]),
_ = emqx_tables:new(?TAB, [ordered_set, protected]),
{ok, #state{seq = 0}}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_call(Req, _From, 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
[] ->
ets:insert(?TAB, {{Seq, Cmd}, MF, Opts});
[] -> ets:insert(?TAB, {{Seq, Cmd}, MF, Opts});
[[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})
end,
noreply(next_seq(State));
handle_cast({unregister_cmd, Cmd}, State) ->
handle_cast({unregister_command, Cmd}, State) ->
ets:match_delete(?TAB, {{'_', Cmd}, '_', '_'}),
noreply(State);
handle_cast(_Msg, State) ->
handle_cast(Msg, State) ->
emqx_logger:error("Unexpected msg: ~p", [Msg]),
noreply(State).
handle_info(_Info, State) ->
handle_info(Info, State) ->
emqx_logger:error("Unexpected info: ~p", [Info]),
noreply(State).
terminate(_Reason, _State) ->
@ -143,7 +143,7 @@ next_seq(State = #state{seq = Seq}) ->
-include_lib("eunit/include/eunit.hrl").
register_cmd_test_() ->
register_command_test_() ->
{setup,
fun() ->
{ok, InitState} = emqx_ctl:init([]),
@ -153,7 +153,7 @@ register_cmd_test_() ->
ok = emqx_ctl:terminate(shutdown, State)
end,
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}))]
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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_hooks).
-behaviour(gen_server).
%% Start
-export([start_link/0]).
%% Hooks API
@ -41,7 +40,7 @@
-record(hook, {name :: atom(), callbacks = [] :: list(#callback{})}).
-define(HOOK_TAB, mqtt_hook).
-define(TAB, ?MODULE).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
@ -104,24 +103,25 @@ run_([], _Args, Acc) ->
-spec(lookup(atom()) -> [#callback{}]).
lookup(HookPoint) ->
case ets:lookup(?HOOK_TAB, HookPoint) of
case ets:lookup(?TAB, HookPoint) of
[#hook{callbacks = Callbacks}] -> Callbacks;
[] -> []
end.
%%--------------------------------------------------------------------
%% gen_server Callbacks
%% gen_server callbacks
%%--------------------------------------------------------------------
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{}}.
handle_call({add, HookPoint, {Tag, Function}, InitArgs, Priority}, _From, State) ->
Callback = #callback{tag = Tag, function = Function,
init_args = InitArgs, priority = Priority},
{reply,
case ets:lookup(?HOOK_TAB, HookPoint) of
case ets:lookup(?TAB, HookPoint) of
[#hook{callbacks = Callbacks}] ->
case contain_(Tag, Function, Callbacks) of
false ->
@ -135,7 +135,7 @@ handle_call({add, HookPoint, {Tag, Function}, InitArgs, Priority}, _From, State)
handle_call({delete, HookPoint, {Tag, Function}}, _From, State) ->
{reply,
case ets:lookup(?HOOK_TAB, HookPoint) of
case ets:lookup(?TAB, HookPoint) of
[#hook{callbacks = Callbacks}] ->
case contain_(Tag, Function, Callbacks) of
true ->
@ -167,7 +167,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
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) ->
lists:keymerge(#callback.priority, Callbacks, [Callback]).

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_inflight).

View File

@ -1,22 +1,23 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_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()).
encode(Term) ->
@ -26,11 +27,41 @@ encode(Term) ->
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()).
decode(JSON) ->
jsx:decode(JSON).
decode(Json) ->
jsx:decode(Json).
-spec(decode(jsx:json_text(), jsx_to_json:config()) -> jsx:json_term()).
decode(JSON, Opts) ->
jsx:decode(JSON, Opts).
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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_keepalive).
@ -29,19 +29,21 @@
start(_, 0, _) ->
{ok, #keepalive{}};
start(StatFun, TimeoutSec, TimeoutMsg) ->
case StatFun() of
case catch StatFun() of
{ok, StatVal} ->
{ok, #keepalive{statfun = StatFun, statval = StatVal,
tsec = TimeoutSec, tmsg = TimeoutMsg,
tref = timer(TimeoutSec, TimeoutMsg)}};
{error, Error} ->
{error, Error}
{error, Error};
{'EXIT', Reason} ->
{error, Reason}
end.
%% @doc Check keepalive, called when timeout.
%% @doc Check keepalive, called when timeout...
-spec(check(keepalive()) -> {ok, keepalive()} | {error, term()}).
check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repeat}) ->
case StatFun() of
case catch StatFun() of
{ok, NewVal} ->
if NewVal =/= LastVal ->
{ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = 0})};
@ -51,9 +53,12 @@ check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repe
{error, timeout}
end;
{error, Error} ->
{error, Error}
{error, Error};
{'EXIT', Reason} ->
{error, Reason}
end.
-spec(resume(keepalive()) -> keepalive()).
resume(KeepAlive = #keepalive{tsec = TimeoutSec, tmsg = TimeoutMsg}) ->
KeepAlive#keepalive{tref = timer(TimeoutSec, TimeoutMsg)}.
@ -64,6 +69,6 @@ cancel(#keepalive{tref = TRef}) when is_reference(TRef) ->
cancel(_) ->
ok.
timer(Sec, Msg) ->
erlang:send_after(timer:seconds(Sec), self(), Msg).
timer(Secs, 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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_log).
-module(emqx_logger).
-compile({no_auto_import,[error/1]}).

View File

@ -50,7 +50,7 @@ get_flag(Flag, #message{flags = Flags}, Default) ->
%% @doc Set flag
-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)}.
%% @doc Unset flag

View File

@ -1,55 +1,110 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_metrics).
-behaviour(gen_server).
-include("emqx_mqtt.hrl").
%% API Function Exports
-export([start_link/0, create/1]).
-export([start_link/0]).
-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
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
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).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
%% @doc Start the metrics server
-spec(start_link() -> {ok, pid()} | ignore | {error, term()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
create({gauge, Name}) ->
%%--------------------------------------------------------------------
%% Metrics API
%%--------------------------------------------------------------------
new({gauge, Name}) ->
ets:insert(?TAB, {{Name, 0}, 0});
create({counter, Name}) ->
Schedulers = lists:seq(1, erlang:system_info(schedulers)),
new({counter, Name}) ->
Schedulers = lists:seq(1, emqx_vm:schedulers()),
ets:insert(?TAB, [{{Name, I}, 0} || I <- Schedulers]).
%% @doc Get all metrics
-spec(all() -> [{atom(), non_neg_integer()}]).
all() ->
@ -63,8 +118,8 @@ all() ->
end, #{}, ?TAB)).
%% @doc Get metric value
-spec(value(atom()) -> non_neg_integer()).
value(Metric) ->
-spec(val(atom()) -> non_neg_integer()).
val(Metric) ->
lists:sum(ets:select(?TAB, [{{{Metric, '_'}, '$1'}, [], ['$1']}])).
%% @doc Increase counter
@ -84,9 +139,9 @@ inc(Metric, Val) when is_atom(Metric) ->
%% @doc Increase metric value
-spec(inc(counter | gauge, atom(), pos_integer()) -> pos_integer()).
inc(gauge, Metric, Val) ->
ets:update_counter(?TAB, key(gauge, Metric), {2, Val});
update_counter(key(gauge, Metric), {2, 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
-spec(dec(gauge, atom()) -> integer()).
@ -96,7 +151,7 @@ dec(gauge, Metric) ->
%% @doc Decrease metric value
-spec(dec(gauge, atom(), pos_integer()) -> integer()).
dec(gauge, Metric, Val) ->
ets:update_counter(?TAB, key(gauge, Metric), {2, -Val}).
update_counter(key(gauge, Metric), {2, -Val}).
%% @doc Set metric value
set(Metric, Val) when is_atom(Metric) ->
@ -104,57 +159,120 @@ set(Metric, Val) when is_atom(Metric) ->
set(gauge, Metric, Val) ->
ets:insert(?TAB, {key(gauge, Metric), Val}).
%% @doc Metric Key
%% @doc Metric key
key(gauge, Metric) ->
{Metric, 0};
key(counter, Metric) ->
{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([]) ->
emqx_time:seed(),
% Create metrics table
_ = ets:new(?TAB, [set, public, named_table, {write_concurrency, true}]),
% Tick to publish metrics
{ok, TRef} = timer:send_after(emqx_sys:sys_interval(), tick),
{ok, #state{tick = TRef}, hibernate}.
_ = emqx_tables:new(?TAB, [set, public, {write_concurrency, true}]),
lists:foreach(fun new/1, ?BYTES_METRICS ++ ?PACKET_METRICS ++ ?MESSAGE_METRICS),
{ok, #state{}, hibernate}.
handle_call(_Req, _From, State) ->
{reply, error, State}.
handle_call(Req, _From, 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}.
handle_info(tick, State) ->
% publish metric message
[publish(Metric, Val) || {Metric, Val} <- all()],
{noreply, State, hibernate};
handle_info(_Info, State) ->
handle_info(Info, State) ->
emqx_logger:error("[METRICS] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{tick = TRef}) ->
%%TODO:
timer:cancel(TRef).
terminate(_Reason, #state{}) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_misc).
-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
-spec(merge_opts(list(), list()) -> list()).
merge_opts(Defaults, Options) ->
lists:foldl(
fun({Opt, Val}, Acc) ->
@ -51,6 +52,10 @@ cancel_timer(Timer) ->
_ -> ok
end.
-spec(proc_name(atom(), pos_integer()) -> atom()).
proc_name(Mod, Id) ->
list_to_atom(lists:concat([Mod, "_", Id])).
-spec(proc_stats() -> list()).
proc_stats() ->
proc_stats(self()).

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_mod_presence).
@ -34,32 +34,32 @@ on_client_connected(ConnAck, Client = #client{client_id = ClientId,
%%clean_sess = CleanSess,
%%proto_ver = ProtoVer
}, Env) ->
case catch emqx_json:encode([{clientid, ClientId},
case emqx_json:safe_encode([{clientid, ClientId},
{username, Username},
{ipaddress, iolist_to_binary(emqx_net:ntoa(IpAddr))},
%%{clean_sess, CleanSess}, %%TODO:: fixme later
%%{protocol, ProtoVer},
{connack, ConnAck},
{ts, emqx_time:now_secs()}]) of
Payload when is_binary(Payload) ->
{ok, Payload} ->
Msg = message(qos(Env), topic(connected, ClientId), Payload),
emqx:publish(emqx_message:set_flag(sys, Msg));
{'EXIT', Reason} ->
emqx_log:error("[Presence Module] json error: ~p", [Reason])
{error, Reason} ->
emqx_logger:error("[Presence Module] Json error: ~p", [Reason])
end,
{ok, Client}.
on_client_disconnected(Reason, #client{client_id = ClientId,
username = Username}, Env) ->
case catch emqx_json:encode([{clientid, ClientId},
case emqx_json:safe_encode([{clientid, ClientId},
{username, Username},
{reason, reason(Reason)},
{ts, emqx_time:now_secs()}]) of
Payload when is_binary(Payload) ->
{ok, Payload} ->
Msg = message(qos(Env), topic(disconnected, ClientId), Payload),
emqx:publish(emqx_message:set_flag(sys, Msg));
{'EXIT', Reason} ->
emqx_log:error("[Presence Module] json error: ~p", [Reason])
{error, Reason} ->
emqx_logger:error("[Presence Module] Json error: ~p", [Reason])
end, ok.
unload(_Env) ->
@ -67,13 +67,13 @@ unload(_Env) ->
emqx:unhook('client.disconnected', fun ?MODULE:on_client_disconnected/3).
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).
topic(connected, ClientId) ->
emqx_topic:systop(list_to_binary(["clients/", ClientId, "/connected"]));
emqx_topic:systop(iolist_to_binary(["clients/", ClientId, "/connected"]));
topic(disconnected, ClientId) ->
emqx_topic:systop(list_to_binary(["clients/", ClientId, "/disconnected"])).
emqx_topic:systop(iolist_to_binary(["clients/", ClientId, "/disconnected"])).
qos(Env) ->
proplists:get_value(qos, Env, 0).

View File

@ -35,11 +35,11 @@ load(Rules0) ->
emqx:hook('message.publish', fun ?MODULE:rewrite_publish/2, [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]}.
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]}.
rewrite_publish(Message = #message{topic = Topic}, Rules) ->

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_mod_subscription).

View File

@ -1,23 +1,24 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_modules).
-export([load/0, unload/0]).
-spec(load() -> ok).
load() ->
lists:foreach(
fun({Mod, Env}) ->
@ -25,6 +26,7 @@ load() ->
io:format("Load ~s module successfully.~n", [Mod])
end, emqx_config:get_env(modules, [])).
-spec(unload() -> ok).
unload() ->
lists:foreach(
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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_parser).

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_plugins).
@ -83,7 +83,7 @@ load_expand_plugin(PluginDir) ->
end, Modules),
case filelib:wildcard(Ebin ++ "/*.app") of
[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}
end.
@ -128,7 +128,7 @@ with_loaded_file(File, SuccFun) ->
{ok, Names} ->
SuccFun(Names);
{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}
end.
@ -136,7 +136,7 @@ load_plugins(Names, Persistent) ->
Plugins = list(), NotFound = Names -- names(Plugins),
case NotFound of
[] -> ok;
NotFound -> emqx_log:error("[Plugins] Cannot find plugins: ~p", [NotFound])
NotFound -> emqx_logger:error("[Plugins] Cannot find plugins: ~p", [NotFound])
end,
NeedToLoad = Names -- NotFound -- names(started_app),
[load_plugin(find_plugin(Name, Plugins), Persistent) || Name <- NeedToLoad].
@ -185,12 +185,12 @@ plugin(CfgFile) ->
load(PluginName) when is_atom(PluginName) ->
case lists:member(PluginName, names(started_app)) of
true ->
emqx_log:error("[Plugins] Plugin ~s is already started", [PluginName]),
emqx_logger:error("[Plugins] Plugin ~s is already started", [PluginName]),
{error, already_started};
false ->
case find_plugin(PluginName) of
false ->
emqx_log:error("[Plugins] Plugin ~s not found", [PluginName]),
emqx_logger:error("[Plugins] Plugin ~s not found", [PluginName]),
{error, not_found};
Plugin ->
load_plugin(Plugin, true)
@ -218,12 +218,12 @@ load_app(App) ->
start_app(App, SuccFun) ->
case application:ensure_all_started(App) of
{ok, Started} ->
emqx_log:info("Started Apps: ~p", [Started]),
emqx_log:info("Load plugin ~s successfully", [App]),
emqx_logger:info("Started Apps: ~p", [Started]),
emqx_logger:info("Load plugin ~s successfully", [App]),
SuccFun(App),
{ok, Started};
{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}}
end.
@ -240,10 +240,10 @@ unload(PluginName) when is_atom(PluginName) ->
{true, true} ->
unload_plugin(PluginName, true);
{false, _} ->
emqx_log:error("Plugin ~s is not started", [PluginName]),
emqx_logger:error("Plugin ~s is not started", [PluginName]),
{error, not_started};
{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}
end.
@ -258,11 +258,11 @@ unload_plugin(App, Persistent) ->
stop_app(App) ->
case application:stop(App) of
ok ->
emqx_log:info("Stop plugin ~s successfully", [App]), ok;
emqx_logger:info("Stop plugin ~s successfully", [App]), ok;
{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} ->
emqx_log:error("Stop plugin ~s error: ~p", [App]), {error, Reason}
emqx_logger:error("Stop plugin ~s error: ~p", [App]), {error, Reason}
end.
%%--------------------------------------------------------------------
@ -294,7 +294,7 @@ plugin_loaded(Name, true) ->
ignore
end;
{error, Error} ->
emqx_log:error("Cannot read loaded plugins: ~p", [Error])
emqx_logger:error("Cannot read loaded plugins: ~p", [Error])
end.
plugin_unloaded(_Name, false) ->
@ -306,10 +306,10 @@ plugin_unloaded(Name, true) ->
true ->
write_loaded(lists:delete(Name, Names));
false ->
emqx_log:error("Cannot find ~s in loaded_file", [Name])
emqx_logger:error("Cannot find ~s in loaded_file", [Name])
end;
{error, Error} ->
emqx_log:error("Cannot read loaded_plugins: ~p", [Error])
emqx_logger:error("Cannot read loaded_plugins: ~p", [Error])
end.
read_loaded() ->
@ -328,7 +328,7 @@ write_loaded(AppNames) ->
file:write(Fd, iolist_to_binary(io_lib:format("~s.~n", [Name])))
end, AppNames);
{error, Error} ->
emqx_log:error("Open File ~p Error: ~p", [File, Error]),
emqx_logger:error("Open File ~p Error: ~p", [File, Error]),
{error, Error}
end.

View File

@ -1,22 +1,24 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_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]}).
@ -43,7 +45,8 @@ monitor(Pid, Val, PM = {?MODULE, [M]}) ->
demonitor(Pid, PM = {?MODULE, [M]}) ->
case maps:find(Pid, M) of
{ok, {Ref, _Val}} ->
erlang:demonitor(Ref, [flush]),
%% Don't flush
_ = erlang:demonitor(Ref),
{?MODULE, [maps:remove(Pid, M)]};
error ->
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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_sup).
@ -39,12 +39,11 @@ start_link(Pool, Type, MFA) ->
start_link(Pool, Type, Schedulers, MFA).
-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) ->
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}]) ->
ensure_pool(Pool, Type, [{size, Size}]),
{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(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])).
%% @doc Init protocol
@ -220,7 +220,7 @@ process(?CONNECT_PACKET(Var), State0) ->
{ok, Session} -> %% TODO:...
SP = true, %% TODO:...
%% TODO: Register the client
emqx_cm:reg(clientid(State2)),
emqx_cm:register_client(clientid(State2)),
%%emqx_cm:reg(client(State2)),
%% Start keepalive
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}) ->
trace(send, Packet, State),
emqx_mqtt_metrics:sent(Packet),
emqx_metrics:sent(Packet),
SendFun(Packet),
{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}) ->
ignore;
shutdown(conflict, _State) ->
%% let it down
shutdown(conflict, _State = #proto_state{client_id = ClientId}) ->
emqx_cm:unregister_client(ClientId),
ignore;
shutdown(mnesia_conflict, _State) ->
%% let it down
%% emqx_cm:unreg(ClientId);
shutdown(mnesia_conflict, _State = #proto_state{client_id = ClientId}) ->
emqx_cm:unregister_client(ClientId),
ignore;
shutdown(Error, State = #proto_state{will_msg = WillMsg}) ->
shutdown(Error, State = #proto_state{client_id = ClientId,
will_msg = WillMsg}) ->
?LOG(info, "Shutdown for ~p", [Error], State),
Client = client(State),
%% Auth failure not publish the will message
@ -415,11 +414,11 @@ shutdown(Error, State = #proto_state{will_msg = WillMsg}) ->
false -> send_willmsg(Client, WillMsg)
end,
emqx_hooks:run('client.disconnected', [Error], Client),
%% let it down
%% emqx_cm:unreg(ClientId).
emqx_cm:unregister_client(ClientId),
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
undefined -> undefined;
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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_router).
-behaviour(gen_server).
-include("emqx.hrl").
-include_lib("ekka/include/ekka.hrl").
%% Mnesia Bootstrap
%% Mnesia bootstrap
-export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}).
%% Start
-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
-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
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-type(group() :: binary()).
-type(destination() :: node() | {group(), node()}
| {cluster(), group(), node()}).
-type(destination() :: node() | {binary(), node()}).
-record(state, {pool, id}).
-define(ROUTE, emqx_route).
%%--------------------------------------------------------------------
%% Mnesia Bootstrap
%% Mnesia bootstrap
%%--------------------------------------------------------------------
mnesia(boot) ->
@ -67,14 +61,17 @@ mnesia(copy) ->
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) ->
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
@ -105,40 +102,24 @@ del_route(From, Topic, Dest) when is_binary(Topic) ->
has_routes(Topic) when is_binary(Topic) ->
ets:member(?ROUTE, Topic).
%%--------------------------------------------------------------------
%% Topics
%%--------------------------------------------------------------------
-spec(topics() -> list(binary())).
%% @doc Get topics
-spec(topics() -> list(topic())).
topics() -> mnesia:dirty_all_keys(?ROUTE).
%%--------------------------------------------------------------------
%% Match routes
%%--------------------------------------------------------------------
%% @doc Match routes
%% 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) ->
Matched = mnesia:ets(fun emqx_trie:match/1, [Topic]),
Routes = [ets:lookup(?ROUTE, To) || To <- [Topic | Matched]],
[{To, Dest} || #route{topic = To, dest = Dest} <- lists:append(Routes)].
%%--------------------------------------------------------------------
%% Print routes
%%--------------------------------------------------------------------
lists:append([get_routes(To) || To <- [Topic | Matched]]).
%% @doc Print routes to a topic
-spec(print_routes(topic()) -> ok).
print_routes(Topic) ->
lists:foreach(fun({To, Dest}) ->
lists:foreach(fun(#route{topic = To, dest = Dest}) ->
io:format("~s -> ~s~n", [To, Dest])
end, match_routes(Topic)).
%%--------------------------------------------------------------------
%% Utility functions
%%--------------------------------------------------------------------
cast(Router, Msg) ->
gen_server:cast(Router, Msg).
@ -154,7 +135,7 @@ init([Pool, Id]) ->
{ok, #state{pool = Pool, id = Id}}.
handle_call(Req, _From, State) ->
emqx_log:error("[Router] Unexpected request: ~p", [Req]),
emqx_logger:error("[Router] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast({add_route, From, Route}, State) ->
@ -163,12 +144,12 @@ handle_cast({add_route, From, Route}, State) ->
{noreply, 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;
false ->
ok = emqx_router_helper:monitor(Dest),
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)
end
end,
@ -185,18 +166,18 @@ handle_cast({del_route, Route = #route{topic = Topic}}, State) ->
true -> ok;
false ->
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)
end
end,
{noreply, State};
handle_cast(Msg, State) ->
emqx_log:error("[Router] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[Router] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info(Info, State) ->
emqx_log:error("[Router] Unexpected info: ~p", [Info]),
emqx_logger:error("[Router] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{pool = Pool, id = Id}) ->
@ -237,8 +218,10 @@ del_trie_route(Route = #route{topic = Topic}) ->
trans(Fun, Args) ->
case mnesia:transaction(Fun, Args) of
{atomic, _} -> ok;
{aborted, Error} ->
emqx_log:error("[Router] Mnesia aborted: ~p", [Error]),
{error, Error}
{aborted, Error} -> {error, Error}
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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_router_helper).
@ -20,72 +20,73 @@
-include("emqx.hrl").
%% Mnesia Bootstrap
%% Mnesia bootstrap
-export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}).
%% API
-export([start_link/1, monitor/1]).
-export([start_link/0, monitor/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
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]}).
-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(boot) ->
ok = ekka_mnesia:create_table(?TAB, [
ok = ekka_mnesia:create_table(?ROUTING_NODE, [
{type, set},
{ram_copies, [node()]},
{record_name, routing_node},
{attributes, record_info(fields, routing_node)}]);
mnesia(copy) ->
ok = ekka_mnesia:copy_table(?TAB).
ok = ekka_mnesia:copy_table(?ROUTING_NODE).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
%% @doc Starts the router helper
-spec(start_link(fun()) -> {ok, pid()} | ignore | {error, any()}).
start_link(StatsFun) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [StatsFun], []).
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% @doc Monitor routing node
-spec(monitor(node()) -> ok).
-spec(monitor(node() | {binary(), node()}) -> ok).
monitor({_Group, Node}) ->
monitor(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;
false ->
mnesia:dirty_write(?TAB, #routing_node{name = Node, ts = os:timestamp()})
false -> mnesia:dirty_write(?ROUTING_NODE, #routing_node{name = Node})
end.
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
init([StatsFun]) ->
init([]) ->
ekka:monitor(membership),
mnesia:subscribe({table, ?TAB, simple}),
mnesia:subscribe({table, ?ROUTING_NODE, simple}),
Nodes = lists:foldl(
fun(Node, Acc) ->
case ekka:is_member(Node) of
@ -93,21 +94,21 @@ init([StatsFun]) ->
false -> _ = erlang:monitor_node(Node, true),
[Node | Acc]
end
end, [], mnesia:dirty_all_keys(?TAB)),
{ok, TRef} = timer:send_interval(timer:seconds(1), stats),
{ok, #state{nodes = Nodes, stats_fun = StatsFun, stats_timer = TRef}}.
end, [], mnesia:dirty_all_keys(?ROUTING_NODE)),
emqx_stats:update_interval(route_stats, stats_fun()),
{ok, #state{nodes = Nodes}}.
handle_call(Req, _From, State) ->
emqx_log:error("[RouterHelper] Unexpected request: ~p", [Req]),
emqx_logger:error("[RouterHelper] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast(Msg, State) ->
emqx_log:error("[RouterHelper] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[RouterHelper] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info({mnesia_table_event, {write, #routing_node{name = Node}, _}},
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
true -> {noreply, State};
false -> _ = erlang:monitor_node(Node, true),
@ -122,8 +123,8 @@ handle_info({nodedown, Node}, State = #state{nodes = Nodes}) ->
fun() ->
mnesia:transaction(fun cleanup_routes/1, [Node])
end),
mnesia:dirty_delete(?TAB, Node),
handle_info(stats, State#state{nodes = lists:delete(Node, Nodes)});
mnesia:dirty_delete(?ROUTING_NODE, Node),
{noreply, State#state{nodes = lists:delete(Node, Nodes)}};
handle_info({membership, {mnesia, down, Node}}, State) ->
handle_info({nodedown, Node}, State);
@ -131,18 +132,14 @@ handle_info({membership, {mnesia, down, Node}}, State) ->
handle_info({membership, _Event}, 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) ->
emqx_log:error("[RouteHelper] Unexpected info: ~p", [Info]),
emqx_logger:error("[RouteHelper] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{stats_timer = TRef}) ->
timer:cancel(TRef),
terminate(_Reason, #state{}) ->
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) ->
{ok, State}.
@ -151,9 +148,19 @@ code_change(_OldVsn, State, _Extra) ->
%% 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) ->
Patterns = [#route{_ = '_', dest = Node},
#route{_ = '_', dest = {'_', Node}}],
[mnesia:delete_object(?TAB, R, write)
|| Pat <- Patterns, R <- mnesia:match_object(?TAB, Pat, write)].
[mnesia:delete_object(?ROUTE, Route, 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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_router_sup).
@ -26,16 +26,14 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
StatsFun = emqx_stats:statsfun('routes/count', 'routes/max'),
%% 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]},
%% Router pool
PoolSup = emqx_pool_sup:spec(router_pool,
[router, hash, emqx_sys:schedulers(),
RouterPool = emqx_pool_sup:spec(emqx_router_pool,
[router, hash, emqx_vm:schedulers(),
{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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_rpc).
@ -20,12 +20,14 @@
-export([multicall/4]).
-define(RPC, rpc).
call(Node, Mod, Fun, Args) ->
rpc:call(Node, Mod, Fun, Args).
?RPC:call(Node, 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) ->
rpc:cast(Node, Mod, Fun, Args).
?RPC:cast(Node, Mod, Fun, Args).

View File

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

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_session_sup).
@ -24,13 +24,12 @@
-export([init/1]).
-spec(start_link() -> {ok, pid()}).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec(start_session(session()) -> {ok, pid()}).
start_session(Session) ->
supervisor:start_child(?MODULE, [Session]).
-spec(start_session(map()) -> {ok, pid()}).
start_session(Attrs) ->
supervisor:start_child(?MODULE, [Attrs]).
%%--------------------------------------------------------------------
%% Supervisor callbacks

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_shared_sub).
@ -20,13 +20,16 @@
-include("emqx.hrl").
%% API
%% Mnesia bootstrap
-export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}).
-export([start_link/0]).
-export([strategy/0]).
-export([subscribe/3, unsubscribe/3]).
-export([dispatch/3]).
%% gen_server callbacks
@ -41,18 +44,26 @@
-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
%%--------------------------------------------------------------------
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
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, [], []).
-spec(strategy() -> random | hash).
@ -62,24 +73,17 @@ strategy() ->
subscribe(undefined, _Topic, _SubPid) ->
ok;
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}).
unsubscribe(undefined, _Topic, _SubPid) ->
ok;
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}.
dispatch({Cluster, Group}, Topic, Delivery) ->
case ekka:cluster_name() of
Cluster ->
dispatch(Group, Topic, Delivery);
_ -> Delivery
end;
%% TODO: ensure the delivery...
dispatch(Group, Topic, Delivery = #delivery{message = Msg, flows = Flows}) ->
case pick(subscribers(Group, Topic)) of
@ -107,7 +111,7 @@ subscribers(Group, Topic) ->
init([]) ->
{atomic, PMon} = mnesia:transaction(fun init_monitors/0),
mnesia:subscribe({table, ?TAB, simple}),
{ok, #state{pmon = PMon}}.
{ok, update_stats(#state{pmon = PMon})}.
init_monitors() ->
mnesia:foldl(
@ -116,36 +120,34 @@ init_monitors() ->
end, emqx_pmon:new(), ?TAB).
handle_call(Req, _From, State) ->
emqx_log:error("[Shared] Unexpected request: ~p", [Req]),
emqx_logger:error("[Shared] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
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) ->
emqx_log:error("[Shared] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[Shared] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = PMon}) ->
emqx_log:info("Shared subscription created: ~p", [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}) ->
emqx_log:info("Shared subscription deleted: ~p", [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) ->
{noreply, State};
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]),
{noreply, State#state{pmon = PMon:erase(SubPid)}};
{noreply, update_stats(State#state{pmon = PMon:erase(SubPid)})};
handle_info(Info, State) ->
emqx_log:error("[Shared] Unexpected info: ~p", [Info]),
emqx_logger:error("[Shared] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->
@ -164,3 +166,8 @@ cleanup_down(SubPid) ->
mnesia:delete_object(?TAB, Record)
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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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).
@ -24,7 +24,8 @@
-export([open_session/1, lookup_session/1, close_session/1]).
-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
-export([dispatch/3]).
@ -32,18 +33,22 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {pmon}).
-record(state, {session_pmon}).
-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()}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% Open Session
%%--------------------------------------------------------------------
gen_server:start_link({local, ?SM}, ?MODULE, [], []).
%% @doc Open a session.
-spec(open_session(map()) -> {ok, pid()} | {error, term()}).
open_session(Attrs = #{clean_start := true,
client_id := ClientId, client_pid := ClientPid}) ->
CleanStart = fun(_) ->
@ -66,81 +71,113 @@ open_session(Attrs = #{clean_start := false,
end,
emqx_sm_locker:trans(ClientId, ResumeStart).
%%--------------------------------------------------------------------
%% Discard Session
%%--------------------------------------------------------------------
%% @doc Discard all the sessions identified by the ClientId.
-spec(discard_session(map()) -> ok).
discard_session(ClientId) when is_binary(ClientId) ->
discard_session(ClientId, self()).
discard_session(ClientId, ClientPid) when is_binary(ClientId) ->
lists:foreach(
fun(#session{pid = SessionPid}) ->
fun({_ClientId, SessionPid}) ->
case catch emqx_session:discard(SessionPid, ClientPid) of
{'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
end
end, lookup_session(ClientId)).
%%--------------------------------------------------------------------
%% Resume Session
%%--------------------------------------------------------------------
%% @doc Try to resume a session.
-spec(resume_session(client_id()) -> {ok, pid()} | {error, term()}).
resume_session(ClientId) ->
resume_session(ClientId, self()).
resume_session(ClientId, ClientPid) ->
case lookup_session(ClientId) of
[] -> {error, not_found};
[#session{pid = SessionPid}] ->
[{_ClientId, SessionPid}] ->
ok = emqx_session:resume(SessionPid, ClientPid),
{ok, SessionPid};
Sessions ->
[#session{pid = SessionPid}|StaleSessions] = lists:reverse(Sessions),
emqx_log:error("[SM] More than one session found: ~p", [Sessions]),
lists:foreach(fun(#session{pid = Pid}) ->
catch emqx_session:discard(Pid, ClientPid)
[{_, SessionPid}|StaleSessions] = lists:reverse(Sessions),
emqx_logger:error("[SM] More than one session found: ~p", [Sessions]),
lists:foreach(fun({_, StalePid}) ->
catch emqx_session:discard(StalePid, ClientPid)
end, StaleSessions),
ok = emqx_session:resume(SessionPid, ClientPid),
{ok, SessionPid}
end.
%%--------------------------------------------------------------------
%% Close a session
%%--------------------------------------------------------------------
close_session(#session{pid = SessionPid}) ->
%% @doc Close a session.
-spec(close_session({client_id(), pid()} | pid()) -> ok).
close_session({_ClientId, SessionPid}) ->
emqx_session:close(SessionPid);
close_session(SessionPid) when is_pid(SessionPid) ->
emqx_session:close(SessionPid).
%%--------------------------------------------------------------------
%% Create/Delete a session
%%--------------------------------------------------------------------
%% @doc Register a session with attributes.
-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) ->
ets:insert(session, Session),
ets:insert(session_attrs, {Session, Attrs}),
register_session(Session = {ClientId, SessionPid}, Attrs)
when is_binary(ClientId), is_pid(SessionPid) ->
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),
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_stats:del_session_stats(Session),
ets:delete(session_attrs, Session),
ets:delete_object(session, Session),
gen_server:cast(?MODULE, {unregistered, Session}).
ets:delete(?SESSION_STATS, Session),
ets:delete(?SESSION_ATTRS, Session),
ets:delete_object(?SESSION_P, Session),
ets:delete_object(?SESSION, Session),
notify({unregistered, ClientId, SessionPid}).
%%--------------------------------------------------------------------
%% Lookup a session from registry
%%--------------------------------------------------------------------
%% @doc Get session stats
-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) ->
emqx_sm_registry:lookup_session(ClientId).
%%--------------------------------------------------------------------
%% Dispatch by client Id
%%--------------------------------------------------------------------
case emqx_sm_registry:is_enabled() of
true -> emqx_sm_registry:lookup_session(ClientId);
false -> ets:lookup(?SESSION, ClientId)
end.
%% @doc Dispatch a message to the session.
-spec(dispatch(client_id(), topic(), message()) -> any()).
dispatch(ClientId, Topic, Msg) ->
case lookup_session_pid(ClientId) of
Pid when is_pid(Pid) ->
@ -149,60 +186,82 @@ dispatch(ClientId, Topic, Msg) ->
emqx_hooks:run('message.dropped', [ClientId, Msg])
end.
%% @doc Lookup session pid.
-spec(lookup_session_pid(client_id()) -> pid() | undefined).
lookup_session_pid(ClientId) ->
try ets:lookup_element(session, ClientId, #session.pid)
catch error:badarg ->
undefined
safe_lookup_element(?SESSION, ClientId, undefined).
safe_lookup_element(Tab, Key, Default) ->
try ets:lookup_element(Tab, Key, 2)
catch
error:badarg -> Default
end.
notify(Event) -> gen_server:cast(?SM, {notify, Event}).
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
init([]) ->
_ = emqx_tables:create(session, [public, set, {keypos, 2},
{read_concurrency, true},
{write_concurrency, true}]),
_ = emqx_tables:create(session_attrs, [public, set,
{write_concurrency, true}]),
{ok, #state{pmon = emqx_pmon:new()}}.
TabOpts = [public, set, {write_concurrency, true}],
_ = emqx_tables:new(?SESSION, [{read_concurrency, true} | TabOpts]),
_ = emqx_tables:new(?SESSION_P, TabOpts),
_ = emqx_tables:new(?SESSION_ATTRS, TabOpts),
_ = emqx_tables:new(?SESSION_STATS, TabOpts),
emqx_stats:update_interval(sm_stats, stats_fun()),
{ok, #state{session_pmon = emqx_pmon:new()}}.
handle_call(Req, _From, State) ->
emqx_log:error("[SM] Unexpected request: ~p", [Req]),
emqx_logger:error("[SM] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast({registered, #session{sid = ClientId, pid = SessionPid}},
State = #state{pmon = PMon}) ->
{noreply, State#state{pmon = PMon:monitor(SessionPid, ClientId)}};
handle_cast({notify, {registered, ClientId, SessionPid}},
State = #state{session_pmon = PMon}) ->
{noreply, State#state{session_pmon = PMon:monitor(SessionPid, ClientId)}};
handle_cast({unregistered, #session{sid = _ClientId, pid = SessionPid}},
State = #state{pmon = PMon}) ->
{noreply, State#state{pmon = PMon:erase(SessionPid)}};
handle_cast({notify, {unregistered, _ClientId, SessionPid}},
State = #state{session_pmon = PMon}) ->
{noreply, State#state{session_pmon = PMon:demonitor(SessionPid)}};
handle_cast(Msg, State) ->
emqx_log:error("[SM] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[SM] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info({'DOWN', _MRef, process, DownPid, _Reason},
State = #state{pmon = PMon}) ->
State = #state{session_pmon = PMon}) ->
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 ->
{noreply, State}
{noreply, State};
ClientId ->
unregister_session({ClientId, DownPid}),
{noreply, State#state{session_pmon = PMon:erase(DownPid)}}
end;
handle_info(Info, State) ->
emqx_log:error("[SM] Unexpected info: ~p", [Info]),
emqx_logger:error("[SM] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
emqx_stats:cancel_update(cm_stats).
code_change(_OldVsn, State, _Extra) ->
{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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_locker).
@ -32,8 +32,11 @@ start_link() ->
trans(ClientId, Fun) ->
trans(ClientId, Fun, undefined).
-spec(trans(client_id(), fun(([node()]) -> any()),
-spec(trans(client_id() | undefined,
fun(([node()]) -> any()),
ekka_locker:piggyback()) -> any()).
trans(undefined, Fun, _Piggyback) ->
Fun([]);
trans(ClientId, Fun, Piggyback) ->
case lock(ClientId, Piggyback) of
{true, Nodes} ->

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_registry).
@ -23,58 +23,73 @@
%% API
-export([start_link/0]).
-export([is_enabled/0]).
-export([register_session/1, lookup_session/1, unregister_session/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
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}).
-record(global_session, {sid, pid}).
-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, [
{type, bag},
{ram_copies, [node()]},
{record_name, session},
{attributes, record_info(fields, session)}]),
{record_name, global_session},
{attributes, record_info(fields, global_session)}]),
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),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_call(Req, _From, State) ->
emqx_logger:error("[Registry] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast(_Msg, State) ->
handle_cast(Msg, State) ->
emqx_logger:error("[Registry] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info({membership, {mnesia, down, Node}}, State) ->
@ -87,7 +102,8 @@ handle_info({membership, {mnesia, down, Node}}, State) ->
handle_info({membership, _Event}, State) ->
{noreply, State};
handle_info(_Info, State) ->
handle_info(Info, State) ->
emqx_logger:error("[Registry] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->
@ -101,7 +117,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
cleanup_sessions(Node) ->
Pat = [{#session{pid = '$1', _ = '_'},
Pat = [{#global_session{pid = '$1', _ = '_'},
[{'==', {node, '$1'}, Node}], ['$_']}],
lists:foreach(fun(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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_sup).
@ -26,12 +26,17 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
Childs = [child(M) || M <- [emqx_sm_locker,
emqx_sm_registry,
emqx_sm_stats,
emqx_sm]],
{ok, {{one_for_all, 10, 3600}, Childs}}.
%% Session Locker
Locker = {locker, {emqx_sm_locker, start_link, []},
permanent, 5000, worker, [emqx_sm_locker]},
child(M) ->
{M, {M, start_link, []}, permanent, 5000, worker, [M]}.
%% Session Registry
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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_stats).
@ -20,69 +20,78 @@
-include("emqx.hrl").
-export([start_link/0, stop/0]).
-export([start_link/0]).
%% Get all Stats
%% Get all stats
-export([all/0]).
%% Statistics API.
%% Stats API.
-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
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
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()})).
-export_type([stats/0]).
-define(STATS_TAB, stats).
%% $SYS Topics for Clients
-define(SYSTOP_CLIENTS, [
%% Client stats
-define(CLIENT_STATS, [
'clients/count', % clients connected current
'clients/max' % max clients connected
'clients/max' % maximum clients connected
]).
%% $SYS Topics for Sessions
-define(SYSTOP_SESSIONS, [
%% Session stats
-define(SESSION_STATS, [
'sessions/count',
'sessions/max'
'sessions/max',
'sessions/persistent/count',
'sessions/persistent/max'
]).
%% $SYS Topics for Subscribers
-define(SYSTOP_PUBSUB, [
'topics/count', % ...
'topics/max', % ...
'subscribers/count', % ...
'subscribers/max', % ...
'subscriptions/count', % ...
'subscriptions/max', % ...
'routes/count', % ...
'routes/max' % ...
%% Subscribers, Subscriptions stats
-define(PUBSUB_STATS, [
'topics/count',
'topics/max',
'subscribers/count',
'subscribers/max',
'subscriptions/count',
'subscriptions/max'
]).
%% $SYS Topic for retained
-define(SYSTOP_RETAINED, [
-define(ROUTE_STATS, [
'routes/count',
'routes/max'
]).
%% Retained stats
-define(RETAINED_STATS, [
'retained/count',
'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
%%--------------------------------------------------------------------
%% @doc Start stats server
-spec(start_link() -> {ok, pid()} | ignore | {error, term()}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() ->
gen_server:call(?MODULE, stop).
all() -> ets:tab2list(?STATS_TAB).
%% Get all stats.
-spec(all() -> stats()).
all() -> getstats().
%% @doc Generate stats fun
-spec(statsfun(Stat :: atom()) -> fun()).
@ -94,71 +103,117 @@ statsfun(Stat, MaxStat) ->
fun(Val) -> setstat(Stat, MaxStat, Val) end.
%% @doc Get all statistics
-spec(getstats() -> [{atom(), non_neg_integer()}]).
-spec(getstats() -> stats()).
getstats() ->
lists:sort(ets:tab2list(?STATS_TAB)).
case ets:info(?TAB, name) of
undefined -> [];
_ -> ets:tab2list(?TAB)
end.
%% @doc Get stats by name
-spec(getstat(atom()) -> non_neg_integer() | undefined).
getstat(Name) ->
case ets:lookup(?STATS_TAB, Name) of
case ets:lookup(?TAB, Name) of
[{Name, Val}] -> Val;
[] -> undefined
end.
%% @doc Set broker stats
%% @doc Set stats
-spec(setstat(Stat :: atom(), Val :: pos_integer()) -> boolean()).
setstat(Stat, Val) ->
ets:update_element(?STATS_TAB, Stat, {2, Val}).
setstat(Stat, Val) when is_integer(Val) ->
safe_update_element(Stat, Val).
%% @doc Set stats with max
-spec(setstat(Stat :: atom(), MaxStat :: atom(), Val :: pos_integer()) -> boolean()).
setstat(Stat, MaxStat, Val) ->
gen_server:cast(?MODULE, {setstat, Stat, MaxStat, Val}).
%% @doc Set stats with max value.
-spec(setstat(Stat :: atom(), MaxStat :: atom(),
Val :: pos_integer()) -> boolean()).
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
%%--------------------------------------------------------------------
init([]) ->
emqx_time:seed(),
_ = emqx_tables:create(?STATS_TAB, [set, public, named_table,
{write_concurrency, true}]),
Topics = ?SYSTOP_CLIENTS ++ ?SYSTOP_SESSIONS ++ ?SYSTOP_PUBSUB ++ ?SYSTOP_RETAINED,
ets:insert(?STATS_TAB, [{Topic, 0} || Topic <- Topics]),
% Tick to publish stats
{ok, TRef} = timer:send_after(emqx_sys:sys_interval(), tick),
{ok, #state{tick = TRef}, hibernate}.
_ = emqx_tables:new(?TAB, [set, public, {write_concurrency, true}]),
Stats = lists:append([?CLIENT_STATS, ?SESSION_STATS, ?PUBSUB_STATS,
?ROUTE_STATS, ?RETAINED_STATS]),
ets:insert(?TAB, [{Name, 0} || Name <- Stats]),
{ok, start_timer(#state{updates = []}), hibernate}.
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
start_timer(State) ->
State#state{timer = emqx_misc:start_timer(timer:seconds(1), tick)}.
handle_call(_Request, _From, State) ->
{reply, error, State}.
handle_call(Req, _From, State) ->
emqx_logger:error("[STATS] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
%% atomic
handle_cast({setstat, Stat, MaxStat, Val}, State) ->
MaxVal = ets:lookup_element(?STATS_TAB, MaxStat, 2),
if
Val > MaxVal ->
ets:update_element(?STATS_TAB, MaxStat, {2, Val});
true -> ok
try ets:lookup_element(?TAB, MaxStat, 2) of
MaxVal when Val > MaxVal ->
ets:update_element(?TAB, MaxStat, {2, Val});
_ -> ok
catch
error:badarg ->
ets:insert(?TAB, {MaxStat, Val})
end,
ets:update_element(?STATS_TAB, Stat, {2, Val}),
safe_update_element(Stat, Val),
{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}.
%% Interval Tick.
handle_info(tick, State) ->
[publish(Stat, Val) || {Stat, Val} <- ets:tab2list(?STATS_TAB)],
{noreply, State, hibernate};
handle_info({timeout, TRef, tick}, State = #state{timer = TRef,
updates = Updates}) ->
lists:foldl(
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}.
terminate(_Reason, #state{tick = TRef}) ->
terminate(_Reason, #state{timer = TRef}) ->
timer:cancel(TRef).
code_change(_OldVsn, State, _Extra) ->
@ -168,12 +223,10 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions
%%--------------------------------------------------------------------
publish(Stat, Val) ->
Msg = emqx_message:make(stats, stats_topic(Stat), bin(Val)),
emqx:publish(emqx_message:set_flag(sys, Msg)).
stats_topic(Stat) ->
emqx_topic:systop(list_to_binary(lists:concat(['stats/', Stat]))).
bin(I) when is_integer(I) -> list_to_binary(integer_to_list(I)).
safe_update_element(Key, Val) ->
try ets:update_element(?TAB, Key, {2, Val})
catch
error:badarg ->
ets:insert_new(?TAB, {Key, Val})
end.

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_sup).
@ -20,7 +20,6 @@
-export([start_link/0, start_child/1, start_child/2, stop_child/1]).
%% Supervisor callbacks
-export([init/1]).
-type(startchild_ret() :: {ok, supervisor:child()}
@ -29,20 +28,24 @@
-define(SUPERVISOR, ?MODULE).
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
start_link() ->
supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
-spec(start_child(supervisor:child_spec()) -> startchild_ret()).
start_child(ChildSpec) when is_tuple(ChildSpec) ->
supervisor:start_child(?SUPERVISOR, ChildSpec).
-spec(start_child(atom(), worker | supervisor) -> startchild_ret()).
start_child(Mod, Type) when Type == worker orelse Type == supervisor ->
start_child(?CHILD(Mod, Type)).
-spec(start_child(module(), worker | supervisor) -> startchild_ret()).
start_child(Mod, worker) ->
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) ->
case supervisor:terminate_child(?SUPERVISOR, ChildId) of
ok -> supervisor:delete_child(?SUPERVISOR, ChildId);
@ -54,24 +57,44 @@ stop_child(ChildId) ->
%%--------------------------------------------------------------------
init([]) ->
{ok, {{one_for_all, 10, 3600},
[?CHILD(emqx_ctl, worker),
?CHILD(emqx_hooks, worker),
?CHILD(emqx_stats, worker),
?CHILD(emqx_metrics, worker),
?CHILD(emqx_sys, worker),
?CHILD(emqx_router_sup, supervisor),
?CHILD(emqx_broker_sup, supervisor),
?CHILD(emqx_pooler, supervisor),
?CHILD(emqx_tracer_sup, supervisor),
?CHILD(emqx_cm_sup, supervisor),
?CHILD(emqx_sm_sup, supervisor),
?CHILD(emqx_session_sup, supervisor),
?CHILD(emqx_ws_connection_sup, supervisor),
?CHILD(emqx_alarm, worker),
?CHILD(emqx_mod_sup, supervisor),
?CHILD(emqx_bridge_sup_sup, supervisor),
?CHILD(emqx_access_control, worker),
?CHILD(emqx_sysmon_sup, supervisor)]
}}.
%% Kernel Sup
KernelSup = supervisor_spec(emqx_kernel_sup),
%% Router Sup
RouterSup = supervisor_spec(emqx_router_sup),
%% Broker Sup
BrokerSup = supervisor_spec(emqx_broker_sup),
%% BridgeSup
BridgeSup = supervisor_spec(emqx_bridge_sup_sup),
%% AccessControl
AccessControl = worker_spec(emqx_access_control),
%% Session Manager
SMSup = supervisor_spec(emqx_sm_sup),
%% Session Sup
SessionSup = supervisor_spec(emqx_session_sup),
%% Connection Manager
CMSup = supervisor_spec(emqx_cm_sup),
%% WebSocket Connection Sup
WSConnSup = supervisor_spec(emqx_ws_connection_sup),
%% 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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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).
@ -22,8 +22,6 @@
-export([start_link/0]).
-export([schedulers/0]).
-export([version/0, uptime/0, datetime/0, sysdescr/0, sys_interval/0]).
-export([info/0]).
@ -31,33 +29,29 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
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(SYS, ?MODULE).
-define(SERVER, ?MODULE).
%% $SYS Topics of Broker
-define(SYSTOP_BROKERS, [
-define(INFO_KEYS, [
version, % Broker version
uptime, % Broker uptime
datetime, % Broker local datetime
sysdescr % Broker description
]).
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
start_link() ->
gen_server:start_link({local, ?SYS}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% 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
-spec(version() -> string()).
version() ->
@ -70,7 +64,7 @@ sysdescr() ->
%% @doc Get sys uptime
-spec(uptime() -> string()).
uptime() -> gen_server:call(?SERVER, uptime).
uptime() -> gen_server:call(?SYS, uptime).
%% @doc Get sys datetime
-spec(datetime() -> string()).
@ -80,6 +74,8 @@ datetime() ->
io_lib:format(
"~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() ->
application:get_env(?APP, sys_interval, 60000).
@ -96,44 +92,49 @@ info() ->
%%--------------------------------------------------------------------
init([]) ->
Tick = fun(I, M) ->
{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),
State = #state{start_time = erlang:timestamp(),
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) ->
{reply, uptime(State), 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}.
handle_cast(Msg, State) ->
emqx_log:error("[SYS] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[SYS] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info(heartbeat, State) ->
handle_info({timeout, TRef, heartbeat}, State = #state{heartbeat = TRef}) ->
publish(uptime, iolist_to_binary(uptime(State))),
publish(datetime, iolist_to_binary(datetime())),
{noreply, State, hibernate};
{noreply, heartbeat(State)};
handle_info(tick, State = #state{version = Version, sysdescr = Descr}) ->
retain(brokers),
retain(version, Version),
retain(sysdescr, Descr),
{noreply, State, hibernate};
handle_info({timeout, TRef, tick}, State = #state{ticker = TRef,
version = Version,
sysdescr = Descr}) ->
publish(version, Version),
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) ->
emqx_log:error("[SYS] Unexpected info: ~p", [Info]),
emqx_logger:error("[SYS] Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{heartbeat = Hb, sys_ticker = TRef}) ->
timer:cancel(Hb),
timer:cancel(TRef).
terminate(_Reason, #state{heartbeat = HBRef, ticker = TRef}) ->
emqx_misc:cancel_timer(HBRef),
emqx_misc:cancel_timer(TRef).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
@ -142,24 +143,9 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions
%%--------------------------------------------------------------------
retain(brokers) ->
Payload = list_to_binary(string:join([atom_to_list(N) ||
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,
uptime(#state{start_time = Ts}) ->
Secs = timer:now_diff(erlang:timestamp(), Ts) div 1000000,
lists:flatten(uptime(seconds, Secs)).
uptime(seconds, Secs) when Secs < 60 ->
[integer_to_list(Secs), " seconds"];
uptime(seconds, Secs) ->
@ -175,3 +161,38 @@ uptime(hours, H) ->
uptime(days, D) ->
[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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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).
-module(emqx_sys_mon).
-behavior(gen_server).
@ -23,31 +23,34 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {tickref, events = [], tracelog}).
%%-define(LOG_FMT, [{formatter_config, [time, " ", message, "\n"]}]).
-record(state, {timer, events}).
-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),
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
-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) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []).
gen_server:start_link({local, ?SYSMON}, ?MODULE, [Opts], []).
%%--------------------------------------------------------------------
%% gen_server Callbacks
%% gen_server callbacks
%%--------------------------------------------------------------------
init([Opts]) ->
erlang:system_monitor(self(), parse_opt(Opts)),
{ok, TRef} = timer:send_interval(timer:seconds(1), reset),
%%TODO: don't trace for performance issue.
%%{ok, TraceLog} = start_tracelog(proplists:get_value(logfile, Opts)),
{ok, #state{tickref = TRef}}.
{ok, start_timer(#state{events = []})}.
start_timer(State) ->
State#state{timer = emqx_misc:start_timer(timer:seconds(2), tick)}.
parse_opt(Opts) ->
parse_opt(Opts, []).
@ -75,53 +78,53 @@ parse_opt([_Opt|Opts], Acc) ->
parse_opt(Opts, Acc).
handle_call(Req, _From, State) ->
emqx_log:error("[SYSMON] Unexpected request: ~p", [Req]),
emqx_logger:error("[SYSMON] Unexpected request: ~p", [Req]),
{reply, ignore, State}.
handle_cast(Msg, State) ->
emqx_log:error("[SYSMON] Unexpected msg: ~p", [Msg]),
emqx_logger:error("[SYSMON] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info({monitor, Pid, long_gc, Info}, State) ->
suppress({long_gc, Pid}, fun() ->
WarnMsg = io_lib:format("long_gc warning: pid = ~p, info: ~p", [Pid, Info]),
?LOG(WarnMsg, procinfo(Pid)),
publish(long_gc, WarnMsg)
safe_publish(long_gc, WarnMsg)
end, State);
handle_info({monitor, Pid, long_schedule, Info}, State) when is_pid(Pid) ->
suppress({long_schedule, Pid}, fun() ->
WarnMsg = io_lib:format("long_schedule warning: pid = ~p, info: ~p", [Pid, Info]),
?LOG(WarnMsg, procinfo(Pid)),
publish(long_schedule, WarnMsg)
safe_publish(long_schedule, WarnMsg)
end, State);
handle_info({monitor, Port, long_schedule, Info}, State) when is_port(Port) ->
suppress({long_schedule, Port}, fun() ->
WarnMsg = io_lib:format("long_schedule warning: port = ~p, info: ~p", [Port, Info]),
?LOG(WarnMsg, erlang:port_info(Port)),
publish(long_schedule, WarnMsg)
safe_publish(long_schedule, WarnMsg)
end, State);
handle_info({monitor, Pid, large_heap, Info}, State) ->
suppress({large_heap, Pid}, fun() ->
WarnMsg = io_lib:format("large_heap warning: pid = ~p, info: ~p", [Pid, Info]),
?LOG(WarnMsg, procinfo(Pid)),
publish(large_heap, WarnMsg)
safe_publish(large_heap, WarnMsg)
end, State);
handle_info({monitor, SusPid, busy_port, Port}, State) ->
suppress({busy_port, Port}, fun() ->
WarnMsg = io_lib:format("busy_port warning: suspid = ~p, port = ~p", [SusPid, Port]),
?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)),
publish(busy_port, WarnMsg)
safe_publish(busy_port, WarnMsg)
end, State);
handle_info({monitor, SusPid, busy_dist_port, Port}, State) ->
suppress({busy_dist_port, Port}, fun() ->
WarnMsg = io_lib:format("busy_dist_port warning: suspid = ~p, port = ~p", [SusPid, Port]),
?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)),
publish(busy_dist_port, WarnMsg)
safe_publish(busy_dist_port, WarnMsg)
end, State);
handle_info(reset, State) ->
@ -131,9 +134,8 @@ handle_info(Info, State) ->
lager:error("[SYSMON] Unexpected Info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{tickref = TRef, tracelog = TraceLog}) ->
timer:cancel(TRef),
cancel_tracelog(TraceLog).
terminate(_Reason, #state{timer = TRef}) ->
emqx_misc:cancel_timer(TRef).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
@ -152,20 +154,13 @@ procinfo(Pid) ->
{Info, GcInfo} -> Info ++ GcInfo
end.
publish(Sysmon, WarnMsg) ->
Msg = emqx_message:make(sysmon, topic(Sysmon), iolist_to_binary(WarnMsg)),
emqx:publish(emqx_message:set_flag(sys, Msg)).
topic(Sysmon) ->
emqx_topic:systop(list_to_binary(lists:concat(['sysmon/', Sysmon]))).
%% start_tracelog(undefined) ->
%% {ok, undefined};
%% start_tracelog(LogFile) ->
%% lager:trace_file(LogFile, [{sysmon, true}], info, ?LOG_FMT).
cancel_tracelog(undefined) ->
ok;
cancel_tracelog(TraceLog) ->
lager:stop_trace(TraceLog).
safe_publish(Event, WarnMsg) ->
try
Topic = emqx_topic:systop(lists:concat(['sysmon/', Event])),
Msg = emqx_message:make(?SYSMON, Topic, iolist_to_binary(WarnMsg)),
emqx_broker:publish(emqx_message:set_flag(sys, Msg))
catch
_:Error ->
emqx_logger:error("[SYSMON] Publish error: ~p", [Error])
end.

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,27 +1,28 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_tables).
-export([create/2]).
-export([new/2]).
create(Tab, Opts) ->
%% Create a named_table ets.
new(Tab, Opts) ->
case ets:info(Tab, name) of
undefined ->
ets:new(Tab, lists:usort([named_table|Opts]));
ets:new(Tab, lists:usort([named_table | Opts]));
Tab -> Tab
end.

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_time).

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_topic).
@ -143,11 +143,10 @@ word(<<"#">>) -> '#';
word(Bin) -> Bin.
%% @doc '$SYS' Topic.
systop(Name) when is_atom(Name) ->
list_to_binary(lists:concat(["$SYS/brokers/", node(), "/", Name]));
systop(Name) when is_atom(Name); is_list(Name) ->
iolist_to_binary(lists:concat(["$SYS/brokers/", node(), "/", 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()).
feed_var(Var, Val, Topic) ->

View File

@ -1,18 +1,18 @@
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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).
@ -22,9 +22,7 @@
-export([start_link/0]).
-export([trace/3]).
-export([start_trace/2, stop_trace/1, all_traces/0]).
-export([start_trace/2, lookup_traces/0, stop_trace/1]).
%% gen_server Function Exports
-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(TRACER, ?MODULE).
%%--------------------------------------------------------------------
%% Start the tracer
%%--------------------------------------------------------------------
-spec(start_link() -> {ok, pid()}).
-spec(start_link() -> {ok, pid()} | ignore | {error, term()}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?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]).
gen_server:start_link({local, ?TRACER}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% Start/Stop Trace
@ -68,11 +52,11 @@ trace(publish, From, #message{topic = Topic, payload = Payload})
-spec(start_trace(trace_who(), string()) -> ok | {error, term()}).
start_trace({client, ClientId}, LogFile) ->
start_trace({start_trace, {client, ClientId}, LogFile});
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.
-spec(stop_trace(trace_who()) -> ok | {error, term()}).
@ -81,25 +65,31 @@ stop_trace({client, ClientId}) ->
stop_trace({topic, Topic}) ->
gen_server:call(?MODULE, {stop_trace, {topic, Topic}}).
%% @doc Lookup all traces.
-spec(all_traces() -> [{Who :: trace_who(), LogFile :: string()}]).
all_traces() ->
gen_server:call(?MODULE, all_traces).
%% @doc Lookup all traces
-spec(lookup_traces() -> [{Who :: trace_who(), LogFile :: string()}]).
lookup_traces() ->
gen_server:call(?TRACER, lookup_traces).
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
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}) ->
case lager:trace_file(LogFile, [Who], Level, ?OPTIONS) of
handle_call({start_trace, Who, LogFile}, _From,
State = #state{level = Level, traces = Traces}) ->
case catch lager:trace_file(LogFile, [Who], Level, ?OPTIONS) of
{ok, exists} ->
{reply, {error, existed}, State};
{reply, {error, alread_existed}, State};
{ok, Trace} ->
{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}
end;
@ -112,23 +102,23 @@ handle_call({stop_trace, Who}, _From, State = #state{traces = Traces}) ->
end,
{reply, ok, State#state{traces = maps:remove(Who, Traces)}};
error ->
{reply, {error, not_found}, State}
{reply, {error, trance_not_found}, State}
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}}
<- maps:to_list(Traces)], 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}.
handle_cast(Msg, State) ->
emqx_log:error("[TRACE] Unexpected Cast: ~p", [Msg]),
emqx_logger:error("[TRACER] Unexpected msg: ~p", [Msg]),
{noreply, State}.
handle_info(Info, State) ->
emqx_log:error("[TRACE] Unexpected Info: ~p", [Info]),
emqx_logger:error("[TRACER] Unexpected info: ~p", [Info]),
{noreply, 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.
%%
%% 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.
%%--------------------------------------------------------------------
%%%===================================================================
%%% 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_trie).
@ -24,39 +24,39 @@
-boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}).
%% Trie API
%% Trie APIs
-export([insert/1, match/1, lookup/1, delete/1]).
%% Tables
%% Mnesia tables
-define(TRIE, emqx_trie).
-define(TRIE_NODE, emqx_trie_node).
%%--------------------------------------------------------------------
%% Mnesia Bootstrap
%% Mnesia bootstrap
%%--------------------------------------------------------------------
%% @doc Create or replicate trie tables.
-spec(mnesia(boot | copy) -> ok).
mnesia(boot) ->
%% Trie Table
%% Trie table
ok = ekka_mnesia:create_table(?TRIE, [
{ram_copies, [node()]},
{record_name, trie},
{attributes, record_info(fields, trie)}]),
%% Trie Node Table
%% Trie node table
ok = ekka_mnesia:create_table(?TRIE_NODE, [
{ram_copies, [node()]},
{record_name, trie_node},
{attributes, record_info(fields, trie_node)}]);
mnesia(copy) ->
%% Copy Trie Table
%% Copy trie table
ok = ekka_mnesia:copy_table(?TRIE),
%% Copy Trie Node Table
%% Copy trie_node table
ok = ekka_mnesia:copy_table(?TRIE_NODE).
%%--------------------------------------------------------------------
%% Trie API
%% Trie APIs
%%--------------------------------------------------------------------
%% @doc Insert a topic into the trie
@ -110,7 +110,7 @@ add_path({Node, Word, Child}) ->
[TrieNode = #trie_node{edge_count = Count}] ->
case mnesia:wread({?TRIE, Edge}) of
[] ->
write_trie_node(TrieNode#trie_node{edge_count = Count+1}),
write_trie_node(TrieNode#trie_node{edge_count = Count + 1}),
write_trie(#trie{edge = Edge, node_id = Child});
[_] ->
ok

View File

@ -26,7 +26,7 @@
-record(wsocket_state, {peername, client_pid, max_packet_size, parser}).
-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])).
@ -38,7 +38,7 @@ handle_request(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"),
Proto = check_protocol_header(Req),
case {is_websocket(Upgrade), Proto} of
@ -56,19 +56,19 @@ handle_request('GET', "/mqtt", Req) ->
max_packet_size = PacketSize,
client_pid = ClientPid});
{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">>})
end;
{false, _} ->
emqx_log:error("Not WebSocket: Upgrade = ~s", [Upgrade]),
emqx_logger:error("Not WebSocket: Upgrade = ~s", [Upgrade]),
Req:respond({400, [], <<"Bad Request">>});
{_, 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">>})
end;
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().
is_websocket(Upgrade) ->

View File

@ -50,7 +50,7 @@
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]).
-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])).
%% @doc Start WebSocket Client.
@ -140,7 +140,7 @@ handle_call(Req, _From, State) ->
reply({error, unexpected_request}, State).
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
{ok, ProtoState1} ->
{noreply, gc(State#wsclient_state{proto_state = ProtoState1}), hibernate};
@ -290,7 +290,7 @@ emit_stats(undefined, State) ->
State;
emit_stats(ClientId, State) ->
{reply, Stats, _, _} = handle_call(stats, undefined, State),
emqx_stats:set_client_stats(ClientId, Stats),
emqx_cm:set_client_stats(ClientId, Stats),
State.
wsock_stats(#wsclient_state{connection = Conn}) ->