• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 12132670350

03 Dec 2024 12:33AM UTC coverage: 91.266% (+0.008%) from 91.258%
12132670350

push

github

web-flow
Merge pull request #4456 from randombit/jack/split-timer

Split up Timer logic

93396 of 102334 relevant lines covered (91.27%)

11444167.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.04
/src/cli/speed.cpp
1
/*
2
* (C) 2009,2010,2014,2015,2017,2018,2024 Jack Lloyd
3
* (C) 2015 Simon Warta (Kullo GmbH)
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "cli.h"
9
#include "perf.h"
10

11
#include <algorithm>
12
#include <chrono>
13
#include <iomanip>
14
#include <map>
15
#include <set>
16
#include <sstream>
17

18
// Always available:
19
#include <botan/version.h>
20
#include <botan/internal/cpuid.h>
21
#include <botan/internal/fmt.h>
22
#include <botan/internal/os_utils.h>
23
#include <botan/internal/stl_util.h>
24

25
#if defined(BOTAN_HAS_ECC_GROUP)
26
   #include <botan/ec_group.h>
27
#endif
28

29
namespace Botan_CLI {
30

31
namespace {
32

33
class JSON_Output final {
2✔
34
   public:
35
      void add(const Timer& timer) { m_results.push_back(timer); }
2✔
36

37
      std::string print() const {
1✔
38
         std::ostringstream out;
1✔
39

40
         out << "[\n";
1✔
41

42
         for(size_t i = 0; i != m_results.size(); ++i) {
3✔
43
            const Timer& t = m_results[i];
2✔
44

45
            out << "{"
2✔
46
                << "\"algo\": \"" << t.get_name() << "\", "
2✔
47
                << "\"op\": \"" << t.doing() << "\", "
2✔
48
                << "\"events\": " << t.events() << ", ";
2✔
49

50
            if(t.cycles_consumed() > 0) {
4✔
51
               out << "\"cycles\": " << t.cycles_consumed() << ", ";
4✔
52
            }
53

54
            if(t.buf_size() > 0) {
2✔
55
               out << "\"bps\": " << static_cast<uint64_t>(t.events() / (t.value() / 1000000000.0)) << ", ";
2✔
56
               out << "\"buf_size\": " << t.buf_size() << ", ";
2✔
57
            }
58

59
            out << "\"nanos\": " << t.value() << "}";
2✔
60

61
            if(i != m_results.size() - 1) {
2✔
62
               out << ",";
1✔
63
            }
64

65
            out << "\n";
2✔
66
         }
67
         out << "]\n";
1✔
68

69
         return out.str();
2✔
70
      }
1✔
71

72
   private:
73
      std::vector<Timer> m_results;
74
};
75

76
class Summary final {
1✔
77
   public:
78
      Summary() = default;
1✔
79

80
      void add(const Timer& t) {
2✔
81
         if(t.buf_size() == 0) {
2✔
82
            m_ops_entries.push_back(t);
×
83
         } else {
84
            m_bps_entries[std::make_pair(t.doing(), t.get_name())].push_back(t);
4✔
85
         }
86
      }
2✔
87

88
      std::string print() {
1✔
89
         const size_t name_padding = 35;
1✔
90
         const size_t op_name_padding = 16;
1✔
91
         const size_t op_padding = 16;
1✔
92

93
         std::ostringstream result_ss;
1✔
94
         result_ss << std::fixed;
1✔
95

96
         if(!m_bps_entries.empty()) {
1✔
97
            result_ss << "\n";
1✔
98

99
            // add table header
100
            result_ss << std::setw(name_padding) << std::left << "algo" << std::setw(op_name_padding) << std::left
1✔
101
                      << "operation";
1✔
102

103
            for(const Timer& t : m_bps_entries.begin()->second) {
2✔
104
               result_ss << std::setw(op_padding) << std::right << (std::to_string(t.buf_size()) + " bytes");
2✔
105
            }
106
            result_ss << "\n";
1✔
107

108
            // add table entries
109
            for(const auto& entry : m_bps_entries) {
3✔
110
               if(entry.second.empty()) {
2✔
111
                  continue;
×
112
               }
113

114
               result_ss << std::setw(name_padding) << std::left << (entry.first.second) << std::setw(op_name_padding)
2✔
115
                         << std::left << (entry.first.first);
2✔
116

117
               for(const Timer& t : entry.second) {
4✔
118
                  if(t.events() == 0) {
2✔
119
                     result_ss << std::setw(op_padding) << std::right << "N/A";
×
120
                  } else {
121
                     result_ss << std::setw(op_padding) << std::right << std::setprecision(2)
2✔
122
                               << (t.bytes_per_second() / 1000.0);
2✔
123
                  }
124
               }
125

126
               result_ss << "\n";
2✔
127
            }
128

129
            result_ss << "\n[results are the number of 1000s bytes processed per second]\n";
1✔
130
         }
131

132
         if(!m_ops_entries.empty()) {
1✔
133
            result_ss << std::setprecision(6) << "\n";
×
134

135
            // sort entries
136
            std::sort(m_ops_entries.begin(), m_ops_entries.end());
×
137

138
            // add table header
139
            result_ss << std::setw(name_padding) << std::left << "algo" << std::setw(op_name_padding) << std::left
×
140
                      << "operation" << std::setw(op_padding) << std::right << "sec/op" << std::setw(op_padding)
×
141
                      << std::right << "op/sec"
×
142
                      << "\n";
×
143

144
            // add table entries
145
            for(const Timer& entry : m_ops_entries) {
×
146
               result_ss << std::setw(name_padding) << std::left << entry.get_name() << std::setw(op_name_padding)
×
147
                         << std::left << entry.doing() << std::setw(op_padding) << std::right
×
148
                         << entry.seconds_per_event() << std::setw(op_padding) << std::right
×
149
                         << entry.events_per_second() << "\n";
×
150
            }
151
         }
152

153
         return result_ss.str();
2✔
154
      }
1✔
155

156
   private:
157
      std::map<std::pair<std::string, std::string>, std::vector<Timer>> m_bps_entries;
158
      std::vector<Timer> m_ops_entries;
159
};
160

161
std::vector<size_t> unique_buffer_sizes(const std::string& cmdline_arg) {
29✔
162
   const size_t MAX_BUF_SIZE = 64 * 1024 * 1024;
29✔
163

164
   std::set<size_t> buf;
29✔
165
   for(const std::string& size_str : Command::split_on(cmdline_arg, ',')) {
56✔
166
      size_t x = 0;
30✔
167
      try {
30✔
168
         size_t converted = 0;
30✔
169
         x = static_cast<size_t>(std::stoul(size_str, &converted, 0));
30✔
170

171
         if(converted != size_str.size()) {
29✔
172
            throw CLI_Usage_Error("Invalid integer");
×
173
         }
174
      } catch(std::exception&) {
1✔
175
         throw CLI_Usage_Error("Invalid integer value '" + size_str + "' for option buf-size");
2✔
176
      }
1✔
177

178
      if(x == 0) {
29✔
179
         throw CLI_Usage_Error("Cannot have a zero-sized buffer");
2✔
180
      }
181

182
      if(x > MAX_BUF_SIZE) {
28✔
183
         throw CLI_Usage_Error("Specified buffer size is too large");
2✔
184
      }
185

186
      buf.insert(x);
27✔
187
   }
29✔
188

189
   return std::vector<size_t>(buf.begin(), buf.end());
29✔
190
}
26✔
191

192
}  // namespace
193

194
class Speed final : public Command {
×
195
   public:
196
      Speed() :
30✔
197
            Command(
198
               "speed --msec=500 --format=default --ecc-groups= --buf-size=1024 --clear-cpuid= --cpu-clock-speed=0 --cpu-clock-ratio=1.0 *algos") {
60✔
199
      }
30✔
200

201
      static std::vector<std::string> default_benchmark_list() {
×
202
         /*
203
         This is not intended to be exhaustive: it just hits the high
204
         points of the most interesting or widely used algorithms.
205
         */
206
         // clang-format off
207
         return {
×
208
            /* Block ciphers */
209
            "AES-128",
210
            "AES-192",
211
            "AES-256",
212
            "ARIA-128",
213
            "ARIA-192",
214
            "ARIA-256",
215
            "Blowfish",
216
            "CAST-128",
217
            "Camellia-128",
218
            "Camellia-192",
219
            "Camellia-256",
220
            "DES",
221
            "TripleDES",
222
            "GOST-28147-89",
223
            "IDEA",
224
            "Noekeon",
225
            "SHACAL2",
226
            "SM4",
227
            "Serpent",
228
            "Threefish-512",
229
            "Twofish",
230

231
            /* Cipher modes */
232
            "AES-128/CBC",
233
            "AES-128/CTR-BE",
234
            "AES-128/EAX",
235
            "AES-128/OCB",
236
            "AES-128/GCM",
237
            "AES-128/XTS",
238
            "AES-128/SIV",
239

240
            "Serpent/CBC",
241
            "Serpent/CTR-BE",
242
            "Serpent/EAX",
243
            "Serpent/OCB",
244
            "Serpent/GCM",
245
            "Serpent/XTS",
246
            "Serpent/SIV",
247

248
            "ChaCha20Poly1305",
249

250
            /* Stream ciphers */
251
            "RC4",
252
            "Salsa20",
253
            "ChaCha20",
254

255
            /* Hashes */
256
            "SHA-1",
257
            "SHA-256",
258
            "SHA-512",
259
            "SHA-3(256)",
260
            "SHA-3(512)",
261
            "RIPEMD-160",
262
            "Skein-512",
263
            "Blake2b",
264
            "Whirlpool",
265

266
            /* XOFs */
267
            "SHAKE-128",
268
            "SHAKE-256",
269

270
            /* MACs */
271
            "CMAC(AES-128)",
272
            "HMAC(SHA-256)",
273

274
            /* pubkey */
275
            "RSA",
276
            "DH",
277
            "ECDH",
278
            "ECDSA",
279
            "Ed25519",
280
            "Ed448",
281
            "X25519",
282
            "X448",
283
            "ML-KEM",
284
            "ML-DSA",
285
            "SLH-DSA",
286
            "FrodoKEM",
287
            "HSS-LMS",
288
         };
×
289
         // clang-format on
290
      }
291

292
      std::string group() const override { return "misc"; }
1✔
293

294
      std::string description() const override { return "Measures the speed of algorithms"; }
1✔
295

296
      void go() override {
29✔
297
         std::chrono::milliseconds msec(get_arg_sz("msec"));
29✔
298
         std::vector<std::string> ecc_groups = Command::split_on(get_arg("ecc-groups"), ',');
58✔
299
         const std::string format = get_arg("format");
29✔
300
         const std::string clock_ratio = get_arg("cpu-clock-ratio");
32✔
301

302
         const size_t clock_speed = get_arg_sz("cpu-clock-speed");
29✔
303

304
         double clock_cycle_ratio = std::strtod(clock_ratio.c_str(), nullptr);
29✔
305

306
         /*
307
         * This argument is intended to be the ratio between the cycle counter
308
         * and the actual machine cycles. It is extremely unlikely that there is
309
         * any machine where the cycle counter increments faster than the actual
310
         * clock.
311
         */
312
         if(clock_cycle_ratio < 0.0 || clock_cycle_ratio > 1.0) {
29✔
313
            throw CLI_Usage_Error("Unlikely CPU clock ratio of " + clock_ratio);
×
314
         }
315

316
         clock_cycle_ratio = 1.0 / clock_cycle_ratio;
29✔
317

318
         if(clock_speed != 0 && Botan::OS::get_cpu_cycle_counter() != 0) {
29✔
319
            error_output() << "The --cpu-clock-speed option is only intended to be used on "
×
320
                              "platforms without access to a cycle counter.\n"
321
                              "Expect incorrect results\n\n";
×
322
         }
323

324
         if(format == "table") {
29✔
325
            m_summary = std::make_unique<Summary>();
1✔
326
         } else if(format == "json") {
28✔
327
            m_json = std::make_unique<JSON_Output>();
1✔
328
         } else if(format != "default") {
27✔
329
            throw CLI_Usage_Error("Unknown --format type '" + format + "'");
×
330
         }
331

332
#if defined(BOTAN_HAS_ECC_GROUP)
333
         if(ecc_groups.empty()) {
29✔
334
            ecc_groups = {"secp256r1", "secp384r1", "secp521r1", "brainpool256r1", "brainpool384r1", "brainpool512r1"};
232✔
335
         } else if(ecc_groups.size() == 1 && ecc_groups[0] == "all") {
×
336
            auto all = Botan::EC_Group::known_named_groups();
×
337
            ecc_groups.assign(all.begin(), all.end());
×
338
         }
×
339
#endif
340

341
         std::vector<std::string> algos = get_arg_list("algos");
32✔
342

343
         const std::vector<size_t> buf_sizes = unique_buffer_sizes(get_arg("buf-size"));
61✔
344

345
         for(const std::string& cpuid_to_clear : Command::split_on(get_arg("clear-cpuid"), ',')) {
27✔
346
            auto bits = Botan::CPUID::bit_from_string(cpuid_to_clear);
1✔
347
            if(bits.empty()) {
1✔
348
               error_output() << "Warning don't know CPUID flag '" << cpuid_to_clear << "'\n";
1✔
349
            }
350

351
            for(auto bit : bits) {
1✔
352
               Botan::CPUID::clear_cpuid_bit(bit);
×
353
            }
354
         }
27✔
355

356
         if(verbose() || m_summary) {
26✔
357
            output() << Botan::version_string() << "\n"
2✔
358
                     << "CPUID: " << Botan::CPUID::to_string() << "\n\n";
3✔
359
         }
360

361
         const bool using_defaults = (algos.empty());
26✔
362
         if(using_defaults) {
26✔
363
            algos = default_benchmark_list();
×
364
         }
365

366
         PerfConfig perf_config([&](const Timer& t) { this->record_result(t); },
493✔
367
                                clock_speed,
368
                                clock_cycle_ratio,
369
                                msec,
370
                                ecc_groups,
371
                                buf_sizes,
372
                                this->error_output(),
373
                                this->rng());
26✔
374

375
         for(const auto& algo : algos) {
72✔
376
            if(auto perf = PerfTest::get(algo)) {
46✔
377
               perf->go(perf_config);
46✔
378
            } else if(verbose() || !using_defaults) {
×
379
               error_output() << "Unknown algorithm '" << algo << "'\n";
×
380
            }
46✔
381
         }
382

383
         if(m_json) {
26✔
384
            output() << m_json->print();
2✔
385
         }
386
         if(m_summary) {
26✔
387
            output() << m_summary->print() << "\n";
3✔
388
         }
389

390
         if(verbose() && clock_speed == 0 && m_cycles_consumed > 0 && m_ns_taken > 0) {
26✔
391
            const double seconds = static_cast<double>(m_ns_taken) / 1000000000;
×
392
            const double Hz = static_cast<double>(m_cycles_consumed) / seconds;
×
393
            const double MHz = Hz / 1000000;
×
394
            output() << "\nEstimated clock speed " << MHz << " MHz\n";
×
395
         }
396
      }
119✔
397

398
   private:
399
      uint64_t m_cycles_consumed = 0;
400
      uint64_t m_ns_taken = 0;
401
      std::unique_ptr<Summary> m_summary;
402
      std::unique_ptr<JSON_Output> m_json;
403

404
      void record_result(const Timer& t) {
467✔
405
         m_ns_taken += t.value();
467✔
406
         m_cycles_consumed += t.cycles_consumed();
467✔
407
         if(m_json) {
467✔
408
            m_json->add(t);
2✔
409
         } else {
410
            output() << t.to_string() << std::flush;
930✔
411
            if(m_summary) {
465✔
412
               m_summary->add(t);
2✔
413
            }
414
         }
415
      }
467✔
416
};
417

418
BOTAN_REGISTER_COMMAND("speed", Speed);
30✔
419

420
}  // namespace Botan_CLI
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc