diff --git a/include/common.hrl b/include/common.hrl index 2290ebb..ca6d8b3 100644 --- a/include/common.hrl +++ b/include/common.hrl @@ -17,7 +17,7 @@ -ifdef(debug). -define(DBGTRC(Msg), io:format("~s:~s (~p) => ~s~n",[?MODULE,?FUNCTION_NAME,?LINE,Msg])). -define(DBGSTR(Msg), io_lib:format("~s:~s (~B) => ~s~n",[?MODULE,?FUNCTION_NAME,?LINE,Msg])). - -define(DBGSTR(FMsg,Args), io_lib:format("~s:~s (~B) => " FMsg "~n",[?MODULE,?FUNCTION_NAME,?LINE] ++ [Args])). + -define(DBGSTR(FMsg,Args), io_lib:format("~s:~s (~B) => " FMsg "~n",[?MODULE,?FUNCTION_NAME,?LINE] ++ Args)). -else. -define(DBGTRC(Msg), true). -define(DBGSTR(Msg), Msg). diff --git a/src/ovsdb_ap.erl b/src/ovsdb_ap.erl index 60ab1a4..9f8c31b 100644 --- a/src/ovsdb_ap.erl +++ b/src/ovsdb_ap.erl @@ -29,7 +29,7 @@ %% API --export([start/2]). +-export([start_link/3]). -export([uuid/1]). @@ -40,14 +40,17 @@ %% data structures +-type ap_status() :: init | ready | running | paused | stopped. +-export_type([ap_status/0]). + -record(ap_state, { sim_node :: pid(), % controlling simnode sim_manager :: pid(), % manager to be contacted to get configuration - serial :: string(), % serial number of the access point - type :: binary(), % device type e.g. EA8300 - uuid :: term(), % unique ID for this AP node + serial = "" :: string(), % serial number of the access point + type = <<"">> :: binary(), % device type e.g. EA8300 + uuid :: string(), % unique ID for this AP node timer :: owls_timers:tms(), - status = init :: init | ready | running | stopped, % internal status + status = init :: ap_status(), % internal status config = #{} :: map(), statistics = #{} :: map() }). @@ -60,14 +63,15 @@ %%%============================================================================ --spec start (Handler, Spec) -> {ok, Pid} | {error, Reason} when +-spec start_link (Handler, Id, SimMan) -> {ok, Pid} | {error, Reason} when Handler :: pid(), - Spec :: proplists:proplist(), + Id :: UUID::string(), + SimMan :: pid(), Pid :: pid(), Reason :: term(). -start (Handler, Spec) -> - gen_server:start(?MODULE, {Handler, Spec}, []). +start_link (Handler, Id, SimMan) -> + gen_server:start_link(?MODULE, {Handler, Id, SimMan}, []). @@ -86,14 +90,15 @@ uuid (Node) -> %%% GEN_SERVER callbacks %%%============================================================================ --spec init ({Handler, Spec}) -> {ok, State} when +-spec init ({Handler, Id, SimMan}) -> {ok, State} when Handler :: pid(), - Spec :: proplists:proplist(), + Id :: UUID::string(), + SimMan :: pid(), State :: #ap_state{}. -init ({Handler,Spec}) -> +init ({Handler,Id,SimMan}) -> process_flag(trap_exit, true), - InitialState = prepare_state(Handler,Spec), + InitialState = prepare_state(Handler,Id,SimMan), gen_server:cast(self(),start_up), {ok, InitialState}. @@ -181,20 +186,19 @@ code_change (_,OldState,_) -> %%%============================================================================ -%---------prepare_state/2----------------convert Spec proplist into internal state +%---------prepare_state/3----------------convert Spec proplist into internal state --spec prepare_state (Node, Spec) -> State when - Node :: pid(), - Spec :: proplists:proplist(), +-spec prepare_state (Handler, ID, SimMan) -> State when + Handler :: pid(), + ID :: UUID::string(), + SimMan :: pid(), State :: #ap_state{}. -prepare_state (Node,Spec) -> +prepare_state (Handler, ID, SimMan) -> #ap_state{ - sim_node = Node, - sim_manager = proplists:get_value(manager,Spec,Node), - serial = proplists:get_value(ap_serial,Spec,"1P000000000"), - type = proplists:get_value(ap_type,Spec,<<"EA8300">>), - uuid = proplists:get_value(uuid,Spec,erlang:phash2({node(),erlang:system_time()})), + sim_node = Handler, + sim_manager = SimMan, + uuid = ID, timer = owls_timers:new(millisecond) }. diff --git a/src/ovsdb_client_handler.erl b/src/ovsdb_client_handler.erl index 5fdfaa3..c3da4b2 100644 --- a/src/ovsdb_client_handler.erl +++ b/src/ovsdb_client_handler.erl @@ -12,7 +12,10 @@ -behaviour(gen_server). -behaviour(gen_sim_client). +-include("../include/common.hrl"). + -define(SERVER, ?MODULE). +-define(MAX_STARTUP_TIME,10000). %% API -export([start_link/0]). @@ -25,14 +28,22 @@ %% data structures --type client_ref() :: {UUID::string(), available | dead | pid()}. +-type client_states() :: #{UUID::string() := available | ovsdb_ap:ap_status()}. +-type client_refs() :: #{pid() := UUID::string()}. --record(hdl_state, { - clients_avail = [] :: [client_ref()], - clients_started = [] :: [client_ref()], - clients_running = [] :: [client_ref()], - clients_paused = [] :: [client_ref()], - config = #{} :: #{} +-record (command,{ + cmd :: start | stop | pause | cancel, + refs :: [UUID::string()] +}). + +-type command() :: #command{}. + +-record (hdl_state, { + client_states = #{} :: client_states(), + client_pids = #{} :: client_refs(), + cmd_queue = [] :: [command()], + config = #{} :: #{}, + timer :: owls_timers:tms() }). @@ -65,7 +76,7 @@ set_configuration (Cfg) -> Reason :: term(). start (What) -> - gen_server:call(?SERVER,{start_sim, What}). + gen_server:call(?SERVER,{cmd_start, What}). @@ -124,7 +135,7 @@ report () -> init (_) -> process_flag(trap_exit, true), - {ok, #hdl_state{}}. + {ok, #hdl_state{timer=owls_timers:new(millisecond)}}. @@ -135,6 +146,9 @@ init (_) -> NewState :: #hdl_state{}, Reason :: term(). +handle_cast (execute, State) -> + {noreply, execute_cmd(State)}; + handle_cast (_,State) -> {noreply, State}. @@ -150,16 +164,10 @@ handle_cast (_,State) -> NewState :: #hdl_state{}. handle_call ({set_config, Cfg},_,State) -> - NewState = apply_config(State,Cfg), - {reply, ok, NewState}; + {reply, ok, apply_config(State,Cfg)}; -handle_call ({start_sim, How},_,State) -> - case start_simulation(State,How) of - {ok, NewState} -> - {reply, ok, NewState}; - Error -> - {reply, Error, State} - end; +handle_call ({cmd_start, How},_,State) -> + {reply, ok, cmd_startup_sim(State,How)}; handle_call (_, _, State) -> {reply, invalid, State}. @@ -212,24 +220,144 @@ code_change (_,OldState,_) -> NewState :: #hdl_state{}. apply_config (State, _Cfg) -> - State#hdl_state{clients_avail=[{"a68d41fa-dd12-4fb7-bc44-e834667280b4",available}]}. + State#hdl_state{client_states=#{"a68d41fa-dd12-4fb7-bc44-e834667280b4" => available}}. -%--------start_simulation/2--------------start a simulation of designated clients +%--------get_clients_in_state/3----------filter all clients with state --spec start_simulation (State, How) -> {ok, NewState} | {error, Reason} when +-spec get_clients_in_state (State, Clients, Candidates) -> Available when + State :: available | ovsdb_ap:ap_status(), + Clients :: client_states(), + Candidates :: all | [UUID::string()], + Available :: [UUID::string()]. + +get_clients_in_state (State, Clients, all) -> + [K || {K,V} <- maps:to_list(Clients), (V=:=State)]; + +get_clients_in_state (State, Clients, Candidates) -> + [K || {K,V} <- maps:to_list(Clients), (V=:=State) and lists:member(K,Candidates)]. + + + +%%----------------------------------------------------------------------------- +%% command queue handling + + +%--------execute_cmd/1-------------------executes the first command in queue + +-spec execute_cmd (State) -> NewState when + State :: #hdl_state{}, + NewState :: #hdl_state{}. + +execute_cmd (#hdl_state{cmd_queue=[]}=State) -> + State; + +execute_cmd (#hdl_state{cmd_queue=Q}=State) -> + [Cmd|RemCmds] = lists:reverse(Q), + AltState = State#hdl_state{cmd_queue=lists:reverse(RemCmds)}, + case Cmd of + #command{cmd=start, refs=R} -> + cmd_start_clients(R, AltState); + + #command{cmd=Ukn} -> + ?L_E(?DBGSTR("Unknown command '~s' in queue",[Ukn])), + AltState + end. + + + + +%--------cmd_startup_sim/2--------------lauches the start-up sequence of simulation clients + +-spec cmd_startup_sim (State, How) -> NewState when State :: #hdl_state{}, How :: all | [UUID::string()], - NewState :: #hdl_state{}, - Reason :: string(). + NewState :: #hdl_state{}. -start_simulation (#hdl_state{clients_avail=[]}, _) -> - {error, "no clients available"}; +cmd_startup_sim (State, How) -> + NewState = #hdl_state{timer=owls_timers:mark("startup",State#hdl_state.timer)}, + ToLaunch = get_clients_in_state (available,State#hdl_state.client_states, How), + ToStart = get_clients_in_state (ready,State#hdl_state.client_states, How), + if + length(ToLaunch) + length(ToStart) > 0 -> + gen_server:cast(self(),execute), + Cmds = [#command{cmd=start,refs=ToStart ++ ToLaunch} | NewState#hdl_state.cmd_queue], + cmd_lauch(ToLaunch,NewState#hdl_state{cmd_queue=Cmds}); + true -> + ?L_E(?DBGSTR("start command issued with no clients available or ready to start")), + NewState + end. -start_simulation (#hdl_state{clients_avail=Cl} = State, all) -> - start_simulation(State,Cl); -start_simulation (State, _Clients) -> - {ok, State}. + + +%--------cmd_launch/2--------------------lauch processes for clients (synchrounsly) + +-spec cmd_lauch (ToLauch,State) -> NewState when + ToLauch :: [UUID::string()], + State :: #hdl_state{}, + NewState :: #hdl_state{}. + +cmd_lauch (ToLauch, State) -> + %% @TODO: pass in the real manager + L = [ {V, ovsdb_ap:start_link(self(),V,self())} || V <- ToLauch], + C = maps:merge(State#hdl_state.client_states,maps:from_list([{V,init} || {V, {ok, _}} <- L])), + P = maps:from_list([{Pid,Uuid}||{Uuid,Pid}<-[{V,P} || {V, {ok, P}} <- L]]), + T = owls_timers:mark("launched",State#hdl_state.timer), + State#hdl_state{client_states=C,client_pids=P,timer=T}. + + + + +%--------cmd_start_clients/2-------------starts the simulation of the cliens (in ready state) + +-spec cmd_start_clients (Refs, State) -> NewState when + Refs :: [UUID::string()], + State :: #hdl_state{}, + NewState :: #hdl_state{}. + +cmd_start_clients (Refs, #hdl_state{timer=T,cmd_queue=Q}=State) -> + {Elapsed, T2} = get_elapsed("start_sim",T), + if + Elapsed > ?MAX_STARTUP_TIME * length(Refs) -> + ?L_E(?DBGSTR("Getting ~B clients ready took too long (~s)",[length(Refs),owls_timers:fmt_duration(Elapsed,T)])), + ?L_I(?DBGSTR("Cancelling simulation start request")), + Cmds = lists:reverse([#command{cmd=cancel, refs=Refs}|lists:reverse(Q)]), + gen_server:cast(self(),execute), + State#hdl_state{cmd_queue=Cmds, timer=T2}; + true -> + Ready = get_clients_in_state (ready,State#hdl_state.client_states,Refs), + check_client_startup(Ready,Refs,State#hdl_state{timer=T2}) + end. + + + + +-spec check_client_startup (Ready, ToStart, State) -> NewState when + Ready :: [UUID::string()], + ToStart :: [UUID::string()], + State :: #hdl_state{}, + NewState :: #hdl_state{}. + +check_client_startup (_Ready, _ToStart, State) -> + State. + + + +-spec get_elapsed (Mark,Timer) -> {Elapsed,NewTimer} when + Mark :: string(), + Timer :: owls_timers:tms(), + Elapsed :: integer(), + NewTimer :: owls_timers:tms(). + +get_elapsed (Mark,T) -> + case owls_timers:delta(Mark,default,T) of + invalid -> + {0,owls_timers:mark("start_sim",T)}; + Stamp -> + {Stamp,T} + end. + + diff --git a/src/owls_timers.erl b/src/owls_timers.erl index 61aba83..c5995a7 100644 --- a/src/owls_timers.erl +++ b/src/owls_timers.erl @@ -37,7 +37,7 @@ new (R) -> Ups = erlang:convert_time_unit(1, second, native), - #tms{resolution=R,stamps=[{"start",os:system_time()}],units_per_sec=Ups}. + #tms{resolution=R,stamps=[{"*start*",os:system_time()}],units_per_sec=Ups}. @@ -53,28 +53,49 @@ mark (Name,Timer) -> %--------stamp/2-----------------------get the value of a specific stamp in units of resolution --spec stamp (Name, Timer) -> Stamp when +-spec stamp (Name, Timer) -> invalid | Stamp when Name :: string(), Timer :: tms(), Stamp :: integer(). stamp (Name,Timer) -> - {ok, Ts, _} = get_stamp(Name,Timer#tms.stamps), - erlang:convert_time_unit(Ts, native, Timer#tms.resolution). + case get_stamp(Name,Timer#tms.stamps) of + {ok, Ts, _} -> + erlang:convert_time_unit(Ts, native, Timer#tms.resolution); + _ -> + invalid + end. %--------delta/3-------------------------gets time difference between two stamps in units of resolution -spec delta (Earlier, Later, Timer) -> Delta when - Earlier :: string(), % Earlier must have been added before Later or in other words - Later :: string(), % Later must have occured after Earlier in the timeline + Earlier :: default | string(), % Earlier must have been added before Later (default means start) or in other words + Later :: default |string(), % Later must have occured after Earlier in the timeline (default means now) Timer :: tms(), - Delta :: integer(). + Delta :: invalid | integer(). + +delta (default,L,T) -> + delta("*start*",L,T); + +delta (E,default,T) -> + delta(E,"now",T); + +delta (default,default,T) -> + delta("start","now",T); delta (E,L,T) -> - {ok, T2, R} = get_stamp(L,T#tms.stamps), - {ok, T1, _} = get_stamp(E,R), - erlang:convert_time_unit(T2 - T1, native, T#tms.resolution). + case get_stamp(L,T#tms.stamps) of + {ok, T2, R} -> + case get_stamp(E,R) of + {ok, T1, _} -> + erlang:convert_time_unit(T2 - T1, native, T#tms.resolution); + _ -> + invalid + end; + _ -> + invalid + end. @@ -154,6 +175,9 @@ fmt_duration (Delta,Timer) -> get_stamp (_,[]) -> invalid; +get_stamp ("now",T) -> + {ok, os:system_time(), T}; + get_stamp (Stamp,[{Stamp,Value}|T]) -> {ok, Value, T};