Merge branch 'master' of github.com:emqtt/emqtt
This commit is contained in:
commit
42c220cfa5
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -2,6 +2,30 @@
|
||||||
eMQTTD ChangeLog
|
eMQTTD ChangeLog
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
0.5.4-alpha (2015-03-22)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Benchmark this release on a ubuntu/14.04 server with 8 cores, 32G memory from QingCloud.com:
|
||||||
|
|
||||||
|
```
|
||||||
|
200K Connections,
|
||||||
|
30K Messages/Sec,
|
||||||
|
20Mbps In/Out Traffic,
|
||||||
|
200K Topics,
|
||||||
|
200K Subscribers,
|
||||||
|
|
||||||
|
Consumed 7G memory, 40% CPU/core
|
||||||
|
```
|
||||||
|
|
||||||
|
Benchmark code: https://github.com/emqtt/emqttd_benchmark
|
||||||
|
|
||||||
|
Change: rewrite emqttd_pubsub to handle more concurrent subscribe requests.
|
||||||
|
|
||||||
|
Change: ./bin/emqttd_ctl add 'stats', 'metrics' commands.
|
||||||
|
|
||||||
|
Bugfix: issue #71, #72
|
||||||
|
|
||||||
|
|
||||||
0.5.3-alpha (2015-03-19)
|
0.5.3-alpha (2015-03-19)
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
|
51
README.md
51
README.md
|
@ -1,15 +1,24 @@
|
||||||
# eMQTT [](https://travis-ci.org/emqtt/emqttd)
|
# eMQTTD [](https://travis-ci.org/emqtt/emqttd)
|
||||||
|
|
||||||
eMQTT is a clusterable, massively scalable, fault-tolerant and extensible MQTT V3.1/V3.1.1 broker written in Erlang/OTP.
|
eMQTTD is a clusterable, massively scalable, fault-tolerant and extensible MQTT V3.1/V3.1.1 broker written in Erlang/OTP.
|
||||||
|
|
||||||
eMQTT support MQTT V3.1/V3.1.1 Protocol Specification.
|
eMQTTD support MQTT V3.1/V3.1.1 Protocol Specification.
|
||||||
|
|
||||||
|
eMQTTD requires Erlang R17+.
|
||||||
|
|
||||||
|
|
||||||
|
## Benchmark
|
||||||
|
|
||||||
|
Benchmark 0.5.4-alpha on a ubuntu/14.04 server with 8 cores, 32G memory from QingCloud:
|
||||||
|
|
||||||
|
200K Connections, 200K Topics, 20K Messages/sec, 20Mbps In/Out with 7G Memory, 40%CPU/core
|
||||||
|
|
||||||
eMQTT requires Erlang R17+.
|
|
||||||
|
|
||||||
## NOTICE
|
## NOTICE
|
||||||
|
|
||||||
eMQTTD still cannot handle massive retained messages.
|
eMQTTD still cannot handle massive retained messages.
|
||||||
|
|
||||||
|
|
||||||
## Featues
|
## Featues
|
||||||
|
|
||||||
Full MQTT V3.1.1 Support
|
Full MQTT V3.1.1 Support
|
||||||
|
@ -41,15 +50,15 @@ Bridge brokers locally or remotelly
|
||||||
## Startup in Five Minutes
|
## Startup in Five Minutes
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone git://github.com/emqtt/emqtt.git
|
$ git clone git://github.com/emqtt/emqttd.git
|
||||||
|
|
||||||
$ cd emqtt
|
$ cd emqttd
|
||||||
|
|
||||||
$ make && make dist
|
$ make && make dist
|
||||||
|
|
||||||
$ cd rel/emqtt
|
$ cd rel/emqttd
|
||||||
|
|
||||||
$ ./bin/emqtt console
|
$ ./bin/emqttd console
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deploy and Start
|
## Deploy and Start
|
||||||
|
@ -57,18 +66,18 @@ $ ./bin/emqtt console
|
||||||
### start
|
### start
|
||||||
|
|
||||||
```
|
```
|
||||||
cp -R rel/emqtt $INSTALL_DIR
|
cp -R rel/emqttd $INSTALL_DIR
|
||||||
|
|
||||||
cd $INSTALL_DIR/emqtt
|
cd $INSTALL_DIR/emqttd
|
||||||
|
|
||||||
./bin/emqtt start
|
./bin/emqttd start
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### stop
|
### stop
|
||||||
|
|
||||||
```
|
```
|
||||||
./bin/emqtt stop
|
./bin/emqttd stop
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -77,7 +86,7 @@ cd $INSTALL_DIR/emqtt
|
||||||
### etc/app.config
|
### etc/app.config
|
||||||
|
|
||||||
```
|
```
|
||||||
{emqtt, [
|
{emqttd, [
|
||||||
{auth, {anonymous, []}}, %internal, anonymous
|
{auth, {anonymous, []}}, %internal, anonymous
|
||||||
{listen, [
|
{listen, [
|
||||||
{mqtt, 1883, [
|
{mqtt, 1883, [
|
||||||
|
@ -104,7 +113,7 @@ cd $INSTALL_DIR/emqtt
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
-name emqtt@127.0.0.1
|
-name emqttd@127.0.0.1
|
||||||
|
|
||||||
-setcookie emqtt
|
-setcookie emqtt
|
||||||
|
|
||||||
|
@ -113,7 +122,7 @@ cd $INSTALL_DIR/emqtt
|
||||||
When nodes clustered, vm.args should be configured as below:
|
When nodes clustered, vm.args should be configured as below:
|
||||||
|
|
||||||
```
|
```
|
||||||
-name emqtt@host1
|
-name emqttd@host1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Cluster
|
## Cluster
|
||||||
|
@ -123,22 +132,22 @@ Suppose we cluster two nodes on 'host1', 'host2', Steps:
|
||||||
on 'host1':
|
on 'host1':
|
||||||
|
|
||||||
```
|
```
|
||||||
./bin/emqtt start
|
./bin/emqttd start
|
||||||
```
|
```
|
||||||
|
|
||||||
on 'host2':
|
on 'host2':
|
||||||
|
|
||||||
```
|
```
|
||||||
./bin/emqtt start
|
./bin/emqttd start
|
||||||
|
|
||||||
./bin/emqtt_ctl cluster emqtt@host1
|
./bin/emqttd_ctl cluster emqttd@host1
|
||||||
```
|
```
|
||||||
|
|
||||||
Run './bin/emqtt_ctl cluster' on 'host1' or 'host2' to check cluster nodes.
|
Run './bin/emqttd_ctl cluster' on 'host1' or 'host2' to check cluster nodes.
|
||||||
|
|
||||||
## HTTP API
|
## HTTP API
|
||||||
|
|
||||||
eMQTT support http to publish message.
|
eMQTTD support http to publish message.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -163,7 +172,7 @@ message | Message
|
||||||
|
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||
[Design Wiki](https://github.com/emqtt/emqtt/wiki)
|
[Design Wiki](https://github.com/emqtt/emqttd/wiki)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
5
TODO
5
TODO
|
@ -1,4 +1,9 @@
|
||||||
|
|
||||||
|
v0.9.0-alpha (2015-03-20)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
emqtt_sm, emqtt_cm, emqtt_pubsub performance issue...
|
||||||
|
|
||||||
v0.8.0-alpha (2015-03-20)
|
v0.8.0-alpha (2015-03-20)
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{application, emqttd,
|
{application, emqttd,
|
||||||
[
|
[
|
||||||
{description, "Erlang MQTT Broker"},
|
{description, "Erlang MQTT Broker"},
|
||||||
{vsn, "0.5.0"},
|
{vsn, "0.5.4"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [kernel,
|
{applications, [kernel,
|
||||||
|
|
|
@ -81,7 +81,7 @@ init([Node, SubTopic, Options]) ->
|
||||||
true ->
|
true ->
|
||||||
true = erlang:monitor_node(Node, true),
|
true = erlang:monitor_node(Node, true),
|
||||||
State = parse_opts(Options, #state{node = Node, subtopic = SubTopic}),
|
State = parse_opts(Options, #state{node = Node, subtopic = SubTopic}),
|
||||||
emqttd_pubsub:subscribe({SubTopic, ?QOS_0}, self()),
|
emqttd_pubsub:subscribe({SubTopic, ?QOS_0}),
|
||||||
{ok, State};
|
{ok, State};
|
||||||
false ->
|
false ->
|
||||||
{stop, {cannot_connect, Node}}
|
{stop, {cannot_connect, Node}}
|
||||||
|
|
|
@ -150,8 +150,8 @@ init([Options]) ->
|
||||||
Topics = ?SYSTOP_CLIENTS ++ ?SYSTOP_SESSIONS ++ ?SYSTOP_PUBSUB,
|
Topics = ?SYSTOP_CLIENTS ++ ?SYSTOP_SESSIONS ++ ?SYSTOP_PUBSUB,
|
||||||
[ets:insert(?BROKER_TAB, {Topic, 0}) || Topic <- Topics],
|
[ets:insert(?BROKER_TAB, {Topic, 0}) || Topic <- Topics],
|
||||||
% Create $SYS Topics
|
% Create $SYS Topics
|
||||||
[{atomic, _} = create(systop(Topic)) || Topic <- ?SYSTOP_BROKERS],
|
[ok = create(systop(Topic)) || Topic <- ?SYSTOP_BROKERS],
|
||||||
[{atomic, _} = create(systop(Topic)) || Topic <- Topics],
|
[ok = create(systop(Topic)) || Topic <- Topics],
|
||||||
SysInterval = proplists:get_value(sys_interval, Options, 60),
|
SysInterval = proplists:get_value(sys_interval, Options, 60),
|
||||||
State = #state{started_at = os:timestamp(), sys_interval = SysInterval},
|
State = #state{started_at = os:timestamp(), sys_interval = SysInterval},
|
||||||
Delay = if
|
Delay = if
|
||||||
|
|
|
@ -86,7 +86,8 @@ lookup(ClientId) when is_binary(ClientId) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec register(ClientId :: binary(), Pid :: pid()) -> ok.
|
-spec register(ClientId :: binary(), Pid :: pid()) -> ok.
|
||||||
register(ClientId, Pid) when is_binary(ClientId), is_pid(Pid) ->
|
register(ClientId, Pid) when is_binary(ClientId), is_pid(Pid) ->
|
||||||
gen_server:call(?SERVER, {register, ClientId, Pid}).
|
%%TODO: infinify to block requests when too many clients, this will be redesinged in 0.9.x...
|
||||||
|
gen_server:call(?SERVER, {register, ClientId, Pid}, infinity).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc
|
%% @doc
|
||||||
|
|
|
@ -38,6 +38,8 @@
|
||||||
|
|
||||||
-export([status/1,
|
-export([status/1,
|
||||||
broker/1,
|
broker/1,
|
||||||
|
stats/1,
|
||||||
|
metrics/1,
|
||||||
cluster/1,
|
cluster/1,
|
||||||
listeners/1,
|
listeners/1,
|
||||||
bridges/1,
|
bridges/1,
|
||||||
|
@ -84,12 +86,12 @@ userdel([Username]) ->
|
||||||
|
|
||||||
broker([]) ->
|
broker([]) ->
|
||||||
Funs = [sysdescr, version, uptime, datetime],
|
Funs = [sysdescr, version, uptime, datetime],
|
||||||
[?PRINT("~s: ~s~n", [Fun, emqttd_broker:Fun()]) || Fun <- Funs];
|
[?PRINT("~s: ~s~n", [Fun, emqttd_broker:Fun()]) || Fun <- Funs].
|
||||||
|
|
||||||
broker(["stats"]) ->
|
stats([]) ->
|
||||||
[?PRINT("~s: ~p~n", [Stat, Val]) || {Stat, Val} <- emqttd_broker:getstats()];
|
[?PRINT("~s: ~p~n", [Stat, Val]) || {Stat, Val} <- emqttd_broker:getstats()].
|
||||||
|
|
||||||
broker(["metrics"]) ->
|
metrics([]) ->
|
||||||
[?PRINT("~s: ~p~n", [Metric, Val]) || {Metric, Val} <- emqttd_metrics:all()].
|
[?PRINT("~s: ~p~n", [Metric, Val]) || {Metric, Val} <- emqttd_metrics:all()].
|
||||||
|
|
||||||
listeners([]) ->
|
listeners([]) ->
|
||||||
|
|
|
@ -182,7 +182,7 @@ init([Options]) ->
|
||||||
% Init metrics
|
% Init metrics
|
||||||
[new_metric(Metric) || Metric <- Metrics],
|
[new_metric(Metric) || Metric <- Metrics],
|
||||||
% $SYS Topics for metrics
|
% $SYS Topics for metrics
|
||||||
[{atomic, _} = emqttd_pubsub:create(systop(Topic)) || {_, Topic} <- Metrics],
|
[ok = emqttd_pubsub:create(systop(Topic)) || {_, Topic} <- Metrics],
|
||||||
PubInterval = proplists:get_value(pub_interval, Options, 60),
|
PubInterval = proplists:get_value(pub_interval, Options, 60),
|
||||||
Delay = if
|
Delay = if
|
||||||
PubInterval == 0 -> 0;
|
PubInterval == 0 -> 0;
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
%%% @doc
|
%%% @doc
|
||||||
%%% emqttd core pubsub.
|
%%% emqttd core pubsub.
|
||||||
%%%
|
%%%
|
||||||
|
%%% TODO: should not use gen_server:call to create, subscribe topics...
|
||||||
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
-module(emqttd_pubsub).
|
-module(emqttd_pubsub).
|
||||||
|
@ -46,8 +48,8 @@
|
||||||
|
|
||||||
-export([topics/0,
|
-export([topics/0,
|
||||||
create/1,
|
create/1,
|
||||||
subscribe/2,
|
subscribe/1,
|
||||||
unsubscribe/2,
|
unsubscribe/1,
|
||||||
publish/1,
|
publish/1,
|
||||||
publish/2,
|
publish/2,
|
||||||
%local node
|
%local node
|
||||||
|
@ -106,8 +108,8 @@ topics() ->
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec create(binary()) -> {atomic, Reason :: any()} | {aborted, Reason :: any()}.
|
-spec create(binary()) -> {atomic, Reason :: any()} | {aborted, Reason :: any()}.
|
||||||
create(Topic) ->
|
create(Topic) when is_binary(Topic) ->
|
||||||
gen_server:call(?SERVER, {create, Topic}).
|
{atomic, ok} = mnesia:transaction(fun trie_add/1, [Topic]), ok.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc
|
%% @doc
|
||||||
|
@ -115,12 +117,25 @@ create(Topic) ->
|
||||||
%%
|
%%
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec subscribe({binary(), mqtt_qos()} | list(), pid()) -> {ok, list(mqtt_qos())}.
|
-spec subscribe({binary(), mqtt_qos()} | list()) -> {ok, list(mqtt_qos())}.
|
||||||
subscribe({Topic, Qos}, SubPid) when is_binary(Topic) and is_pid(SubPid) ->
|
subscribe({Topic, Qos}) when is_binary(Topic) ->
|
||||||
subscribe([{Topic, Qos}], SubPid);
|
case subscribe([{Topic, Qos}]) of
|
||||||
|
{ok, [GrantedQos]} -> {ok, GrantedQos};
|
||||||
|
{error, Error} -> {error, Error}
|
||||||
|
end;
|
||||||
|
subscribe(Topics = [{_Topic, _Qos}|_]) ->
|
||||||
|
subscribe(Topics, self(), []).
|
||||||
|
|
||||||
subscribe(Topics, SubPid) when is_list(Topics) and is_pid(SubPid) ->
|
subscribe([], _SubPid, Acc) ->
|
||||||
gen_server:call(?SERVER, {subscribe, Topics, SubPid}).
|
{ok, lists:reverse(Acc)};
|
||||||
|
%%TODO: check this function later.
|
||||||
|
subscribe([{Topic, Qos}|Topics], SubPid, Acc) ->
|
||||||
|
Subscriber = #topic_subscriber{topic=Topic, qos = Qos, subpid=SubPid},
|
||||||
|
F = fun() -> trie_add(Topic), mnesia:write(Subscriber) end,
|
||||||
|
case mnesia:transaction(F) of
|
||||||
|
{atomic, ok} -> subscribe(Topics, SubPid, [Qos|Acc]);
|
||||||
|
Error -> {error, Error}
|
||||||
|
end.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc
|
%% @doc
|
||||||
|
@ -128,12 +143,27 @@ subscribe(Topics, SubPid) when is_list(Topics) and is_pid(SubPid) ->
|
||||||
%%
|
%%
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec unsubscribe(binary() | list(binary()), pid()) -> ok.
|
-spec unsubscribe(binary() | list(binary())) -> ok.
|
||||||
unsubscribe(Topic, SubPid) when is_binary(Topic) and is_pid(SubPid) ->
|
unsubscribe(Topic) when is_binary(Topic) ->
|
||||||
unsubscribe([Topic], SubPid);
|
unsubscribe([Topic]);
|
||||||
|
|
||||||
unsubscribe(Topics, SubPid) when is_list(Topics) and is_pid(SubPid) ->
|
unsubscribe(Topics = [Topic|_]) when is_list(Topics) and is_binary(Topic) ->
|
||||||
gen_server:cast(?SERVER, {unsubscribe, Topics, SubPid}).
|
unsubscribe(Topics, self()).
|
||||||
|
|
||||||
|
%%TODO: check this function later.
|
||||||
|
unsubscribe(Topics, SubPid) ->
|
||||||
|
F = fun() ->
|
||||||
|
Subscribers = mnesia:index_read(topic_subscriber, SubPid, #topic_subscriber.subpid),
|
||||||
|
lists:foreach(fun(Sub = #topic_subscriber{topic = Topic}) ->
|
||||||
|
case lists:member(Topic, Topics) of
|
||||||
|
true -> mneisa:delete_object(Sub);
|
||||||
|
false -> ok
|
||||||
|
end
|
||||||
|
end, Subscribers)
|
||||||
|
%TODO: try to remove topic??? if topic is dynamic...
|
||||||
|
%%try_remove_topic(Topic)
|
||||||
|
end,
|
||||||
|
{atomic, _} = mneisa:transaction(F), ok.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc
|
%% @doc
|
||||||
|
@ -164,7 +194,7 @@ publish(Topic, Msg) when is_binary(Topic) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec dispatch(Topic :: binary(), Msg :: mqtt_message()) -> non_neg_integer().
|
-spec dispatch(Topic :: binary(), Msg :: mqtt_message()) -> non_neg_integer().
|
||||||
dispatch(Topic, Msg = #mqtt_message{qos = Qos}) when is_binary(Topic) ->
|
dispatch(Topic, Msg = #mqtt_message{qos = Qos}) when is_binary(Topic) ->
|
||||||
Subscribers = ets:lookup(topic_subscriber, Topic),
|
Subscribers = mnesia:dirty_read(topic_subscriber, Topic),
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(#topic_subscriber{qos = SubQos, subpid=SubPid}) ->
|
fun(#topic_subscriber{qos = SubQos, subpid=SubPid}) ->
|
||||||
Msg1 = if
|
Msg1 = if
|
||||||
|
@ -193,72 +223,79 @@ match(Topic) when is_binary(Topic) ->
|
||||||
%% ------------------------------------------------------------------
|
%% ------------------------------------------------------------------
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
mnesia:create_table(topic_trie, [
|
%% trie and topic tables, will be copied by all nodes.
|
||||||
{ram_copies, [node()]},
|
|
||||||
{attributes, record_info(fields, topic_trie)}]),
|
|
||||||
mnesia:create_table(topic_trie_node, [
|
mnesia:create_table(topic_trie_node, [
|
||||||
{ram_copies, [node()]},
|
{ram_copies, [node()]},
|
||||||
{attributes, record_info(fields, topic_trie_node)}]),
|
{attributes, record_info(fields, topic_trie_node)}]),
|
||||||
|
mnesia:add_table_copy(topic_trie_node, node(), ram_copies),
|
||||||
|
mnesia:create_table(topic_trie, [
|
||||||
|
{ram_copies, [node()]},
|
||||||
|
{attributes, record_info(fields, topic_trie)}]),
|
||||||
|
mnesia:add_table_copy(topic_trie, node(), ram_copies),
|
||||||
mnesia:create_table(topic, [
|
mnesia:create_table(topic, [
|
||||||
{type, bag},
|
{type, bag},
|
||||||
{record_name, topic},
|
{record_name, topic},
|
||||||
{ram_copies, [node()]},
|
{ram_copies, [node()]},
|
||||||
{attributes, record_info(fields, topic)}]),
|
{attributes, record_info(fields, topic)}]),
|
||||||
mnesia:add_table_copy(topic_trie, node(), ram_copies),
|
|
||||||
mnesia:add_table_copy(topic_trie_node, node(), ram_copies),
|
|
||||||
mnesia:add_table_copy(topic, node(), ram_copies),
|
mnesia:add_table_copy(topic, node(), ram_copies),
|
||||||
ets:new(topic_subscriber, [bag, named_table, {keypos, 2}]),
|
mnesia:subscribe({table, topic, simple}),
|
||||||
|
%% local table, not shared with other table
|
||||||
|
mnesia:create_table(topic_subscriber, [
|
||||||
|
{type, bag},
|
||||||
|
{record_name, topic_subscriber},
|
||||||
|
{ram_copies, [node()]},
|
||||||
|
{attributes, record_info(fields, topic_subscriber)},
|
||||||
|
{index, [subpid]},
|
||||||
|
{local_content, true}]),
|
||||||
|
mnesia:subscribe({table, topic_subscriber, simple}),
|
||||||
{ok, #state{}}.
|
{ok, #state{}}.
|
||||||
|
|
||||||
handle_call(getstats, _From, State = #state{max_subs = Max}) ->
|
handle_call(getstats, _From, State = #state{max_subs = Max}) ->
|
||||||
Stats = [{'topics/count', mnesia:table_info(topic, size)},
|
Stats = [{'topics/count', mnesia:table_info(topic, size)},
|
||||||
{'subscribers/count', ets:info(topic_subscriber, size)},
|
{'subscribers/count', mnesia:info(topic_subscriber, size)},
|
||||||
{'subscribers/max', Max}],
|
{'subscribers/max', Max}],
|
||||||
{reply, Stats, State};
|
{reply, Stats, State};
|
||||||
|
|
||||||
handle_call({create, Topic}, _From, State) ->
|
|
||||||
Result = mnesia:transaction(fun trie_add/1, [Topic]),
|
|
||||||
{reply, Result, setstats(State)};
|
|
||||||
|
|
||||||
handle_call({subscribe, Topics, SubPid}, _From, State) ->
|
|
||||||
Result = [subscribe_topic({Topic, Qos}, SubPid) || {Topic, Qos} <- Topics],
|
|
||||||
Reply =
|
|
||||||
case [Err || Err = {error, _} <- Result] of
|
|
||||||
[] -> {ok, [Qos || {ok, Qos} <- Result]};
|
|
||||||
Errors -> hd(Errors)
|
|
||||||
end,
|
|
||||||
{reply, Reply, setstats(State)};
|
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
handle_call(Req, _From, State) ->
|
||||||
{stop, {badreq, Req}, State}.
|
lager:error("Bad Req: ~p", [Req]),
|
||||||
|
{reply, error, State}.
|
||||||
handle_cast({unsubscribe, Topics, SubPid}, State) ->
|
|
||||||
lists:foreach(fun(Topic) ->
|
|
||||||
ets:match_delete(topic_subscriber, #topic_subscriber{topic=Topic, qos ='_', subpid=SubPid}),
|
|
||||||
try_remove_topic(Topic)
|
|
||||||
end, Topics),
|
|
||||||
{noreply, setstats(State)};
|
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
handle_cast(Msg, State) ->
|
||||||
{stop, {badmsg, Msg}, State}.
|
lager:error("Bad Msg: ~p", [Msg]),
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({'DOWN', Mon, _Type, _Object, _Info}, State) ->
|
%% a new record has been written.
|
||||||
case get({submon, Mon}) of
|
handle_info({mnesia_table_event, {write, #topic_subscriber{subpid = Pid}, _ActivityId}}, State) ->
|
||||||
undefined ->
|
erlang:monitor(process, Pid),
|
||||||
lager:error("unexpected 'DOWN': ~p", [Mon]);
|
{noreply, setstats(State)};
|
||||||
SubPid ->
|
|
||||||
erase({submon, Mon}),
|
%% {write, #topic{}, _ActivityId}
|
||||||
erase({subscriber, SubPid}),
|
%% {delete_object, _OldRecord, _ActivityId}
|
||||||
Subs = ets:match_object(topic_subscriber, #topic_subscriber{subpid=SubPid, _='_'}),
|
%% {delete, {Tab, Key}, ActivityId}
|
||||||
[ets:delete_object(topic_subscriber, Sub) || Sub <- Subs],
|
handle_info({mnesia_table_event, _Event}, State) ->
|
||||||
[try_remove_topic(Topic) || #topic_subscriber{topic=Topic} <- Subs]
|
{noreply, setstats(State)};
|
||||||
|
|
||||||
|
handle_info({'DOWN', _Mon, _Type, DownPid, _Info}, State) ->
|
||||||
|
F = fun() ->
|
||||||
|
%%TODO: how to read with write lock?
|
||||||
|
[mnesia:delete_object(Sub) || Sub <- mnesia:index_read(topic_subscriber, DownPid, #topic_subscriber.subpid)]
|
||||||
|
%%TODO: try to remove dynamic topics without subscribers
|
||||||
|
%% [try_remove_topic(Topic) || #topic_subscriber{topic=Topic} <- Subs]
|
||||||
|
end,
|
||||||
|
case catch mnesia:transaction(F) of
|
||||||
|
{atomic, _} -> ok;
|
||||||
|
{aborted, Reason} -> lager:error("Failed to delete 'DOWN' subscriber ~p: ~p", [DownPid, Reason])
|
||||||
end,
|
end,
|
||||||
{noreply, setstats(State)};
|
{noreply, setstats(State)};
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
{stop, {badinfo, Info}, State}.
|
lager:error("Bad Info: ~p", [Info]),
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, _State) ->
|
terminate(_Reason, _State) ->
|
||||||
|
mnesia:unsubscribe({table, topic, simple}),
|
||||||
|
mnesia:unsubscribe({table, topic_subscriber, simple}),
|
||||||
|
%%TODO: clear topics belongs to this node???
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
@ -267,57 +304,22 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
subscribe_topic({Topic, Qos}, SubPid) ->
|
|
||||||
case mnesia:transaction(fun trie_add/1, [Topic]) of
|
|
||||||
{atomic, _} ->
|
|
||||||
case get({subscriber, SubPid}) of
|
|
||||||
undefined ->
|
|
||||||
%%TODO: refactor later...
|
|
||||||
MonRef = erlang:monitor(process, SubPid),
|
|
||||||
put({subcriber, SubPid}, MonRef),
|
|
||||||
put({submon, MonRef}, SubPid);
|
|
||||||
_ ->
|
|
||||||
already_monitored
|
|
||||||
end,
|
|
||||||
%% remove duplicated subscribers
|
|
||||||
try_remove_subscriber({Topic, Qos}, SubPid),
|
|
||||||
ets:insert(topic_subscriber, #topic_subscriber{topic=Topic, qos = Qos, subpid=SubPid}),
|
|
||||||
%TODO: GrantedQos??
|
|
||||||
{ok, Qos};
|
|
||||||
{aborted, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
try_remove_subscriber({Topic, Qos}, SubPid) ->
|
%%try_remove_topic(Name) when is_binary(Name) ->
|
||||||
case ets:lookup(topic_subscriber, Topic) of
|
%% case ets:member(topic_subscriber, Name) of
|
||||||
[] ->
|
%% false ->
|
||||||
not_found;
|
%% Topic = emqttd_topic:new(Name),
|
||||||
Subs ->
|
%% Fun = fun() ->
|
||||||
DupSubs = [Sub || Sub = #topic_subscriber{qos = OldQos, subpid = OldPid}
|
%% mnesia:delete_object(Topic),
|
||||||
<- Subs, Qos =/= OldQos, OldPid =:= SubPid],
|
%% case mnesia:read(topic, Name) of
|
||||||
case DupSubs of
|
%% [] -> trie_delete(Name);
|
||||||
[] -> ok;
|
%% _ -> ignore
|
||||||
[DupSub] ->
|
%% end
|
||||||
lager:warning("PubSub: remove duplicated subscriber ~p", [DupSub]),
|
%% end,
|
||||||
ets:delete(topic_subscriber, DupSub)
|
%% mnesia:transaction(Fun);
|
||||||
end
|
%% true ->
|
||||||
end.
|
%% ok
|
||||||
|
%% end.
|
||||||
try_remove_topic(Name) when is_binary(Name) ->
|
|
||||||
case ets:member(topic_subscriber, Name) of
|
|
||||||
false ->
|
|
||||||
Topic = emqttd_topic:new(Name),
|
|
||||||
Fun = fun() ->
|
|
||||||
mnesia:delete_object(Topic),
|
|
||||||
case mnesia:read(topic, Name) of
|
|
||||||
[] -> trie_delete(Name);
|
|
||||||
_ -> ignore
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
mnesia:transaction(Fun);
|
|
||||||
true ->
|
|
||||||
ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
trie_add(Topic) when is_binary(Topic) ->
|
trie_add(Topic) when is_binary(Topic) ->
|
||||||
mnesia:write(emqttd_topic:new(Topic)),
|
mnesia:write(emqttd_topic:new(Topic)),
|
||||||
|
@ -325,7 +327,7 @@ trie_add(Topic) when is_binary(Topic) ->
|
||||||
[TrieNode=#topic_trie_node{topic=undefined}] ->
|
[TrieNode=#topic_trie_node{topic=undefined}] ->
|
||||||
mnesia:write(TrieNode#topic_trie_node{topic=Topic});
|
mnesia:write(TrieNode#topic_trie_node{topic=Topic});
|
||||||
[#topic_trie_node{topic=Topic}] ->
|
[#topic_trie_node{topic=Topic}] ->
|
||||||
{atomic, already_exist};
|
ok;
|
||||||
[] ->
|
[] ->
|
||||||
%add trie path
|
%add trie path
|
||||||
[trie_add_path(Triple) || Triple <- emqttd_topic:triples(Topic)],
|
[trie_add_path(Triple) || Triple <- emqttd_topic:triples(Topic)],
|
||||||
|
@ -401,7 +403,7 @@ trie_delete_path([{NodeId, Word, _} | RestPath]) ->
|
||||||
|
|
||||||
setstats(State = #state{max_subs = Max}) ->
|
setstats(State = #state{max_subs = Max}) ->
|
||||||
emqttd_broker:setstat('topics/count', mnesia:table_info(topic, size)),
|
emqttd_broker:setstat('topics/count', mnesia:table_info(topic, size)),
|
||||||
SubCount = ets:info(topic_subscriber, size),
|
SubCount = mnesia:table_info(topic_subscriber, size),
|
||||||
emqttd_broker:setstat('subscribers/count', SubCount),
|
emqttd_broker:setstat('subscribers/count', SubCount),
|
||||||
if
|
if
|
||||||
SubCount > Max ->
|
SubCount > Max ->
|
||||||
|
|
|
@ -185,7 +185,7 @@ subscribe(SessState = #session_state{client_id = ClientId, submap = SubMap}, Top
|
||||||
_ -> lager:warning("~s resubscribe ~p", [ClientId, Resubs])
|
_ -> lager:warning("~s resubscribe ~p", [ClientId, Resubs])
|
||||||
end,
|
end,
|
||||||
SubMap1 = lists:foldl(fun({Name, Qos}, Acc) -> maps:put(Name, Qos, Acc) end, SubMap, Topics),
|
SubMap1 = lists:foldl(fun({Name, Qos}, Acc) -> maps:put(Name, Qos, Acc) end, SubMap, Topics),
|
||||||
{ok, GrantedQos} = emqttd_pubsub:subscribe(Topics, self()),
|
{ok, GrantedQos} = emqttd_pubsub:subscribe(Topics),
|
||||||
%%TODO: should be gen_event and notification...
|
%%TODO: should be gen_event and notification...
|
||||||
emqttd_server:subscribe([ Name || {Name, _} <- Topics ], self()),
|
emqttd_server:subscribe([ Name || {Name, _} <- Topics ], self()),
|
||||||
{ok, SessState#session_state{submap = SubMap1}, GrantedQos};
|
{ok, SessState#session_state{submap = SubMap1}, GrantedQos};
|
||||||
|
@ -208,7 +208,7 @@ unsubscribe(SessState = #session_state{client_id = ClientId, submap = SubMap}, T
|
||||||
BadUnsubs -> lager:warning("~s should not unsubscribe ~p", [ClientId, BadUnsubs])
|
BadUnsubs -> lager:warning("~s should not unsubscribe ~p", [ClientId, BadUnsubs])
|
||||||
end,
|
end,
|
||||||
%%unsubscribe from topic tree
|
%%unsubscribe from topic tree
|
||||||
ok = emqttd_pubsub:unsubscribe(Topics, self()),
|
ok = emqttd_pubsub:unsubscribe(Topics),
|
||||||
SubMap1 = lists:foldl(fun(Topic, Acc) -> maps:remove(Topic, Acc) end, SubMap, Topics),
|
SubMap1 = lists:foldl(fun(Topic, Acc) -> maps:remove(Topic, Acc) end, SubMap, Topics),
|
||||||
{ok, SessState#session_state{submap = SubMap1}};
|
{ok, SessState#session_state{submap = SubMap1}};
|
||||||
|
|
||||||
|
|
|
@ -149,8 +149,8 @@ case "$1" in
|
||||||
;;
|
;;
|
||||||
|
|
||||||
broker)
|
broker)
|
||||||
if [ $# -gt 2 ]; then
|
if [ $# -ne 1 ]; then
|
||||||
echo "Usage: $SCRIPT broker [status | stats | metrics]"
|
echo "Usage: $SCRIPT broker"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -165,6 +165,41 @@ case "$1" in
|
||||||
$NODETOOL rpc emqttd_ctl broker $@
|
$NODETOOL rpc emqttd_ctl broker $@
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
stats)
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
echo "Usage: $SCRIPT stats"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure the local node IS running
|
||||||
|
RES=`$NODETOOL ping`
|
||||||
|
if [ "$RES" != "pong" ]; then
|
||||||
|
echo "emqttd is not running!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
|
||||||
|
$NODETOOL rpc emqttd_ctl stats $@
|
||||||
|
;;
|
||||||
|
|
||||||
|
metrics)
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
echo "Usage: $SCRIPT metrics"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure the local node IS running
|
||||||
|
RES=`$NODETOOL ping`
|
||||||
|
if [ "$RES" != "pong" ]; then
|
||||||
|
echo "emqttd is not running!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
|
||||||
|
$NODETOOL rpc emqttd_ctl metrics $@
|
||||||
|
;;
|
||||||
|
|
||||||
|
|
||||||
bridges)
|
bridges)
|
||||||
# Make sure the local node IS running
|
# Make sure the local node IS running
|
||||||
RES=`$NODETOOL ping`
|
RES=`$NODETOOL ping`
|
||||||
|
@ -223,8 +258,10 @@ case "$1" in
|
||||||
|
|
||||||
*)
|
*)
|
||||||
echo "Usage: $SCRIPT"
|
echo "Usage: $SCRIPT"
|
||||||
echo " status #query status"
|
echo " status #query broker status"
|
||||||
echo " broker [stats | metrics] #query broker stats or metrics"
|
echo " broker #query broker version, uptime and description"
|
||||||
|
echo " stats #query broker statistics of clients, topics, subscribers"
|
||||||
|
echo " metrics #query broker metrics"
|
||||||
echo " cluster [<Node>] #query or cluster nodes"
|
echo " cluster [<Node>] #query or cluster nodes"
|
||||||
echo " plugins list #query loaded plugins"
|
echo " plugins list #query loaded plugins"
|
||||||
echo " plugins load <Plugin> #load plugin"
|
echo " plugins load <Plugin> #load plugin"
|
||||||
|
|
Loading…
Reference in New Issue