%% @doc %% This module implements the statitics counter for RADIUS servers and clients -module(eradius_counter). -export([init_counter/1, inc_counter/2, reset_counter/1, inc_request_counter/2, inc_reply_counter/2]). -behaviour(gen_server). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export([start_link/0, reset/0, pull/0, read/0, aggregate/1]). -export([collect/2]). -include("eradius_lib.hrl"). -record(state, { reset :: erlang:timestamp() }). -type srv_counters() :: [#server_counter{}]. -type nas_counters() :: {erlang:timestamp(), [#nas_counter{}]}. -type stats() :: {srv_counters(), nas_counters()}. %% ------------------------------------------------------------------------------------------ %% API %% @doc initialize a counter structure init_counter(Key = {_ServerIP, ServerPort}) when is_integer(ServerPort) -> #server_counter{key = Key, startTime = eradius_lib:timestamp(), resetTime = eradius_lib:timestamp()}; init_counter(#nas_prop{server_ip = ServerIP, server_port = ServerPort, nas_ip = NasIP}) -> #nas_counter{key = {{ServerIP, ServerPort}, NasIP}}; init_counter({{ServerIP, ServerPort}, NasIP}) when is_tuple(ServerIP), is_integer(ServerPort), is_tuple(NasIP) -> #nas_counter{key = {{ServerIP, ServerPort}, NasIP}}. %% @doc reset counters reset_counter(#server_counter{startTime = Up}) -> #server_counter{startTime = Up, resetTime = eradius_lib:timestamp()}; reset_counter(Nas = #nas_prop{}) -> init_counter(Nas). %% @doc increment requests counters inc_request_counter(Counter, Nas) -> inc_counter(requests, Nas), inc_counter(Counter, Nas). %% @doc increment reply counters inc_reply_counter(Counter, Nas) -> inc_counter(replies, Nas), inc_counter(Counter, Nas). %% @doc increment a specific counter value inc_counter(invalidRequests, Counters = #server_counter{invalidRequests = Value}) -> Counters#server_counter{invalidRequests = Value + 1}; inc_counter(discardNoHandler, Counters = #server_counter{discardNoHandler = Value}) -> Counters#server_counter{discardNoHandler = Value + 1}; inc_counter(Counter, Nas = #nas_prop{}) -> gen_server:cast(?MODULE, {inc_counter, Counter, Nas}). %% @doc reset all counters to zero reset() -> gen_server:call(?MODULE, reset). %% @doc read counters and reset to zero -spec pull() -> stats(). pull() -> gen_server:call(?MODULE, pull). %% @doc read counters -spec read() -> stats(). read() -> gen_server:call(?MODULE, read). %% @doc calculate the per server sum of all counters of a per NAS list of counters -spec aggregate(stats()) -> stats(). aggregate({Servers, {ResetTS, Nass}}) -> NSums = lists:foldl(fun(Nas = #nas_counter{key = {ServerId, _}}, Acc) -> orddict:update(ServerId, fun(Value) -> add_counter(Value, Nas) end, Nas#nas_counter{key = ServerId}, Acc) end, orddict:new(), Nass), NSum1 = [Value || {_Key, Value} <- orddict:to_list(NSums)], {Servers, {ResetTS, NSum1}}. %% helper to be called from the aggregator to fetch this nodes values %% @private collect(Ref, Process) -> lists:foreach(fun(Node) -> gen_server:cast({?MODULE, Node}, {collect, Ref, Process}) end, eradius_node_mon:get_module_nodes(?MODULE)). %% @private -spec start_link() -> {ok, pid()} | {error, term()}. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). %% ------------------------------------------------------------------------------------------ %% -- gen_server Callbacks %% @private init([]) -> ets:new(?MODULE, [ordered_set, protected, named_table, {keypos, #nas_counter.key}, {write_concurrency,true}]), eradius:modules_ready([?MODULE]), {ok, #state{reset = eradius_lib:timestamp()}}. %% @private handle_call(pull, _From, State) -> Nass = read_stats(State), Servers = server_stats(pull), ets:delete_all_objects(?MODULE), {reply, {Servers, Nass}, State#state{reset = eradius_lib:timestamp()}}; handle_call(read, _From, State) -> Nass = read_stats(State), Servers = server_stats(read), {reply, {Servers, Nass}, State}; handle_call(reset, _From, State) -> server_stats(reset), ets:delete_all_objects(?MODULE), {reply, ok, State#state{reset = eradius_lib:timestamp()}}. %% @private handle_cast({inc_counter, Counter, Nas = #nas_prop{server_ip = ServerIP, server_port = ServerPort, nas_ip = NasIP}}, State) -> Key = {{ServerIP, ServerPort}, NasIP}, Cnt0 = case ets:lookup(?MODULE, Key) of [] -> init_counter(Nas); [Cnt] -> Cnt end, ets:insert(?MODULE, do_inc_counter(Counter, Cnt0)), {noreply, State}; handle_cast({collect, Ref, Process}, State) -> Process ! {collect, Ref, ets:tab2list(?MODULE)}, ets:delete_all_objects(?MODULE), {noreply, State#state{reset = eradius_lib:timestamp()}}; handle_cast(_Msg, State) -> {noreply, State}. %% -- unused callbacks %% @private handle_info(_Info, State) -> {noreply, State}. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. %% @private terminate(_Reason, _State) -> ok. %% ------------------------------------------------------------------------------------------ %% -- helper functions %% @private read_stats(State) -> {State#state.reset, ets:tab2list(?MODULE)}. server_stats(Func) -> lists:foldl(fun(S, Acc) -> [eradius_server:stats(S, Func)|Acc] end, [], eradius_server_sup:all()). %% @private do_inc_counter(requests, Counters = #nas_counter{requests = Value}) -> Counters#nas_counter{requests = Value + 1}; do_inc_counter(replies, Counters = #nas_counter{replies = Value}) -> Counters#nas_counter{replies = Value + 1}; do_inc_counter(dupRequests, Counters = #nas_counter{dupRequests = Value}) -> Counters#nas_counter{dupRequests = Value + 1}; do_inc_counter(malformedRequests, Counters = #nas_counter{malformedRequests = Value}) -> Counters#nas_counter{malformedRequests = Value + 1}; do_inc_counter(accessRequests, Counters = #nas_counter{accessRequests = Value}) -> Counters#nas_counter{accessRequests = Value + 1}; do_inc_counter(accessAccepts, Counters = #nas_counter{accessAccepts = Value}) -> Counters#nas_counter{accessAccepts = Value + 1}; do_inc_counter(accessRejects, Counters = #nas_counter{accessRejects = Value}) -> Counters#nas_counter{accessRejects = Value + 1}; do_inc_counter(accessChallenges, Counters = #nas_counter{accessChallenges = Value}) -> Counters#nas_counter{accessChallenges = Value + 1}; do_inc_counter(accountRequests, Counters = #nas_counter{accountRequests = Value}) -> Counters#nas_counter{accountRequests = Value + 1}; do_inc_counter(accountResponses, Counters = #nas_counter{accountResponses = Value}) -> Counters#nas_counter{accountResponses = Value + 1}; do_inc_counter(noRecords, Counters = #nas_counter{noRecords = Value}) -> Counters#nas_counter{noRecords = Value + 1}; do_inc_counter(badAuthenticators, Counters = #nas_counter{badAuthenticators = Value}) -> Counters#nas_counter{badAuthenticators = Value + 1}; do_inc_counter(packetsDropped, Counters = #nas_counter{packetsDropped = Value}) -> Counters#nas_counter{packetsDropped = Value + 1}; do_inc_counter(unknownTypes, Counters = #nas_counter{unknownTypes = Value}) -> Counters#nas_counter{unknownTypes = Value + 1}; do_inc_counter(handlerFailure, Counters = #nas_counter{handlerFailure = Value}) -> Counters#nas_counter{handlerFailure = Value + 1}; do_inc_counter(coaRequests, Counters = #nas_counter{coaRequests = Value}) -> Counters#nas_counter{coaRequests = Value + 1}; do_inc_counter(coaAcks, Counters = #nas_counter{coaAcks = Value}) -> Counters#nas_counter{coaAcks = Value + 1}; do_inc_counter(coaNaks, Counters = #nas_counter{coaNaks = Value}) -> Counters#nas_counter{coaNaks = Value + 1}; do_inc_counter(discRequests, Counters = #nas_counter{discRequests = Value}) -> Counters#nas_counter{discRequests = Value + 1}; do_inc_counter(discAcks, Counters = #nas_counter{discAcks = Value}) -> Counters#nas_counter{discAcks = Value + 1}; do_inc_counter(discNaks, Counters = #nas_counter{discNaks = Value}) -> Counters#nas_counter{discNaks = Value + 1}. add_counter(Cnt1 = #nas_counter{}, Cnt2 = #nas_counter{}) -> #nas_counter{ key = Cnt1#nas_counter.key, requests = Cnt1#nas_counter.requests + Cnt2#nas_counter.requests, replies = Cnt1#nas_counter.replies + Cnt2#nas_counter.replies, dupRequests = Cnt1#nas_counter.dupRequests + Cnt2#nas_counter.dupRequests, malformedRequests = Cnt1#nas_counter.malformedRequests + Cnt2#nas_counter.malformedRequests, accessRequests = Cnt1#nas_counter.accessRequests + Cnt2#nas_counter.accessRequests, accessAccepts = Cnt1#nas_counter.accessAccepts + Cnt2#nas_counter.accessAccepts, accessRejects = Cnt1#nas_counter.accessRejects + Cnt2#nas_counter.accessRejects, accessChallenges = Cnt1#nas_counter.accessChallenges + Cnt2#nas_counter.accessChallenges, accountRequests = Cnt1#nas_counter.accountRequests + Cnt2#nas_counter.accountRequests, accountResponses = Cnt1#nas_counter.accountResponses + Cnt2#nas_counter.accountResponses, noRecords = Cnt1#nas_counter.noRecords + Cnt2#nas_counter.noRecords, badAuthenticators = Cnt1#nas_counter.badAuthenticators + Cnt2#nas_counter.badAuthenticators, packetsDropped = Cnt1#nas_counter.packetsDropped + Cnt2#nas_counter.packetsDropped, unknownTypes = Cnt1#nas_counter.unknownTypes + Cnt2#nas_counter.unknownTypes, handlerFailure = Cnt1#nas_counter.handlerFailure + Cnt2#nas_counter.handlerFailure, coaRequests = Cnt1#nas_counter.coaRequests + Cnt2#nas_counter.coaRequests, coaAcks = Cnt1#nas_counter.coaAcks + Cnt2#nas_counter.coaAcks, coaNaks = Cnt1#nas_counter.coaNaks + Cnt2#nas_counter.coaNaks, discRequests = Cnt1#nas_counter.discRequests + Cnt2#nas_counter.discRequests, discAcks = Cnt1#nas_counter.discAcks + Cnt2#nas_counter.discAcks, discNaks = Cnt1#nas_counter.discNaks + Cnt2#nas_counter.discNaks }.