%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved via the world wide web at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% The Initial Developer of the Original Code is Ericsson Utvecklings AB. %% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings %% AB. All Rights Reserved.'' %% %% $Id$ %% %%%---------------------------------------------------------------------- %%% File : inet_speed.erl %%% Author : Raimo Niskanen %%% Purpose : Speed test of Bluetail`s new inet driver. %%% Created : 29 Aug 2000 by Raimo Niskanen %%%---------------------------------------------------------------------- -module(inet_speed). -export([test/3, test/4, run/1, run/2, run_log/2, run_log/3, run_std/2, run_std/3, start/0, start/1]). %% Internal exports -export([client/6]). % For timer:tc %% Debug exports -export([combine_options/1]). -define(HDRSIZE, 4). %% Option defaults -define(TOTAL, (4096*1024)). % Total data size per test socket. -define(MIN, (8)). % Packet min size for run/*. -define(MAX, (256*1024)). % Packet max size for run/*. -define(FACTOR, (2)). % Size multiplication step -define(PARALLEL, (1)). % Number of parallel sockets -define(FILL, (147)). % Packet contents (filler) %% Debug defines %-define(DEBUG, true). %-define(TRACE, true). %-define(ERRTRACE, true). -ifdef(DEBUG). -define(DEBUG_DISPLAY(Term), erlang:display(Term)). -else. -define(DEBUG_DISPLAY(_Term), void). -endif. -ifdef(TRACE). -define(TRACE_DISPLAY(Term), io:format("~p\r\n", [Term])). -else. -define(TRACE_DISPLAY(_Term), void). -endif. -ifdef(ERRTRACE). -define(ERROR_TRACE(Error), io:format("~p:~p ~p\r\n", [?FILE, ?LINE, Error])). -else. -define(ERROR_TRACE(_Error), void). -endif. %%%---------------------------------------------------------------------- %%% Exported functions %%% %%% User interface %%% %%% Arguments: %%% Server = Node name for the other end of the test socket(s) %%% Options = list of test options, see getopt/2 further down %%% OptionsList = list of test options or (lists of options) %%%---------------------------------------------------------------------- %% Tests socket(s) with Count messages of size Size (per socket) %% Returns: {ok, Time}, time in microseconds %% {error, Reason} test(Server, Size, Count) -> test(Server, Size, Count, []). test(Server, Size, Count, Options) when atom(Server), integer(Size), Size > ?HDRSIZE, integer(Count), Count > 0, list(Options) -> do_test(Server, Size, Count, Options). %% Runs several socket tests of increasing packet size. %% Returns: {ok, [Result | ...]} %% {error, Reason, [Result | ...]}, Result for all successful tests %% so far. %% where Result = {TotalSize, PacketSize, Time}, Time like above run(Server) -> run(Server, []). run(Server, Options) when atom(Server), list(Options) -> Total = getopt(total, Options), MinSize = getopt(min, Options), MaxSize = getopt(max, Options), Factor = getopt(factor, Options), run(Server, Total, MaxSize, MinSize, Factor, Options, []). %% Runs a test run like above, and write the results to a file %% in cwd with a name derived from the test options. %% Returns: ok %% {error, Reason, [Result | ...]}, like above %% %% Also: if Servers or any of the options in OptionsList %% are combined options (see combine_option/1, below); %% runs and logs a test run for each combination of servers and options. %% %% Each test run is executed on the freshly started node Client. run_log(Client, Servers) when list(Servers) -> run_log(Client, Servers, [[]], [[]]); run_log(Client, Server) -> run_log(Client, [Server], [[]], [[]]). run_log(Client, Servers, OptionsList) when list(Servers) -> CombinedOptionsList = combine_options(OptionsList), run_log(Client, Servers, CombinedOptionsList, CombinedOptionsList); run_log(Client, Server, OptionsList) -> CombinedOptionsList = combine_options(OptionsList), run_log(Client, [Server], CombinedOptionsList, CombinedOptionsList). %% Standard test run run_std() -> run_std(inet_speed_client, 32, inet_speed_server). run_std(RemoteHost) -> run_std(RemoteHost, 32). run_std(RemoteHost, Parallel) when atom(RemoteHost), integer(Parallel), Parallel >=1 -> RemoteNode = list_to_atom("inet_speed_server@" ++ atom_to_list(RemoteHost)), run_std(inet_speed_client, Parallel, [inet_speed_server, RemoteNode]). run_std(Client, Parallel, Servers) when atom(Client), integer(Parallel), Parallel >= 1 -> io:format("~p:run_std(~p,~p,~p)\n", [?MODULE, Client, Parallel, Servers]), TotalEcho = ?TOTAL, TotalFlow = 4*TotalEcho, TotalEchoP = TotalEcho div Parallel, TotalFlowP = TotalFlow div Parallel, ListMax = 4*1024, run_log( Client, Servers, [{[{total, TotalEcho}, echo], [{total, TotalFlow}, flow], [{total, TotalEchoP}, {parallel, Parallel}, echo], [{total, TotalFlowP}, {parallel, Parallel}, flow]}, {[{max, ListMax}, list], binary}]). %% Batch entry point. Start from OS command prompt with: %% erl -sname Node -s inet_speed [start [RemoteHost [Parallel]]] %% or with: %% erl -sname Node -s inet_speed start ClientNode Parallel ServerNode1\ %% [ServerNode2 | ...] %% %% These match run_std/0..3. start() -> io:format("~p:start()\n", [?MODULE]), io:format("~p:run_std() -> ~p\n", [?MODULE, run_std()]), io:format("~p:start() halt()\n", [?MODULE]), receive after 2000 -> true end, halt(). start([RemoteHost] = Arg) when atom(RemoteHost) -> io:format("~p:start(~p)\n", [?MODULE, Arg]), io:format("~p:run_std(~p) -> ~p\n", [?MODULE, RemoteHost, run_std(RemoteHost)]), io:format("~p:start(~p) halt()\n", [?MODULE, Arg]), receive after 2000 -> true end, halt(); start([RemoteHost, Parallel] = Arg) when atom(RemoteHost), atom(Parallel) -> io:format("~p:start(~p)\n", [?MODULE, Arg]), P = list_to_integer(atom_to_list(Parallel)), io:format("~p:run_std(~p,~p) -> ~p\n", [?MODULE, RemoteHost,P , run_std(RemoteHost, P)]), io:format("~p:start(~p) halt()\n", [?MODULE, Arg]), receive after 2000 -> true end, halt(); start([Client, Parallel | Servers] = Arg) when atom(Client), atom(Parallel) -> io:format("~p:start(~p)\n", [?MODULE, Arg]), P = list_to_integer(atom_to_list(Parallel)), io:format("~p:run_std(~p,~p,~p) -> ~p\n", [?MODULE, Client, P, Servers, run_std(Client, P, Servers)]), io:format("~p:start(~p) halt()\n", [?MODULE, Arg]), receive after 2000 -> true end, halt(). %%%---------------------------------------------------------------------- %%% Local functions %%%---------------------------------------------------------------------- do_test(Server, Size, Count, Options) -> ?TRACE_DISPLAY({?MODULE, ?LINE, [Server, Size, Count, Options]}), {Host, Name} = splitnode(Server), ModuleDir = filename:dirname(code:which(?MODULE)), case slave:start_link(Host, Name, "-pa " ++ ModuleDir) of {ok, ServerNode} -> Parallel = getopt(parallel, Options), Self = self(), Pid = spawn( fun() -> ?TRACE_DISPLAY({?MODULE, ?LINE, [ServerNode, Self, Size, Count, Parallel, Options]}), Fill = getopt(fill, Options), Data = getopt(type, Options), Packet = case Data of list -> mk_list(Size - ?HDRSIZE, Fill); binary -> mk_binary(Size - ?HDRSIZE, Fill) end, Mode = getopt(mode, Options), Sockopts = [Data], Sockets = start_sockets(ServerNode, self(), Packet, Count, Sockopts, Mode, Parallel, []), wait_sockets(Self, Sockets, []) end), Mref = erlang:monitor(process, Pid), receive {Pid, Time} when pid(Pid), integer(Time) -> slave:stop(ServerNode), receive {'DOWN', Mref, _, _, _} -> Result = {ok, Time}, ?ERROR_TRACE(Result), Result end; {'DOWN', Mref, _, _, Reason} -> slave:stop(ServerNode), ?ERROR_TRACE(Reason), {error, Reason} end; {error, Reason} = Error -> ?ERROR_TRACE(Reason), Error end. wait_sockets(Parent, [], Result) -> %% Calculate mean time. {Sum, Count} = lists:foldl(fun(Time, {S, C}) -> {S+Time, C+1} end, {0, 0}, Result), Mean = (Sum+(Count bsr 2)) div Count, % Trick a roundoff Parent ! {self(), Mean}; wait_sockets(Parent, Sockets, Result) -> ?TRACE_DISPLAY({?MODULE, ?LINE, [Parent, Sockets, Result]}), receive {Pid, Time} when pid(Pid), integer(Time) -> ?TRACE_DISPLAY({?MODULE, ?LINE, [Pid, Time]}), wait_sockets(Parent, Sockets -- [Pid], [Time|Result]); Other -> ?TRACE_DISPLAY({?MODULE, ?LINE, Other}), wait_sockets(Parent, Sockets, Result) end. start_sockets(_ServerNode, _Parent, _Packet, _Count, _Sockopts, _Mode, 0, Sockets) -> Sockets; start_sockets(ServerNode, Parent, Packet, Count, Sockopts, Mode, Parallel, Sockets) -> ?TRACE_DISPLAY({?MODULE, ?LINE, [ServerNode, Parent, Count, Sockopts, Mode, Parallel, Sockets]}), Pid = spawn_link( fun() -> case server_start_link(ServerNode, Sockopts, Mode) of {ok, Address, Portnum} -> case timer:tc(?MODULE, client, [Address, Portnum, [{packet, ?HDRSIZE} | Sockopts], Packet, Count, Mode]) of {_Time, {'EXIT', Reason}} -> ?ERROR_TRACE(Reason), exit(Reason); {Time, ok} -> Parent ! {self(), Time}, ?ERROR_TRACE(Time) end; {error, Reason} -> ?ERROR_TRACE(Reason), exit(Reason) end end), start_sockets(ServerNode, Parent, Packet, Count, Sockopts, Mode, Parallel-1, [Pid | Sockets]). run(_Server, Total, MaxSize, Size, _Factor, _Options, Result) when Size > MaxSize; Size > Total -> {ok, Result}; run(Server, Total, MaxSize, Size, Factor, Options, Result) -> Count = Total div Size, io:format("~p:test(~p, ~p, ~p, ~p)\r\n", [?MODULE, Server, Size, Count, Options]), case test(Server, Size, Count, Options) of {ok, Time} -> Entry = {Size*Count, Size, Time}, run(Server, Total, MaxSize, Size * Factor, Factor, Options, [Entry | Result]); {error, Reason} -> {error, Reason, Result} end. %% Takes a list of option combinations and returns a list of %% plain option lists. %% %% Remember: a constant is a term that is neither a list nor a tuple. %% An option is an constant or a 2-tuple with a constant as first element. %% Combinations are created with non-option tuples in the combination list. %% %% Examples: %% %% combine_options([a,b,c]) -> [[a,b,c]]; %% combine_options([a,{b,2},c]) -> [[a,{b,2},c]]; %% combine_options([a,{b,c,d},e]) -> [[a,b,e],[a,c,e],[a,d,e]]; %% combine_options([a,{[b],d},e]) -> [[a,b,e],[a,d,e]]; %% combine_options([a,{[b,c],d},e]) -> [[a,b,c,e],[a,d,e]]; %% combine_options([{[echo],flow},{[list,{total,8192}],binary}]) -> %% [[echo,list,{total,8192}], %% [echo,binary], %% [flow,list,{total,8192}], %% [flow,binary]]. combine_options([{}|T]) -> % Empty combination combine_options(T); combine_options([{O,_V}=H|T]) when constant(O) -> % Option [[H|Y] || Y <- combine_options(T)]; combine_options([H|T]) when tuple(H) -> % Combination combine_options_2([[X|T] || X <- tuple_to_list(H)]); combine_options([H|T]) when list(H) -> % Sequence combine_options(H++T); combine_options([H|T]) -> % constant(H) - Option [[H|Y] || Y <- combine_options(T)]; combine_options([]) -> [[]]. combine_options_2([H|T]) -> % list(H) - Outer list is now a combination combine_options(H) ++ combine_options_2(T); combine_options_2([]) -> []. run_log(Client, [], _, _) when atom(Client) -> ok; run_log(Client, [_Server|ServerTail], [], OptionList) when atom(Client) -> run_log(Client, ServerTail, OptionList, OptionList); run_log(Client, [Server|_ServerTail] = Servers, [Options|OptionsTail], OptionsList) when atom(Client) -> ?TRACE_DISPLAY({?MODULE, ?LINE, [Client, Servers, OptionsList]}), io:format("~p:run_log() will eventually write ~p\n", [?MODULE, logfilename(Server, Options)]), {Host, Name} = splitnode(Client), ModuleDir = filename:dirname(code:which(?MODULE)), case slave:start_link(Host, Name, "-pa " ++ ModuleDir) of {ok, ClientNode} -> Self = self(), Pid = spawn(ClientNode, fun() -> case run(Server, Options) of {ok, Result} -> case write_result(Result, Server, Options) of {ok, File} -> io:format( "~p:run_log() wrote ~p\n", [?MODULE, File]), Self ! {self(), {ok, File}}; Result -> Self ! {self(), Result} end; Result -> Self ! {self(), Result} end end), Mref = erlang:monitor(process, Pid), receive {Pid, R} when pid(Pid) -> slave:stop(ClientNode), receive {'DOWN', Mref, _, _, _} -> case R of {ok, F} -> ?ERROR_TRACE(R), run_log(Client, Servers, OptionsTail, OptionsList); {error, Reason} -> ?ERROR_TRACE(Reason), {error, Reason}; Reason -> ?ERROR_TRACE(Reason), {error, Reason} end end; {'DOWN', Mref, _, _, Reason} -> slave:stop(ClientNode), ?ERROR_TRACE(Reason), {error, Reason} end; {error, Reason} -> ?ERROR_TRACE(Reason), {error, Reason} end. write_result(Result, Server, Options) -> Logfilename = logfilename(Server, Options), case file:open(Logfilename, [write]) of {ok, IoDevice} -> lists:foreach(fun(Entry) -> write_entry(IoDevice, Entry) end, Result), file:close(IoDevice), {ok, Logfilename}; {error, Reason} -> {error, Reason, Result} end. write_entry(IoDevice, Item) when tuple(Item) -> write_entry(IoDevice, tuple_to_list(Item)); write_entry(IoDevice, []) -> io:nl(IoDevice); write_entry(IoDevice, [Item]) -> io:format(IoDevice, "~p\n", [Item]); write_entry(IoDevice, [Item|Tail]) -> io:format(IoDevice, "~p\t", [Item]), write_entry(IoDevice, Tail); write_entry(IoDevice, Item) -> io:format(IoDevice, "~p\n", [Item]). logfilename(Server, Options) -> {Host, _Name} = splitnode(Server), Logfile = atom_to_list(?MODULE) ++ "_" ++ erlang:info(machine) ++ "_" ++ erlang:info(version) ++ "_" ++ atom_to_list(Host) ++ "_" ++ atom_to_list(getopt(mode, Options)) ++ "_" ++ integer_to_list(getopt(parallel, Options)) ++ "_" ++ atom_to_list(getopt(type, Options)) ++ ".log". %% echo | flow getopt(mode, Options) when list(Options) -> getopt([echo, flow], echo, Options); %% list | binary getopt(type, Options) when list(Options) -> getopt([list, binary], list, Options); %% {fill, X}, integer(X), X in the range 0 through 255 getopt(fill, Options) when list(Options) -> X = getopt(fill, ?FILL, Options), % Arbitrary data filler value if integer(X), 0 =< X, X =< 255 -> X; true -> ?FILL end; %% {total, X}, integer(X), X >= ?HDRSIZE, default ?TOTAL getopt(total, Options) when list(Options) -> X = getopt(total, ?TOTAL, Options), if integer(X), ?HDRSIZE =< X -> X; true -> ?HDRSIZE end; %% {min, X}, integer(X), X >= ?HDRSIZE, default ?MIN getopt(min, Options) when list(Options) -> X = getopt(min, ?MIN, Options), if integer(X), ?HDRSIZE =< X -> X; true -> ?HDRSIZE end; %% {max, X}, integer(X), X >= ?HDRSIZE, default ?MAX getopt(max, Options) when list(Options) -> X = getopt(max, ?MAX, Options), if integer(X), ?HDRSIZE =< X -> X; true -> ?HDRSIZE end; %% {factor, X}, integer(X), X >= 2, default 2 getopt(factor, Options) when list(Options) -> X = getopt(factor, ?FACTOR, Options), if integer(X), 2 =< X -> X; true -> 2 end; %% {parallel, X}, integer(X), X >= 1, default 1 getopt(parallel, Options) when list(Options) -> X = getopt(parallel, ?PARALLEL, Options), if integer(X), 1 =< X -> X; true -> 1 end. %% Atomic options, first found wins getopt([], Default, Options) when list(Options) -> Default; getopt([Tag|Tail], Default, Options) when list(Options) -> case lists:member(Tag, Options) of true -> Tag; false -> getopt(Tail, Default, Options) end; % 2-tuple options; {Tag, Value} getopt(Tag, Default, Options) when atom(Tag), list(Options) -> case lists:keysearch(Tag, 1, Options) of {value, {_, Value}} -> Value; _ -> Default end. splitnode(NodeName) when atom(NodeName) -> {Name, Host} = case lists:splitwith(fun(X)-> X/=$@ end, atom_to_list(NodeName)) of {N, [$@ | H]} -> {N, H}; {N, H} -> {N, H} end, case Host of [] -> case lists:splitwith(fun(X)-> X/=$@ end, atom_to_list(node())) of {_, [$@ | M]} -> {list_to_atom(M), list_to_atom(Name)}; _ -> {list_to_atom(Host), list_to_atom(Name)} end; _ -> {list_to_atom(Host), list_to_atom(Name)} end. %%%---------------------------------------------------------------------- %%% Local functions %%% %%% Server side %%%---------------------------------------------------------------------- server_start_link(Node, Options, Mode) when atom(Node), list(Options) -> GroupLeader = group_leader(), Self = self(), Pid = spawn_link( Node, fun() -> group_leader(GroupLeader, self()), ?TRACE_DISPLAY({?MODULE, ?LINE, [Self, Options]}), {ok, ListenSocket} = gen_tcp:listen(0, Options ++ [{active, false}]), {ok, Portnum} = inet:port(ListenSocket), {ok, Address} = inet:gethostname(), ?DEBUG_DISPLAY({?MODULE, ?LINE}), Self ! {self(), socket, Address, Portnum}, server_accept(ListenSocket, Mode) end), receive {Pid, socket, Address, Portnum} -> {ok, Address, Portnum} end. server_accept(ListenSocket, Mode) -> ?TRACE_DISPLAY({?MODULE, server_accept, [ListenSocket]}), {ok, Socket} = gen_tcp:accept(ListenSocket), ok = gen_tcp:close(ListenSocket), case Mode of echo -> ?TRACE_DISPLAY({?MODULE, server_accept, [ListenSocket], server_echo_loop, [Socket]}), server_echo_loop(Socket); flow -> ?TRACE_DISPLAY({?MODULE, server_accept, [ListenSocket], server_flow_loop, [Socket]}), server_flow_loop(Socket) end. server_echo_loop(Socket) -> case gen_tcp:recv(Socket, 0) of {ok, Packet} -> ?DEBUG_DISPLAY({?MODULE, ?LINE}), case gen_tcp:send(Socket, Packet) of ok -> server_echo_loop(Socket); {error, closed} -> ?TRACE_DISPLAY({?MODULE, server_echo_loop, [Socket], done}), done; {error, enotconn} -> ?TRACE_DISPLAY({?MODULE, server_echo_loop, [Socket], done}), done; {error, Reason} -> ?TRACE_DISPLAY({?MODULE, server_echo_loop, [Socket], {error, Reason}, ?LINE}), gen_tcp:close(Socket), ?ERROR_TRACE(Reason), exit(Reason) end; {error, closed} -> ?TRACE_DISPLAY({?MODULE, server_echo_loop, [Socket], done}), done; {error, Reason} -> ?TRACE_DISPLAY({?MODULE, server_echo_loop, [Socket], {error, Reason}, ?LINE}), gen_tcp:close(Socket), ?ERROR_TRACE(Reason), exit(Reason) end. server_flow_loop(Socket) -> case gen_tcp:recv(Socket, 0) of {ok, _Packet} -> server_flow_loop(Socket); {error, closed} -> ?TRACE_DISPLAY({?MODULE, server_flow_loop, [Socket], done}), done; {error, Reason} -> ?TRACE_DISPLAY({?MODULE, server_flow_loop, [Socket], {error, Reason}}), gen_tcp:close(Socket), ?ERROR_TRACE(Reason), exit(Reason) end. %%%---------------------------------------------------------------------- %%% Local functions %%% %%% Client side %%%---------------------------------------------------------------------- %%% Unfortunately exported for timer:tc/3 client(Address, Portnum, Options, Data, Count, Mode) -> ?TRACE_DISPLAY({?MODULE, client, [Address, Portnum, Options, data, Count]}), {ok, Socket} = gen_tcp:connect(Address, Portnum, Options ++ [{active, false}]), case Mode of echo -> ?TRACE_DISPLAY({?MODULE, client, [], client_echo_loop, [Socket, data, Count]}), client_echo_loop(Socket, Data, Count); flow -> ?TRACE_DISPLAY({?MODULE, client, [], client_flow_loop, [Socket, data, Count]}), client_flow_loop(Socket, Data, Count) end. client_echo_loop(Socket, _Data, 0) -> ?TRACE_DISPLAY({?MODULE, client_echo_loop, [Socket, data, 0], ok}), gen_tcp:close(Socket), ok; client_echo_loop(Socket, Data, Count) when integer(Count), Count > 0 -> case gen_tcp:send(Socket, Data) of ok -> case gen_tcp:recv(Socket, 0) of {ok, RecvData} -> ?DEBUG_DISPLAY({?MODULE, ?LINE}), client_echo_loop(Socket, Data, Count - 1); {error, Reason} -> ?TRACE_DISPLAY({?MODULE, client_echo_loop, [], {error, Reason}, ?LINE}), gen_tcp:close(Socket), ?ERROR_TRACE(Reason), exit(Reason) end; {error, Reason} -> ?TRACE_DISPLAY({?MODULE, client_echo_loop, [], {error, Reason}, ?LINE}), gen_tcp:close(Socket), ?ERROR_TRACE(Reason), exit(Reason) end. client_flow_loop(Socket, _Data, 0) -> ?TRACE_DISPLAY({?MODULE, client_flow_loop, [Socket, data, 0], ok}), gen_tcp:close(Socket), ok; client_flow_loop(Socket, Data, Count) when integer(Count), Count > 0 -> case gen_tcp:send(Socket, Data) of ok -> client_flow_loop(Socket, Data, Count - 1); {error, Reason} -> ?TRACE_DISPLAY({?MODULE, client_flow_loop, [], {error, Reason}, ?LINE}), gen_tcp:close(Socket), ?ERROR_TRACE(Reason), exit(Reason) end. %%%---------------------------------------------------------------------- %%% Local functions %%% %%% Utility functions %%%---------------------------------------------------------------------- mk_list(0, Fill) when integer(Fill), 0 =< Fill, Fill =< 255 -> []; mk_list(1, Fill) when integer(Fill), 0 =< Fill, Fill =< 255 -> [Fill]; mk_list(Size, Fill) when integer(Size), Size >= 0, integer(Fill), 0 =< Fill, Fill =< 255 -> HalfList = mk_list(Size div 2, Fill), case Size rem 2 of 1 -> [Fill, HalfList | HalfList]; 0 -> [HalfList | HalfList] end. mk_binary(Size, Fill) when integer(Size), Size >= 0, integer(Fill), 0 =< Fill, Fill =< 255 -> list_to_binary(mk_list(Size, Fill)).