-module(market_subscriber_statistic).
-behaviour(gen_server).
%% API
-export([start_link/1,
on_connected/1,
on_disconnected/1,
on_subscribe/2,
on_unsubscribe/2]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(SERVER, ?MODULE).
-include("access.hrl").
-include_lib("amqp_client/include/amqp_client.hrl").
-record(state, {client_count,
instrument_count,
name,
timer,
channel,
node_id,
exchange,
params,
routing_key,
today
}).
-record(md_statistic, {date, node_id, hour, client_count, instrument_count}).
%%%===================================================================
%%% API
%%%===================================================================
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
%% @end
%%--------------------------------------------------------------------
start_link(Params) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [Params], []).
on_connected(_Access) ->
{_, {Hour, _, _}} = erlang:localtime(),
gen_server:cast(?SERVER, {connected, Hour}).
on_disconnected(_Access) ->
{_, {Hour, _, _}} = erlang:localtime(),
gen_server:cast(?SERVER, {disconnected, Hour}).
on_subscribe(_Access, SubCount) ->
{_, {Hour, _, _}} = erlang:localtime(),
gen_server:cast(?SERVER, {subscribe, Hour, SubCount}).
on_unsubscribe(_Access, SubCount) ->
{_, {Hour, _, _}} = erlang:localtime(),
gen_server:cast(?SERVER, {unsubscribe, Hour, SubCount}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
init([Params]) ->
Name = config_val(name, Params, ?MODULE),
Exchange = config_val(exchange, Params, list_to_binary(atom_to_list(?MODULE))),
RoutingKey = config_val(routing_key, Params, undefined),
{ok, NodeId} = application:get_env(market_dispatcher, auth_id),
AmqpParams = #amqp_params_network {
username = config_val(amqp_user, Params, <<"guest">>),
password = config_val(amqp_pass, Params, <<"guest">>),
virtual_host = config_val(amqp_vhost, Params, <<"/">>),
host = config_val(amqp_host, Params, "127.0.0.1"),
port = config_val(amqp_port, Params, 5672)
},
Channel = case amqp_channel(AmqpParams) of
{ok, Channel0} ->
lager:info("Amqp Channel start!"),
#'exchange.declare_ok'{} = amqp_channel:call(Channel0, #'exchange.declare'{exchange = Exchange,
type = <<"topic">> }),
Channel0;
{error, Reason} ->
lager:error("Amqp channel start error ~p", [Reason]),
undefined
end,
{ok, Timer} = set_timer(),
{ok, #state{ client_count = 0,
instrument_count = 0,
timer = Timer,
name = Name,
channel = Channel,
node_id = NodeId,
exchange = Exchange,
params = AmqpParams,
routing_key = RoutingKey
}}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @spec handle_call(Request, From, State) ->
%% {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
lager:warning("Can't handle request: ~p", [_Request]),
{reply, {error, invalid_req}, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @spec handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_cast(_Msg, #state{node_id = undefined} = State) ->
lager:error("Can't handle Msg because auth_id is undefined"),
{noreply, State};
handle_cast({connected, Hour}, #state{} = State) ->
#state{client_count = ClientCount, instrument_count = InstrumentCount,
node_id = NodeId, channel = Channel} = State,
send(State, [NodeId, Hour, ClientCount + 1, InstrumentCount], Channel),
{noreply, State#state{client_count = ClientCount + 1, instrument_count = InstrumentCount}};
handle_cast({disconnected, Hour}, #state{} = State) ->
#state{client_count = ClientCount, instrument_count = InstrumentCount,
node_id = NodeId, channel = Channel} = State,
send(State, [NodeId, Hour, ClientCount - 1, InstrumentCount], Channel),
{noreply, State#state{client_count = ClientCount - 1, instrument_count = InstrumentCount}};
handle_cast({subscribe, Hour, Subscribers}, #state{} = State) ->
#state{client_count = ClientCount, instrument_count = InstrumentCount,
node_id = NodeId, channel = Channel} = State,
send(State, [NodeId, Hour, ClientCount, InstrumentCount + Subscribers], Channel),
{noreply, State#state{client_count = ClientCount, instrument_count = InstrumentCount + Subscribers}};
handle_cast({unsubscribe, Hour, Subscribers}, #state{} = State) ->
#state{client_count = ClientCount, instrument_count = InstrumentCount,
node_id = NodeId, channel = Channel} = State,
send(State, [NodeId, Hour, ClientCount, InstrumentCount - Subscribers], Channel),
{noreply, State#state{client_count = ClientCount, instrument_count = InstrumentCount - Subscribers}};
handle_cast(_Msg, State) ->
lager:warning("Can't handle msg: ~p", [_Msg]),
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
% handle_info(write_to_mnesia, #state{} = State) ->
% #state{timer = Timer, node_id = NodeId, today = Today,
% md_statistic = MDStatistic} = State,
% {ok, cancel} = timer:cancel(Timer),
% {Date, {Hour, _, _}} = erlang:localtime(),
% EndTime = (calendar:datetime_to_gregorian_seconds({Date, {Hour + 1, 0, 0}}) -
% calendar:datetime_to_gregorian_seconds(erlang:localtime())) * 1000,
% {ok, Timer2} = timer:send_after(EndTime, write_to_mnesia),
% MDStatRecord = case orddict:find({NodeId, Hour - 1}, MDStatistic) of
% error ->
% #md_statistic{date = Today, node_id = NodeId, hour = Hour - 1, client_count = 0, instrument_count = 0};
% {ok, {ClientCount, InstrumentCount}} ->
% #md_statistic{date = Today, node_id = NodeId, hour = Hour - 1, client_count = ClientCount,
% instrument_count = InstrumentCount}
% end,
% mnesia:dirty_write(md_statistic, MDStatRecord),
% {noreply, State#state{timer = Timer2, today = date_util:today_to_epoch()}};
handle_info(send_data, State) ->
#state{client_count = ClientCount, instrument_count = InstrumentCount,
node_id = NodeId, channel = Channel} = State,
set_timer(),
{_, {Hour, _, _}} = erlang:localtime(),
send(State, [NodeId, Hour, ClientCount, InstrumentCount], Channel),
{noreply, State};
handle_info(_Info, State) ->
lager:warning("Can't handle info: ~p", [_Info]),
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
send(#state{ name = Name, exchange = Exchange, routing_key = RK } = _State, Message, Channel) ->
RoutingKey = case RK of
undefined -> routing_key(node(), Name);
_ -> RK
end,
Publish = #'basic.publish'{ exchange = Exchange, routing_key = RoutingKey },
Props = #'P_basic'{ content_type = <<"application/json">>},
Body = jsx:term_to_json(Message),
Msg = #amqp_msg{ payload = Body, props = Props },
case Channel of
undefined ->
undefined;
_ ->
amqp_channel:cast(Channel, Publish, Msg)
end.
set_timer() ->
{_Date, {_Hour, Min, Sec}} = erlang:localtime(),
EndTime = (3600 - ((Min * 60) + Sec)) * 1000,
timer:send_after(EndTime, send_data).
routing_key(Node, Name) ->
RoutingKey = case Name of
[] -> Node;
Name -> string:join([Node, Name], ".")
end,
list_to_binary(RoutingKey).
amqp_channel(AmqpParams) ->
case maybe_new_pid({AmqpParams, connection},
fun() -> amqp_connection:start(AmqpParams) end) of
{ok, Client} ->
maybe_new_pid({AmqpParams, channel},
fun() -> amqp_connection:open_channel(Client) end);
Error ->
Error
end.
maybe_new_pid(Group, StartFun) ->
case pg2:get_closest_pid(Group) of
{error, {no_such_group, _}} ->
pg2:create(Group),
maybe_new_pid(Group, StartFun);
{error, {no_process, _}} ->
case StartFun() of
{ok, Pid} ->
pg2:join(Group, Pid),
{ok, Pid};
Error ->
Error
end;
Pid ->
{ok, Pid}
end.
config_val(C, Params, Default) ->
case lists:keyfind(C, 1, Params) of
{C, V} -> V;
_ -> Default
end.
向rabbitmq发送消息
最新推荐文章于 2024-03-13 11:53:02 发布