Skip to content
Browse files

MAJOR Update (not yet finished): New configuration system using an XM…

…L file. This requires xmerl-0.15

SVN Revision: 123
  • Loading branch information...
1 parent a25bab1 commit 65af9b809e61c597e77139f80a181249d1b5bc53 @nniclausse nniclausse committed Dec 12, 2003
View
13 Makefile
@@ -12,11 +12,14 @@ prefix = /usr/local/idx-tsunami
# export ERLC_EMULATOR to fix a bug in R9B with native compilation
ERLC_EMULATOR=/usr/bin/erl
export ERLC_EMULATOR
+ERL_COMPILER_OPTIONS="[warn_unused_vars]"
+export ERL_COMPILER_OPTIONS
OPTIONS:=+debug_info
#OPTIONS:=+native +\{hipe,\[o3\]\}
#OPTIONS:=+export_all
#OPTIONS:=
-ERLC = erlc $(OPTIONS)
+INC = ./include
+ERLC = erlc $(OPTIONS) -I $(INC)
OUTDIR = ebin
ALLERLS:= $(wildcard src/*.erl)
ALLBEAMS:=$(patsubst src/%.erl,$(OUTDIR)/%.beam, $(ALLERLS))
@@ -41,14 +44,14 @@ clean:
rm -f $(ALLBEAMS) tsunami.boot tsunami.script ebin/tsunami*.app ebin/tsunami*.rel ebin/idx-tsunami.pl ebin/analyse_msg.pl
tsunami.boot: ebin $(ALLBEAMS) $(UTILS) src/tsunami.rel.src src/tsunami.app.src src/analyse_msg.pl.src src/idx-tsunami.pl.src Makefile
- sed -e 's;%VSN%;$(VSN);' ./src/tsunami.app.src > ./ebin/tsunami.app
+ sed -e 's@%VSN%@$(VSN)@;s@%prefix%@$(prefix)@' ./src/tsunami.app.src > ./ebin/tsunami.app
sed -e 's;%VSN%;$(VSN);' ./src/tsunami.rel.src > ./ebin/tsunami.rel
sed -e 's@%VSN%@$(VSN)@;s@%prefix%@$(prefix)@' ./src/idx-tsunami.pl.src > ./ebin/idx-tsunami.pl
sed -e 's;%VSN%;$(VSN);' ./src/analyse_msg.pl.src > ./ebin/analyse_msg.pl
erl -noshell $(PA) ./src -s make_boot make_boot tsunami
tsunami_controller.boot: ebin $(ALLBEAMS) $(UTILS) src/tsunami_controller.rel.src src/tsunami_controller.app.src Makefile
- sed -e 's;%VSN%;$(VSN);' ./src/tsunami_controller.app.src > ./ebin/tsunami_controller.app
+ sed -e 's@%VSN%@$(VSN)@;s@%prefix%@$(prefix)@' ./src/tsunami_controller.app.src > ./ebin/tsunami_controller.app
sed -e 's;%VSN%;$(VSN);' ./src/tsunami_controller.rel.src > ./ebin/tsunami_controller.rel
erl -noshell $(PA) ./src -s make_boot make_boot tsunami_controller
@@ -66,9 +69,10 @@ install: tsunami.boot tsunami_controller.boot
mkdir -p $(prefix)/bin
mkdir -p $(prefix)/log
mkdir -p $(prefix)/etc
- mkdir -p $(prefix)/bin
+ mkdir -p $(prefix)/erlang/tsunami-$(VSN)/src
install -m 0644 tsunami.boot $(prefix)/bin
install -m 0644 tsunami.boot $(prefix)/bin
+ install -m 0644 idx-tsunami.xml $(prefix)/etc/idx-tsunami_default.xml
install -m 0644 tsunami_controller.boot $(prefix)/bin
sed -e 's;%prefix%;$(prefix);' idx-tsunamirc > $(prefix)/etc/idx-tsunamirc.default
install ebin/idx-tsunami.pl ${prefix}/bin
@@ -79,4 +83,5 @@ install: tsunami.boot tsunami_controller.boot
mkdir -p $(prefix)/erlang/tsunami-$(VSN)/ebin
install $(ALLBEAMS) $(prefix)/erlang/tsunami-$(VSN)/ebin
install ebin/*.app $(prefix)/erlang/tsunami-$(VSN)/ebin
+ install src/*.erl $(prefix)/erlang/tsunami-$(VSN)/src
View
68 include/ts_config.hrl
@@ -0,0 +1,68 @@
+%%%
+%%% Copyright © IDEALX S.A.S. 2003
+%%%
+%%% Author : Nicolas Niclausse <nicolas.niclausse@IDEALX.com>
+%%% Created: 03 Dec 2003 by Nicolas Niclausse <nicolas.niclausse@IDEALX.com>
+%%%
+%%% 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.
+%%%
+
+-vc('$Id$ ').
+-author('nicolas.niclausse@IDEALX.com').
+
+-record(config, {
+ name,
+ loglevel = ?WARN,
+ monitoring = none,
+ controller, %% controller machine
+ clients=[], %% client machines
+
+ server, %% server to test
+ arrivalphases =[], %% arrival process specs
+ thinktime, %% default thinktime specs
+ sessions=[],
+ session_tab,
+ curthink, %% temporary var (current request think)
+ curid = 0 %% temporary var (current request id)
+ }).
+
+-record(client,
+ {host,
+ weight = 1,
+ maxusers,
+ ip = []
+ }).
+-record(server,
+ {host,
+ port,
+ type
+ }).
+-record(session,
+ { id,
+ popularity,
+ type,
+ messages_ack = parse,
+ persistent = false,
+ size
+ }).
+
+-record(arrivalphase,
+ {phase,
+ duration,
+ unit,
+ number, %% ?
+ intensity,
+ maxnumber
+ }).
View
11 include/ts_http.hrl
@@ -21,7 +21,16 @@
-author('nicolas.niclausse@IDEALX.com').
%% use by the client to create the request
--record(http_request, {url, cookie=none, method=get, body=[], id = 0 }).
+-record(http_request, {
+ url,
+ version="1.1", % default is HTTP/1.1
+ server_name, % use for the 'Host:' header
+ get_ims_date = none, % used when the method is getims
+ cookie=none,
+ method=get,
+ body=[],
+ id = 0
+ }).
-record(url,
{scheme, %% http, https, ...
View
3 include/ts_profile.hrl
@@ -23,7 +23,6 @@
-record(message, {thinktime,
ack,
param,
- type=static,
endpage=false,
host, % override global server hostname
port, % override global server port
@@ -51,6 +50,8 @@
-define(restart_sleep, 2000).
-define(infinity_timeout, 15000).
-define(short_timeout, 1).
+-define(config_timeout, 60000).
+-define(check_noclient_timeout, 60000).
-define(retries, 4).
-define(CRLF, "\r\n").
View
7 src/jabber_dynamic.erl
@@ -38,9 +38,10 @@ get_client(N, Id)->
param = #jabber {type = 'authenticate', id = Id}},
#message{ack = no_ack, thinktime=2000+random:uniform(?config(presence_delay)),
param = #jabber {type = 'presence'}},
- #message{type= dynamic, ack = no_ack, thinktime=
- round(ts_stats:exponential(?messages_intensity)),
- param = #jabber {type = 'chat', size= ?config(messages_size), id =Id}},
+% FIXME: no more dynamic messages
+% #message{type= dynamic, ack = no_ack, thinktime=
+% round(ts_stats:exponential(?messages_intensity)),
+% param = #jabber {type = 'chat', size= ?config(messages_size), id =Id}},
#message{ack=local, thinktime=infinity, param=#jabber{type = 'close'}}
],
?LOGF("~w~n", [List_Fin], ?DEB),
View
79 src/ts_client.erl
@@ -25,21 +25,22 @@
-behaviour(gen_server).
--include("../include/ts_profile.hrl").
+-include("ts_profile.hrl").
+-include("ts_config.hrl").
%% External exports
-export([start/1, next/1, close/1]).
%% gen_server callbacks
--export([init/1, init/2, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
-record(state, {rcvpid, % pid of receiving process
server, % name (or IP) of server
port, % server port
protocol, % gen_tcp, gen_udp or ssl
socket, % Socket descriptor
- clienttype, % type of client (ts_http11, jabber_online, etc.)
- mestype, % type of messages (dynamic or static)
+ ip, % local ip to bind to
+ clienttype, % type of client (ts_http, jabber_online, etc.)
profile, % list of requests parameters
persistent, % if true, don't exit when connexion is closed
lasttimeout,% value of the last timeout
@@ -89,53 +90,41 @@ next({Pid, DynData}) ->
%% ignore |
%% {stop, Reason}
%%----------------------------------------------------------------------
-init([{Profile, Size}, {CType, PType, static, Persistent}]) ->
- ?LOG("Init ... static~n",?DEB),
- init([Profile, {CType, PType, static, Persistent}], Size);
-
-init([{Profile, Size}, {CType, PType, dynamic, Persistent}]) ->
- ?LOG("Init ... dynamic~n",?DEB),
- random:seed(),
- init([Profile, {CType, PType, static, Persistent}],
- ?config(messages_number) + Size - 1);
-
-init(Args) ->
- ?LOGF("Init ... with bad args ~p~n",[Args], ?DEB),
- ts_mon:addcount({ badprofile }).
-
-
-init([Profile, {CType, PType, MType, Persistent}], Count) ->
+init({Session=#session{id = Profile,
+ persistent = Persistent,
+ messages_ack = PType,
+ type = CType}, Count, IP}) ->
?LOGF("Init ... started with count = ~p ~n",[Count],?DEB),
%%init seed
-% random:seed(ts_utils:init_seed()),
-% ?LOG("seed OK ~n",?DEB),
+ %% random:seed(ts_utils:init_seed()),
case ts_session_cache:get_req(Profile, 1) of
#message{host=undefined, port= undefined, scheme= undefined} ->
?LOG("Server not configured in msg, get global conf ~n",?DEB),
- {ServerName, Port, Protocol} = ts_profile:get_server(); % get global server profile
+ % get global server profile
+ {ServerName, Port, Protocol} = ts_config_server:get_server_config();
#message{host=ServerName, port= Port, scheme= Protocol} ->
%% server profile can be overriden in the first URL of the session
%% curently, the following server modifications in the session are not used.
?LOGF("Server setup overriden for this client (~p) host=~s port=~p proto=~p ~n",
[self(), ServerName, Port, Protocol], ?INFO);
Other ->
?LOGF("ERROR while getting first req [~p]! ~n",[Other],?ERR),
- {ServerName, Port, Protocol} = ts_profile:get_server() % get global server profile
+ {ServerName, Port, Protocol} = ts_config_server:get_server_config()
end,
- ?LOG("Got first message, connect ... ~n",?DEB),
% open connection
- Opts = protocol_options(Protocol),
+ Opts = protocol_options(Protocol) ++ [{ip, IP}],
+ ?LOGF("Got first message, connect to ~p with options ~p ~n",
+ [{ServerName, Port, Protocol},Opts],?DEB),
StartTime= now(),
case Protocol:connect(ServerName, Port, Opts, ?config(connect_timeout)) of
{ok, Socket} ->
- % start a new process for receiving messages from the server
- case ts_client_rcv:start({PType,
- CType, self(),
- Socket,
- Protocol,
- ?config(tcp_timeout),
- ?config(messages_ack),
- ?config(monitoring)}) of
+ %% start a new process for receiving messages from the server
+ case ts_client_rcv:start({ CType, self(),
+ Socket,
+ Protocol,
+ ?config(tcp_timeout),
+ PType,
+ ?config(monitoring)}) of
{ok, Pid} ->
?LOG("rcv server started ~n",?DEB),
controlling_process(Protocol, Socket, Pid),
@@ -145,11 +134,12 @@ init([Profile, {CType, PType, MType, Persistent}], Count) ->
{ok, #state{rcvpid = Pid, socket = Socket, port = Port,
server= ServerName, profile= Profile,
protocol = Protocol,
- clienttype = CType, mestype = MType,
+ clienttype = CType,
persistent = Persistent,
starttime = StartTime,
monitor = ?config(monitoring),
count = Count,
+ ip = IP,
maxcount = Count
}, ?short_timeout};
{error, Reason} ->
@@ -189,13 +179,13 @@ handle_cast({next_msg}, State = #state{lasttimeout = infinity}) ->
?LOGF("next_msg, count is ~p~n", [State#state.count], ?DEB),
{noreply, State#state{lasttimeout=1}, ?short_timeout};
handle_cast({next_msg}, State) ->
- ?LOG("next_msg (infinite timeout)",?DEB),
+ ?LOGF("next_msg (timeout is set to ~p)",[State#state.lasttimeout],?DEB),
{noreply, State, State#state.lasttimeout};
handle_cast({next_msg, DynData}, State = #state{lasttimeout = infinity}) ->
?LOGF("next_msg, count is ~p~n", [State#state.count], ?DEB),
{noreply, State#state{lasttimeout=1, dyndata=DynData}, ?short_timeout};
handle_cast({next_msg, DynData}, State) ->
- ?LOG("next_msg (infinite timeout)",?DEB),
+ ?LOGF("next_msg (timeout is set to ~p)",[State#state.lasttimeout],?DEB),
{noreply, State#state{dyndata=DynData}, State#state.lasttimeout};
%% more case to handle ?
@@ -233,7 +223,7 @@ handle_cast({closed, Pid}, State) ->
{stop, normal, State};
handle_cast({closed, Reason, Pid}, State) ->
- ?LOGF("Closed after an error while while pending count is: ~p~n!",
+ ?LOGF("Closed after an error while pending count is: ~p~n!",
[State#state.count], ?INFO),
ts_mon:addcount({ Reason }),
{stop, normal, State};
@@ -290,7 +280,7 @@ handle_info(timeout, State ) ->
end,
%% reconnect if needed
- case reconnect(Socket, Host, Port, Protocol, State#state.rcvpid) of
+ case reconnect(Socket, Host, Port, Protocol, State#state.ip, State#state.rcvpid) of
{ok, NewSocket} ->
%% warn the receiving side that we are sending a new request
ts_client_rcv:wait_ack({State#state.rcvpid,Profile#message.ack, Now,
@@ -335,6 +325,7 @@ handle_info(timeout, State ) ->
%% Returns: any (ignored by gen_server)
%%----------------------------------------------------------------------
terminate(Reason, State) ->
+ %% FIXME : check Reason and report anormal return values to ts_mon
?LOGF("Stop, reason= ~p~n",[Reason],?INFO),
Now = now(),
Elapsed = ts_utils:elapsed(State#state.starttime, Now),
@@ -354,12 +345,8 @@ terminate(Reason, State) ->
%% static message, get the thinktime from profile
set_profile(MaxCount, Count, ProfileId) when integer(ProfileId) ->
set_profile(MaxCount, Count, ts_session_cache:get_req(ProfileId, MaxCount-Count+1));
-set_profile(MaxCount, Count, Profile=#message{type = static}) ->
- {Profile#message.thinktime, Profile };
-%% dynamic message, last message
-%% FIXME: don't work anymore: when do we stop ?
set_profile(MaxCount, Count, Profile) ->
- {ts_profile:thinktime(), Profile }.
+ {Profile#message.thinktime, Profile }.
%%----------------------------------------------------------------------
%% Func: new_timeout/3
@@ -379,7 +366,7 @@ new_timeout(_Else, _Count, Thinktime) -> infinity.
%%----------------------------------------------------------------------
reconnect(none, ServerName, Port, Protocol, Pid) ->
?LOGF("Try to reconnect to: ~p (~p)~n",[ServerName, Pid], ?DEB),
- Opts = protocol_options(Protocol),
+ Opts = protocol_options(Protocol) ++ [{ip, IP}],
case Protocol:connect(ServerName, Port, Opts) of
{ok, Socket} ->
controlling_process(Protocol, Socket, Pid),
@@ -391,7 +378,7 @@ reconnect(none, ServerName, Port, Protocol, Pid) ->
ts_mon:addcount({ list_to_atom(CountName) }),
{stop, connfailed}
end;
-reconnect(Socket, Server, Port, Protocol, Pid) ->
+reconnect(Socket, Server, Port, Protocol, IP, Pid) ->
{ok, Socket}.
%% close socket if it exists
View
6 src/ts_client_rcv.erl
@@ -57,7 +57,7 @@ wait_ack({Pid, Ack, When, EndPage, Socket, Protocol}) ->
%% ignore |
%% {stop, Reason}
%%----------------------------------------------------------------------
-init([{PType, CType, PPid, Socket, Protocol, Timeout, Ack, Monitor}]) ->
+init([{CType, PPid, Socket, Protocol, Timeout, Ack, Monitor}]) ->
{ok, #state_rcv{socket = Socket, timeout= Timeout, ack = Ack,
ppid= PPid, clienttype = CType, protocol= Protocol,
session = ts_profile:new_session(CType, Ack),
@@ -128,7 +128,7 @@ handle_info({ssl, Socket, Data}, State) ->
{noreply, NewState, State#state_rcv.timeout};
handle_info({tcp_closed, Socket}, State) ->
- ?LOG("TCP close: ~n", ?NOTICE),
+ ?LOG("TCP close: ~n", ?INFO),
ts_client:close(State#state_rcv.ppid),
{noreply, State};
@@ -139,7 +139,7 @@ handle_info({tcp_error, Socket, Reason}, State) ->
{noreply, State};
handle_info({ssl_closed, Socket}, State) ->
- ?LOG("SSL close: ~n", ?NOTICE),
+ ?LOG("SSL close: ~n", ?INFO),
ts_client:close(State#state_rcv.ppid),
{noreply, State};
View
16 src/ts_client_sup.erl
@@ -26,7 +26,7 @@
-include("../include/ts_profile.hrl").
%% External exports
--export([start_link/0, start_child/1]).
+-export([start_link/0, start_child/1, active_clients/0]).
%% supervisor callbacks
-export([init/1]).
@@ -37,13 +37,22 @@
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-start_child(ServerId) ->
- supervisor:start_child(?MODULE,[ServerId]).
+start_child(Profile) ->
+ supervisor:start_child(?MODULE,[Profile]).
%%%----------------------------------------------------------------------
%%% Callback functions from supervisor
%%%----------------------------------------------------------------------
+%%--------------------------------------------------------------------
+%% Func: active_clients/0
+%% Returns: [ Client ]
+%% Description: returns the list of all active children on this beam's
+%% client supervisor.
+%%--------------------------------------------------------------------
+active_clients()->
+ length(supervisor:which_children(?MODULE)).
+
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, {SupFlags, [ChildSpec]}} |
@@ -59,6 +68,7 @@ init([]) ->
],
{ok, {SupFlags, ChildSpec}}.
+
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
View
314 src/ts_config.erl
@@ -0,0 +1,314 @@
+%%%----------------------------------------------------------------------
+%%% File : config.erl
+%%% Author : Nicolas Niclausse <nicolas.niclausse@IDEALX.com>
+%%% Purpose : Read the IDX-Tsunami XML config file
+%%% Created : 3 Dec 2003 by Nicolas Niclausse <nniclausse@idealx.com>
+%%%----------------------------------------------------------------------
+%%%
+%%% This code was developped by IDEALX (http://IDEALX.org/) and
+%%% contributors (their names can be found in the CONTRIBUTORS file).
+%%% Copyright (C) 2000-2003 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.
+%%%
+%%%----------------------------------------------------------------------
+
+-module(ts_config).
+-author('nniclausse@idealx.com').
+-vc('$Id$ ').
+
+-include("ts_profile.hrl").
+-include("ts_config.hrl").
+
+-include("xmerl.hrl").
+
+-export([read/1,
+ getAttr/2,
+ parse/2
+ ]).
+
+%%%----------------------------------------------------------------------
+%%% Function: read/1
+%%% Purpose: read the xml config file
+%%%----------------------------------------------------------------------
+read(Filename) ->
+ case xmerl_scan:file(Filename) of
+ {ok, Root = #xmlElement{}} ->
+ Table = ets:new(sessiontable, [ordered_set, protected]),
+ {ok, parse(Root, #config{session_tab = Table})};
+ Error ->
+ {error, Error}
+ end.
+
+%%%----------------------------------------------------------------------
+%%% Function: parse/2
+%%% Purpose: parse the xmerl structure
+%%%----------------------------------------------------------------------
+parse(Element = #xmlElement{parents = []}, Conf=#config{}) ->
+ Loglevel = getAttr(Element#xmlElement.attributes, loglevel),
+ Dump = getAttr(Element#xmlElement.attributes, dumptraffic),
+ Monitor = case Dump of
+ "false" -> none;
+ "true" -> full;
+ "light" -> light
+ end,
+ lists:foldl(fun parse/2,
+ Conf#config{monitoring= Monitor, loglevel= ts_utils:level2int(Loglevel)},
+ Element#xmlElement.content);
+
+
+%% parsing the Controller elements
+parse(Element = #xmlElement{name=controller}, Conf = #config{controller=Controller}) ->
+ Server = getAttr(Element#xmlElement.attributes, host),
+
+ lists:foldl(fun parse/2,
+ Conf#config{controller = {Server}},
+ Element#xmlElement.content);
+
+%% parsing the Server elements
+parse(Element = #xmlElement{name=server}, Conf = #config{server=SList}) ->
+ Server = getAttr(Element#xmlElement.attributes, host),
+ Port = getAttr(Element#xmlElement.attributes, port),
+ Type = case getAttr(Element#xmlElement.attributes, type) of
+ "ssl" -> ssl;
+ "tcp" -> gen_tcp;
+ "udp" -> gen_udp
+ end,
+
+ {ok, [{integer,1,IPort}],1} = erl_scan:string(Port),
+
+ lists:foldl(fun parse/2,
+ Conf#config{server = #server{host=Server,
+ port=IPort,
+ type=Type
+ }},
+ Element#xmlElement.content);
+
+%% Parsing the Client element
+parse(Element = #xmlElement{name=client},
+ Conf = #config{clients=CList}) ->
+
+ Host = getAttr(Element#xmlElement.attributes, host),
+ Weight = getAttr(Element#xmlElement.attributes, weight),
+ MaxUsers = getAttr(Element#xmlElement.attributes, maxusers),
+ {ok, [{integer,1,IWeight}],1} = erl_scan:string(Weight),
+ {ok, [{integer,1,IMaxUsers}],1} = erl_scan:string(MaxUsers),
+
+ lists:foldl(fun parse/2,
+ Conf#config{clients = [#client{host = Host,
+ weight = IWeight,
+ maxusers = IMaxUsers}
+ |CList]},
+ Element#xmlElement.content);
+
+%% Parsing the ip element
+parse(Element = #xmlElement{name=ip},
+ Conf = #config{clients=[CurClient|CList]}) ->
+ IPList = CurClient#client.ip,
+
+ StrIP = getAttr(Element#xmlElement.attributes, value),
+ {ok, IP } = inet:getaddr(StrIP,inet),
+
+ lists:foldl(fun parse/2,
+ Conf#config{clients = [CurClient#client{ip = [IP|IPList]}
+ |CList]},
+ Element#xmlElement.content);
+
+%% Parsing the arrivalphase element
+parse(Element = #xmlElement{name=arrivalphase},
+ Conf = #config{arrivalphases=AList}) ->
+
+ Phase = getAttr(Element#xmlElement.attributes, phase),
+ Duration = getAttr(Element#xmlElement.attributes, duration),
+ {ok, [{integer,1,IDuration}],1} = erl_scan:string(Duration),
+
+ lists:foldl(fun parse/2,
+ Conf#config{arrivalphases = [#arrivalphase{phase=Phase,
+ duration=IDuration
+ }
+ |AList]},
+ Element#xmlElement.content);
+
+%% Parsing the users element
+parse(Element = #xmlElement{name=users},
+ Conf = #config{arrivalphases=[CurA | AList]}) ->
+
+ MaxNumber =
+ case getAttr(Element#xmlElement.attributes, maxnumber) of
+ "" -> infinity;
+ Val ->
+ ?LOGF("Maximum number of users ~p~n",[Val],?INFO),
+ {ok, [{integer,1,IMaxN}],1} = erl_scan:string(Val),
+ IMaxN
+ end,
+
+ InterArrival =
+ case erl_scan:string(getAttr(Element#xmlElement.attributes, interarrival)) of
+ {ok, [{integer,1,I}],1} -> I;
+ {ok, [{float,1,F}],1} -> F
+ end,
+ Intensity= 1/(1000 * InterArrival),
+
+ lists:foldl(fun parse/2,
+ Conf#config{arrivalphases = [CurA#arrivalphase{maxnumber = MaxNumber,
+ intensity=Intensity}
+ |AList]},
+ Element#xmlElement.content);
+
+%% Parsing the session element
+parse(Element = #xmlElement{name=session},
+ Conf = #config{session_tab = Tab, curid= PrevReqId, sessions=SList}) ->
+
+ Id = length(SList),
+ Msg_ack = getAttr(Element#xmlElement.attributes, messages_ack),
+ Persistent = getAttr(Element#xmlElement.attributes, persistent),
+ Name = getAttr(Element#xmlElement.attributes, name),
+ Type = getAttr(Element#xmlElement.attributes, type),
+ ?LOGF("Session name for id ~p is ~p~n",[Id+1, Name],?NOTICE),
+ Popularity =
+ case erl_scan:string(getAttr(Element#xmlElement.attributes, popularity)) of
+ {ok, [{integer,1,IPop}],1} -> IPop;
+ {ok, [{float,1,FPop}],1} -> FPop
+ end,
+
+ {ok, [{atom,1,AMsg_Ack}],1} = erl_scan:string(Msg_ack),
+ {ok, [{atom,1,APersistent}],1} = erl_scan:string(Persistent),
+ {ok, [{atom,1,AType}],1} = erl_scan:string(Type),
+
+ case Id of
+ 0 -> ok; % first session
+ _ -> ets:insert(Tab, {{Id, size}, PrevReqId})
+ %% add total requests count in previous session in ets table
+ end,
+
+ lists:foldl(fun parse/2,
+ Conf#config{sessions = [#session{id = Id + 1,
+ popularity = Popularity,
+ type = AType,
+ messages_ack = AMsg_Ack,
+ persistent = APersistent
+ }
+ |SList],
+ curid=0},% re-initialize request id
+ Element#xmlElement.content);
+
+%%% Parsing the request element
+parse(Element = #xmlElement{name=request},
+ Conf = #config{sessions=[CurSess|SList], curid=Id}) ->
+
+ Type = CurSess#session.type,
+
+ lists:foldl( {Type, parse_config},
+ Conf#config{curid=Id+1},
+ Element#xmlElement.content);
+
+%%% Parsing the default element
+parse(Element = #xmlElement{name=default},
+ Conf = #config{session_tab = Tab}) ->
+ case getAttr(Element#xmlElement.attributes, type) of
+ "" ->
+ case getAttr(Element#xmlElement.attributes, name) of
+ "thinktime" ->
+ Val = getAttr(Element#xmlElement.attributes, value),
+ {ok, [{integer,1,IThink}],1} = erl_scan:string(Val),
+ ets:insert(Tab,{{thinktime, value}, IThink}),
+ Random = getAttr(Element#xmlElement.attributes, random),
+ ets:insert(Tab,{{thinktime, random}, Random})
+ end,
+ lists:foldl( fun parse/2, Conf, Element#xmlElement.content);
+ Type ->
+ {ok, [{atom,1,Module}],1} = erl_scan:string(Type),
+ Module:parse_config(Element, Conf)
+ end;
+
+
+
+%%% Parsing the thinktim element
+parse(Element = #xmlElement{name=thinktime},
+ Conf = #config{curid=Id, session_tab = Tab, sessions = [CurS |SList]}) ->
+ case ets:lookup(Tab,{thinktime, value}) of
+ [] -> % no default value
+ Think = case getAttr(Element#xmlElement.attributes, value) of
+ "" -> 0;
+ Val ->
+ {ok, [{integer,1,IThink}],1} = erl_scan:string(Val),
+ IThink
+ end,
+ Randomize = case getAttr(Element#xmlElement.attributes, random) of
+ "true" -> true;
+ _ -> false
+ end;
+ [{_Key, Think}] ->
+ Randomize = case ets:lookup(Tab,{thinktime, value}) of
+ {_K, "true"} -> true;
+ _ -> false
+ end
+ end,
+ RealThink = case Randomize of
+ true ->
+ round(ts_stats:exponential(1/(Think*1000)));
+ false ->
+ round(Think * 1000)
+ end,
+ ?LOGF("New thinktime ~p for id (~p:~p)~n",[RealThink, CurS#session.id, Id],
+ ?INFO),
+ [{Key, Msg}] = ets:lookup(Tab,{CurS#session.id, Id}),
+ ets:insert(Tab,{Key, Msg#message{thinktime=RealThink, endpage=true}}),
+
+ lists:foldl( fun parse/2, Conf#config{curthink=Think},
+ Element#xmlElement.content);
+
+
+%% Parsing other elements
+parse(Element = #xmlElement{}, Conf = #config{}) ->
+ lists:foldl(fun parse/2, Conf, Element#xmlElement.content);
+
+%% Parsing non #xmlElement elements
+parse(Element, Conf = #config{}) ->
+ Conf.
+
+
+%%%----------------------------------------------------------------------
+%%% Function: getAttr/2
+%%% Purpose: search the attibute list for the given one
+%%%----------------------------------------------------------------------
+getAttr(Attr, Name) -> getAttr(Attr, Name, "").
+
+getAttr([Attr = #xmlAttribute{name=Name}|Tail], Name, Default) ->
+ case Attr#xmlAttribute.value of
+ [] -> Default;
+ A -> A
+ end;
+
+getAttr([H|T], Name, Default) ->
+ getAttr(T, Name, Default);
+
+getAttr([], Name, Default) ->
+ Default.
+
+
+%%%----------------------------------------------------------------------
+%%% Function: getText/1
+%%% Purpose: get the text of the XML node
+%%%----------------------------------------------------------------------
+getText([Text = #xmlText{value=Value}|Tail]) -> build_list(
+ string:strip(Value, both));
+getText(_Other) -> "".
+
+
+%% Default separator is '%'
+build_list(String) -> build_list(String, "%").
+build_list(String, Sep) ->
+ string:tokens(String, Sep).
View
367 src/ts_config_server.erl
@@ -0,0 +1,367 @@
+%%%
+%%% Copyright © IDEALX S.A.S. 2003
+%%%
+%%% Author : Nicolas Niclausse <nicolas.niclausse@IDEALX.com>
+%%% Created: 04 Dec 2003 by Nicolas Niclausse <nicolas.niclausse@IDEALX.com>
+%%%
+%%% 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.
+%%%
+%%%-------------------------------------------------------------------
+%%% File : ts_config_server.erl
+%%% Author : Nicolas Niclausse <nniclausse@idealx.com>
+%%% Description :
+%%%
+%%% Created : 4 Dec 2003 by Nicolas Niclausse <nniclausse@idealx.com>
+%%%-------------------------------------------------------------------
+-module(ts_config_server).
+
+-vc('$Id$ ').
+-author('nicolas.niclausse@IDEALX.com').
+
+-behaviour(gen_server).
+
+%%--------------------------------------------------------------------
+%% Include files
+%%--------------------------------------------------------------------
+
+-include("ts_profile.hrl").
+-include("ts_config.hrl").
+
+-include("xmerl.hrl").
+
+%%--------------------------------------------------------------------
+%% External exports
+-export([start_link/0, read_config/1, get_req/2, get_next_session/0,
+ get_client_config/1, newbeam/1, newbeam/2, get_server_config/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {config,
+ start_date, %
+ last_beam_id = 0, % last tsunami beam id (used to set nodenames)
+ total_weight % total weight of client machines
+ }).
+
+%%====================================================================
+%% External functions
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link/0
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+start_link() ->
+ gen_server:start_link({global, ?MODULE}, ?MODULE, [], []).
+
+%%--------------------------------------------------------------------
+%% Function: newbeam/1
+%% Description: start a new beam
+%%--------------------------------------------------------------------
+newbeam(Host)->
+ gen_server:cast({global, ?MODULE},{newbeam, Host, [] }).
+%%--------------------------------------------------------------------
+%% Function: newbeam/2
+%% Description: start a new beam with given config.
+%%--------------------------------------------------------------------
+newbeam(Host, {Arrivals, MaxUsers})->
+ gen_server:cast({global, ?MODULE},{newbeam, Host, {Arrivals, MaxUsers} }).
+
+%%--------------------------------------------------------------------
+%% Function: read_config/2
+%% Description: get Nth request from given session Id
+%% Returns: #message | {error, Reason}
+%%--------------------------------------------------------------------
+get_req(Id, Count)->
+ gen_server:call({global, ?MODULE},{get_req, Id, Count}).
+
+%%--------------------------------------------------------------------
+%% Function: read_config/1
+%% Description: Read Config file
+%% Returns: ok | {error, Reason}
+%%--------------------------------------------------------------------
+read_config(ConfigFile)->
+ gen_server:call({global,?MODULE},{read_config, ConfigFile},?config_timeout).
+
+%%--------------------------------------------------------------------
+%% Function: get_client_config/1
+%% Description: get client machine setup (for the launcher)
+%% Returns: {ok, {ArrivalList, StartDate, MaxUsers}} | {error, notfound}
+%%--------------------------------------------------------------------
+get_client_config(Host)->
+ gen_server:call({global,?MODULE},{get_client_config, Host}).
+
+%%--------------------------------------------------------------------
+%% Function: get_server_config/0
+%% Returns: {Hostname, Port (integer), Protocol type (ssl|gen_tcp|gen_udp)}
+%%--------------------------------------------------------------------
+get_server_config()->
+ gen_server:call({global,?MODULE},{get_server_config}).
+
+%%--------------------------------------------------------------------
+%% Function: get_next_session/0
+%% Description: choose randomly a session
+%% Returns: {ok, Session ID, Session Size (integer), IP (tuple)}
+%%--------------------------------------------------------------------
+get_next_session()->
+ gen_server:call({global, ?MODULE},{get_next_session, node()}).
+
+%%====================================================================
+%% Server functions
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init/1
+%% Description: Initiates the server
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%%--------------------------------------------------------------------
+init([]) ->
+ {Msec, Sec, Nsec} = ts_utils:init_seed(),
+ random:seed(Msec,Sec,Nsec),
+ {ok, #state{}}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_call/3
+%% Description: Handling call messages
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%%--------------------------------------------------------------------
+handle_call({read_config, ConfigFile}, From, State) ->
+ case ts_config:read(ConfigFile) of
+ {ok, Config=#config{session_tab=Tab,curid=LastReqId,sessions=[LastSess| SList]}} ->
+ check_popularity(Config#config.sessions),
+ application:set_env(tsunami_controller, clients, Config#config.clients),
+ application:set_env(tsunami_controller, monitoring, Config#config.monitoring),
+ application:set_env(tsunami_controller, debug_level, Config#config.loglevel),
+ SumWeights = fun(X, Sum) -> X#client.weight + Sum end,
+ Sum = lists:foldl(SumWeights, 0, Config#config.clients),
+ %% we only know now the size of last session from the file: add it
+ %% in the table
+ ets:insert(Tab, {{LastSess#session.id, size}, LastReqId}),
+ {reply, ok, #state{config=Config, total_weight = Sum}};
+ {error, Reason} -> {reply, {error, Reason}, State}
+ end;
+
+%% get Nth request from given session Id
+handle_call({get_req, Id, N}, From, State) ->
+ Config = State#state.config,
+ Tab = Config#config.session_tab,
+ ?LOGF("look for ~p th request in session ~p for ~p~n",[N,Id,From],?DEB),
+ case ets:lookup(Tab, {Id, N}) of
+ [{Key, Session}] ->
+ ?LOGF("ok, found ~p for ~p~n",[Session,From],?DEB),
+ {reply, Session, State};
+ Other ->
+ {reply, {error, Other}, State}
+ end;
+
+
+%% get a new session id and an ip for the given node
+handle_call({get_next_session, Node}, From, State) ->
+ Config = State#state.config,
+ Tab = Config#config.session_tab,
+
+ {ok, HostName} = ts_utils:node_to_hostname(Node),
+ {value, Client} = lists:keysearch(HostName, #client.host, Config#config.clients),
+
+ {ok, IP} = choose_client_ip(Client),
+
+ ?LOGF("get new session for ~p~n",[From],?DEB),
+ case choose_session(Config#config.sessions) of
+ {ok, Session=#session{id=Id}} ->
+ ?LOGF("Session ~p choosen~n",[Id],?INFO),
+ case ets:lookup(Tab, {Id, size}) of
+ [{Key, Size}] ->
+ {reply, {ok, {Session, Size, IP}}, State};
+ Other ->
+ {reply, {error, Other}, State}
+ end;
+ Other ->
+ {reply, {error, Other}, State}
+ end;
+
+%%
+handle_call({get_server_config}, From, State) ->
+ Config = State#state.config,
+ Server = Config#config.server,
+ {reply,{Server#server.host, Server#server.port, Server#server.type}, State};
+
+%%
+handle_call({get_client_config, Host}, From, State) ->
+ Config = State#state.config,
+ %% set start date if not done yet
+ StartDate = case State#state.start_date of
+ undefined ->
+ ts_utils:add_time(now(), ?config(warm_time));
+ Date -> Date
+ end,
+ case get_client_cfg(Config#config.arrivalphases,
+ Config#config.clients, State#state.total_weight,
+ Host) of
+ {ok,List,Max} ->
+ {reply, {ok, {List, StartDate,Max}},
+ State#state{start_date = StartDate}};
+ _ ->
+ {reply, {error, notfound}, State}
+ end;
+
+handle_call(Request, From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast/2
+%% Description: Handling cast messages
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%--------------------------------------------------------------------
+%% start a new beam with slave module
+handle_cast({newbeam, Host, Arrivals}, State=#state{last_beam_id = NodeId}) ->
+ Config = State#state.config,
+ Name = "tsunami" ++ integer_to_list(NodeId),
+ {ok, [[PathName, PathVal]]} = init:get_argument(boot_var),
+ {ok, [[BootController]]} = init:get_argument(boot),
+ Shared = case init:get_argument(shared) of
+ error -> "";
+ {ok,[[]]} -> " -shared"
+ end,
+ {ok, Boot, _} = regexp:sub(BootController,"tsunami_controller","tsunami"),
+ Args = lists:append(["-rsh ssh -setcookie ",atom_to_list(erlang:get_cookie()),
+ " -boot ", Boot,
+ " -boot_var ", PathName, " ",PathVal,
+ " -tsunami debug_level ", integer_to_list(?config(debug_level)),
+ " -tsunami monitoring ", atom_to_list(?config(monitoring)), Shared]),
+ {ok, Node} = slave:start_link(Host, Name, Args),
+ ?LOGF("started newbeam on node ~p~n", [Node], ?NOTICE),
+ Res = net_adm:ping(Node),
+ ts_launcher:launch({Node, Arrivals}),
+ {noreply, State#state{last_beam_id = NodeId +1}};
+
+handle_cast(Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info/2
+%% Description: Handling all non call/cast messages
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%--------------------------------------------------------------------
+handle_info(Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate/2
+%% Description: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%--------------------------------------------------------------------
+terminate(Reason, State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState}
+%%--------------------------------------------------------------------
+code_change(OldVsn, State, Extra) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: choose_client_ip/1
+%% Args: #client
+%% Purpose: choose an IP for a client
+%% Returns: IPv4 address {A1,A2,A3,A4}
+%%----------------------------------------------------------------------
+choose_client_ip(Client=#client{ip = [IP]}) -> %% only one IP
+ {ok, IP};
+choose_client_ip(Client=#client{ip = IPList}) ->
+ %% FIXME: optimize (use round robin for ex. ?)
+ N = random:uniform(length(IPList)), %% random IP
+ {ok, lists:nth(N,IPList)}.
+
+%%----------------------------------------------------------------------
+%% Func: choose_session/1
+%% Args: List of #session
+%% Purpose: choose an session randomly
+%% Returns: #session
+%%----------------------------------------------------------------------
+choose_session([Session]) -> %% only one Session
+ {ok, Session};
+choose_session(Sessions) ->
+ choose_session(Sessions, random:uniform(100),0).
+
+choose_session([S=#session{popularity=P} | SList],Rand,Cur) when Rand =< P+Cur->
+ {ok, S};
+choose_session([S=#session{popularity=P} | SList], Rand, Cur) ->
+ choose_session(SList, Rand, Cur+P).
+
+
+%%----------------------------------------------------------------------
+%% Func: get_client_cfg/3
+%% Args: list of #arrivalphase, list of #client, String
+%% Purpose: set parameters for given host client
+%% Returns: {ok, {Intensity = float, Users=integer, StartDate = tuple}}
+%% | {error, Reason}
+%%----------------------------------------------------------------------
+get_client_cfg(Arrival, Clients, TotalWeight, Host) ->
+ get_client_cfg(Arrival, Clients, TotalWeight,Host, []).
+
+get_client_cfg([], Clients, TotalWeight, Host, Cur) ->
+ {value, Client} = lists:keysearch(Host, #client.host, Clients),
+ Max = Client#client.maxusers,
+ {ok, lists:reverse(Cur), Max};
+get_client_cfg([Arrival=#arrivalphase{duration=Duration,
+ intensity=PhaseIntensity,
+ maxnumber= MaxNumber } | AList],
+ Clients, TotalWeight, Host, Cur) ->
+ {value, Client} = lists:keysearch(Host, #client.host, Clients),
+ Weight = Client#client.weight,
+ ClientIntensity = PhaseIntensity * Weight / TotalWeight,
+ NUsers = case MaxNumber of
+ infinity -> %% only use the duration to set the number of users
+ Duration * 1000 * ClientIntensity;
+ Val ->
+ lists:min([MaxNumber, Duration * 1000 * ClientIntensity])
+ end,
+ ?LOGF("New arrival phase: will start ~p users~n",[NUsers],?INFO),
+ get_client_cfg(AList, Clients, TotalWeight, Host,
+ [{ClientIntensity, round(NUsers)} | Cur]).
+
+%%----------------------------------------------------------------------
+%% Func: get_client_cfg/1
+%% Purpose: Check if the sum of session's popularity is 100
+%%----------------------------------------------------------------------
+check_popularity(Sessions) ->
+ Sum = lists:foldl(fun(X, Sum) -> X#session.popularity+Sum end, 0, Sessions),
+ Epsilon = 0.01, %% popularity may be a float number. 10-3 precision
+ Delta = abs(Sum - 100),
+ case Delta < Epsilon of
+ true -> ok;
+ false ->
+ ?LOGF("*** Total sum of popularity is not 100 (~p) !",[Sum],?ERR)
+ end.
+
View
5 src/ts_controller_sup.erl
@@ -50,6 +50,9 @@ start_link() ->
%%----------------------------------------------------------------------
init([]) ->
?LOG("starting",?DEB),
+ Config = {ts_config_server, {ts_config_server, start_link,
+ []}, transient, 2000,
+ worker, [ts_config_server]},
Monitor = {ts_mon, {ts_mon, start, []}, transient, 2000,
worker, [ts_mon]},
Timer = {ts_timer, {ts_timer, start, [?config(nclients)]}, transient, 2000,
@@ -63,7 +66,7 @@ init([]) ->
?config(nclients)]]},
transient, 2000, worker, [ts_user_server]},
{ok,{{one_for_one,?retries,10},
- [Monitor, Timer, Request, Msg, User]}}.
+ [Config, Monitor, Timer, Request, Msg, User]}}.
%%%----------------------------------------------------------------------
%%% Internal functions
View
26 src/ts_http.erl
@@ -26,32 +26,38 @@
-include("../include/ts_http.hrl").
-export([get_client/2, add_dynparams/2,
- get_random_message/1, parse/2, new_session/0]).
+ get_random_message/1,
+ parse/2,
+ parse_config/2,
+ new_session/0]).
%%
new_session() ->
#http{}.
%%
-get_random_message(#http_request{url = URL, method=get, cookie=Cookie}) ->
- ts_http_common:http_get(URL, ?config(http_version), Cookie);
-
-get_random_message(#http_request{url = URL, method=getims, cookie=Cookie}) ->
- ts_http_common:http_get_ifmodsince(URL, ?config(http_version), Cookie);
-
-get_random_message(#http_request{url = URL, method=post, cookie=Cookie, body= Body}) ->
- ts_http_common:http_post(URL, ?config(http_version), Cookie, Body).
+get_random_message(Req=#http_request{method=get}) ->
+ ts_http_common:http_get(Req);
+get_random_message(Req=#http_request{method=getims}) ->
+ ts_http_common:http_get_ifmodsince(Req);
+get_random_message(Req=#http_request{method=post}) ->
+ ts_http_common:http_post(Req).
%%
get_client(N, Id) ->
- ts_httperf_sesslog:get_client(N, Id).
+ {ok, Session, Size, IP} = ts_config_server:get_next_session(),
+ {Session, Size, IP}.
%%
parse(Data, State) ->
ts_http_common:parse(Data, State).
+%%
+parse_config(Element, Conf) ->
+ ts_http_common:parse_config(Element, Conf).
+
%%----------------------------------------------------------------------
%% Func: add_dynparams/2
%%----------------------------------------------------------------------
View
180 src/ts_http_common.erl
@@ -24,51 +24,58 @@
-vc('$Id$ ').
-author('nicolas.niclausse@IDEALX.com').
--include("../include/ts_profile.hrl").
--include("../include/ts_http.hrl").
+-include("ts_profile.hrl").
+-include("ts_http.hrl").
+
+-include("ts_config.hrl").
+-include("xmerl.hrl").
-export([
- http_get/3,
- http_get_ifmodsince/3,
- http_post/4,
+ http_get/1,
+ http_get_ifmodsince/1,
+ http_post/1,
+ set_msg/1, set_msg/2,
parse/2,
parse_URL/1,
- protocol_headers/1
+ parse_config/2
]).
%%----------------------------------------------------------------------
-%% Func: http_get/3
-%% Args: URL, HTTP Version, Cookie
+%% Func: http_get/1
+%% Args: #http_request
%%----------------------------------------------------------------------
-http_get(URL, Version, Cookie) ->
+http_get(Req=#http_request{url=URL, version=Version, cookie=Cookie,
+ server_name = Host}) ->
list_to_binary([?GET, " ", URL," ", "HTTP/", Version, ?CRLF,
- protocol_headers(Version),
- user_agent(),
- get_cookie(Cookie),
- ?CRLF]).
+ "Host: ", Host, ?CRLF,
+ user_agent(),
+ get_cookie(Cookie),
+ ?CRLF]).
%%----------------------------------------------------------------------
-%% Func: http_get_ifmodsince/3
-%% Args: URL, HTTP Version, Cookie
+%% Func: http_get_ifmodsince/1
+%% Args: #http_request
%%----------------------------------------------------------------------
-http_get_ifmodsince(URL, Version, Cookie) ->
+http_get_ifmodsince(Req=#http_request{url=URL, version=Version, cookie=Cookie,
+ get_ims_date=Date, server_name=Host}) ->
list_to_binary([?GET, " ", URL," ", "HTTP/", Version, ?CRLF,
- ["If-Modified-Since: ", ?config(http_modified_since_date),?CRLF],
- protocol_headers(Version),
+ ["If-Modified-Since: ", Date, ?CRLF],
+ "Host: ", Host, ?CRLF,
user_agent(),
get_cookie(Cookie),
?CRLF]).
%%----------------------------------------------------------------------
-%% Func: http_post/4
-%% Args: URL, HTTP Version, Cookie, Content
+%% Func: http_post/1
+%% Args: #http_request
%% TODO: Content-Type ?
%%----------------------------------------------------------------------
-http_post(URL, Version, Cookie, Content) ->
+http_post(Req=#http_request{url=URL, version=Version, cookie=Cookie, body=Content,
+ server_name=Host}) ->
ContentLength=integer_to_list(size(Content)),
?LOGF("Content Length of POST: ~p~n.", [ContentLength], ?DEB),
Headers = [?POST, " ", URL," ", "HTTP/", Version, ?CRLF,
- protocol_headers(Version),
+ "Host: ", Host, ?CRLF,
user_agent(),
get_cookie(Cookie),
"Content-Length: ",ContentLength, ?CRLF,
@@ -82,22 +89,63 @@ http_post(URL, Version, Cookie, Content) ->
user_agent() ->
["User-Agent: ", ?USER_AGENT, ?CRLF].
-get_cookie([]) ->
- [];
-get_cookie(none) ->
- [];
+get_cookie([]) -> [];
+get_cookie(none) -> [];
get_cookie(Cookie) ->
["Cookie: ", Cookie, ?CRLF].
+
%%----------------------------------------------------------------------
-%% set HTTP headers specific to the protocol version
+%% Func: parse_config/2
+%% Args: Element, Config
+%% Returns: List
+%% Purpose: parse a request defined in the XML config file
%%----------------------------------------------------------------------
-protocol_headers("1.1") ->
- %% Host is mandatory in HTTP/1.1
- ["Host: ", ?config(server_name), ?CRLF];
-protocol_headers("1.0") ->
- [].
-
+parse_config(Element = #xmlElement{name=http},
+ Config=#config{curid= Id, session_tab = Tab,
+ sessions = [CurS |SList]}) ->
+ Version = ts_config:getAttr(Element#xmlElement.attributes, version),
+ URL = ts_config:getAttr(Element#xmlElement.attributes, url),
+ Contents = ts_config:getAttr(Element#xmlElement.attributes, contents),
+ Date = ts_config:getAttr(Element#xmlElement.attributes, date),
+ Method = case ts_config:getAttr(Element#xmlElement.attributes, method) of
+ "GET" -> get;
+ "GETIMS" -> getims;
+ "POST" -> post;
+ Other ->
+ ?LOGF("Bad method ! ~p ~n",[Other],?ERR),
+ get
+ end,
+ ServerName = case ets:lookup(Tab,{http_server_name, value}) of
+ [] ->
+ ?config(server_name);
+ [{_Key, SName}] ->
+ SName
+ end,
+ Msg = set_msg(#http_request{url = URL,
+ method = Method,
+ version = Version,
+ get_ims_date= Date,
+ server_name = ServerName,
+ body = list_to_binary(Contents)}, 0),
+ ets:insert(Tab,{{CurS#session.id, Id}, Msg}),
+ lists:foldl( {ts_config, parse},
+ Config#config{},
+ Element#xmlElement.content);
+%% Parsing default values
+parse_config(Element = #xmlElement{name=default}, Conf = #config{session_tab = Tab}) ->
+ case ts_config:getAttr(Element#xmlElement.attributes, name) of
+ "server_name" ->
+ Val = ts_config:getAttr(Element#xmlElement.attributes, value),
+ ets:insert(Tab,{{http_server_name, value}, Val})
+ end,
+ lists:foldl( {ts_config, parse}, Conf, Element#xmlElement.content);
+%% Parsing other elements
+parse_config(Element = #xmlElement{}, Conf = #config{}) ->
+ lists:foldl( {ts_config, parse}, Conf, Element#xmlElement.content);
+%% Parsing non #xmlElement elements
+parse_config(Element, Conf = #config{}) ->
+ Conf.
%%----------------------------------------------------------------------
%% Func: parse/2
@@ -300,10 +348,10 @@ parse_cookie(ParsedHeader) ->
end
end.
-
-
-
-% parse host
+%%----------------------------------------------------------------------
+%% Func: parse_URL/1
+%% Returns: #url
+%%----------------------------------------------------------------------
parse_URL("https://" ++ String) ->
parse_URL(host, String, [], #url{scheme=https});
parse_URL("http://" ++ String) ->
@@ -312,7 +360,6 @@ parse_URL("http://" ++ String) ->
%%----------------------------------------------------------------------
%% Func: parse_URL/4 (inspired by yaws_api.erl)
%% Returns: #url record
-%% Purpose: parse a URL (surprise !)
%%----------------------------------------------------------------------
% parse host
parse_URL(host, [], Acc, URL) -> % no path or port
@@ -323,17 +370,74 @@ parse_URL(host, [$:|Tail], Acc, URL) -> % port starts here
parse_URL(port, Tail, [], URL#url{host=lists:reverse(Acc)});
parse_URL(host, [H|Tail], Acc, URL) ->
parse_URL(host, Tail, [H|Acc], URL);
+
% parse port
parse_URL(port,[], Acc, URL) ->
URL#url{port=list_to_integer(lists:reverse(Acc)), path= "/"};
parse_URL(port,[$/|T], Acc, URL) ->
parse_URL(path, T, "/", URL#url{port=list_to_integer(lists:reverse(Acc))});
parse_URL(port,[H|T], Acc, URL) ->
parse_URL(port, T, [H|Acc], URL);
+
% parse path
parse_URL(path,[], Acc, URL) ->
URL#url{path=lists:reverse(Acc)};
parse_URL(path,[$?|T], Acc, URL) ->
URL#url{path=lists:reverse(Acc), querypart=T};
parse_URL(path,[H|T], Acc, URL) ->
parse_URL(path, T, [H|Acc], URL).
+
+
+
+
+%%----------------------------------------------------------------------
+%% Func: set_msg/1 or /2 or /3
+%% Returns: #message record
+%% Purpose:
+%% unless specified, the thinktime is an exponential random var.
+%%----------------------------------------------------------------------
+set_msg(HTTPRequest) ->
+ set_msg(HTTPRequest, round(ts_stats:exponential(?messages_intensity))).
+
+
+%% if the URL is full (http://...), we parse it and get server host,
+%% port and scheme from the URL and override the global setup of the
+%% server. These informations are stored in the #message record.
+set_msg(HTTP=#http_request{url="http" ++ URL}, ThinkTime) -> % full URL
+ URLrec = parse_URL("http" ++ URL),
+ Path = URLrec#url.path ++ URLrec#url.querypart,
+ case URLrec of
+ #url{scheme= http, port = P} ->
+ case P of
+ undefined -> Port = 80;
+ Val -> Port = Val
+ end,
+ set_msg(HTTP#http_request{url=Path},
+ ThinkTime,
+ #message{ack = parse,
+ host = URLrec#url.host,
+ scheme = gen_tcp,
+ port = Port});
+ #url{scheme= https, port = P} ->
+ case P of
+ undefined -> Port = 443;
+ Val -> Port = Val
+ end,
+ set_msg(HTTP#http_request{url=Path},
+ ThinkTime,
+ #message{ack = parse,
+ host = URLrec#url.host,
+ scheme = ssl,
+ port = Port})
+ end;
+%
+set_msg(HTTPRequest, Think) -> % relative URL, use global host, port and scheme
+ set_msg(HTTPRequest, Think, #message{ack = parse}).
+
+set_msg(HTTPRequest, 0, Msg) -> % no thinktime, only wait for response
+ Msg#message{ thinktime=infinity,
+ param = HTTPRequest };
+set_msg(HTTPRequest, Think, Msg) -> % end of a page, wait before the next one
+ Msg#message{ endpage = true,
+ thinktime = Think,
+ param = HTTPRequest }.
View
114 src/ts_launcher.erl
@@ -32,24 +32,37 @@
-behaviour(gen_fsm). %% a primitive gen_fsm with two state: launcher and wait
%% External exports
--export([start/1, launch/1]).
+-export([start/0, launch/1]).
%% gen_fsm callbacks
--export([init/1, launcher/2, wait/2, handle_event/3,
+-export([init/1, launcher/2, wait/2, finish/2, handle_event/3,
handle_sync_event/4, handle_info/3, terminate/3]).
--record(state, {interarrival=[]}).
+-record(state, {interarrival=[],
+ intensity,
+ maxusers %% if maxusers are currently active, launch a
+ %% new beam to handle the new users
+ }).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-start(Opts) ->
- ?LOGF("starting ~p~n",[[Opts]], ?DEB),
- gen_fsm:start_link({local, ?MODULE}, ?MODULE, {Opts}, []).
-launch(Node) ->
+%%--------------------------------------------------------------------
+%% Function: start/0
+%%--------------------------------------------------------------------
+start() ->
+ ?LOG("starting ~n", ?DEB),
+ 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]], ?DEB),
- gen_fsm:send_event({?MODULE, Node}, launch).
+ gen_fsm:send_event({?MODULE, Node}, {launch, Arrivals}).
+
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
@@ -62,40 +75,74 @@ launch(Node) ->
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
-init({[Clients, Intensity]}) ->
+init([]) ->
{Msec, Sec, Nsec} = ts_utils:init_seed(),
random:seed(Msec,Sec,Nsec),
- {ok, wait, #state{interarrival=ts_stats:exponential(Intensity,Clients)}}.
+ {ok, wait, #state{}}.
+% {ok, wait, #state{interarrival = ts_stats:exponential(Intensity,Clients),
+% intensity = Intensity}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
-%% no more clients to launch, stop
-wait(launch, State) ->
- Nodes = nodes(),
- ?LOGF("Available nodes : ~p ~n",[Nodes],?NOTICE),
- {next_state, launcher, State, 10000}.
-
-launcher(Event, #state{interarrival = []}) ->
- ?LOG("no more clients to start, stop ~n",?DEB),
- ts_mon:stop(),
- {stop, normal, #state{}};
-
-launcher(timeout, #state{interarrival = [X |List]}) ->
- %Get one client
- Id = ts_user_server:get_idle(),%% some bench shows that this is not a bottleneck
- %set the profile of the client
- Profile = ts_profile:get_client(?config(client_type), Id),
- ts_client_sup:start_child([Profile, {?config(client_type),
- ?config(parse_type),
- ?config(mes_type),
- ?config(persistent)}]),
- ?LOGF("client launched, waiting ~p msec before launching next client",
- [X],?DEB),
- {next_state, launcher, #state{interarrival= List}, round(X)}.
+wait({launch, []}, State) ->
+ {ok, MyHostName} = ts_utils:node_to_hostname(node()),
+ ?LOGF("Launch msg receive (~p)~n",[MyHostName], ?NOTICE),
+ {ok, {[{Intensity, Users}| Rest], StartDate, Max}} =
+ ts_config_server:get_client_config(MyHostName),
+ WaitBeforeStart = ts_utils:elapsed(now(), StartDate),
+ Warm_timeout = round(ts_stats:exponential(Intensity) + WaitBeforeStart),
+ ?LOGF("Activate launcher (~p users) in ~p msec ~n",[Users, Warm_timeout], ?NOTICE),
+ {next_state, launcher, State#state{interarrival =
+ ts_stats:exponential(Intensity, Users),
+ intensity = Rest, maxusers= Max }, Warm_timeout};
+wait({launch, {Arrivals, Max}}, State) ->
+ ?LOGF("Starting with ~p users todo (max is ~p)~n",
+ [length(Arrivals), Max],?DEB),
+ {next_state, launcher, State#state{interarrival = Arrivals, maxusers=Max}, 1}.
+
+launcher(Event, State=#state{interarrival = []}) ->
+ ?LOG("no more clients to start, wait ~n",?DEB),
+ {next_state, finish, #state{}, ?check_noclient_timeout};
+
+launcher(timeout, State=#state{interarrival = [X |List]}) ->
+ ActiveClients = ts_client_sup:active_clients(),
+ case ActiveClients >= State#state.maxusers of
+ true -> %% max users reached, must start a new beam
+ ?LOGF("Max number of clients reached, must start a new beam (~p users)~n",
+ [length(List)],?NOTICE),
+ {ok, MyHostName} = ts_utils:node_to_hostname(node()),
+ ts_config_server:newbeam(list_to_atom(MyHostName),
+ {List, State#state.maxusers}),
+ NewList = [];
+ false ->
+ ?LOGF("Current clients on beam: ~p~n", [ActiveClients],?DEB),
+ NewList = List
+ end,
+ %%Get one client
+ %% Id = ts_user_server:get_idle(),%% FIXME: make it work again with new config server
+ %%set the profile of the client
+ {ok, Profile} = ts_config_server:get_next_session(),
+ ts_client_sup:start_child(Profile),
+ ?LOGF("client launched, waiting ~p msec before launching next client",
+ [X],?DEB),
+ {next_state, launcher, State#state{interarrival= NewList}, round(X)}.
+
+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
@@ -144,6 +191,7 @@ handle_info(Info, StateName, StateData) ->
%% Returns: any
%%----------------------------------------------------------------------
terminate(Reason, StateName, StatData) ->
+ ?LOGF("launcher terminating for reason~p~n",[Reason], ?INFO),
ok.
%%%----------------------------------------------------------------------
View
63 src/ts_mon.erl
@@ -33,11 +33,13 @@
-behaviour(gen_server).
--include("../include/ts_profile.hrl").
+-include("ts_profile.hrl").
+-include("ts_config.hrl").
%% External exports
-export([start/0, stop/0, newclient/1, endclient/1, newclient/1, sendmes/1,
- rcvmes/1, error/1,
+ start_clients/1,
+ rcvmes/1, error/1,
addsample/1, get_sample/1,
addcount/1, get_count/1,
addsum/1, get_sum/1,
@@ -72,6 +74,9 @@ start() ->
?LOG("starting monitor, global ~n",?NOTICE),
gen_server:start_link({global, ?MODULE}, ?MODULE, [], []).
+start_clients({Machines, Monitoring}) ->
+ gen_server:call({global, ?MODULE}, {start_clients, Machines, Monitoring}).
+
stop() ->
gen_server:cast({global, ?MODULE}, {stop}).
@@ -133,20 +138,18 @@ init([]) ->
{{Y,M,D},{H,Min,S}} = erlang:universaltime(),
Date = io_lib:format("-~w:~w:~w-~w:~w",[Y,M,D,H,Min]),
Filename = ?config(log_file) ++ Date,
+ erlang:display(?config(log_file)),
case file:open(Filename,write) of
{ok, Stream} ->
?LOG("starting monitor~n",?NOTICE),
Tab = dict:new(),
- start_launchers(?config(machines)),
- timer:apply_interval(?config(dumpstats_interval), ?MODULE, dumpstats, [] ),
- {ok, #state{type = ?config(monitoring),
- log = Stream,
- stats = Tab,
- laststats = Tab
+ {ok, #state{ log = Stream,
+ stats = Tab,
+ laststats = Tab
}};
{error, Reason} ->
?LOGF("Can't open mon log file! ~p~n",[Reason], ?ERR),
- {stop,openerror}
+ {stop, Reason}
end.
%%----------------------------------------------------------------------
@@ -158,6 +161,11 @@ init([]) ->
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
+handle_call({start_clients, Machines, Monitoring}, From, State) ->
+ timer:apply_interval(?config(dumpstats_interval), ?MODULE, dumpstats, [] ),
+ start_launchers(Machines),
+ {reply, ok, State#state{type=Monitoring}};
+
handle_call({get_sample, Type}, From, State) ->
Reply = dict:find(Type, State#state.stats),
{reply, Reply, State};
@@ -205,8 +213,6 @@ handle_cast({sum, Type, Val}, State) ->
{noreply, State#state{stats=NewTab}};
handle_cast({dumpstats}, State) ->
- DateStr = ts_utils:now_sec(),
- io:format(State#state.log,"# stats: dump at ~w~n",[DateStr]),
print_stats(State),
NewStats = reset_all_stats(State#state.stats),
{noreply, State#state{laststats = NewStats, stats= NewStats}};
@@ -289,9 +295,10 @@ handle_info(Info, State) ->
%% Returns: any (ignored by gen_server)
%%----------------------------------------------------------------------
terminate(Reason, State) ->
- ?LOG("stoping monitor~n",?NOTICE),
+ ?LOGF("stoping monitor (~p)~n",[Reason],?NOTICE),
print_stats(State),
file:close(State#state.log),
+ slave:stop(node()),
ok.
%%%----------------------------------------------------------------------
@@ -303,6 +310,8 @@ terminate(Reason, State) ->
%%----------------------------------------------------------------------
%% TODO: add a function to print stats for gnuplot ?
print_stats(State) ->
+ DateStr = ts_utils:now_sec(),
+ io:format(State#state.log,"# stats: dump at ~w~n",[DateStr]),
Res = dict:to_list(State#state.stats),
%% print number of simultaneous users
io:format(State#state.log, "stats: ~p ~p ~p~n", [users, State#state.client, State#state.maxclient]),
@@ -349,7 +358,10 @@ reset_all_stats(Dict)->
MyFun = fun (Key, OldVal) -> reset_stats(OldVal) end,
dict:map(MyFun, Dict).
-%% reset all stats except min and max
+%%----------------------------------------------------------------------
+%% Func: reset_stats/1
+%% Purpose: reset all stats except min and max
+%%----------------------------------------------------------------------
reset_stats([]) ->
[0, 0, 0, 0, 0];
reset_stats([Mean, Var, Max, Min, Count]) ->
@@ -362,26 +374,11 @@ reset_stats(Args) ->
%% start the launcher on clients nodes
%%----------------------------------------------------------------------
-%% list of machines separated by ":". Construct a list of node's atom
start_launchers(Machines) ->
- ?LOGF("Need to start tsunami client on ~s~n",[Machines], ?DEB),
- %% tokenise and remove duplicates with usort
- Hostnames = lists:usort(string:tokens( Machines, ":")),
- Atomize = fun(A) -> list_to_atom(A) end,
- HostList = lists:map(Atomize, Hostnames),
+ ?LOGF("Need to start tsunami client on ~p~n",[Machines], ?DEB),
+ GetHost = fun(A) -> list_to_atom(A#client.host) end,
+ HostList = lists:map(GetHost, Machines),
?LOGF("Hostlist is ~p~n",[HostList], ?DEB),
- Nodes = net_adm:world_list(HostList), %% get list of nodes
- ?LOGF("Connected nodes ~p~n",[Nodes], ?DEB),
- start_launchers(Nodes, node()).
-
-start_launchers([], Self) ->
- ?LOG("no more nodes~n", ?DEB),
- ok;
-start_launchers([Self | NodeList], Self) -> % don't launch in controller node
- ?LOG("skip myself ! ~n", ?DEB),
- start_launchers(NodeList, Self);
-start_launchers([Node | NodeList], Self) ->
- ?LOGF("starting launcher on ~p~n",[Node],?NOTICE),
- ts_launcher:launch(Node),
- start_launchers(NodeList, Self).
+ %% starts beam on all client hosts
+ lists:foreach({ts_config_server, newbeam}, HostList).
View
17 src/ts_profile.erl
@@ -27,34 +27,27 @@
-author('nicolas.niclausse@IDEALX.com').
%% API
--export([get_client/2, get_server/0, get_message/2, parse/3, add_dynparams/3,
+-export([get_client/2, get_message/2, parse/3, add_dynparams/3,
thinktime/0, new_session/2]).
-include("../include/ts_profile.hrl").
%%----------------------------------------------------------------------
-%% Function: get_server/0
-%% Purpose: Get server parameters
-%% Returns: tuple
-%%----------------------------------------------------------------------
-get_server() ->
- {?config(server_adr), ?config(server_port), ?config(server_protocol)}.
-
-%%----------------------------------------------------------------------
%% Function: new_session/2
%% Purpose: initialize session information (used by 'parse' clients)
%% Returns: record or []
%%----------------------------------------------------------------------
new_session(Module, parse) ->
Module:new_session();
-new_session(Module, _) ->
+new_session(Module, _Else) ->
+ ?LOGF("new session without parsing (~p)~n",[_Else],?DEB),
[].
%%----------------------------------------------------------------------
%% Function: get_client/2
%% Purpose: Generate a client session for a given protocol (Module).
%% Args: Module (module name)
%% Id
-%% Returns: List of #message
+%% Returns: {Id of sessions, Number of msg in session, local IP}
%%----------------------------------------------------------------------
get_client(Module, Id)->
@@ -73,8 +66,6 @@ get_client(Module, Id)->
get_message(Module, Param) ->
?LOGF("get_message called with args ~p ~p ~n",[Module,Param],?DEB),
Module:get_random_message(Param).
-%%TODO: utiliser le meme nom ? pourquoi ajouter random ?
-
%%----------------------------------------------------------------------
%% Function: parse/3
View
17 src/ts_req_server.erl
@@ -23,7 +23,9 @@
%% External exports
-export([start/0, get_random_req/0, get_all_req/0, stop/0, read/1]).
--export([read_sesslog/1, get_next_session/0, get_req/2]).
+-export([read_sesslog/1,
+ get_next_session/0,
+ get_req/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
@@ -35,7 +37,7 @@
total =0 %% number of sessions in ets table
}).
--include("../include/ts_profile.hrl").
+-include("ts_profile.hrl").
%%%----------------------------------------------------------------------
%%% API
@@ -53,7 +55,7 @@ start() ->
gen_server:start_link({global, ?MODULE}, ?MODULE, [], []).
get_next_session()->
- gen_server:call({global, ?MODULE},get_next_session).
+ gen_server:call({global, ?MODULE},{get_next_session, node()}).
get_req(Id, Count)->
gen_server:call({global, ?MODULE},{get_req, Id, Count}).
@@ -96,8 +98,10 @@ handle_call(get_all_req, From, State) ->
Reply = {ok, State#state.items},
{reply, Reply, State};
-%% get a new session id
-handle_call(get_next_session, From, State) ->
+%% get a new session id and an ip for the given node
+handle_call({get_next_session, Node}, From, State) ->
+%% IP = set_client_ip(Node),
+ IP = "127.0.0.1", % FIXME
Tab = State#state.table,
Total=State#state.total,
case State#state.current of
@@ -109,7 +113,7 @@ handle_call(get_next_session, From, State) ->
?LOGF("get new session for ~p~n",[From],?DEB),
case ets:lookup(Tab, {State#state.current, size}) of
[{Key, Size}] ->
- {reply, {ok, {State#state.current, Size}}, State#state{current=Next}};
+ {reply, {ok, {State#state.current, Size, IP}}, State#state{current=Next}};
Other ->
{reply, {error, Other}, State#state{current=Next}}
end;
@@ -274,4 +278,3 @@ read_item(File, L)->
end
end.
-
View
14 src/ts_session_cache.erl
@@ -26,7 +26,7 @@
total=0.0 % total number of requests
}).
--include("../include/ts_profile.hrl").
+-include("ts_profile.hrl").
%%====================================================================
%% External functions
@@ -72,14 +72,18 @@ init([]) ->
handle_call({get_req, Id, N}, From, State) ->
Tab = State#state.table,
Total = State#state.total+1,
+ ?LOGF("look for ~p th request in session ~p for ~p~n",[N,Id,From],?DEB),
case ets:lookup(Tab, {Id, N}) of
[{Key, Session}] ->
Hit = State#state.hit+1,
- ?LOGF("hitrate is ~.3f~n",[100.0*Hit/Total],?DEB),
+ ?LOGF("ok, found in cache for ~p~n",[From],?DEB),
+ ?LOGF("hitrate is ~.3f~n",[100.0*Hit/Total],?INFO),
{reply, Session, State#state{hit= Hit, total = Total}};
- [] -> %% no match, ask the req_server
- Reply = ts_req_server:get_req(Id, N),
- ets:insert(Tab, {{Id, N}, Reply}), %% cache the response FIXME: handle bad response ?
+ [] -> %% no match, ask the config_server
+ ?LOGF("not found in cache (~p th request in session ~p for ~p)~n",[N,Id,From],?DEB),
+ Reply = ts_config_server:get_req(Id, N),
+ %% cache the response FIXME: handle bad response ?
+ ets:insert(Tab, {{Id, N}, Reply}),
{reply, Reply, State#state{total = Total}};
Other -> %%
?LOGF("error ! (~p)~n",[Other],?DEB),
View
2 src/ts_sup.erl
@@ -53,7 +53,7 @@ init([]) ->
ClientsSup = {ts_client_sup, {ts_client_sup, start_link, []}, permanent, 2000,
supervisor, [ts_client_sup]},
Launcher = {ts_launcher, {ts_launcher,
- start, [[?config(nclients),?clients_intensity]]},
+ start, []},
transient, 2000, worker, [ts_launcher]},
SessionCache = {ts_session_cache, {ts_session_cache,
start, []},
View
86 src/ts_utils.erl
@@ -25,9 +25,22 @@
%% user interface
-export([debug/3, debug/4, get_val/1, init_seed/0, chop/1, elapsed/2,
- now_sec/0, inet_setopts/4,
- split_chr/2]).
+ now_sec/0, inet_setopts/4, node_to_hostname/1, add_time/2,
+ level2int/1]).
+level2int("debug") -> ?DEB;
+level2int("info") -> ?INFO;
+level2int("notice") -> ?NOTICE;
+level2int("warning") -> ?WARN;
+level2int("error") -> ?ERR;
+level2int("critical") -> ?CRIT;
+level2int("emergency") -> ?EMERG.
+
+%%----------------------------------------------------------------------
+%% Func: get_val/1
+%% Purpose: return environnement variable value for the current application
+%% Returns: Value | undef_var
+%%----------------------------------------------------------------------
get_val(Var) ->
case application:get_env(Var) of
{ok, Val} ->
@@ -37,7 +50,10 @@ get_val(Var) ->