Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

356 lines (321 sloc) 16.357 kB
%%% This code was developped by IDEALX (http://IDEALX.org/) and
%%% contributors (their names can be found in the CONTRIBUTORS file).
%%% Copyright (C) 2000-2001 IDEALX
%%%
%%% This program is free software; you can redistribute it and/or modify
%%% it under the terms of the GNU General Public License as published by
%%% the Free Software Foundation; either version 2 of the License, or
%%% (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%%% GNU General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
%%%
%%% In addition, as a special exception, you have the permission to
%%% link the code of this program with any library released under
%%% the EPL license and distribute linked combinations including
%%% the two.
%%% This module launch clients (ts_client module) given a number of
%%% clients and the intensity of the arrival process (intensity =
%%% inverse of the mean of inter arrival). The arrival process is a
%%% Poisson Process (ie, inter-arrivals are independant and exponential)
-module(ts_launcher).
-created('Date: 2000/10/23 12:09:57 nniclausse ').
-vc('$Id$ ').
-author('nicolas.niclausse@niclux.org').
-include("ts_profile.hrl").
% wait up to 10ms after an error
-define(NEXT_AFTER_FAILED_TIMEOUT, 10).
-behaviour(gen_fsm). %% a primitive gen_fsm with two state: launcher and wait
%% External exports
-export([start/0, launch/1]).
%% gen_fsm callbacks
-export([init/1, launcher/2, wait/2, finish/2, handle_event/3,
handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
-record(state, {nusers,
phases =[],
myhostname,
intensity,
phase_nusers, % total number of users to start in the current phase
phase_duration, % expected phase duration
phase_start, % timestamp
maxusers %% if maxusers are currently active, launch a
%% new beam to handle the new users
}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
%%--------------------------------------------------------------------
%% Function: start/0
%%--------------------------------------------------------------------
start() ->
?LOG("starting ~n", ?INFO),
gen_fsm:start_link({local, ?MODULE}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% Function: launch/1
%%--------------------------------------------------------------------
%% Start clients with given interarrival (can be empty list)
launch({Node, Arrivals}) ->
?LOGF("starting on node ~p~n",[[Node]], ?INFO),
gen_fsm:send_event({?MODULE, Node}, {launch, Arrivals});
% same erlang beam case
launch({Node, Host, Arrivals}) ->
?LOGF("starting on node ~p~n",[[Node]], ?INFO),
gen_fsm:send_event({?MODULE, Node}, {launch, Arrivals, atom_to_list(Host)}).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([]) ->
ts_utils:init_seed(),
{ok, MyHostName} = ts_utils:node_to_hostname(node()),
{ok, wait, #state{myhostname=MyHostName}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait({launch, Args, Hostname}, State) ->
wait({launch, Args}, State#state{myhostname = Hostname});
wait({launch, []}, State) ->
MyHostName = State#state.myhostname,
?LOGF("Launch msg receive (~p)~n",[MyHostName], ?NOTICE),
check_registered(),
{ok, {[{Intensity, Users}| Rest], StartDate, Max}} =
ts_config_server:get_client_config(MyHostName),
Warm_timeout=case ts_utils:elapsed(now(), StartDate) of
WaitBeforeStart when WaitBeforeStart>0 ->
round(ts_stats:exponential(Intensity) + WaitBeforeStart);
_Neg ->
?LOG("Negative Warm timeout !!! Check if client "++
" machines are synchronized (ntp ?)~n"++
"Anyway, start launcher NOW! ~n", ?WARN),
1
end,
Warm = lists:min([Warm_timeout,?config(max_warm_delay)]),
?LOGF("Activate launcher (~p users) in ~p msec ~n",[Users, Warm], ?NOTICE),
Duration = Users/Intensity,
?LOGF("Expected duration of first phase: ~p sec ~n",[Duration/1000], ?NOTICE),
PhaseStart = ts_utils:add_time(now(), Warm div 1000),
{next_state,launcher,State#state{phases = Rest, nusers = Users,
phase_nusers = Users,
phase_duration=Duration,
phase_start = PhaseStart,
intensity=Intensity,maxusers=Max }, Warm};
wait({launch, {[{Intensity, Users}| Rest], Max}}, State) ->
?LOGF("Starting with ~p users to do in the current phase (max is ~p)~n",
[Users, Max],?DEB),
Duration = Users/Intensity,
?LOGF("Expected duration of phase: ~p sec ~n",[Duration/1000], ?NOTICE),
check_registered(),
{next_state, launcher, State#state{phases = Rest, nusers = Users,
phase_nusers = Users,
phase_duration=Duration,
phase_start = now(),
intensity = Intensity, maxusers=Max},
?short_timeout}.
launcher(_Event, #state{nusers = 0, phases = [] }) ->
?LOG("no more clients to start, wait ~n",?INFO),
ts_config_server:endlaunching(node()),
{next_state, finish, #state{}, ?check_noclient_timeout};
launcher(timeout, State=#state{nusers = Users,
phase_nusers = PhaseUsers,
phases = Phases,
intensity = Intensity}) ->
BeforeLaunch = now(),
case do_launch({Intensity,State#state.myhostname}) of
{ok, Wait} ->
case check_max_raised(State) of
true ->
ts_config_server:endlaunching(node()),
{next_state, finish, State, ?check_noclient_timeout};
false->
Duration = ts_utils:elapsed(State#state.phase_start, BeforeLaunch),
case change_phase(Users-1, Phases, Duration,
{State#state.phase_duration, PhaseUsers}) of
{change, NewUsers, NewIntensity, Rest} ->
ts_mon:add({ count, newphase }),
PhaseLength = NewUsers/NewIntensity,
?LOGF("Start a new arrival phase (~p ~p); expected duration=~p sec~n",
[NewUsers, NewIntensity, Duration/1000], ?NOTICE),
{next_state,launcher,State#state{phases = Rest,
nusers = NewUsers,
phase_nusers = NewUsers,
phase_duration=PhaseLength,
phase_start = now(),
intensity = NewIntensity},
round(Wait)};
{stop} ->
ts_config_server:endlaunching(node()),
{next_state,finish, State, ?check_noclient_timeout};
{continue} ->
LaunchDuration = ts_utils:elapsed(BeforeLaunch, now()),
%% to keep the rate of new users as expected,
%% remove the time to launch a client to the next
%% wait.
NewWait = case Wait > LaunchDuration of
true -> round(Wait - LaunchDuration);
false -> 0
end,
?DebugF("Real Wait =~p ~n", [NewWait]),
{next_state,launcher,State#state{nusers = Users-1} , NewWait}
end
end;
error ->
% retry with the same user, wait randomly a few msec
RndWait = random:uniform(?NEXT_AFTER_FAILED_TIMEOUT),
{next_state,launcher,State , RndWait}
end.
finish(timeout, State) ->
case ts_client_sup:active_clients() of
0 -> %% no users left, stop
?LOG("No more active users, stop beam~n", ?NOTICE),
ts_mon:stop(),
slave:stop(node()), %% commit suicide
{stop, normal, State}; %% should never be executed
ActiveClients ->
?LOGF("Still ~p active client(s)~n", [ActiveClients],?NOTICE),
{next_state, finish, State, ?check_noclient_timeout}
end.
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok,
{reply, Reply, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_info/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
terminate(Reason, _StateName, _StateData) ->
?LOGF("launcher terminating for reason~p~n",[Reason], ?INFO),
ok.
%%--------------------------------------------------------------------
%% Func: code_change/4
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState, NewStateData}
%%--------------------------------------------------------------------
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
%%%----------------------------------------------------------------------
%%% Func: change_phase/4
%%% Purpose: decide if we need to change phase (if current users is
%%% reached or if max duration is reached)
%%% ----------------------------------------------------------------------
change_phase(0, [{NewIntensity, NewUsers}|Rest], _, _) ->
{change, NewUsers, NewIntensity, Rest};
change_phase(0, [], _, _) ->
?LOG("This was the last phase, wait for connected users to finish their session~n",?NOTICE),
{stop};
change_phase(N,NewPhases,Current,{Total, PhaseUsers}) when Current>Total ->
Percent = 100*N/PhaseUsers,
case {Percent > ?MAX_PHASE_EXCEED_PERCENT, N > ?MAX_PHASE_EXCEED_NUSERS} of
{true,true} ->
?LOGF("Phase duration exceeded, more than ~p% (~.1f%) of users were not launched in time (~p users), tsung may be overloaded !~n",
[?MAX_PHASE_EXCEED_PERCENT,Percent,N],?WARN);
{_,_} ->
?LOGF("Phase duration exceeded, but not all users were launched (~p users, ~.1f% of phase)~n",
[N, Percent],?NOTICE)
end,
case NewPhases of
[{NewIntensity,NewUsers}|Rest] ->
{change, NewUsers, NewIntensity, Rest};
[] ->
?LOG("This was the last phase, wait for connected users to finish their session~n",?NOTICE),
{stop}
end;
change_phase(_N, _, _Current, {_Total, _}) ->
{continue}.
%%%----------------------------------------------------------------------
%%% Func: check_max_raised/1
%%%----------------------------------------------------------------------
check_max_raised(State=#state{phases=Phases,maxusers=Max,nusers=Users,
intensity=Intensity})->
ActiveClients = ts_client_sup:active_clients(),
case ActiveClients >= Max of
true ->
?LOG("Max number of concurrent clients reached, must start a new beam~n", ?NOTICE),
Args = case Users of
0 -> Phases;
_ -> [{Intensity,Users-1}|Phases]
end,
ts_config_server:newbeam(list_to_atom(State#state.myhostname), {Args, Max}),
true;
false ->
?DebugF("Current clients on beam: ~p~n", [ActiveClients]),
false
end.
%%%----------------------------------------------------------------------
%%% Func: do_launch/1
%%%----------------------------------------------------------------------
do_launch({Intensity, MyHostName})->
%%Get one client
%%set the profile of the client
case catch ts_config_server:get_next_session(MyHostName) of
{timeout, _ } ->
?LOG("get_next_session failed (timeout), skip this session !~n", ?ERR),
error;
{ok, Profile} ->
ts_client_sup:start_child(Profile),
X = ts_stats:exponential(Intensity),
?DebugF("client launched, wait ~p ms before launching next client~n",[X]),
{ok, X};
Error ->
?LOGF("get_next_session failed [~p], skip this session !~n", [Error],?ERR),
error
end.
%% Check if global names are synced; Annoying "feature" of R10B7 and up
check_registered() ->
case global:registered_names() of
[] ->
?LOG("No registered processes ! syncing ...~n", ?WARN),
global:sync();
_ -> ok
end.
Jump to Line
Something went wrong with that request. Please try again.