%%%-------------------------------------------------------------------
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2015. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(ssh_benchmark_SUITE).
-compile(export_all).
-include_lib("common_test/include/ct_event.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("ssh/src/ssh.hrl").
-include_lib("ssh/src/ssh_connect.hrl").
suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
%%suite() -> [{ct_hooks,[ts_install_cth]}].
all() -> [{group, opensshc_erld}
%% {group, erlc_opensshd}
].
groups() ->
[{opensshc_erld, [{repeat, 3}], [openssh_client_shell]},
{erlc_opensshd, [{repeat, 3}], [erl_shell]}
].
init_per_suite(Config) ->
catch ssh:stop(),
catch crypto:stop(),
try
ok = crypto:start(),
ok = ssh:start(),
{ok,TracerPid} = erlang_trace(),
[{tracer_pid,TracerPid} | Config]
catch
C:E ->
{skip, io_lib:format("Couldn't start ~p:~p",[C,E])}
end.
end_per_suite(_Config) ->
catch ssh:stop(),
catch crypto:stop(),
ok.
init_per_group(opensshc_erld, Config) ->
case ssh_test_lib:ssh_type() of
openSSH ->
DataDir = ?config(data_dir, Config),
UserDir = ?config(priv_dir, Config),
ssh_test_lib:setup_dsa(DataDir, UserDir),
ssh_test_lib:setup_rsa(DataDir, UserDir),
ssh_test_lib:setup_ecdsa("256", DataDir, UserDir),
[{c_kexs, ssh_test_lib:sshc(kex)},
{c_ciphers, ssh_test_lib:sshc(cipher)}
| Config];
_ ->
{skip, "No OpenSsh client found"}
end;
init_per_group(erlc_opensshd, _) ->
{skip, "Group erlc_opensshd not implemented"};
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, _Config) ->
ok.
init_per_testcase(_Func, Conf) ->
Conf.
end_per_testcase(_Func, _Conf) ->
ok.
%%%================================================================
openssh_client_shell(Config) ->
SystemDir = ?config(data_dir, Config),
UserDir = ?config(priv_dir, Config),
KnownHosts = filename:join(UserDir, "known_hosts"),
{ok, TracerPid} = erlang_trace(),
{ServerPid, _Host, Port} =
ssh_test_lib:daemon([{system_dir, SystemDir},
{public_key_alg, ssh_dsa},
{failfun, fun ssh_test_lib:failfun/2}]),
ct:sleep(500),
Data = lists:duplicate(100000, $a),
Cmd = lists:concat(["ssh -p ",Port,
" -o UserKnownHostsFile=", KnownHosts,
" -o \"StrictHostKeyChecking no\"",
" localhost '\"",Data,"\"'."]),
%% ct:pal("Cmd ="++Cmd),
Parent = self(),
SlavePid = spawn(fun() ->
Parent ! {self(),os:cmd(Cmd)}
end),
receive
{SlavePid, ClientResponse} ->
%% ct:pal("ClientResponse = ~p",[ClientResponse]),
{ok, List} = get_trace_list(TracerPid),
Times = find_times(List),
Algs = proplists:get_value(algorithms, List, #alg{}),
ct:pal("List = ~p~n~nAlgorithms = ~p~n~nTimes = ~p",[List,Algs,Times]),
lists:foreach(
fun({Tag0,MicroSeconds,Unit}) ->
Tag = case Tag0 of
{A,B} -> lists:concat([A," ",B]);
_ when is_list(Tag0) -> lists:concat(Tag0);
_ when is_atom(Tag0) -> Tag0
end,
DataName =
["Erl server ",Tag,sp(algo(Tag,Algs))," [",Unit,"]"],
EventData = [{value, MicroSeconds},
{suite, ?MODULE},
{name, lists:concat(DataName)}
],
ct:pal("ct_event:notify ~p",[EventData]),
ct_event:notify(#event{name = benchmark_data,
data = EventData})
end, Times),
ssh:stop_daemon(ServerPid),
ok
after 10000 ->
ssh:stop_daemon(ServerPid),
exit(SlavePid, kill),
{fail, timeout}
end.
algo(kex, #alg{kex=Alg} ) -> Alg;
algo(_, _) -> "".
sp("") -> "";
sp(A) -> lists:concat([" ",A]).
%%%================================================================
find_times(L) ->
[{accept_to_hello, find_time([tcp_accept,
{send,hello}], L, [])/1000,
millisec},
{kex, find_time([{send,hello},
{send,ssh_msg_newkeys}], L, []),
microsec},
{kex_to_auth, find_time([{send,ssh_msg_newkeys},
{recv,ssh_msg_userauth_request}], L, []),
microsec},
{auth, find_time([{recv,ssh_msg_userauth_request},
{send,ssh_msg_userauth_success}], L, []),
microsec},
{to_prompt, find_time([tcp_accept,
{recv,{ssh_msg_channel_request,"env"}}], L, []),
microsec}
| alg_times([encrypt,decrypt], L)
].
find_time([Event|Events], [{Event,T}|TraceList], Ts) ->
%% Important that the first one found is used!
find_time(Events, TraceList, [T|Ts]);
find_time([], _, [T1,T0]) ->
now2micro_sec(now_diff(T1,T0));
find_time(Events, [_|TraceList], Ts) ->
find_time(Events, TraceList, Ts);
find_time(_, [], _Ts) ->
throw({error,not_found}).
alg_times(Ops, L) ->
OpAlgs = lists:usort([{Op,Alg} || Op <- Ops,
{{{Op,Alg},_,_},_} <- L]),
[begin
{[Op,"(",Alg,")"],
sum_times(OpAlg, L, 0, 0),
"microsec/kbyte"
}
end || {Op,Alg} = OpAlg <- OpAlgs].
sum_times(T, [{{T,start,Id={_,Nbytes}},TS0}|Events], SumBytes, SumMicroSec) ->
TS1 = proplists:get_value({T,stop,Id}, Events),
sum_times(T, Events, SumBytes+Nbytes, SumMicroSec+now2micro_sec(now_diff(TS1,TS0)));
sum_times(T, [_|Events], SumBytes, SumMicroSec) ->
sum_times(T, Events, SumBytes, SumMicroSec);
sum_times(T, [], SumBytes, SumMicroSec) ->
round(1024*SumMicroSec / SumBytes). % Microseconds per 1k bytes.
%%%----------------------------------------------------------------
%%%
%%% API for the traceing
%%%
get_trace_list(TracerPid) ->
TracerPid ! {get_trace_list,self()},
receive
{trace_list,L} -> {ok,lists:reverse(L)}
after 5000 -> {error,no_reply}
end.
erlang_trace() ->
TracerPid = spawn(fun trace_loop/0),
0 = erlang:trace(new, true, [call,timestamp,{tracer,TracerPid}]),
[init_trace(MFA, TP)
|| {MFA,TP} <- [{{ssh_acceptor,handle_connection,5}, []},
{{ssh_connection_handler,hello,2}, []},
{{ssh_message,encode,1}, []},
{{ssh_message,decode,1}, [{['_'], [], [{return_trace}]}]},
{{ssh_transport,select_algorithm,3}, [{['_','_','_'], [], [{return_trace}]}]},
{{ssh_transport,encrypt,2}, [{['_','_'], [], [{return_trace}]}]},
{{ssh_transport,decrypt,2}, [{['_','_'], [], [{return_trace}]}]}
]],
{ok, TracerPid}.
%%%----------------
init_trace(MFA = {Module,_,_}, TP) ->
case code:is_loaded(Module) of
false -> code:load_file(Module);
_ -> ok
end,
erlang:trace_pattern(MFA, TP, [local]).
trace_loop() ->
trace_loop([]).
trace_loop(L) ->
receive
{trace_ts, Pid, call, {M,F,Args}, TS} = Ev ->
cond_pal(Ev),
trace_loop(save_event(call, Pid, {M,F,Args}, TS, L));
{trace_ts, Pid, return_from, {M,F,Arity}, Ret, TS} = Ev ->
cond_pal(Ev),
trace_loop(save_event(return_from, Pid, {M,F,Arity,Ret}, TS, L));
{get_trace_list, From} ->
From ! {trace_list, L},
trace_loop(L)
; Other -> io:format('~p got ~p~n',[self(),Other]), trace_loop(L)
end.
%%cond_pal(Ev) -> ct:pal("~p",[Ev]).
cond_pal(Ev) -> ok.
save_event(_Type, _Pid, MFA, TimeStamp, L) ->
try
event_name(MFA)
of
{Tag, 'TS'} -> [{Tag,TimeStamp} | L];
Val -> [Val | L]
catch
_:_ -> L
end.
event_name({ssh_acceptor,handle_connection,_}) -> {tcp_accept, 'TS'};
event_name({ssh_connection_handler,hello,[socket_control|_]}) -> {{send,hello}, 'TS'};
event_name({ssh_connection_handler,hello,[{version_exchange,_}|_]}) -> {{recv,hello}, 'TS'};
event_name({ssh_message,encode,[Msg]}) -> {{send,element(1,Msg)}, 'TS'};
event_name({ssh_message,decode,1,
#ssh_msg_channel_request{request_type=ReqType}}) -> {{recv,{ssh_msg_channel_request,ReqType}}, 'TS'};
event_name({ssh_message,decode,1,Return}) -> {{recv,element(1,Return)}, 'TS'};
event_name({ssh_transport,select_algorithm,3,{ok,Algs}}) -> {algorithms,Algs};
event_name({ssh_transport,encrypt,[S,Data]}) -> {{{encrypt,S#ssh.encrypt},start, {S#ssh.send_sequence,size(Data)}}, 'TS'};
event_name({ssh_transport,encrypt,2,{S,Ret}}) -> {{{encrypt,S#ssh.encrypt},stop, {S#ssh.send_sequence,size(Ret) }}, 'TS'};
event_name({ssh_transport,decrypt,[S,Data]}) -> {{{decrypt,S#ssh.decrypt},start, {S#ssh.recv_sequence,size(Data)}}, 'TS'};
event_name({ssh_transport,decrypt,2,{S,Ret}}) -> {{{decrypt,S#ssh.decrypt},stop, {S#ssh.recv_sequence,size(Ret) }}, 'TS'}.
now2sec({A,B,C}) -> A*1000000 + B + C/1000000.
now2micro_sec({A,B,C}) -> (A*1000000 + B)*1000000 + C.
now_diff({A1,B1,C1}, {A0,B0,C0}) -> {A1-A0, B1-B0, C1-C0}.