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

randombit / botan / 39195077976

18 Oct 2026 05:18PM UTC coverage: 92.258% (+0.06%) from 92.195%
39195077976

push

github

randombit
Update release notes [ci skip]

137358 of 148884 relevant lines covered (92.26%)

8967647.39 hits per line

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

72.03
/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 <cmath>
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/parsing.h>
21
#include <botan/internal/target_info.h>
22

23
#if defined(BOTAN_HAS_CPUID)
24
   #include <botan/internal/cpuid.h>
25
#endif
26

27
#if defined(BOTAN_HAS_OS_UTILS)
28
   #include <botan/internal/os_utils.h>
29
#endif
30

31
#if defined(BOTAN_HAS_ECC_GROUP)
32
   #include <botan/ec_group.h>
33
#endif
34

35
namespace Botan_CLI {
36

37
namespace {
38

39
class JSON_Output final {
2 ✔
40
   public:
41
      void add(const Timer& timer) { m_results.push_back(timer); }
2 ✔
42

43
      std::string print() const {
1 ✔
44
         std::ostringstream out;
1 ✔
45

46
         out << "[\n";
1 ✔
47

48
         out << "{"
1 ✔
49
             << R"("arch": ")" << BOTAN_TARGET_ARCH << "\", "
50
             << R"("version": ")" << Botan::short_version_cstr() << "\", ";
1 ✔
51

52
         if(auto vc_revision = Botan::version_vc_revision()) {
1 ✔
53
            out << R"("git": ")" << *vc_revision << "\", ";
×
54
         }
×
55

56
         out << R"("compiler": ")" << BOTAN_COMPILER_INVOCATION_STRING << "\""
1 ✔
57
             << "},\n";
1 ✔
58

59
         for(size_t i = 0; i != m_results.size(); ++i) {
3 ✔
60
            const Timer& t = m_results[i];
2 ✔
61

62
            out << "{"
2 ✔
63
                << R"("algo": ")" << t.get_name() << "\", "
2 ✔
64
                << R"("op": ")" << t.doing() << "\", "
2 ✔
65
                << "\"events\": " << t.events() << ", ";
2 ✔
66

67
            if(t.cycles_consumed() > 0) {
4 ✔
68
               out << "\"cycles\": " << t.cycles_consumed() << ", ";
4 ✔
69
            }
70

71
            if(t.buf_size() > 0) {
2 ✔
72
               out << "\"bps\": " << static_cast<uint64_t>(t.events() / (t.nanoseconds() / 1000000000.0)) << ", ";
2 ✔
73
               out << "\"buf_size\": " << t.buf_size() << ", ";
2 ✔
74
            }
75

76
            out << "\"nanos\": " << t.value() << "}";
2 ✔
77

78
            if(i != m_results.size() - 1) {
2 ✔
79
               out << ",";
1 ✔
80
            }
81

82
            out << "\n";
2 ✔
83
         }
84
         out << "]\n";
1 ✔
85

86
         return out.str();
2 ✔
87
      }
1 ✔
88

89
   private:
90
      std::vector<Timer> m_results;
91
};
92

93
class Summary final {
1 ✔
94
   public:
95
      Summary() = default;
1 ✔
96

97
      void add(const Timer& t) {
2 ✔
98
         if(t.buf_size() == 0) {
2 ✔
99
            m_ops_entries.push_back(t);
×
100
         } else {
101
            m_bps_entries[std::make_pair(t.doing(), t.get_name())].push_back(t);
4 ✔
102
         }
103
      }
2 ✔
104

105
      std::string print() {
1 ✔
106
         const size_t name_padding = 35;
1 ✔
107
         const size_t op_name_padding = 16;
1 ✔
108
         const size_t op_padding = 16;
1 ✔
109

110
         std::ostringstream result_ss;
1 ✔
111
         result_ss << std::fixed;
1 ✔
112

113
         if(!m_bps_entries.empty()) {
1 ✔
114
            result_ss << "\n";
1 ✔
115

116
            // add table header
117
            result_ss << std::setw(name_padding) << std::left << "algo" << std::setw(op_name_padding) << std::left
1 ✔
118
                      << "operation";
1 ✔
119

120
            for(const Timer& t : m_bps_entries.begin()->second) {
2 ✔
121
               result_ss << std::setw(op_padding) << std::right << (std::to_string(t.buf_size()) + " bytes");
2 ✔
122
            }
123
            result_ss << "\n";
1 ✔
124

125
            // add table entries
126
            for(const auto& entry : m_bps_entries) {
3 ✔
127
               if(entry.second.empty()) {
2 ✔
128
                  continue;
×
129
               }
130

131
               result_ss << std::setw(name_padding) << std::left << (entry.first.second) << std::setw(op_name_padding)
2 ✔
132
                         << std::left << (entry.first.first);
2 ✔
133

134
               for(const Timer& t : entry.second) {
4 ✔
135
                  if(t.events() == 0) {
2 ✔
136
                     result_ss << std::setw(op_padding) << std::right << "N/A";
×
137
                  } else {
138
                     result_ss << std::setw(op_padding) << std::right << std::setprecision(2)
2 ✔
139
                               << (t.bytes_per_second() / 1000.0);
2 ✔
140
                  }
141
               }
142

143
               result_ss << "\n";
2 ✔
144
            }
145

146
            result_ss << "\n[results are the number of 1000s bytes processed per second]\n";
1 ✔
147
         }
148

149
         if(!m_ops_entries.empty()) {
1 ✔
150
            result_ss << std::setprecision(6) << "\n";
×
151

152
            // sort entries
153
            std::sort(m_ops_entries.begin(), m_ops_entries.end());
×
154

155
            // add table header
156
            result_ss << std::setw(name_padding) << std::left << "algo" << std::setw(op_name_padding) << std::left
×
157
                      << "operation" << std::setw(op_padding) << std::right << "sec/op" << std::setw(op_padding)
×
158
                      << std::right << "op/sec"
×
159
                      << "\n";
×
160

161
            // add table entries
162
            for(const Timer& entry : m_ops_entries) {
×
163
               result_ss << std::setw(name_padding) << std::left << entry.get_name() << std::setw(op_name_padding)
×
164
                         << std::left << entry.doing() << std::setw(op_padding) << std::right
×
165
                         << entry.seconds_per_event() << std::setw(op_padding) << std::right
×
166
                         << entry.events_per_second() << "\n";
×
167
            }
168
         }
169

170
         return result_ss.str();
2 ✔
171
      }
1 ✔
172

173
   private:
174
      std::map<std::pair<std::string, std::string>, std::vector<Timer>> m_bps_entries;
175
      std::vector<Timer> m_ops_entries;
176
};
177

178
std::vector<size_t> unique_buffer_sizes(const std::string& cmdline_arg) {
32 ✔
179
   const size_t MAX_BUF_SIZE = 64 * 1024 * 1024;
32 ✔
180

181
   std::set<size_t> buf;
32 ✔
182
   for(const std::string& size_str : Command::split_on(cmdline_arg, ',')) {
62 ✔
183
      if(const auto sz = Botan::parse_sz(size_str)) {
33 ✔
184
         if(*sz == 0) {
32 ✔
185
            throw CLI_Usage_Error("Cannot have a zero-sized buffer");
2 ✔
186
         }
187
         if(*sz > MAX_BUF_SIZE) {
31 ✔
188
            throw CLI_Usage_Error("Specified buffer size is too large");
2 ✔
189
         }
190

191
         buf.insert(*sz);
30 ✔
192
      } else {
193
         throw CLI_Usage_Error("Invalid integer value '" + size_str + "' for option buf-size");
2 ✔
194
      }
195
   }
32 ✔
196

197
   return std::vector<size_t>(buf.begin(), buf.end());
32 ✔
198
}
29 ✔
199

200
std::string format_timer(const Timer& t, size_t time_unit) {
564 ✔
201
   constexpr size_t MiB = 1024 * 1024;
564 ✔
202

203
   std::ostringstream oss;
564 ✔
204

205
   oss << t.get_name() << " ";
564 ✔
206

207
   const uint64_t events = t.events();
564 ✔
208

209
   if(t.buf_size() == 0) {
564 ✔
210
      // Report operations/time unit
211

212
      if(events == 0) {
541 ✔
213
         oss << "no events ";
×
214
      } else {
215
         oss << static_cast<uint64_t>(t.events_per_second()) << ' ' << t.doing() << "/sec; ";
1,082 ✔
216

217
         if(time_unit == 1000) {
541 ✔
218
            oss << std::setprecision(2) << std::fixed << (t.milliseconds() / events) << " ms/op ";
541 ✔
219
         } else if(time_unit == 1000 * 1000) {
×
220
            oss << std::setprecision(2) << std::fixed << (t.microseconds() / events) << " us/op ";
×
221
         } else if(time_unit == 1000 * 1000 * 1000) {
×
222
            oss << std::setprecision(0) << std::fixed << (t.nanoseconds() / events) << " ns/op ";
×
223
         }
224

225
         if(t.cycles_consumed() != 0 && events > 0) {
1,082 ✔
226
            const double cycles_per_op = static_cast<double>(t.cycles_consumed()) / events;
541 ✔
227
            const int precision = (cycles_per_op < 10000) ? 2 : 0;
541 ✔
228
            oss << std::fixed << std::setprecision(precision) << cycles_per_op << " cycles/op ";
541 ✔
229
         }
230

231
         oss << "(" << events << " " << (events == 1 ? "op" : "ops") << " in " << t.milliseconds() << " ms)";
924 ✔
232
      }
233
   } else {
234
      // Bulk op - report bytes/time unit
235

236
      const double MiB_total = static_cast<double>(events) / MiB;
23 ✔
237
      const double MiB_per_sec = MiB_total / t.seconds();
23 ✔
238

239
      if(!t.doing().empty()) {
23 ✔
240
         oss << t.doing() << " ";
23 ✔
241
      }
242

243
      if(t.buf_size() > 0) {
23 ✔
244
         oss << "buffer size " << t.buf_size() << " bytes: ";
23 ✔
245
      }
246

247
      if(events == 0) {
23 ✔
248
         oss << "N/A ";
×
249
      } else {
250
         oss << std::fixed << std::setprecision(3) << MiB_per_sec << " MiB/sec ";
23 ✔
251
      }
252

253
      if(t.cycles_consumed() != 0 && events > 0) {
46 ✔
254
         const double cycles_per_byte = static_cast<double>(t.cycles_consumed()) / events;
23 ✔
255
         oss << std::fixed << std::setprecision(2) << cycles_per_byte << " cycles/byte ";
23 ✔
256
      }
257

258
      oss << "(" << MiB_total << " MiB in " << t.milliseconds() << " ms)";
23 ✔
259
   }
260

261
   return oss.str();
1,128 ✔
262
}
564 ✔
263

264
std::vector<std::string> interpret_ecc_groups(const std::string& arg) {
32 ✔
265
   if(arg.empty()) {
32 ✔
266
      return {"secp256r1", "secp384r1", "secp521r1", "brainpool256r1", "brainpool384r1", "brainpool512r1"};
32 ✔
267
   }
268
   if(arg == "nist") {
×
269
      return {"secp224r1", "secp256r1", "secp384r1", "secp521r1"};
×
270
   }
271

272
#if defined(BOTAN_HAS_ECC_GROUP)
273
   if(arg == "all") {
×
274
      const auto& all = Botan::EC_Group::known_named_groups();
×
275
      return std::vector<std::string>(all.begin(), all.end());
×
276
   }
277

278
   if(arg == "generic") {
×
279
      std::vector<std::string> groups;
×
280
      for(const auto& group_name : Botan::EC_Group::known_named_groups()) {
×
281
         const Botan::EC_Group group(group_name);
×
282
         if(group.engine() == Botan::EC_Group_Engine::Generic) {
×
283
            groups.push_back(group_name);
×
284
         }
285
      }
×
286
      return groups;
×
287
   }
×
288

289
   if(arg == "pcurves") {
×
290
      std::vector<std::string> groups;
×
291
      for(const auto& group_name : Botan::EC_Group::known_named_groups()) {
×
292
         const Botan::EC_Group group(group_name);
×
293
         if(group.engine() == Botan::EC_Group_Engine::Optimized) {
×
294
            groups.push_back(group_name);
×
295
         }
296
      }
×
297
      return groups;
×
298
   }
×
299
#endif
300

301
   return Command::split_on(arg, ',');
×
302
}
303

304
class Speed final : public Command {
×
305
   public:
306
      Speed() :
40 ✔
307
            Command(
308
               "speed --msec=500 --format=default --time-unit=ms --ecc-groups= --buf-size=1024 --clear-cpuid= --cpu-clock-speed=0 --cpu-clock-ratio=1.0 --one-shot *algos") {
80 ✔
309
      }
40 ✔
310

311
      static std::vector<std::string> default_benchmark_list() {
×
312
         /*
313
         This is not intended to be exhaustive: it just hits the high
314
         points of the most interesting or widely used algorithms.
315
         */
316
         // clang-format off
317
         return {
×
318
            /* Block ciphers */
319
            "AES-128",
320
            "AES-192",
321
            "AES-256",
322
            "ARIA-128",
323
            "ARIA-192",
324
            "ARIA-256",
325
            "Blowfish",
326
            "CAST-128",
327
            "Camellia-128",
328
            "Camellia-192",
329
            "Camellia-256",
330
            "DES",
331
            "TripleDES",
332
            "GOST-28147-89",
333
            "IDEA",
334
            "Noekeon",
335
            "SHACAL2",
336
            "SM4",
337
            "Serpent",
338
            "Threefish-512",
339
            "Twofish",
340

341
            /* Cipher modes */
342
            "AES-128/CBC",
343
            "AES-128/CTR-BE",
344
            "AES-128/EAX",
345
            "AES-128/OCB",
346
            "AES-128/GCM",
347
            "AES-128/XTS",
348
            "AES-128/SIV",
349
            "Ascon-AEAD128",
350

351
            "Serpent/CBC",
352
            "Serpent/CTR-BE",
353
            "Serpent/EAX",
354
            "Serpent/OCB",
355
            "Serpent/GCM",
356
            "Serpent/XTS",
357
            "Serpent/SIV",
358

359
            "ChaCha20Poly1305",
360

361
            /* Stream ciphers */
362
            "RC4",
363
            "Salsa20",
364
            "ChaCha20",
365

366
            /* Hashes */
367
            "SHA-1",
368
            "SHA-256",
369
            "SHA-512",
370
            "SHA-3(256)",
371
            "SHA-3(512)",
372
            "Ascon-Hash256",
373
            "RIPEMD-160",
374
            "Skein-512",
375
            "Blake2b",
376
            "Whirlpool",
377

378
            /* XOFs */
379
            "SHAKE-128",
380
            "SHAKE-256",
381
            "Ascon-XOF128",
382

383
            /* MACs */
384
            "CMAC(AES-128)",
385
            "HMAC(SHA-256)",
386

387
            /* pubkey */
388
            "RSA",
389
            "DH",
390
            "ECDH",
391
            "ECDSA",
392
            "Ed25519",
393
            "Ed448",
394
            "X25519",
395
            "X448",
396
            "ML-KEM",
397
            "ML-DSA",
398
            "SLH-DSA",
399
            "FrodoKEM",
400
            "HSS-LMS",
401
         };
×
402
         // clang-format on
403
      }
404

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

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

409
      void go() override {
39 ✔
410
         const uint64_t milliseconds = get_arg_sz("msec");
39 ✔
411
         const std::string ecc_groups_arg = get_arg("ecc-groups");
39 ✔
412
         const std::string format = get_arg("format");
49 ✔
413
         const std::string clock_ratio = get_arg("cpu-clock-ratio");
49 ✔
414

415
         const size_t clock_speed = get_arg_sz("cpu-clock-speed");
39 ✔
416

417
         m_time_unit = [](std::string_view tu) {
166 ✔
418
            if(tu == "ms") {
39 ✔
419
               return 1000;
39 ✔
420
            } else if(tu == "us") {
×
421
               return 1000 * 1000;
×
422
            } else if(tu == "ns") {
×
423
               return 1000 * 1000 * 1000;
×
424
            } else {
425
               throw CLI_Usage_Error("Unknown time unit (supported: ms, us, ns)");
×
426
            }
427
         }(get_arg("time-unit"));
39 ✔
428

429
         /*
430
         * This argument is intended to be the ratio between the cycle counter
431
         * and the actual machine cycles. It is extremely unlikely that there is
432
         * any machine where the cycle counter increments faster than the actual
433
         * clock.
434
         */
435
         const double clock_cycle_ratio = [&]() {
120 ✔
436
            const char* begin = clock_ratio.c_str();
39 ✔
437
            char* end = nullptr;
39 ✔
438
            const double ratio = std::strtod(begin, &end);
39 ✔
439

440
            if(clock_ratio.empty() || end != begin + clock_ratio.size() || !std::isfinite(ratio) || ratio <= 0.0 ||
39 ✔
441
               ratio > 1.0) {
442
               throw CLI_Usage_Error("Unlikely CPU clock ratio of " + clock_ratio);
14 ✔
443
            }
444

445
            return 1.0 / ratio;
32 ✔
446
         }();
39 ✔
447

448
#if defined(BOTAN_HAS_OS_UTILS)
449
         if(clock_speed != 0 && Botan::OS::get_cpu_cycle_counter() != 0) {
32 ✔
450
            error_output() << "The --cpu-clock-speed option is only intended to be used on "
×
451
                              "platforms without access to a cycle counter.\n"
452
                              "Expect incorrect results\n\n";
×
453
         }
454
#endif
455

456
         if(format == "table") {
32 ✔
457
            m_summary = std::make_unique<Summary>();
1 ✔
458
         } else if(format == "json") {
31 ✔
459
            m_json = std::make_unique<JSON_Output>();
11 ✔
460
         } else if(format != "default") {
30 ✔
461
            throw CLI_Usage_Error("Unknown --format type '" + format + "'");
×
462
         }
463

464
         const auto ecc_groups = interpret_ecc_groups(ecc_groups_arg);
32 ✔
465

466
         std::vector<std::string> algos = get_arg_list("algos");
32 ✔
467

468
         const std::vector<size_t> buf_sizes = unique_buffer_sizes(get_arg("buf-size"));
67 ✔
469

470
#if defined(BOTAN_HAS_CPUID)
471
         for(const std::string& cpuid_to_clear : Command::split_on(get_arg("clear-cpuid"), ',')) {
30 ✔
472
            if(auto bit = Botan::CPUID::bit_from_string(cpuid_to_clear)) {
1 ✔
473
               Botan::CPUID::clear_cpuid_bit(*bit);
×
474
            } else {
475
               error_output() << "Warning don't know CPUID flag '" << cpuid_to_clear << "'\n";
1 ✔
476
            }
477
         }
29 ✔
478
#endif
479

480
         if(verbose() || m_summary) {
29 ✔
481
#if defined(BOTAN_HAS_CPUID)
482
            output() << Botan::version_string() << "\n"
2 ✔
483
                     << "CPUID: " << Botan::CPUID::to_string() << "\n\n";
3 ✔
484
#else
485
            output() << Botan::version_string() << "\n\n";
486
#endif
487
         }
488

489
         const bool using_defaults = (algos.empty());
29 ✔
490
         if(using_defaults) {
29 ✔
491
            algos = default_benchmark_list();
×
492
         }
493

494
         const PerfConfig perf_config([&](const Timer& t) { this->record_result(t); },
566 ✔
495
                                      clock_speed,
496
                                      clock_cycle_ratio,
497
                                      milliseconds,
498
                                      ecc_groups,
499
                                      buf_sizes,
500
                                      flag_set("one-shot"),
29 ✔
501
                                      this->error_output(),
502
                                      this->rng());
87 ✔
503

504
         for(const auto& algo : algos) {
88 ✔
505
            if(auto perf = PerfTest::get(algo)) {
59 ✔
506
               perf->go(perf_config);
59 ✔
507
            } else if(verbose() || !using_defaults) {
×
508
               error_output() << "Unknown algorithm '" << algo << "'\n";
×
509
            }
59 ✔
510
         }
511

512
         if(m_json) {
29 ✔
513
            output() << m_json->print();
2 ✔
514
         }
515
         if(m_summary) {
29 ✔
516
            output() << m_summary->print() << "\n";
3 ✔
517
         }
518

519
         if(verbose() && clock_speed == 0 && m_cycles_consumed > 0 && m_ns_taken > 0) {
29 ✔
520
            const double seconds = static_cast<double>(m_ns_taken) / 1000000000;
×
521
            const double Hz = static_cast<double>(m_cycles_consumed) / seconds;
×
522
            const double MHz = Hz / 1000000;
×
523
            output() << "\nEstimated clock speed " << MHz << " MHz\n";
×
524
         }
525
      }
84 ✔
526

527
   private:
528
      size_t m_time_unit = 0;
529
      uint64_t m_cycles_consumed = 0;
530
      uint64_t m_ns_taken = 0;
531
      std::unique_ptr<Summary> m_summary;
532
      std::unique_ptr<JSON_Output> m_json;
533

534
      void record_result(const Timer& t) {
566 ✔
535
         m_ns_taken += t.value();
566 ✔
536
         m_cycles_consumed += t.cycles_consumed();
566 ✔
537
         if(m_json) {
566 ✔
538
            m_json->add(t);
2 ✔
539
         } else {
540
            output() << format_timer(t, m_time_unit) << "\n" << std::flush;
1,128 ✔
541

542
            if(m_summary) {
564 ✔
543
               m_summary->add(t);
2 ✔
544
            }
545
         }
546
      }
566 ✔
547
};
548

549
BOTAN_REGISTER_COMMAND("speed", Speed);
40 ✔
550

551
}  // namespace
552

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

© 2026 Coveralls, Inc