%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2009-2018. 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(crypto_bench_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). -include_lib("common_test/include/ct_event.hrl"). -include_lib("common_test/include/ct.hrl"). suite() -> [%%{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}, {timetrap,{minutes,2}} ]. all() -> [ {group, textblock_256} ]. groups() -> [ {textblock_256, [], [ {group, ciphers_128}, {group, ciphers_256} ]}, {ciphers_128, [{repeat, 5}], [ block, stream ]}, {ciphers_256, [{repeat, 5}], [ block, stream, chacha ]} ]. %%%---------------------------------------------------------------- %%% init_per_suite(Config0) -> try crypto:start() of _ -> [{_,_,Info}] = crypto:info_lib(), ct:comment("~s",[Info]), ct:pal("Crypto version: ~p~n~n~p",[Info,crypto:supports()]), Config1 = measure_openssl_aes_cbc([128,256], Config0), calibrate([{sec_goal,10} | Config1]) catch _:_ -> {fail, "Crypto did not start"} end. end_per_suite(_Config) -> application:stop(crypto). %%%---------------------------------------------------------------- %%% init_per_group(Group, Config) -> case atom_to_list(Group) of "ciphers_"++KeySizeStr -> KeySize = list_to_integer(KeySizeStr), [{key_size,KeySize} | Config]; "textblock_"++BlockSizeStr -> BlockSize = list_to_integer(BlockSizeStr), [{block_size,BlockSize} | Config]; _ -> Config end. end_per_group(_Group, Config) -> Config. measure_openssl_aes_cbc(KeySizes, Config) -> BLno_acc = [baseline(aes_cbc, KeySize, false) || KeySize <- KeySizes], ct:pal("Non-accelerated baseline encryption time [µs/block]:~n~p", [BLno_acc]), BLacc = [baseline(aes_cbc, KeySize, true) || KeySize <- KeySizes], ct:pal("Possibly accelerated baseline encryption time [µs/block]:~n~p", [BLacc]), [{acc,BLacc}, {no_acc,BLno_acc} | Config]. calibrate(Config) -> Secs = proplists:get_value(sec_goal, Config, 10), {_,Empty} = data(empty, 0, 0), {Ne,Te} = run1(Secs*3000, Empty), report(["Overhead"], Te/Ne), [{overhead,Te/Ne} | Config]. %%%================================================================ %%% %%% block(Config) -> run_cryptos([aes_cbc, aes_gcm, aes_ccm], Config). stream(Config) -> run_cryptos([aes_ctr], Config). chacha(Config) -> run_cryptos([chacha20, chacha20_poly1305], Config). %%%================================================================ %%% %%% run_cryptos(Cryptos, Config) -> KeySize = proplists:get_value(key_size, Config), BlockSize = proplists:get_value(block_size, Config), MilliSecGoal = 1000*proplists:get_value(sec_goal,Config), OverHead = proplists:get_value(overhead, Config, 0), [try TimePerOpBrutto = run(Crypto,KeySize,BlockSize,MilliSecGoal), %% ct:pal("Brutto: ~p Overhead: ~p (~.2f %) Netto: ~p", %% [TimePerOpBrutto, OverHead, 100*OverHead/TimePerOpBrutto,TimePerOpBrutto - OverHead]), TimePerOpBrutto - OverHead of TimePerOp -> % µs %% First, Report speed of encrypting blocks of 1000. [blocks/sec] ReportUnit = 1000, Label = [fmt(Crypto)," key:",KeySize," block:",BlockSize], report(Label, (BlockSize/ReportUnit)*1000000/TimePerOp ), EffCrypto = case Crypto of X -> X end, %% Percent of accelerated speed case find_value([acc,{EffCrypto,KeySize},BlockSize], Config) of undefined -> ok; TimePerOpBaseAcc -> report(["Percent of acc OpenSSL "|Label], 100*TimePerOpBaseAcc/TimePerOp % Percent of base *speed* ) end, %% Percent of non-accelerated speed case find_value([no_acc,{EffCrypto,KeySize},BlockSize], Config) of undefined -> ok; TimePerOpBaseNoAcc -> report(["Percent of noacc OpenSSL "|Label], 100*TimePerOpBaseNoAcc/TimePerOp % Percent of base *speed* ) end catch _:_ -> ct:pal("~p unsupported",[{Crypto,KeySize,BlockSize}]) end || Crypto <- Cryptos, supported(Crypto) ]. run(Crypto, KeySize, BlockSize, MilliSecGoal) -> {_Type, Funs} = data(Crypto, KeySize, BlockSize), {Nc,Tc} = run1(MilliSecGoal, Funs), Tc/Nc. fmt(X) -> X. find_value(KeyPath, PropList, Default) -> try find_value(KeyPath, PropList) of undefined -> Default catch error:function_clause -> Default end. find_value(KeyPath, PropList) -> lists:foldl(fun(K, L) when is_list(L) -> proplists:get_value(K,L); (_, _) -> undefined end, PropList, KeyPath). %%%================================================================ %%% %%% funs({block, {Type, Key, IV, Block}}) -> {fun() -> ok end, fun(_) -> crypto:block_encrypt(Type, Key, IV, Block) end, fun(_) -> ok end}; funs({stream, {Type, Key, IV, Block}}) -> {fun() -> {crypto:stream_init(Type, Key, IV),ok} end, fun({Ctx,_}) -> crypto:stream_encrypt(Ctx, Block) end, fun(_) -> ok end}. data(aes_cbc, KeySize, BlockSize) -> Type = case KeySize of 128 -> aes_cbc128; 256 -> aes_cbc256 end, Key = mk_bin(KeySize div 8), IV = mk_bin(16), Block = mk_bin(BlockSize), {Type, funs({block, {Type, Key, IV, Block}})}; data(aes_gcm, KeySize, BlockSize) -> Type = aes_gcm, Key = mk_bin(KeySize div 8), IV = mk_bin(12), Block = mk_bin(BlockSize), AAD = <<01,02,03,04>>, {Type, funs({block, {Type, Key, IV, {AAD,Block,16}}})}; data(aes_ccm, KeySize, BlockSize) -> Type = aes_ccm, Key = mk_bin(KeySize div 8), IV = mk_bin(12), Block = mk_bin(BlockSize), AAD = <<01,02,03,04>>, {Type, funs({block, {Type, Key, IV, {AAD,Block,12}}})}; data(aes_ctr, KeySize, BlockSize) -> Type = aes_ctr, Key = mk_bin(KeySize div 8), IV = mk_bin(16), Block = mk_bin(BlockSize), {Type, funs({stream, {Type, Key, IV, Block}})}; data(chacha20_poly1305, 256=KeySize, BlockSize) -> Type = chacha20_poly1305, Key = mk_bin(KeySize div 8), IV = mk_bin(16), AAD = <<01,02,03,04>>, Block = mk_bin(BlockSize), {Type, funs({block, {Type, Key, IV, {AAD,Block}}})}; data(chacha20, 256=KeySize, BlockSize) -> Type = chacha20, Key = mk_bin(KeySize div 8), IV = mk_bin(16), Block = mk_bin(BlockSize), {Type, funs({stream, {Type, Key, IV, Block}})}; data(empty, 0, 0) -> {undefined, {fun() -> ok end, fun(X) -> X end, fun(_) -> ok end}}. %%%================================================================ %%% %%% run1(MilliSecGoal, Funs) -> Parent = self(), Pid = spawn(fun() -> {Fi,Fu,Ff} = Funs, Ctx0 = Fi(), erlang:garbage_collect(), T0 = start_time(), {N,Ctx} = loop(Fu, Ctx0, 0), T = elapsed_time(T0), Ff(Ctx), Parent ! {result,N,microseconds(T)} end), Pid ! go, receive after MilliSecGoal -> Pid ! stop end, receive {result,N,MicroSecs} -> {N,MicroSecs} end. loop(F, Ctx, N) -> receive stop -> {N, Ctx} after 0 -> loop(F, F(Ctx), N+1) end. %%%---------------------------------------------------------------- report(LabelList, Value) -> Label = report_chars(lists:concat(LabelList)), ct:pal("ct_event:notify ~p: ~p", [Label, Value]), ct_event:notify( #event{name = benchmark_data, data = [{name, Label}, {value,Value}]}). report_chars(Cs) -> [case C of $- -> $_; _ -> C end || C <- Cs]. %%%---------------------------------------------------------------- supported(Algorithm) -> lists:member(Algorithm, [A || {_,As} <- crypto:supports(), A <- As] ). %%%---------------------------------------------------------------- start_time() -> erlang:system_time(). elapsed_time(StartTime) -> erlang:system_time() - StartTime. microseconds(Time) -> erlang:convert_time_unit(Time, native, microsecond). %%%---------------------------------------------------------------- %% Example output: %% +DT:aes-128-cbc:3:16 %% +R:135704772:aes-128-cbc:2.980000 %% +DT:aes-128-cbc:3:64 %% +R:36835089:aes-128-cbc:3.000000 %% +DT:aes-128-cbc:3:256 %% +R:9398616:aes-128-cbc:3.000000 %% +DT:aes-128-cbc:3:1024 %% +R:2355683:aes-128-cbc:2.990000 %% +DT:aes-128-cbc:3:8192 %% +R:294508:aes-128-cbc:2.990000 %% +H:16:64:256:1024:8192 %% +F:22:aes-128-cbc:728616225.50:785815232.00:802015232.00:806762338.46:806892821.40 baseline(Crypto, KeySize, EVP) -> Spec= case {Crypto,KeySize} of {aes_cbc, 128} -> "aes-128-cbc"; {aes_cbc, 256} -> "aes-256-cbc" end, {{Crypto,KeySize}, baseline(Spec, EVP)}. baseline(Spec, EVP) -> Cmd = case EVP of true -> "openssl speed -mr -evp " ++ Spec; false-> "openssl speed -mr " ++ Spec end, get_base_values(string:tokens(os:cmd(Cmd),"\n"), Spec, []). get_base_values(["+DT:"++Sdt, "+R:"++Sr |T], Crypto, Acc) -> [Crypto0,_GoalSecs0,BlockSize0] = string:tokens(Sdt, ":"), [Nblocks0,Crypto0,RealSecs0] = string:tokens(Sr, ":"), Crypto = fix_possible_space_bug(Crypto0), RealSecs = list_to_float(RealSecs0), BlockSize = list_to_integer(BlockSize0), Nblocks = list_to_integer(Nblocks0), get_base_values(T, Crypto, [{BlockSize, 1000000*RealSecs/Nblocks} | Acc]); get_base_values([_|T], Crypto, Acc) -> get_base_values(T, Crypto, Acc); get_base_values([], _, Acc) -> lists:sort(Acc). fix_possible_space_bug(S) -> lists:concat(lists:join("-",string:tokens(S,"- "))). %%%---------------------------------------------------------------- mk_bin(Size) when Size =< 256 -> list_to_binary(lists:seq(0,Size-1)); mk_bin(Size) when 1024 =< Size -> B = mk_bin(Size div 4), Brest = mk_bin(Size rem 4), <>; mk_bin(Size) when 256 < Size -> B = mk_bin(Size div 2), Brest = mk_bin(Size rem 2), <>.