1 /**
2 * Runtime benchmarking
3 * 
4 * Copyright:
5 * (C) 2008-2009 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.utils.benchmark;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_BENCHMARK):
15 
16 import botan.algo_factory.algo_factory;
17 import botan.algo_base.buf_comp;
18 import botan.block.block_cipher;
19 import botan.stream.stream_cipher;
20 import botan.modes.aead.aead;
21 import botan.hash.hash;
22 import botan.mac.mac;
23 import std.datetime;
24 import std.conv;
25 import botan.utils.types;
26 import std.datetime;
27 import botan.rng.rng;
28 import memutils.hashmap;
29 // import string;
30 import std.datetime;
31 
32 /**
33 * Time aspects of an algorithm/provider
34 * Params:
35 *  name = the name of the algorithm to test
36 *  af = the algorithm factory used to create objects
37 *  provider = the provider to use
38 *  rng = the rng to use to generate random inputs
39 *  runtime = total time for the benchmark to run
40 *  buf_size = size of buffer to benchmark against, in KiB
41 * Returns: results a map from op type to operations per second
42 */
43 HashMapRef!(string, double)
44     timeAlgorithmOps(in string name,
45                        AlgorithmFactory af,
46                        in string provider,
47                        RandomNumberGenerator rng,
48                        Duration runtime,
49                        size_t buf_size)
50 {
51     const size_t Mebibyte = 1024*1024;
52     
53     SecureVector!ubyte buffer = SecureVector!ubyte(buf_size * 1024);
54     rng.randomize(buffer.ptr, buffer.length);
55     
56     const double mb_mult = buffer.length / cast(double)(Mebibyte);
57     
58 
59     {
60 
61         const BlockCipher proto = af.prototypeBlockCipher(name, provider);
62         if (proto) {
63             Unique!BlockCipher bc = proto.clone();
64             
65             const SymmetricKey key = SymmetricKey(rng, bc.maximumKeylength());
66             
67             HashMapRef!(string, double) ret;
68             ret["key schedule"] = timeOp(runtime / 8, { bc.setKey(key); });
69             ret["encrypt"] = mb_mult * timeOp(runtime / 2, { bc.encrypt(buffer); });
70             ret["decrypt"] = mb_mult * timeOp(runtime / 2, { bc.decrypt(buffer); });
71             return ret;
72         }
73     }
74     {
75         const StreamCipher proto = af.prototypeStreamCipher(name, provider);
76         if (proto) {
77             Unique!StreamCipher sc = proto.clone();
78             
79             const SymmetricKey key = SymmetricKey(rng, sc.maximumKeylength());
80             HashMapRef!(string, double) ret;
81             ret["key schedule"] = timeOp(runtime / 8, { sc.setKey(key); });
82             ret[""] = mb_mult * timeOp(runtime, { sc.encipher(buffer); });
83             return ret;
84         }
85     }
86     {
87         const HashFunction proto = af.prototypeHashFunction(name, provider);
88         if (proto) {
89             Unique!HashFunction h = proto.clone();
90             HashMapRef!(string, double) ret;
91             ret[""] = mb_mult * timeOp(runtime, { h.update(buffer); });
92             return ret;
93         }
94     }
95     {
96         const MessageAuthenticationCode proto = af.prototypeMac(name, provider);
97         
98         if (proto) {
99             Unique!MessageAuthenticationCode mac = proto.clone();
100             
101             const SymmetricKey key = SymmetricKey(rng, mac.maximumKeylength());
102             HashMapRef!(string, double) ret;
103             ret["key schedule"] =timeOp(runtime / 8, { mac.setKey(key); });
104             ret[""] = mb_mult * timeOp(runtime, { mac.update(buffer); });
105             return ret;
106         }
107     }
108     {
109         Unique!AEADMode enc = getAead(name, ENCRYPTION);
110         Unique!AEADMode dec = getAead(name, DECRYPTION);
111         
112         if (!enc.isEmpty && !dec.isEmpty)
113         {
114             const SymmetricKey key = SymmetricKey(rng, enc.keySpec().maximumKeylength());
115             HashMapRef!(string, double) ret;
116             ret["key schedule"] = timeOp(runtime / 4, { enc.setKey(key); dec.setKey(key); }) / 2;
117             ret["encrypt"] = mb_mult * timeOp(runtime / 2, { enc.update(buffer, 0); buffer.resize(buf_size*1024); });
118             ret["decrypt"] = mb_mult * timeOp(runtime / 2, { dec.update(buffer, 0); buffer.resize(buf_size*1024); });
119             return ret;
120         }
121     }
122     
123             
124     return HashMapRef!(string, double)();
125 }
126 
127 /**
128 * Algorithm benchmark
129 * Params:
130 *  name = the name of the algorithm to test (cipher, hash, or MAC)
131 *  af = the algorithm factory used to create objects
132 *  rng = the rng to use to generate random inputs
133 *  milliseconds = total time for the benchmark to run
134 *  buf_size = size of buffer to benchmark against, in KiB
135 * Returns: results a map from provider to speed in mebibytes per second
136 */
137 HashMapRef!(string, double)
138     algorithmBenchmark(in string name,
139                         AlgorithmFactory af,
140                         RandomNumberGenerator rng,
141                         Duration milliseconds,
142                         size_t buf_size)
143 {
144     const Vector!string providers = af.providersOf(name);
145     
146     HashMapRef!(string, double) all_results; // provider . ops/sec
147     
148     if (!providers.empty)
149     {
150         const Duration ns_per_provider = milliseconds / providers.length;
151         
152         foreach (provider; providers)
153         {
154             auto results = timeAlgorithmOps(name, af, provider, rng, ns_per_provider, buf_size);
155             all_results[provider] = findFirstIn(results, ["", "update", "encrypt"]);
156         }
157     }
158     
159     return all_results;
160 }
161 
162 
163 double timeOp(Duration runtime, void delegate() op)
164 {
165     StopWatch sw;
166     sw.start();
167     int reps = 0;
168     while (sw.peek().to!Duration < runtime)
169     {
170         op();
171         ++reps;
172     }
173     sw.stop();
174     return reps.to!double / sw.peek().seconds.to!double; // ie, return ops per second
175 }
176 
177 private double findFirstIn(in HashMapRef!(string, double) m, 
178                              const ref Vector!string keys)
179 {
180     foreach (key; keys[])
181     {
182         auto val = m.get(key, double.nan);
183         if (val != double.nan)
184             return val;
185     }
186     
187     throw new Exception("algorithmFactory no usable keys found in result");
188 }