%%%-------------------------------------------------------------------
%% %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_transport.hrl").
-include_lib("ssh/src/ssh_connect.hrl").
-include_lib("ssh/src/ssh_userauth.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 = find_algs(List),
ct:pal("Algorithms = ~p~n~nTimes = ~p",[Algs,Times]),
lists:foreach(
fun({Tag,Value,Unit}) ->
EventData =
case Tag of
{A,B} when A==encrypt ; A==decrypt ->
[{value, Value},
{suite, ?MODULE},
{name, mk_name(["Cipher ",A," ",B," [",Unit,"]"])}
];
kex ->
[{value, Value},
{suite, ?MODULE},
{name, mk_name(["Erl server kex ",Algs#alg.kex," [",Unit,"]"])}
];
_ when is_atom(Tag) ->
[{value, Value},
{suite, ?MODULE},
{name, mk_name(["Erl server ",Tag," [",Unit,"]"])}
]
end,
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.
%%%================================================================
mk_name(Name) -> [char(C) || C <- lists:concat(Name)].
char($-) -> $_;
char(C) -> C.
%%%----------------------------------------------------------------
find_times(L) ->
Xs = [accept_to_hello, kex, kex_to_auth, auth, to_prompt],
[find_time(X,L) || X <- Xs] ++
crypto_algs_times_sizes([encrypt,decrypt], L).
-record(call, {
mfa,
pid,
t_call,
t_return,
args,
result
}).
%%%----------------
-define(send(M), fun(C=#call{mfa = {ssh_message,encode,1},
args = [M]}) ->
C#call.t_return
end).
-define(recv(M), fun(C=#call{mfa = {ssh_message,decode,1},
result = M}) ->
C#call.t_call
end).
find_time(accept_to_hello, L) ->
[T0,T1] = find([fun(C=#call{mfa = {ssh_acceptor,handle_connection,5}}) ->
C#call.t_call
end,
fun(C=#call{mfa = {ssh_connection_handler,hello,_},
args = [socket_control|_]}) ->
C#call.t_return
end
], L, []),
{accept_to_hello, now2micro_sec(now_diff(T1,T0)), microsec};
find_time(kex, L) ->
[T0,T1] = find([fun(C=#call{mfa = {ssh_connection_handler,hello,_},
args = [socket_control|_]}) ->
C#call.t_call
end,
?send(#ssh_msg_newkeys{})
], L, []),
{kex, now2micro_sec(now_diff(T1,T0)), microsec};
find_time(kex_to_auth, L) ->
[T0,T1] = find([?send(#ssh_msg_newkeys{}),
?recv(#ssh_msg_userauth_request{})
], L, []),
{kex_to_auth, now2micro_sec(now_diff(T1,T0)), microsec};
find_time(auth, L) ->
[T0,T1] = find([?recv(#ssh_msg_userauth_request{}),
?send(#ssh_msg_userauth_success{})
], L, []),
{auth, now2micro_sec(now_diff(T1,T0)), microsec};
find_time(to_prompt, L) ->
[T0,T1] = find([fun(C=#call{mfa = {ssh_acceptor,handle_connection,5}}) ->
C#call.t_call
end,
?recv(#ssh_msg_channel_request{request_type="env"})
], L, []),
{to_prompt, now2micro_sec(now_diff(T1,T0)), microsec}.
find([F|Fs], [C|Cs], Acc) when is_function(F,1) ->
try
F(C)
of
T -> find(Fs, Cs, [T|Acc])
catch
_:_ -> find([F|Fs], Cs, Acc)
end;
find([], _, Acc) ->
lists:reverse(Acc).
find_algs(L) ->
{value,#call{result={ok,Algs}}} =
lists:keysearch({ssh_transport,select_algorithm,3}, #call.mfa, L),
Algs.
%%%----------------
crypto_algs_times_sizes(EncDecs, L) ->
Raw = [{_Algorithm = case EncDec of
encrypt -> {encrypt,S#ssh.encrypt};
decrypt -> {decrypt,S#ssh.decrypt}
end,
size(Data),
now2micro_sec(now_diff(T1, T0))
}
|| EncDec <- EncDecs,
#call{mfa = {ssh_transport,ED,2},
args = [S,Data],
t_call = T0,
t_return = T1} <- L,
ED == EncDec
],
[{Alg, round(1024*Time/Size), "microsec per kbyte"} % Microseconds per 1k bytes.
|| {Alg,Size,Time} <- lists:foldl(fun increment/2, [], Raw)].
increment({Alg,Sz,T}, [{Alg,SumSz,SumT}|Acc]) ->
[{Alg,SumSz+Sz,SumT+T} | Acc];
increment(Spec, [X|Acc]) ->
[X | increment(Spec,Acc)]; % Not so many Alg, 2 or 3
increment({Alg,Sz,T},[]) ->
[{Alg,Sz,T}].
%%%----------------------------------------------------------------
%%%
%%% API for the traceing
%%%
get_trace_list(TracerPid) ->
TracerPid ! {get_trace_list,self()},
receive
{trace_list,L} -> {ok, pair_events(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))
|| MFA <- [{ssh_acceptor,handle_connection,5},
{ssh_connection_handler,hello,2},
{ssh_message,encode,1},
{ssh_message,decode,1},
{ssh_transport,select_algorithm,3},
{ssh_transport,encrypt,2},
{ssh_transport,decrypt,2}
]],
{ok, TracerPid}.
tp({_M,_F,Arity}) ->
[{lists:duplicate(Arity,'_'), [], [{return_trace}]}].
%%%----------------------------------------------------------------
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
{get_trace_list, From} ->
From ! {trace_list, L},
trace_loop(L);
Ev ->
trace_loop([Ev|L])
end.
pair_events(L) ->
pair_events(L, []).
pair_events([{trace_ts,Pid,call,{M,F,Args},TS0} | L], Acc) ->
Arity = length(Args),
{ReturnValue,TS1} = find_return(Pid, {M,F,Arity}, L),
pair_events(L, [#call{mfa = {M,F,Arity},
pid = Pid,
t_call = TS0,
t_return = TS1,
args = Args,
result = ReturnValue} | Acc]);
pair_events([_|L], Acc) ->
pair_events(L, Acc);
pair_events([], Acc) ->
lists:reverse(Acc).
find_return(Pid, MFA,
[{trace_ts, Pid, return_from, MFA, ReturnValue, TS}|_]) ->
{ReturnValue, TS};
find_return(Pid, MFA, [_|L]) ->
find_return(Pid, MFA, L);
find_return(_, _, []) ->
{undefined, undefined}.
%%%----------------------------------------------------------------
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}.