From 2a4ffc6645d21c12d6e0df9ac6bf1f372d368984 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Wed, 18 Apr 2018 16:34:23 +0800 Subject: [PATCH] Add more service modules for MQTT Version 5.0 --- include/emqx.hrl | 35 +-- src/emqx.erl | 57 ++--- src/emqx_access_control.erl | 41 ++-- src/emqx_acl_internal.erl | 34 +-- src/emqx_alarm.erl | 4 +- src/emqx_app.erl | 1 - src/emqx_auth_mod.erl | 4 +- src/emqx_banned.erl | 138 +++++++++--- src/emqx_bridge.erl | 10 +- src/emqx_bridge_sup_sup.erl | 56 ++--- src/emqx_broker.erl | 79 ++++--- src/emqx_broker_helper.erl | 85 +++++--- src/emqx_broker_sup.erl | 69 +++--- src/emqx_cli.erl | 30 +-- src/emqx_client.erl | 70 ++++++ src/emqx_cm.erl | 205 ++++++++++------- src/emqx_cm_stats.erl | 72 ------ src/emqx_cm_sup.erl | 34 ++- src/emqx_connection.erl | 8 +- src/emqx_ctl.erl | 112 +++++----- src/emqx_flow_control.erl | 70 ++++++ src/emqx_hooks.erl | 46 ++-- src/emqx_inflight.erl | 30 +-- src/emqx_json.erl | 71 ++++-- src/emqx_keepalive.erl | 49 +++-- src/emqx_kernel_sup.erl | 43 ++++ src/{emqx_log.erl => emqx_logger.erl} | 32 +-- src/emqx_message.erl | 2 +- src/emqx_metrics.erl | 254 ++++++++++++++++------ src/emqx_misc.erl | 37 ++-- src/emqx_mod_presence.erl | 70 +++--- src/emqx_mod_rewrite.erl | 4 +- src/emqx_mod_subscription.erl | 30 +-- src/emqx_modules.erl | 32 +-- src/emqx_mqtt_metrics.erl | 159 -------------- src/emqx_parser.erl | 30 +-- src/emqx_plugins.erl | 64 +++--- src/emqx_pmon.erl | 37 ++-- src/emqx_pool.erl | 106 +++++++++ src/emqx_pool_sup.erl | 35 ++- src/emqx_pooler.erl | 98 --------- src/emqx_protocol.erl | 27 ++- src/emqx_rate_limiter.erl | 67 ++++++ src/emqx_router.erl | 105 ++++----- src/emqx_router_helper.erl | 109 +++++----- src/emqx_router_sup.erl | 42 ++-- src/emqx_rpc.erl | 38 ++-- src/emqx_session.erl | 17 +- src/emqx_session_sup.erl | 37 ++-- src/emqx_shared_sub.erl | 97 +++++---- src/emqx_sm.erl | 239 ++++++++++++-------- src/emqx_sm_locker.erl | 35 +-- src/emqx_sm_registry.erl | 108 +++++---- src/emqx_sm_stats.erl | 72 ------ src/emqx_sm_sup.erl | 49 +++-- src/emqx_stats.erl | 237 ++++++++++++-------- src/emqx_sup.erl | 107 +++++---- src/emqx_sys.erl | 161 ++++++++------ src/{emqx_sysmon.erl => emqx_sys_mon.erl} | 105 +++++---- src/emqx_sys_sup.erl | 36 +++ src/emqx_sysmon_sup.erl | 35 --- src/emqx_tables.erl | 37 ++-- src/emqx_time.erl | 30 +-- src/emqx_topic.erl | 37 ++-- src/emqx_tracer.erl | 96 ++++---- src/emqx_tracer_sup.erl | 32 --- src/emqx_trie.erl | 48 ++-- src/emqx_ws.erl | 14 +- src/emqx_ws_connection.erl | 8 +- 69 files changed, 2428 insertions(+), 2040 deletions(-) create mode 100644 src/emqx_client.erl delete mode 100644 src/emqx_cm_stats.erl create mode 100644 src/emqx_flow_control.erl create mode 100644 src/emqx_kernel_sup.erl rename src/{emqx_log.erl => emqx_logger.erl} (60%) delete mode 100644 src/emqx_mqtt_metrics.erl create mode 100644 src/emqx_pool.erl delete mode 100644 src/emqx_pooler.erl create mode 100644 src/emqx_rate_limiter.erl delete mode 100644 src/emqx_sm_stats.erl rename src/{emqx_sysmon.erl => emqx_sys_mon.erl} (61%) create mode 100644 src/emqx_sys_sup.erl delete mode 100644 src/emqx_sysmon_sup.erl delete mode 100644 src/emqx_tracer_sup.erl diff --git a/include/emqx.hrl b/include/emqx.hrl index cb06118da..9ee90b186 100644 --- a/include/emqx.hrl +++ b/include/emqx.hrl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== %%-------------------------------------------------------------------- %% Banner @@ -154,7 +154,10 @@ %% Route %%-------------------------------------------------------------------- --record(route, { topic :: topic(), dest }). +-record(route, + { topic :: topic(), + dest :: node() | {binary(), node()} + }). -type(route() :: #route{}). diff --git a/src/emqx.erl b/src/emqx.erl index 24668029a..7d6e89427 100644 --- a/src/emqx.erl +++ b/src/emqx.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx). @@ -29,7 +29,7 @@ -export([topics/0, subscriptions/1, subscribers/1, subscribed/2]). %% Get/Set suboptions --export([getopts/2, setopts/3]). +-export([get_subopts/2, set_subopts/3]). %% Hooks API -export([hook/4, hook/3, unhook/2, run_hooks/2, run_hooks/3]). @@ -47,12 +47,17 @@ %%-------------------------------------------------------------------- %% @doc Start emqx application --spec(start() -> ok | {error, term()}). -start() -> application:start(?APP). +-spec(start() -> {ok, list(atom())} | {error, term()}). +start() -> + %% Check OS + %% Check VM + %% Check Mnesia + application:ensure_all_started(?APP). %% @doc Stop emqx application. -spec(stop() -> ok | {error, term()}). -stop() -> application:stop(?APP). +stop() -> + application:stop(?APP). %% @doc Is emqx running? -spec(is_running(node()) -> boolean()). @@ -96,13 +101,13 @@ unsubscribe(Topic, Subscriber) -> %% PubSub management API %%-------------------------------------------------------------------- --spec(getopts(topic() | string(), subscriber()) -> [suboption()]). -getopts(Topic, Subscriber) -> - emqx_broker:getopts(iolist_to_binary(Topic), list_to_subid(Subscriber)). +-spec(get_subopts(topic() | string(), subscriber()) -> [suboption()]). +get_subopts(Topic, Subscriber) -> + emqx_broker:get_subopts(iolist_to_binary(Topic), list_to_subid(Subscriber)). --spec(setopts(topic() | string(), subscriber(), [suboption()]) -> ok). -setopts(Topic, Subscriber, Options) when is_list(Options) -> - emqx_broker:setopts(iolist_to_binary(Topic), list_to_subid(Subscriber), Options). +-spec(set_subopts(topic() | string(), subscriber(), [suboption()]) -> ok). +set_subopts(Topic, Subscriber, Options) when is_list(Options) -> + emqx_broker:set_subopts(iolist_to_binary(Topic), list_to_subid(Subscriber), Options). -spec(topics() -> list(topic())). topics() -> emqx_router:topics(). @@ -165,7 +170,7 @@ shutdown() -> shutdown(normal). shutdown(Reason) -> - emqx_log:error("EMQ shutdown for ~s", [Reason]), + emqx_logger:error("EMQ shutdown for ~s", [Reason]), emqx_plugins:unload(), lists:foreach(fun application:stop/1, [emqx, ekka, mochiweb, esockd, gproc]). diff --git a/src/emqx_access_control.erl b/src/emqx_access_control.erl index 988a625dc..85713b7cc 100644 --- a/src/emqx_access_control.erl +++ b/src/emqx_access_control.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_access_control). @@ -28,10 +28,9 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-define(TAB, ?MODULE). -define(SERVER, ?MODULE). --define(TAB, access_control). - -type(password() :: undefined | binary()). -record(state, {}). @@ -122,7 +121,7 @@ stop() -> %%-------------------------------------------------------------------- init([]) -> - _ = emqx_tables:create(?TAB, [set, protected, {read_concurrency, true}]), + _ = emqx_tables:new(?TAB, [set, protected, {read_concurrency, true}]), {ok, #state{}}. handle_call({register_mod, Type, Mod, Opts, Seq}, _From, State) -> @@ -157,15 +156,15 @@ handle_call(stop, _From, State) -> {stop, normal, ok, State}; handle_call(Req, _From, State) -> - emqx_log:error("[AccessControl] Unexpected request: ~p", [Req]), + emqx_logger:error("[AccessControl] Unexpected request: ~p", [Req]), {reply, ignore, State}. handle_cast(Msg, State) -> - emqx_log:error("[AccessControl] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[AccessControl] Unexpected msg: ~p", [Msg]), {noreply, State}. handle_info(Info, State) -> - emqx_log:error("[AccessControl] Unexpected info: ~p", [Info]), + emqx_logger:error("[AccessControl] Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/emqx_acl_internal.erl b/src/emqx_acl_internal.erl index b7275af22..3f2824fbc 100644 --- a/src/emqx_acl_internal.erl +++ b/src/emqx_acl_internal.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_acl_internal). @@ -25,7 +25,7 @@ %% ACL callbacks -export([init/1, check_acl/2, reload_acl/1, description/0]). --define(ACL_RULE_TAB, mqtt_acl_rule). +-define(ACL_RULE_TAB, emqx_acl_rule). -record(state, {config}). @@ -48,7 +48,7 @@ all_rules() -> %% @doc Init internal ACL -spec(init([File :: string()]) -> {ok, State :: any()}). init([File]) -> - _ = emqx_tables:create(?ACL_RULE_TAB, [set, public, {read_concurrency, true}]), + _ = emqx_tables:new(?ACL_RULE_TAB, [set, public, {read_concurrency, true}]), {ok, load_rules_from_file(#state{config = File})}. load_rules_from_file(State = #state{config = AclFile}) -> diff --git a/src/emqx_alarm.erl b/src/emqx_alarm.erl index 73e2d2ca2..2b5990db4 100644 --- a/src/emqx_alarm.erl +++ b/src/emqx_alarm.erl @@ -92,7 +92,7 @@ handle_event({set_alarm, Alarm = #alarm{id = AlarmId, {summary, iolist_to_binary(Summary)}, {ts, emqx_time:now_secs(TS)}]) of {'EXIT', Reason} -> - emqx_log:error("[Alarm] Failed to encode set_alarm: ~p", [Reason]); + emqx_logger:error("[Alarm] Failed to encode set_alarm: ~p", [Reason]); JSON -> emqx_broker:publish(alarm_msg(alert, AlarmId, JSON)) end, @@ -101,7 +101,7 @@ handle_event({set_alarm, Alarm = #alarm{id = AlarmId, handle_event({clear_alarm, AlarmId}, Alarms) -> case catch emqx_json:encode([{id, AlarmId}, {ts, emqx_time:now_secs()}]) of {'EXIT', Reason} -> - emqx_log:error("[Alarm] Failed to encode clear_alarm: ~p", [Reason]); + emqx_logger:error("[Alarm] Failed to encode clear_alarm: ~p", [Reason]); JSON -> emqx_broker:publish(alarm_msg(clear, AlarmId, JSON)) end, diff --git a/src/emqx_app.erl b/src/emqx_app.erl index a4c0493d8..f7a51fc7f 100644 --- a/src/emqx_app.erl +++ b/src/emqx_app.erl @@ -34,7 +34,6 @@ start(_Type, _Args) -> ekka:start(), {ok, Sup} = emqx_sup:start_link(), %%TODO: fixme later - emqx_mqtt_metrics:init(), ok = register_acl_mod(), emqx_modules:load(), start_autocluster(), diff --git a/src/emqx_auth_mod.erl b/src/emqx_auth_mod.erl index 123527177..620584488 100644 --- a/src/emqx_auth_mod.erl +++ b/src/emqx_auth_mod.erl @@ -62,13 +62,13 @@ passwd_hash(pbkdf2, {Salt, Password, Macfun, Iterations, Dklen}) -> case pbkdf2:pbkdf2(Macfun, Password, Salt, Iterations, Dklen) of {ok, Hexstring} -> pbkdf2:to_hex(Hexstring); {error, Error} -> - emqx_log:error("[AuthMod] PasswdHash with pbkdf2 error:~p", [Error]), <<>> + emqx_logger:error("[AuthMod] PasswdHash with pbkdf2 error:~p", [Error]), <<>> end; passwd_hash(bcrypt, {Salt, Password}) -> case bcrypt:hashpw(Password, Salt) of {ok, HashPassword} -> list_to_binary(HashPassword); {error, Error}-> - emqx_log:error("[AuthMod] PasswdHash with bcrypt error:~p", [Error]), <<>> + emqx_logger:error("[AuthMod] PasswdHash with bcrypt error:~p", [Error]), <<>> end. hexstring(<>) -> diff --git a/src/emqx_banned.erl b/src/emqx_banned.erl index 85cfd8b6c..ff5250f27 100644 --- a/src/emqx_banned.erl +++ b/src/emqx_banned.erl @@ -1,72 +1,138 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -%% Banned an IP Address, ClientId? -module(emqx_banned). -behaviour(gen_server). +-include("emqx.hrl"). + +%% Mnesia bootstrap +-export([mnesia/1]). + +-boot_mnesia({mnesia, [boot]}). +-copy_mnesia({mnesia, [copy]}). + %% API -export([start_link/0]). +-export([check/1]). +-export([add/1, del/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-define(TAB, ?MODULE). -define(SERVER, ?MODULE). --record(state, {}). +-type(key() :: {client_id, client_id()} | + {ipaddr, inet:ip_address()} | + {username, username()}). -%%%=================================================================== -%%% API -%%%=================================================================== +-record(state, {expiry_timer}). -%% @doc Starts the server +-record(banned, {key :: key(), reason, by, desc, until}). + +%%-------------------------------------------------------------------- +%% Mnesia bootstrap +%%-------------------------------------------------------------------- + +mnesia(boot) -> + ok = ekka_mnesia:create_table(?TAB, [ + {type, ordered_set}, + {disc_copies, [node()]}, + {record_name, banned}, + {attributes, record_info(fields, banned)}]); + +mnesia(copy) -> + ok = ekka_mnesia:copy_table(?TAB). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +%% @doc Start the banned server -spec(start_link() -> {ok, pid()} | ignore | {error, any()}). start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -%%%=================================================================== -%%% gen_server callbacks -%%%=================================================================== +-spec(check(client()) -> boolean()). +check(#client{client_id = ClientId, + username = Username, + peername = {IPAddr, _}}) -> + ets:member(?TAB, {client_id, ClientId}) + orelse ets:member(?TAB, {username, Username}) + orelse ets:member(?TAB, {ipaddr, IPAddr}). + +add(Record) when is_record(Record, banned) -> + mnesia:dirty_write(?TAB, Record). + +del(Key) -> + mnesia:dirty_delete(?TAB, Key). + +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([]) -> - {ok, #state{}}. + emqx_timer:seed(), + {ok, ensure_expiry_timer(#state{})}. -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. +handle_call(Req, _From, State) -> + emqx_logger:error("[BANNED] Unexpected request: ~p", [Req]), + {reply, ignore, State}. -handle_cast(_Msg, State) -> +handle_cast(Msg, State) -> + emqx_logger:error("[BANNED] Unexpected msg: ~p", [Msg]), {noreply, State}. -handle_info(_Info, State) -> +handle_info({timeout, Ref, expire}, State = #state{expiry_timer = Ref}) -> + mnesia:async_dirty(fun expire_banned_items/1, [erlang:timestamp()]), + {noreply, ensure_expiry_timer(State), hibernate}; + +handle_info(Info, State) -> + emqx_logger:error("[BANNED] Unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, _State) -> - ok. +terminate(_Reason, #state{expiry_timer = Timer}) -> + emqx_misc:cancel_timer(Timer). code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%=================================================================== -%%% Internal functions -%%%=================================================================== - +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +ensure_expiry_timer(State) -> + Interval = emqx_config:get_env(banned_expiry_interval, timer:minutes(5)), + State#state{expiry_timer = emqx_misc:start_timer( + Interval + rand:uniform(Interval), expire)}. +expire_banned_items(Now) -> + expire_banned_item(mnesia:first(?TAB), Now). +expire_banned_item('$end_of_table', _Now) -> + ok; +expire_banned_item(Key, Now) -> + case mnesia:read(?TAB, Key) of + [#banned{until = undefined}] -> ok; + [B = #banned{until = Until}] when Until < Now -> + mnesia:delete_object(?TAB, B, sticky_write); + [] -> ok + end, + expire_banned_item(mnesia:next(Key), Now). diff --git a/src/emqx_bridge.erl b/src/emqx_bridge.erl index 1e998f6cb..f45e4045c 100644 --- a/src/emqx_bridge.erl +++ b/src/emqx_bridge.erl @@ -103,11 +103,11 @@ qname(Node, Topic) -> iolist_to_binary(["Bridge:", Node, ":", Topic]). handle_call(Req, _From, State) -> - emqx_log:error("[Bridge] Unexpected request: ~p", [Req]), + emqx_logger:error("[Bridge] Unexpected request: ~p", [Req]), {reply, ignore, State}. handle_cast(Msg, State) -> - emqx_log:error("[Bridge] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[Bridge] Unexpected msg: ~p", [Msg]), {noreply, State}. handle_info({dispatch, _Topic, Msg}, State = #state{mqueue = MQ, status = down}) -> @@ -118,7 +118,7 @@ handle_info({dispatch, _Topic, Msg}, State = #state{node = Node, status = up}) - {noreply, State, hibernate}; handle_info({nodedown, Node}, State = #state{node = Node, ping_down_interval = Interval}) -> - emqx_log:warning("[Bridge] Node Down: ~s", [Node]), + emqx_logger:warning("[Bridge] Node Down: ~s", [Node]), erlang:send_after(Interval, self(), ping_down_node), {noreply, State#state{status = down}, hibernate}; @@ -126,7 +126,7 @@ handle_info({nodeup, Node}, State = #state{node = Node}) -> %% TODO: Really fast?? case emqx:is_running(Node) of true -> - emqx_log:warning("[Bridge] Node up: ~s", [Node]), + emqx_logger:warning("[Bridge] Node up: ~s", [Node]), {noreply, dequeue(State#state{status = up})}; false -> self() ! {nodedown, Node}, @@ -149,7 +149,7 @@ handle_info({'EXIT', _Pid, normal}, State) -> {noreply, State}; handle_info(Info, State) -> - emqx_log:error("[Bridge] Unexpected info: ~p", [Info]), + emqx_logger:error("[Bridge] Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #state{pool = Pool, id = Id}) -> diff --git a/src/emqx_bridge_sup_sup.erl b/src/emqx_bridge_sup_sup.erl index 7c26cd047..14fecaecc 100644 --- a/src/emqx_bridge_sup_sup.erl +++ b/src/emqx_bridge_sup_sup.erl @@ -1,24 +1,27 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_bridge_sup_sup). -behavior(supervisor). --export([start_link/0, bridges/0, start_bridge/2, start_bridge/3, stop_bridge/2]). +-include("emqx.hrl"). + +-export([start_link/0, bridges/0]). +-export([start_bridge/2, start_bridge/3, stop_bridge/2]). -export([init/1]). @@ -32,27 +35,28 @@ start_link() -> %%-------------------------------------------------------------------- %% @doc List all bridges --spec(bridges() -> [{node(), binary(), pid()}]). +-spec(bridges() -> [{node(), topic(), pid()}]). bridges() -> [{Node, Topic, Pid} || {?CHILD_ID(Node, Topic), Pid, supervisor, _} - <- supervisor:which_children(?MODULE)]. + <- supervisor:which_children(?MODULE)]. %% @doc Start a bridge --spec(start_bridge(atom(), binary()) -> {ok, pid()} | {error, term()}). -start_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) -> +-spec(start_bridge(node(), topic()) -> {ok, pid()} | {error, term()}). +start_bridge(Node, Topic) when is_atom(Node), is_binary(Topic) -> start_bridge(Node, Topic, []). --spec(start_bridge(atom(), binary(), [emqx_bridge:option()]) -> {ok, pid()} | {error, term()}). +-spec(start_bridge(node(), topic(), [emqx_bridge:option()]) + -> {ok, pid()} | {error, term()}). start_bridge(Node, _Topic, _Options) when Node =:= node() -> {error, bridge_to_self}; -start_bridge(Node, Topic, Options) when is_atom(Node) andalso is_binary(Topic) -> +start_bridge(Node, Topic, Options) when is_atom(Node), is_binary(Topic) -> {ok, BridgeEnv} = emqx_config:get_env(bridge), Options1 = emqx_misc:merge_opts(BridgeEnv, Options), supervisor:start_child(?MODULE, bridge_spec(Node, Topic, Options1)). %% @doc Stop a bridge --spec(stop_bridge(atom(), binary()) -> {ok, pid()} | ok). -stop_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) -> +-spec(stop_bridge(node(), topic()) -> ok | {error, term()}). +stop_bridge(Node, Topic) when is_atom(Node), is_binary(Topic) -> ChildId = ?CHILD_ID(Node, Topic), case supervisor:terminate_child(?MODULE, ChildId) of ok -> supervisor:delete_child(?MODULE, ChildId); @@ -68,6 +72,6 @@ init([]) -> bridge_spec(Node, Topic, Options) -> {?CHILD_ID(Node, Topic), - {emqx_bridge_sup, start_link, [Node, Topic, Options]}, - permanent, infinity, supervisor, [emqx_bridge_sup]}. + {emqx_bridge_sup, start_link, [Node, Topic, Options]}, + permanent, infinity, supervisor, [emqx_bridge_sup]}. diff --git a/src/emqx_broker.erl b/src/emqx_broker.erl index 79f7911f7..e40f181cb 100644 --- a/src/emqx_broker.erl +++ b/src/emqx_broker.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_broker). @@ -45,17 +45,19 @@ -define(TIMEOUT, 120000). %% ETS tables --define(SUBOPTION, emqx_suboption). --define(SUBSCRIBER, emqx_subscriber). +-define(SUBOPTION, emqx_suboption). +-define(SUBSCRIBER, emqx_subscriber). -define(SUBSCRIPTION, emqx_subscription). %%-------------------------------------------------------------------- %% Start a broker %%-------------------------------------------------------------------- --spec(start_link(atom(), pos_integer()) -> {ok, pid()} | ignore | {error, term()}). +-spec(start_link(atom(), pos_integer()) + -> {ok, pid()} | ignore | {error, term()}). start_link(Pool, Id) -> - gen_server:start_link(?MODULE, [Pool, Id], [{hibernate_after, 2000}]). + gen_server:start_link(emqx_misc:proc_name(?MODULE, Id), + ?MODULE, [Pool, Id], [{hibernate_after, 2000}]). %%-------------------------------------------------------------------- %% Subscriber/Unsubscribe @@ -101,15 +103,32 @@ unsubscribe(Topic, Subscriber, Timeout) -> -spec(publish(message()) -> delivery() | stopped). publish(Msg = #message{from = From}) -> - emqx_tracer:trace(publish, From, Msg), + %% Hook to trace? + trace(public, From, Msg), case emqx_hooks:run('message.publish', [], Msg) of {ok, Msg1 = #message{topic = Topic}} -> publish(Topic, Msg1); {stop, Msg1} -> - emqx_log:warning("Stop publishing: ~s", [emqx_message:format(Msg1)]), + emqx_logger:warning("Stop publishing: ~s", [emqx_message:format(Msg1)]), stopped end. +%%-------------------------------------------------------------------- +%% Trace +%%-------------------------------------------------------------------- + +trace(publish, From, _Msg) when is_atom(From) -> + %% Dont' trace '$SYS' publish + ignore; +trace(public, #client{client_id = ClientId, username = Username}, + #message{topic = Topic, payload = Payload}) -> + emqx_logger:info([{client, ClientId}, {topic, Topic}], + "~s/~s PUBLISH to ~s: ~p", [ClientId, Username, Topic, Payload]); +trace(public, From, #message{topic = Topic, payload = Payload}) + when is_binary(From); is_list(From) -> + emqx_logger:info([{client, From}, {topic, Topic}], + "~s PUBLISH to ~s: ~p", [From, Topic, Payload]). + publish(Topic, Msg) -> route(aggre(emqx_router:match_routes(Topic)), delivery(Msg)). @@ -131,16 +150,14 @@ route(Routes, Delivery) -> aggre([]) -> []; -aggre([{To, Dest}]) -> +aggre([#route{topic = To, dest = Dest}]) -> [{To, Dest}]; aggre(Routes) -> lists:foldl( - fun({To, Node}, Acc) when is_atom(Node) -> + fun(#route{topic = To, dest = Node}, Acc) when is_atom(Node) -> [{To, Node} | Acc]; - ({To, {Group, _Node}}, Acc) -> - lists:usort([{To, Group} | Acc]); - ({To, {Cluster, Group, _Node}}, Acc) -> - lists:usort([{To, {Cluster, Group}} | Acc]) + (#route{topic = To, dest = {Group, _Node}}, Acc) -> + lists:usort([{To, Group} | Acc]) end, [], Routes). %% @doc Forward message to another node. @@ -148,7 +165,7 @@ forward(Node, To, Delivery) -> %% rpc:call to ensure the delivery, but the latency:( case emqx_rpc:call(Node, ?BROKER, dispatch, [To, Delivery]) of {badrpc, Reason} -> - emqx_log:error("[Broker] Failed to forward msg to ~s: ~p", [Node, Reason]), + emqx_logger:error("[Broker] Failed to forward msg to ~s: ~p", [Node, Reason]), Delivery; Delivery1 -> Delivery1 end. @@ -261,7 +278,7 @@ handle_call({set_subopts, Topic, Subscriber, Opts}, _From, State) -> end; handle_call(Request, _From, State) -> - emqx_log:error("[Broker] Unexpected request: ~p", [Request]), + emqx_logger:error("[Broker] Unexpected request: ~p", [Request]), {reply, ignore, State}. handle_cast({From, {subscribe, Topic, Subscriber, Options}}, State) -> @@ -292,7 +309,7 @@ handle_cast({From, {unsubscribe, Topic, Subscriber}}, State) -> {noreply, State}; handle_cast(Msg, State) -> - emqx_log:error("[Broker] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[Broker] Unexpected msg: ~p", [Msg]), {noreply, State}. handle_info({'DOWN', _MRef, process, SubPid, _Reason}, @@ -321,7 +338,7 @@ handle_info({'DOWN', _MRef, process, SubPid, _Reason}, {noreply, demonitor_subscriber(SubPid, State)}; handle_info(Info, State) -> - emqx_log:error("[Broker] Unexpected info: ~p", [Info]), + emqx_logger:error("[Broker] Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #state{pool = Pool, id = Id}) -> diff --git a/src/emqx_broker_helper.erl b/src/emqx_broker_helper.erl index fc9a84be9..f461c44c7 100644 --- a/src/emqx_broker_helper.erl +++ b/src/emqx_broker_helper.erl @@ -1,66 +1,79 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_broker_helper). -behaviour(gen_server). --export([start_link/1]). +-export([start_link/0]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --define(SERVER, ?MODULE). +-define(HELPER, ?MODULE). --record(state, {stats_fun, stats_timer}). +-record(state, {}). -%%-------------------------------------------------------------------- -%% API -%%-------------------------------------------------------------------- - --spec(start_link(fun()) -> {ok, pid()} | ignore | {error, any()}). -start_link(StatsFun) -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [StatsFun], []). +-spec(start_link() -> {ok, pid()} | ignore | {error, any()}). +start_link() -> + gen_server:start_link({local, ?HELPER}, ?MODULE, [], []). %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- -init([StatsFun]) -> - {ok, TRef} = timer:send_interval(timer:seconds(1), stats), - {ok, #state{stats_fun = StatsFun, stats_timer = TRef}}. +init([]) -> + emqx_stats:update_interval(broker_stats, stats_fun()), + {ok, #state{}, hibernate}. handle_call(Req, _From, State) -> - emqx_log:error("[BrokerHelper] Unexpected request: ~p", [Req]), + emqx_logger:error("[BrokerHelper] Unexpected request: ~p", [Req]), {reply, ignore, State}. handle_cast(Msg, State) -> - emqx_log:error("[BrokerHelper] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[BrokerHelper] Unexpected msg: ~p", [Msg]), {noreply, State}. -handle_info(stats, State = #state{stats_fun = StatsFun}) -> - StatsFun(), {noreply, State, hibernate}; - handle_info(Info, State) -> - emqx_log:error("[BrokerHelper] Unexpected info: ~p", [Info]), + emqx_logger:error("[BrokerHelper] Unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #state{stats_timer = TRef}) -> - timer:cancel(TRef). +terminate(_Reason, #state{}) -> + emqx_stats:cancel_update(broker_stats). code_change(_OldVsn, State, _Extra) -> {ok, State}. +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +stats_fun() -> + fun() -> + safe_update_stats(emqx_subscriber, + 'subscribers/count', 'subscribers/max'), + safe_update_stats(emqx_subscription, + 'subscriptions/count', 'subscriptions/max'), + safe_update_stats(emqx_suboptions, + 'suboptions/count', 'suboptions/max') + end. + +safe_update_stats(Tab, Stat, MaxStat) -> + case ets:info(Tab, size) of + undefined -> ok; + Size -> emqx_stats:setstat(Stat, MaxStat, Size) + end. + diff --git a/src/emqx_broker_sup.erl b/src/emqx_broker_sup.erl index 84bde9c6d..c16b5c63d 100644 --- a/src/emqx_broker_sup.erl +++ b/src/emqx_broker_sup.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_broker_sup). @@ -22,7 +22,11 @@ -export([init/1]). --define(CONCURRENCY_OPTS, [{read_concurrency, true}, {write_concurrency, true}]). +-import(lists, [foreach/2]). + +-define(TAB_OPTS, [public, + {read_concurrency, true}, + {write_concurrency, true}]). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). @@ -33,24 +37,23 @@ start_link() -> init([]) -> %% Create the pubsub tables - lists:foreach(fun create_tab/1, - [subscription, subscriber, suboption]), + foreach(fun create_tab/1, [subscription, subscriber, suboption]), %% Shared subscription - Shared = {shared_sub, {emqx_shared_sub, start_link, []}, - permanent, 5000, worker, [emqx_shared_sub]}, + SharedSub = {shared_sub, {emqx_shared_sub, start_link, []}, + permanent, 5000, worker, [emqx_shared_sub]}, %% Broker helper - Helper = {broker_helper, {emqx_broker_helper, start_link, [stats_fun()]}, + Helper = {broker_helper, {emqx_broker_helper, start_link, []}, permanent, 5000, worker, [emqx_broker_helper]}, %% Broker pool - PoolArgs = [broker, hash, emqx_sys:schedulers() * 2, + PoolArgs = [broker, hash, emqx_vm:schedulers() * 2, {emqx_broker, start_link, []}], - PoolSup = emqx_pool_sup:spec(broker_pool, PoolArgs), + PoolSup = emqx_pool_sup:spec(eqmx_broker_pool, PoolArgs), - {ok, {{one_for_all, 0, 3600}, [Shared, Helper, PoolSup]}}. + {ok, {{one_for_all, 0, 1}, [SharedSub, Helper, PoolSup]}}. %%-------------------------------------------------------------------- %% Create tables @@ -58,27 +61,15 @@ init([]) -> create_tab(suboption) -> %% Suboption: {Topic, Sub} -> [{qos, 1}] - emqx_tables:create(emqx_suboption, [public, set | ?CONCURRENCY_OPTS]); + emqx_tables:new(emqx_suboption, [set | ?TAB_OPTS]); create_tab(subscriber) -> %% Subscriber: Topic -> Sub1, Sub2, Sub3, ..., SubN %% duplicate_bag: o(1) insert - emqx_tables:create(emqx_subscriber, [public, duplicate_bag | ?CONCURRENCY_OPTS]); + emqx_tables:new(emqx_subscriber, [duplicate_bag | ?TAB_OPTS]); create_tab(subscription) -> %% Subscription: Sub -> Topic1, Topic2, Topic3, ..., TopicN %% bag: o(n) insert - emqx_tables:create(emqx_subscription, [public, bag | ?CONCURRENCY_OPTS]). - -%%-------------------------------------------------------------------- -%% Stats function -%%-------------------------------------------------------------------- - -stats_fun() -> - fun() -> - emqx_stats:setstat('subscribers/count', 'subscribers/max', - ets:info(emqx_subscriber, size)), - emqx_stats:setstat('subscriptions/count', 'subscriptions/max', - ets:info(emqx_subscription, size)) - end. + emqx_tables:new(emqx_subscription, [bag | ?TAB_OPTS]). diff --git a/src/emqx_cli.erl b/src/emqx_cli.erl index 3a98f95b4..ffbacb35e 100644 --- a/src/emqx_cli.erl +++ b/src/emqx_cli.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_cli). diff --git a/src/emqx_client.erl b/src/emqx_client.erl new file mode 100644 index 000000000..06399b1b5 --- /dev/null +++ b/src/emqx_client.erl @@ -0,0 +1,70 @@ +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== + +-module(emqx_client). + +-behaviour(gen_server). + +%% API +-export([start_link/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). + +-record(state, {}). + +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc Starts the server +-spec(start_link() -> {ok, pid()} | ignore | {error, any()}). +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init([]) -> + {ok, #state{}}. + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + + + + diff --git a/src/emqx_cm.erl b/src/emqx_cm.erl index 5a2ff93b4..a7e7f6356 100644 --- a/src/emqx_cm.erl +++ b/src/emqx_cm.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_cm). @@ -22,92 +22,145 @@ -export([start_link/0]). --export([lookup/1, reg/1, unreg/1]). +-export([lookup_client/1, register_client/1, register_client/2, + unregister_client/1]). + +-export([get_client_attrs/1, lookup_client_pid/1]). + +-export([get_client_stats/1, set_client_stats/2]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, {stats_fun, stats_timer, monitors}). +-record(state, {client_pmon}). --define(SERVER, ?MODULE). +-define(CM, ?MODULE). -%%-------------------------------------------------------------------- -%% API -%%-------------------------------------------------------------------- +%% ETS Tables. +-define(CLIENT, emqx_client). +-define(CLIENT_ATTRS, emqx_client_attrs). +-define(CLIENT_STATS, emqx_client_stats). -%% @doc Start the client manager +%% @doc Start the client manager. -spec(start_link() -> {ok, pid()} | ignore | {error, term()}). start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + gen_server:start_link({local, ?CM}, ?MODULE, [], []). -%% @doc Lookup ClientPid by ClientId --spec(lookup(client_id()) -> pid() | undefined). -lookup(ClientId) when is_binary(ClientId) -> - try ets:lookup_element(client, ClientId, 2) +%% @doc Lookup a client. +-spec(lookup_client(client_id()) -> list({client_id(), pid()})). +lookup_client(ClientId) when is_binary(ClientId) -> + ets:lookup(?CLIENT, ClientId). + +%% @doc Register a client. +-spec(register_client(client_id() | {client_id(), pid()}) -> ok). +register_client(ClientId) when is_binary(ClientId) -> + register_client({ClientId, self()}); + +register_client({ClientId, ClientPid}) when is_binary(ClientId), + is_pid(ClientPid) -> + register_client({ClientId, ClientPid}, []). + +-spec(register_client({client_id(), pid()}, list()) -> ok). +register_client({ClientId, ClientPid}, Attrs) when is_binary(ClientId), + is_pid(ClientPid) -> + ets:insert(?CLIENT, {ClientId, ClientPid}), + ets:insert(?CLIENT_ATTRS, {{ClientId, ClientPid}, Attrs}), + notify({registered, ClientId, ClientPid}). + +%% @doc Get client attrs +-spec(get_client_attrs({client_id(), pid()}) -> list()). +get_client_attrs({ClientId, ClientPid}) when is_binary(ClientId), + is_pid(ClientPid) -> + try ets:lookup_element(?CLIENT_ATTRS, {ClientId, ClientPid}, 2) catch - error:badarg -> undefined + error:badarg -> [] end. -%% @doc Register a clientId --spec(reg(client_id()) -> ok). -reg(ClientId) -> - gen_server:cast(?SERVER, {reg, ClientId, self()}). +%% @doc Unregister a client. +-spec(unregister_client(client_id() | {client_id(), pid()}) -> ok). +unregister_client(ClientId) when is_binary(ClientId) -> + unregister_client({ClientId, self()}); -%% @doc Unregister clientId with pid. --spec(unreg(client_id()) -> ok). -unreg(ClientId) -> - gen_server:cast(?SERVER, {unreg, ClientId, self()}). +unregister_client({ClientId, ClientPid}) when is_binary(ClientId), + is_pid(ClientPid) -> + ets:delete(?CLIENT_STATS, {ClientId, ClientPid}), + ets:delete(?CLIENT_ATTRS, {ClientId, ClientPid}), + ets:delete_object(?CLIENT, {ClientId, ClientPid}), + notify({unregistered, ClientId, ClientPid}). + +%% @doc Lookup client pid +-spec(lookup_client_pid(client_id()) -> pid() | undefined). +lookup_client_pid(ClientId) when is_binary(ClientId) -> + case lookup_client_pid(ClientId) of + [] -> undefined; + [{_, Pid}] -> Pid + end. + +%% @doc Get client stats +-spec(get_client_stats({client_id(), pid()}) -> list(emqx_stats:stats())). +get_client_stats({ClientId, ClientPid}) when is_binary(ClientId), + is_pid(ClientPid) -> + try ets:lookup_element(?CLIENT_STATS, {ClientId, ClientPid}, 2) + catch + error:badarg -> [] + end. + +%% @doc Set client stats. +-spec(set_client_stats(client_id(), list(emqx_stats:stats())) -> boolean()). +set_client_stats(ClientId, Stats) when is_binary(ClientId) -> + set_client_stats({ClientId, self()}, Stats); + +set_client_stats({ClientId, ClientPid}, Stats) when is_binary(ClientId), + is_pid(ClientPid) -> + ets:insert(?CLIENT_STATS, {{ClientId, ClientPid}, Stats}). + +notify(Msg) -> + gen_server:cast(?CM, {notify, Msg}). %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- init([]) -> - _ = emqx_tables:create(client, [public, set, {keypos, 2}, - {read_concurrency, true}, - {write_concurrency, true}]), - _ = emqx_tables:create(client_attrs, [public, set, - {write_concurrency, true}]), - {ok, #state{monitors = dict:new()}}. + TabOpts = [public, set, {write_concurrency, true}], + _ = emqx_tables:new(?CLIENT, [{read_concurrency, true} | TabOpts]), + _ = emqx_tables:new(?CLIENT_ATTRS, TabOpts), + _ = emqx_tables:new(?CLIENT_STATS, TabOpts), + ok = emqx_stats:update_interval(cm_stats, fun update_client_stats/0), + {ok, #state{client_pmon = emqx_pmon:new()}}. handle_call(Req, _From, State) -> - emqx_log:error("[CM] Unexpected request: ~p", [Req]), + emqx_logger:error("[CM] Unexpected request: ~p", [Req]), {reply, ignore, State}. -handle_cast({reg, ClientId, Pid}, State) -> - _ = ets:insert(client, {ClientId, Pid}), - {noreply, monitor_client(ClientId, Pid, State)}; +handle_cast({notify, {registered, ClientId, Pid}}, + State = #state{client_pmon = PMon}) -> + {noreply, State#state{client_pmon = PMon:monitor(Pid, ClientId)}}; -handle_cast({unreg, ClientId, Pid}, State) -> - case lookup(ClientId) of - Pid -> remove_client({ClientId, Pid}); - _ -> ok - end, - {noreply, State}; +handle_cast({notify, {unregistered, _ClientId, Pid}}, + State = #state{client_pmon = PMon}) -> + {noreply, State#state{client_pmon = PMon:demonitor(Pid)}}; handle_cast(Msg, State) -> - emqx_log:error("[CM] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[CM] Unexpected msg: ~p", [Msg]), {noreply, State}. -handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) -> - case dict:find(MRef, State#state.monitors) of - {ok, ClientId} -> - case lookup(ClientId) of - DownPid -> remove_client({ClientId, DownPid}); - _ -> ok - end, - {noreply, erase_monitor(MRef, State)}; - error -> - emqx_log:error("[CM] down client ~p not found", [DownPid]), - {noreply, State} +handle_info({'DOWN', _MRef, process, DownPid, _Reason}, + State = #state{client_pmon = PMon}) -> + case PMon:find(DownPid) of + undefined -> + {noreply, State}; + ClientId -> + unregister_client({ClientId, DownPid}), + {noreply, State#state{client_pmon = PMon:erase(DownPid)}} end; handle_info(Info, State) -> - emqx_log:error("[CM] Unexpected info: ~p", [Info]), + emqx_logger:error("[CM] Unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, _State = #state{stats_timer = TRef}) -> - timer:cancel(TRef). +terminate(_Reason, _State = #state{}) -> + emqx_stats:cancel_update(cm_stats). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -116,16 +169,10 @@ code_change(_OldVsn, State, _Extra) -> %% Internal functions %%-------------------------------------------------------------------- -remove_client(Client) -> - ets:delete_object(client, Client), - ets:delete(client_stats, Client), - ets:delete(client_attrs, Client). - -monitor_client(ClientId, Pid, State = #state{monitors = Monitors}) -> - MRef = erlang:monitor(process, Pid), - State#state{monitors = dict:store(MRef, ClientId, Monitors)}. - -erase_monitor(MRef, State = #state{monitors = Monitors}) -> - erlang:demonitor(MRef), - State#state{monitors = dict:erase(MRef, Monitors)}. +update_client_stats() -> + case ets:info(?CLIENT, size) of + undefined -> ok; + Size -> + emqx_stats:setstat('clients/count', 'clients/max', Size) + end. diff --git a/src/emqx_cm_stats.erl b/src/emqx_cm_stats.erl deleted file mode 100644 index 475087c37..000000000 --- a/src/emqx_cm_stats.erl +++ /dev/null @@ -1,72 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_cm_stats). - --behaviour(gen_statem). - --include("emqx.hrl"). - -%% API --export([start_link/0]). - --export([set_client_stats/2, get_client_stats/1, del_client_stats/1]). - -%% gen_statem callbacks --export([init/1, callback_mode/0, handle_event/4, terminate/3, code_change/4]). - --define(TAB, client_stats). - --record(state, {statsfun}). - -start_link() -> - gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []). - --spec(set_client_stats(client_id(), emqx_stats:stats()) -> true). -set_client_stats(ClientId, Stats) -> - ets:insert(?TAB, {ClientId, [{'$ts', emqx_time:now_secs()}|Stats]}). - --spec(get_client_stats(client_id()) -> emqx_stats:stats()). -get_client_stats(ClientId) -> - case ets:lookup(?TAB, ClientId) of - [{_, Stats}] -> Stats; - [] -> [] - end. - --spec(del_client_stats(client_id()) -> true). -del_client_stats(ClientId) -> - ets:delete(?TAB, ClientId). - -init([]) -> - _ = emqx_tables:create(?TAB, [public, {write_concurrency, true}]), - StatsFun = emqx_stats:statsfun('clients/count', 'clients/max'), - {ok, idle, #state{statsfun = StatsFun}, timer:seconds(1)}. - -callback_mode() -> handle_event_function. - -handle_event(timeout, _Timeout, idle, State = #state{statsfun = StatsFun}) -> - case ets:info(client, size) of - undefined -> ok; - Size -> StatsFun(Size) - end, - {next_state, idle, State, timer:seconds(1)}. - -terminate(_Reason, _StateName, _State) -> - ok. - -code_change(_OldVsn, StateName, State, _Extra) -> - {ok, StateName, State}. - diff --git a/src/emqx_cm_sup.erl b/src/emqx_cm_sup.erl index 89618e1c9..9b98e0c97 100644 --- a/src/emqx_cm_sup.erl +++ b/src/emqx_cm_sup.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_cm_sup). @@ -26,9 +26,7 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - Stats = {emqx_cm_stats, {emqx_cm_stats, start_link, []}, - permanent, 5000, worker, [emqx_cm_stats]}, CM = {emqx_cm, {emqx_cm, start_link, []}, permanent, 5000, worker, [emqx_cm]}, - {ok, {{one_for_all, 10, 3600}, [Stats, CM]}}. + {ok, {{one_for_all, 10, 3600}, [CM]}}. diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl index 4b693a3a4..c034e4701 100644 --- a/src/emqx_connection.erl +++ b/src/emqx_connection.erl @@ -57,8 +57,8 @@ -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). -define(LOG(Level, Format, Args, State), - emqx_log:Level("Client(~s): " ++ Format, - [esockd_net:format(State#state.peername) | Args])). + emqx_logger:Level("Client(~s): " ++ Format, + [esockd_net:format(State#state.peername) | Args])). start_link(Conn, Env) -> {ok, proc_lib:spawn_link(?MODULE, init, [[Conn, Env]])}. @@ -316,7 +316,7 @@ received(Bytes, State = #state{parser = Parser, {more, NewParser} -> {noreply, run_socket(State#state{parser = NewParser}), IdleTimeout}; {ok, Packet, Rest} -> - emqx_mqtt_metrics:received(Packet), + emqx_metrics:received(Packet), case emqx_protocol:received(Packet, ProtoState) of {ok, ProtoState1} -> received(Rest, State#state{parser = emqx_parser:initial_state(PacketSize), @@ -371,7 +371,7 @@ emit_stats(undefined, State) -> State; emit_stats(ClientId, State) -> {reply, Stats, _, _} = handle_call(stats, undefined, State), - emqx_stats:set_client_stats(ClientId, Stats), + emqx_cm:set_client_stats(ClientId, Stats), State. sock_stats(#state{connection = Conn}) -> diff --git a/src/emqx_ctl.erl b/src/emqx_ctl.erl index 15f86b785..805982e0e 100644 --- a/src/emqx_ctl.erl +++ b/src/emqx_ctl.erl @@ -1,26 +1,26 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_ctl). -behaviour(gen_server). -%% API Function Exports --export([start_link/0, register_cmd/2, register_cmd/3, unregister_cmd/1, - lookup/1, run/1]). +-export([start_link/0]). +-export([register_command/2, register_command/3, unregister_command/1]). +-export([run_command/2, lookup_command/1]). %% gen_server Function Exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -28,9 +28,10 @@ -record(state, {seq = 0}). --define(SERVER, ?MODULE). +-type(cmd() :: atom()). --define(TAB, ?MODULE). +-define(SERVER, ?MODULE). +-define(TAB, emqx_command). %%-------------------------------------------------------------------- %% API @@ -40,47 +41,44 @@ start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). %% @doc Register a command --spec(register_cmd(atom(), {module(), atom()}) -> ok). -register_cmd(Cmd, MF) -> - register_cmd(Cmd, MF, []). +-spec(register_command(cmd(), {module(), atom()}) -> ok). +register_command(Cmd, MF) when is_atom(Cmd) -> + register_command(Cmd, MF, []). -%% @doc Register a command with opts --spec(register_cmd(atom(), {module(), atom()}, list()) -> ok). -register_cmd(Cmd, MF, Opts) -> - cast({register_cmd, Cmd, MF, Opts}). +%% @doc Register a command with options +-spec(register_command(cmd(), {module(), atom()}, list()) -> ok). +register_command(Cmd, MF, Opts) when is_atom(Cmd) -> + cast({register_command, Cmd, MF, Opts}). %% @doc Unregister a command --spec(unregister_cmd(atom()) -> ok). -unregister_cmd(Cmd) -> - cast({unregister_cmd, Cmd}). +-spec(unregister_command(cmd()) -> ok). +unregister_command(Cmd) when is_atom(Cmd) -> + cast({unregister_command, Cmd}). cast(Msg) -> gen_server:cast(?SERVER, Msg). %% @doc Run a command --spec(run([string()]) -> any()). -run([]) -> usage(), ok; - -run(["help"]) -> usage(), ok; - -run([CmdS|Args]) -> - case lookup(list_to_atom(CmdS)) of +-spec(run_command(cmd(), [string()]) -> ok | {error, term()}). +run_command(help, []) -> + usage(); +run_command(Cmd, Args) when is_atom(Cmd) -> + case lookup_command(Cmd) of [{Mod, Fun}] -> try Mod:Fun(Args) of - _ -> ok + _ -> ok catch _:Reason -> - io:format("Reason:~p, get_stacktrace:~p~n", - [Reason, erlang:get_stacktrace()]), + emqx_logger:error("[CTL] Cmd error:~p, stacktrace:~p", + [Reason, erlang:get_stacktrace()]), {error, Reason} end; [] -> - usage(), - {error, cmd_not_found} + usage(), {error, cmd_not_found} end. %% @doc Lookup a command --spec(lookup(atom()) -> [{module(), atom()}]). -lookup(Cmd) -> +-spec(lookup_command(cmd()) -> [{module(), atom()}]). +lookup_command(Cmd) when is_atom(Cmd) -> case ets:match(?TAB, {{'_', Cmd}, '$1', '_'}) of [El] -> El; [] -> [] @@ -97,30 +95,32 @@ usage() -> %%-------------------------------------------------------------------- init([]) -> - ets:new(?TAB, [ordered_set, named_table, protected]), + _ = emqx_tables:new(?TAB, [ordered_set, protected]), {ok, #state{seq = 0}}. -handle_call(_Request, _From, State) -> - {reply, ok, State}. +handle_call(Req, _From, State) -> + emqx_logger:error("Unexpected request: ~p", [Req]), + {reply, ignore, State}. -handle_cast({register_cmd, Cmd, MF, Opts}, State = #state{seq = Seq}) -> +handle_cast({register_command, Cmd, MF, Opts}, State = #state{seq = Seq}) -> case ets:match(?TAB, {{'$1', Cmd}, '_', '_'}) of - [] -> - ets:insert(?TAB, {{Seq, Cmd}, MF, Opts}); + [] -> ets:insert(?TAB, {{Seq, Cmd}, MF, Opts}); [[OriginSeq] | _] -> - emqx_log:warning("[CLI] ~s is overidden by ~p", [Cmd, MF]), + emqx_logger:warning("[CTL] cmd ~s is overidden by ~p", [Cmd, MF]), ets:insert(?TAB, {{OriginSeq, Cmd}, MF, Opts}) end, noreply(next_seq(State)); -handle_cast({unregister_cmd, Cmd}, State) -> +handle_cast({unregister_command, Cmd}, State) -> ets:match_delete(?TAB, {{'_', Cmd}, '_', '_'}), noreply(State); -handle_cast(_Msg, State) -> +handle_cast(Msg, State) -> + emqx_logger:error("Unexpected msg: ~p", [Msg]), noreply(State). -handle_info(_Info, State) -> +handle_info(Info, State) -> + emqx_logger:error("Unexpected info: ~p", [Info]), noreply(State). terminate(_Reason, _State) -> @@ -143,7 +143,7 @@ next_seq(State = #state{seq = Seq}) -> -include_lib("eunit/include/eunit.hrl"). -register_cmd_test_() -> +register_command_test_() -> {setup, fun() -> {ok, InitState} = emqx_ctl:init([]), @@ -153,7 +153,7 @@ register_cmd_test_() -> ok = emqx_ctl:terminate(shutdown, State) end, fun(State = #state{seq = Seq}) -> - emqx_ctl:handle_cast({register_cmd, test0, {?MODULE, test0}, []}, State), + emqx_ctl:handle_cast({register_command, test0, {?MODULE, test0}, []}, State), [?_assertMatch([{{0,test0},{?MODULE, test0}, []}], ets:lookup(?TAB, {Seq,test0}))] end }. diff --git a/src/emqx_flow_control.erl b/src/emqx_flow_control.erl new file mode 100644 index 000000000..6effb1d53 --- /dev/null +++ b/src/emqx_flow_control.erl @@ -0,0 +1,70 @@ +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== + +-module(emqx_flow_control). + +-behaviour(gen_server). + +%% API +-export([start_link/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). + +-record(state, {}). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +%% @doc Starts the server +-spec(start_link() -> {ok, pid()} | ignore | {error, any()}). +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- + +init([]) -> + {ok, #state{}}. + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + + + + diff --git a/src/emqx_hooks.erl b/src/emqx_hooks.erl index 3f926a0bb..06bd0eabe 100644 --- a/src/emqx_hooks.erl +++ b/src/emqx_hooks.erl @@ -1,24 +1,23 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_hooks). -behaviour(gen_server). -%% Start -export([start_link/0]). %% Hooks API @@ -41,7 +40,7 @@ -record(hook, {name :: atom(), callbacks = [] :: list(#callback{})}). --define(HOOK_TAB, mqtt_hook). +-define(TAB, ?MODULE). start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). @@ -104,24 +103,25 @@ run_([], _Args, Acc) -> -spec(lookup(atom()) -> [#callback{}]). lookup(HookPoint) -> - case ets:lookup(?HOOK_TAB, HookPoint) of + case ets:lookup(?TAB, HookPoint) of [#hook{callbacks = Callbacks}] -> Callbacks; [] -> [] end. %%-------------------------------------------------------------------- -%% gen_server Callbacks +%% gen_server callbacks %%-------------------------------------------------------------------- init([]) -> - ets:new(?HOOK_TAB, [set, protected, named_table, {keypos, #hook.name}]), + _ = emqx_tables:new(?TAB, [set, protected, {keypos, #hook.name}, + {read_concurrency, true}]), {ok, #state{}}. handle_call({add, HookPoint, {Tag, Function}, InitArgs, Priority}, _From, State) -> Callback = #callback{tag = Tag, function = Function, init_args = InitArgs, priority = Priority}, {reply, - case ets:lookup(?HOOK_TAB, HookPoint) of + case ets:lookup(?TAB, HookPoint) of [#hook{callbacks = Callbacks}] -> case contain_(Tag, Function, Callbacks) of false -> @@ -135,7 +135,7 @@ handle_call({add, HookPoint, {Tag, Function}, InitArgs, Priority}, _From, State) handle_call({delete, HookPoint, {Tag, Function}}, _From, State) -> {reply, - case ets:lookup(?HOOK_TAB, HookPoint) of + case ets:lookup(?TAB, HookPoint) of [#hook{callbacks = Callbacks}] -> case contain_(Tag, Function, Callbacks) of true -> @@ -167,7 +167,7 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- insert_hook_(HookPoint, Callbacks) -> - ets:insert(?HOOK_TAB, #hook{name = HookPoint, callbacks = Callbacks}), ok. + ets:insert(?TAB, #hook{name = HookPoint, callbacks = Callbacks}), ok. add_callback_(Callback, Callbacks) -> lists:keymerge(#callback.priority, Callbacks, [Callback]). diff --git a/src/emqx_inflight.erl b/src/emqx_inflight.erl index 3bdc8b02a..73a60058a 100644 --- a/src/emqx_inflight.erl +++ b/src/emqx_inflight.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_inflight). diff --git a/src/emqx_json.erl b/src/emqx_json.erl index 907b6e76d..e3b4241d6 100644 --- a/src/emqx_json.erl +++ b/src/emqx_json.erl @@ -1,22 +1,23 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_json). --export([encode/1, encode/2, decode/1, decode/2]). +-export([encode/1, encode/2, safe_encode/1, safe_encode/2]). +-export([decode/1, decode/2, safe_decode/1, safe_decode/2]). -spec(encode(jsx:json_term()) -> jsx:json_text()). encode(Term) -> @@ -26,11 +27,41 @@ encode(Term) -> encode(Term, Opts) -> jsx:encode(Term, Opts). +-spec(safe_encode(jsx:json_term()) + -> {ok, jsx:json_text()} | {error, term()}). +safe_encode(Term) -> + safe_encode(Term, []). + +-spec(safe_encode(jsx:json_term(), jsx_to_json:config()) + -> {ok, jsx:json_text()} | {error, term()}). +safe_encode(Term, Opts) -> + try encode(Term, Opts) of + Json -> {ok, Json} + catch + error:Reason -> + {error, Reason} + end. + -spec(decode(jsx:json_text()) -> jsx:json_term()). -decode(JSON) -> - jsx:decode(JSON). +decode(Json) -> + jsx:decode(Json). -spec(decode(jsx:json_text(), jsx_to_json:config()) -> jsx:json_term()). -decode(JSON, Opts) -> - jsx:decode(JSON, Opts). +decode(Json, Opts) -> + jsx:decode(Json, Opts). + +-spec(safe_decode(jsx:json_text()) + -> {ok, jsx:json_term()} | {error, term()}). +safe_decode(Json) -> + safe_decode(Json, []). + +-spec(safe_decode(jsx:json_text(), jsx_to_json:config()) + -> {ok, jsx:json_term()} | {error, term()}). +safe_decode(Json, Opts) -> + try decode(Json, Opts) of + Term -> {ok, Term} + catch + error:Reason -> + {error, Reason} + end. diff --git a/src/emqx_keepalive.erl b/src/emqx_keepalive.erl index b1d8d87b0..ae38d28bc 100644 --- a/src/emqx_keepalive.erl +++ b/src/emqx_keepalive.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_keepalive). @@ -29,19 +29,21 @@ start(_, 0, _) -> {ok, #keepalive{}}; start(StatFun, TimeoutSec, TimeoutMsg) -> - case StatFun() of + case catch StatFun() of {ok, StatVal} -> {ok, #keepalive{statfun = StatFun, statval = StatVal, tsec = TimeoutSec, tmsg = TimeoutMsg, tref = timer(TimeoutSec, TimeoutMsg)}}; {error, Error} -> - {error, Error} + {error, Error}; + {'EXIT', Reason} -> + {error, Reason} end. -%% @doc Check keepalive, called when timeout. +%% @doc Check keepalive, called when timeout... -spec(check(keepalive()) -> {ok, keepalive()} | {error, term()}). check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repeat}) -> - case StatFun() of + case catch StatFun() of {ok, NewVal} -> if NewVal =/= LastVal -> {ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = 0})}; @@ -51,9 +53,12 @@ check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repe {error, timeout} end; {error, Error} -> - {error, Error} + {error, Error}; + {'EXIT', Reason} -> + {error, Reason} end. +-spec(resume(keepalive()) -> keepalive()). resume(KeepAlive = #keepalive{tsec = TimeoutSec, tmsg = TimeoutMsg}) -> KeepAlive#keepalive{tref = timer(TimeoutSec, TimeoutMsg)}. @@ -64,6 +69,6 @@ cancel(#keepalive{tref = TRef}) when is_reference(TRef) -> cancel(_) -> ok. -timer(Sec, Msg) -> - erlang:send_after(timer:seconds(Sec), self(), Msg). +timer(Secs, Msg) -> + erlang:send_after(timer:seconds(Secs), self(), Msg). diff --git a/src/emqx_kernel_sup.erl b/src/emqx_kernel_sup.erl new file mode 100644 index 000000000..486c20705 --- /dev/null +++ b/src/emqx_kernel_sup.erl @@ -0,0 +1,43 @@ +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== + +-module(emqx_kernel_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + {ok, {{one_for_one, 10, 100}, + [child_spec(emqx_pool, supervisor), + child_spec(emqx_alarm, worker), + child_spec(emqx_hooks, worker), + child_spec(emqx_stats, worker), + child_spec(emqx_metrics, worker), + child_spec(emqx_ctl, worker), + child_spec(emqx_tracer, worker)]}}. + +child_spec(M, worker) -> + {M, {M, start_link, []}, permanent, 5000, worker, [M]}; +child_spec(M, supervisor) -> + {M, {M, start_link, []}, + permanent, infinity, supervisor, [M]}. + diff --git a/src/emqx_log.erl b/src/emqx_logger.erl similarity index 60% rename from src/emqx_log.erl rename to src/emqx_logger.erl index 7f85b3309..0c5cf9240 100644 --- a/src/emqx_log.erl +++ b/src/emqx_logger.erl @@ -1,20 +1,20 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== --module(emqx_log). +-module(emqx_logger). -compile({no_auto_import,[error/1]}). diff --git a/src/emqx_message.erl b/src/emqx_message.erl index 3c732f676..b8b919ee9 100644 --- a/src/emqx_message.erl +++ b/src/emqx_message.erl @@ -50,7 +50,7 @@ get_flag(Flag, #message{flags = Flags}, Default) -> %% @doc Set flag -spec(set_flag(message_flag(), message()) -> message()). -set_flag(Flag, Msg = #message{flags = Flags}) -> +set_flag(Flag, Msg = #message{flags = Flags}) when is_atom(Flag) -> Msg#message{flags = maps:put(Flag, true, Flags)}. %% @doc Unset flag diff --git a/src/emqx_metrics.erl b/src/emqx_metrics.erl index ba5ddf3c0..c356e62d7 100644 --- a/src/emqx_metrics.erl +++ b/src/emqx_metrics.erl @@ -1,55 +1,110 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_metrics). --behaviour(gen_server). +-include("emqx_mqtt.hrl"). -%% API Function Exports --export([start_link/0, create/1]). +-export([start_link/0]). --export([all/0, value/1, inc/1, inc/2, inc/3, dec/2, dec/3, set/2]). +-export([new/1, all/0]). + +-export([val/1, inc/1, inc/2, inc/3, dec/2, dec/3, set/2]). + +%% Received/sent metrics +-export([received/1, sent/1]). %% gen_server Function Exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, {tick}). +-record(state, {}). --define(TAB, metrics). +%% Bytes sent and received of Broker +-define(BYTES_METRICS, [ + {counter, 'bytes/received'}, % Total bytes received + {counter, 'bytes/sent'} % Total bytes sent +]). + +%% Packets sent and received of broker +-define(PACKET_METRICS, [ + {counter, 'packets/received'}, % All Packets received + {counter, 'packets/sent'}, % All Packets sent + {counter, 'packets/connect'}, % CONNECT Packets received + {counter, 'packets/connack'}, % CONNACK Packets sent + {counter, 'packets/publish/received'}, % PUBLISH packets received + {counter, 'packets/publish/sent'}, % PUBLISH packets sent + {counter, 'packets/puback/received'}, % PUBACK packets received + {counter, 'packets/puback/sent'}, % PUBACK packets sent + {counter, 'packets/puback/missed'}, % PUBACK packets missed + {counter, 'packets/pubrec/received'}, % PUBREC packets received + {counter, 'packets/pubrec/sent'}, % PUBREC packets sent + {counter, 'packets/pubrec/missed'}, % PUBREC packets missed + {counter, 'packets/pubrel/received'}, % PUBREL packets received + {counter, 'packets/pubrel/sent'}, % PUBREL packets sent + {counter, 'packets/pubrel/missed'}, % PUBREL packets missed + {counter, 'packets/pubcomp/received'}, % PUBCOMP packets received + {counter, 'packets/pubcomp/sent'}, % PUBCOMP packets sent + {counter, 'packets/pubcomp/missed'}, % PUBCOMP packets missed + {counter, 'packets/subscribe'}, % SUBSCRIBE Packets received + {counter, 'packets/suback'}, % SUBACK packets sent + {counter, 'packets/unsubscribe'}, % UNSUBSCRIBE Packets received + {counter, 'packets/unsuback'}, % UNSUBACK Packets sent + {counter, 'packets/pingreq'}, % PINGREQ packets received + {counter, 'packets/pingresp'}, % PINGRESP Packets sent + {counter, 'packets/disconnect'}, % DISCONNECT Packets received + {counter, 'packets/auth'} % Auth Packets received +]). + +%% Messages sent and received of broker +-define(MESSAGE_METRICS, [ + {counter, 'messages/received'}, % All Messages received + {counter, 'messages/sent'}, % All Messages sent + {counter, 'messages/qos0/received'}, % QoS0 Messages received + {counter, 'messages/qos0/sent'}, % QoS0 Messages sent + {counter, 'messages/qos1/received'}, % QoS1 Messages received + {counter, 'messages/qos1/sent'}, % QoS1 Messages sent + {counter, 'messages/qos2/received'}, % QoS2 Messages received + {counter, 'messages/qos2/sent'}, % QoS2 Messages sent + {counter, 'messages/qos2/dropped'}, % QoS2 Messages dropped + {gauge, 'messages/retained'}, % Messagea retained + {counter, 'messages/dropped'}, % Messages dropped + {counter, 'messages/forward'} % Messages forward +]). + +-define(TAB, ?MODULE). -define(SERVER, ?MODULE). -%%-------------------------------------------------------------------- -%% API -%%-------------------------------------------------------------------- - %% @doc Start the metrics server -spec(start_link() -> {ok, pid()} | ignore | {error, term()}). start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -create({gauge, Name}) -> +%%-------------------------------------------------------------------- +%% Metrics API +%%-------------------------------------------------------------------- + +new({gauge, Name}) -> ets:insert(?TAB, {{Name, 0}, 0}); -create({counter, Name}) -> - Schedulers = lists:seq(1, erlang:system_info(schedulers)), +new({counter, Name}) -> + Schedulers = lists:seq(1, emqx_vm:schedulers()), ets:insert(?TAB, [{{Name, I}, 0} || I <- Schedulers]). - %% @doc Get all metrics -spec(all() -> [{atom(), non_neg_integer()}]). all() -> @@ -63,8 +118,8 @@ all() -> end, #{}, ?TAB)). %% @doc Get metric value --spec(value(atom()) -> non_neg_integer()). -value(Metric) -> +-spec(val(atom()) -> non_neg_integer()). +val(Metric) -> lists:sum(ets:select(?TAB, [{{{Metric, '_'}, '$1'}, [], ['$1']}])). %% @doc Increase counter @@ -84,9 +139,9 @@ inc(Metric, Val) when is_atom(Metric) -> %% @doc Increase metric value -spec(inc(counter | gauge, atom(), pos_integer()) -> pos_integer()). inc(gauge, Metric, Val) -> - ets:update_counter(?TAB, key(gauge, Metric), {2, Val}); + update_counter(key(gauge, Metric), {2, Val}); inc(counter, Metric, Val) -> - ets:update_counter(?TAB, key(counter, Metric), {2, Val}). + update_counter(key(counter, Metric), {2, Val}). %% @doc Decrease metric value -spec(dec(gauge, atom()) -> integer()). @@ -96,7 +151,7 @@ dec(gauge, Metric) -> %% @doc Decrease metric value -spec(dec(gauge, atom(), pos_integer()) -> integer()). dec(gauge, Metric, Val) -> - ets:update_counter(?TAB, key(gauge, Metric), {2, -Val}). + update_counter(key(gauge, Metric), {2, -Val}). %% @doc Set metric value set(Metric, Val) when is_atom(Metric) -> @@ -104,57 +159,120 @@ set(Metric, Val) when is_atom(Metric) -> set(gauge, Metric, Val) -> ets:insert(?TAB, {key(gauge, Metric), Val}). -%% @doc Metric Key +%% @doc Metric key key(gauge, Metric) -> {Metric, 0}; key(counter, Metric) -> {Metric, erlang:system_info(scheduler_id)}. +update_counter(Key, UpOp) -> + ets:update_counter(?TAB, Key, UpOp). + %%-------------------------------------------------------------------- -%% gen_server Callbacks +%% Receive/Sent metrics +%%-------------------------------------------------------------------- + +%% @doc Count packets received. +-spec(received(mqtt_packet()) -> ok). +received(Packet) -> + inc('packets/received'), + received1(Packet). +received1(?PUBLISH_PACKET(Qos, _PktId)) -> + inc('packets/publish/received'), + inc('messages/received'), + qos_received(Qos); +received1(?PACKET(Type)) -> + received2(Type). +received2(?CONNECT) -> + inc('packets/connect'); +received2(?PUBACK) -> + inc('packets/puback/received'); +received2(?PUBREC) -> + inc('packets/pubrec/received'); +received2(?PUBREL) -> + inc('packets/pubrel/received'); +received2(?PUBCOMP) -> + inc('packets/pubcomp/received'); +received2(?SUBSCRIBE) -> + inc('packets/subscribe'); +received2(?UNSUBSCRIBE) -> + inc('packets/unsubscribe'); +received2(?PINGREQ) -> + inc('packets/pingreq'); +received2(?DISCONNECT) -> + inc('packets/disconnect'); +received2(_) -> + ignore. +qos_received(?QOS_0) -> + inc('messages/qos0/received'); +qos_received(?QOS_1) -> + inc('messages/qos1/received'); +qos_received(?QOS_2) -> + inc('messages/qos2/received'). + +%% @doc Count packets received. Will not count $SYS PUBLISH. +-spec(sent(mqtt_packet()) -> ignore | non_neg_integer()). +sent(?PUBLISH_PACKET(_Qos, <<"$SYS/", _/binary>>, _, _)) -> + ignore; +sent(Packet) -> + inc('packets/sent'), + sent1(Packet). +sent1(?PUBLISH_PACKET(Qos, _PktId)) -> + inc('packets/publish/sent'), + inc('messages/sent'), + qos_sent(Qos); +sent1(?PACKET(Type)) -> + sent2(Type). +sent2(?CONNACK) -> + inc('packets/connack'); +sent2(?PUBACK) -> + inc('packets/puback/sent'); +sent2(?PUBREC) -> + inc('packets/pubrec/sent'); +sent2(?PUBREL) -> + inc('packets/pubrel/sent'); +sent2(?PUBCOMP) -> + inc('packets/pubcomp/sent'); +sent2(?SUBACK) -> + inc('packets/suback'); +sent2(?UNSUBACK) -> + inc('packets/unsuback'); +sent2(?PINGRESP) -> + inc('packets/pingresp'); +sent2(_Type) -> + ignore. +qos_sent(?QOS_0) -> + inc('messages/qos0/sent'); +qos_sent(?QOS_1) -> + inc('messages/qos1/sent'); +qos_sent(?QOS_2) -> + inc('messages/qos2/sent'). + +%%-------------------------------------------------------------------- +%% gen_server callbacks %%-------------------------------------------------------------------- init([]) -> - emqx_time:seed(), % Create metrics table - _ = ets:new(?TAB, [set, public, named_table, {write_concurrency, true}]), - % Tick to publish metrics - {ok, TRef} = timer:send_after(emqx_sys:sys_interval(), tick), - {ok, #state{tick = TRef}, hibernate}. + _ = emqx_tables:new(?TAB, [set, public, {write_concurrency, true}]), + lists:foreach(fun new/1, ?BYTES_METRICS ++ ?PACKET_METRICS ++ ?MESSAGE_METRICS), + {ok, #state{}, hibernate}. -handle_call(_Req, _From, State) -> - {reply, error, State}. +handle_call(Req, _From, State) -> + emqx_logger:error("[METRICS] Unexpected request: ~p", [Req]), + {reply, ignore, State}. -handle_cast(_Msg, State) -> +handle_cast(Msg, State) -> + emqx_logger:error("[METRICS] Unexpected msg: ~p", [Msg]), {noreply, State}. -handle_info(tick, State) -> - % publish metric message - [publish(Metric, Val) || {Metric, Val} <- all()], - {noreply, State, hibernate}; - -handle_info(_Info, State) -> +handle_info(Info, State) -> + emqx_logger:error("[METRICS] Unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #state{tick = TRef}) -> - %%TODO: - timer:cancel(TRef). +terminate(_Reason, #state{}) -> + ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%-------------------------------------------------------------------- -%% Internal functions -%%-------------------------------------------------------------------- - -%% TODO: the depencies are not right - -publish(Metric, Val) -> - Msg = emqx_message:make(metrics, metric_topic(Metric), bin(Val)), - emqx_broker:publish(emqx_message:set_flag(sys, Msg)). - -metric_topic(Metric) -> - emqx_topic:systop(list_to_binary(lists:concat(['metrics/', Metric]))). - -bin(I) when is_integer(I) -> list_to_binary(integer_to_list(I)). - diff --git a/src/emqx_misc.erl b/src/emqx_misc.erl index 0d3eecc5f..27c18f8c3 100644 --- a/src/emqx_misc.erl +++ b/src/emqx_misc.erl @@ -1,25 +1,26 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_misc). -export([merge_opts/2, start_timer/2, start_timer/3, cancel_timer/1, - proc_stats/0, proc_stats/1]). + proc_name/2, proc_stats/0, proc_stats/1]). %% @doc Merge Options +-spec(merge_opts(list(), list()) -> list()). merge_opts(Defaults, Options) -> lists:foldl( fun({Opt, Val}, Acc) -> @@ -51,6 +52,10 @@ cancel_timer(Timer) -> _ -> ok end. +-spec(proc_name(atom(), pos_integer()) -> atom()). +proc_name(Mod, Id) -> + list_to_atom(lists:concat([Mod, "_", Id])). + -spec(proc_stats() -> list()). proc_stats() -> proc_stats(self()). diff --git a/src/emqx_mod_presence.erl b/src/emqx_mod_presence.erl index 17ece8211..ca3f83e8f 100644 --- a/src/emqx_mod_presence.erl +++ b/src/emqx_mod_presence.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_mod_presence). @@ -34,32 +34,32 @@ on_client_connected(ConnAck, Client = #client{client_id = ClientId, %%clean_sess = CleanSess, %%proto_ver = ProtoVer }, Env) -> - case catch emqx_json:encode([{clientid, ClientId}, - {username, Username}, - {ipaddress, iolist_to_binary(emqx_net:ntoa(IpAddr))}, - %%{clean_sess, CleanSess}, %%TODO:: fixme later - %%{protocol, ProtoVer}, - {connack, ConnAck}, - {ts, emqx_time:now_secs()}]) of - Payload when is_binary(Payload) -> + case emqx_json:safe_encode([{clientid, ClientId}, + {username, Username}, + {ipaddress, iolist_to_binary(emqx_net:ntoa(IpAddr))}, + %%{clean_sess, CleanSess}, %%TODO:: fixme later + %%{protocol, ProtoVer}, + {connack, ConnAck}, + {ts, emqx_time:now_secs()}]) of + {ok, Payload} -> Msg = message(qos(Env), topic(connected, ClientId), Payload), emqx:publish(emqx_message:set_flag(sys, Msg)); - {'EXIT', Reason} -> - emqx_log:error("[Presence Module] json error: ~p", [Reason]) + {error, Reason} -> + emqx_logger:error("[Presence Module] Json error: ~p", [Reason]) end, {ok, Client}. on_client_disconnected(Reason, #client{client_id = ClientId, username = Username}, Env) -> - case catch emqx_json:encode([{clientid, ClientId}, - {username, Username}, - {reason, reason(Reason)}, - {ts, emqx_time:now_secs()}]) of - Payload when is_binary(Payload) -> + case emqx_json:safe_encode([{clientid, ClientId}, + {username, Username}, + {reason, reason(Reason)}, + {ts, emqx_time:now_secs()}]) of + {ok, Payload} -> Msg = message(qos(Env), topic(disconnected, ClientId), Payload), emqx:publish(emqx_message:set_flag(sys, Msg)); - {'EXIT', Reason} -> - emqx_log:error("[Presence Module] json error: ~p", [Reason]) + {error, Reason} -> + emqx_logger:error("[Presence Module] Json error: ~p", [Reason]) end, ok. unload(_Env) -> @@ -67,13 +67,13 @@ unload(_Env) -> emqx:unhook('client.disconnected', fun ?MODULE:on_client_disconnected/3). message(Qos, Topic, Payload) -> - Msg = emqx_message:make(presence, Topic, iolist_to_binary(Payload)), + Msg = emqx_message:make(?MODULE, Topic, iolist_to_binary(Payload)), emqx_message:set_header(qos, Qos, Msg). topic(connected, ClientId) -> - emqx_topic:systop(list_to_binary(["clients/", ClientId, "/connected"])); + emqx_topic:systop(iolist_to_binary(["clients/", ClientId, "/connected"])); topic(disconnected, ClientId) -> - emqx_topic:systop(list_to_binary(["clients/", ClientId, "/disconnected"])). + emqx_topic:systop(iolist_to_binary(["clients/", ClientId, "/disconnected"])). qos(Env) -> proplists:get_value(qos, Env, 0). diff --git a/src/emqx_mod_rewrite.erl b/src/emqx_mod_rewrite.erl index b9c664afd..671fe7311 100644 --- a/src/emqx_mod_rewrite.erl +++ b/src/emqx_mod_rewrite.erl @@ -35,11 +35,11 @@ load(Rules0) -> emqx:hook('message.publish', fun ?MODULE:rewrite_publish/2, [Rules]). rewrite_subscribe(_ClientId, _Username, TopicTable, Rules) -> - emqx_log:info("Rewrite subscribe: ~p", [TopicTable]), + emqx_logger:info("Rewrite subscribe: ~p", [TopicTable]), {ok, [{match_rule(Topic, Rules), Opts} || {Topic, Opts} <- TopicTable]}. rewrite_unsubscribe(_ClientId, _Username, TopicTable, Rules) -> - emqx_log:info("Rewrite unsubscribe: ~p", [TopicTable]), + emqx_logger:info("Rewrite unsubscribe: ~p", [TopicTable]), {ok, [{match_rule(Topic, Rules), Opts} || {Topic, Opts} <- TopicTable]}. rewrite_publish(Message = #message{topic = Topic}, Rules) -> diff --git a/src/emqx_mod_subscription.erl b/src/emqx_mod_subscription.erl index ed3331d51..c6d6dd554 100644 --- a/src/emqx_mod_subscription.erl +++ b/src/emqx_mod_subscription.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_mod_subscription). diff --git a/src/emqx_modules.erl b/src/emqx_modules.erl index 31dbd3c4a..cd5523280 100644 --- a/src/emqx_modules.erl +++ b/src/emqx_modules.erl @@ -1,23 +1,24 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_modules). -export([load/0, unload/0]). +-spec(load() -> ok). load() -> lists:foreach( fun({Mod, Env}) -> @@ -25,6 +26,7 @@ load() -> io:format("Load ~s module successfully.~n", [Mod]) end, emqx_config:get_env(modules, [])). +-spec(unload() -> ok). unload() -> lists:foreach( fun({Mod, Env}) -> diff --git a/src/emqx_mqtt_metrics.erl b/src/emqx_mqtt_metrics.erl deleted file mode 100644 index 175990f0b..000000000 --- a/src/emqx_mqtt_metrics.erl +++ /dev/null @@ -1,159 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_mqtt_metrics). - --include("emqx_mqtt.hrl"). - --import(emqx_metrics, [inc/1]). - --export([init/0]). - -%% Received/Sent Metrics --export([received/1, sent/1]). - -%% Bytes sent and received of Broker --define(SYSTOP_BYTES, [ - {counter, 'bytes/received'}, % Total bytes received - {counter, 'bytes/sent'} % Total bytes sent -]). - -%% Packets sent and received of Broker --define(SYSTOP_PACKETS, [ - {counter, 'packets/received'}, % All Packets received - {counter, 'packets/sent'}, % All Packets sent - {counter, 'packets/connect'}, % CONNECT Packets received - {counter, 'packets/connack'}, % CONNACK Packets sent - {counter, 'packets/publish/received'}, % PUBLISH packets received - {counter, 'packets/publish/sent'}, % PUBLISH packets sent - {counter, 'packets/puback/received'}, % PUBACK packets received - {counter, 'packets/puback/sent'}, % PUBACK packets sent - {counter, 'packets/puback/missed'}, % PUBACK packets missed - {counter, 'packets/pubrec/received'}, % PUBREC packets received - {counter, 'packets/pubrec/sent'}, % PUBREC packets sent - {counter, 'packets/pubrec/missed'}, % PUBREC packets missed - {counter, 'packets/pubrel/received'}, % PUBREL packets received - {counter, 'packets/pubrel/sent'}, % PUBREL packets sent - {counter, 'packets/pubrel/missed'}, % PUBREL packets missed - {counter, 'packets/pubcomp/received'}, % PUBCOMP packets received - {counter, 'packets/pubcomp/sent'}, % PUBCOMP packets sent - {counter, 'packets/pubcomp/missed'}, % PUBCOMP packets missed - {counter, 'packets/subscribe'}, % SUBSCRIBE Packets received - {counter, 'packets/suback'}, % SUBACK packets sent - {counter, 'packets/unsubscribe'}, % UNSUBSCRIBE Packets received - {counter, 'packets/unsuback'}, % UNSUBACK Packets sent - {counter, 'packets/pingreq'}, % PINGREQ packets received - {counter, 'packets/pingresp'}, % PINGRESP Packets sent - {counter, 'packets/disconnect'} % DISCONNECT Packets received -]). - -%% Messages sent and received of broker --define(SYSTOP_MESSAGES, [ - {counter, 'messages/received'}, % All Messages received - {counter, 'messages/sent'}, % All Messages sent - {counter, 'messages/qos0/received'}, % QoS0 Messages received - {counter, 'messages/qos0/sent'}, % QoS0 Messages sent - {counter, 'messages/qos1/received'}, % QoS1 Messages received - {counter, 'messages/qos1/sent'}, % QoS1 Messages sent - {counter, 'messages/qos2/received'}, % QoS2 Messages received - {counter, 'messages/qos2/sent'}, % QoS2 Messages sent - {counter, 'messages/qos2/dropped'}, % QoS2 Messages dropped - {gauge, 'messages/retained'}, % Messagea retained - {counter, 'messages/dropped'}, % Messages dropped - {counter, 'messages/forward'} % Messages forward -]). - -% Init metrics -init() -> - lists:foreach(fun emqx_metrics:create/1, - ?SYSTOP_BYTES ++ ?SYSTOP_PACKETS ++ ?SYSTOP_MESSAGES). - -%% @doc Count packets received. --spec(received(mqtt_packet()) -> ok). -received(Packet) -> - inc('packets/received'), - received1(Packet). -received1(?PUBLISH_PACKET(Qos, _PktId)) -> - inc('packets/publish/received'), - inc('messages/received'), - qos_received(Qos); -received1(?PACKET(Type)) -> - received2(Type). -received2(?CONNECT) -> - inc('packets/connect'); -received2(?PUBACK) -> - inc('packets/puback/received'); -received2(?PUBREC) -> - inc('packets/pubrec/received'); -received2(?PUBREL) -> - inc('packets/pubrel/received'); -received2(?PUBCOMP) -> - inc('packets/pubcomp/received'); -received2(?SUBSCRIBE) -> - inc('packets/subscribe'); -received2(?UNSUBSCRIBE) -> - inc('packets/unsubscribe'); -received2(?PINGREQ) -> - inc('packets/pingreq'); -received2(?DISCONNECT) -> - inc('packets/disconnect'); -received2(_) -> - ignore. -qos_received(?QOS_0) -> - inc('messages/qos0/received'); -qos_received(?QOS_1) -> - inc('messages/qos1/received'); -qos_received(?QOS_2) -> - inc('messages/qos2/received'). - -%% @doc Count packets received. Will not count $SYS PUBLISH. --spec(sent(mqtt_packet()) -> ignore | non_neg_integer()). -sent(?PUBLISH_PACKET(_Qos, <<"$SYS/", _/binary>>, _, _)) -> - ignore; -sent(Packet) -> - inc('packets/sent'), - sent1(Packet). -sent1(?PUBLISH_PACKET(Qos, _PktId)) -> - inc('packets/publish/sent'), - inc('messages/sent'), - qos_sent(Qos); -sent1(?PACKET(Type)) -> - sent2(Type). -sent2(?CONNACK) -> - inc('packets/connack'); -sent2(?PUBACK) -> - inc('packets/puback/sent'); -sent2(?PUBREC) -> - inc('packets/pubrec/sent'); -sent2(?PUBREL) -> - inc('packets/pubrel/sent'); -sent2(?PUBCOMP) -> - inc('packets/pubcomp/sent'); -sent2(?SUBACK) -> - inc('packets/suback'); -sent2(?UNSUBACK) -> - inc('packets/unsuback'); -sent2(?PINGRESP) -> - inc('packets/pingresp'); -sent2(_Type) -> - ignore. -qos_sent(?QOS_0) -> - inc('messages/qos0/sent'); -qos_sent(?QOS_1) -> - inc('messages/qos1/sent'); -qos_sent(?QOS_2) -> - inc('messages/qos2/sent'). - diff --git a/src/emqx_parser.erl b/src/emqx_parser.erl index f72e8ea4e..27faaacc2 100644 --- a/src/emqx_parser.erl +++ b/src/emqx_parser.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_parser). diff --git a/src/emqx_plugins.erl b/src/emqx_plugins.erl index 3d40d1849..837ed8a0e 100644 --- a/src/emqx_plugins.erl +++ b/src/emqx_plugins.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_plugins). @@ -83,7 +83,7 @@ load_expand_plugin(PluginDir) -> end, Modules), case filelib:wildcard(Ebin ++ "/*.app") of [App|_] -> application:load(list_to_atom(filename:basename(App, ".app"))); - _ -> emqx_log:error("App file cannot be found."), + _ -> emqx_logger:error("App file cannot be found."), {error, load_app_fail} end. @@ -128,7 +128,7 @@ with_loaded_file(File, SuccFun) -> {ok, Names} -> SuccFun(Names); {error, Error} -> - emqx_log:error("[Plugins] Failed to read: ~p, error: ~p", [File, Error]), + emqx_logger:error("[Plugins] Failed to read: ~p, error: ~p", [File, Error]), {error, Error} end. @@ -136,7 +136,7 @@ load_plugins(Names, Persistent) -> Plugins = list(), NotFound = Names -- names(Plugins), case NotFound of [] -> ok; - NotFound -> emqx_log:error("[Plugins] Cannot find plugins: ~p", [NotFound]) + NotFound -> emqx_logger:error("[Plugins] Cannot find plugins: ~p", [NotFound]) end, NeedToLoad = Names -- NotFound -- names(started_app), [load_plugin(find_plugin(Name, Plugins), Persistent) || Name <- NeedToLoad]. @@ -185,12 +185,12 @@ plugin(CfgFile) -> load(PluginName) when is_atom(PluginName) -> case lists:member(PluginName, names(started_app)) of true -> - emqx_log:error("[Plugins] Plugin ~s is already started", [PluginName]), + emqx_logger:error("[Plugins] Plugin ~s is already started", [PluginName]), {error, already_started}; false -> case find_plugin(PluginName) of false -> - emqx_log:error("[Plugins] Plugin ~s not found", [PluginName]), + emqx_logger:error("[Plugins] Plugin ~s not found", [PluginName]), {error, not_found}; Plugin -> load_plugin(Plugin, true) @@ -218,12 +218,12 @@ load_app(App) -> start_app(App, SuccFun) -> case application:ensure_all_started(App) of {ok, Started} -> - emqx_log:info("Started Apps: ~p", [Started]), - emqx_log:info("Load plugin ~s successfully", [App]), + emqx_logger:info("Started Apps: ~p", [Started]), + emqx_logger:info("Load plugin ~s successfully", [App]), SuccFun(App), {ok, Started}; {error, {ErrApp, Reason}} -> - emqx_log:error("Load plugin ~s error, cannot start app ~s for ~p", [App, ErrApp, Reason]), + emqx_logger:error("Load plugin ~s error, cannot start app ~s for ~p", [App, ErrApp, Reason]), {error, {ErrApp, Reason}} end. @@ -240,10 +240,10 @@ unload(PluginName) when is_atom(PluginName) -> {true, true} -> unload_plugin(PluginName, true); {false, _} -> - emqx_log:error("Plugin ~s is not started", [PluginName]), + emqx_logger:error("Plugin ~s is not started", [PluginName]), {error, not_started}; {true, false} -> - emqx_log:error("~s is not a plugin, cannot unload it", [PluginName]), + emqx_logger:error("~s is not a plugin, cannot unload it", [PluginName]), {error, not_found} end. @@ -258,11 +258,11 @@ unload_plugin(App, Persistent) -> stop_app(App) -> case application:stop(App) of ok -> - emqx_log:info("Stop plugin ~s successfully", [App]), ok; + emqx_logger:info("Stop plugin ~s successfully", [App]), ok; {error, {not_started, App}} -> - emqx_log:error("Plugin ~s is not started", [App]), ok; + emqx_logger:error("Plugin ~s is not started", [App]), ok; {error, Reason} -> - emqx_log:error("Stop plugin ~s error: ~p", [App]), {error, Reason} + emqx_logger:error("Stop plugin ~s error: ~p", [App]), {error, Reason} end. %%-------------------------------------------------------------------- @@ -294,7 +294,7 @@ plugin_loaded(Name, true) -> ignore end; {error, Error} -> - emqx_log:error("Cannot read loaded plugins: ~p", [Error]) + emqx_logger:error("Cannot read loaded plugins: ~p", [Error]) end. plugin_unloaded(_Name, false) -> @@ -306,10 +306,10 @@ plugin_unloaded(Name, true) -> true -> write_loaded(lists:delete(Name, Names)); false -> - emqx_log:error("Cannot find ~s in loaded_file", [Name]) + emqx_logger:error("Cannot find ~s in loaded_file", [Name]) end; {error, Error} -> - emqx_log:error("Cannot read loaded_plugins: ~p", [Error]) + emqx_logger:error("Cannot read loaded_plugins: ~p", [Error]) end. read_loaded() -> @@ -328,7 +328,7 @@ write_loaded(AppNames) -> file:write(Fd, iolist_to_binary(io_lib:format("~s.~n", [Name]))) end, AppNames); {error, Error} -> - emqx_log:error("Open File ~p Error: ~p", [File, Error]), + emqx_logger:error("Open File ~p Error: ~p", [File, Error]), {error, Error} end. diff --git a/src/emqx_pmon.erl b/src/emqx_pmon.erl index 7e76e0a18..bcb4ae537 100644 --- a/src/emqx_pmon.erl +++ b/src/emqx_pmon.erl @@ -1,22 +1,24 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_pmon). --export([new/0, monitor/2, monitor/3, demonitor/2, find/2, erase/2]). +-export([new/0]). + +-export([monitor/2, monitor/3, demonitor/2, find/2, erase/2]). -compile({no_auto_import,[monitor/3]}). @@ -43,7 +45,8 @@ monitor(Pid, Val, PM = {?MODULE, [M]}) -> demonitor(Pid, PM = {?MODULE, [M]}) -> case maps:find(Pid, M) of {ok, {Ref, _Val}} -> - erlang:demonitor(Ref, [flush]), + %% Don't flush + _ = erlang:demonitor(Ref), {?MODULE, [maps:remove(Pid, M)]}; error -> PM diff --git a/src/emqx_pool.erl b/src/emqx_pool.erl new file mode 100644 index 000000000..43f4ef845 --- /dev/null +++ b/src/emqx_pool.erl @@ -0,0 +1,106 @@ +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== + +-module(emqx_pool). + +-behaviour(gen_server). + +%% Start the pool supervisor +-export([start_link/0]). + +%% API Exports +-export([start_link/2, submit/1, async_submit/1]). + +%% gen_server Function Exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, {pool, id}). + +-define(POOL, ?MODULE). + +%% @doc Start Pooler Supervisor. +start_link() -> + emqx_pool_sup:start_link(?POOL, random, {?MODULE, start_link, []}). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +-spec(start_link(atom(), pos_integer()) + -> {ok, pid()} | ignore | {error, term()}). +start_link(Pool, Id) -> + gen_server:start_link({local, emqx_misc:proc_name(?MODULE, Id)}, + ?MODULE, [Pool, Id], []). + +%% @doc Submit work to the pool +-spec(submit(fun()) -> any()). +submit(Fun) -> + gen_server:call(worker(), {submit, Fun}, infinity). + +%% @doc Submit work to the pool asynchronously +-spec(async_submit(fun()) -> ok). +async_submit(Fun) -> + gen_server:cast(worker(), {async_submit, Fun}). + +worker() -> + gproc_pool:pick_worker(pool). + +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- + +init([Pool, Id]) -> + gproc_pool:connect_worker(Pool, {Pool, Id}), + {ok, #state{pool = Pool, id = Id}}. + +handle_call({submit, Fun}, _From, State) -> + {reply, catch run(Fun), State}; + +handle_call(Req, _From, State) -> + emqx_logger:error("[POOL] Unexpected request: ~p", [Req]), + {reply, ignore, State}. + +handle_cast({async_submit, Fun}, State) -> + try run(Fun) + catch _:Error -> + emqx_logger:error("[POOL] Error: ~p, ~p", [Error, erlang:get_stacktrace()]) + end, + {noreply, State}; + +handle_cast(Msg, State) -> + emqx_logger:error("[POOL] Unexpected msg: ~p", [Msg]), + {noreply, State}. + +handle_info(Info, State) -> + emqx_logger:error("[POOL] Unexpected info: ~p", [Info]), + {noreply, State}. + +terminate(_Reason, #state{pool = Pool, id = Id}) -> + gproc_pool:disconnect_worker(Pool, {Pool, Id}). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +run({M, F, A}) -> + erlang:apply(M, F, A); +run(Fun) when is_function(Fun) -> + Fun(). + diff --git a/src/emqx_pool_sup.erl b/src/emqx_pool_sup.erl index 789e00a75..29f656c73 100644 --- a/src/emqx_pool_sup.erl +++ b/src/emqx_pool_sup.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_pool_sup). @@ -39,12 +39,11 @@ start_link(Pool, Type, MFA) -> start_link(Pool, Type, Schedulers, MFA). -spec(start_link(atom() | tuple(), atom(), pos_integer(), mfa()) -> {ok, pid()} | {error, term()}). +start_link(Pool, Type, Size, MFA) when is_atom(Pool) -> + supervisor:start_link({local, Pool}, ?MODULE, [Pool, Type, Size, MFA]); start_link(Pool, Type, Size, MFA) -> supervisor:start_link(?MODULE, [Pool, Type, Size, MFA]). -%% sup_name(Pool) when is_atom(Pool) -> -%% list_to_atom(atom_to_list(Pool) ++ "_pool_sup"). - init([Pool, Type, Size, {M, F, Args}]) -> ensure_pool(Pool, Type, [{size, Size}]), {ok, {{one_for_one, 10, 3600}, [ diff --git a/src/emqx_pooler.erl b/src/emqx_pooler.erl deleted file mode 100644 index 9bce4e2b5..000000000 --- a/src/emqx_pooler.erl +++ /dev/null @@ -1,98 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_pooler). - --behaviour(gen_server). - -%% Start the pool supervisor --export([start_link/0]). - -%% API Exports --export([start_link/2, submit/1, async_submit/1]). - -%% gen_server Function Exports --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --record(state, {pool, id}). - -%% @doc Start Pooler Supervisor. -start_link() -> - emqx_pool_sup:start_link(pooler, random, {?MODULE, start_link, []}). - -%%-------------------------------------------------------------------- -%% API -%%-------------------------------------------------------------------- - --spec(start_link(atom(), pos_integer()) -> {ok, pid()} | ignore | {error, term()}). -start_link(Pool, Id) -> - gen_server:start_link({local, name(Id)}, ?MODULE, [Pool, Id], []). - -name(Id) -> list_to_atom(lists:concat([?MODULE, "_", Id])). - -%% @doc Submit work to pooler -submit(Fun) -> gen_server:call(worker(), {submit, Fun}, infinity). - -%% @doc Submit work to pooler asynchronously -async_submit(Fun) -> - gen_server:cast(worker(), {async_submit, Fun}). - -worker() -> - gproc_pool:pick_worker(pooler). - -%%-------------------------------------------------------------------- -%% gen_server callbacks -%%-------------------------------------------------------------------- - -init([Pool, Id]) -> - gproc_pool:connect_worker(Pool, {Pool, Id}), - {ok, #state{pool = Pool, id = Id}}. - -handle_call({submit, Fun}, _From, State) -> - {reply, catch run(Fun), State}; - -handle_call(_Req, _From, State) -> - {reply, ok, State}. - -handle_cast({async_submit, Fun}, State) -> - try run(Fun) - catch _:Error -> - emqx_log:error("Pooler Error: ~p, ~p", [Error, erlang:get_stacktrace()]) - end, - {noreply, State}; - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, #state{pool = Pool, id = Id}) -> - gproc_pool:disconnect_worker(Pool, {Pool, Id}). - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%-------------------------------------------------------------------- -%% Internal functions -%%-------------------------------------------------------------------- - -run({M, F, A}) -> - erlang:apply(M, F, A); -run(Fun) when is_function(Fun) -> - Fun(). - diff --git a/src/emqx_protocol.erl b/src/emqx_protocol.erl index a86c19459..f301378cf 100644 --- a/src/emqx_protocol.erl +++ b/src/emqx_protocol.erl @@ -57,8 +57,8 @@ -define(STATS_KEYS, [recv_pkt, recv_msg, send_pkt, send_msg]). -define(LOG(Level, Format, Args, State), - emqx_log:Level([{client, State#proto_state.client_id}], "Client(~s@~s): " ++ Format, - [State#proto_state.client_id, esockd_net:format(State#proto_state.peername) | Args])). + emqx_logger:Level([{client, State#proto_state.client_id}], "Client(~s@~s): " ++ Format, + [State#proto_state.client_id, esockd_net:format(State#proto_state.peername) | Args])). %% @doc Init protocol init(Peername, SendFun, Opts) -> @@ -220,7 +220,7 @@ process(?CONNECT_PACKET(Var), State0) -> {ok, Session} -> %% TODO:... SP = true, %% TODO:... %% TODO: Register the client - emqx_cm:reg(clientid(State2)), + emqx_cm:register_client(clientid(State2)), %%emqx_cm:reg(client(State2)), %% Start keepalive start_keepalive(KeepAlive, State2), @@ -362,7 +362,7 @@ send(Msg, State = #proto_state{client_id = ClientId, send(Packet = ?PACKET(Type), State = #proto_state{sendfun = SendFun, stats_data = Stats}) -> trace(send, Packet, State), - emqx_mqtt_metrics:sent(Packet), + emqx_metrics:sent(Packet), SendFun(Packet), {ok, State#proto_state{stats_data = inc_stats(send, Type, Stats)}}. @@ -398,15 +398,14 @@ stop_if_auth_failure(_RC, State) -> shutdown(_Error, #proto_state{client_id = undefined}) -> ignore; -shutdown(conflict, _State) -> - %% let it down +shutdown(conflict, _State = #proto_state{client_id = ClientId}) -> + emqx_cm:unregister_client(ClientId), ignore; -shutdown(mnesia_conflict, _State) -> - %% let it down - %% emqx_cm:unreg(ClientId); +shutdown(mnesia_conflict, _State = #proto_state{client_id = ClientId}) -> + emqx_cm:unregister_client(ClientId), ignore; - -shutdown(Error, State = #proto_state{will_msg = WillMsg}) -> +shutdown(Error, State = #proto_state{client_id = ClientId, + will_msg = WillMsg}) -> ?LOG(info, "Shutdown for ~p", [Error], State), Client = client(State), %% Auth failure not publish the will message @@ -415,11 +414,11 @@ shutdown(Error, State = #proto_state{will_msg = WillMsg}) -> false -> send_willmsg(Client, WillMsg) end, emqx_hooks:run('client.disconnected', [Error], Client), - %% let it down - %% emqx_cm:unreg(ClientId). + emqx_cm:unregister_client(ClientId), ok. -willmsg(Packet, State = #proto_state{mountpoint = MountPoint}) when is_record(Packet, mqtt_packet_connect) -> +willmsg(Packet, State = #proto_state{mountpoint = MountPoint}) + when is_record(Packet, mqtt_packet_connect) -> case emqx_packet:to_message(Packet) of undefined -> undefined; Msg -> mount(replvar(MountPoint, State), Msg) diff --git a/src/emqx_rate_limiter.erl b/src/emqx_rate_limiter.erl new file mode 100644 index 000000000..76eaf0c61 --- /dev/null +++ b/src/emqx_rate_limiter.erl @@ -0,0 +1,67 @@ +%%%------------------------------------------------------------------- +%%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%------------------------------------------------------------------- + +-module(emqx_rate_limiter). + +-behaviour(gen_server). + +%% API +-export([start_link/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). + +-record(state, {}). + +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc Starts the server +-spec(start_link() -> {ok, pid()} | ignore | {error, any()}). +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init([]) -> + {ok, #state{}}. + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + diff --git a/src/emqx_router.erl b/src/emqx_router.erl index 7d16d12d8..dffc91133 100644 --- a/src/emqx_router.erl +++ b/src/emqx_router.erl @@ -1,59 +1,53 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_router). -behaviour(gen_server). -include("emqx.hrl"). - -include_lib("ekka/include/ekka.hrl"). -%% Mnesia Bootstrap +%% Mnesia bootstrap -export([mnesia/1]). -boot_mnesia({mnesia, [boot]}). -copy_mnesia({mnesia, [copy]}). -%% Start -export([start_link/2]). +%% Route APIs +-export([add_route/2, add_route/3, get_routes/1, del_route/2, del_route/3]). +-export([has_routes/1, match_routes/1, print_routes/1]). + %% Topics -export([topics/0]). -%% Route management APIs --export([add_route/2, add_route/3, get_routes/1, del_route/2, del_route/3]). - --export([has_routes/1, match_routes/1, print_routes/1]). - %% gen_server Function Exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --type(group() :: binary()). - --type(destination() :: node() | {group(), node()} - | {cluster(), group(), node()}). +-type(destination() :: node() | {binary(), node()}). -record(state, {pool, id}). -define(ROUTE, emqx_route). %%-------------------------------------------------------------------- -%% Mnesia Bootstrap +%% Mnesia bootstrap %%-------------------------------------------------------------------- mnesia(boot) -> @@ -67,14 +61,17 @@ mnesia(copy) -> ok = ekka_mnesia:copy_table(?ROUTE). %%-------------------------------------------------------------------- -%% Start a router +%% Strat a Router %%-------------------------------------------------------------------- +-spec(start_link(atom(), pos_integer()) + -> {ok, pid()} | ignore | {error, term()}). start_link(Pool, Id) -> - gen_server:start_link(?MODULE, [Pool, Id], [{hibernate_after, 10000}]). + gen_server:start_link(emqx_misc:proc_name(?MODULE, Id), + ?MODULE, [Pool, Id], [{hibernate_after, 10000}]). %%-------------------------------------------------------------------- -%% Add/Del Routes +%% Route APIs %%-------------------------------------------------------------------- %% @doc Add a route @@ -105,40 +102,24 @@ del_route(From, Topic, Dest) when is_binary(Topic) -> has_routes(Topic) when is_binary(Topic) -> ets:member(?ROUTE, Topic). -%%-------------------------------------------------------------------- -%% Topics -%%-------------------------------------------------------------------- - --spec(topics() -> list(binary())). +%% @doc Get topics +-spec(topics() -> list(topic())). topics() -> mnesia:dirty_all_keys(?ROUTE). -%%-------------------------------------------------------------------- -%% Match routes -%%-------------------------------------------------------------------- - %% @doc Match routes %% Optimize: routing table will be replicated to all router nodes. --spec(match_routes(Topic:: topic()) -> [{topic(), binary() | node()}]). +-spec(match_routes(topic()) -> [route()]). match_routes(Topic) when is_binary(Topic) -> Matched = mnesia:ets(fun emqx_trie:match/1, [Topic]), - Routes = [ets:lookup(?ROUTE, To) || To <- [Topic | Matched]], - [{To, Dest} || #route{topic = To, dest = Dest} <- lists:append(Routes)]. - -%%-------------------------------------------------------------------- -%% Print routes -%%-------------------------------------------------------------------- + lists:append([get_routes(To) || To <- [Topic | Matched]]). %% @doc Print routes to a topic -spec(print_routes(topic()) -> ok). print_routes(Topic) -> - lists:foreach(fun({To, Dest}) -> + lists:foreach(fun(#route{topic = To, dest = Dest}) -> io:format("~s -> ~s~n", [To, Dest]) end, match_routes(Topic)). -%%-------------------------------------------------------------------- -%% Utility functions -%%-------------------------------------------------------------------- - cast(Router, Msg) -> gen_server:cast(Router, Msg). @@ -154,7 +135,7 @@ init([Pool, Id]) -> {ok, #state{pool = Pool, id = Id}}. handle_call(Req, _From, State) -> - emqx_log:error("[Router] Unexpected request: ~p", [Req]), + emqx_logger:error("[Router] Unexpected request: ~p", [Req]), {reply, ignore, State}. handle_cast({add_route, From, Route}, State) -> @@ -163,12 +144,12 @@ handle_cast({add_route, From, Route}, State) -> {noreply, State}; handle_cast({add_route, Route = #route{topic = Topic, dest = Dest}}, State) -> - case lists:member(Route, ets:lookup(?ROUTE, Topic)) of + case lists:member(Route, get_routes(Topic)) of true -> ok; false -> ok = emqx_router_helper:monitor(Dest), case emqx_topic:wildcard(Topic) of - true -> trans(fun add_trie_route/1, [Route]); + true -> log(trans(fun add_trie_route/1, [Route])); false -> add_direct_route(Route) end end, @@ -185,18 +166,18 @@ handle_cast({del_route, Route = #route{topic = Topic}}, State) -> true -> ok; false -> case emqx_topic:wildcard(Topic) of - true -> trans(fun del_trie_route/1, [Route]); + true -> log(trans(fun del_trie_route/1, [Route])); false -> del_direct_route(Route) end end, {noreply, State}; handle_cast(Msg, State) -> - emqx_log:error("[Router] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[Router] Unexpected msg: ~p", [Msg]), {noreply, State}. handle_info(Info, State) -> - emqx_log:error("[Router] Unexpected info: ~p", [Info]), + emqx_logger:error("[Router] Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #state{pool = Pool, id = Id}) -> @@ -237,8 +218,10 @@ del_trie_route(Route = #route{topic = Topic}) -> trans(Fun, Args) -> case mnesia:transaction(Fun, Args) of {atomic, _} -> ok; - {aborted, Error} -> - emqx_log:error("[Router] Mnesia aborted: ~p", [Error]), - {error, Error} + {aborted, Error} -> {error, Error} end. +log(ok) -> ok; +log({error, Error}) -> + emqx_logger:error("[Router] Mnesia aborted: ~p", [Error]). + diff --git a/src/emqx_router_helper.erl b/src/emqx_router_helper.erl index 5a4de5f69..f4a19aca9 100644 --- a/src/emqx_router_helper.erl +++ b/src/emqx_router_helper.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_router_helper). @@ -20,72 +20,73 @@ -include("emqx.hrl"). -%% Mnesia Bootstrap +%% Mnesia bootstrap -export([mnesia/1]). -boot_mnesia({mnesia, [boot]}). -copy_mnesia({mnesia, [copy]}). %% API --export([start_link/1, monitor/1]). +-export([start_link/0, monitor/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(routing_node, {name, ts}). +-record(routing_node, {name, const = unused}). --record(state, {nodes = [], stats_fun, stats_timer}). +-record(state, {nodes = []}). -compile({no_auto_import, [monitor/1]}). -define(SERVER, ?MODULE). --define(TAB, emqx_routing_node). +-define(ROUTE, emqx_route). +-define(ROUTING_NODE, emqx_routing_node). --define(LOCK, {?MODULE, clean_routes}). +-define(LOCK, {?MODULE, cleanup_routes}). %%-------------------------------------------------------------------- %% Mnesia bootstrap %%-------------------------------------------------------------------- mnesia(boot) -> - ok = ekka_mnesia:create_table(?TAB, [ + ok = ekka_mnesia:create_table(?ROUTING_NODE, [ {type, set}, {ram_copies, [node()]}, {record_name, routing_node}, {attributes, record_info(fields, routing_node)}]); mnesia(copy) -> - ok = ekka_mnesia:copy_table(?TAB). + ok = ekka_mnesia:copy_table(?ROUTING_NODE). %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- %% @doc Starts the router helper --spec(start_link(fun()) -> {ok, pid()} | ignore | {error, any()}). -start_link(StatsFun) -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [StatsFun], []). +-spec(start_link() -> {ok, pid()} | ignore | {error, any()}). +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). %% @doc Monitor routing node --spec(monitor(node()) -> ok). -monitor({_Group, Node}) -> +-spec(monitor(node() | {binary(), node()}) -> ok). +monitor({_Group, Node}) -> monitor(Node); monitor(Node) when is_atom(Node) -> - case ekka:is_member(Node) orelse ets:member(?TAB, Node) of + case ekka:is_member(Node) + orelse ets:member(?ROUTING_NODE, Node) of true -> ok; - false -> - mnesia:dirty_write(?TAB, #routing_node{name = Node, ts = os:timestamp()}) + false -> mnesia:dirty_write(?ROUTING_NODE, #routing_node{name = Node}) end. %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- -init([StatsFun]) -> +init([]) -> ekka:monitor(membership), - mnesia:subscribe({table, ?TAB, simple}), + mnesia:subscribe({table, ?ROUTING_NODE, simple}), Nodes = lists:foldl( fun(Node, Acc) -> case ekka:is_member(Node) of @@ -93,21 +94,21 @@ init([StatsFun]) -> false -> _ = erlang:monitor_node(Node, true), [Node | Acc] end - end, [], mnesia:dirty_all_keys(?TAB)), - {ok, TRef} = timer:send_interval(timer:seconds(1), stats), - {ok, #state{nodes = Nodes, stats_fun = StatsFun, stats_timer = TRef}}. + end, [], mnesia:dirty_all_keys(?ROUTING_NODE)), + emqx_stats:update_interval(route_stats, stats_fun()), + {ok, #state{nodes = Nodes}}. handle_call(Req, _From, State) -> - emqx_log:error("[RouterHelper] Unexpected request: ~p", [Req]), + emqx_logger:error("[RouterHelper] Unexpected request: ~p", [Req]), {reply, ignore, State}. handle_cast(Msg, State) -> - emqx_log:error("[RouterHelper] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[RouterHelper] Unexpected msg: ~p", [Msg]), {noreply, State}. handle_info({mnesia_table_event, {write, #routing_node{name = Node}, _}}, State = #state{nodes = Nodes}) -> - emqx_log:info("[RouterHelper] New routing node: ~s", [Node]), + emqx_logger:info("[RouterHelper] New routing node: ~s", [Node]), case ekka:is_member(Node) orelse lists:member(Node, Nodes) of true -> {noreply, State}; false -> _ = erlang:monitor_node(Node, true), @@ -122,8 +123,8 @@ handle_info({nodedown, Node}, State = #state{nodes = Nodes}) -> fun() -> mnesia:transaction(fun cleanup_routes/1, [Node]) end), - mnesia:dirty_delete(?TAB, Node), - handle_info(stats, State#state{nodes = lists:delete(Node, Nodes)}); + mnesia:dirty_delete(?ROUTING_NODE, Node), + {noreply, State#state{nodes = lists:delete(Node, Nodes)}}; handle_info({membership, {mnesia, down, Node}}, State) -> handle_info({nodedown, Node}, State); @@ -131,18 +132,14 @@ handle_info({membership, {mnesia, down, Node}}, State) -> handle_info({membership, _Event}, State) -> {noreply, State}; -handle_info(stats, State = #state{stats_fun = StatsFun}) -> - ok = StatsFun(mnesia:table_info(emqx_route, size)), - {noreply, State, hibernate}; - handle_info(Info, State) -> - emqx_log:error("[RouteHelper] Unexpected info: ~p", [Info]), + emqx_logger:error("[RouteHelper] Unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #state{stats_timer = TRef}) -> - timer:cancel(TRef), +terminate(_Reason, #state{}) -> ekka:unmonitor(membership), - mnesia:unsubscribe({table, ?TAB, simple}). + emqx_stats:cancel_update(route_stats), + mnesia:unsubscribe({table, ?ROUTING_NODE, simple}). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -151,9 +148,19 @@ code_change(_OldVsn, State, _Extra) -> %% Internal functions %%-------------------------------------------------------------------- +stats_fun() -> + fun() -> + case ets:info(?ROUTE, size) of + undefined -> ok; + Size -> + emqx_stats:setstat('routes/count', 'routes/max', Size), + emqx_stats:setstat('topics/count', 'topics/max', Size) + end + end. + cleanup_routes(Node) -> Patterns = [#route{_ = '_', dest = Node}, #route{_ = '_', dest = {'_', Node}}], - [mnesia:delete_object(?TAB, R, write) - || Pat <- Patterns, R <- mnesia:match_object(?TAB, Pat, write)]. + [mnesia:delete_object(?ROUTE, Route, write) + || Pat <- Patterns, Route <- mnesia:match_object(?ROUTE, Pat, write)]. diff --git a/src/emqx_router_sup.erl b/src/emqx_router_sup.erl index 98d4eb68d..bc8daf4b4 100644 --- a/src/emqx_router_sup.erl +++ b/src/emqx_router_sup.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_router_sup). @@ -26,16 +26,14 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - StatsFun = emqx_stats:statsfun('routes/count', 'routes/max'), - %% Router helper - Helper = {router_helper, {emqx_router_helper, start_link, [StatsFun]}, + Helper = {router_helper, {emqx_router_helper, start_link, []}, permanent, 5000, worker, [emqx_router_helper]}, %% Router pool - PoolSup = emqx_pool_sup:spec(router_pool, - [router, hash, emqx_sys:schedulers(), - {emqx_router, start_link, []}]), + RouterPool = emqx_pool_sup:spec(emqx_router_pool, + [router, hash, emqx_vm:schedulers(), + {emqx_router, start_link, []}]), - {ok, {{one_for_all, 0, 3600}, [Helper, PoolSup]}}. + {ok, {{one_for_all, 0, 1}, [Helper, RouterPool]}}. diff --git a/src/emqx_rpc.erl b/src/emqx_rpc.erl index b133b2292..f7c13fa4e 100644 --- a/src/emqx_rpc.erl +++ b/src/emqx_rpc.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_rpc). @@ -20,12 +20,14 @@ -export([multicall/4]). +-define(RPC, rpc). + call(Node, Mod, Fun, Args) -> - rpc:call(Node, Mod, Fun, Args). + ?RPC:call(Node, Mod, Fun, Args). multicall(Nodes, Mod, Fun, Args) -> - rpc:multicall(Nodes, Mod, Fun, Args). + ?RPC:multicall(Nodes, Mod, Fun, Args). cast(Node, Mod, Fun, Args) -> - rpc:cast(Node, Mod, Fun, Args). + ?RPC:cast(Node, Mod, Fun, Args). diff --git a/src/emqx_session.erl b/src/emqx_session.erl index f6f88de40..4d7ee4ce6 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -155,8 +155,8 @@ created_at]). -define(LOG(Level, Format, Args, State), - emqx_log:Level([{client, State#state.client_id}], - "Session(~s): " ++ Format, [State#state.client_id | Args])). + emqx_logger:Level([{client, State#state.client_id}], + "Session(~s): " ++ Format, [State#state.client_id | Args])). %% @doc Start a Session -spec(start_link(map()) -> {ok, pid()} | {error, term()}). @@ -296,7 +296,7 @@ init(#{clean_start := CleanStart, enable_stats = EnableStats, ignore_loop_deliver = IgnoreLoopDeliver, created_at = os:timestamp()}, - emqx_sm:register_session(#session{sid = ClientId, pid = self()}, info(State)), + emqx_sm:register_session(ClientId, info(State)), emqx_hooks:run('session.created', [ClientId, Username]), io:format("Session started: ~p~n", [self()]), {ok, emit_stats(State), hibernate}. @@ -342,7 +342,7 @@ handle_call(state, _From, State) -> reply(?record_to_proplist(state, State, ?STATE_KEYS), State); handle_call(Req, _From, State) -> - emqx_log:error("[Session] Unexpected request: ~p", [Req]), + emqx_logger:error("[Session] Unexpected request: ~p", [Req]), {reply, ignore, State}. handle_cast({subscribe, From, TopicTable, AckFun}, @@ -501,7 +501,7 @@ handle_cast({resume, ClientPid}, {noreply, emit_stats(dequeue(retry_delivery(true, State1)))}; handle_cast(Msg, State) -> - emqx_log:error("[Session] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[Session] Unexpected msg: ~p", [Msg]), {noreply, State}. %% Ignore Messages delivered by self @@ -551,13 +551,13 @@ handle_info({'EXIT', Pid, Reason}, State = #state{client_pid = ClientPid}) -> {noreply, State, hibernate}; handle_info(Info, State) -> - emqx_log:error("[Session] Unexpected info: ~p", [Info]), + emqx_logger:error("[Session] Unexpected info: ~p", [Info]), {noreply, State}. terminate(Reason, #state{client_id = ClientId, username = Username}) -> emqx_hooks:run('session.terminated', [ClientId, Username, Reason]), - emqx_sm:unregister_session(#session{sid = ClientId, pid = self()}). + emqx_sm:unregister_session(ClientId). code_change(_OldVsn, Session, _Extra) -> {ok, Session}. @@ -812,8 +812,7 @@ next_msg_id(State = #state{next_msg_id = Id}) -> emit_stats(State = #state{enable_stats = false}) -> State; emit_stats(State = #state{client_id = ClientId}) -> - Session = #session{sid = ClientId, pid = self()}, - emqx_sm_stats:set_session_stats(Session, stats(State)), + emqx_sm:set_session_stats(ClientId, stats(State)), State. inc_stats(Key) -> put(Key, get(Key) + 1). diff --git a/src/emqx_session_sup.erl b/src/emqx_session_sup.erl index 6a19e29ef..c1941207b 100644 --- a/src/emqx_session_sup.erl +++ b/src/emqx_session_sup.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_session_sup). @@ -24,13 +24,12 @@ -export([init/1]). --spec(start_link() -> {ok, pid()}). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). --spec(start_session(session()) -> {ok, pid()}). -start_session(Session) -> - supervisor:start_child(?MODULE, [Session]). +-spec(start_session(map()) -> {ok, pid()}). +start_session(Attrs) -> + supervisor:start_child(?MODULE, [Attrs]). %%-------------------------------------------------------------------- %% Supervisor callbacks diff --git a/src/emqx_shared_sub.erl b/src/emqx_shared_sub.erl index a5a3a5d9f..f4f0ecf8e 100644 --- a/src/emqx_shared_sub.erl +++ b/src/emqx_shared_sub.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_shared_sub). @@ -20,13 +20,16 @@ -include("emqx.hrl"). -%% API +%% Mnesia bootstrap +-export([mnesia/1]). + +-boot_mnesia({mnesia, [boot]}). +-copy_mnesia({mnesia, [copy]}). + -export([start_link/0]). -export([strategy/0]). - -export([subscribe/3, unsubscribe/3]). - -export([dispatch/3]). %% gen_server callbacks @@ -41,18 +44,26 @@ -record(shared_subscription, {group, topic, subpid}). +%%-------------------------------------------------------------------- +%% Mnesia bootstrap +%%-------------------------------------------------------------------- + +mnesia(boot) -> + ok = ekka_mnesia:create_table(?TAB, [ + {type, bag}, + {ram_copies, [node()]}, + {record_name, shared_subscription}, + {attributes, record_info(fields, shared_subscription)}]); + +mnesia(copy) -> + ok = ekka_mnesia:copy_table(?TAB). + %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- -spec(start_link() -> {ok, pid()} | ignore | {error, any()}). start_link() -> - ok = ekka_mnesia:create_table(?TAB, [ - {type, bag}, - {ram_copies, [node()]}, - {record_name, shared_subscription}, - {attributes, record_info(fields, shared_subscription)}]), - ok = ekka_mnesia:copy_table(?TAB), gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -spec(strategy() -> random | hash). @@ -62,24 +73,17 @@ strategy() -> subscribe(undefined, _Topic, _SubPid) -> ok; subscribe(Group, Topic, SubPid) when is_pid(SubPid) -> - mnesia:dirty_write(?TAB, r(Group, Topic, SubPid)), + mnesia:dirty_write(?TAB, record(Group, Topic, SubPid)), gen_server:cast(?SERVER, {monitor, SubPid}). unsubscribe(undefined, _Topic, _SubPid) -> ok; unsubscribe(Group, Topic, SubPid) when is_pid(SubPid) -> - mnesia:dirty_delete_object(?TAB, r(Group, Topic, SubPid)). + mnesia:dirty_delete_object(?TAB, record(Group, Topic, SubPid)). -r(Group, Topic, SubPid) -> +record(Group, Topic, SubPid) -> #shared_subscription{group = Group, topic = Topic, subpid = SubPid}. -dispatch({Cluster, Group}, Topic, Delivery) -> - case ekka:cluster_name() of - Cluster -> - dispatch(Group, Topic, Delivery); - _ -> Delivery - end; - %% TODO: ensure the delivery... dispatch(Group, Topic, Delivery = #delivery{message = Msg, flows = Flows}) -> case pick(subscribers(Group, Topic)) of @@ -107,7 +111,7 @@ subscribers(Group, Topic) -> init([]) -> {atomic, PMon} = mnesia:transaction(fun init_monitors/0), mnesia:subscribe({table, ?TAB, simple}), - {ok, #state{pmon = PMon}}. + {ok, update_stats(#state{pmon = PMon})}. init_monitors() -> mnesia:foldl( @@ -116,36 +120,34 @@ init_monitors() -> end, emqx_pmon:new(), ?TAB). handle_call(Req, _From, State) -> - emqx_log:error("[Shared] Unexpected request: ~p", [Req]), + emqx_logger:error("[Shared] Unexpected request: ~p", [Req]), {reply, ignore, State}. handle_cast({monitor, SubPid}, State= #state{pmon = PMon}) -> - {noreply, State#state{pmon = PMon:monitor(SubPid)}}; + {noreply, update_stats(State#state{pmon = PMon:monitor(SubPid)})}; handle_cast(Msg, State) -> - emqx_log:error("[Shared] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[Shared] Unexpected msg: ~p", [Msg]), {noreply, State}. handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = PMon}) -> - emqx_log:info("Shared subscription created: ~p", [NewRecord]), #shared_subscription{subpid = SubPid} = NewRecord, - {noreply, State#state{pmon = PMon:monitor(SubPid)}}; + {noreply, update_stats(State#state{pmon = PMon:monitor(SubPid)})}; handle_info({mnesia_table_event, {delete_object, OldRecord, _}}, State = #state{pmon = PMon}) -> - emqx_log:info("Shared subscription deleted: ~p", [OldRecord]), #shared_subscription{subpid = SubPid} = OldRecord, - {noreply, State#state{pmon = PMon:demonitor(SubPid)}}; + {noreply, update_stats(State#state{pmon = PMon:demonitor(SubPid)})}; handle_info({mnesia_table_event, _Event}, State) -> {noreply, State}; handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #state{pmon = PMon}) -> - emqx_log:info("Shared subscription down: ~p", [SubPid]), + emqx_logger:info("Shared subscription down: ~p", [SubPid]), mnesia:async_dirty(fun cleanup_down/1, [SubPid]), - {noreply, State#state{pmon = PMon:erase(SubPid)}}; + {noreply, update_stats(State#state{pmon = PMon:erase(SubPid)})}; handle_info(Info, State) -> - emqx_log:error("[Shared] Unexpected info: ~p", [Info]), + emqx_logger:error("[Shared] Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> @@ -164,3 +166,8 @@ cleanup_down(SubPid) -> mnesia:delete_object(?TAB, Record) end, mnesia:match_object(Pat)). +update_stats(State) -> + emqx_stats:setstat('subscriptions/shared/count', + 'subscriptions/shared/max', + ets:info(?TAB, size)), State. + diff --git a/src/emqx_sm.erl b/src/emqx_sm.erl index 85c4c4423..e2e5ca20c 100644 --- a/src/emqx_sm.erl +++ b/src/emqx_sm.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_sm). @@ -24,7 +24,8 @@ -export([open_session/1, lookup_session/1, close_session/1]). -export([resume_session/1, resume_session/2, discard_session/1, discard_session/2]). --export([register_session/2, unregister_session/1]). +-export([register_session/2, get_session_attrs/1, unregister_session/1]). +-export([get_session_stats/1, set_session_stats/2]). %% Internal functions for rpc -export([dispatch/3]). @@ -32,18 +33,22 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, {pmon}). +-record(state, {session_pmon}). -define(SM, ?MODULE). +%% ETS Tables +-define(SESSION, emqx_session). +-define(SESSION_P, emqx_persistent_session). +-define(SESSION_ATTRS, emqx_session_attrs). +-define(SESSION_STATS, emqx_session_stats). + -spec(start_link() -> {ok, pid()} | ignore | {error, term()}). start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - -%%-------------------------------------------------------------------- -%% Open Session -%%-------------------------------------------------------------------- + gen_server:start_link({local, ?SM}, ?MODULE, [], []). +%% @doc Open a session. +-spec(open_session(map()) -> {ok, pid()} | {error, term()}). open_session(Attrs = #{clean_start := true, client_id := ClientId, client_pid := ClientPid}) -> CleanStart = fun(_) -> @@ -66,81 +71,113 @@ open_session(Attrs = #{clean_start := false, end, emqx_sm_locker:trans(ClientId, ResumeStart). -%%-------------------------------------------------------------------- -%% Discard Session -%%-------------------------------------------------------------------- - +%% @doc Discard all the sessions identified by the ClientId. +-spec(discard_session(map()) -> ok). discard_session(ClientId) when is_binary(ClientId) -> discard_session(ClientId, self()). discard_session(ClientId, ClientPid) when is_binary(ClientId) -> lists:foreach( - fun(#session{pid = SessionPid}) -> + fun({_ClientId, SessionPid}) -> case catch emqx_session:discard(SessionPid, ClientPid) of {'EXIT', Error} -> - emqx_log:error("[SM] Failed to discard ~p: ~p", [SessionPid, Error]); + emqx_logger:error("[SM] Failed to discard ~p: ~p", [SessionPid, Error]); ok -> ok end end, lookup_session(ClientId)). -%%-------------------------------------------------------------------- -%% Resume Session -%%-------------------------------------------------------------------- - +%% @doc Try to resume a session. +-spec(resume_session(client_id()) -> {ok, pid()} | {error, term()}). resume_session(ClientId) -> resume_session(ClientId, self()). resume_session(ClientId, ClientPid) -> case lookup_session(ClientId) of [] -> {error, not_found}; - [#session{pid = SessionPid}] -> + [{_ClientId, SessionPid}] -> ok = emqx_session:resume(SessionPid, ClientPid), {ok, SessionPid}; Sessions -> - [#session{pid = SessionPid}|StaleSessions] = lists:reverse(Sessions), - emqx_log:error("[SM] More than one session found: ~p", [Sessions]), - lists:foreach(fun(#session{pid = Pid}) -> - catch emqx_session:discard(Pid, ClientPid) + [{_, SessionPid}|StaleSessions] = lists:reverse(Sessions), + emqx_logger:error("[SM] More than one session found: ~p", [Sessions]), + lists:foreach(fun({_, StalePid}) -> + catch emqx_session:discard(StalePid, ClientPid) end, StaleSessions), ok = emqx_session:resume(SessionPid, ClientPid), {ok, SessionPid} end. -%%-------------------------------------------------------------------- -%% Close a session -%%-------------------------------------------------------------------- - -close_session(#session{pid = SessionPid}) -> +%% @doc Close a session. +-spec(close_session({client_id(), pid()} | pid()) -> ok). +close_session({_ClientId, SessionPid}) -> + emqx_session:close(SessionPid); +close_session(SessionPid) when is_pid(SessionPid) -> emqx_session:close(SessionPid). -%%-------------------------------------------------------------------- -%% Create/Delete a session -%%-------------------------------------------------------------------- +%% @doc Register a session with attributes. +-spec(register_session(client_id() | {client_id(), pid()}, + list(emqx_session:attribute())) -> ok). +register_session(ClientId, Attrs) when is_binary(ClientId) -> + register_session({ClientId, self()}, Attrs); -register_session(Session, Attrs) when is_record(Session, session) -> - ets:insert(session, Session), - ets:insert(session_attrs, {Session, Attrs}), +register_session(Session = {ClientId, SessionPid}, Attrs) + when is_binary(ClientId), is_pid(SessionPid) -> + ets:insert(?SESSION, Session), + ets:insert(?SESSION_ATTRS, {Session, Attrs}), + case proplists:get_value(clean_start, Attrs, true) of + true -> ok; + false -> ets:insert(?SESSION_P, Session) + end, emqx_sm_registry:register_session(Session), - gen_server:cast(?MODULE, {registered, Session}). + notify({registered, ClientId, SessionPid}). -unregister_session(Session) when is_record(Session, session) -> +%% @doc Get session attrs +-spec(get_session_attrs({client_id(), pid()}) + -> list(emqx_session:attribute())). +get_session_attrs(Session = {ClientId, SessionPid}) + when is_binary(ClientId), is_pid(SessionPid) -> + safe_lookup_element(?SESSION_ATTRS, Session, []). + +%% @doc Unregister a session +-spec(unregister_session(client_id() | {client_id(), pid()}) -> ok). +unregister_session(ClientId) when is_binary(ClientId) -> + unregister_session({ClientId, self()}); + +unregister_session(Session = {ClientId, SessionPid}) + when is_binary(ClientId), is_pid(SessionPid) -> emqx_sm_registry:unregister_session(Session), - emqx_sm_stats:del_session_stats(Session), - ets:delete(session_attrs, Session), - ets:delete_object(session, Session), - gen_server:cast(?MODULE, {unregistered, Session}). + ets:delete(?SESSION_STATS, Session), + ets:delete(?SESSION_ATTRS, Session), + ets:delete_object(?SESSION_P, Session), + ets:delete_object(?SESSION, Session), + notify({unregistered, ClientId, SessionPid}). -%%-------------------------------------------------------------------- -%% Lookup a session from registry -%%-------------------------------------------------------------------- - +%% @doc Get session stats +-spec(get_session_stats({client_id(), pid()}) -> list(emqx_stats:stats())). +get_session_stats(Session = {ClientId, SessionPid}) + when is_binary(ClientId), is_pid(SessionPid) -> + safe_lookup_element(?SESSION_STATS, Session, []). + +%% @doc Set session stats +-spec(set_session_stats(client_id() | {client_id(), pid()}, + emqx_stats:stats()) -> ok). +set_session_stats(ClientId, Stats) when is_binary(ClientId) -> + set_session_stats({ClientId, self()}, Stats); + +set_session_stats(Session = {ClientId, SessionPid}, Stats) + when is_binary(ClientId), is_pid(SessionPid) -> + ets:insert(?SESSION_STATS, {Session, Stats}). + +%% @doc Lookup a session from registry +-spec(lookup_session(client_id()) -> list({client_id(), pid()})). lookup_session(ClientId) -> - emqx_sm_registry:lookup_session(ClientId). - -%%-------------------------------------------------------------------- -%% Dispatch by client Id -%%-------------------------------------------------------------------- + case emqx_sm_registry:is_enabled() of + true -> emqx_sm_registry:lookup_session(ClientId); + false -> ets:lookup(?SESSION, ClientId) + end. +%% @doc Dispatch a message to the session. +-spec(dispatch(client_id(), topic(), message()) -> any()). dispatch(ClientId, Topic, Msg) -> case lookup_session_pid(ClientId) of Pid when is_pid(Pid) -> @@ -149,60 +186,82 @@ dispatch(ClientId, Topic, Msg) -> emqx_hooks:run('message.dropped', [ClientId, Msg]) end. +%% @doc Lookup session pid. +-spec(lookup_session_pid(client_id()) -> pid() | undefined). lookup_session_pid(ClientId) -> - try ets:lookup_element(session, ClientId, #session.pid) - catch error:badarg -> - undefined + safe_lookup_element(?SESSION, ClientId, undefined). + +safe_lookup_element(Tab, Key, Default) -> + try ets:lookup_element(Tab, Key, 2) + catch + error:badarg -> Default end. +notify(Event) -> gen_server:cast(?SM, {notify, Event}). + %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- init([]) -> - _ = emqx_tables:create(session, [public, set, {keypos, 2}, - {read_concurrency, true}, - {write_concurrency, true}]), - _ = emqx_tables:create(session_attrs, [public, set, - {write_concurrency, true}]), - {ok, #state{pmon = emqx_pmon:new()}}. + TabOpts = [public, set, {write_concurrency, true}], + _ = emqx_tables:new(?SESSION, [{read_concurrency, true} | TabOpts]), + _ = emqx_tables:new(?SESSION_P, TabOpts), + _ = emqx_tables:new(?SESSION_ATTRS, TabOpts), + _ = emqx_tables:new(?SESSION_STATS, TabOpts), + emqx_stats:update_interval(sm_stats, stats_fun()), + {ok, #state{session_pmon = emqx_pmon:new()}}. handle_call(Req, _From, State) -> - emqx_log:error("[SM] Unexpected request: ~p", [Req]), + emqx_logger:error("[SM] Unexpected request: ~p", [Req]), {reply, ignore, State}. -handle_cast({registered, #session{sid = ClientId, pid = SessionPid}}, - State = #state{pmon = PMon}) -> - {noreply, State#state{pmon = PMon:monitor(SessionPid, ClientId)}}; +handle_cast({notify, {registered, ClientId, SessionPid}}, + State = #state{session_pmon = PMon}) -> + {noreply, State#state{session_pmon = PMon:monitor(SessionPid, ClientId)}}; -handle_cast({unregistered, #session{sid = _ClientId, pid = SessionPid}}, - State = #state{pmon = PMon}) -> - {noreply, State#state{pmon = PMon:erase(SessionPid)}}; +handle_cast({notify, {unregistered, _ClientId, SessionPid}}, + State = #state{session_pmon = PMon}) -> + {noreply, State#state{session_pmon = PMon:demonitor(SessionPid)}}; handle_cast(Msg, State) -> - emqx_log:error("[SM] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[SM] Unexpected msg: ~p", [Msg]), {noreply, State}. handle_info({'DOWN', _MRef, process, DownPid, _Reason}, - State = #state{pmon = PMon}) -> + State = #state{session_pmon = PMon}) -> case PMon:find(DownPid) of - {ok, ClientId} -> - case ets:lookup(session, ClientId) of - [] -> ok; - _ -> unregister_session(#session{sid = ClientId, pid = DownPid}) - end, - {noreply, State}; undefined -> - {noreply, State} + {noreply, State}; + ClientId -> + unregister_session({ClientId, DownPid}), + {noreply, State#state{session_pmon = PMon:erase(DownPid)}} end; handle_info(Info, State) -> - emqx_log:error("[SM] Unexpected info: ~p", [Info]), + emqx_logger:error("[SM] Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> - ok. + emqx_stats:cancel_update(cm_stats). code_change(_OldVsn, State, _Extra) -> {ok, State}. +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +stats_fun() -> + fun () -> + safe_update_stats(?SESSION, 'sessions/count', 'sessions/max'), + safe_update_stats(?SESSION_P, 'sessions/persistent/count', + 'sessions/persistent/max') + end. + +safe_update_stats(Tab, Stat, MaxStat) -> + case ets:info(Tab, size) of + undefined -> ok; + Size -> emqx_stats:setstat(Stat, MaxStat, Size) + end. + diff --git a/src/emqx_sm_locker.erl b/src/emqx_sm_locker.erl index 07770061b..50b1fe462 100644 --- a/src/emqx_sm_locker.erl +++ b/src/emqx_sm_locker.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_sm_locker). @@ -32,8 +32,11 @@ start_link() -> trans(ClientId, Fun) -> trans(ClientId, Fun, undefined). --spec(trans(client_id(), fun(([node()]) -> any()), +-spec(trans(client_id() | undefined, + fun(([node()]) -> any()), ekka_locker:piggyback()) -> any()). +trans(undefined, Fun, _Piggyback) -> + Fun([]); trans(ClientId, Fun, Piggyback) -> case lock(ClientId, Piggyback) of {true, Nodes} -> diff --git a/src/emqx_sm_registry.erl b/src/emqx_sm_registry.erl index ad3597bcc..085403d79 100644 --- a/src/emqx_sm_registry.erl +++ b/src/emqx_sm_registry.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_sm_registry). @@ -23,58 +23,73 @@ %% API -export([start_link/0]). +-export([is_enabled/0]). + -export([register_session/1, lookup_session/1, unregister_session/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --define(SERVER, ?MODULE). +-define(REGISTRY, ?MODULE). --define(TAB, session_registry). +-define(TAB, emqx_session_registry). -define(LOCK, {?MODULE, cleanup_sessions}). +-record(global_session, {sid, pid}). + -record(state, {}). +-type(session_pid() :: pid()). + +%% @doc Start the session manager. +-spec(start_link() -> {ok, pid()} | ignore | {error, term()}). +start_link() -> + gen_server:start_link({local, ?REGISTRY}, ?MODULE, [], []). + +-spec(is_enabled() -> boolean()). +is_enabled() -> + ets:info(?TAB, name) =/= undefined. + +-spec(lookup_session(client_id()) -> list({client_id(), session_pid()})). +lookup_session(ClientId) -> + [{ClientId, SessionPid} || #global_session{pid = SessionPid} + <- mnesia:dirty_read(?TAB, ClientId)]. + +-spec(register_session({client_id(), session_pid()}) -> ok). +register_session({ClientId, SessionPid}) when is_binary(ClientId), + is_pid(SessionPid) -> + mnesia:dirty_write(?TAB, record(ClientId, SessionPid)). + +-spec(unregister_session({client_id(), session_pid()}) -> ok). +unregister_session({ClientId, SessionPid}) when is_binary(ClientId), + is_pid(SessionPid) -> + mnesia:dirty_delete_object(?TAB, record(ClientId, SessionPid)). + +record(ClientId, SessionPid) -> + #global_session{sid = ClientId, pid = SessionPid}. + %%-------------------------------------------------------------------- -%% API +%% gen_server callbacks %%-------------------------------------------------------------------- -start_link() -> +init([]) -> ok = ekka_mnesia:create_table(?TAB, [ {type, bag}, {ram_copies, [node()]}, - {record_name, session}, - {attributes, record_info(fields, session)}]), + {record_name, global_session}, + {attributes, record_info(fields, global_session)}]), ok = ekka_mnesia:copy_table(?TAB), - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). - --spec(lookup_session(client_id()) -> list(session())). -lookup_session(ClientId) -> - mnesia:dirty_read(?TAB, ClientId). - --spec(register_session(session()) -> ok). -register_session(Session) when is_record(Session, session) -> - mnesia:dirty_write(?TAB, Session). - --spec(unregister_session(session()) -> ok). -unregister_session(Session) when is_record(Session, session) -> - mnesia:dirty_delete_object(?TAB, Session). - -%%%=================================================================== -%%% gen_server callbacks -%%%=================================================================== - -init([]) -> ekka:monitor(membership), {ok, #state{}}. -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. +handle_call(Req, _From, State) -> + emqx_logger:error("[Registry] Unexpected request: ~p", [Req]), + {reply, ignore, State}. -handle_cast(_Msg, State) -> +handle_cast(Msg, State) -> + emqx_logger:error("[Registry] Unexpected msg: ~p", [Msg]), {noreply, State}. handle_info({membership, {mnesia, down, Node}}, State) -> @@ -87,7 +102,8 @@ handle_info({membership, {mnesia, down, Node}}, State) -> handle_info({membership, _Event}, State) -> {noreply, State}; -handle_info(_Info, State) -> +handle_info(Info, State) -> + emqx_logger:error("[Registry] Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> @@ -101,7 +117,7 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- cleanup_sessions(Node) -> - Pat = [{#session{pid = '$1', _ = '_'}, + Pat = [{#global_session{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}], lists:foreach(fun(Session) -> mnesia:delete_object(?TAB, Session) diff --git a/src/emqx_sm_stats.erl b/src/emqx_sm_stats.erl deleted file mode 100644 index dab8d4a5d..000000000 --- a/src/emqx_sm_stats.erl +++ /dev/null @@ -1,72 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_sm_stats). - --behaviour(gen_statem). - --include("emqx.hrl"). - -%% API --export([start_link/0]). - --export([set_session_stats/2, get_session_stats/1, del_session_stats/1]). - -%% gen_statem callbacks --export([init/1, callback_mode/0, handle_event/4, terminate/3, code_change/4]). - --define(TAB, session_stats). - --record(state, {statsfun}). - -start_link() -> - gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []). - --spec(set_session_stats(session(), emqx_stats:stats()) -> true). -set_session_stats(Session, Stats) when is_record(Session, session) -> - ets:insert(?TAB, {Session, [{'$ts', emqx_time:now_secs()}|Stats]}). - --spec(get_session_stats(session()) -> emqx_stats:stats()). -get_session_stats(Session) -> - case ets:lookup(?TAB, Session) of - [{_, Stats}] -> Stats; - [] -> [] - end. - --spec(del_session_stats(session()) -> true). -del_session_stats(Session) -> - ets:delete(?TAB, Session). - -init([]) -> - _ = emqx_tables:create(?TAB, [public, {write_concurrency, true}]), - StatsFun = emqx_stats:statsfun('sessions/count', 'sessions/max'), - {ok, idle, #state{statsfun = StatsFun}, timer:seconds(1)}. - -callback_mode() -> handle_event_function. - -handle_event(timeout, _Timeout, idle, State = #state{statsfun = StatsFun}) -> - case ets:info(session, size) of - undefined -> ok; - Size -> StatsFun(Size) - end, - {next_state, idle, State, timer:seconds(1)}. - -terminate(_Reason, _StateName, _State) -> - ok. - -code_change(_OldVsn, StateName, State, _Extra) -> - {ok, StateName, State}. - diff --git a/src/emqx_sm_sup.erl b/src/emqx_sm_sup.erl index 9f2b44fa3..16e1b8715 100644 --- a/src/emqx_sm_sup.erl +++ b/src/emqx_sm_sup.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_sm_sup). @@ -26,12 +26,17 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - Childs = [child(M) || M <- [emqx_sm_locker, - emqx_sm_registry, - emqx_sm_stats, - emqx_sm]], - {ok, {{one_for_all, 10, 3600}, Childs}}. + %% Session Locker + Locker = {locker, {emqx_sm_locker, start_link, []}, + permanent, 5000, worker, [emqx_sm_locker]}, -child(M) -> - {M, {M, start_link, []}, permanent, 5000, worker, [M]}. + %% Session Registry + Registry = {registry, {emqx_sm_registry, start_link, []}, + permanent, 5000, worker, [emqx_sm_registry]}, + + %% Session Manager + Manager = {manager, {emqx_sm, start_link, []}, + permanent, 5000, worker, [emqx_sm]}, + + {ok, {{one_for_rest, 10, 3600}, [Locker, Registry, Manager]}}. diff --git a/src/emqx_stats.erl b/src/emqx_stats.erl index 4aad2fb3d..0bcf2e5b8 100644 --- a/src/emqx_stats.erl +++ b/src/emqx_stats.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_stats). @@ -20,69 +20,78 @@ -include("emqx.hrl"). --export([start_link/0, stop/0]). +-export([start_link/0]). -%% Get all Stats +%% Get all stats -export([all/0]). -%% Statistics API. +%% Stats API. -export([statsfun/1, statsfun/2, getstats/0, getstat/1, setstat/2, setstat/3]). +-export([update_interval/2, update_interval/3, cancel_update/1]). + %% gen_server Function Exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, {tick}). +-record(update, {name, countdown, interval, func}). + +-record(state, {timer, updates :: #update{}}). -type(stats() :: list({atom(), non_neg_integer()})). -export_type([stats/0]). --define(STATS_TAB, stats). - -%% $SYS Topics for Clients --define(SYSTOP_CLIENTS, [ +%% Client stats +-define(CLIENT_STATS, [ 'clients/count', % clients connected current - 'clients/max' % max clients connected + 'clients/max' % maximum clients connected ]). -%% $SYS Topics for Sessions --define(SYSTOP_SESSIONS, [ +%% Session stats +-define(SESSION_STATS, [ 'sessions/count', - 'sessions/max' + 'sessions/max', + 'sessions/persistent/count', + 'sessions/persistent/max' ]). -%% $SYS Topics for Subscribers --define(SYSTOP_PUBSUB, [ - 'topics/count', % ... - 'topics/max', % ... - 'subscribers/count', % ... - 'subscribers/max', % ... - 'subscriptions/count', % ... - 'subscriptions/max', % ... - 'routes/count', % ... - 'routes/max' % ... +%% Subscribers, Subscriptions stats +-define(PUBSUB_STATS, [ + 'topics/count', + 'topics/max', + 'subscribers/count', + 'subscribers/max', + 'subscriptions/count', + 'subscriptions/max' ]). -%% $SYS Topic for retained --define(SYSTOP_RETAINED, [ +-define(ROUTE_STATS, [ + 'routes/count', + 'routes/max' +]). + +%% Retained stats +-define(RETAINED_STATS, [ 'retained/count', 'retained/max' ]). +-define(TAB, ?MODULE). +-define(SERVER, ?MODULE). + +%% @doc Start stats server +-spec(start_link() -> {ok, pid()} | ignore | {error, term()}). +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- -%% @doc Start stats server --spec(start_link() -> {ok, pid()} | ignore | {error, term()}). -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - -stop() -> - gen_server:call(?MODULE, stop). - -all() -> ets:tab2list(?STATS_TAB). +%% Get all stats. +-spec(all() -> stats()). +all() -> getstats(). %% @doc Generate stats fun -spec(statsfun(Stat :: atom()) -> fun()). @@ -94,71 +103,117 @@ statsfun(Stat, MaxStat) -> fun(Val) -> setstat(Stat, MaxStat, Val) end. %% @doc Get all statistics --spec(getstats() -> [{atom(), non_neg_integer()}]). +-spec(getstats() -> stats()). getstats() -> - lists:sort(ets:tab2list(?STATS_TAB)). + case ets:info(?TAB, name) of + undefined -> []; + _ -> ets:tab2list(?TAB) + end. %% @doc Get stats by name -spec(getstat(atom()) -> non_neg_integer() | undefined). getstat(Name) -> - case ets:lookup(?STATS_TAB, Name) of + case ets:lookup(?TAB, Name) of [{Name, Val}] -> Val; [] -> undefined end. -%% @doc Set broker stats +%% @doc Set stats -spec(setstat(Stat :: atom(), Val :: pos_integer()) -> boolean()). -setstat(Stat, Val) -> - ets:update_element(?STATS_TAB, Stat, {2, Val}). +setstat(Stat, Val) when is_integer(Val) -> + safe_update_element(Stat, Val). -%% @doc Set stats with max --spec(setstat(Stat :: atom(), MaxStat :: atom(), Val :: pos_integer()) -> boolean()). -setstat(Stat, MaxStat, Val) -> - gen_server:cast(?MODULE, {setstat, Stat, MaxStat, Val}). +%% @doc Set stats with max value. +-spec(setstat(Stat :: atom(), MaxStat :: atom(), + Val :: pos_integer()) -> boolean()). +setstat(Stat, MaxStat, Val) when is_integer(Val) -> + cast({setstat, Stat, MaxStat, Val}). + +-spec(update_interval(atom(), fun()) -> ok). +update_interval(Name, UpFun) -> + update_interval(Name, 1, UpFun). + +-spec(update_interval(atom(), pos_integer(), fun()) -> ok). +update_interval(Name, Secs, UpFun) when is_integer(Secs), Secs >= 1 -> + cast({update_interval, rec(Name, Secs, UpFun)}). + +-spec(cancel_update(atom()) -> ok). +cancel_update(Name) -> + cast({cancel_update, Name}). + +rec(Name, Secs, UpFun) -> + #update{name = Name, countdown = Secs, interval = Secs, func = UpFun}. + +cast(Msg) -> + gen_server:cast(?SERVER, Msg). %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- init([]) -> - emqx_time:seed(), - _ = emqx_tables:create(?STATS_TAB, [set, public, named_table, - {write_concurrency, true}]), - Topics = ?SYSTOP_CLIENTS ++ ?SYSTOP_SESSIONS ++ ?SYSTOP_PUBSUB ++ ?SYSTOP_RETAINED, - ets:insert(?STATS_TAB, [{Topic, 0} || Topic <- Topics]), - % Tick to publish stats - {ok, TRef} = timer:send_after(emqx_sys:sys_interval(), tick), - {ok, #state{tick = TRef}, hibernate}. + _ = emqx_tables:new(?TAB, [set, public, {write_concurrency, true}]), + Stats = lists:append([?CLIENT_STATS, ?SESSION_STATS, ?PUBSUB_STATS, + ?ROUTE_STATS, ?RETAINED_STATS]), + ets:insert(?TAB, [{Name, 0} || Name <- Stats]), + {ok, start_timer(#state{updates = []}), hibernate}. -handle_call(stop, _From, State) -> - {stop, normal, ok, State}; +start_timer(State) -> + State#state{timer = emqx_misc:start_timer(timer:seconds(1), tick)}. -handle_call(_Request, _From, State) -> - {reply, error, State}. +handle_call(Req, _From, State) -> + emqx_logger:error("[STATS] Unexpected request: ~p", [Req]), + {reply, ignore, State}. -%% atomic handle_cast({setstat, Stat, MaxStat, Val}, State) -> - MaxVal = ets:lookup_element(?STATS_TAB, MaxStat, 2), - if - Val > MaxVal -> - ets:update_element(?STATS_TAB, MaxStat, {2, Val}); - true -> ok + try ets:lookup_element(?TAB, MaxStat, 2) of + MaxVal when Val > MaxVal -> + ets:update_element(?TAB, MaxStat, {2, Val}); + _ -> ok + catch + error:badarg -> + ets:insert(?TAB, {MaxStat, Val}) end, - ets:update_element(?STATS_TAB, Stat, {2, Val}), + safe_update_element(Stat, Val), {noreply, State}; -handle_cast(_Msg, State) -> +handle_cast({update_interval, Update = #update{name = Name}}, + State = #state{updates = Updates}) -> + case lists:keyfind(Name, #update.name, Updates) of + #update{} -> + emqx_logger:error("[STATS]: Duplicated update: ~s", [Name]), + {noreply, State}; + false -> + {noreply, State#state{updates = [Update | Updates]}} + end; + +handle_cast({cancel_update, Name}, State = #state{updates = Updates}) -> + {noreply, State#state{updates = lists:keydelete(Name, #update.name, Updates)}}; + +handle_cast(Msg, State) -> + emqx_logger:error("[STATS] Unexpected msg: ~p", [Msg]), {noreply, State}. -%% Interval Tick. -handle_info(tick, State) -> - [publish(Stat, Val) || {Stat, Val} <- ets:tab2list(?STATS_TAB)], - {noreply, State, hibernate}; +handle_info({timeout, TRef, tick}, State = #state{timer = TRef, + updates = Updates}) -> + lists:foldl( + fun(Update = #update{name = Name, countdown = C, interval = I, + func = UpFun}, Acc) when C =< 0 -> + try UpFun() + catch _:Error -> + emqx_logger:error("[STATS] Update ~s error: ~p", [Name, Error]) + end, + [Update#update{countdown = I} | Acc]; + (Update = #update{countdown = C}, Acc) -> + [Update#update{countdown = C - 1} | Acc] + end, [], Updates), + {noreply, start_timer(State), hibernate}; -handle_info(_Info, State) -> +handle_info(Info, State) -> + emqx_logger:error("[STATS] Unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #state{tick = TRef}) -> +terminate(_Reason, #state{timer = TRef}) -> timer:cancel(TRef). code_change(_OldVsn, State, _Extra) -> @@ -168,12 +223,10 @@ code_change(_OldVsn, State, _Extra) -> %% Internal functions %%-------------------------------------------------------------------- -publish(Stat, Val) -> - Msg = emqx_message:make(stats, stats_topic(Stat), bin(Val)), - emqx:publish(emqx_message:set_flag(sys, Msg)). - -stats_topic(Stat) -> - emqx_topic:systop(list_to_binary(lists:concat(['stats/', Stat]))). - -bin(I) when is_integer(I) -> list_to_binary(integer_to_list(I)). +safe_update_element(Key, Val) -> + try ets:update_element(?TAB, Key, {2, Val}) + catch + error:badarg -> + ets:insert_new(?TAB, {Key, Val}) + end. diff --git a/src/emqx_sup.erl b/src/emqx_sup.erl index ce2c438f0..61d48042e 100644 --- a/src/emqx_sup.erl +++ b/src/emqx_sup.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_sup). @@ -20,7 +20,6 @@ -export([start_link/0, start_child/1, start_child/2, stop_child/1]). -%% Supervisor callbacks -export([init/1]). -type(startchild_ret() :: {ok, supervisor:child()} @@ -29,20 +28,24 @@ -define(SUPERVISOR, ?MODULE). --define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). - start_link() -> supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + -spec(start_child(supervisor:child_spec()) -> startchild_ret()). start_child(ChildSpec) when is_tuple(ChildSpec) -> supervisor:start_child(?SUPERVISOR, ChildSpec). --spec(start_child(atom(), worker | supervisor) -> startchild_ret()). -start_child(Mod, Type) when Type == worker orelse Type == supervisor -> - start_child(?CHILD(Mod, Type)). +-spec(start_child(module(), worker | supervisor) -> startchild_ret()). +start_child(Mod, worker) -> + start_child(worker_spec(Mod)); +start_child(Mod, supervisor) -> + start_child(supervisor_spec(Mod)). --spec(stop_child(supervisor:child_id()) -> ok | {error, any()}). +-spec(stop_child(supervisor:child_id()) -> ok | {error, term()}). stop_child(ChildId) -> case supervisor:terminate_child(?SUPERVISOR, ChildId) of ok -> supervisor:delete_child(?SUPERVISOR, ChildId); @@ -54,24 +57,44 @@ stop_child(ChildId) -> %%-------------------------------------------------------------------- init([]) -> - {ok, {{one_for_all, 10, 3600}, - [?CHILD(emqx_ctl, worker), - ?CHILD(emqx_hooks, worker), - ?CHILD(emqx_stats, worker), - ?CHILD(emqx_metrics, worker), - ?CHILD(emqx_sys, worker), - ?CHILD(emqx_router_sup, supervisor), - ?CHILD(emqx_broker_sup, supervisor), - ?CHILD(emqx_pooler, supervisor), - ?CHILD(emqx_tracer_sup, supervisor), - ?CHILD(emqx_cm_sup, supervisor), - ?CHILD(emqx_sm_sup, supervisor), - ?CHILD(emqx_session_sup, supervisor), - ?CHILD(emqx_ws_connection_sup, supervisor), - ?CHILD(emqx_alarm, worker), - ?CHILD(emqx_mod_sup, supervisor), - ?CHILD(emqx_bridge_sup_sup, supervisor), - ?CHILD(emqx_access_control, worker), - ?CHILD(emqx_sysmon_sup, supervisor)] - }}. + %% Kernel Sup + KernelSup = supervisor_spec(emqx_kernel_sup), + %% Router Sup + RouterSup = supervisor_spec(emqx_router_sup), + %% Broker Sup + BrokerSup = supervisor_spec(emqx_broker_sup), + %% BridgeSup + BridgeSup = supervisor_spec(emqx_bridge_sup_sup), + %% AccessControl + AccessControl = worker_spec(emqx_access_control), + %% Session Manager + SMSup = supervisor_spec(emqx_sm_sup), + %% Session Sup + SessionSup = supervisor_spec(emqx_session_sup), + %% Connection Manager + CMSup = supervisor_spec(emqx_cm_sup), + %% WebSocket Connection Sup + WSConnSup = supervisor_spec(emqx_ws_connection_sup), + %% Sys Sup + SysSup = supervisor_spec(emqx_sys_sup), + {ok, {{one_for_all, 0, 1}, + [KernelSup, + RouterSup, + BrokerSup, + BridgeSup, + AccessControl, + SMSup, + SessionSup, + CMSup, + WSConnSup, + SysSup]}}. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +worker_spec(M) -> + {M, {M, start_link, []}, permanent, 30000, worker, [M]}. +supervisor_spec(M) -> + {M, {M, start_link, []}, permanent, infinity, supervisor, [M]}. diff --git a/src/emqx_sys.erl b/src/emqx_sys.erl index b18cc195b..65f361b95 100644 --- a/src/emqx_sys.erl +++ b/src/emqx_sys.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_sys). @@ -22,8 +22,6 @@ -export([start_link/0]). --export([schedulers/0]). - -export([version/0, uptime/0, datetime/0, sysdescr/0, sys_interval/0]). -export([info/0]). @@ -31,33 +29,29 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, {started_at, heartbeat, sys_ticker, version, sysdescr}). +-import(emqx_topic, [systop/1]). +-import(emqx_misc, [start_timer/2]). + +-record(state, {start_time, heartbeat, ticker, version, sysdescr}). -define(APP, emqx). +-define(SYS, ?MODULE). --define(SERVER, ?MODULE). - -%% $SYS Topics of Broker --define(SYSTOP_BROKERS, [ +-define(INFO_KEYS, [ version, % Broker version uptime, % Broker uptime datetime, % Broker local datetime sysdescr % Broker description ]). +-spec(start_link() -> {ok, pid()} | ignore | {error, any()}). +start_link() -> + gen_server:start_link({local, ?SYS}, ?MODULE, [], []). + %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- --spec(start_link() -> {ok, pid()} | ignore | {error, any()}). -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). - -%% @doc Get schedulers --spec(schedulers() -> pos_integer()). -schedulers() -> - erlang:system_info(schedulers). - %% @doc Get sys version -spec(version() -> string()). version() -> @@ -70,7 +64,7 @@ sysdescr() -> %% @doc Get sys uptime -spec(uptime() -> string()). -uptime() -> gen_server:call(?SERVER, uptime). +uptime() -> gen_server:call(?SYS, uptime). %% @doc Get sys datetime -spec(datetime() -> string()). @@ -80,6 +74,8 @@ datetime() -> io_lib:format( "~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S])). +%% @doc Get sys interval +-spec(sys_interval() -> pos_integer()). sys_interval() -> application:get_env(?APP, sys_interval, 60000). @@ -96,44 +92,49 @@ info() -> %%-------------------------------------------------------------------- init([]) -> - Tick = fun(I, M) -> - {ok, TRef} = timer:send_interval(I, M), TRef - end, - {ok, #state{started_at = os:timestamp(), - heartbeat = Tick(1000, heartbeat), - sys_ticker = Tick(sys_interval(), tick), - version = iolist_to_binary(version()), - sysdescr = iolist_to_binary(sysdescr())}, hibernate}. + State = #state{start_time = erlang:timestamp(), + version = iolist_to_binary(version()), + sysdescr = iolist_to_binary(sysdescr())}, + {ok, heartbeat(tick(State))}. + +heartbeat(State) -> + State#state{heartbeat = start_timer(timer:seconds(1), heartbeat)}. +tick(State) -> + State#state{ticker = start_timer(sys_interval(), tick)}. handle_call(uptime, _From, State) -> {reply, uptime(State), State}; handle_call(Req, _From, State) -> - emqx_log:error("[SYS] Unexpected request: ~p", [Req]), + emqx_logger:error("[SYS] Unexpected request: ~p", [Req]), {reply, ignore, State}. handle_cast(Msg, State) -> - emqx_log:error("[SYS] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[SYS] Unexpected msg: ~p", [Msg]), {noreply, State}. -handle_info(heartbeat, State) -> +handle_info({timeout, TRef, heartbeat}, State = #state{heartbeat = TRef}) -> publish(uptime, iolist_to_binary(uptime(State))), publish(datetime, iolist_to_binary(datetime())), - {noreply, State, hibernate}; + {noreply, heartbeat(State)}; -handle_info(tick, State = #state{version = Version, sysdescr = Descr}) -> - retain(brokers), - retain(version, Version), - retain(sysdescr, Descr), - {noreply, State, hibernate}; +handle_info({timeout, TRef, tick}, State = #state{ticker = TRef, + version = Version, + sysdescr = Descr}) -> + publish(version, Version), + publish(sysdescr, Descr), + publish(brokers, ekka_mnesia:running_nodes()), + publish(stats, emqx_stats:all()), + publish(metrics, emqx_metrics:all()), + {noreply, tick(State), hibernate}; handle_info(Info, State) -> - emqx_log:error("[SYS] Unexpected info: ~p", [Info]), + emqx_logger:error("[SYS] Unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #state{heartbeat = Hb, sys_ticker = TRef}) -> - timer:cancel(Hb), - timer:cancel(TRef). +terminate(_Reason, #state{heartbeat = HBRef, ticker = TRef}) -> + emqx_misc:cancel_timer(HBRef), + emqx_misc:cancel_timer(TRef). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -142,24 +143,9 @@ code_change(_OldVsn, State, _Extra) -> %% Internal functions %%-------------------------------------------------------------------- -retain(brokers) -> - Payload = list_to_binary(string:join([atom_to_list(N) || - N <- ekka_mnesia:running_nodes()], ",")), - Msg = emqx_message:make(broker, <<"$SYS/brokers">>, Payload), - emqx:publish(emqx_message:set_flag(sys, emqx_message:set_flag(retain, Msg))). - -retain(Topic, Payload) when is_binary(Payload) -> - Msg = emqx_message:make(broker, emqx_topic:systop(Topic), Payload), - emqx:publish(emqx_message:set_flag(sys, emqx_message:set_flag(retain, Msg))). - -publish(Topic, Payload) when is_binary(Payload) -> - Msg = emqx_message:make(broker, emqx_topic:systop(Topic), Payload), - emqx:publish(emqx_message:set_flag(sys, Msg)). - -uptime(#state{started_at = Ts}) -> - Secs = timer:now_diff(os:timestamp(), Ts) div 1000000, +uptime(#state{start_time = Ts}) -> + Secs = timer:now_diff(erlang:timestamp(), Ts) div 1000000, lists:flatten(uptime(seconds, Secs)). - uptime(seconds, Secs) when Secs < 60 -> [integer_to_list(Secs), " seconds"]; uptime(seconds, Secs) -> @@ -175,3 +161,38 @@ uptime(hours, H) -> uptime(days, D) -> [integer_to_list(D), " days,"]. +publish(uptime, Uptime) -> + safe_publish(systop(uptime), [sys], Uptime); +publish(datetime, Datetime) -> + safe_publish(systop(datatype), [sys], Datetime); +publish(version, Version) -> + safe_publish(systop(version), [sys, retain], Version); +publish(sysdescr, Descr) -> + safe_publish(systop(sysdescr), [sys, retain], Descr); +publish(brokers, Nodes) -> + Payload = string:join([atom_to_list(N) || N <- Nodes], ","), + safe_publish(<<"$SYS/brokers">>, [sys, retain], Payload); +publish(stats, Stats) -> + [begin + Topic = systop(lists:concat(['stats/', Stat])), + safe_publish(Topic, [sys], integer_to_binary(Val)) + end || {Stat, Val} <- Stats, is_atom(Stat), is_integer(Val)]; +publish(metrics, Metrics) -> + [begin + Topic = systop(lists:concat(['metrics/', Metric])), + safe_publish(Topic, [sys], integer_to_binary(Val)) + end || {Metric, Val} <- Metrics, is_atom(Metric), is_integer(Val)]. + +safe_publish(Topic, Flags, Payload) -> + try do_publish(Topic, Flags, Payload) + catch + _:Error -> + emqx_logger:error("[SYS] Publish error: ~p", [Error]) + end. + +do_publish(Topic, Flags, Payload) -> + Msg0 = emqx_message:make(?SYS, Topic, iolist_to_binary(Payload)), + emqx_broker:publish(lists:foldl(fun(Flag, Msg) -> + emqx_message:set_flag(Flag, Msg) + end, Msg0, Flags)). + diff --git a/src/emqx_sysmon.erl b/src/emqx_sys_mon.erl similarity index 61% rename from src/emqx_sysmon.erl rename to src/emqx_sys_mon.erl index d0c3d9001..6e58f84bb 100644 --- a/src/emqx_sysmon.erl +++ b/src/emqx_sys_mon.erl @@ -1,20 +1,20 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== --module(emqx_sysmon). +-module(emqx_sys_mon). -behavior(gen_server). @@ -23,31 +23,34 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, {tickref, events = [], tracelog}). - -%%-define(LOG_FMT, [{formatter_config, [time, " ", message, "\n"]}]). +-record(state, {timer, events}). -define(LOG(Msg, ProcInfo), - emqx_log:warning([{sysmon, true}], "[SYSMON] ~s~n~p", [WarnMsg, ProcInfo])). + emqx_logger:warning([{sysmon, true}], + "[SYSMON] ~s~n~p", [WarnMsg, ProcInfo])). -define(LOG(Msg, ProcInfo, PortInfo), - emqx_log:warning([{sysmon, true}], "[SYSMON] ~s~n~p~n~p", [WarnMsg, ProcInfo, PortInfo])). + emqx_logger:warning([{sysmon, true}], + "[SYSMON] ~s~n~p~n~p", [WarnMsg, ProcInfo, PortInfo])). + +-define(SYSMON, ?MODULE). %% @doc Start system monitor --spec(start_link(Opts :: list(tuple())) -> {ok, pid()} | ignore | {error, term()}). +-spec(start_link(Opts :: list(tuple())) + -> {ok, pid()} | ignore | {error, term()}). start_link(Opts) -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []). + gen_server:start_link({local, ?SYSMON}, ?MODULE, [Opts], []). %%-------------------------------------------------------------------- -%% gen_server Callbacks +%% gen_server callbacks %%-------------------------------------------------------------------- init([Opts]) -> erlang:system_monitor(self(), parse_opt(Opts)), - {ok, TRef} = timer:send_interval(timer:seconds(1), reset), - %%TODO: don't trace for performance issue. - %%{ok, TraceLog} = start_tracelog(proplists:get_value(logfile, Opts)), - {ok, #state{tickref = TRef}}. + {ok, start_timer(#state{events = []})}. + +start_timer(State) -> + State#state{timer = emqx_misc:start_timer(timer:seconds(2), tick)}. parse_opt(Opts) -> parse_opt(Opts, []). @@ -75,53 +78,53 @@ parse_opt([_Opt|Opts], Acc) -> parse_opt(Opts, Acc). handle_call(Req, _From, State) -> - emqx_log:error("[SYSMON] Unexpected request: ~p", [Req]), + emqx_logger:error("[SYSMON] Unexpected request: ~p", [Req]), {reply, ignore, State}. handle_cast(Msg, State) -> - emqx_log:error("[SYSMON] Unexpected msg: ~p", [Msg]), + emqx_logger:error("[SYSMON] Unexpected msg: ~p", [Msg]), {noreply, State}. handle_info({monitor, Pid, long_gc, Info}, State) -> suppress({long_gc, Pid}, fun() -> WarnMsg = io_lib:format("long_gc warning: pid = ~p, info: ~p", [Pid, Info]), ?LOG(WarnMsg, procinfo(Pid)), - publish(long_gc, WarnMsg) + safe_publish(long_gc, WarnMsg) end, State); handle_info({monitor, Pid, long_schedule, Info}, State) when is_pid(Pid) -> suppress({long_schedule, Pid}, fun() -> WarnMsg = io_lib:format("long_schedule warning: pid = ~p, info: ~p", [Pid, Info]), ?LOG(WarnMsg, procinfo(Pid)), - publish(long_schedule, WarnMsg) + safe_publish(long_schedule, WarnMsg) end, State); handle_info({monitor, Port, long_schedule, Info}, State) when is_port(Port) -> suppress({long_schedule, Port}, fun() -> WarnMsg = io_lib:format("long_schedule warning: port = ~p, info: ~p", [Port, Info]), ?LOG(WarnMsg, erlang:port_info(Port)), - publish(long_schedule, WarnMsg) + safe_publish(long_schedule, WarnMsg) end, State); handle_info({monitor, Pid, large_heap, Info}, State) -> suppress({large_heap, Pid}, fun() -> WarnMsg = io_lib:format("large_heap warning: pid = ~p, info: ~p", [Pid, Info]), ?LOG(WarnMsg, procinfo(Pid)), - publish(large_heap, WarnMsg) + safe_publish(large_heap, WarnMsg) end, State); handle_info({monitor, SusPid, busy_port, Port}, State) -> suppress({busy_port, Port}, fun() -> WarnMsg = io_lib:format("busy_port warning: suspid = ~p, port = ~p", [SusPid, Port]), ?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)), - publish(busy_port, WarnMsg) + safe_publish(busy_port, WarnMsg) end, State); handle_info({monitor, SusPid, busy_dist_port, Port}, State) -> suppress({busy_dist_port, Port}, fun() -> WarnMsg = io_lib:format("busy_dist_port warning: suspid = ~p, port = ~p", [SusPid, Port]), ?LOG(WarnMsg, procinfo(SusPid), erlang:port_info(Port)), - publish(busy_dist_port, WarnMsg) + safe_publish(busy_dist_port, WarnMsg) end, State); handle_info(reset, State) -> @@ -131,9 +134,8 @@ handle_info(Info, State) -> lager:error("[SYSMON] Unexpected Info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #state{tickref = TRef, tracelog = TraceLog}) -> - timer:cancel(TRef), - cancel_tracelog(TraceLog). +terminate(_Reason, #state{timer = TRef}) -> + emqx_misc:cancel_timer(TRef). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -152,20 +154,13 @@ procinfo(Pid) -> {Info, GcInfo} -> Info ++ GcInfo end. -publish(Sysmon, WarnMsg) -> - Msg = emqx_message:make(sysmon, topic(Sysmon), iolist_to_binary(WarnMsg)), - emqx:publish(emqx_message:set_flag(sys, Msg)). - -topic(Sysmon) -> - emqx_topic:systop(list_to_binary(lists:concat(['sysmon/', Sysmon]))). - -%% start_tracelog(undefined) -> -%% {ok, undefined}; -%% start_tracelog(LogFile) -> -%% lager:trace_file(LogFile, [{sysmon, true}], info, ?LOG_FMT). - -cancel_tracelog(undefined) -> - ok; -cancel_tracelog(TraceLog) -> - lager:stop_trace(TraceLog). +safe_publish(Event, WarnMsg) -> + try + Topic = emqx_topic:systop(lists:concat(['sysmon/', Event])), + Msg = emqx_message:make(?SYSMON, Topic, iolist_to_binary(WarnMsg)), + emqx_broker:publish(emqx_message:set_flag(sys, Msg)) + catch + _:Error -> + emqx_logger:error("[SYSMON] Publish error: ~p", [Error]) + end. diff --git a/src/emqx_sys_sup.erl b/src/emqx_sys_sup.erl new file mode 100644 index 000000000..745817469 --- /dev/null +++ b/src/emqx_sys_sup.erl @@ -0,0 +1,36 @@ +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== + +-module(emqx_sys_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + Sys = {sys, {emqx_sys, start_link, []}, + permanent, 5000, worker, [emqx_sys]}, + + {ok, Env} = emqx_config:get_env(sysmon), + Sysmon = {sys_mon, {emqx_sysmon, start_link, [Env]}, + permanent, 5000, worker, [emqx_sys_mon]}, + {ok, {{one_for_one, 10, 100}, [Sys, Sysmon]}}. + diff --git a/src/emqx_sysmon_sup.erl b/src/emqx_sysmon_sup.erl deleted file mode 100644 index 0c4566bc3..000000000 --- a/src/emqx_sysmon_sup.erl +++ /dev/null @@ -1,35 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_sysmon_sup). - --behaviour(supervisor). - -%% API --export([start_link/0]). - -%% Supervisor callbacks --export([init/1]). - -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -init([]) -> - {ok, Env} = emqx_config:get_env(sysmon), - Sysmon = {sysmon, {emqx_sysmon, start_link, [Env]}, - permanent, 5000, worker, [emqx_sysmon]}, - {ok, {{one_for_one, 10, 100}, [Sysmon]}}. - diff --git a/src/emqx_tables.erl b/src/emqx_tables.erl index 78993104c..c5f249592 100644 --- a/src/emqx_tables.erl +++ b/src/emqx_tables.erl @@ -1,27 +1,28 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_tables). --export([create/2]). +-export([new/2]). -create(Tab, Opts) -> +%% Create a named_table ets. +new(Tab, Opts) -> case ets:info(Tab, name) of undefined -> - ets:new(Tab, lists:usort([named_table|Opts])); + ets:new(Tab, lists:usort([named_table | Opts])); Tab -> Tab end. diff --git a/src/emqx_time.erl b/src/emqx_time.erl index cfcfadf63..1d1405900 100644 --- a/src/emqx_time.erl +++ b/src/emqx_time.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_time). diff --git a/src/emqx_topic.erl b/src/emqx_topic.erl index db7332be7..8cd7ce3b6 100644 --- a/src/emqx_topic.erl +++ b/src/emqx_topic.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_topic). @@ -143,11 +143,10 @@ word(<<"#">>) -> '#'; word(Bin) -> Bin. %% @doc '$SYS' Topic. -systop(Name) when is_atom(Name) -> - list_to_binary(lists:concat(["$SYS/brokers/", node(), "/", Name])); - +systop(Name) when is_atom(Name); is_list(Name) -> + iolist_to_binary(lists:concat(["$SYS/brokers/", node(), "/", Name])); systop(Name) when is_binary(Name) -> - list_to_binary(["$SYS/brokers/", atom_to_list(node()), "/", Name]). + iolist_to_binary(["$SYS/brokers/", atom_to_list(node()), "/", Name]). -spec(feed_var(binary(), binary(), binary()) -> binary()). feed_var(Var, Val, Topic) -> diff --git a/src/emqx_tracer.erl b/src/emqx_tracer.erl index 1d0211e78..5e68ed4c5 100644 --- a/src/emqx_tracer.erl +++ b/src/emqx_tracer.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_tracer). @@ -22,9 +22,7 @@ -export([start_link/0]). --export([trace/3]). - --export([start_trace/2, stop_trace/1, all_traces/0]). +-export([start_trace/2, lookup_traces/0, stop_trace/1]). %% gen_server Function Exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -36,29 +34,15 @@ -define(OPTIONS, [{formatter_config, [time, " [",severity,"] ", message, "\n"]}]). +-define(TRACER, ?MODULE). + %%-------------------------------------------------------------------- %% Start the tracer %%-------------------------------------------------------------------- --spec(start_link() -> {ok, pid()}). +-spec(start_link() -> {ok, pid()} | ignore | {error, term()}). start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - -%%-------------------------------------------------------------------- -%% Trace -%%-------------------------------------------------------------------- - -trace(publish, From, _Msg) when is_atom(From) -> - %% Dont' trace '$SYS' publish - ignore; -trace(publish, #client{client_id = ClientId, username = Username}, - #message{topic = Topic, payload = Payload}) -> - emqx_log:info([{client, ClientId}, {topic, Topic}], - "~s/~s PUBLISH to ~s: ~p", [ClientId, Username, Topic, Payload]); -trace(publish, From, #message{topic = Topic, payload = Payload}) - when is_binary(From); is_list(From) -> - emqx_log:info([{client, From}, {topic, Topic}], - "~s PUBLISH to ~s: ~p", [From, Topic, Payload]). + gen_server:start_link({local, ?TRACER}, ?MODULE, [], []). %%-------------------------------------------------------------------- %% Start/Stop Trace @@ -68,11 +52,11 @@ trace(publish, From, #message{topic = Topic, payload = Payload}) -spec(start_trace(trace_who(), string()) -> ok | {error, term()}). start_trace({client, ClientId}, LogFile) -> start_trace({start_trace, {client, ClientId}, LogFile}); - start_trace({topic, Topic}, LogFile) -> start_trace({start_trace, {topic, Topic}, LogFile}). -start_trace(Req) -> gen_server:call(?MODULE, Req, infinity). +start_trace(Req) -> + gen_server:call(?MODULE, Req, infinity). %% @doc Stop tracing client or topic. -spec(stop_trace(trace_who()) -> ok | {error, term()}). @@ -81,25 +65,31 @@ stop_trace({client, ClientId}) -> stop_trace({topic, Topic}) -> gen_server:call(?MODULE, {stop_trace, {topic, Topic}}). -%% @doc Lookup all traces. --spec(all_traces() -> [{Who :: trace_who(), LogFile :: string()}]). -all_traces() -> - gen_server:call(?MODULE, all_traces). +%% @doc Lookup all traces +-spec(lookup_traces() -> [{Who :: trace_who(), LogFile :: string()}]). +lookup_traces() -> + gen_server:call(?TRACER, lookup_traces). %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- init([]) -> - {ok, #state{level = emqx_config:get_env(trace_level, debug), traces = #{}}}. + Level = emqx_config:get_env(trace_level, debug), + {ok, #state{level = Level, traces = #{}}}. -handle_call({start_trace, Who, LogFile}, _From, State = #state{level = Level, traces = Traces}) -> - case lager:trace_file(LogFile, [Who], Level, ?OPTIONS) of +handle_call({start_trace, Who, LogFile}, _From, + State = #state{level = Level, traces = Traces}) -> + case catch lager:trace_file(LogFile, [Who], Level, ?OPTIONS) of {ok, exists} -> - {reply, {error, existed}, State}; - {ok, Trace} -> + {reply, {error, alread_existed}, State}; + {ok, Trace} -> {reply, ok, State#state{traces = maps:put(Who, {Trace, LogFile}, Traces)}}; - {error, Error} -> + {error, Reason} -> + emqx_logger:error("[TRACER] trace error: ~p", [Reason]), + {reply, {error, Reason}, State}; + {'EXIT', Error} -> + emqx_logger:error("[TRACER] trace exit: ~p", [Error]), {reply, {error, Error}, State} end; @@ -112,23 +102,23 @@ handle_call({stop_trace, Who}, _From, State = #state{traces = Traces}) -> end, {reply, ok, State#state{traces = maps:remove(Who, Traces)}}; error -> - {reply, {error, not_found}, State} + {reply, {error, trance_not_found}, State} end; -handle_call(all_traces, _From, State = #state{traces = Traces}) -> +handle_call(lookup_traces, _From, State = #state{traces = Traces}) -> {reply, [{Who, LogFile} || {Who, {_Trace, LogFile}} <- maps:to_list(Traces)], State}; handle_call(Req, _From, State) -> - emqx_log:error("[TRACE] Unexpected Call: ~p", [Req]), + emqx_logger:error("[TRACER] Unexpected request: ~p", [Req]), {reply, ignore, State}. handle_cast(Msg, State) -> - emqx_log:error("[TRACE] Unexpected Cast: ~p", [Msg]), + emqx_logger:error("[TRACER] Unexpected msg: ~p", [Msg]), {noreply, State}. handle_info(Info, State) -> - emqx_log:error("[TRACE] Unexpected Info: ~p", [Info]), + emqx_logger:error("[TRACER] Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/emqx_tracer_sup.erl b/src/emqx_tracer_sup.erl deleted file mode 100644 index bc5faa82c..000000000 --- a/src/emqx_tracer_sup.erl +++ /dev/null @@ -1,32 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_tracer_sup). - --behaviour(supervisor). - --export([start_link/0]). - --export([init/1]). - -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -init([]) -> - Tracer = {tracer, {emqx_tracer, start_link, []}, - permanent, 5000, worker, [emqx_tracer]}, - {ok, {{one_for_one, 10, 3600}, [Tracer]}}. - diff --git a/src/emqx_trie.erl b/src/emqx_trie.erl index a1b67561d..595b38116 100644 --- a/src/emqx_trie.erl +++ b/src/emqx_trie.erl @@ -1,18 +1,18 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- +%%%=================================================================== +%%% Copyright (c) 2013-2018 EMQ Inc. All rights reserved. +%%% +%%% Licensed under the Apache License, Version 2.0 (the "License"); +%%% you may not use this file except in compliance with the License. +%%% You may obtain a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, software +%%% distributed under the License is distributed on an "AS IS" BASIS, +%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%%% See the License for the specific language governing permissions and +%%% limitations under the License. +%%%=================================================================== -module(emqx_trie). @@ -24,39 +24,39 @@ -boot_mnesia({mnesia, [boot]}). -copy_mnesia({mnesia, [copy]}). -%% Trie API +%% Trie APIs -export([insert/1, match/1, lookup/1, delete/1]). -%% Tables +%% Mnesia tables -define(TRIE, emqx_trie). -define(TRIE_NODE, emqx_trie_node). %%-------------------------------------------------------------------- -%% Mnesia Bootstrap +%% Mnesia bootstrap %%-------------------------------------------------------------------- %% @doc Create or replicate trie tables. -spec(mnesia(boot | copy) -> ok). mnesia(boot) -> - %% Trie Table + %% Trie table ok = ekka_mnesia:create_table(?TRIE, [ {ram_copies, [node()]}, {record_name, trie}, {attributes, record_info(fields, trie)}]), - %% Trie Node Table + %% Trie node table ok = ekka_mnesia:create_table(?TRIE_NODE, [ {ram_copies, [node()]}, {record_name, trie_node}, {attributes, record_info(fields, trie_node)}]); mnesia(copy) -> - %% Copy Trie Table + %% Copy trie table ok = ekka_mnesia:copy_table(?TRIE), - %% Copy Trie Node Table + %% Copy trie_node table ok = ekka_mnesia:copy_table(?TRIE_NODE). %%-------------------------------------------------------------------- -%% Trie API +%% Trie APIs %%-------------------------------------------------------------------- %% @doc Insert a topic into the trie @@ -110,7 +110,7 @@ add_path({Node, Word, Child}) -> [TrieNode = #trie_node{edge_count = Count}] -> case mnesia:wread({?TRIE, Edge}) of [] -> - write_trie_node(TrieNode#trie_node{edge_count = Count+1}), + write_trie_node(TrieNode#trie_node{edge_count = Count + 1}), write_trie(#trie{edge = Edge, node_id = Child}); [_] -> ok diff --git a/src/emqx_ws.erl b/src/emqx_ws.erl index b2bdc2e54..e7ad63d61 100644 --- a/src/emqx_ws.erl +++ b/src/emqx_ws.erl @@ -26,8 +26,8 @@ -record(wsocket_state, {peername, client_pid, max_packet_size, parser}). -define(WSLOG(Level, Format, Args, State), - emqx_log:Level("WsClient(~s): " ++ Format, - [esockd_net:format(State#wsocket_state.peername) | Args])). + emqx_logger:Level("WsClient(~s): " ++ Format, + [esockd_net:format(State#wsocket_state.peername) | Args])). handle_request(Req) -> @@ -38,7 +38,7 @@ handle_request(Req) -> %%-------------------------------------------------------------------- handle_request('GET', "/mqtt", Req) -> - emqx_log:debug("WebSocket Connection from: ~s", [Req:get(peer)]), + emqx_logger:debug("WebSocket Connection from: ~s", [Req:get(peer)]), Upgrade = Req:get_header_value("Upgrade"), Proto = check_protocol_header(Req), case {is_websocket(Upgrade), Proto} of @@ -56,19 +56,19 @@ handle_request('GET', "/mqtt", Req) -> max_packet_size = PacketSize, client_pid = ClientPid}); {error, Reason} -> - emqx_log:error("Get peername with error ~s", [Reason]), + emqx_logger:error("Get peername with error ~s", [Reason]), Req:respond({400, [], <<"Bad Request">>}) end; {false, _} -> - emqx_log:error("Not WebSocket: Upgrade = ~s", [Upgrade]), + emqx_logger:error("Not WebSocket: Upgrade = ~s", [Upgrade]), Req:respond({400, [], <<"Bad Request">>}); {_, Proto} -> - emqx_log:error("WebSocket with error Protocol: ~s", [Proto]), + emqx_logger:error("WebSocket with error Protocol: ~s", [Proto]), Req:respond({400, [], <<"Bad WebSocket Protocol">>}) end; handle_request(Method, Path, Req) -> - emqx_log:error("Unexpected WS Request: ~s ~s", [Method, Path]), + emqx_logger:error("Unexpected WS Request: ~s ~s", [Method, Path]), Req:not_found(). is_websocket(Upgrade) -> diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index 643f8d825..161104568 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -50,8 +50,8 @@ -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). -define(WSLOG(Level, Format, Args, State), - emqx_log:Level("WsClient(~s): " ++ Format, - [esockd_net:format(State#wsclient_state.peername) | Args])). + emqx_logger:Level("WsClient(~s): " ++ Format, + [esockd_net:format(State#wsclient_state.peername) | Args])). %% @doc Start WebSocket Client. start_link(Env, WsPid, Req, ReplyChannel) -> @@ -140,7 +140,7 @@ handle_call(Req, _From, State) -> reply({error, unexpected_request}, State). handle_cast({received, Packet}, State = #wsclient_state{proto_state = ProtoState}) -> - emqx_mqtt_metrics:received(Packet), + emqx_metrics:received(Packet), case emqx_protocol:received(Packet, ProtoState) of {ok, ProtoState1} -> {noreply, gc(State#wsclient_state{proto_state = ProtoState1}), hibernate}; @@ -290,7 +290,7 @@ emit_stats(undefined, State) -> State; emit_stats(ClientId, State) -> {reply, Stats, _, _} = handle_call(stats, undefined, State), - emqx_stats:set_client_stats(ClientId, Stats), + emqx_cm:set_client_stats(ClientId, Stats), State. wsock_stats(#wsclient_state{connection = Conn}) ->