Merge pull request #403 from emqtt/0.14

0.14 - New PubSub, Router Design
This commit is contained in:
Feng Lee 2015-12-10 15:30:41 +08:00
commit 82119afd88
73 changed files with 1852 additions and 1416 deletions

20
TODO Normal file
View File

@ -0,0 +1,20 @@
DONE TODO 1. refactor gproc_pool usage
DONE TODO 2. emqttd_router, emqttd_pubsub to route message
DONE TODO 3. sup, pool_sup, manager......
DONE TODO 4. route ageing...
TODO 5. dashboard
TODO 6. emqttd_ctl
DONE TODO 7. transaction on route, and topic?
TODO 8. topics, subscriptions CLI
DONE TODO 9. LOG.....
DONE TODO 10. emqttd_sm.erl to remove mnesia:index_read...

7
doc/pool.md Normal file
View File

@ -0,0 +1,7 @@
sup(one_for_all)
manager
pool_sup(one_for_one)
worker1
worker2
...
workerN

View File

@ -60,26 +60,15 @@
-type mqtt_topic() :: #mqtt_topic{}. -type mqtt_topic() :: #mqtt_topic{}.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% MQTT Subscriber %% MQTT Subscription
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-record(mqtt_subscriber, { -record(mqtt_subscription, {
subid :: binary() | atom(),
topic :: binary(), topic :: binary(),
subpid :: pid(),
qos = 0 :: 0 | 1 | 2 qos = 0 :: 0 | 1 | 2
}). }).
-type mqtt_subscriber() :: #mqtt_subscriber{}. -type mqtt_subscription() :: #mqtt_subscription{}.
%%------------------------------------------------------------------------------
%% P2P Queue Subscriber
%%------------------------------------------------------------------------------
-record(mqtt_queue, {
name :: binary(),
qpid :: pid(),
qos = 0 :: 0 | 1 | 2
}).
-type mqtt_queue() :: #mqtt_queue{}.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% MQTT Client %% MQTT Client

View File

@ -19,12 +19,18 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Internal Header File
%%% MQTT Internal Header.
%%% %%%
%%% @end
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-define(GPROC_POOL(JoinOrLeave, Pool, I),
(begin
case JoinOrLeave of
join -> gproc_pool:connect_worker(Pool, {Pool, Id});
leave -> gproc_pool:disconnect_worker(Pool, {Pool, I})
end
end)).
-define(record_to_proplist(Def, Rec), -define(record_to_proplist(Def, Rec),
lists:zip(record_info(fields, Def), lists:zip(record_info(fields, Def),
tl(tuple_to_list(Rec)))). tl(tuple_to_list(Rec)))).
@ -33,3 +39,21 @@
[{K, V} || {K, V} <- ?record_to_proplist(Def, Rec), [{K, V} || {K, V} <- ?record_to_proplist(Def, Rec),
lists:member(K, Fields)]). lists:member(K, Fields)]).
-define(UNEXPECTED_REQ(Req, State),
(begin
lager:error("Unexpected Request: ~p", [Req]),
{reply, {error, unexpected_request}, State}
end)).
-define(UNEXPECTED_MSG(Msg, State),
(begin
lager:error("Unexpected Message: ~p", [Msg]),
{noreply, State}
end)).
-define(UNEXPECTED_INFO(Info, State),
(begin
lager:error("Unexpected Info: ~p", [Info]),
{noreply, State}
end)).

View File

@ -17,6 +17,8 @@
{crash_log, "log/emqttd_crash.log"}, {crash_log, "log/emqttd_crash.log"},
{handlers, [ {handlers, [
{lager_console_backend, info}, {lager_console_backend, info},
%%NOTICE: Level >= error
%%{lager_emqtt_backend, error},
{lager_file_backend, [ {lager_file_backend, [
{formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]}, {formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]},
{file, "log/emqttd_info.log"}, {file, "log/emqttd_info.log"},
@ -141,11 +143,25 @@
%% Max Payload Size of retained message %% Max Payload Size of retained message
{max_playload_size, 65536} {max_playload_size, 65536}
]}, ]},
%% PubSub
%% PubSub and Router
{pubsub, [ {pubsub, [
%% default should be scheduler numbers %% Default should be scheduler numbers
%% {pool_size, 8} %% {pool_size, 8},
%% Subscription: disc | ram | false
{subscription, ram},
%% Route shard
{route_shard, false},
%% Route delay, false | integer
{route_delay, false},
%% Route aging time(seconds)
{route_aging, 5}
]}, ]},
%% Bridge %% Bridge
{bridge, [ {bridge, [
%%TODO: bridge queue size %%TODO: bridge queue size
@ -262,12 +278,11 @@
%% Erlang System Monitor %% Erlang System Monitor
{sysmon, [ {sysmon, [
%% Long GC %% Long GC
{long_gc, 100}, {long_gc, 100},
%% Long Schedule(ms) %% Long Schedule(ms)
{long_schedule, 50}, {long_schedule, 100},
%% 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM. %% 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM.
%% 8 * 1024 * 1024 %% 8 * 1024 * 1024

View File

@ -17,6 +17,8 @@
{crash_log, "log/emqttd_crash.log"}, {crash_log, "log/emqttd_crash.log"},
{handlers, [ {handlers, [
%%{lager_console_backend, info}, %%{lager_console_backend, info},
%%NOTICE: Level >= error
%%{lager_emqtt_backend, error},
{lager_file_backend, [ {lager_file_backend, [
{formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]}, {formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]},
{file, "log/emqttd_error.log"}, {file, "log/emqttd_error.log"},
@ -133,11 +135,25 @@
%% Max Payload Size of retained message %% Max Payload Size of retained message
{max_playload_size, 65536} {max_playload_size, 65536}
]}, ]},
%% PubSub
%% PubSub and Router
{pubsub, [ {pubsub, [
%% default should be scheduler numbers %% Default should be scheduler numbers
%% {pool_size, 8} %% {pool_size, 8},
%% Subscription: disc | ram | false
{subscription, ram},
%% Route shard
{route_shard, false},
%% Route delay, false | integer
{route_delay, false},
%% Route aging time(seconds)
{route_aging, 5}
]}, ]},
%% Bridge %% Bridge
{bridge, [ {bridge, [
%%TODO: bridge queue size %%TODO: bridge queue size
@ -254,20 +270,19 @@
%% Erlang System Monitor %% Erlang System Monitor
{sysmon, [ {sysmon, [
%% Long GC, don't monitor in production mode for: %% Long GC, don't monitor in production mode for:
%% https://github.com/erlang/otp/blob/feb45017da36be78d4c5784d758ede619fa7bfd3/erts/emulator/beam/erl_gc.c#L421 %% https://github.com/erlang/otp/blob/feb45017da36be78d4c5784d758ede619fa7bfd3/erts/emulator/beam/erl_gc.c#L421
{long_gc, false}, {long_gc, false},
%% Long Schedule(ms) %% Long Schedule(ms)
{long_schedule, 100}, {long_schedule, 240},
%% 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM. %% 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM.
%% 8 * 1024 * 1024 %% 8 * 1024 * 1024
{large_heap, 8388608}, {large_heap, 8388608},
%% Busy Port %% Busy Port
{busy_port, true}, {busy_port, false},
%% Busy Dist Port %% Busy Dist Port
{busy_dist_port, true} {busy_dist_port, true}

View File

@ -34,6 +34,9 @@
## Valid range is 1-2097151. Default is 1024. ## Valid range is 1-2097151. Default is 1024.
## +zdbbl 8192 ## +zdbbl 8192
## CPU Schedulers
## +sbt db
##------------------------------------------------------------------------- ##-------------------------------------------------------------------------
## Env ## Env
##------------------------------------------------------------------------- ##-------------------------------------------------------------------------

View File

@ -1,14 +1,15 @@
{application, emqttd, {application, emqttd,
[ [
{id, "emqttd"}, {id, "emqttd"},
{vsn, "0.13.1"}, {vsn, "0.14.0"},
{description, "Erlang MQTT Broker"}, {description, "Erlang MQTT Broker"},
{modules, []}, {modules, []},
{registered, []}, {registered, []},
{applications, [kernel, {applications, [kernel,
stdlib, stdlib,
gproc, gproc,
esockd]}, esockd,
mochiweb]},
{mod, {emqttd_app, []}}, {mod, {emqttd_app, []}},
{env, []} {env, []}
]}. ]}.

View File

@ -19,17 +19,14 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd main module.
%%% emqttd main module.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd). -module(emqttd).
-author("Feng Lee <feng@emqtt.io>").
-export([start/0, env/1, env/2, -export([start/0, env/1, env/2,
open_listeners/1, close_listeners/1, start_listeners/0, stop_listeners/0,
load_all_mods/0, is_mod_enabled/1, load_all_mods/0, is_mod_enabled/1,
is_running/1]). is_running/1]).
@ -38,8 +35,9 @@
{packet, raw}, {packet, raw},
{reuseaddr, true}, {reuseaddr, true},
{backlog, 512}, {backlog, 512},
{nodelay, true} {nodelay, true}]).
]).
-define(APP, ?MODULE).
-type listener() :: {atom(), inet:port_number(), [esockd:option()]}. -type listener() :: {atom(), inet:port_number(), [esockd:option()]}.
@ -49,7 +47,7 @@
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec start() -> ok | {error, any()}. -spec start() -> ok | {error, any()}.
start() -> start() ->
application:start(emqttd). application:start(?APP).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Get environment %% @doc Get environment
@ -57,39 +55,41 @@ start() ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec env(atom()) -> list(). -spec env(atom()) -> list().
env(Group) -> env(Group) ->
application:get_env(emqttd, Group, []). application:get_env(?APP, Group, []).
-spec env(atom(), atom()) -> undefined | any(). -spec env(atom(), atom()) -> undefined | any().
env(Group, Name) -> env(Group, Name) ->
proplists:get_value(Name, env(Group)). proplists:get_value(Name, env(Group)).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Open Listeners %% @doc Start Listeners
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec open_listeners([listener()]) -> any(). -spec start_listeners() -> any().
open_listeners(Listeners) when is_list(Listeners) -> start_listeners() ->
[open_listener(Listener) || Listener <- Listeners]. {ok, Listeners} = application:get_env(?APP, listeners),
lists:foreach(fun start_listener/1, Listeners).
%% open mqtt port %% Start mqtt listener
open_listener({mqtt, Port, Options}) -> -spec start_listener(listener()) -> any().
open_listener(mqtt, Port, Options); start_listener({mqtt, Port, Options}) ->
start_listener(mqtt, Port, Options);
%% open mqtt(SSL) port %% Start mqtt(SSL) listener
open_listener({mqtts, Port, Options}) -> start_listener({mqtts, Port, Options}) ->
open_listener(mqtts, Port, Options); start_listener(mqtts, Port, Options);
%% open http port %% Start http listener
open_listener({http, Port, Options}) -> start_listener({http, Port, Options}) ->
MFArgs = {emqttd_http, handle_request, []}, MFArgs = {emqttd_http, handle_request, []},
mochiweb:start_http(Port, Options, MFArgs); mochiweb:start_http(Port, Options, MFArgs);
%% open https port %% Start https listener
open_listener({https, Port, Options}) -> start_listener({https, Port, Options}) ->
MFArgs = {emqttd_http, handle_request, []}, MFArgs = {emqttd_http, handle_request, []},
mochiweb:start_http(Port, Options, MFArgs). mochiweb:start_http(Port, Options, MFArgs).
open_listener(Protocol, Port, Options) -> start_listener(Protocol, Port, Options) ->
MFArgs = {emqttd_client, start_link, [env(mqtt)]}, MFArgs = {emqttd_client, start_link, [env(mqtt)]},
esockd:open(Protocol, Port, merge_sockopts(Options) , MFArgs). esockd:open(Protocol, Port, merge_sockopts(Options) , MFArgs).
@ -99,22 +99,25 @@ merge_sockopts(Options) ->
emqttd_opts:merge(Options, [{sockopts, SockOpts}]). emqttd_opts:merge(Options, [{sockopts, SockOpts}]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Close Listeners %% @doc Stop Listeners
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec close_listeners([listener()]) -> any(). stop_listeners() ->
close_listeners(Listeners) when is_list(Listeners) -> {ok, Listeners} = application:get_env(?APP, listeners),
[close_listener(Listener) || Listener <- Listeners]. lists:foreach(fun stop_listener/1, Listeners).
close_listener({Protocol, Port, _Options}) -> stop_listener({Protocol, Port, _Options}) ->
esockd:close({Protocol, Port}). esockd:close({Protocol, Port}).
load_all_mods() -> load_all_mods() ->
lists:foreach(fun({Name, Opts}) -> lists:foreach(fun load_mod/1, env(modules)).
load_mod({Name, Opts}) ->
Mod = list_to_atom("emqttd_mod_" ++ atom_to_list(Name)), Mod = list_to_atom("emqttd_mod_" ++ atom_to_list(Name)),
Mod:load(Opts), case catch Mod:load(Opts) of
lager:info("load module ~s successfully", [Name]) {ok, _State} -> lager:info("load module ~s successfully", [Name]);
end, env(modules)). {'EXIT', Reason} -> lager:error("load module ~s error: ~p", [Name, Reason])
end.
is_mod_enabled(Name) -> is_mod_enabled(Name) ->
env(modules, Name) =/= undefined. env(modules, Name) =/= undefined.
@ -124,7 +127,7 @@ is_mod_enabled(Name) ->
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
is_running(Node) -> is_running(Node) ->
case rpc:call(Node, erlang, whereis, [emqttd]) of case rpc:call(Node, erlang, whereis, [?APP]) of
{badrpc, _} -> false; {badrpc, _} -> false;
undefined -> false; undefined -> false;
Pid when is_pid(Pid) -> true Pid when is_pid(Pid) -> true

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Authentication and ACL Control Server
%%% emqttd authentication and ACL server.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_access_control). -module(emqttd_access_control).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-behaviour(gen_server). -behaviour(gen_server).
@ -62,9 +59,9 @@
start_link() -> start_link() ->
start_link(emqttd:env(access)). start_link(emqttd:env(access)).
-spec start_link(AcOpts :: list()) -> {ok, pid()} | ignore | {error, any()}. -spec start_link(Opts :: list()) -> {ok, pid()} | ignore | {error, any()}.
start_link(AcOpts) -> start_link(Opts) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [AcOpts], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [Opts], []).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Authenticate MQTT Client %% @doc Authenticate MQTT Client
@ -76,10 +73,11 @@ auth(Client, Password) when is_record(Client, mqtt_client) ->
auth(_Client, _Password, []) -> auth(_Client, _Password, []) ->
{error, "No auth module to check!"}; {error, "No auth module to check!"};
auth(Client, Password, [{Mod, State, _Seq} | Mods]) -> auth(Client, Password, [{Mod, State, _Seq} | Mods]) ->
case Mod:check(Client, Password, State) of case catch Mod:check(Client, Password, State) of
ok -> ok; ok -> ok;
ignore -> auth(Client, Password, Mods);
{error, Reason} -> {error, Reason}; {error, Reason} -> {error, Reason};
ignore -> auth(Client, Password, Mods) {'EXIT', Error} -> {error, Error}
end. end.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -117,7 +115,7 @@ reload_acl() ->
%% @doc Register authentication or ACL module %% @doc Register authentication or ACL module
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec register_mod(Type :: auth | acl, Mod :: atom(), Opts :: list()) -> ok | {error, any()}. -spec register_mod(auth | acl, atom(), list()) -> ok | {error, any()}.
register_mod(Type, Mod, Opts) when Type =:= auth; Type =:= acl-> register_mod(Type, Mod, Opts) when Type =:= auth; Type =:= acl->
register_mod(Type, Mod, Opts, 0). register_mod(Type, Mod, Opts, 0).
@ -143,10 +141,9 @@ lookup_mods(Type) ->
[] -> []; [] -> [];
[{_, Mods}] -> Mods [{_, Mods}] -> Mods
end. end.
tab_key(auth) ->
auth_modules; tab_key(auth) -> auth_modules;
tab_key(acl) -> tab_key(acl) -> acl_modules.
acl_modules.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Stop access control server %% @doc Stop access control server
@ -159,10 +156,11 @@ stop() ->
%%% gen_server callbacks %%% gen_server callbacks
%%%============================================================================= %%%=============================================================================
init([AcOpts]) -> init([Opts]) ->
ets:new(?ACCESS_CONTROL_TAB, [set, named_table, protected, {read_concurrency, true}]), ets:new(?ACCESS_CONTROL_TAB, [set, named_table, protected, {read_concurrency, true}]),
ets:insert(?ACCESS_CONTROL_TAB, {auth_modules, init_mods(auth, proplists:get_value(auth, AcOpts))}),
ets:insert(?ACCESS_CONTROL_TAB, {acl_modules, init_mods(acl, proplists:get_value(acl, AcOpts))}), ets:insert(?ACCESS_CONTROL_TAB, {auth_modules, init_mods(auth, proplists:get_value(auth, Opts))}),
ets:insert(?ACCESS_CONTROL_TAB, {acl_modules, init_mods(acl, proplists:get_value(acl, Opts))}),
{ok, state}. {ok, state}.
init_mods(auth, AuthMods) -> init_mods(auth, AuthMods) ->
@ -233,8 +231,11 @@ code_change(_OldVsn, State, _Extra) ->
%%%============================================================================= %%%=============================================================================
authmod(Name) when is_atom(Name) -> authmod(Name) when is_atom(Name) ->
list_to_atom(lists:concat(["emqttd_auth_", Name])). mod(emqttd_auth_, Name).
aclmod(Name) when is_atom(Name) -> aclmod(Name) when is_atom(Name) ->
list_to_atom(lists:concat(["emqttd_acl_", Name])). mod(emqttd_acl_, Name).
mod(Prefix, Name) ->
list_to_atom(lists:concat([Prefix, Name])).

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd ACL Rule
%%% emqttd ACL rule.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_access_rule). -module(emqttd_access_rule).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-type who() :: all | binary() | -type who() :: all | binary() |

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Internal ACL that load rules from etc/acl.config
%%% Internal ACL that load rules from etc/acl.config
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_acl_internal). -module(emqttd_acl_internal).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-export([all_rules/0]). -export([all_rules/0]).
@ -142,6 +139,5 @@ reload_acl(State) ->
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec description() -> string(). -spec description() -> string().
description() -> description() -> "Internal ACL with etc/acl.config".
"Internal ACL with etc/acl.config".

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc ACL module behaviour
%%% ACL module behaviour.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_acl_mod). -module(emqttd_acl_mod).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
%%%============================================================================= %%%=============================================================================

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Copy alarm_handler
%%% copy alarm_handler.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_alarm). -module(emqttd_alarm).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-behaviour(gen_event). -behaviour(gen_event).
@ -108,10 +105,10 @@ handle_event({set_alarm, Alarm = #mqtt_alarm{id = AlarmId,
emqttd_pubsub:publish(alarm_msg(alert, AlarmId, Json)), emqttd_pubsub:publish(alarm_msg(alert, AlarmId, Json)),
{ok, [Alarm#mqtt_alarm{timestamp = Timestamp} | Alarms]}; {ok, [Alarm#mqtt_alarm{timestamp = Timestamp} | Alarms]};
handle_event({clear_alarm, AlarmId}, Alarms)-> handle_event({clear_alarm, AlarmId}, Alarms) ->
Json = mochijson2:encode([{id, AlarmId}, {ts, emqttd_util:now_to_secs()}]), Json = mochijson2:encode([{id, AlarmId}, {ts, emqttd_util:now_to_secs()}]),
emqttd_pubsub:publish(alarm_msg(clear, AlarmId, Json)), emqttd_pubsub:publish(alarm_msg(clear, AlarmId, Json)),
{ok, lists:keydelete(AlarmId, 2, Alarms)}; {ok, lists:keydelete(AlarmId, 2, Alarms), hibernate};
handle_event(_, Alarms)-> handle_event(_, Alarms)->
{ok, Alarms}. {ok, Alarms}.

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd application.
%%% emqttd application.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_app). -module(emqttd_app).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd_cli.hrl"). -include("emqttd_cli.hrl").
-behaviour(application). -behaviour(application).
@ -52,7 +49,7 @@ start(_StartType, _StartArgs) ->
emqttd_cli:load(), emqttd_cli:load(),
emqttd:load_all_mods(), emqttd:load_all_mods(),
emqttd_plugins:load(), emqttd_plugins:load(),
start_listeners(), emqttd:start_listeners(),
register(emqttd, self()), register(emqttd, self()),
print_vsn(), print_vsn(),
{ok, Sup}. {ok, Sup}.
@ -65,18 +62,14 @@ print_vsn() ->
{ok, Desc} = application:get_key(description), {ok, Desc} = application:get_key(description),
?PRINT("~s ~s is running now~n", [Desc, Vsn]). ?PRINT("~s ~s is running now~n", [Desc, Vsn]).
start_listeners() ->
{ok, Listeners} = application:get_env(listeners),
emqttd:open_listeners(Listeners).
start_servers(Sup) -> start_servers(Sup) ->
Servers = [{"emqttd ctl", emqttd_ctl}, Servers = [{"emqttd ctl", emqttd_ctl},
{"emqttd trace", emqttd_trace}, {"emqttd trace", {supervisor, emqttd_trace_sup}},
{"emqttd pubsub", {supervisor, emqttd_pubsub_sup}}, {"emqttd pubsub", {supervisor, emqttd_pubsub_sup}},
{"emqttd stats", emqttd_stats}, {"emqttd stats", emqttd_stats},
{"emqttd metrics", emqttd_metrics}, {"emqttd metrics", emqttd_metrics},
{"emqttd retained", emqttd_retained}, {"emqttd retainer", emqttd_retainer},
{"emqttd pooler", {supervisor, emqttd_pooler_sup}}, {"emqttd pooler", {supervisor, emqttd_pooler}},
{"emqttd client manager", {supervisor, emqttd_cm_sup}}, {"emqttd client manager", {supervisor, emqttd_cm_sup}},
{"emqttd session manager", {supervisor, emqttd_sm_sup}}, {"emqttd session manager", {supervisor, emqttd_sm_sup}},
{"emqttd session supervisor", {supervisor, emqttd_session_sup}}, {"emqttd session supervisor", {supervisor, emqttd_session_sup}},
@ -85,7 +78,7 @@ start_servers(Sup) ->
{"emqttd mod supervisor", emqttd_mod_sup}, {"emqttd mod supervisor", emqttd_mod_sup},
{"emqttd bridge supervisor", {supervisor, emqttd_bridge_sup}}, {"emqttd bridge supervisor", {supervisor, emqttd_bridge_sup}},
{"emqttd access control", emqttd_access_control}, {"emqttd access control", emqttd_access_control},
{"emqttd system monitor", emqttd_sysmon, emqttd:env(sysmon)}], {"emqttd system monitor", {supervisor, emqttd_sysmon_sup}}],
[start_server(Sup, Server) || Server <- Servers]. [start_server(Sup, Server) || Server <- Servers].
start_server(_Sup, {Name, F}) when is_function(F) -> start_server(_Sup, {Name, F}) when is_function(F) ->
@ -135,15 +128,5 @@ worker_spec(M, F, A) ->
-spec stop(State :: term()) -> term(). -spec stop(State :: term()) -> term().
stop(_State) -> stop(_State) ->
stop_listeners(). catch emqttd:stop_listeners().
stop_listeners() ->
%% ensure that esockd applications is started?
case lists:keyfind(esockd, 1, application:which_applications()) of
false ->
ignore;
_Tuple ->
{ok, Listeners} = application:get_env(listeners),
emqttd:close_listeners(Listeners)
end.

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Anonymous Authentication Module
%%% Anonymous authentication module.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_auth_anonymous). -module(emqttd_auth_anonymous).
-author("Feng Lee <feng@emqtt.io>").
-behaviour(emqttd_auth_mod). -behaviour(emqttd_auth_mod).
-export([init/1, check/3, description/0]). -export([init/1, check/3, description/0]).

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc ClientId Authentication Module
%%% ClientId Authentication Module.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_auth_clientid). -module(emqttd_auth_clientid).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-export([add_clientid/1, add_clientid/2, -export([add_clientid/1, add_clientid/2,

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc LDAP Authentication Module
%%% LDAP Authentication Module
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_auth_ldap). -module(emqttd_auth_ldap).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-import(proplists, [get_value/2, get_value/3]). -import(proplists, [get_value/2, get_value/3]).
@ -84,6 +81,5 @@ ldap_bind(LDAP, UserDn, Password) ->
fill(Username, UserDn) -> fill(Username, UserDn) ->
re:replace(UserDn, "\\$u", Username, [global, {return, list}]). re:replace(UserDn, "\\$u", Username, [global, {return, list}]).
description() -> description() -> "LDAP Authentication Module".
"LDAP Authentication Module".

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd Authentication Behaviour
%%% emqttd authentication behaviour.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_auth_mod). -module(emqttd_auth_mod).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
%%%============================================================================= %%%=============================================================================

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Authentication with username and password
%%% Authentication with username and password.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_auth_username). -module(emqttd_auth_username).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_cli.hrl"). -include("emqttd_cli.hrl").

View File

@ -19,19 +19,18 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd bridge
%%% emqttd bridge.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_bridge). -module(emqttd_bridge).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").
-include("emqttd_internal.hrl").
%% API Function Exports %% API Function Exports
-export([start_link/3]). -export([start_link/3]).
@ -85,7 +84,7 @@ init([Node, SubTopic, Options]) ->
MQueue = emqttd_mqueue:new(qname(Node, SubTopic), MQueue = emqttd_mqueue:new(qname(Node, SubTopic),
[{max_len, State#state.max_queue_len}], [{max_len, State#state.max_queue_len}],
emqttd_alarm:alarm_fun()), emqttd_alarm:alarm_fun()),
emqttd_pubsub:subscribe(SubTopic, State#state.qos), emqttd_pubsub:subscribe({SubTopic, State#state.qos}),
{ok, State#state{mqueue = MQueue}}; {ok, State#state{mqueue = MQueue}};
false -> false ->
{stop, {cannot_connect, Node}} {stop, {cannot_connect, Node}}
@ -111,23 +110,23 @@ qname(Node, SubTopic) when is_atom(Node) ->
qname(Node, SubTopic) -> qname(Node, SubTopic) ->
list_to_binary(["Bridge:", Node, ":", SubTopic]). list_to_binary(["Bridge:", Node, ":", SubTopic]).
handle_call(_Request, _From, State) -> handle_call(Req, _From, State) ->
{reply, error, State}. ?UNEXPECTED_REQ(Req, State).
handle_cast(_Msg, State) -> handle_cast(Msg, State) ->
{noreply, State}. ?UNEXPECTED_MSG(Msg, State).
handle_info({dispatch, Msg}, State = #state{mqueue = MQ, status = down}) -> handle_info({dispatch, _Topic, Msg}, State = #state{mqueue = MQ, status = down}) ->
{noreply, State#state{mqueue = emqttd_mqueue:in(Msg, MQ)}}; {noreply, State#state{mqueue = emqttd_mqueue:in(Msg, MQ)}};
handle_info({dispatch, Msg}, State = #state{node = Node, status = up}) -> handle_info({dispatch, _Topic, Msg}, State = #state{node = Node, status = up}) ->
rpc:cast(Node, emqttd_pubsub, publish, [transform(Msg, State)]), rpc:cast(Node, emqttd_pubsub, publish, [transform(Msg, State)]),
{noreply, State}; {noreply, State, hibernate};
handle_info({nodedown, Node}, State = #state{node = Node, ping_down_interval = Interval}) -> handle_info({nodedown, Node}, State = #state{node = Node, ping_down_interval = Interval}) ->
lager:warning("Bridge Node Down: ~p", [Node]), lager:warning("Bridge Node Down: ~p", [Node]),
erlang:send_after(Interval, self(), ping_down_node), erlang:send_after(Interval, self(), ping_down_node),
{noreply, State#state{status = down}}; {noreply, State#state{status = down}, hibernate};
handle_info({nodeup, Node}, State = #state{node = Node}) -> handle_info({nodeup, Node}, State = #state{node = Node}) ->
%% TODO: Really fast?? %% TODO: Really fast??
@ -156,8 +155,7 @@ handle_info({'EXIT', _Pid, normal}, State) ->
{noreply, State}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
lager:error("Unexpected Info: ~p", [Info]), ?UNEXPECTED_INFO(Info, State).
{noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ok. ok.

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Bridge Supervisor
%%% emqttd bridge supervisor.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_bridge_sup). -module(emqttd_bridge_sup).
-author("Feng Lee <feng@emqtt.io>").
-behavior(supervisor). -behavior(supervisor).
-export([start_link/0, -export([start_link/0,

View File

@ -19,17 +19,18 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd broker
%%% emqttd broker.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_broker). -module(emqttd_broker).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(gen_server).
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_internal.hrl").
%% API Function Exports %% API Function Exports
-export([start_link/0]). -export([start_link/0]).
@ -48,18 +49,16 @@
%% Tick API %% Tick API
-export([start_tick/1, stop_tick/1]). -export([start_tick/1, stop_tick/1]).
-behaviour(gen_server).
-define(SERVER, ?MODULE).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-define(BROKER_TAB, mqtt_broker).
-record(state, {started_at, sys_interval, heartbeat, tick_tref}). -record(state, {started_at, sys_interval, heartbeat, tick_tref}).
-define(SERVER, ?MODULE).
-define(BROKER_TAB, mqtt_broker).
%% $SYS Topics of Broker %% $SYS Topics of Broker
-define(SYSTOP_BROKERS, [ -define(SYSTOP_BROKERS, [
version, % Broker version version, % Broker version
@ -219,10 +218,10 @@ stop_tick(TRef) ->
%%%============================================================================= %%%=============================================================================
init([]) -> init([]) ->
random:seed(now()), random:seed(os:timestamp()),
ets:new(?BROKER_TAB, [set, public, named_table]), ets:new(?BROKER_TAB, [set, public, named_table]),
% Create $SYS Topics % Create $SYS Topics
emqttd_pubsub:create(<<"$SYS/brokers">>), emqttd_pubsub:create(topic, <<"$SYS/brokers">>),
[ok = create_topic(Topic) || Topic <- ?SYSTOP_BROKERS], [ok = create_topic(Topic) || Topic <- ?SYSTOP_BROKERS],
% Tick % Tick
{ok, #state{started_at = os:timestamp(), {ok, #state{started_at = os:timestamp(),
@ -258,11 +257,10 @@ handle_call({unhook, Hook, Name}, _From, State) ->
{reply, Reply, State}; {reply, Reply, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
lager:error("Unexpected request: ~p", [Req]), ?UNEXPECTED_REQ(Req, State).
{reply, {error, badreq}, State}.
handle_cast(_Msg, State) -> handle_cast(Msg, State) ->
{noreply, State}. ?UNEXPECTED_MSG(Msg, State).
handle_info(heartbeat, State) -> handle_info(heartbeat, State) ->
publish(uptime, list_to_binary(uptime(State))), publish(uptime, list_to_binary(uptime(State))),
@ -273,10 +271,10 @@ handle_info(tick, State) ->
retain(brokers), retain(brokers),
retain(version, list_to_binary(version())), retain(version, list_to_binary(version())),
retain(sysdescr, list_to_binary(sysdescr())), retain(sysdescr, list_to_binary(sysdescr())),
{noreply, State}; {noreply, State, hibernate};
handle_info(_Info, State) -> handle_info(Info, State) ->
{noreply, State}. ?UNEXPECTED_INFO(Info, State).
terminate(_Reason, #state{heartbeat = Hb, tick_tref = TRef}) -> terminate(_Reason, #state{heartbeat = Hb, tick_tref = TRef}) ->
stop_tick(Hb), stop_tick(Hb),
@ -291,7 +289,7 @@ code_change(_OldVsn, State, _Extra) ->
%%%============================================================================= %%%=============================================================================
create_topic(Topic) -> create_topic(Topic) ->
emqttd_pubsub:create(emqttd_topic:systop(Topic)). emqttd_pubsub:create(topic, emqttd_topic:systop(Topic)).
retain(brokers) -> retain(brokers) ->
Payload = list_to_binary(string:join([atom_to_list(N) || N <- running_nodes()], ",")), Payload = list_to_binary(string:join([atom_to_list(N) || N <- running_nodes()], ",")),

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd cli
%%% emqttd cli.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_cli). -module(emqttd_cli).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_cli.hrl"). -include("emqttd_cli.hrl").
@ -42,6 +39,8 @@
clients/1, sessions/1, plugins/1, listeners/1, clients/1, sessions/1, plugins/1, listeners/1,
vm/1, mnesia/1, trace/1]). vm/1, mnesia/1, trace/1]).
%% TODO: topics, subscriptions...
-define(PROC_INFOKEYS, [status, -define(PROC_INFOKEYS, [status,
memory, memory,
message_queue_len, message_queue_len,
@ -50,6 +49,8 @@
stack_size, stack_size,
reductions]). reductions]).
-define(APP, emqttd).
load() -> load() ->
Cmds = [Fun || {Fun, _} <- ?MODULE:module_info(exports), is_cmd(Fun)], Cmds = [Fun || {Fun, _} <- ?MODULE:module_info(exports), is_cmd(Fun)],
[emqttd_ctl:register_cmd(Cmd, {?MODULE, Cmd}, []) || Cmd <- Cmds]. [emqttd_ctl:register_cmd(Cmd, {?MODULE, Cmd}, []) || Cmd <- Cmds].
@ -68,10 +69,10 @@ is_cmd(Fun) ->
status([]) -> status([]) ->
{InternalStatus, _ProvidedStatus} = init:get_status(), {InternalStatus, _ProvidedStatus} = init:get_status(),
?PRINT("Node ~p is ~p~n", [node(), InternalStatus]), ?PRINT("Node ~p is ~p~n", [node(), InternalStatus]),
case lists:keysearch(emqttd, 1, application:which_applications()) of case lists:keysearch(?APP, 1, application:which_applications()) of
false -> false ->
?PRINT_MSG("emqttd is not running~n"); ?PRINT_MSG("emqttd is not running~n");
{value, {emqttd, _Desc, Vsn}} -> {value, {?APP, _Desc, Vsn}} ->
?PRINT("emqttd ~s is running~n", [Vsn]) ?PRINT("emqttd ~s is running~n", [Vsn])
end; end;
status(_) -> status(_) ->
@ -132,13 +133,9 @@ cluster([SNode]) ->
false -> false ->
cluster(Node, fun() -> cluster(Node, fun() ->
emqttd_plugins:unload(), emqttd_plugins:unload(),
application:stop(emqttd), stop_apps(),
application:stop(esockd),
application:stop(gproc),
emqttd_mnesia:cluster(Node), emqttd_mnesia:cluster(Node),
application:start(gproc), start_apps()
application:start(esockd),
application:start(emqttd)
end) end)
end; end;
@ -160,6 +157,12 @@ cluster(pong, Node, DoCluster) ->
cluster(pang, Node, _DoCluster) -> cluster(pang, Node, _DoCluster) ->
?PRINT("Cannot connect to ~s~n", [Node]). ?PRINT("Cannot connect to ~s~n", [Node]).
stop_apps() ->
[application:stop(App) || App <- [emqttd, esockd, gproc]].
start_apps() ->
[application:start(App) || App <- [gproc, esockd, emqttd]].
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Query clients %% @doc Query clients
%% @end %% @end

View File

@ -19,14 +19,13 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc MQTT Client Connection
%%% MQTT Client Connection.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_client). -module(emqttd_client).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(gen_server).
-include("emqttd.hrl"). -include("emqttd.hrl").
@ -34,12 +33,10 @@
-include("emqttd_internal.hrl"). -include("emqttd_internal.hrl").
-behaviour(gen_server).
%% API Function Exports %% API Function Exports
-export([start_link/2, session/1, info/1, kick/1]). -export([start_link/2, session/1, info/1, kick/1]).
%% SUB/UNSUB Asynchronously, called by plugins. %% SUB/UNSUB Asynchronously. Called by plugins.
-export([subscribe/2, unsubscribe/2]). -export([subscribe/2, unsubscribe/2]).
%% gen_server Function Exports %% gen_server Function Exports
@ -131,8 +128,7 @@ handle_call(kick, _From, State) ->
{stop, {shutdown, kick}, ok, State}; {stop, {shutdown, kick}, ok, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?LOG(critical, "Unexpected request: ~p", [Req], State), ?UNEXPECTED_REQ(Req, State).
{reply, {error, unsupported_request}, State}.
handle_cast({subscribe, TopicTable}, State) -> handle_cast({subscribe, TopicTable}, State) ->
with_session(fun(SessPid) -> with_session(fun(SessPid) ->
@ -145,8 +141,7 @@ handle_cast({unsubscribe, Topics}, State) ->
end, State); end, State);
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?LOG(critical, "Unexpected msg: ~p", [Msg], State), ?UNEXPECTED_MSG(Msg, State).
noreply(State).
handle_info(timeout, State) -> handle_info(timeout, State) ->
shutdown(idle_timeout, State); shutdown(idle_timeout, State);
@ -214,8 +209,7 @@ handle_info({keepalive, check}, State = #client_state{keepalive = KeepAlive}) ->
end; end;
handle_info(Info, State) -> handle_info(Info, State) ->
?LOG(critical, "Unexpected info: ~p", [Info], State), ?UNEXPECTED_INFO(Info, State).
noreply(State).
terminate(Reason, #client_state{connection = Connection, terminate(Reason, #client_state{connection = Connection,
keepalive = KeepAlive, keepalive = KeepAlive,
@ -246,7 +240,7 @@ with_session(Fun, State = #client_state{proto_state = ProtoState}) ->
Fun(emqttd_protocol:session(ProtoState)), Fun(emqttd_protocol:session(ProtoState)),
hibernate(State). hibernate(State).
%% receive and parse tcp data %% Receive and parse tcp data
received(<<>>, State) -> received(<<>>, State) ->
hibernate(State); hibernate(State);

View File

@ -19,19 +19,18 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc MQTT Client Manager
%%% MQTT Client Manager
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_cm). -module(emqttd_cm).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_internal.hrl").
%% API Exports %% API Exports
-export([start_link/2, pool/0]). -export([start_link/3]).
-export([lookup/1, lookup_proc/1, register/1, unregister/1]). -export([lookup/1, lookup_proc/1, register/1, unregister/1]).
@ -44,28 +43,27 @@
%% gen_server2 priorities %% gen_server2 priorities
-export([prioritise_call/4, prioritise_cast/3, prioritise_info/3]). -export([prioritise_call/4, prioritise_cast/3, prioritise_info/3]).
-record(state, {id, statsfun, monitors}). -record(state, {pool, id, statsfun, monitors}).
-define(CM_POOL, ?MODULE). -define(POOL, ?MODULE).
%%%============================================================================= %%%=============================================================================
%%% API %%% API
%%%============================================================================= %%%=============================================================================
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Start client manager %% @doc Start Client Manager
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec start_link(Id, StatsFun) -> {ok, pid()} | ignore | {error, any()} when -spec start_link(Pool, Id, StatsFun) -> {ok, pid()} | ignore | {error, any()} when
Pool :: atom(),
Id :: pos_integer(), Id :: pos_integer(),
StatsFun :: fun(). StatsFun :: fun().
start_link(Id, StatsFun) -> start_link(Pool, Id, StatsFun) ->
gen_server2:start_link(?MODULE, [Id, StatsFun], []). gen_server2:start_link(?MODULE, [Pool, Id, StatsFun], []).
pool() -> ?CM_POOL.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Lookup client by clientId %% @doc Lookup Client by ClientId
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec lookup(ClientId :: binary()) -> mqtt_client() | undefined. -spec lookup(ClientId :: binary()) -> mqtt_client() | undefined.
@ -81,19 +79,18 @@ lookup(ClientId) when is_binary(ClientId) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec lookup_proc(ClientId :: binary()) -> pid() | undefined. -spec lookup_proc(ClientId :: binary()) -> pid() | undefined.
lookup_proc(ClientId) when is_binary(ClientId) -> lookup_proc(ClientId) when is_binary(ClientId) ->
try ets:lookup_element(mqtt_client, ClientId, #mqtt_client.client_pid) of try ets:lookup_element(mqtt_client, ClientId, #mqtt_client.client_pid)
Pid -> Pid
catch catch
error:badarg -> undefined error:badarg -> undefined
end. end.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Register clientId with pid. %% @doc Register ClientId with Pid.
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec register(Client :: mqtt_client()) -> ok. -spec register(Client :: mqtt_client()) -> ok.
register(Client = #mqtt_client{client_id = ClientId}) -> register(Client = #mqtt_client{client_id = ClientId}) ->
CmPid = gproc_pool:pick_worker(?CM_POOL, ClientId), CmPid = gproc_pool:pick_worker(?POOL, ClientId),
gen_server2:cast(CmPid, {register, Client}). gen_server2:cast(CmPid, {register, Client}).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -102,16 +99,18 @@ register(Client = #mqtt_client{client_id = ClientId}) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec unregister(ClientId :: binary()) -> ok. -spec unregister(ClientId :: binary()) -> ok.
unregister(ClientId) when is_binary(ClientId) -> unregister(ClientId) when is_binary(ClientId) ->
CmPid = gproc_pool:pick_worker(?CM_POOL, ClientId), CmPid = gproc_pool:pick_worker(?POOL, ClientId),
gen_server2:cast(CmPid, {unregister, ClientId, self()}). gen_server2:cast(CmPid, {unregister, ClientId, self()}).
%%%============================================================================= %%%=============================================================================
%%% gen_server callbacks %%% gen_server callbacks
%%%============================================================================= %%%=============================================================================
init([Id, StatsFun]) -> init([Pool, Id, StatsFun]) ->
gproc_pool:connect_worker(?CM_POOL, {?MODULE, Id}), ?GPROC_POOL(join, Pool, Id),
{ok, #state{id = Id, statsfun = StatsFun, monitors = dict:new()}}. {ok, #state{pool = Pool, id = Id,
statsfun = StatsFun,
monitors = dict:new()}}.
prioritise_call(_Req, _From, _Len, _State) -> prioritise_call(_Req, _From, _Len, _State) ->
1. 1.
@ -127,8 +126,7 @@ prioritise_info(_Msg, _Len, _State) ->
3. 3.
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
lager:error("Unexpected request: ~p", [Req]), ?UNEXPECTED_REQ(Req, State).
{reply, {error, unsupported_req}, State}.
handle_cast({register, Client = #mqtt_client{client_id = ClientId, handle_cast({register, Client = #mqtt_client{client_id = ClientId,
client_pid = Pid}}, State) -> client_pid = Pid}}, State) ->
@ -150,8 +148,7 @@ handle_cast({unregister, ClientId, Pid}, State) ->
end; end;
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
lager:error("Unexpected Msg: ~p", [Msg]), ?UNEXPECTED_MSG(Msg, State).
{noreply, State}.
handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) -> handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
case dict:find(MRef, State#state.monitors) of case dict:find(MRef, State#state.monitors) of
@ -169,12 +166,10 @@ handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
end; end;
handle_info(Info, State) -> handle_info(Info, State) ->
lager:error("Unexpected Info: ~p", [Info]), ?UNEXPECTED_INFO(Info, State).
{noreply, State}.
terminate(_Reason, #state{id = Id}) -> terminate(_Reason, #state{pool = Pool, id = Id}) ->
gproc_pool:disconnect_worker(?CM_POOL, {?MODULE, Id}), ?GPROC_POOL(leave, Pool, Id), ok.
ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -183,12 +178,12 @@ code_change(_OldVsn, State, _Extra) ->
%%% Internal functions %%% Internal functions
%%%============================================================================= %%%=============================================================================
monitor_client(ClientId, Pid, State = #state{monitors = Monintors}) -> monitor_client(ClientId, Pid, State = #state{monitors = Monitors}) ->
MRef = erlang:monitor(process, Pid), MRef = erlang:monitor(process, Pid),
State#state{monitors = dict:store(MRef, {ClientId, Pid}, Monintors)}. State#state{monitors = dict:store(MRef, {ClientId, Pid}, Monitors)}.
erase_monitor(MRef, State = #state{monitors = Monintors}) -> erase_monitor(MRef, State = #state{monitors = Monitors}) ->
State#state{monitors = dict:erase(MRef, Monintors)}. State#state{monitors = dict:erase(MRef, Monitors)}.
setstats(State = #state{statsfun = StatsFun}) -> setstats(State = #state{statsfun = StatsFun}) ->
StatsFun(ets:info(mqtt_client, size)), State. StatsFun(ets:info(mqtt_client, size)), State.

View File

@ -19,41 +19,45 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Client Manager Supervisor.
%%% emqttd client manager supervisor.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_cm_sup). -module(emqttd_cm_sup).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(supervisor).
-include("emqttd.hrl"). -include("emqttd.hrl").
-behaviour(supervisor).
%% API %% API
-export([start_link/0]). -export([start_link/0]).
%% Supervisor callbacks %% Supervisor callbacks
-export([init/1]). -export([init/1]).
-define(CM, emqttd_cm).
-define(TAB, mqtt_client).
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) -> init([]) ->
ets:new(mqtt_client, [ordered_set, named_table, public, %% Create client table
{keypos, 2}, {write_concurrency, true}]), create_client_tab(),
Schedulers = erlang:system_info(schedulers),
gproc_pool:new(emqttd_cm:pool(), hash, [{size, Schedulers}]),
StatsFun = emqttd_stats:statsfun('clients/count', 'clients/max'),
Children = lists:map(
fun(I) ->
Name = {emqttd_cm, I},
gproc_pool:add_worker(emqttd_cm:pool(), Name, I),
{Name, {emqttd_cm, start_link, [I, StatsFun]},
permanent, 10000, worker, [emqttd_cm]}
end, lists:seq(1, Schedulers)),
{ok, {{one_for_all, 10, 100}, Children}}.
%% CM Pool Sup
MFA = {?CM, start_link, [emqttd_stats:statsfun('clients/count', 'clients/max')]},
PoolSup = emqttd_pool_sup:spec([?CM, hash, erlang:system_info(schedulers), MFA]),
{ok, {{one_for_all, 10, 3600}, [PoolSup]}}.
create_client_tab() ->
case ets:info(?TAB, name) of
undefined ->
ets:new(?TAB, [ordered_set, named_table, public,
{keypos, 2}, {write_concurrency, true}]);
_ ->
ok
end.

View File

@ -19,21 +19,18 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd control
%%% emqttd control.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_ctl). -module(emqttd_ctl).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(gen_server).
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_cli.hrl"). -include("emqttd_cli.hrl").
-behaviour(gen_server).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
%% API Function Exports %% API Function Exports

View File

@ -19,10 +19,9 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd distribution functions
%%% emqttd distribution functions.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_dist). -module(emqttd_dist).

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd gen_mod behaviour
%%% emqttd gen_mod behaviour
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_gen_mod). -module(emqttd_gen_mod).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-ifdef(use_specs). -ifdef(use_specs).

View File

@ -34,11 +34,11 @@
%%% 4. Sequence: 2 bytes sequence in one process %%% 4. Sequence: 2 bytes sequence in one process
%%% %%%
%%% @end %%% @end
%%%
%%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_guid). -module(emqttd_guid).
-author("Feng Lee <feng@emqtt.io>").
-export([gen/0, new/0, timestamp/1]). -export([gen/0, new/0, timestamp/1]).
-define(MAX_SEQ, 16#FFFF). -define(MAX_SEQ, 16#FFFF).

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd http publish API and websocket client.
%%% emqttd http publish API and websocket client.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_http). -module(emqttd_http).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").
@ -44,7 +41,7 @@ handle_request('GET', "/status", Req) ->
AppStatus = AppStatus =
case lists:keysearch(emqttd, 1, application:which_applications()) of case lists:keysearch(emqttd, 1, application:which_applications()) of
false -> not_running; false -> not_running;
{value, _Ver} -> running {value, _Val} -> running
end, end,
Status = io_lib:format("Node ~s is ~s~nemqttd is ~s", Status = io_lib:format("Node ~s is ~s~nemqttd is ~s",
[node(), InternalStatus, AppStatus]), [node(), InternalStatus, AppStatus]),
@ -81,7 +78,7 @@ handle_request('POST', "/mqtt/publish", Req) ->
%% MQTT Over WebSocket %% MQTT Over WebSocket
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
handle_request('GET', "/mqtt", Req) -> handle_request('GET', "/mqtt", Req) ->
lager:info("Websocket Connection from: ~s", [Req:get(peer)]), lager:info("WebSocket Connection from: ~s", [Req:get(peer)]),
Upgrade = Req:get_header_value("Upgrade"), Upgrade = Req:get_header_value("Upgrade"),
Proto = Req:get_header_value("Sec-WebSocket-Protocol"), Proto = Req:get_header_value("Sec-WebSocket-Protocol"),
case {is_websocket(Upgrade), Proto} of case {is_websocket(Upgrade), Proto} of

View File

@ -21,12 +21,10 @@
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc client keepalive %%% @doc client keepalive
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_keepalive). -module(emqttd_keepalive).
-author("Feng Lee <feng@emqtt.io>").
-export([start/3, check/1, cancel/1]). -export([start/3, check/1, cancel/1]).
-record(keepalive, {statfun, statval, -record(keepalive, {statfun, statval,

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc MQTT Message Functions
%%% MQTT Message Functions
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_message). -module(emqttd_message).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").

View File

@ -19,21 +19,18 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd metrics. responsible for collecting broker metrics
%%% emqttd metrics. responsible for collecting broker metrics.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_metrics). -module(emqttd_metrics).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(gen_server).
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").
-behaviour(gen_server).
-define(SERVER, ?MODULE). -define(SERVER, ?MODULE).
%% API Function Exports %% API Function Exports
@ -286,14 +283,14 @@ key(counter, Metric) ->
%%%============================================================================= %%%=============================================================================
init([]) -> init([]) ->
random:seed(now()), random:seed(os:timestamp()),
Metrics = ?SYSTOP_BYTES ++ ?SYSTOP_PACKETS ++ ?SYSTOP_MESSAGES, Metrics = ?SYSTOP_BYTES ++ ?SYSTOP_PACKETS ++ ?SYSTOP_MESSAGES,
% Create metrics table % Create metrics table
ets:new(?METRIC_TAB, [set, public, named_table, {write_concurrency, true}]), ets:new(?METRIC_TAB, [set, public, named_table, {write_concurrency, true}]),
% Init metrics % Init metrics
[create_metric(Metric) || Metric <- Metrics], [create_metric(Metric) || Metric <- Metrics],
% $SYS Topics for metrics % $SYS Topics for metrics
[ok = emqttd_pubsub:create(metric_topic(Topic)) || {_, Topic} <- Metrics], [ok = emqttd_pubsub:create(topic, metric_topic(Topic)) || {_, Topic} <- Metrics],
% Tick to publish metrics % Tick to publish metrics
{ok, #state{tick_tref = emqttd_broker:start_tick(tick)}, hibernate}. {ok, #state{tick_tref = emqttd_broker:start_tick(tick)}, hibernate}.

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd mnesia
%%% emqttd mnesia.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_mnesia). -module(emqttd_mnesia).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-export([start/0, cluster/1]). -export([start/0, cluster/1]).
@ -122,8 +119,7 @@ copy_table(Table) ->
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
wait_for_tables() -> wait_for_tables() ->
%%TODO: is not right? %% io:format("mnesia wait_for_tables: ~p~n", [mnesia:system_info(local_tables)]),
%%lager:info("local_tables: ~p", [mnesia:system_info(local_tables)]),
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity). mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------

View File

@ -19,21 +19,18 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd auto subscribe module.
%%% emqttd auto subscribe module.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_mod_autosub). -module(emqttd_mod_autosub).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(emqttd_gen_mod).
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").
-behaviour(emqttd_gen_mod).
-export([load/1, client_connected/3, unload/1]). -export([load/1, client_connected/3, unload/1]).
-record(state, {topics}). -record(state, {topics}).

View File

@ -19,19 +19,16 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd presence management module
%%% emqttd presence management module.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_mod_presence). -module(emqttd_mod_presence).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(emqttd_gen_mod).
-include("emqttd.hrl"). -include("emqttd.hrl").
-behaviour(emqttd_gen_mod).
-export([load/1, unload/1]). -export([load/1, unload/1]).
-export([client_connected/3, client_disconnected/3]). -export([client_connected/3, client_disconnected/3]).

View File

@ -19,19 +19,16 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd rewrite module
%%% emqttd rewrite module.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_mod_rewrite). -module(emqttd_mod_rewrite).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(emqttd_gen_mod).
-include("emqttd.hrl"). -include("emqttd.hrl").
-behaviour(emqttd_gen_mod).
-export([load/1, reload/1, unload/1]). -export([load/1, reload/1, unload/1]).
-export([rewrite/3, rewrite/4]). -export([rewrite/3, rewrite/4]).

View File

@ -19,19 +19,16 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd module supervisor.
%%% emqttd module supervisor.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_mod_sup). -module(emqttd_mod_sup).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(supervisor).
-include("emqttd.hrl"). -include("emqttd.hrl").
-behaviour(supervisor).
%% API %% API
-export([start_link/0, start_child/1, start_child/2]). -export([start_link/0, start_child/1, start_child/2]).

View File

@ -46,11 +46,11 @@
%%% otherwise dropped the oldest pending one. %%% otherwise dropped the oldest pending one.
%%% %%%
%%% @end %%% @end
%%%
%%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_mqueue). -module(emqttd_mqueue).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd net utility functions. some functions copied from rabbitmq.
%%% emqttd net utility functions. some functions copied from rabbitmq.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_net). -module(emqttd_net).
-author("Feng Lee <feng@emqtt.io>").
-include_lib("kernel/include/inet.hrl"). -include_lib("kernel/include/inet.hrl").
-export([tcp_name/3, tcp_host/1, getopts/2, setopts/2, -export([tcp_name/3, tcp_host/1, getopts/2, setopts/2,

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd options handler.
%%% emqttd options handler.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_opts). -module(emqttd_opts).
-author("Feng Lee <feng@emqtt.io>").
-export([merge/2, g/2, g/3]). -export([merge/2, g/2, g/3]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -38,10 +35,8 @@ merge(Defaults, Options) ->
lists:foldl( lists:foldl(
fun({Opt, Val}, Acc) -> fun({Opt, Val}, Acc) ->
case lists:keymember(Opt, 1, Acc) of case lists:keymember(Opt, 1, Acc) of
true -> true -> lists:keyreplace(Opt, 1, Acc, {Opt, Val});
lists:keyreplace(Opt, 1, Acc, {Opt, Val}); false -> [{Opt, Val}|Acc]
false ->
[{Opt, Val}|Acc]
end; end;
(Opt, Acc) -> (Opt, Acc) ->
case lists:member(Opt, Acc) of case lists:member(Opt, Acc) of

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc MQTT Packet Functions
%%% MQTT Packet Functions
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_packet). -module(emqttd_packet).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc MQTT Packet Parser
%%% MQTT Packet Parser.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_parser). -module(emqttd_parser).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd plugins.
%%% emqttd plugins.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_plugins). -module(emqttd_plugins).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-export([load/0, unload/0]). -export([load/0, unload/0]).

77
src/emqttd_pool_sup.erl Normal file
View File

@ -0,0 +1,77 @@
%%%-----------------------------------------------------------------------------
%%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in all
%%% copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE.
%%%-----------------------------------------------------------------------------
%%% @doc Common Pool Supervisor
%%%
%%% @author Feng Lee <feng@emqtt.io>
%%%-----------------------------------------------------------------------------
-module(emqttd_pool_sup).
-behaviour(supervisor).
%% API
-export([spec/1, spec/2, start_link/3, start_link/4]).
%% Supervisor callbacks
-export([init/1]).
-spec spec(list()) -> supervisor:child_spec().
spec(Args) ->
spec(pool_sup, Args).
-spec spec(any(), list()) -> supervisor:child_spec().
spec(ChildId, Args) ->
{ChildId, {?MODULE, start_link, Args},
transient, infinity, supervisor, [?MODULE]}.
-spec start_link(atom(), atom(), mfa()) -> {ok, pid()} | {error, any()}.
start_link(Pool, Type, MFA) ->
Schedulers = erlang:system_info(schedulers),
start_link(Pool, Type, Schedulers, MFA).
-spec start_link(atom(), atom(), pos_integer(), mfa()) -> {ok, pid()} | {error, any()}.
start_link(Pool, Type, Size, MFA) ->
supervisor:start_link({local, sup_name(Pool)}, ?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}, [
begin
ensure_pool_worker(Pool, {Pool, I}, I),
{{M, I}, {M, F, [Pool, I | Args]},
transient, 5000, worker, [M]}
end || I <- lists:seq(1, Size)]}}.
ensure_pool(Pool, Type, Opts) ->
try gproc_pool:new(Pool, Type, Opts)
catch
error:exists -> ok
end.
ensure_pool_worker(Pool, Name, Slot) ->
try gproc_pool:add_worker(Pool, Name, Slot)
catch
error:exists -> ok
end.

View File

@ -19,32 +19,41 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd pooler.
%%% emqttd pooler.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_pooler). -module(emqttd_pooler).
-author("Feng Lee <feng@emqtt.io>").
-behaviour(gen_server). -behaviour(gen_server).
-include("emqttd_internal.hrl").
%% Start the pool supervisor
-export([start_link/0]).
%% API Exports %% API Exports
-export([start_link/1, submit/1, async_submit/1]). -export([start_link/2, submit/1, async_submit/1]).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-record(state, {id}). -record(state, {pool, id}).
%%------------------------------------------------------------------------------
%% @doc Start Pooler Supervisor.
%% @end
%%------------------------------------------------------------------------------
start_link() ->
emqttd_pool_sup:start_link(pooler, random, {?MODULE, start_link, []}).
%%%============================================================================= %%%=============================================================================
%%% API %%% API
%%%============================================================================= %%%=============================================================================
-spec start_link(Id :: pos_integer()) -> {ok, pid()} | ignore | {error, any()}. -spec start_link(atom(), pos_integer()) -> {ok, pid()} | ignore | {error, any()}.
start_link(Id) -> start_link(Pool, Id) ->
gen_server:start_link({local, name(Id)}, ?MODULE, [Id], []). gen_server:start_link({local, name(Id)}, ?MODULE, [Pool, Id], []).
name(Id) -> name(Id) ->
list_to_atom(lists:concat([?MODULE, "_", integer_to_list(Id)])). list_to_atom(lists:concat([?MODULE, "_", integer_to_list(Id)])).
@ -54,22 +63,25 @@ name(Id) ->
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
submit(Fun) -> submit(Fun) ->
gen_server:call(gproc_pool:pick_worker(pooler), {submit, Fun}, infinity). gen_server:call(worker(), {submit, Fun}, infinity).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Submit work to pooler asynchronously %% @doc Submit work to pooler asynchronously
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
async_submit(Fun) -> async_submit(Fun) ->
gen_server:cast(gproc_pool:pick_worker(pooler), {async_submit, Fun}). gen_server:cast(worker(), {async_submit, Fun}).
worker() ->
gproc_pool:pick_worker(pooler).
%%%============================================================================= %%%=============================================================================
%%% gen_server callbacks %%% gen_server callbacks
%%%============================================================================= %%%=============================================================================
init([Id]) -> init([Pool, Id]) ->
gproc_pool:connect_worker(pooler, {pooler, Id}), ?GPROC_POOL(join, Pool, Id),
{ok, #state{id = Id}}. {ok, #state{pool = Pool, id = Id}}.
handle_call({submit, Fun}, _From, State) -> handle_call({submit, Fun}, _From, State) ->
{reply, run(Fun), State}; {reply, run(Fun), State};
@ -90,8 +102,8 @@ handle_cast(_Msg, State) ->
handle_info(_Info, State) -> handle_info(_Info, State) ->
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{id = I}) -> terminate(_Reason, #state{pool = Pool, id = Id}) ->
gproc_pool:disconnect_worker(pooler, {pooler, I}), ok. ?GPROC_POOL(leave, Pool, Id), ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -105,4 +117,3 @@ run({M, F, A}) ->
run(Fun) when is_function(Fun) -> run(Fun) when is_function(Fun) ->
Fun(). Fun().

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd protocol.
%%% emqttd protocol.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_protocol). -module(emqttd_protocol).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").
@ -263,7 +260,7 @@ send(Packet, State = #proto_state{sendfun = SendFun})
when is_record(Packet, mqtt_packet) -> when is_record(Packet, mqtt_packet) ->
trace(send, Packet, State), trace(send, Packet, State),
emqttd_metrics:sent(Packet), emqttd_metrics:sent(Packet),
Data = emqttd_serialiser:serialise(Packet), Data = emqttd_serializer:serialize(Packet),
?LOG(debug, "SEND ~p", [Data], State), ?LOG(debug, "SEND ~p", [Data], State),
emqttd_metrics:inc('bytes/sent', size(Data)), emqttd_metrics:inc('bytes/sent', size(Data)),
SendFun(Data), SendFun(Data),

View File

@ -19,19 +19,20 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd pubsub
%%% emqttd pubsub.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_pubsub). -module(emqttd_pubsub).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(gen_server2).
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").
-include("emqttd_internal.hrl").
%% Mnesia Callbacks %% Mnesia Callbacks
-export([mnesia/1]). -export([mnesia/1]).
@ -39,59 +40,82 @@
-copy_mnesia({mnesia, [copy]}). -copy_mnesia({mnesia, [copy]}).
%% API Exports %% API Exports
-export([start_link/2]). -export([start_link/4]).
-export([create/1, -export([create/2, subscribe/1, subscribe/2,
subscribe/1, subscribe/2, unsubscribe/1, unsubscribe/2, publish/1]).
unsubscribe/1,
publish/1]).
%% Local node %% Local node
-export([dispatch/2, match/1]). -export([match/1]).
-behaviour(gen_server2).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
%% gen_server2 priorities -ifdef(TEST).
-export([prioritise_call/4, prioritise_cast/3, prioritise_info/3]). -compile(export_all).
-endif.
-define(POOL, pubsub). -record(state, {pool, id, statsfun}).
-record(state, {id, submap :: map()}). -define(ROUTER, emqttd_router).
-define(HELPER, emqttd_pubsub_helper).
%%%============================================================================= %%%=============================================================================
%%% Mnesia callbacks %%% Mnesia callbacks
%%%============================================================================= %%%=============================================================================
mnesia(boot) -> mnesia(boot) ->
%% p2p queue table ok = create_table(topic, ram_copies),
ok = emqttd_mnesia:create_table(queue, [ if_subscription(fun(RamOrDisc) ->
{type, set}, ok = create_table(subscription, RamOrDisc)
{ram_copies, [node()]}, end);
{record_name, mqtt_queue},
{attributes, record_info(fields, mqtt_queue)}]),
%% topic table
ok = emqttd_mnesia:create_table(topic, [
{type, bag},
{ram_copies, [node()]},
{record_name, mqtt_topic},
{attributes, record_info(fields, mqtt_topic)}]),
%% local subscriber table, not shared with other nodes
ok = emqttd_mnesia:create_table(subscriber, [
{type, bag},
{ram_copies, [node()]},
{record_name, mqtt_subscriber},
{attributes, record_info(fields, mqtt_subscriber)},
{index, [subpid]},
{local_content, true}]);
mnesia(copy) -> mnesia(copy) ->
ok = emqttd_mnesia:copy_table(queue),
ok = emqttd_mnesia:copy_table(topic), ok = emqttd_mnesia:copy_table(topic),
ok = emqttd_mnesia:copy_table(subscriber). %% Only one disc_copy???
if_subscription(fun(_RamOrDisc) ->
ok = emqttd_mnesia:copy_table(subscription)
end).
%% Topic Table
create_table(topic, RamOrDisc) ->
emqttd_mnesia:create_table(topic, [
{type, bag},
{RamOrDisc, [node()]},
{record_name, mqtt_topic},
{attributes, record_info(fields, mqtt_topic)}]);
%% Subscription Table
create_table(subscription, RamOrDisc) ->
emqttd_mnesia:create_table(subscription, [
{type, bag},
{RamOrDisc, [node()]},
{record_name, mqtt_subscription},
{attributes, record_info(fields, mqtt_subscription)},
{storage_properties, [{ets, [compressed]},
{dets, [{auto_save, 5000}]}]}]).
if_subscription(Fun) ->
case env(subscription) of
disc -> Fun(disc_copies);
ram -> Fun(ram_copies);
false -> ok;
undefined -> ok
end.
env(Key) ->
case get({pubsub, Key}) of
undefined ->
cache_env(Key);
Val ->
Val
end.
cache_env(Key) ->
Val = emqttd_opts:g(Key, emqttd_broker:env(pubsub)),
put({pubsub, Key}, Val),
Val.
%%%============================================================================= %%%=============================================================================
%%% API %%% API
@ -101,52 +125,64 @@ mnesia(copy) ->
%% @doc Start one pubsub server %% @doc Start one pubsub server
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec start_link(Id, Opts) -> {ok, pid()} | ignore | {error, any()} when -spec start_link(Pool, Id, StatsFun, Opts) -> {ok, pid()} | ignore | {error, any()} when
Pool :: atom(),
Id :: pos_integer(), Id :: pos_integer(),
Opts :: list(). StatsFun :: fun(),
start_link(Id, Opts) -> Opts :: list(tuple()).
gen_server2:start_link({local, name(Id)}, ?MODULE, [Id, Opts], []). start_link(Pool, Id, StatsFun, Opts) ->
gen_server2:start_link({local, name(Id)}, ?MODULE, [Pool, Id, StatsFun, Opts], []).
name(Id) -> name(Id) ->
list_to_atom("emqttd_pubsub_" ++ integer_to_list(Id)). list_to_atom("emqttd_pubsub_" ++ integer_to_list(Id)).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Create topic. Notice That this transaction is not protected by pubsub pool %% @doc Create Topic or Subscription.
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec create(Topic :: binary()) -> ok | {error, Error :: any()}. -spec create(topic | subscription, binary()) -> ok | {error, any()}.
create(<<"$Q/", _Queue/binary>>) -> create(topic, Topic) when is_binary(Topic) ->
%% protecte from queue Record = #mqtt_topic{topic = Topic, node = node()},
{error, cannot_create_queue}; case mnesia:transaction(fun add_topic/1, [Record]) of
{atomic, ok} -> ok;
{aborted, Error} -> {error, Error}
end;
create(Topic) when is_binary(Topic) -> create(subscription, {SubId, Topic, Qos}) ->
TopicR = #mqtt_topic{topic = Topic, node = node()}, case mnesia:transaction(fun add_subscription/2, [SubId, {Topic, Qos}]) of
case mnesia:transaction(fun add_topic/1, [TopicR]) of {atomic, ok} -> ok;
{atomic, ok} -> {aborted, Error} -> {error, Error}
setstats(topics), ok;
{aborted, Error} ->
{error, Error}
end. end.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Subscribe topic %% @doc Subscribe Topics
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec subscribe({Topic, Qos} | list({Topic, Qos})) -> -spec subscribe({Topic, Qos} | list({Topic, Qos})) ->
{ok, Qos | list(Qos)} | {error, any()} when {ok, Qos | list(Qos)} | {error, any()} when
Topic :: binary(), Topic :: binary(),
Qos :: mqtt_qos() | mqtt_qos_name(). Qos :: mqtt_qos() | mqtt_qos_name().
subscribe({Topic, Qos}) when is_binary(Topic) andalso (?IS_QOS(Qos) orelse is_atom(Qos)) -> subscribe({Topic, Qos}) ->
call({subscribe, self(), Topic, ?QOS_I(Qos)}); subscribe([{Topic, Qos}]);
subscribe(TopicTable) when is_list(TopicTable) ->
call({subscribe, {undefined, self()}, fixqos(TopicTable)}).
subscribe(Topics = [{_Topic, _Qos} | _]) -> -spec subscribe(ClientId, {Topic, Qos} | list({Topic, Qos})) ->
call({subscribe, self(), [{Topic, ?QOS_I(Qos)} || {Topic, Qos} <- Topics]}). {ok, Qos | list(Qos)} | {error, any()} when
ClientId :: binary(),
-spec subscribe(Topic, Qos) -> {ok, Qos} when
Topic :: binary(), Topic :: binary(),
Qos :: mqtt_qos() | mqtt_qos_name(). Qos :: mqtt_qos() | mqtt_qos_name().
subscribe(Topic, Qos) -> subscribe(ClientId, {Topic, Qos}) when is_binary(ClientId) ->
subscribe({Topic, Qos}). subscribe(ClientId, [{Topic, Qos}]);
subscribe(ClientId, TopicTable) when is_binary(ClientId) andalso is_list(TopicTable) ->
call({subscribe, {ClientId, self()}, fixqos(TopicTable)}).
fixqos(TopicTable) ->
[{Topic, ?QOS_I(Qos)} || {Topic, Qos} <- TopicTable].
call(Request) ->
PubSub = gproc_pool:pick_worker(pubsub, self()),
gen_server2:call(PubSub, Request, infinity).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Unsubscribe Topic or Topics %% @doc Unsubscribe Topic or Topics
@ -154,18 +190,19 @@ subscribe(Topic, Qos) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec unsubscribe(binary() | list(binary())) -> ok. -spec unsubscribe(binary() | list(binary())) -> ok.
unsubscribe(Topic) when is_binary(Topic) -> unsubscribe(Topic) when is_binary(Topic) ->
cast({unsubscribe, self(), Topic}); unsubscribe([Topic]);
unsubscribe(Topics = [Topic|_]) when is_binary(Topic) -> unsubscribe(Topics = [Topic|_]) when is_binary(Topic) ->
cast({unsubscribe, self(), Topics}). cast({unsubscribe, {undefined, self()}, Topics}).
call(Req) -> -spec unsubscribe(binary(), binary() | list(binary())) -> ok.
Pid = gproc_pool:pick_worker(?POOL, self()), unsubscribe(ClientId, Topic) when is_binary(ClientId) andalso is_binary(Topic) ->
gen_server2:call(Pid, Req, infinity). unsubscribe(ClientId, [Topic]);
unsubscribe(ClientId, Topics = [Topic|_]) when is_binary(Topic) ->
cast({unsubscribe, {ClientId, self()}, Topics}).
cast(Msg) -> cast(Msg) ->
Pid = gproc_pool:pick_worker(?POOL, self()), PubSub = gproc_pool:pick_worker(pubsub, self()),
gen_server2:cast(Pid, Msg). gen_server2:cast(PubSub, Msg).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Publish to cluster nodes %% @doc Publish to cluster nodes
@ -174,229 +211,107 @@ cast(Msg) ->
-spec publish(Msg :: mqtt_message()) -> ok. -spec publish(Msg :: mqtt_message()) -> ok.
publish(Msg = #mqtt_message{from = From}) -> publish(Msg = #mqtt_message{from = From}) ->
trace(publish, From, Msg), trace(publish, From, Msg),
Msg1 = #mqtt_message{topic = Topic} Msg1 = #mqtt_message{topic = To}
= emqttd_broker:foldl_hooks('message.publish', [], Msg), = emqttd_broker:foldl_hooks('message.publish', [], Msg),
%% Retain message first. Don't create retained topic. %% Retain message first. Don't create retained topic.
case emqttd_retained:retain(Msg1) of case emqttd_retainer:retain(Msg1) of
ok -> ok ->
%% TODO: why unset 'retain' flag? %% TODO: why unset 'retain' flag?
publish(Topic, emqttd_message:unset_flag(Msg1)); publish(To, emqttd_message:unset_flag(Msg1));
ignore -> ignore ->
publish(Topic, Msg1) publish(To, Msg1)
end. end.
publish(Queue = <<"$Q/", _/binary>>, Msg = #mqtt_message{qos = Qos}) -> publish(To, Msg) ->
lists:foreach( lists:foreach(fun(#mqtt_topic{topic = Topic, node = Node}) ->
fun(#mqtt_queue{qpid = QPid, qos = SubQos}) ->
Msg1 = if
Qos > SubQos -> Msg#mqtt_message{qos = SubQos};
true -> Msg
end,
QPid ! {dispatch, Msg1}
end, mnesia:dirty_read(queue, Queue));
publish(Topic, Msg) when is_binary(Topic) ->
lists:foreach(fun(#mqtt_topic{topic=Name, node=Node}) ->
case Node =:= node() of case Node =:= node() of
true -> dispatch(Name, Msg); true -> ?ROUTER:route(Topic, Msg);
false -> rpc:cast(Node, ?MODULE, dispatch, [Name, Msg]) false -> rpc:cast(Node, ?ROUTER, route, [Topic, Msg])
end end
end, match(Topic)). end, match(To)).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Dispatch message locally. should only be called by publish. %% @doc Match Topic Name with Topic Filters
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec dispatch(Topic :: binary(), Msg :: mqtt_message()) -> non_neg_integer(). -spec match(binary()) -> [mqtt_topic()].
dispatch(Topic, Msg = #mqtt_message{qos = Qos}) when is_binary(Topic) -> match(To) ->
Subscribers = mnesia:dirty_read(subscriber, Topic), MatchedTopics = mnesia:async_dirty(fun emqttd_trie:match/1, [To]),
setstats(dropped, Subscribers =:= []), %% ets:lookup for topic table will be replicated.
lists:foreach( lists:append([ets:lookup(topic, Topic) || Topic <- MatchedTopics]).
fun(#mqtt_subscriber{subpid=SubPid, qos = SubQos}) ->
Msg1 = if
Qos > SubQos -> Msg#mqtt_message{qos = SubQos};
true -> Msg
end,
SubPid ! {dispatch, Msg1}
end, Subscribers),
length(Subscribers).
-spec match(Topic :: binary()) -> [mqtt_topic()].
match(Topic) when is_binary(Topic) ->
MatchedTopics = mnesia:async_dirty(fun emqttd_trie:match/1, [Topic]),
lists:append([mnesia:dirty_read(topic, Name) || Name <- MatchedTopics]).
%%%============================================================================= %%%=============================================================================
%%% gen_server callbacks %%% gen_server callbacks
%%%============================================================================= %%%=============================================================================
init([Id, _Opts]) -> init([Pool, Id, StatsFun, Opts]) ->
%%process_flag(priority, high), ?ROUTER:init(Opts),
%%process_flag(min_heap_size, 1024*1024), ?GPROC_POOL(join, Pool, Id),
gproc_pool:connect_worker(pubsub, {?MODULE, Id}), {ok, #state{pool = Pool, id = Id, statsfun = StatsFun}}.
{ok, #state{id = Id, submap = maps:new()}}.
prioritise_call(Msg, _From, _Len, _State) -> handle_call({subscribe, {SubId, SubPid}, TopicTable}, _From,
case Msg of State = #state{statsfun = StatsFun}) ->
{subscribe, _, _} -> 1;
_ -> 0
end.
prioritise_cast(Msg, _Len, _State) -> Topics = [Topic || {Topic, _Qos} <- TopicTable],
case Msg of
{unsubscribe, _, _} -> 2;
_ -> 0
end.
prioritise_info(Msg, _Len, _State) -> %% Add routes first
case Msg of ?ROUTER:add_routes(Topics, SubPid),
{'DOWN', _, _, _, _} -> 3;
_ -> 0
end.
handle_call({subscribe, SubPid, Topics}, _From, State) -> %% Insert topic records to global topic table
TopicSubs = lists:map(fun({<<"$Q/", _/binary>> = Queue, Qos}) -> Records = [#mqtt_topic{topic = Topic, node = node()} || Topic <- Topics],
#mqtt_queue{name = Queue, qpid = SubPid, qos = Qos};
({Topic, Qos}) ->
{#mqtt_topic{topic = Topic, node = node()},
#mqtt_subscriber{topic = Topic, subpid = SubPid, qos = Qos}}
end, Topics),
F = fun() ->
lists:map(fun(QueueR) when is_record(QueueR, mqtt_queue) ->
add_queue(QueueR);
(TopicSub) ->
add_subscriber(TopicSub)
end, TopicSubs)
end,
case mnesia:transaction(F) of
{atomic, _Result} ->
setstats(all),
NewState = monitor_subscriber(SubPid, State),
%%TODO: grant all qos
{reply, {ok, [Qos || {_Topic, Qos} <- Topics]}, NewState};
{aborted, Error} ->
{reply, {error, Error}, State}
end;
handle_call({subscribe, SubPid, <<"$Q/", _/binary>> = Queue, Qos}, _From, State) -> case mnesia:transaction(fun add_topics/1, [Records]) of
case mnesia:dirty_read(queue, Queue) of {atomic, _} ->
[OldQueueR] -> lager:error("Queue is overwrited by ~p: ~p", [SubPid, OldQueueR]); StatsFun(topic),
[] -> ok if_subscription(
end, fun(_) ->
QueueR = #mqtt_queue{name = Queue, qpid = SubPid, qos = Qos}, %% Add subscriptions
case mnesia:transaction(fun add_queue/1, [QueueR]) of Args = [fun add_subscriptions/2, [SubId, TopicTable]],
{atomic, ok} -> emqttd_pooler:async_submit({mnesia, async_dirty, Args}),
setstats(queues), StatsFun(subscription)
{reply, {ok, Qos}, monitor_subscriber(SubPid, State)}; end),
{aborted, Error} -> %% Grant all qos...
{reply, {error, Error}, State} {reply, {ok, [Qos || {_Topic, Qos} <- TopicTable]}, State};
end;
handle_call({subscribe, SubPid, Topic, Qos}, _From, State) ->
TopicR = #mqtt_topic{topic = Topic, node = node()},
Subscriber = #mqtt_subscriber{topic = Topic, subpid = SubPid, qos = Qos},
case mnesia:transaction(fun add_subscriber/1, [{TopicR, Subscriber}]) of
{atomic, ok} ->
setstats(all),
{reply, {ok, Qos}, monitor_subscriber(SubPid, State)};
{aborted, Error} -> {aborted, Error} ->
{reply, {error, Error}, State} {reply, {error, Error}, State}
end; end;
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
lager:error("Bad Request: ~p", [Req]), ?UNEXPECTED_REQ(Req, State).
{reply, {error, badreq}, State}.
handle_cast({unsubscribe, SubPid, Topics}, State) when is_list(Topics) -> handle_cast({unsubscribe, {SubId, SubPid}, Topics}, State = #state{statsfun = StatsFun}) ->
%% Delete routes first
?ROUTER:delete_routes(Topics, SubPid),
TopicSubs = lists:map(fun(<<"$Q/", _/binary>> = Queue) -> %% Remove subscriptions
#mqtt_queue{name = Queue, qpid = SubPid}; if_subscription(
(Topic) -> fun(_) ->
{#mqtt_topic{topic = Topic, node = node()}, Args = [fun remove_subscriptions/2, [SubId, Topics]],
#mqtt_subscriber{topic = Topic, subpid = SubPid, _ = '_'}} emqttd_pooler:async_submit({mnesia, async_dirty, Args}),
end, Topics), StatsFun(subscription)
F = fun() -> end),
lists:foreach(
fun(QueueR) when is_record(QueueR, mqtt_queue) ->
remove_queue(QueueR);
(TopicSub) ->
remove_subscriber(TopicSub)
end, TopicSubs)
end,
case mnesia:transaction(F) of
{atomic, _} -> ok;
{aborted, Error} -> lager:error("unsubscribe ~p error: ~p", [Topics, Error])
end,
setstats(all),
{noreply, State};
handle_cast({unsubscribe, SubPid, <<"$Q/", _/binary>> = Queue}, State) ->
QueueR = #mqtt_queue{name = Queue, qpid = SubPid},
case mnesia:transaction(fun remove_queue/1, [QueueR]) of
{atomic, _} ->
setstats(queues);
{aborted, Error} ->
lager:error("unsubscribe queue ~s error: ~p", [Queue, Error])
end,
{noreply, State};
handle_cast({unsubscribe, SubPid, Topic}, State) ->
TopicR = #mqtt_topic{topic = Topic, node = node()},
Subscriber = #mqtt_subscriber{topic = Topic, subpid = SubPid, _ = '_'},
case mnesia:transaction(fun remove_subscriber/1, [{TopicR, Subscriber}]) of
{atomic, _} -> ok;
{aborted, Error} -> lager:error("unsubscribe ~s error: ~p", [Topic, Error])
end,
setstats(all),
{noreply, State}; {noreply, State};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
lager:error("Bad Msg: ~p", [Msg]), ?UNEXPECTED_MSG(Msg, State).
{noreply, State}.
handle_info({'DOWN', _Mon, _Type, DownPid, _Info}, State = #state{submap = SubMap}) -> handle_info({'DOWN', _Mon, _Type, DownPid, _Info}, State) ->
case maps:is_key(DownPid, SubMap) of
true ->
Node = node(),
F = fun() ->
%% remove queue...
Queues = mnesia:match_object(queue, #mqtt_queue{qpid = DownPid, _ = '_'}, write),
lists:foreach(fun(QueueR) ->
mnesia:delete_object(queue, QueueR, write)
end, Queues),
%% remove subscribers... Routes = ?ROUTER:lookup_routes(DownPid),
Subscribers = mnesia:index_read(subscriber, DownPid, #mqtt_subscriber.subpid),
lists:foreach(fun(Sub = #mqtt_subscriber{topic = Topic}) -> %% Delete all routes of the process
mnesia:delete_object(subscriber, Sub, write), ?ROUTER:delete_routes(DownPid),
try_remove_topic(#mqtt_topic{topic = Topic, node = Node})
end, Subscribers) ?HELPER:aging([Topic || Topic <- Routes, not ?ROUTER:has_route(Topic)]),
end,
case catch mnesia:transaction(F) of {noreply, State, hibernate};
{atomic, _} -> ok;
{aborted, Reason} ->
lager:error("Failed to delete 'DOWN' subscriber ~p: ~p", [DownPid, Reason])
end,
setstats(all),
{noreply, State#state{submap = maps:remove(DownPid, SubMap)}};
false ->
lager:error("Unexpected 'DOWN' from ~p", [DownPid]),
{noreply, State}
end;
handle_info(Info, State) -> handle_info(Info, State) ->
lager:error("Unexpected Info: ~p", [Info]), ?UNEXPECTED_INFO(Info, State).
{noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, #state{pool = Pool, id = Id}) ->
TopicR = #mqtt_topic{_ = '_', node = node()}, ?GPROC_POOL(leave, Pool, Id).
F = fun() ->
[mnesia:delete_object(topic, R, write) || R <- mnesia:match_object(topic, TopicR, write)]
%%TODO: remove trie??
end,
mnesia:transaction(F),
setstats(all).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -405,8 +320,8 @@ code_change(_OldVsn, State, _Extra) ->
%%% Internal functions %%% Internal functions
%%%============================================================================= %%%=============================================================================
add_queue(QueueR) -> add_topics(Records) ->
mnesia:write(queue, QueueR, write). lists:foreach(fun add_topic/1, Records).
add_topic(TopicR = #mqtt_topic{topic = Topic}) -> add_topic(TopicR = #mqtt_topic{topic = Topic}) ->
case mnesia:wread({topic, Topic}) of case mnesia:wread({topic, Topic}) of
@ -420,92 +335,46 @@ add_topic(TopicR = #mqtt_topic{topic = Topic}) ->
end end
end. end.
%% Fix issue #53 - Remove Overlapping Subscriptions add_subscriptions(undefined, _TopicTable) ->
add_subscriber({TopicR, Subscriber = #mqtt_subscriber{topic = Topic, subpid = SubPid, qos = Qos}}) ok;
when is_record(TopicR, mqtt_topic) -> add_subscriptions(SubId, TopicTable) ->
case add_topic(TopicR) of lists:foreach(fun({Topic, Qos}) ->
ok -> add_subscription(SubId, {Topic, Qos})
OverlapSubs = [Sub || Sub = #mqtt_subscriber{topic = SubTopic, qos = SubQos} end,TopicTable).
<- mnesia:index_read(subscriber, SubPid, #mqtt_subscriber.subpid),
SubTopic =:= Topic, SubQos =/= Qos],
%% remove overlapping subscribers add_subscription(SubId, {Topic, Qos}) ->
if Subscription = #mqtt_subscription{subid = SubId, topic = Topic, qos = Qos},
length(OverlapSubs) =:= 0 -> ok; Pattern = #mqtt_subscription{subid = SubId, topic = Topic, qos = '_'},
Records = mnesia:match_object(subscription, Pattern, write),
case lists:member(Subscription, Records) of
true -> true ->
lager:warning("Remove overlapping subscribers: ~p", [OverlapSubs]), ok;
[mnesia:delete_object(subscriber, OverlapSub, write) || OverlapSub <- OverlapSubs]
end,
%% insert subscriber
mnesia:write(subscriber, Subscriber, write);
Error ->
Error
end.
monitor_subscriber(SubPid, State = #state{submap = SubMap}) ->
NewSubMap = case maps:is_key(SubPid, SubMap) of
false -> false ->
maps:put(SubPid, erlang:monitor(process, SubPid), SubMap); [delete_subscription(Record) || Record <- Records],
true -> insert_subscription(Subscription)
SubMap
end,
State#state{submap = NewSubMap}.
remove_queue(#mqtt_queue{name = Name, qpid = Pid}) ->
case mnesia:wread({queue, Name}) of
[R = #mqtt_queue{qpid = Pid}] ->
mnesia:delete(queue, R, write);
_ ->
ok
end. end.
remove_subscriber({TopicR, Subscriber}) when is_record(TopicR, mqtt_topic) -> insert_subscription(Record) ->
[mnesia:delete_object(subscriber, Sub, write) || mnesia:write(subscription, Record, write).
Sub <- mnesia:match_object(subscriber, Subscriber, write)],
try_remove_topic(TopicR).
try_remove_topic(TopicR = #mqtt_topic{topic = Topic}) -> remove_subscriptions(undefined, _Topics) ->
case mnesia:read({subscriber, Topic}) of ok;
[] -> remove_subscriptions(SubId, Topics) ->
mnesia:delete_object(topic, TopicR, write), lists:foreach(fun(Topic) ->
case mnesia:read(topic, Topic) of Pattern = #mqtt_subscription{subid = SubId, topic = Topic, qos = '_'},
[] -> emqttd_trie:delete(Topic); Records = mnesia:match_object(subscription, Pattern, write),
_ -> ok [delete_subscription(Record) || Record <- Records]
end; end, Topics).
_ ->
ok delete_subscription(Record) ->
end. mnesia:delete_object(subscription, Record, write).
%%%============================================================================= %%%=============================================================================
%%% Stats functions %%% Trace Functions
%%%=============================================================================
setstats(all) ->
[setstats(Stat) || Stat <- [queues, topics, subscribers]];
setstats(queues) ->
emqttd_stats:setstats('queues/count', 'queues/max',
mnesia:table_info(queue, size));
setstats(topics) ->
emqttd_stats:setstats('topics/count', 'topics/max',
mnesia:table_info(topic, size));
setstats(subscribers) ->
emqttd_stats:setstats('subscribers/count', 'subscribers/max',
mnesia:table_info(subscriber, size)).
setstats(dropped, false) ->
ignore;
setstats(dropped, true) ->
emqttd_metrics:inc('messages/dropped').
%%%=============================================================================
%%% Trace functions
%%%============================================================================= %%%=============================================================================
trace(publish, From, _Msg) when is_atom(From) -> trace(publish, From, _Msg) when is_atom(From) ->
%%dont' trace broker publish %% Dont' trace '$SYS' publish
ignore; ignore;
trace(publish, From, #mqtt_message{topic = Topic, payload = Payload}) -> trace(publish, From, #mqtt_message{topic = Topic, payload = Payload}) ->

View File

@ -0,0 +1,194 @@
%%%-----------------------------------------------------------------------------
%%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in all
%%% copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE.
%%%-----------------------------------------------------------------------------
%%% @doc PubSub Route Aging Helper
%%%
%%% @author Feng Lee <feng@emqtt.io>
%%%-----------------------------------------------------------------------------
-module(emqttd_pubsub_helper).
-behaviour(gen_server2).
-include("emqttd.hrl").
-include("emqttd_internal.hrl").
%% API Function Exports
-export([start_link/2, aging/1]).
%% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-ifdef(TEST).
-compile(export_all).
-endif.
-record(aging, {topics, time, tref}).
-record(state, {aging :: #aging{}, statsfun}).
-define(SERVER, ?MODULE).
-define(ROUTER, emqttd_router).
%%%=============================================================================
%%% API
%%%=============================================================================
%%------------------------------------------------------------------------------
%% @doc Start pubsub helper.
%% @end
%%------------------------------------------------------------------------------
-spec start_link(fun(), list(tuple())) -> {ok, pid()} | ignore | {error, any()}.
start_link(StatsFun, Opts) ->
gen_server2:start_link({local, ?SERVER}, ?MODULE, [StatsFun, Opts], []).
%%------------------------------------------------------------------------------
%% @doc Aging topics
%% @end
%%------------------------------------------------------------------------------
-spec aging(list(binary())) -> ok.
aging(Topics) ->
gen_server2:cast(?SERVER, {aging, Topics}).
%%%=============================================================================
%%% gen_server callbacks
%%%=============================================================================
init([StatsFun, Opts]) ->
mnesia:subscribe(system),
AgingSecs = proplists:get_value(route_aging, Opts, 5),
%% Aging Timer
{ok, AgingTref} = start_tick(AgingSecs div 2),
{ok, #state{aging = #aging{topics = dict:new(),
time = AgingSecs,
tref = AgingTref},
statsfun = StatsFun}}.
start_tick(Secs) ->
timer:send_interval(timer:seconds(Secs), {clean, aged}).
handle_call(Req, _From, State) ->
?UNEXPECTED_REQ(Req, State).
handle_cast({aging, Topics}, State = #state{aging = Aging}) ->
#aging{topics = Dict} = Aging,
TS = emqttd_util:now_to_secs(),
Dict1 =
lists:foldl(fun(Topic, Acc) ->
case dict:find(Topic, Acc) of
{ok, _} -> Acc;
error -> dict:store(Topic, TS, Acc)
end
end, Dict, Topics),
{noreply, State#state{aging = Aging#aging{topics = Dict1}}};
handle_cast(Msg, State) ->
?UNEXPECTED_MSG(Msg, State).
handle_info({clean, aged}, State = #state{aging = Aging}) ->
#aging{topics = Dict, time = Time} = Aging,
ByTime = emqttd_util:now_to_secs() - Time,
Dict1 = try_clean(ByTime, dict:to_list(Dict)),
NewAging = Aging#aging{topics = dict:from_list(Dict1)},
noreply(State#state{aging = NewAging});
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
%% mnesia master?
Pattern = #mqtt_topic{_ = '_', node = Node},
F = fun() ->
[mnesia:delete_object(topic, R, write) ||
R <- mnesia:match_object(topic, Pattern, write)]
end,
mnesia:async_dirty(F),
noreply(State);
handle_info(Info, State) ->
?UNEXPECTED_INFO(Info, State).
terminate(_Reason, #state{aging = #aging{tref = TRef}}) ->
timer:cancel(TRef).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%=============================================================================
%%% Internal Functions
%%%=============================================================================
noreply(State = #state{statsfun = StatsFun}) ->
StatsFun(topic),
{noreply, State, hibernate}.
try_clean(ByTime, List) ->
try_clean(ByTime, List, []).
try_clean(_ByTime, [], Acc) ->
Acc;
try_clean(ByTime, [{Topic, TS} | Left], Acc) ->
case ?ROUTER:has_route(Topic) of
false ->
try_clean2(ByTime, {Topic, TS}, Left, Acc);
true ->
try_clean(ByTime, Left, Acc)
end.
try_clean2(ByTime, {Topic, TS}, Left, Acc) when TS > ByTime ->
try_clean(ByTime, Left, [{Topic, TS}|Acc]);
try_clean2(ByTime, {Topic, _TS}, Left, Acc) ->
TopicR = #mqtt_topic{topic = Topic, node = node()},
mnesia:transaction(fun try_remove_topic/1, [TopicR]),
try_clean(ByTime, Left, Acc).
try_remove_topic(TopicR = #mqtt_topic{topic = Topic}) ->
%% Lock topic first
case mnesia:wread({topic, Topic}) of
[] -> ok;
[TopicR] ->
if_no_route(Topic, fun() ->
%% Remove topic and trie
mnesia:delete_object(topic, TopicR, write),
emqttd_trie:delete(Topic)
end);
_More ->
if_no_route(Topic, fun() ->
%% Remove topic
mnesia:delete_object(topic, TopicR, write)
end)
end.
if_no_route(Topic, Fun) ->
case ?ROUTER:has_route(Topic) of
true -> ok;
false -> Fun()
end.

View File

@ -19,18 +19,17 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc PubSub Supervisor
%%% emqttd pubsub supervisor.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_pubsub_sup). -module(emqttd_pubsub_sup).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(supervisor).
-include("emqttd.hrl"). -include("emqttd.hrl").
-behaviour(supervisor). -define(HELPER, emqttd_pubsub_helper).
%% API %% API
-export([start_link/0]). -export([start_link/0]).
@ -39,19 +38,26 @@
-export([init/1]). -export([init/1]).
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, [emqttd_broker:env(pubsub)]).
init([]) -> init([Opts]) ->
Opts = emqttd_broker:env(pubsub), %% PubSub Helper
Helper = {helper, {?HELPER, start_link, [fun stats/1, Opts]},
permanent, infinity, worker, [?HELPER]},
%% PubSub Pool Sup
MFA = {emqttd_pubsub, start_link, [fun stats/1, Opts]},
PoolSup = emqttd_pool_sup:spec([pubsub, hash, pool_size(Opts), MFA]),
{ok, {{one_for_all, 10, 60}, [Helper, PoolSup]}}.
pool_size(Opts) ->
Schedulers = erlang:system_info(schedulers), Schedulers = erlang:system_info(schedulers),
PoolSize = proplists:get_value(pool_size, Opts, Schedulers), proplists:get_value(pool_size, Opts, Schedulers).
gproc_pool:new(pubsub, hash, [{size, PoolSize}]),
Children = lists:map( stats(topic) ->
fun(I) -> emqttd_stats:setstats('topics/count', 'topics/max',
Name = {emqttd_pubsub, I}, mnesia:table_info(topic, size));
gproc_pool:add_worker(pubsub, Name, I), stats(subscription) ->
{Name, {emqttd_pubsub, start_link, [I, Opts]}, emqttd_stats:setstats('subscriptions/count', 'subscriptions/max',
permanent, 10000, worker, [emqttd_pubsub]} mnesia:table_info(subscription, size)).
end, lists:seq(1, PoolSize)),
{ok, {{one_for_all, 10, 100}, Children}}.

View File

@ -19,19 +19,22 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc MQTT retained message storage.
%%% MQTT retained message storage.
%%% %%%
%%% TODO: should match topic tree %%% TODO: should match topic tree
%%% %%%
%%% @end %%% @end
%%%
%%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_retained). -module(emqttd_retainer).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(gen_server).
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_internal.hrl").
-include_lib("stdlib/include/ms_transform.hrl"). -include_lib("stdlib/include/ms_transform.hrl").
%% Mnesia callbacks %% Mnesia callbacks
@ -46,8 +49,6 @@
%% API Function Exports %% API Function Exports
-export([start_link/0, expire/1]). -export([start_link/0, expire/1]).
-behaviour(gen_server).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
@ -66,7 +67,6 @@ mnesia(boot) ->
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, mqtt_retained}, {record_name, mqtt_retained},
{attributes, record_info(fields, mqtt_retained)}]); {attributes, record_info(fields, mqtt_retained)}]);
mnesia(copy) -> mnesia(copy) ->
ok = emqttd_mnesia:copy_table(retained). ok = emqttd_mnesia:copy_table(retained).
@ -142,7 +142,7 @@ dispatch(Topic, CPid) when is_binary(Topic) ->
end, end,
mnesia:async_dirty(fun mnesia:foldl/3, [Fun, [], retained]) mnesia:async_dirty(fun mnesia:foldl/3, [Fun, [], retained])
end, end,
lists:foreach(fun(Msg) -> CPid ! {dispatch, Msg} end, lists:reverse(Msgs)). lists:foreach(fun(Msg) -> CPid ! {dispatch, Topic, Msg} end, lists:reverse(Msgs)).
%%%============================================================================= %%%=============================================================================
%%% gen_server callbacks %%% gen_server callbacks
@ -159,12 +159,11 @@ init([]) ->
stats_timer = StatsTimer, stats_timer = StatsTimer,
expire_timer = ExpireTimer}}. expire_timer = ExpireTimer}}.
handle_call(_Request, _From, State) -> handle_call(Req, _From, State) ->
{reply, ok, State}. ?UNEXPECTED_REQ(Req, State).
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
lager:error("Unexpected Msg: ~p", [Msg]), ?UNEXPECTED_MSG(Msg, State).
{noreply, State}.
handle_info(stats, State = #state{stats_fun = StatsFun}) -> handle_info(stats, State = #state{stats_fun = StatsFun}) ->
StatsFun(mnesia:table_info(retained, size)), StatsFun(mnesia:table_info(retained, size)),
@ -179,8 +178,7 @@ handle_info(expire, State = #state{expired_after = ExpiredAfter}) ->
{noreply, State, hibernate}; {noreply, State, hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
lager:error("Unexpected Info: ~p", [Info]), ?UNEXPECTED_INFO(Info, State).
{noreply, State}.
terminate(_Reason, _State = #state{stats_timer = TRef1, expire_timer = TRef2}) -> terminate(_Reason, _State = #state{stats_timer = TRef1, expire_timer = TRef2}) ->
timer:cancel(TRef1), timer:cancel(TRef1),

179
src/emqttd_router.erl Normal file
View File

@ -0,0 +1,179 @@
%%%-----------------------------------------------------------------------------
%%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in all
%%% copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE.
%%%-----------------------------------------------------------------------------
%%% @doc MQTT Message Router on Local Node
%%%
%%% Route Table:
%%%
%%% Topic -> Pid1, Pid2, ...
%%%
%%% Reverse Route Table:
%%%
%%% Pid -> Topic1, Topic2, ...
%%%
%%% @end
%%%
%%% @author Feng Lee <feng@emqtt.io>
%%%-----------------------------------------------------------------------------
-module(emqttd_router).
-include("emqttd.hrl").
-include("emqttd_protocol.hrl").
-export([init/1, route/2, lookup_routes/1, has_route/1,
add_routes/2, delete_routes/1, delete_routes/2]).
-ifdef(TEST).
-compile(export_all).
-endif.
%%------------------------------------------------------------------------------
%% @doc Create route tables.
%% @end
%%------------------------------------------------------------------------------
init(_Opts) ->
TabOpts = [bag, public, named_table,
{write_concurrency, true}],
%% Route Table: Topic -> {Pid, QoS}
%% Route Shard: {Topic, Shard} -> {Pid, QoS}
ensure_tab(route, TabOpts),
%% Reverse Route Table: Pid -> {Topic, QoS}
ensure_tab(reverse_route, TabOpts).
ensure_tab(Tab, Opts) ->
case ets:info(Tab, name) of
undefined ->
ets:new(Tab, Opts);
_ ->
ok
end.
%%------------------------------------------------------------------------------
%% @doc Add Routes.
%% @end
%%------------------------------------------------------------------------------
-spec add_routes(list(binary()), pid()) -> ok.
add_routes(Topics, Pid) when is_pid(Pid) ->
with_stats(fun() ->
case lookup_routes(Pid) of
[] ->
erlang:monitor(process, Pid),
insert_routes(Topics, Pid);
InEts ->
insert_routes(Topics -- InEts, Pid)
end
end).
%%------------------------------------------------------------------------------
%% @doc Lookup Routes
%% @end
%%------------------------------------------------------------------------------
-spec lookup_routes(pid()) -> list(binary()).
lookup_routes(Pid) when is_pid(Pid) ->
[Topic || {_, Topic} <- ets:lookup(reverse_route, Pid)].
%%------------------------------------------------------------------------------
%% @doc Has Route?
%% @end
%%------------------------------------------------------------------------------
-spec has_route(binary()) -> boolean().
has_route(Topic) ->
ets:member(route, Topic).
%%------------------------------------------------------------------------------
%% @doc Delete Routes
%% @end
%%------------------------------------------------------------------------------
-spec delete_routes(list(binary()), pid()) -> ok.
delete_routes(Topics, Pid) ->
with_stats(fun() ->
Routes = [{Topic, Pid} || Topic <- Topics],
lists:foreach(fun delete_route/1, Routes)
end).
-spec delete_routes(pid()) -> ok.
delete_routes(Pid) when is_pid(Pid) ->
with_stats(fun() ->
Routes = [{Topic, Pid} || Topic <- lookup_routes(Pid)],
ets:delete(reverse_route, Pid),
lists:foreach(fun delete_route_only/1, Routes)
end).
%%------------------------------------------------------------------------------
%% @doc Route Message on Local Node.
%% @end
%%------------------------------------------------------------------------------
-spec route(binary(), mqtt_message()) -> non_neg_integer().
route(Queue = <<"$Q/", _Q>>, Msg) ->
case ets:lookup(route, Queue) of
[] ->
emqttd_metrics:inc('messages/dropped');
Routes ->
Idx = crypto:rand_uniform(1, length(Routes) + 1),
{_, SubPid} = lists:nth(Idx, Routes),
dispatch(SubPid, Queue, Msg)
end;
route(Topic, Msg) ->
case ets:lookup(route, Topic) of
[] ->
emqttd_metrics:inc('messages/dropped');
Routes ->
lists:foreach(fun({_Topic, SubPid}) ->
dispatch(SubPid, Topic, Msg)
end, Routes)
end.
dispatch(SubPid, Topic, Msg) -> SubPid ! {dispatch, Topic, Msg}.
%%%=============================================================================
%%% Internal Functions
%%%=============================================================================
insert_routes([], _Pid) ->
ok;
insert_routes(Topics, Pid) ->
{Routes, ReverseRoutes} = routes(Topics, Pid),
ets:insert(route, Routes),
ets:insert(reverse_route, ReverseRoutes).
routes(Topics, Pid) ->
lists:unzip([{{Topic, Pid}, {Pid, Topic}} || Topic <- Topics]).
delete_route({Topic, Pid}) ->
ets:delete_object(reverse_route, {Pid, Topic}),
ets:delete_object(route, {Topic, Pid}).
delete_route_only({Topic, Pid}) ->
ets:delete_object(route, {Topic, Pid}).
with_stats(Fun) ->
Ok = Fun(), setstats(), Ok.
setstats() ->
lists:foreach(fun setstat/1, [{route, 'routes/count'},
{reverse_route, 'routes/reverse'}]).
setstat({Tab, Stat}) ->
emqttd_stats:setstat(Stat, ets:info(Tab, size)).

View File

@ -19,48 +19,45 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc MQTT Packet Serializer
%%% MQTT Packet Serialiser.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_serialiser). -module(emqttd_serializer).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").
%% API %% API
-export([serialise/1]). -export([serialize/1]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Serialise MQTT Packet %% @doc Serialise MQTT Packet
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec serialise(mqtt_packet()) -> binary(). -spec serialize(mqtt_packet()) -> binary().
serialise(#mqtt_packet{header = Header = #mqtt_packet_header{type = Type}, serialize(#mqtt_packet{header = Header = #mqtt_packet_header{type = Type},
variable = Variable, variable = Variable,
payload = Payload}) -> payload = Payload}) ->
serialise_header(Header, serialize_header(Header,
serialise_variable(Type, Variable, serialize_variable(Type, Variable,
serialise_payload(Payload))). serialize_payload(Payload))).
serialise_header(#mqtt_packet_header{type = Type, serialize_header(#mqtt_packet_header{type = Type,
dup = Dup, dup = Dup,
qos = Qos, qos = Qos,
retain = Retain}, retain = Retain},
{VariableBin, PayloadBin}) when ?CONNECT =< Type andalso Type =< ?DISCONNECT -> {VariableBin, PayloadBin}) when ?CONNECT =< Type andalso Type =< ?DISCONNECT ->
Len = size(VariableBin) + size(PayloadBin), Len = size(VariableBin) + size(PayloadBin),
true = (Len =< ?MAX_LEN), true = (Len =< ?MAX_LEN),
LenBin = serialise_len(Len), LenBin = serialize_len(Len),
<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1, <<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1,
LenBin/binary, LenBin/binary,
VariableBin/binary, VariableBin/binary,
PayloadBin/binary>>. PayloadBin/binary>>.
serialise_variable(?CONNECT, #mqtt_packet_connect{client_id = ClientId, serialize_variable(?CONNECT, #mqtt_packet_connect{client_id = ClientId,
proto_ver = ProtoVer, proto_ver = ProtoVer,
proto_name = ProtoName, proto_name = ProtoName,
will_retain = WillRetain, will_retain = WillRetain,
@ -83,79 +80,79 @@ serialise_variable(?CONNECT, #mqtt_packet_connect{client_id = ClientId,
(opt(CleanSess)):1, (opt(CleanSess)):1,
0:1, 0:1,
KeepAlive:16/big-unsigned-integer>>, KeepAlive:16/big-unsigned-integer>>,
PayloadBin = serialise_utf(ClientId), PayloadBin = serialize_utf(ClientId),
PayloadBin1 = case WillFlag of PayloadBin1 = case WillFlag of
true -> <<PayloadBin/binary, true -> <<PayloadBin/binary,
(serialise_utf(WillTopic))/binary, (serialize_utf(WillTopic))/binary,
(size(WillMsg)):16/big-unsigned-integer, (size(WillMsg)):16/big-unsigned-integer,
WillMsg/binary>>; WillMsg/binary>>;
false -> PayloadBin false -> PayloadBin
end, end,
UserPasswd = << <<(serialise_utf(B))/binary>> || B <- [Username, Password], B =/= undefined >>, UserPasswd = << <<(serialize_utf(B))/binary>> || B <- [Username, Password], B =/= undefined >>,
{VariableBin, <<PayloadBin1/binary, UserPasswd/binary>>}; {VariableBin, <<PayloadBin1/binary, UserPasswd/binary>>};
serialise_variable(?CONNACK, #mqtt_packet_connack{ack_flags = AckFlags, serialize_variable(?CONNACK, #mqtt_packet_connack{ack_flags = AckFlags,
return_code = ReturnCode}, undefined) -> return_code = ReturnCode}, undefined) ->
{<<AckFlags:8, ReturnCode:8>>, <<>>}; {<<AckFlags:8, ReturnCode:8>>, <<>>};
serialise_variable(?SUBSCRIBE, #mqtt_packet_subscribe{packet_id = PacketId, serialize_variable(?SUBSCRIBE, #mqtt_packet_subscribe{packet_id = PacketId,
topic_table = Topics }, undefined) -> topic_table = Topics }, undefined) ->
{<<PacketId:16/big>>, serialise_topics(Topics)}; {<<PacketId:16/big>>, serialize_topics(Topics)};
serialise_variable(?SUBACK, #mqtt_packet_suback{packet_id = PacketId, serialize_variable(?SUBACK, #mqtt_packet_suback{packet_id = PacketId,
qos_table = QosTable}, undefined) -> qos_table = QosTable}, undefined) ->
{<<PacketId:16/big>>, << <<Q:8>> || Q <- QosTable >>}; {<<PacketId:16/big>>, << <<Q:8>> || Q <- QosTable >>};
serialise_variable(?UNSUBSCRIBE, #mqtt_packet_unsubscribe{packet_id = PacketId, serialize_variable(?UNSUBSCRIBE, #mqtt_packet_unsubscribe{packet_id = PacketId,
topics = Topics }, undefined) -> topics = Topics }, undefined) ->
{<<PacketId:16/big>>, serialise_topics(Topics)}; {<<PacketId:16/big>>, serialize_topics(Topics)};
serialise_variable(?UNSUBACK, #mqtt_packet_unsuback{packet_id = PacketId}, undefined) -> serialize_variable(?UNSUBACK, #mqtt_packet_unsuback{packet_id = PacketId}, undefined) ->
{<<PacketId:16/big>>, <<>>}; {<<PacketId:16/big>>, <<>>};
serialise_variable(?PUBLISH, #mqtt_packet_publish{topic_name = TopicName, serialize_variable(?PUBLISH, #mqtt_packet_publish{topic_name = TopicName,
packet_id = PacketId }, PayloadBin) -> packet_id = PacketId }, PayloadBin) ->
TopicBin = serialise_utf(TopicName), TopicBin = serialize_utf(TopicName),
PacketIdBin = if PacketIdBin = if
PacketId =:= undefined -> <<>>; PacketId =:= undefined -> <<>>;
true -> <<PacketId:16/big>> true -> <<PacketId:16/big>>
end, end,
{<<TopicBin/binary, PacketIdBin/binary>>, PayloadBin}; {<<TopicBin/binary, PacketIdBin/binary>>, PayloadBin};
serialise_variable(PubAck, #mqtt_packet_puback{packet_id = PacketId}, _Payload) serialize_variable(PubAck, #mqtt_packet_puback{packet_id = PacketId}, _Payload)
when PubAck =:= ?PUBACK; PubAck =:= ?PUBREC; PubAck =:= ?PUBREL; PubAck =:= ?PUBCOMP -> when PubAck =:= ?PUBACK; PubAck =:= ?PUBREC; PubAck =:= ?PUBREL; PubAck =:= ?PUBCOMP ->
{<<PacketId:16/big>>, <<>>}; {<<PacketId:16/big>>, <<>>};
serialise_variable(?PINGREQ, undefined, undefined) -> serialize_variable(?PINGREQ, undefined, undefined) ->
{<<>>, <<>>}; {<<>>, <<>>};
serialise_variable(?PINGRESP, undefined, undefined) -> serialize_variable(?PINGRESP, undefined, undefined) ->
{<<>>, <<>>}; {<<>>, <<>>};
serialise_variable(?DISCONNECT, undefined, undefined) -> serialize_variable(?DISCONNECT, undefined, undefined) ->
{<<>>, <<>>}. {<<>>, <<>>}.
serialise_payload(undefined) -> serialize_payload(undefined) ->
undefined; undefined;
serialise_payload(Bin) when is_binary(Bin) -> serialize_payload(Bin) when is_binary(Bin) ->
Bin. Bin.
serialise_topics([{_Topic, _Qos}|_] = Topics) -> serialize_topics([{_Topic, _Qos}|_] = Topics) ->
<< <<(serialise_utf(Topic))/binary, ?RESERVED:6, Qos:2>> || {Topic, Qos} <- Topics >>; << <<(serialize_utf(Topic))/binary, ?RESERVED:6, Qos:2>> || {Topic, Qos} <- Topics >>;
serialise_topics([H|_] = Topics) when is_binary(H) -> serialize_topics([H|_] = Topics) when is_binary(H) ->
<< <<(serialise_utf(Topic))/binary>> || Topic <- Topics >>. << <<(serialize_utf(Topic))/binary>> || Topic <- Topics >>.
serialise_utf(String) -> serialize_utf(String) ->
StringBin = unicode:characters_to_binary(String), StringBin = unicode:characters_to_binary(String),
Len = size(StringBin), Len = size(StringBin),
true = (Len =< 16#ffff), true = (Len =< 16#ffff),
<<Len:16/big, StringBin/binary>>. <<Len:16/big, StringBin/binary>>.
serialise_len(N) when N =< ?LOWBITS -> serialize_len(N) when N =< ?LOWBITS ->
<<0:1, N:7>>; <<0:1, N:7>>;
serialise_len(N) -> serialize_len(N) ->
<<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>. <<1:1, (N rem ?HIGHBIT):7, (serialize_len(N div ?HIGHBIT))/binary>>.
opt(undefined) -> ?RESERVED; opt(undefined) -> ?RESERVED;
opt(false) -> 0; opt(false) -> 0;

View File

@ -19,9 +19,7 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Session for persistent MQTT client.
%%%
%%% Session for persistent MQTT client.
%%% %%%
%%% Session State in the broker consists of: %%% Session State in the broker consists of:
%%% %%%
@ -43,15 +41,17 @@
%%% State of Message: newcome, inflight, pending %%% State of Message: newcome, inflight, pending
%%% %%%
%%% @end %%% @end
%%%
%%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_session). -module(emqttd_session).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").
-include("emqttd_internal.hrl").
-behaviour(gen_server2). -behaviour(gen_server2).
%% Session API %% Session API
@ -86,7 +86,7 @@
packet_id = 1, packet_id = 1,
%% Clients subscriptions. %% Clients subscriptions.
subscriptions :: list(), subscriptions :: dict:dict(),
%% Inflight qos1, qos2 messages sent to the client but unacked, %% Inflight qos1, qos2 messages sent to the client but unacked,
%% QoS 1 and QoS 2 messages which have been sent to the Client, %% QoS 1 and QoS 2 messages which have been sent to the Client,
@ -245,7 +245,7 @@ init([CleanSess, ClientId, ClientPid]) ->
clean_sess = CleanSess, clean_sess = CleanSess,
client_id = ClientId, client_id = ClientId,
client_pid = ClientPid, client_pid = ClientPid,
subscriptions = [], subscriptions = dict:new(),
inflight_queue = [], inflight_queue = [],
max_inflight = emqttd_opts:g(max_inflight, SessEnv, 0), max_inflight = emqttd_opts:g(max_inflight, SessEnv, 0),
message_queue = emqttd_mqueue:new(ClientId, QEnv, emqttd_alarm:alarm_fun()), message_queue = emqttd_mqueue:new(ClientId, QEnv, emqttd_alarm:alarm_fun()),
@ -287,7 +287,7 @@ prioritise_info(Msg, _Len, _State) ->
expired -> 10; expired -> 10;
{timeout, _, _} -> 5; {timeout, _, _} -> 5;
collect_info -> 2; collect_info -> 2;
{dispatch, _} -> 1; {dispatch, _, _} -> 1;
_ -> 0 _ -> 0
end. end.
@ -308,21 +308,20 @@ handle_call({publish, Msg = #mqtt_message{qos = ?QOS_2, pktid = PktId}},
end; end;
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?LOG(critical, "Unexpected Request: ~p", [Req], State), ?UNEXPECTED_REQ(Req, State).
{reply, {error, unsupported_req}, State, hibernate}.
handle_cast({subscribe, TopicTable0, AckFun}, Session = #session{client_id = ClientId, handle_cast({subscribe, TopicTable0, AckFun}, Session = #session{client_id = ClientId,
subscriptions = Subscriptions}) -> subscriptions = Subscriptions}) ->
TopicTable = emqttd_broker:foldl_hooks('client.subscribe', [ClientId], TopicTable0), TopicTable = emqttd_broker:foldl_hooks('client.subscribe', [ClientId], TopicTable0),
case TopicTable -- Subscriptions of case TopicTable -- dict:to_list(Subscriptions) of
[] -> [] ->
AckFun([Qos || {_, Qos} <- TopicTable]), AckFun([Qos || {_, Qos} <- TopicTable]),
hibernate(Session); hibernate(Session);
_ -> _ ->
%% subscribe first and don't care if the subscriptions have been existed %% subscribe first and don't care if the subscriptions have been existed
{ok, GrantedQos} = emqttd_pubsub:subscribe(TopicTable), {ok, GrantedQos} = emqttd_pubsub:subscribe(ClientId, TopicTable),
AckFun(GrantedQos), AckFun(GrantedQos),
@ -331,21 +330,22 @@ handle_cast({subscribe, TopicTable0, AckFun}, Session = #session{client_id = Cli
?LOG(info, "Subscribe ~p, Granted QoS: ~p", [TopicTable, GrantedQos], Session), ?LOG(info, "Subscribe ~p, Granted QoS: ~p", [TopicTable, GrantedQos], Session),
Subscriptions1 = Subscriptions1 =
lists:foldl(fun({Topic, Qos}, Acc) -> lists:foldl(fun({Topic, Qos}, Dict) ->
case lists:keyfind(Topic, 1, Acc) of case dict:find(Topic, Dict) of
{Topic, Qos} -> {ok, Qos} ->
?LOG(warning, "resubscribe ~s, qos = ~w", [Topic, Qos], Session), ?LOG(warning, "resubscribe ~s, qos = ~w", [Topic, Qos], Session),
Acc; Dict;
{Topic, OldQos} -> {ok, OldQos} ->
?LOG(warning, "resubscribe ~s, old qos=~w, new qos=~w", [Topic, OldQos, Qos], Session), ?LOG(warning, "resubscribe ~s, old qos=~w, new qos=~w", [Topic, OldQos, Qos], Session),
lists:keyreplace(Topic, 1, Acc, {Topic, Qos}); dict:store(Topic, Qos, Dict);
false -> error ->
%%TODO: the design is ugly, rewrite later...:( %%TODO: the design is ugly, rewrite later...:(
%% <MQTT V3.1.1>: 3.8.4 %% <MQTT V3.1.1>: 3.8.4
%% Where the Topic Filter is not identical to any existing Subscriptions filter, %% Where the Topic Filter is not identical to any existing Subscriptions filter,
%% a new Subscription is created and all matching retained messages are sent. %% a new Subscription is created and all matching retained messages are sent.
emqttd_retained:dispatch(Topic, self()), emqttd_retainer:dispatch(Topic, self()),
[{Topic, Qos} | Acc]
dict:store(Topic, Qos, Dict)
end end
end, Subscriptions, TopicTable), end, Subscriptions, TopicTable),
hibernate(Session#session{subscriptions = Subscriptions1}) hibernate(Session#session{subscriptions = Subscriptions1})
@ -362,13 +362,8 @@ handle_cast({unsubscribe, Topics0}, Session = #session{client_id = ClientId,
?LOG(info, "unsubscribe ~p", [Topics], Session), ?LOG(info, "unsubscribe ~p", [Topics], Session),
Subscriptions1 = Subscriptions1 =
lists:foldl(fun(Topic, Acc) -> lists:foldl(fun(Topic, Dict) ->
case lists:keyfind(Topic, 1, Acc) of dict:erase(Topic, Dict)
{Topic, _Qos} ->
lists:keydelete(Topic, 1, Acc);
false ->
Acc
end
end, Subscriptions, Topics), end, Subscriptions, Topics),
hibernate(Session#session{subscriptions = Subscriptions1}); hibernate(Session#session{subscriptions = Subscriptions1});
@ -483,31 +478,12 @@ handle_cast({pubcomp, PktId}, Session = #session{awaiting_comp = AwaitingComp})
end; end;
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?LOG(critical, "Unexpected Msg: ~p", [Msg], State), ?UNEXPECTED_MSG(Msg, State).
hibernate(State).
%% Queue messages when client is offline %% Dispatch Message
handle_info({dispatch, Msg}, Session = #session{client_pid = undefined, handle_info({dispatch, Topic, Msg}, Session = #session{subscriptions = Subscriptions})
message_queue = Q})
when is_record(Msg, mqtt_message) -> when is_record(Msg, mqtt_message) ->
hibernate(Session#session{message_queue = emqttd_mqueue:in(Msg, Q)}); dispatch(fixqos(Topic, Msg, Subscriptions), Session);
%% Dispatch qos0 message directly to client
handle_info({dispatch, Msg = #mqtt_message{qos = ?QOS_0}},
Session = #session{client_pid = ClientPid}) ->
ClientPid ! {deliver, Msg},
hibernate(Session);
handle_info({dispatch, Msg = #mqtt_message{qos = QoS}},
Session = #session{message_queue = MsgQ})
when QoS =:= ?QOS_1 orelse QoS =:= ?QOS_2 ->
case check_inflight(Session) of
true ->
noreply(deliver(Msg, Session));
false ->
hibernate(Session#session{message_queue = emqttd_mqueue:in(Msg, MsgQ)})
end;
handle_info({timeout, awaiting_ack, PktId}, Session = #session{client_pid = undefined, handle_info({timeout, awaiting_ack, PktId}, Session = #session{client_pid = undefined,
awaiting_ack = AwaitingAck}) -> awaiting_ack = AwaitingAck}) ->
@ -580,8 +556,7 @@ handle_info(expired, Session) ->
shutdown(expired, Session); shutdown(expired, Session);
handle_info(Info, Session) -> handle_info(Info, Session) ->
?LOG(critical, "Unexpected info: ~p", [Info], Session), ?UNEXPECTED_INFO(Info, Session).
hibernate(Session).
terminate(_Reason, #session{clean_sess = CleanSess, client_id = ClientId}) -> terminate(_Reason, #session{clean_sess = CleanSess, client_id = ClientId}) ->
emqttd_sm:unregister_session(CleanSess, ClientId). emqttd_sm:unregister_session(CleanSess, ClientId).
@ -606,6 +581,38 @@ kick(ClientId, OldPid, Pid) ->
%% Clean noproc %% Clean noproc
receive {'EXIT', OldPid, _} -> ok after 0 -> ok end. receive {'EXIT', OldPid, _} -> ok after 0 -> ok end.
%%------------------------------------------------------------------------------
%% Dispatch Messages
%%------------------------------------------------------------------------------
%% Queue message if client disconnected
dispatch(Msg, Session = #session{client_pid = undefined, message_queue = Q}) ->
hibernate(Session#session{message_queue = emqttd_mqueue:in(Msg, Q)});
%% Deliver qos0 message directly to client
dispatch(Msg = #mqtt_message{qos = ?QOS0}, Session = #session{client_pid = ClientPid}) ->
ClientPid ! {deliver, Msg},
hibernate(Session);
dispatch(Msg = #mqtt_message{qos = QoS}, Session = #session{message_queue = MsgQ})
when QoS =:= ?QOS1 orelse QoS =:= ?QOS2 ->
case check_inflight(Session) of
true ->
noreply(deliver(Msg, Session));
false ->
hibernate(Session#session{message_queue = emqttd_mqueue:in(Msg, MsgQ)})
end.
fixqos(Topic, Msg = #mqtt_message{qos = PubQos}, Subscriptions) ->
case dict:find(Topic, Subscriptions) of
{ok, SubQos} when PubQos > SubQos ->
Msg#mqtt_message{qos = SubQos};
{ok, _SubQos} ->
Msg;
error ->
Msg
end.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Check inflight and awaiting_rel %% Check inflight and awaiting_rel
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -725,7 +732,7 @@ sess_info(#session{clean_sess = CleanSess,
timestamp = CreatedAt}) -> timestamp = CreatedAt}) ->
Stats = emqttd_mqueue:stats(MessageQueue), Stats = emqttd_mqueue:stats(MessageQueue),
[{clean_sess, CleanSess}, [{clean_sess, CleanSess},
{subscriptions, Subscriptions}, {subscriptions, dict:to_list(Subscriptions)},
{max_inflight, MaxInflight}, {max_inflight, MaxInflight},
{inflight_queue, length(InflightQueue)}, {inflight_queue, length(InflightQueue)},
{message_queue, proplists:get_value(len, Stats)}, {message_queue, proplists:get_value(len, Stats)},

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd session supervisor.
%%% emqttd session supervisor.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_session_sup). -module(emqttd_session_sup).
-author("Feng Lee <feng@emqtt.io>").
-behavior(supervisor). -behavior(supervisor).
-export([start_link/0, start_session/3]). -export([start_link/0, start_session/3]).

View File

@ -19,17 +19,16 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Session Manager
%%% emqttd session manager.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_sm). -module(emqttd_sm).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_internal.hrl").
%% Mnesia Callbacks %% Mnesia Callbacks
-export([mnesia/1]). -export([mnesia/1]).
@ -37,7 +36,7 @@
-copy_mnesia({mnesia, [copy]}). -copy_mnesia({mnesia, [copy]}).
%% API Function Exports %% API Function Exports
-export([start_link/1, pool/0]). -export([start_link/2]).
-export([start_session/2, lookup_session/1]). -export([start_session/2, lookup_session/1]).
@ -52,11 +51,11 @@
%% gen_server2 priorities %% gen_server2 priorities
-export([prioritise_call/4, prioritise_cast/3, prioritise_info/3]). -export([prioritise_call/4, prioritise_cast/3, prioritise_info/3]).
-record(state, {id}). -record(state, {pool, id, monitors}).
-define(SM_POOL, ?MODULE). -define(POOL, ?MODULE).
-define(TIMEOUT, 60000). -define(TIMEOUT, 120000).
-define(LOG(Level, Format, Args, Session), -define(LOG(Level, Format, Args, Session),
lager:Level("SM(~s): " ++ Format, [Session#mqtt_session.client_id | Args])). lager:Level("SM(~s): " ++ Format, [Session#mqtt_session.client_id | Args])).
@ -66,13 +65,12 @@
%%%============================================================================= %%%=============================================================================
mnesia(boot) -> mnesia(boot) ->
%% global session... %% Global Session Table
ok = emqttd_mnesia:create_table(session, [ ok = emqttd_mnesia:create_table(session, [
{type, ordered_set}, {type, set},
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, mqtt_session}, {record_name, mqtt_session},
{attributes, record_info(fields, mqtt_session)}, {attributes, record_info(fields, mqtt_session)}]);
{index, [sess_pid]}]);
mnesia(copy) -> mnesia(copy) ->
ok = emqttd_mnesia:copy_table(session). ok = emqttd_mnesia:copy_table(session).
@ -85,26 +83,20 @@ mnesia(copy) ->
%% @doc Start a session manager %% @doc Start a session manager
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec start_link(Id :: pos_integer()) -> {ok, pid()} | ignore | {error, any()}. -spec start_link(atom(), pos_integer()) -> {ok, pid()} | ignore | {error, any()}.
start_link(Id) -> start_link(Pool, Id) ->
gen_server2:start_link({local, name(Id)}, ?MODULE, [Id], []). gen_server2:start_link({local, name(Id)}, ?MODULE, [Pool, Id], []).
name(Id) -> name(Id) ->
list_to_atom("emqttd_sm_" ++ integer_to_list(Id)). list_to_atom("emqttd_sm_" ++ integer_to_list(Id)).
%%------------------------------------------------------------------------------
%% @doc Pool name.
%% @end
%%------------------------------------------------------------------------------
pool() -> ?SM_POOL.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Start a session %% @doc Start a session
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec start_session(CleanSess :: boolean(), binary()) -> {ok, pid(), boolean()} | {error, any()}. -spec start_session(CleanSess :: boolean(), binary()) -> {ok, pid(), boolean()} | {error, any()}.
start_session(CleanSess, ClientId) -> start_session(CleanSess, ClientId) ->
SM = gproc_pool:pick_worker(?SM_POOL, ClientId), SM = gproc_pool:pick_worker(?POOL, ClientId),
call(SM, {start_session, {CleanSess, ClientId, self()}}). call(SM, {start_session, {CleanSess, ClientId, self()}}).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -149,9 +141,10 @@ call(SM, Req) ->
%%% gen_server callbacks %%% gen_server callbacks
%%%============================================================================= %%%=============================================================================
init([Id]) -> init([Pool, Id]) ->
gproc_pool:connect_worker(?SM_POOL, {?MODULE, Id}), ?GPROC_POOL(join, Pool, Id),
{ok, #state{id = Id}}. {ok, #state{pool = Pool, id = Id,
monitors = dict:new()}}.
prioritise_call(_Msg, _From, _Len, _State) -> prioritise_call(_Msg, _From, _Len, _State) ->
1. 1.
@ -162,50 +155,63 @@ prioritise_cast(_Msg, _Len, _State) ->
prioritise_info(_Msg, _Len, _State) -> prioritise_info(_Msg, _Len, _State) ->
2. 2.
%% persistent session %% Persistent Session
handle_call({start_session, {false, ClientId, ClientPid}}, _From, State) -> handle_call({start_session, Client = {false, ClientId, ClientPid}}, _From, State) ->
case lookup_session(ClientId) of case lookup_session(ClientId) of
undefined -> undefined ->
%% create session locally %% Create session locally
reply(create_session(false, ClientId, ClientPid), false, State); create_session(Client, State);
Session -> Session ->
reply(resume_session(Session, ClientPid), true, State) case resume_session(Session, ClientPid) of
{ok, SessPid} ->
{reply, {ok, SessPid, true}, State};
{error, Erorr} ->
{reply, {error, Erorr}, State}
end
end; end;
%% transient session %% Transient Session
handle_call({start_session, {true, ClientId, ClientPid}}, _From, State) -> handle_call({start_session, Client = {true, ClientId, _ClientPid}}, _From, State) ->
case lookup_session(ClientId) of case lookup_session(ClientId) of
undefined -> undefined ->
reply(create_session(true, ClientId, ClientPid), false, State); create_session(Client, State);
Session -> Session ->
case destroy_session(Session) of case destroy_session(Session) of
ok -> ok ->
reply(create_session(true, ClientId, ClientPid), false, State); create_session(Client, State);
{error, Error} -> {error, Error} ->
{reply, {error, Error}, State} {reply, {error, Error}, State}
end end
end; end;
handle_call(_Request, _From, State) -> handle_call(Req, _From, State) ->
{reply, ok, State}. ?UNEXPECTED_REQ(Req, State).
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
lager:error("Unexpected Msg: ~p", [Msg]), ?UNEXPECTED_MSG(Msg, State).
{noreply, State}.
handle_info({'DOWN', _MRef, process, DownPid, _Reason}, State) -> handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
case dict:find(MRef, State#state.monitors) of
{ok, ClientId} ->
mnesia:transaction(fun() -> mnesia:transaction(fun() ->
[mnesia:delete_object(session, Sess, write) || Sess case mnesia:wread({session, ClientId}) of
<- mnesia:index_read(session, DownPid, #mqtt_session.sess_pid)] [] -> ok;
[Sess = #mqtt_session{sess_pid = DownPid}] ->
mnesia:delete_object(session, Sess, write);
[_Sess] -> ok
end
end), end),
{noreply, State}; {noreply, erase_monitor(MRef, State)};
error ->
lager:error("MRef of session ~p not found", [DownPid]),
{noreply, State}
end;
handle_info(Info, State) -> handle_info(Info, State) ->
lager:error("Unexpected Info: ~p", [Info]), ?UNEXPECTED_INFO(Info, State).
{noreply, State}.
terminate(_Reason, #state{id = Id}) -> terminate(_Reason, #state{pool = Pool, id = Id}) ->
gproc_pool:disconnect_worker(?SM_POOL, {?MODULE, Id}), ok. ?GPROC_POOL(leave, Pool, Id).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -214,6 +220,16 @@ code_change(_OldVsn, State, _Extra) ->
%%% Internal functions %%% Internal functions
%%%============================================================================= %%%=============================================================================
%% Create Session Locally
create_session({CleanSess, ClientId, ClientPid}, State) ->
case create_session(CleanSess, ClientId, ClientPid) of
{ok, SessPid} ->
{reply, {ok, SessPid, false},
monitor_session(ClientId, SessPid, State)};
{error, Error} ->
{reply, {error, Error}, State}
end.
create_session(CleanSess, ClientId, ClientPid) -> create_session(CleanSess, ClientId, ClientPid) ->
case emqttd_session_sup:start_session(CleanSess, ClientId, ClientPid) of case emqttd_session_sup:start_session(CleanSess, ClientId, ClientPid) of
{ok, SessPid} -> {ok, SessPid} ->
@ -226,7 +242,6 @@ create_session(CleanSess, ClientId, ClientPid) ->
lager:error("SM(~s): Conflict with ~p", [ClientId, ConflictPid]), lager:error("SM(~s): Conflict with ~p", [ClientId, ConflictPid]),
{error, mnesia_conflict}; {error, mnesia_conflict};
{atomic, ok} -> {atomic, ok} ->
erlang:monitor(process, SessPid),
{ok, SessPid} {ok, SessPid}
end; end;
{error, Error} -> {error, Error} ->
@ -301,8 +316,10 @@ remove_session(Session) ->
{aborted, Error} -> {error, Error} {aborted, Error} -> {error, Error}
end. end.
reply({ok, SessPid}, SP, State) -> monitor_session(ClientId, SessPid, State = #state{monitors = Monitors}) ->
{reply, {ok, SessPid, SP}, State}; MRef = erlang:monitor(process, SessPid),
reply({error, Error}, _SP, State) -> State#state{monitors = dict:store(MRef, ClientId, Monitors)}.
{reply, {error, Error}, State}.
erase_monitor(MRef, State = #state{monitors = Monitors}) ->
State#state{monitors = dict:erase(MRef, Monitors)}.

View File

@ -19,23 +19,22 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Session Helper.
%%% emqttd session helper.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_sm_helper). -module(emqttd_sm_helper).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(gen_server).
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_internal.hrl").
-include_lib("stdlib/include/ms_transform.hrl"). -include_lib("stdlib/include/ms_transform.hrl").
%% API Function Exports %% API Function Exports
-export([start_link/0]). -export([start_link/1]).
-behaviour(gen_server).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@ -47,22 +46,20 @@
%% @doc Start a session helper %% @doc Start a session helper
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec start_link() -> {ok, pid()} | ignore | {error, any()}. -spec start_link(fun()) -> {ok, pid()} | ignore | {error, any()}.
start_link() -> start_link(StatsFun) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [StatsFun], []).
init([]) -> init([StatsFun]) ->
mnesia:subscribe(system), mnesia:subscribe(system),
{ok, TRef} = timer:send_interval(timer:seconds(1), tick), {ok, TRef} = timer:send_interval(timer:seconds(1), tick),
StatsFun = emqttd_stats:statsfun('sessions/count', 'sessions/max'),
{ok, #state{stats_fun = StatsFun, tick_tref = TRef}}. {ok, #state{stats_fun = StatsFun, tick_tref = TRef}}.
handle_call(_Request, _From, State) -> handle_call(Req, _From, State) ->
{reply, ok, State}. ?UNEXPECTED_REQ(Req, State).
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
lager:error("Unexpected Msg: ~p", [Msg]), ?UNEXPECTED_MSG(Msg, State).
{noreply, State}.
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) -> handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
lager:error("!!!Mnesia node down: ~s", [Node]), lager:error("!!!Mnesia node down: ~s", [Node]),
@ -83,8 +80,7 @@ handle_info(tick, State) ->
{noreply, setstats(State), hibernate}; {noreply, setstats(State), hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
lager:error("Unexpected Info: ~p", [Info]), ?UNEXPECTED_INFO(Info, State).
{noreply, State}.
terminate(_Reason, _State = #state{tick_tref = TRef}) -> terminate(_Reason, _State = #state{tick_tref = TRef}) ->
timer:cancel(TRef), timer:cancel(TRef),

View File

@ -19,25 +19,26 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Session Manager Supervisor.
%%% emqttd session manager supervisor.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_sm_sup). -module(emqttd_sm_sup).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(supervisor).
-include("emqttd.hrl"). -include("emqttd.hrl").
-define(CHILD(Mod), {Mod, {Mod, start_link, []}, -define(SM, emqttd_sm).
permanent, 5000, worker, [Mod]}).
-define(HELPER, emqttd_sm_helper).
-define(TABS, [mqtt_transient_session,
mqtt_persistent_session]).
%% API %% API
-export([start_link/0]). -export([start_link/0]).
-behaviour(supervisor).
%% Supervisor callbacks %% Supervisor callbacks
-export([init/1]). -export([init/1]).
@ -45,20 +46,22 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) -> init([]) ->
init_session_ets(), %% Create session tables
Schedulers = erlang:system_info(schedulers), create_session_tabs(),
gproc_pool:new(emqttd_sm:pool(), hash, [{size, Schedulers}]),
Managers = lists:map(
fun(I) ->
Name = {emqttd_sm, I},
gproc_pool:add_worker(emqttd_sm:pool(), Name, I),
{Name, {emqttd_sm, start_link, [I]},
permanent, 10000, worker, [emqttd_sm]}
end, lists:seq(1, Schedulers)),
{ok, {{one_for_all, 10, 100}, [?CHILD(emqttd_sm_helper) | Managers]}}.
init_session_ets() -> %% Helper
Tables = [mqtt_transient_session, mqtt_persistent_session], StatsFun = emqttd_stats:statsfun('sessions/count', 'sessions/max'),
Attrs = [ordered_set, named_table, public, {write_concurrency, true}], Helper = {?HELPER, {?HELPER, start_link, [StatsFun]},
lists:foreach(fun(Tab) -> ets:new(Tab, Attrs) end, Tables). permanent, 5000, worker, [?HELPER]},
%% SM Pool Sup
MFA = {?SM, start_link, []},
PoolSup = emqttd_pool_sup:spec([?SM, hash, erlang:system_info(schedulers), MFA]),
{ok, {{one_for_all, 10, 3600}, [Helper, PoolSup]}}.
create_session_tabs() ->
Opts = [ordered_set, named_table, public,
{write_concurrency, true}],
[ets:new(Tab, Opts) || Tab <- ?TABS].

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd statistics
%%% emqttd statistics.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_stats). -module(emqttd_stats).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-behaviour(gen_server). -behaviour(gen_server).
@ -63,10 +60,12 @@
%% $SYS Topics for Subscribers %% $SYS Topics for Subscribers
-define(SYSTOP_PUBSUB, [ -define(SYSTOP_PUBSUB, [
'routes/count', % ...
'routes/reverse', % ...
'topics/count', % ... 'topics/count', % ...
'topics/max', % ... 'topics/max', % ...
'subscribers/count', % ... 'subscriptions/count', % ...
'subscribers/max', % ... 'subscriptions/max', % ...
'queues/count', % ... 'queues/count', % ...
'queues/max' % ... 'queues/max' % ...
]). ]).
@ -141,12 +140,12 @@ setstats(Stat, MaxStat, Val) ->
%%%============================================================================= %%%=============================================================================
init([]) -> init([]) ->
random:seed(now()), random:seed(os:timestamp()),
ets:new(?STATS_TAB, [set, public, named_table, {write_concurrency, true}]), ets:new(?STATS_TAB, [set, public, named_table, {write_concurrency, true}]),
Topics = ?SYSTOP_CLIENTS ++ ?SYSTOP_SESSIONS ++ ?SYSTOP_PUBSUB ++ ?SYSTOP_RETAINED, Topics = ?SYSTOP_CLIENTS ++ ?SYSTOP_SESSIONS ++ ?SYSTOP_PUBSUB ++ ?SYSTOP_RETAINED,
ets:insert(?STATS_TAB, [{Topic, 0} || Topic <- Topics]), ets:insert(?STATS_TAB, [{Topic, 0} || Topic <- Topics]),
% Create $SYS Topics % Create $SYS Topics
[ok = emqttd_pubsub:create(stats_topic(Topic)) || Topic <- Topics], [ok = emqttd_pubsub:create(topic, stats_topic(Topic)) || Topic <- Topics],
% Tick to publish stats % Tick to publish stats
{ok, #state{tick_tref = emqttd_broker:start_tick(tick)}, hibernate}. {ok, #state{tick_tref = emqttd_broker:start_tick(tick)}, hibernate}.

View File

@ -19,19 +19,16 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd top supervisor.
%%% emqttd supervisor.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_sup). -module(emqttd_sup).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(supervisor).
-include("emqttd.hrl"). -include("emqttd.hrl").
-behaviour(supervisor).
%% API %% API
-export([start_link/0, start_child/1, start_child/2]). -export([start_link/0, start_child/1, start_child/2]).

View File

@ -19,23 +19,30 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc VM System Monitor
%%% emqttd system monitor.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_sysmon). -module(emqttd_sysmon).
-author("Feng Lee <feng@emqtt.io>").
-behavior(gen_server). -behavior(gen_server).
-include("emqttd_internal.hrl").
-export([start_link/1]). -export([start_link/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-record(state, {tick_tref, events = []}). -record(state, {tickref, events = [], tracelog}).
-define(LOG_FMT, [{formatter_config, [time, " ", message, "\n"]}]).
-define(LOG(Msg, ProcInfo),
lager:warning([{sysmon, true}], "~s~n~p", [WarnMsg, ProcInfo])).
-define(LOG(Msg, ProcInfo, PortInfo),
lager:warning([{sysmon, true}], "~s~n~p~n~p", [WarnMsg, ProcInfo, PortInfo])).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Start system monitor %% @doc Start system monitor
@ -53,7 +60,9 @@ start_link(Opts) ->
init([Opts]) -> init([Opts]) ->
erlang:system_monitor(self(), parse_opt(Opts)), erlang:system_monitor(self(), parse_opt(Opts)),
{ok, TRef} = timer:send_interval(timer:seconds(1), reset), {ok, TRef} = timer:send_interval(timer:seconds(1), reset),
{ok, #state{tick_tref = TRef}}. %%TODO: don't trace for performance issue.
%%{ok, TraceLog} = start_tracelog(proplists:get_value(logfile, Opts)),
{ok, #state{tickref = TRef}}.
parse_opt(Opts) -> parse_opt(Opts) ->
parse_opt(Opts, []). parse_opt(Opts, []).
@ -63,6 +72,8 @@ parse_opt([{long_gc, false}|Opts], Acc) ->
parse_opt(Opts, Acc); parse_opt(Opts, Acc);
parse_opt([{long_gc, Ms}|Opts], Acc) when is_integer(Ms) -> parse_opt([{long_gc, Ms}|Opts], Acc) when is_integer(Ms) ->
parse_opt(Opts, [{long_gc, Ms}|Acc]); parse_opt(Opts, [{long_gc, Ms}|Acc]);
parse_opt([{long_schedule, false}|Opts], Acc) ->
parse_opt(Opts, Acc);
parse_opt([{long_schedule, Ms}|Opts], Acc) when is_integer(Ms) -> parse_opt([{long_schedule, Ms}|Opts], Acc) when is_integer(Ms) ->
parse_opt(Opts, [{long_schedule, Ms}|Acc]); parse_opt(Opts, [{long_schedule, Ms}|Acc]);
parse_opt([{large_heap, Size}|Opts], Acc) when is_integer(Size) -> parse_opt([{large_heap, Size}|Opts], Acc) when is_integer(Size) ->
@ -74,55 +85,55 @@ parse_opt([{busy_port, false}|Opts], Acc) ->
parse_opt([{busy_dist_port, true}|Opts], Acc) -> parse_opt([{busy_dist_port, true}|Opts], Acc) ->
parse_opt(Opts, [busy_dist_port|Acc]); parse_opt(Opts, [busy_dist_port|Acc]);
parse_opt([{busy_dist_port, false}|Opts], Acc) -> parse_opt([{busy_dist_port, false}|Opts], Acc) ->
parse_opt(Opts, Acc);
parse_opt([_Opt|Opts], Acc) ->
parse_opt(Opts, Acc). parse_opt(Opts, Acc).
handle_call(Request, _From, State) -> handle_call(Req, _From, State) ->
lager:error("Unexpected request: ~p", [Request]), ?UNEXPECTED_REQ(Req, State).
{reply, {error, unexpected_request}, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
lager:error("Unexpected msg: ~p", [Msg]), ?UNEXPECTED_MSG(Msg, State).
{noreply, State}.
handle_info({monitor, Pid, long_gc, Info}, State) -> handle_info({monitor, Pid, long_gc, Info}, State) ->
suppress({long_gc, Pid}, fun() -> suppress({long_gc, Pid}, fun() ->
WarnMsg = io_lib:format("long_gc: pid = ~p, info: ~p", [Pid, Info]), WarnMsg = io_lib:format("long_gc warning: pid = ~p, info: ~p", [Pid, Info]),
lager:error("~s~n~p", [WarnMsg, procinfo(Pid)]), ?LOG(WarnMsg, procinfo(Pid)),
publish(long_gc, WarnMsg) publish(long_gc, WarnMsg)
end, State); end, State);
handle_info({monitor, Pid, long_schedule, Info}, State) when is_pid(Pid) -> handle_info({monitor, Pid, long_schedule, Info}, State) when is_pid(Pid) ->
suppress({long_schedule, Pid}, fun() -> suppress({long_schedule, Pid}, fun() ->
WarnMsg = io_lib:format("long_schedule warning: pid = ~p, info: ~p", [Pid, Info]), WarnMsg = io_lib:format("long_schedule warning: pid = ~p, info: ~p", [Pid, Info]),
lager:error("~s~n~p", [WarnMsg, procinfo(Pid)]), ?LOG(WarnMsg, procinfo(Pid)),
publish(long_schedule, WarnMsg) publish(long_schedule, WarnMsg)
end, State); end, State);
handle_info({monitor, Port, long_schedule, Info}, State) when is_port(Port) -> handle_info({monitor, Port, long_schedule, Info}, State) when is_port(Port) ->
suppress({long_schedule, Port}, fun() -> suppress({long_schedule, Port}, fun() ->
WarnMsg = io_lib:format("long_schedule warning: port = ~p, info: ~p", [Port, Info]), WarnMsg = io_lib:format("long_schedule warning: port = ~p, info: ~p", [Port, Info]),
lager:error("~s~n~p", [WarnMsg, erlang:port_info(Port)]), ?LOG(WarnMsg, erlang:port_info(Port)),
publish(long_schedule, WarnMsg) publish(long_schedule, WarnMsg)
end, State); end, State);
handle_info({monitor, Pid, large_heap, Info}, State) -> handle_info({monitor, Pid, large_heap, Info}, State) ->
suppress({large_heap, Pid}, fun() -> suppress({large_heap, Pid}, fun() ->
WarnMsg = io_lib:format("large_heap warning: pid = ~p, info: ~p", [Pid, Info]), WarnMsg = io_lib:format("large_heap warning: pid = ~p, info: ~p", [Pid, Info]),
lager:error("~s~n~p", [WarnMsg, procinfo(Pid)]), ?LOG(WarnMsg, procinfo(Pid)),
publish(large_heap, WarnMsg) publish(large_heap, WarnMsg)
end, State); end, State);
handle_info({monitor, SusPid, busy_port, Port}, State) -> handle_info({monitor, SusPid, busy_port, Port}, State) ->
suppress({busy_port, Port}, fun() -> suppress({busy_port, Port}, fun() ->
WarnMsg = io_lib:format("busy_port warning: suspid = ~p, port = ~p", [SusPid, Port]), WarnMsg = io_lib:format("busy_port warning: suspid = ~p, port = ~p", [SusPid, Port]),
lager:error("~s~n~p~n~p", [WarnMsg, procinfo(SusPid), erlang:port_info(Port)]), ?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)),
publish(busy_port, WarnMsg) publish(busy_port, WarnMsg)
end, State); end, State);
handle_info({monitor, SusPid, busy_dist_port, Port}, State) -> handle_info({monitor, SusPid, busy_dist_port, Port}, State) ->
suppress({busy_dist_port, Port}, fun() -> suppress({busy_dist_port, Port}, fun() ->
WarnMsg = io_lib:format("busy_dist_port warning: suspid = ~p, port = ~p", [SusPid, Port]), WarnMsg = io_lib:format("busy_dist_port warning: suspid = ~p, port = ~p", [SusPid, Port]),
lager:error("~s~n~p~n~p", [WarnMsg, procinfo(SusPid), erlang:port_info(Port)]), ?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)),
publish(busy_dist_port, WarnMsg) publish(busy_dist_port, WarnMsg)
end, State); end, State);
@ -130,11 +141,11 @@ handle_info(reset, State) ->
{noreply, State#state{events = []}}; {noreply, State#state{events = []}};
handle_info(Info, State) -> handle_info(Info, State) ->
lager:error("Unexpected info: ~p", [Info]), ?UNEXPECTED_INFO(Info, State).
{noreply, State}.
terminate(_Reason, #state{tick_tref = TRef}) -> terminate(_Reason, #state{tickref = TRef, tracelog = TraceLog}) ->
timer:cancel(TRef). timer:cancel(TRef),
cancel_tracelog(TraceLog).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -162,3 +173,13 @@ publish(Sysmon, WarnMsg) ->
topic(Sysmon) -> topic(Sysmon) ->
emqttd_topic:systop(list_to_binary(lists:concat(['sysmon/', Sysmon]))). emqttd_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).

View File

@ -19,40 +19,26 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd sysmon supervisor.
%%% emqttd pooler supervisor.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_pooler_sup). -module(emqttd_sysmon_sup).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl").
-behaviour(supervisor). -behaviour(supervisor).
%% API %% API
-export([start_link/0, start_link/1]). -export([start_link/0]).
%% Supervisor callbacks %% Supervisor callbacks
-export([init/1]). -export([init/1]).
start_link() -> start_link() ->
start_link(erlang:system_info(schedulers)). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
start_link(PoolSize) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, [PoolSize]).
init([PoolSize]) ->
gproc_pool:new(pooler, random, [{size, PoolSize}]),
Children = lists:map(
fun(I) ->
gproc_pool:add_worker(pooler, {pooler, I}, I),
{{emqttd_pooler, I},
{emqttd_pooler, start_link, [I]},
permanent, 5000, worker, [emqttd_pooler]}
end, lists:seq(1, PoolSize)),
{ok, {{one_for_all, 10, 100}, Children}}.
init([]) ->
Env = emqttd:env(sysmon),
{ok, {{one_for_one, 10, 100},
[{sysmon, {emqttd_sysmon, start_link, [Env]},
permanent, 5000, worker, [emqttd_sysmon]}]}}.

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc MQTT Topic Functions
%%% MQTT Topic Functions
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_topic). -module(emqttd_topic).
-author("Feng Lee <feng@emqtt.io>").
-import(lists, [reverse/1]). -import(lists, [reverse/1]).
-export([match/2, validate/1, triples/1, words/1, wildcard/1]). -export([match/2, validate/1, triples/1, words/1, wildcard/1]).

View File

@ -19,27 +19,26 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc Trace MQTT packets/messages by ClientID or Topic.
%%% Trace MQTT packets/messages by clientid or topic.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_trace). -module(emqttd_trace).
-author("Feng Lee <feng@emqtt.io>"). -behaviour(gen_server).
-include("emqttd_internal.hrl").
%% API Function Exports %% API Function Exports
-export([start_link/0]). -export([start_link/0]).
-export([start_trace/2, stop_trace/1, all_traces/0]). -export([start_trace/2, stop_trace/1, all_traces/0]).
-behaviour(gen_server).
%% gen_server Function Exports %% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
-record(state, {level, trace_map}). -record(state, {level, traces}).
-type trace_who() :: {client | topic, binary()}. -type trace_who() :: {client | topic, binary()}.
@ -85,41 +84,42 @@ all_traces() ->
gen_server:call(?MODULE, all_traces). gen_server:call(?MODULE, all_traces).
init([]) -> init([]) ->
{ok, #state{level = info, trace_map = #{}}}. {ok, #state{level = info, traces = #{}}}.
handle_call({start_trace, Who, LogFile}, _From, State = #state{level = Level, trace_map = TraceMap}) -> handle_call({start_trace, Who, LogFile}, _From, State = #state{level = Level, traces = Traces}) ->
case lager:trace_file(LogFile, [Who], Level, ?TRACE_OPTIONS) of case lager:trace_file(LogFile, [Who], Level, ?TRACE_OPTIONS) of
{ok, exists} -> {ok, exists} ->
{reply, {error, existed}, State}; {reply, {error, existed}, State};
{ok, Trace} -> {ok, Trace} ->
{reply, ok, State#state{trace_map = maps:put(Who, {Trace, LogFile}, TraceMap)}}; {reply, ok, State#state{traces = maps:put(Who, {Trace, LogFile}, Traces)}};
{error, Error} -> {error, Error} ->
{reply, {error, Error}, State} {reply, {error, Error}, State}
end; end;
handle_call({stop_trace, Who}, _From, State = #state{trace_map = TraceMap}) -> handle_call({stop_trace, Who}, _From, State = #state{traces = Traces}) ->
case maps:find(Who, TraceMap) of case maps:find(Who, Traces) of
{ok, {Trace, _LogFile}} -> {ok, {Trace, _LogFile}} ->
case lager:stop_trace(Trace) of case lager:stop_trace(Trace) of
ok -> ok; ok -> ok;
{error, Error} -> lager:error("Stop trace ~p error: ~p", [Who, Error]) {error, Error} -> lager:error("Stop trace ~p error: ~p", [Who, Error])
end, end,
{reply, ok, State#state{trace_map = maps:remove(Who, TraceMap)}}; {reply, ok, State#state{traces = maps:remove(Who, Traces)}};
error -> error ->
{reply, {error, not_found}, State} {reply, {error, not_found}, State}
end; end;
handle_call(all_traces, _From, State = #state{trace_map = TraceMap}) -> handle_call(all_traces, _From, State = #state{traces = Traces}) ->
{reply, [{Who, LogFile} || {Who, {_Trace, LogFile}} <- maps:to_list(TraceMap)], State}; {reply, [{Who, LogFile} || {Who, {_Trace, LogFile}}
<- maps:to_list(Traces)], State};
handle_call(_Req, _From, State) -> handle_call(Req, _From, State) ->
{reply, error, State}. ?UNEXPECTED_REQ(Req, State).
handle_cast(_Msg, State) -> handle_cast(Msg, State) ->
{noreply, State}. ?UNEXPECTED_MSG(Msg, State).
handle_info(_Info, State) -> handle_info(Info, State) ->
{noreply, State}. ?UNEXPECTED_INFO(Info, State).
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
ok. ok.

43
src/emqttd_trace_sup.erl Normal file
View File

@ -0,0 +1,43 @@
%%%-----------------------------------------------------------------------------
%%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in all
%%% copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE.
%%%-----------------------------------------------------------------------------
%%% @doc emqttd trace supervisor.
%%%
%%% @author Feng Lee <feng@emqtt.io>
%%%-----------------------------------------------------------------------------
-module(emqttd_trace_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
{ok, {{one_for_one, 10, 100},
[{trace, {emqttd_trace, start_link, []},
permanent, 5000, worker, [emqttd_trace]}]}}.

View File

@ -20,16 +20,16 @@
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc
%%% MQTT Topic Trie Tree. %%% MQTT Topic Trie.
%%% %%%
%%% [Trie](http://en.wikipedia.org/wiki/Trie) %%% [Trie](http://en.wikipedia.org/wiki/Trie)
%%% %%%
%%% @end %%% @end
%%%
%%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_trie). -module(emqttd_trie).
-author("Feng Lee <feng@emqtt.io>").
%% Mnesia Callbacks %% Mnesia Callbacks
-export([mnesia/1]). -export([mnesia/1]).
@ -62,16 +62,17 @@
%%%============================================================================= %%%=============================================================================
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Create trie tables %% @doc Create Trie Tables
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec mnesia(boot | copy) -> ok. -spec mnesia(boot | copy) -> ok.
mnesia(boot) -> mnesia(boot) ->
%% trie tree tables %% Trie Table
ok = emqttd_mnesia:create_table(trie, [ ok = emqttd_mnesia:create_table(trie, [
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, trie}, {record_name, trie},
{attributes, record_info(fields, trie)}]), {attributes, record_info(fields, trie)}]),
%% Trie Node Table
ok = emqttd_mnesia:create_table(trie_node, [ ok = emqttd_mnesia:create_table(trie_node, [
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, trie_node}, {record_name, trie_node},
@ -82,7 +83,9 @@ mnesia(boot) ->
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
mnesia(copy) -> mnesia(copy) ->
%% Copy Trie Table
ok = emqttd_mnesia:copy_table(trie), ok = emqttd_mnesia:copy_table(trie),
%% Copy Trie Node Table
ok = emqttd_mnesia:copy_table(trie_node). ok = emqttd_mnesia:copy_table(trie_node).
%%%============================================================================= %%%=============================================================================
@ -114,10 +117,10 @@ insert(Topic) when is_binary(Topic) ->
-spec match(Topic :: binary()) -> list(MatchedTopic :: binary()). -spec match(Topic :: binary()) -> list(MatchedTopic :: binary()).
match(Topic) when is_binary(Topic) -> match(Topic) when is_binary(Topic) ->
TrieNodes = match_node(root, emqttd_topic:words(Topic)), TrieNodes = match_node(root, emqttd_topic:words(Topic)),
[Name || #trie_node{topic=Name} <- TrieNodes, Name=/= undefined]. [Name || #trie_node{topic=Name} <- TrieNodes, Name =/= undefined].
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Delete topic from trie tree %% @doc Delete topic from trie
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec delete(Topic :: binary()) -> ok. -spec delete(Topic :: binary()) -> ok.

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd utility functions
%%% emqttd utility functions.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_util). -module(emqttd_util).
-author("Feng Lee <feng@emqtt.io>").
-export([apply_module_attributes/1, -export([apply_module_attributes/1,
all_module_attributes/1, all_module_attributes/1,
cancel_timer/1, cancel_timer/1,

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd erlang vm.
%%% emqttd erlang vm.
%%% %%%
%%% @end %%% @author huangdan
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_vm). -module(emqttd_vm).
-author('huangdan').
-export([schedulers/0]). -export([schedulers/0]).
-export([microsecs/0]). -export([microsecs/0]).
@ -36,23 +33,15 @@
-export([get_memory/0]). -export([get_memory/0]).
-export([get_process_list/0, -export([get_process_list/0, get_process_info/0, get_process_info/1,
get_process_info/0, get_process_gc/0, get_process_gc/1,
get_process_info/1,
get_process_gc/0,
get_process_gc/1,
get_process_group_leader_info/1, get_process_group_leader_info/1,
get_process_limit/0]). get_process_limit/0]).
-export([get_ets_list/0, -export([get_ets_list/0, get_ets_info/0, get_ets_info/1,
get_ets_info/0, get_ets_object/0, get_ets_object/1]).
get_ets_info/1,
get_ets_object/0,
get_ets_object/1]).
-export([get_port_types/0, -export([get_port_types/0, get_port_info/0, get_port_info/1]).
get_port_info/0,
get_port_info/1]).
-define(UTIL_ALLOCATORS, [temp_alloc, -define(UTIL_ALLOCATORS, [temp_alloc,
eheap_alloc, eheap_alloc,
@ -195,8 +184,7 @@ format_system_info(allocator, {_,_,_,List}) ->
List; List;
format_system_info(dist_ctrl, List) -> format_system_info(dist_ctrl, List) ->
lists:map(fun({Node, Socket}) -> lists:map(fun({Node, Socket}) ->
{ok, Stats} = inet:getstat(Socket), {ok, Stats} = inet:getstat(Socket), {Node, Stats}
{Node, Stats}
end, List); end, List);
format_system_info(driver_version, Value) -> format_system_info(driver_version, Value) ->
list_to_binary(Value); list_to_binary(Value);
@ -241,10 +229,9 @@ scheduler_usage(Interval) when is_integer(Interval) ->
scheduler_usage_diff(First, Last). scheduler_usage_diff(First, Last).
scheduler_usage_diff(First, Last) -> scheduler_usage_diff(First, Last) ->
lists:map( lists:map(fun({{I, A0, T0},{I, A1, T1}}) ->
fun({{I, A0, T0},{I, A1, T1}}) ->{I, (A1 - A0)/(T1 - T0)}end, {I, (A1 - A0)/(T1 - T0)}
lists:zip(lists:sort(First), lists:sort(Last)) end, lists:zip(lists:sort(First), lists:sort(Last))).
).
get_memory()-> get_memory()->
[{Key, get_memory(Key, current)} || Key <- [used, allocated, unused, usage]] ++ erlang:memory(). [{Key, get_memory(Key, current)} || Key <- [used, allocated, unused, usage]] ++ erlang:memory().
@ -278,10 +265,8 @@ allocators() ->
UtilAllocators = erlang:system_info(alloc_util_allocators), UtilAllocators = erlang:system_info(alloc_util_allocators),
Allocators = [sys_alloc, mseg_alloc|UtilAllocators], Allocators = [sys_alloc, mseg_alloc|UtilAllocators],
[{{A, N},lists:sort(proplists:delete(versions, Props))} || [{{A, N},lists:sort(proplists:delete(versions, Props))} ||
A <- Allocators, A <- Allocators, Allocs <- [erlang:system_info({allocator, A})],
Allocs <- [erlang:system_info({allocator, A})], Allocs =/= false, {_, N, Props} <- Allocs].
Allocs =/= false,
{_, N, Props} <- Allocs].
container_size(Prop, Keyword, Container) -> container_size(Prop, Keyword, Container) ->
Sbcs = container_value(Prop, Keyword, sbcs, Container), Sbcs = container_value(Prop, Keyword, sbcs, Container),
@ -294,7 +279,8 @@ container_value(Props, Pos, mbcs = Type, Container) when is_integer(Pos)->
Pool = case proplists:get_value(mbcs_pool, Props) of Pool = case proplists:get_value(mbcs_pool, Props) of
PoolProps when PoolProps =/= undefined -> PoolProps when PoolProps =/= undefined ->
element(Pos, lists:keyfind(Container, 1, PoolProps)); element(Pos, lists:keyfind(Container, 1, PoolProps));
_ -> 0 _ ->
0
end, end,
TypeProps = proplists:get_value(Type, Props), TypeProps = proplists:get_value(Type, Props),
Pool + element(Pos, lists:keyfind(Container, 1, TypeProps)); Pool + element(Pos, lists:keyfind(Container, 1, TypeProps));
@ -368,7 +354,7 @@ port_info(Port, meta) ->
case port_info(Port, registered_name) of case port_info(Port, registered_name) of
[] -> {meta, List}; [] -> {meta, List};
Name -> {meta, [Name | List]} Name -> {meta, [Name | List]}
end; end;
port_info(PortTerm, signals) -> port_info(PortTerm, signals) ->
port_info_type(PortTerm, signals, [connected, links, monitors]); port_info_type(PortTerm, signals, [connected, links, monitors]);
@ -387,25 +373,25 @@ port_info(PortTerm, specific) ->
Type =:= "sctp_inet" -> Type =:= "sctp_inet" ->
case catch inet:getstat(Port) of case catch inet:getstat(Port) of
{ok, Stats} -> [{statistics, Stats}]; {ok, Stats} -> [{statistics, Stats}];
_ ->[] _ -> []
end ++ end ++
case catch inet:peername(Port) of case catch inet:peername(Port) of
{ok, Peer} ->[{peername, Peer}]; {ok, Peer} -> [{peername, Peer}];
{error, _} ->[] {error, _} -> []
end ++ end ++
case catch inet:sockname(Port) of case catch inet:sockname(Port) of
{ok, Local} ->[{sockname, Local}]; {ok, Local} -> [{sockname, Local}];
{error, _} -> [] {error, _} -> []
end ++ end ++
case catch inet:getopts(Port, ?SOCKET_OPTS ) of case catch inet:getopts(Port, ?SOCKET_OPTS ) of
{ok, Opts} -> [{options, Opts}]; {ok, Opts} -> [{options, Opts}];
{error, _} -> [] {error, _} -> []
end; end;
{_, "efile"} -> {_, "efile"} ->
[]; [];
_ ->[] _ ->
end, []
end,
{specific, Props}; {specific, Props};
port_info(PortTerm, Keys) when is_list(Keys) -> port_info(PortTerm, Keys) when is_list(Keys) ->
Port = transform_port(PortTerm), Port = transform_port(PortTerm),
@ -426,11 +412,7 @@ transform_port(N) when is_integer(N) ->
Name = iolist_to_binary(atom_to_list(node())), Name = iolist_to_binary(atom_to_list(node())),
NameLen = iolist_size(Name), NameLen = iolist_size(Name),
Vsn = binary:last(term_to_binary(self())), Vsn = binary:last(term_to_binary(self())),
Bin = <<131, 102, 100, Bin = <<131, 102, 100, NameLen:2/unit:8, Name:NameLen/binary, N:4/unit:8, Vsn:8>>,
NameLen:2/unit:8,
Name:NameLen/binary,
N:4/unit:8,
Vsn:8>>,
binary_to_term(Bin). binary_to_term(Bin).
ports_type_list() -> ports_type_list() ->

View File

@ -19,15 +19,12 @@
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc emqttd websocket client
%%% emqttd websocket client.
%%% %%%
%%% @end %%% @author Feng Lee <feng@emqtt.io>
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_ws_client). -module(emqttd_ws_client).
-author("Feng Lee <feng@emqtt.io>").
-include("emqttd.hrl"). -include("emqttd.hrl").
-include("emqttd_protocol.hrl"). -include("emqttd_protocol.hrl").

View File

@ -0,0 +1,96 @@
%%%-----------------------------------------------------------------------------
%%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in all
%%% copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%%% SOFTWARE.
%%%-----------------------------------------------------------------------------
%%% @doc emqtt lager backend
%%%
%%% @author Feng Lee <feng@emqtt.io>
%%%-----------------------------------------------------------------------------
-module(lager_emqtt_backend).
-behaviour(gen_event).
-include_lib("lager/include/lager.hrl").
-export([init/1, handle_call/2, handle_event/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {level :: {'mask', integer()},
formatter :: atom(),
format_config :: any()}).
-define(DEFAULT_FORMAT, [time, " ", pid, " [",severity, "] ", message]).
init([Level]) when is_atom(Level) ->
init(Level);
init(Level) when is_atom(Level) ->
init([Level,{lager_default_formatter, ?DEFAULT_FORMAT}]);
init([Level,{Formatter, FormatterConfig}]) when is_atom(Formatter) ->
Levels = lager_util:config_to_mask(Level),
{ok, #state{level = Levels, formatter = Formatter,
format_config = FormatterConfig}}.
handle_call(get_loglevel, #state{level = Level} = State) ->
{ok, Level, State};
handle_call({set_loglevel, Level}, State) ->
try lager_util:config_to_mask(Level) of
Levels -> {ok, ok, State#state{level = Levels}}
catch
_:_ -> {ok, {error, bad_log_level}, State}
end;
handle_call(_Request, State) ->
{ok, ok, State}.
handle_event({log, Message}, State = #state{level = L}) ->
case lager_util:is_loggable(Message, L, ?MODULE) of
true ->
publish_log(Message, State);
false ->
{ok, State}
end;
handle_event(_Event, State) ->
{ok, State}.
handle_info(_Info, State) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
publish_log(Message, State = #state{formatter = Formatter,
format_config = FormatConfig}) ->
Severity = lager_msg:severity(Message),
Payload = Formatter:format(Message, FormatConfig),
emqttd_pubsub:publish(
emqttd_message:make(
log, topic(Severity), iolist_to_binary(Payload))),
{ok, State}.
topic(Severity) ->
emqttd_topic:systop(list_to_binary(lists:concat(['logs/', Severity]))).

View File

@ -20,11 +20,11 @@
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc
%%% emqttd_serialiser tests. %%% emqttd_serializer tests.
%%% %%%
%%% @end %%% @end
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
-module(emqttd_serialiser_tests). -module(emqttd_serializer_tests).
-ifdef(TEST). -ifdef(TEST).
@ -32,47 +32,47 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-import(emqttd_serialiser, [serialise/1]). -import(emqttd_serializer, [serialize/1]).
serialise_connect_test() -> serilize_connect_test() ->
serialise(?CONNECT_PACKET(#mqtt_packet_connect{})). serilize(?CONNECT_PACKET(#mqtt_packet_connect{})).
serialise_connack_test() -> serilize_connack_test() ->
ConnAck = #mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK}, ConnAck = #mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK},
variable = #mqtt_packet_connack{ack_flags = 0, return_code = 0}}, variable = #mqtt_packet_connack{ack_flags = 0, return_code = 0}},
?assertEqual(<<32,2,0,0>>, serialise(ConnAck)). ?assertEqual(<<32,2,0,0>>, serilize(ConnAck)).
serialise_publish_test() -> serilize_publish_test() ->
serialise(?PUBLISH_PACKET(?QOS_0, <<"Topic">>, undefined, <<"Payload">>)), serilize(?PUBLISH_PACKET(?QOS_0, <<"Topic">>, undefined, <<"Payload">>)),
serialise(?PUBLISH_PACKET(?QOS_1, <<"Topic">>, 938, <<"Payload">>)). serilize(?PUBLISH_PACKET(?QOS_1, <<"Topic">>, 938, <<"Payload">>)).
serialise_puback_test() -> serilize_puback_test() ->
serialise(?PUBACK_PACKET(?PUBACK, 10384)). serilize(?PUBACK_PACKET(?PUBACK, 10384)).
serialise_pubrel_test() -> serilize_pubrel_test() ->
serialise(?PUBREL_PACKET(10384)). serilize(?PUBREL_PACKET(10384)).
serialise_subscribe_test() -> serilize_subscribe_test() ->
TopicTable = [{<<"TopicQos0">>, ?QOS_0}, {<<"TopicQos1">>, ?QOS_1}, {<<"TopicQos2">>, ?QOS_2}], TopicTable = [{<<"TopicQos0">>, ?QOS_0}, {<<"TopicQos1">>, ?QOS_1}, {<<"TopicQos2">>, ?QOS_2}],
serialise(?SUBSCRIBE_PACKET(10, TopicTable)). serilize(?SUBSCRIBE_PACKET(10, TopicTable)).
serialise_suback_test() -> serilize_suback_test() ->
serialise(?SUBACK_PACKET(10, [?QOS_0, ?QOS_1, 128])). serilize(?SUBACK_PACKET(10, [?QOS_0, ?QOS_1, 128])).
serialise_unsubscribe_test() -> serilize_unsubscribe_test() ->
serialise(?UNSUBSCRIBE_PACKET(10, [<<"Topic1">>, <<"Topic2">>])). serilize(?UNSUBSCRIBE_PACKET(10, [<<"Topic1">>, <<"Topic2">>])).
serialise_unsuback_test() -> serilize_unsuback_test() ->
serialise(?UNSUBACK_PACKET(10)). serilize(?UNSUBACK_PACKET(10)).
serialise_pingreq_test() -> serilize_pingreq_test() ->
serialise(?PACKET(?PINGREQ)). serilize(?PACKET(?PINGREQ)).
serialise_pingresp_test() -> serilize_pingresp_test() ->
serialise(?PACKET(?PINGRESP)). serilize(?PACKET(?PINGRESP)).
serialise_disconnect_test() -> serilize_disconnect_test() ->
serialise(?PACKET(?DISCONNECT)). serilize(?PACKET(?DISCONNECT)).
-endif. -endif.