1%%%------------------------------------------------------------------- 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2015-2020. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(ssh_bench_SUITE). 21 22-export([ 23 suite/0, 24 all/0, 25 init_per_suite/1, 26 end_per_suite/1, 27 init_per_testcase/2, 28 end_per_testcase/2 29 ]). 30 31-export([ 32 connect/1, 33 transfer_text/1, 34 send_wait_acc/3 35 ]). 36 37-include_lib("common_test/include/ct_event.hrl"). 38-include_lib("common_test/include/ct.hrl"). 39 40-include("ssh.hrl"). 41-include("ssh_transport.hrl"). 42-include("ssh_connect.hrl"). 43-include("ssh_auth.hrl"). 44 45%%%================================================================ 46%%% 47%%% Suite declarations 48%%% 49 50suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}, 51 {timetrap,{minutes,1}} 52 ]. 53all() -> [connect, 54 transfer_text 55 ]. 56 57-define(UID, "foo"). 58-define(PWD, "bar"). 59-define(Nruns, 8). 60 61%%%================================================================ 62%%% 63%%% Init per suite 64%%% 65 66init_per_suite(Config) -> 67 catch ssh:stop(), 68 try 69 ok = ssh:start() 70 of 71 ok -> 72 DataSize = 1000000, 73 SystemDir = proplists:get_value(data_dir, Config), 74 Algs = ssh:default_algorithms(), 75 {_ServerPid, _Host, Port} = 76 ssh_test_lib:daemon([{system_dir, SystemDir}, 77 {user_passwords, [{?UID,?PWD}]}, 78 {failfun, fun ssh_test_lib:failfun/2}, 79 {preferred_algorithms, Algs}, 80 {modify_algorithms,[{prepend,[{cipher,[none]}, 81 {mac,[none]} 82 ]} 83 %% ,{rm, [{cipher,['aes256-gcm@openssh.com', 84 %% 'aes128-gcm@openssh.com']} 85 %% ]} 86 ]}, 87 {max_random_length_padding, 0}, 88 {subsystems, [{"/dev/null", {ssh_bench_dev_null,[DataSize]}}]} 89 ]), 90 [{host,"localhost"}, {port,Port}, {uid,?UID}, {pwd,?PWD}, {data_size,DataSize} | Config] 91 catch 92 C:E -> 93 {skip, io_lib:format("Couldn't start ~p:~p",[C,E])} 94 end. 95 96end_per_suite(_Config) -> 97 catch ssh:stop(), 98 ok. 99 100%%%================================================================ 101%%% 102%%% Init per testcase 103%%% 104 105init_per_testcase(_Func, Conf) -> 106 Conf. 107 108end_per_testcase(_Func, _Conf) -> 109 ok. 110 111%%%================================================================ 112%%% 113%%% Testcases 114%%% 115 116%%%---------------------------------------------------------------- 117%%% Measure the time for an Erlang client to connect to an Erlang 118%%% server on the localhost 119 120connect(Config) -> 121 KexAlgs = proplists:get_value(kex, ssh:default_algorithms()), 122 ct:log("KexAlgs = ~p",[KexAlgs]), 123 lists:foreach( 124 fun(KexAlg) -> 125 PrefAlgs = preferred_algorithms(KexAlg), 126 TimeMicroSec = measure_connect(Config, 127 [{preferred_algorithms,PrefAlgs}]), 128 report(["Connect erlc erld ",KexAlg," [connects per sec]"], 129 1000000 / TimeMicroSec) 130 end, KexAlgs). 131 132 133measure_connect(Config, Opts) -> 134 Port = proplists:get_value(port, Config), 135 ConnectOptions = [{user, proplists:get_value(uid, Config)}, 136 {password, proplists:get_value(pwd, Config)}, 137 {user_dir, proplists:get_value(priv_dir, Config)}, 138 {silently_accept_hosts, true}, 139 {user_interaction, false}, 140 {max_random_length_padding, 0} 141 ] ++ Opts, 142 median( 143 [begin 144 {Time, {ok,Pid}} = timer:tc(ssh,connect,["localhost", Port, ConnectOptions]), 145 ssh:close(Pid), 146 Time % in µs 147 end || _ <- lists:seq(1,?Nruns)]). 148 149%%%---------------------------------------------------------------- 150%%% Measure the time to transfer a set of data with 151%%% and without crypto 152 153transfer_text(Config) -> 154 Port = proplists:get_value(port, Config), 155 Options = [{user, proplists:get_value(uid, Config)}, 156 {password, proplists:get_value(pwd, Config)}, 157 {user_dir, proplists:get_value(priv_dir, Config)}, 158 {silently_accept_hosts, true}, 159 {save_accepted_host, false}, 160 {user_interaction, false}, 161 {max_random_length_padding, 0} 162 ], 163 Data = gen_data(proplists:get_value(data_size,Config)), 164 165 [connect_measure(Port, Crypto, Mac, Data, Options) 166 || {Crypto,Mac} <- [{ none, none}, 167 {'aes128-ctr', 'hmac-sha1'}, 168 {'aes256-ctr', 'hmac-sha1'}, 169{'aes128-gcm@openssh.com', 'hmac-sha1'}, 170{'chacha20-poly1305@openssh.com', 'hmac-sha1'}, 171 {'aes128-cbc', 'hmac-sha1'}, 172 {'3des-cbc', 'hmac-sha1'}, 173 {'aes128-ctr', 'hmac-sha2-256'}, 174 {'aes128-ctr', 'hmac-sha2-512'} 175 ], 176 crypto_mac_supported(Crypto,Mac)]. 177 178 179crypto_mac_supported(none, none) -> 180 true; 181crypto_mac_supported(C, M) -> 182 Algs = ssh:default_algorithms(), 183 [{_,Cs},_] = proplists:get_value(cipher, Algs), 184 [{_,Ms},_] = proplists:get_value(mac, Algs), 185 lists:member(C,Cs) andalso lists:member(M,Ms). 186 187 188gen_data(DataSz) -> 189 Data0 = << <<C>> || _ <- lists:seq(1,DataSz div 256), 190 C <- lists:seq(0,255) >>, 191 Data1 = << <<C>> || C <- lists:seq(0,(DataSz rem 256) - 1) >>, 192 <<Data0/binary, Data1/binary>>. 193 194 195connect_measure(Port, Cipher, Mac, Data, Options) -> 196 _AES_GCM = {cipher, 197 []}, 198 %% ['aes256-gcm@openssh.com', 199 %% 'aes128-gcm@openssh.com']}, 200 201 AlgOpt = case {Cipher,Mac} of 202 {none,none} -> 203 [{modify_algorithms,[{prepend, [{cipher,[Cipher]}, 204 {mac,[Mac]}]} 205%%% ,{rm,[_AES_GCM]} 206 ]}]; 207 {none,_} -> 208 [{modify_algorithms,[{prepend, [{cipher,[Cipher]}]} 209%%% ,{rm,[_AES_GCM]} 210 ]}, 211 {preferred_algorithms, [{mac,[Mac]}]}]; 212 {_,none} -> 213 [{modify_algorithms,[{prepend, [{mac,[Mac]}]} 214%%% ,{rm,[_AES_GCM]} 215 ]}, 216 {preferred_algorithms, [{cipher,[Cipher]}]}]; 217 _ -> 218 [{preferred_algorithms, [{cipher,[Cipher]}, 219 {mac,[Mac]}]} 220%%% ,{modify_algorithms, [{rm,[_AES_GCM]}]} 221 ] 222 end, 223 Times = 224 [begin 225 {ok,C} = ssh:connect("localhost", Port, AlgOpt ++ Options), 226 {ok,Ch} = ssh_connection:session_channel(C, 10000), 227 success = ssh_connection:subsystem(C, Ch, "/dev/null", 10000), 228 {Time,ok} = timer:tc(?MODULE, send_wait_acc, [C, Ch, Data]), 229 ok = ssh_connection:send_eof(C, Ch), 230 ssh:close(C), 231 Time 232 end || _ <- lists:seq(1,?Nruns)], 233 report(["Transfer ",Cipher,"/",Mac," [Mbyte per sec]"], 234 1000000 / median(Times)). 235 236send_wait_acc(C, Ch, Data) -> 237 ssh_connection:send(C, Ch, Data), 238 receive 239 {ssh_cm, C, {data, Ch, 0, <<"READY">>}} -> ok 240 end. 241 242 243%%%================================================================ 244%%% 245%%% Private 246%%% 247 248%%%---------------------------------------------------------------- 249preferred_algorithms(KexAlg) -> 250 [{kex, [KexAlg]}, 251 {public_key, ['rsa-sha2-256']}, 252 {cipher, ['aes128-ctr']}, 253 {mac, ['hmac-sha1']}, 254 {compression, [none]} 255 ]. 256 257%%%---------------------------------------------------------------- 258median(Data) when is_list(Data) -> 259 SortedData = lists:sort(Data), 260 N = length(Data), 261 Median = 262 case N rem 2 of 263 0 -> 264 MeanOfMiddle = (lists:nth(N div 2, SortedData) + 265 lists:nth(N div 2 + 1, SortedData)) / 2, 266 round(MeanOfMiddle); 267 1 -> 268 lists:nth(N div 2 + 1, SortedData) 269 end, 270 ct:log("median(~p) = ~p",[SortedData,Median]), 271 Median. 272 273%%%---------------------------------------------------------------- 274report(LabelList, Value) -> 275 Label = report_chars(lists:concat(LabelList)), 276 ct:log("ct_event:notify ~p: ~p", [Label, Value]), 277 ct_event:notify( 278 #event{name = benchmark_data, 279 data = [{suite, ?MODULE}, 280 {name, Label}, 281 {value, Value}]}). 282 283report_chars(Cs) -> 284 [case C of 285 $- -> $_; 286 _ -> C 287 end || C <- Cs]. 288 289