57
57
%%% @doc: read and parse the xml config file
59
59
%%%----------------------------------------------------------------------
60
read(Filename=standard_io, LogDir) ->
61
?LOG("Reading config file from stdin~n", ?NOTICE),
63
handle_read(catch xmerl_scan:string(XML,
64
[{fetch_path,["/usr/share/tsung/","./"]},
65
{validation,true}]),Filename,LogDir);
60
66
read(Filename, LogDir) ->
61
case catch xmerl_scan:file(Filename,
62
[{fetch_path,["/usr/share/tsung/","./"]},
63
{validation,true}]) of
64
{ok, Root = #xmlElement{}} -> % xmerl-0.15
65
?LOGF("Reading config file: ~s~n", [Filename], ?NOTICE),
66
Table = ets:new(sessiontable, [ordered_set, protected]),
67
{ok, parse(Root, #config{session_tab = Table})};
68
{Root = #xmlElement{}, _Tail} -> % xmerl-0.19 and up
69
?LOGF("Reading config file: ~s~n", [Filename], ?NOTICE),
70
Table = ets:new(sessiontable, [ordered_set, protected]),
71
backup_config(LogDir, Filename, Root),
72
{ok, parse(Root, #config{session_tab = Table, proto_opts=#proto_opts{}})};
67
?LOGF("Reading config file: ~s~n", [Filename], ?NOTICE),
68
handle_read(catch xmerl_scan:file(Filename,
69
[{fetch_path,["/usr/share/tsung/","./"]},
70
{validation,true}]),Filename,LogDir).
72
handle_read( {Root = #xmlElement{}, _Tail}, Filename, LogDir) ->
73
Table = ets:new(sessiontable, [ordered_set, protected]),
74
backup_config(LogDir, Filename, Root),
75
{ok, parse(Root, #config{session_tab = Table, proto_opts=#proto_opts{}})};
76
handle_read({error,Reason},_,_) ->
78
handle_read({'EXIT',Reason},_,_) ->
79
81
%%%----------------------------------------------------------------------
80
82
%%% Function: parse/2
100
102
parse(Element = #xmlElement{name=server, attributes=Attrs}, Conf=#config{servers=ServerList}) ->
101
103
Server = getAttr(Attrs, host),
102
104
Port = getAttr(integer, Attrs, port),
103
Type = case getAttr(Attrs, type) of
105
Type = set_net_type(getAttr(Attrs, type)),
110
107
lists:foldl(fun parse/2,
111
108
Conf#config{servers = [#server{host=Server,
131
130
community, ?config(snmp_community)),
132
131
Version = getAttr(atom,SnmpEl#xmlElement.attributes,
133
132
version, ?config(snmp_version)),
134
{snmp, {Port, Community, Version}};
134
TmpConf = lists:foldl(fun parse/2, Conf#config{oids=[]}, SnmpEl#xmlElement.content),
135
{snmp, {Port, Community, Version,TmpConf#config.oids}};
136
137
{snmp, {?config(snmp_port),
137
?config(snmp_community),
138
?config(snmp_version)}}
138
?config(snmp_community),
139
?config(snmp_version),[]}}
141
142
case lists:keysearch(munin,#xmlElement.name,
159
160
Conf#config{monitor_hosts = lists:append(MHList, NewMon)},
160
161
Element#xmlElement.content);
164
parse(#xmlElement{name=oid, attributes=Attrs}, Conf=#config{oids=OIDS}) ->
165
OIDStr = getAttr(Attrs, value),
166
OID = lists:map(fun erlang:list_to_integer/1, string:tokens(OIDStr,".")),
167
Name = getAttr(atom, Attrs, name),
168
Type = case getAttr(atom, Attrs, type, sample) of
170
counter -> sample_counter;
173
Snippet = getAttr(string, Attrs, eval, "fun(X)-> X end."),
174
Fun= ts_utils:eval(Snippet),
175
true = is_function(Fun, 1),
176
Conf#config{oids=[{OID,Name,Type,Fun}| OIDS]};
163
179
parse(Element = #xmlElement{name=load, attributes=Attrs}, Conf) ->
164
180
Loop = getAttr(integer, Attrs, loop, 0),
223
239
%% must be hostname and not ip:
224
240
case ts_utils:is_ip(Host) of
226
?LOGF("ERROR: client config: 'host' attribute must be a hostname, "++
227
"not an IP ! (was ~p)~n",[Host],?EMERG),
242
io:format(standard_error,"ERROR: client config: 'host' attribute must be a hostname, "++ "not an IP ! (was ~p)~n",[Host]),
228
243
exit({error, badhostname});
230
245
%% add a new client for each CPU
255
270
?LOGF("resolving host ~p~n",[ToResolve],?WARN),
256
{ok,IPtmp} = inet:getaddr(ToResolve,inet),
271
{ok,IPtmp} = case inet:getaddr(ToResolve,inet) of
272
{error,nxdomain} -> % retry with IPv6
273
inet:getaddr(ToResolve,inet6);
259
279
?LOGF("resolved host ~p~n",[IP],?WARN),
269
289
Phase = getAttr(integer,Attrs, phase),
270
290
IDuration = getAttr(integer, Attrs, duration),
271
291
Unit = getAttr(string,Attrs, unit, "second"),
272
D = 1000 * to_seconds(Unit, IDuration),
292
D = to_milliseconds(Unit, IDuration),
273
293
case lists:keysearch(Phase,#arrivalphase.phase,AList) of
275
295
lists:foldl(fun parse/2,
280
300
Element#xmlElement.content);
281
301
_ -> % already existing phase, wrong configuration.
282
?LOGF("Client config error: phase ~p already defined, abort !~n",[Phase],?EMERG),
302
io:format(standard_error,"Client config error: phase ~p already defined, abort !~n",[Phase]),
283
303
exit({error, already_defined_phase})
290
310
Start = getAttr(float_or_integer,Attrs, start_time),
291
311
Unit = getAttr(string,Attrs, unit, "second"),
292
312
Session = getAttr(string,Attrs, session),
293
Delay = to_seconds(Unit,Start)*1000,
313
Delay = to_milliseconds(Unit,Start),
294
314
NewUsers= Users++[{Delay,Session}],
295
315
lists:foldl(fun parse/2, Conf#config{static_users = NewUsers},
296
316
Element#xmlElement.content);
309
329
exit({invalid_xml,"arrival or interarrival must be specified"});
310
330
{[], Rate} when Rate > 0 ->
311
to_seconds(Unit,Rate) / 1000;
331
Rate / to_milliseconds(Unit,1);
312
332
{InterArrival,[]} when InterArrival > 0 ->
313
1/(1000 * to_seconds(Unit,InterArrival));
333
1/to_milliseconds(Unit,InterArrival);
314
334
{_Value, _Value2} ->
315
335
exit({invalid_xml,"arrivalrate and interarrival can't be defined simultaneously"})
383
403
%%%% Parsing the 'if' element
384
404
parse(_Element = #xmlElement{name='if', attributes=Attrs,content=Content},
385
405
Conf = #config{session_tab = Tab, sessions=[CurS|_], curid=Id}) ->
386
VarName = getAttr(atom,Attrs,var),
406
VarName=get_dynvar_name(getAttr(string,Attrs,var)),
387
407
{Rel,Value} = case getAttr(string,Attrs,eq,none) of
388
408
none -> {neq,getAttr(string,Attrs,neq)};
393
413
NewConf = lists:foldl(fun parse/2, Conf#config{curid=Id+1}, Content),
394
414
NewId = NewConf#config.curid,
395
415
?LOGF("endif in session ~p as id ~p",[CurS#session.id,NewId+1],?INFO),
396
InitialAction = {ctrl_struct, {if_start, Rel, VarName, Value , NewId+1}},
416
InitialAction = {ctrl_struct, {if_start, Rel, VarName, list_to_binary(Value) , NewId+1}},
397
417
%%NewId+1 -> id of the first action after the if
398
418
ets:insert(Tab,{{CurS#session.id,Id+1},InitialAction}),
457
477
parse(_Element = #xmlElement{name=repeat,attributes=Attrs,content=Content},
458
478
Conf = #config{session_tab = Tab, sessions=[CurS|_], curid=Id}) ->
459
479
MaxRepeat = getAttr(integer,Attrs,max_repeat,20),
460
RepeatName = getAttr(atom,Attrs,name),
480
RepeatName = get_dynvar_name(getAttr(string,Attrs,name)),
461
482
[LastElement|_] = lists:reverse([E || E=#xmlElement{} <- Content]),
462
483
case LastElement of
463
484
#xmlElement{name=While,attributes=WhileAttrs}
470
491
Var = getAttr(atom,WhileAttrs,var),
471
492
NewConf = lists:foldl(fun parse/2, Conf#config{curid=Id}, Content),
472
493
NewId = NewConf#config.curid,
473
EndAction = {ctrl_struct,{repeat,RepeatName, While,Rel,Var,Value,Id+1, MaxRepeat}},
494
EndAction = {ctrl_struct,{repeat,RepeatName, While,Rel,Var,list_to_binary(Value),Id+1, MaxRepeat}},
474
495
%Id+1 -> id of the first action inside the loop
475
496
?LOGF("Add repeat action in session ~p as id ~p, Jump to: ~p",
476
497
[CurS#session.id,NewId+1,Id+1],?INFO),
535
556
Port = getAttr(integer, Attrs, port),
536
557
Store = getAttr(atom, Attrs, store, false),
537
558
Restore = getAttr(atom, Attrs, restore, false),
538
PType = case getAttr(Attrs, server_type) of
559
PType = set_net_type(getAttr(Attrs, server_type)),
544
560
SessType=case Conf#config.main_sess_type == CType of
545
561
false -> CurS#session.type;
546
562
true -> CType % back to the main type
793
809
Delimiter = getAttr(string,Attrs,delimiter,";"),
794
810
{setdynvars,file,{Order,FileId,Delimiter},Vars};
796
?LOGF("Unknown_file_id ~p in file setdynvars declaration: you forgot to add a file_server option~n",[FileId],?EMERG),
812
io:format(standard_error, "Unknown_file_id ~p in file setdynvars declaration: you forgot to add a file_server option~n",[FileId]),
797
813
exit({error, unknown_file_id})
799
815
"random_string" ->
881
897
to_seconds("hour", Val)-> Val*3600;
882
898
to_seconds("millisecond", Val)-> Val/1000.
900
to_milliseconds("second", Val)-> Val*1000;
901
to_milliseconds("minute", Val)-> Val*60000;
902
to_milliseconds("hour", Val)-> Val*3600000;
903
to_milliseconds("millisecond", Val)-> Val.
884
905
%%%----------------------------------------------------------------------
885
906
%%% Function: get_default/2
886
907
%%%----------------------------------------------------------------------
948
969
%% Use parsed config file to expand all ENTITY
950
971
%%----------------------------------------------------------------------
972
backup_config(Dir,standard_io, Config) ->
973
backup_config(Dir, "tsung_stdin.xml", Config);
951
974
backup_config(Dir, Name, Config) ->
952
975
BaseName = filename:basename(Name),
953
976
{ok,IOF}=file:open(filename:join(Dir,BaseName),[write]),
986
%% @spec read_stdio()-> string()
987
%% @doc Read config from standard input
990
read_stdio(io:get_line(""),[]).
992
read_stdio(eof, Data)->
994
read_stdio(Data,Acc) ->
995
read_stdio(io:get_line(""),[Acc,Data]).
997
set_net_type("tcp") -> gen_tcp;
998
set_net_type("tcp6") -> gen_tcp6;
999
set_net_type("udp") -> gen_udp;
1000
set_net_type("udp6") -> gen_udp6;
1001
set_net_type("ssl") -> ssl;
1002
set_net_type("ssl6") -> ssl6.
1004
get_dynvar_name(VarNameStr) ->
1005
%% check if the var name is for an array (myvar[N])
1006
case re:run(VarNameStr,"(.+)\[(\d+)\]",[{capture,all_but_first,list},dotall]) of
1007
{match,[Name,Index]} -> {list_to_atom(Name),Index};
1008
_ -> list_to_atom(VarNameStr)