183 lines
5.5 KiB
Erlang
183 lines
5.5 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
%%--------------------------------------------------------------------
|
|
|
|
-module(emqx_dashboard_collection).
|
|
|
|
-behaviour(gen_server).
|
|
|
|
-include("emqx_dashboard.hrl").
|
|
-include_lib("stdlib/include/ms_transform.hrl").
|
|
|
|
-export([ start_link/0
|
|
]).
|
|
|
|
-export([ init/1
|
|
, handle_call/3
|
|
, handle_cast/2
|
|
, handle_info/2
|
|
, terminate/2
|
|
, code_change/3
|
|
]).
|
|
|
|
-export([get_collect/0]).
|
|
|
|
-export([get_local_time/0]).
|
|
|
|
-boot_mnesia({mnesia, [boot]}).
|
|
-copy_mnesia({mnesia, [copy]}).
|
|
|
|
%% Mnesia bootstrap
|
|
-export([mnesia/1]).
|
|
|
|
-define(APP, emqx_dashboard).
|
|
|
|
-define(DEFAULT_INTERVAL, 10). %% seconds
|
|
|
|
-define(COLLECT, {[],[],[]}).
|
|
|
|
-define(CLEAR_INTERVAL, 86400000).
|
|
|
|
-define(EXPIRE_INTERVAL, 86400000 * 7).
|
|
|
|
mnesia(boot) ->
|
|
ok = ekka_mnesia:create_table(emqx_collect, [
|
|
{type, set},
|
|
{local_content, true},
|
|
{disc_only_copies, [node()]},
|
|
{record_name, mqtt_collect},
|
|
{attributes, record_info(fields, mqtt_collect)}]);
|
|
mnesia(copy) ->
|
|
mnesia:add_table_copy(emqx_collect, node(), disc_only_copies).
|
|
|
|
start_link() ->
|
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
|
|
|
get_collect() -> gen_server:call(whereis(?MODULE), get_collect).
|
|
|
|
init([]) ->
|
|
timer(next_interval(), collect),
|
|
timer(get_today_remaining_seconds(), clear_expire_data),
|
|
ExpireInterval = emqx:get_config([emqx_dashboard, monitor, interval], ?EXPIRE_INTERVAL),
|
|
State = #{
|
|
count => count(),
|
|
expire_interval => ExpireInterval,
|
|
collect => ?COLLECT,
|
|
temp_collect => {0, 0, 0, 0},
|
|
last_collects => {0, 0, 0}
|
|
},
|
|
{ok, State}.
|
|
|
|
%% @doc every whole interval seconds;
|
|
%% example:
|
|
%% interval is 10s
|
|
%% now 15:01:07 (or 15:07:01 ~ 15:07:10)
|
|
%% next will be 15:01:10, 15:01:20, 15:01:30 ...
|
|
%% ensure all counters in cluster have sync time
|
|
next_interval() ->
|
|
(1000 * interval()) - (erlang:system_time(millisecond) rem (1000 * interval())) - 1.
|
|
|
|
interval() ->
|
|
emqx:get_config([?APP, sample_interval], ?DEFAULT_INTERVAL).
|
|
|
|
count() ->
|
|
60 div interval().
|
|
|
|
handle_call(get_collect, _From, State = #{temp_collect := {Received, Sent, _, _}}) ->
|
|
{reply, {Received, Sent, collect(subscriptions), collect(connections)}, State, hibernate};
|
|
handle_call(_Req, _From, State) ->
|
|
{reply, ok, State}.
|
|
handle_cast(_Req, State) ->
|
|
{noreply, State}.
|
|
|
|
handle_info(collect, State = #{collect := Collect, count := 1, temp_collect := TempCollect, last_collects := LastCollect}) ->
|
|
timer(next_interval(), collect),
|
|
NewLastCollect = flush(collect_all(Collect), LastCollect),
|
|
TempCollect1 = temp_collect(TempCollect),
|
|
{noreply, State#{count => count(),
|
|
collect => ?COLLECT,
|
|
temp_collect => TempCollect1,
|
|
last_collects => NewLastCollect}};
|
|
|
|
handle_info(collect, State = #{count := Count, collect := Collect, temp_collect := TempCollect}) ->
|
|
timer(next_interval(), collect),
|
|
TempCollect1 = temp_collect(TempCollect),
|
|
{noreply, State#{count => Count - 1,
|
|
collect => collect_all(Collect),
|
|
temp_collect => TempCollect1}, hibernate};
|
|
|
|
handle_info(clear_expire_data, State = #{expire_interval := ExpireInterval}) ->
|
|
timer(?CLEAR_INTERVAL, clear_expire_data),
|
|
T1 = get_local_time(),
|
|
Spec = ets:fun2ms(fun({_, T, _C} = Data) when (T1 - T) > ExpireInterval -> Data end),
|
|
Collects = dets:select(emqx_collect, Spec),
|
|
lists:foreach(fun(Collect) ->
|
|
dets:delete_object(emqx_collect, Collect)
|
|
end, Collects),
|
|
{noreply, State, hibernate};
|
|
|
|
handle_info(_Info, State) ->
|
|
{noreply, State}.
|
|
|
|
terminate(_Reason, _State) ->
|
|
ok.
|
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
{ok, State}.
|
|
|
|
temp_collect({_, _, Received, Sent}) ->
|
|
Received1 = collect(received),
|
|
Sent1 = collect(sent),
|
|
{(Received1 - Received) div interval(),
|
|
(Sent1 - Sent) div interval(),
|
|
Received1,
|
|
Sent1}.
|
|
|
|
collect_all({Connection, Route, Subscription}) ->
|
|
{[collect(connections)| Connection],
|
|
[collect(routes)| Route],
|
|
[collect(subscriptions)| Subscription]}.
|
|
|
|
collect(connections) ->
|
|
emqx_stats:getstat('connections.count');
|
|
collect(routes) ->
|
|
emqx_stats:getstat('routes.count');
|
|
collect(subscriptions) ->
|
|
emqx_stats:getstat('subscriptions.count');
|
|
collect(received) ->
|
|
emqx_metrics:val('messages.received');
|
|
collect(sent) ->
|
|
emqx_metrics:val('messages.sent');
|
|
collect(dropped) ->
|
|
emqx_metrics:val('messages.dropped').
|
|
|
|
flush({Connection, Route, Subscription}, {Received0, Sent0, Dropped0}) ->
|
|
Received = collect(received),
|
|
Sent = collect(sent),
|
|
Dropped = collect(dropped),
|
|
Collect = {avg(Connection),
|
|
avg(Route),
|
|
avg(Subscription),
|
|
diff(Received, Received0),
|
|
diff(Sent, Sent0),
|
|
diff(Dropped, Dropped0)},
|
|
Ts = get_local_time(),
|
|
_ = mnesia:dirty_write(emqx_collect, #mqtt_collect{timestamp = Ts, collect = Collect}),
|
|
{Received, Sent, Dropped}.
|
|
|
|
avg(Items) ->
|
|
lists:sum(Items) div count().
|
|
|
|
diff(Item0, Item1) ->
|
|
Item0 - Item1.
|
|
|
|
timer(Secs, Msg) ->
|
|
erlang:send_after(Secs, self(), Msg).
|
|
|
|
get_today_remaining_seconds() ->
|
|
?CLEAR_INTERVAL - (get_local_time() rem ?CLEAR_INTERVAL).
|
|
|
|
get_local_time() ->
|
|
(calendar:datetime_to_gregorian_seconds(calendar:local_time()) -
|
|
calendar:datetime_to_gregorian_seconds({{1970,1,1}, {0,0,0}})).
|