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

randombit / botan / 13536213672

26 Feb 2025 03:22AM UTC coverage: 91.691% (-0.005%) from 91.696%
13536213672

push

github

web-flow
Merge pull request #4718 from randombit/jack/split-cpuid

Make cpuid module optional

95828 of 104512 relevant lines covered (91.69%)

11271236.94 hits per line

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

78.4
/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/fmt.h>
21
#include <botan/internal/stl_util.h>
22
#include <botan/internal/target_info.h>
23

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

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

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

36
namespace Botan_CLI {
37

38
namespace {
39

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

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

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

49
         out << "{"
1✔
50
             << "\"arch\": \"" << BOTAN_TARGET_ARCH << "\", "
51
             << "\"version\": \"" << Botan::short_version_cstr() << "\", "
52
             << "\"git\": \"" << BOTAN_VERSION_VC_REVISION << "\", "
53
             << "\"compiler\": \"" << BOTAN_COMPILER_INVOCATION_STRING << "\""
54
             << "},\n";
1✔
55

56
         for(size_t i = 0; i != m_results.size(); ++i) {
3✔
57
            const Timer& t = m_results[i];
2✔
58

59
            out << "{"
2✔
60
                << "\"algo\": \"" << t.get_name() << "\", "
2✔
61
                << "\"op\": \"" << t.doing() << "\", "
2✔
62
                << "\"events\": " << t.events() << ", ";
2✔
63

64
            if(t.cycles_consumed() > 0) {
4✔
65
               out << "\"cycles\": " << t.cycles_consumed() << ", ";
4✔
66
            }
67

68
            if(t.buf_size() > 0) {
2✔
69
               out << "\"bps\": " << static_cast<uint64_t>(t.events() / (t.value() / 1000000000.0)) << ", ";
2✔
70
               out << "\"buf_size\": " << t.buf_size() << ", ";
2✔
71
            }
72

73
            out << "\"nanos\": " << t.value() << "}";
2✔
74

75
            if(i != m_results.size() - 1) {
2✔
76
               out << ",";
1✔
77
            }
78

79
            out << "\n";
2✔
80
         }
81
         out << "]\n";
1✔
82

83
         return out.str();
2✔
84
      }
1✔
85

86
   private:
87
      std::vector<Timer> m_results;
88
};
89

90
class Summary final {
1✔
91
   public:
92
      Summary() = default;
1✔
93

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

102
      std::string print() {
1✔
103
         const size_t name_padding = 35;
1✔
104
         const size_t op_name_padding = 16;
1✔
105
         const size_t op_padding = 16;
1✔
106

107
         std::ostringstream result_ss;
1✔
108
         result_ss << std::fixed;
1✔
109

110
         if(!m_bps_entries.empty()) {
1✔
111
            result_ss << "\n";
1✔
112

113
            // add table header
114
            result_ss << std::setw(name_padding) << std::left << "algo" << std::setw(op_name_padding) << std::left
1✔
115
                      << "operation";
1✔
116

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

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

128
               result_ss << std::setw(name_padding) << std::left << (entry.first.second) << std::setw(op_name_padding)
2✔
129
                         << std::left << (entry.first.first);
2✔
130

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

140
               result_ss << "\n";
2✔
141
            }
142

143
            result_ss << "\n[results are the number of 1000s bytes processed per second]\n";
1✔
144
         }
145

146
         if(!m_ops_entries.empty()) {
1✔
147
            result_ss << std::setprecision(6) << "\n";
×
148

149
            // sort entries
150
            std::sort(m_ops_entries.begin(), m_ops_entries.end());
×
151

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

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

167
         return result_ss.str();
2✔
168
      }
1✔
169

170
   private:
171
      std::map<std::pair<std::string, std::string>, std::vector<Timer>> m_bps_entries;
172
      std::vector<Timer> m_ops_entries;
173
};
174

175
std::vector<size_t> unique_buffer_sizes(const std::string& cmdline_arg) {
28✔
176
   const size_t MAX_BUF_SIZE = 64 * 1024 * 1024;
28✔
177

178
   std::set<size_t> buf;
28✔
179
   for(const std::string& size_str : Command::split_on(cmdline_arg, ',')) {
54✔
180
      size_t x = 0;
29✔
181
      try {
29✔
182
         size_t converted = 0;
29✔
183
         x = static_cast<size_t>(std::stoul(size_str, &converted, 0));
29✔
184

185
         if(converted != size_str.size()) {
28✔
186
            throw CLI_Usage_Error("Invalid integer");
×
187
         }
188
      } catch(std::exception&) {
1✔
189
         throw CLI_Usage_Error("Invalid integer value '" + size_str + "' for option buf-size");
2✔
190
      }
1✔
191

192
      if(x == 0) {
28✔
193
         throw CLI_Usage_Error("Cannot have a zero-sized buffer");
2✔
194
      }
195

196
      if(x > MAX_BUF_SIZE) {
27✔
197
         throw CLI_Usage_Error("Specified buffer size is too large");
2✔
198
      }
199

200
      buf.insert(x);
26✔
201
   }
28✔
202

203
   return std::vector<size_t>(buf.begin(), buf.end());
28✔
204
}
25✔
205

206
std::string format_timer(const Timer& t, size_t time_unit) {
489✔
207
   constexpr size_t MiB = 1024 * 1024;
489✔
208

209
   std::ostringstream oss;
489✔
210

211
   oss << t.get_name() << " ";
489✔
212

213
   const uint64_t events = t.events();
489✔
214

215
   if(t.buf_size() == 0) {
489✔
216
      // Report operations/time unit
217

218
      if(events == 0) {
466✔
219
         oss << "no events ";
×
220
      } else {
221
         oss << static_cast<uint64_t>(t.events_per_second()) << ' ' << t.doing() << "/sec; ";
932✔
222

223
         if(time_unit == 1000) {
466✔
224
            oss << std::setprecision(2) << std::fixed << (t.milliseconds() / events) << " ms/op ";
466✔
225
         } else if(time_unit == 1000 * 1000) {
×
226
            oss << std::setprecision(2) << std::fixed << (t.microseconds() / events) << " us/op ";
×
227
         } else if(time_unit == 1000 * 1000 * 1000) {
×
228
            oss << std::setprecision(0) << std::fixed << (t.nanoseconds() / events) << " ns/op ";
×
229
         }
230

231
         if(t.cycles_consumed() != 0 && events > 0) {
932✔
232
            const double cycles_per_op = static_cast<double>(t.cycles_consumed()) / events;
466✔
233
            const int precision = (cycles_per_op < 10000) ? 2 : 0;
466✔
234
            oss << std::fixed << std::setprecision(precision) << cycles_per_op << " cycles/op ";
466✔
235
         }
236

237
         oss << "(" << events << " " << (events == 1 ? "op" : "ops") << " in " << t.milliseconds() << " ms)";
714✔
238
      }
239
   } else {
240
      // Bulk op - report bytes/time unit
241

242
      const double MiB_total = static_cast<double>(events) / MiB;
23✔
243
      const double MiB_per_sec = MiB_total / t.seconds();
23✔
244

245
      if(!t.doing().empty()) {
23✔
246
         oss << t.doing() << " ";
23✔
247
      }
248

249
      if(t.buf_size() > 0) {
23✔
250
         oss << "buffer size " << t.buf_size() << " bytes: ";
23✔
251
      }
252

253
      if(events == 0) {
23✔
254
         oss << "N/A ";
×
255
      } else {
256
         oss << std::fixed << std::setprecision(3) << MiB_per_sec << " MiB/sec ";
23✔
257
      }
258

259
      if(t.cycles_consumed() != 0 && events > 0) {
46✔
260
         const double cycles_per_byte = static_cast<double>(t.cycles_consumed()) / events;
23✔
261
         oss << std::fixed << std::setprecision(2) << cycles_per_byte << " cycles/byte ";
23✔
262
      }
263

264
      oss << "(" << MiB_total << " MiB in " << t.milliseconds() << " ms)";
23✔
265
   }
266

267
   return oss.str();
978✔
268
}
489✔
269

270
}  // namespace
271

272
class Speed final : public Command {
×
273
   public:
274
      Speed() :
29✔
275
            Command(
276
               "speed --msec=500 --format=default --time-unit=ms --ecc-groups= --buf-size=1024 --clear-cpuid= --cpu-clock-speed=0 --cpu-clock-ratio=1.0 *algos") {
58✔
277
      }
29✔
278

279
      static std::vector<std::string> default_benchmark_list() {
×
280
         /*
281
         This is not intended to be exhaustive: it just hits the high
282
         points of the most interesting or widely used algorithms.
283
         */
284
         // clang-format off
285
         return {
×
286
            /* Block ciphers */
287
            "AES-128",
288
            "AES-192",
289
            "AES-256",
290
            "ARIA-128",
291
            "ARIA-192",
292
            "ARIA-256",
293
            "Blowfish",
294
            "CAST-128",
295
            "Camellia-128",
296
            "Camellia-192",
297
            "Camellia-256",
298
            "DES",
299
            "TripleDES",
300
            "GOST-28147-89",
301
            "IDEA",
302
            "Noekeon",
303
            "SHACAL2",
304
            "SM4",
305
            "Serpent",
306
            "Threefish-512",
307
            "Twofish",
308

309
            /* Cipher modes */
310
            "AES-128/CBC",
311
            "AES-128/CTR-BE",
312
            "AES-128/EAX",
313
            "AES-128/OCB",
314
            "AES-128/GCM",
315
            "AES-128/XTS",
316
            "AES-128/SIV",
317

318
            "Serpent/CBC",
319
            "Serpent/CTR-BE",
320
            "Serpent/EAX",
321
            "Serpent/OCB",
322
            "Serpent/GCM",
323
            "Serpent/XTS",
324
            "Serpent/SIV",
325

326
            "ChaCha20Poly1305",
327

328
            /* Stream ciphers */
329
            "RC4",
330
            "Salsa20",
331
            "ChaCha20",
332

333
            /* Hashes */
334
            "SHA-1",
335
            "SHA-256",
336
            "SHA-512",
337
            "SHA-3(256)",
338
            "SHA-3(512)",
339
            "RIPEMD-160",
340
            "Skein-512",
341
            "Blake2b",
342
            "Whirlpool",
343

344
            /* XOFs */
345
            "SHAKE-128",
346
            "SHAKE-256",
347

348
            /* MACs */
349
            "CMAC(AES-128)",
350
            "HMAC(SHA-256)",
351

352
            /* pubkey */
353
            "RSA",
354
            "DH",
355
            "ECDH",
356
            "ECDSA",
357
            "Ed25519",
358
            "Ed448",
359
            "X25519",
360
            "X448",
361
            "ML-KEM",
362
            "ML-DSA",
363
            "SLH-DSA",
364
            "FrodoKEM",
365
            "HSS-LMS",
366
         };
×
367
         // clang-format on
368
      }
369

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

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

374
      void go() override {
28✔
375
         std::chrono::milliseconds msec(get_arg_sz("msec"));
28✔
376
         std::vector<std::string> ecc_groups = Command::split_on(get_arg("ecc-groups"), ',');
56✔
377
         const std::string format = get_arg("format");
28✔
378
         const std::string clock_ratio = get_arg("cpu-clock-ratio");
31✔
379

380
         const size_t clock_speed = get_arg_sz("cpu-clock-speed");
28✔
381

382
         double clock_cycle_ratio = std::strtod(clock_ratio.c_str(), nullptr);
28✔
383

384
         m_time_unit = [](std::string_view tu) {
115✔
385
            if(tu == "ms") {
28✔
386
               return 1000;
28✔
387
            } else if(tu == "us") {
×
388
               return 1000 * 1000;
×
389
            } else if(tu == "ns") {
×
390
               return 1000 * 1000 * 1000;
×
391
            } else {
392
               throw CLI_Usage_Error("Unknown time unit (supported: ms, us, ns)");
×
393
            }
394
         }(get_arg("time-unit"));
28✔
395

396
         /*
397
         * This argument is intended to be the ratio between the cycle counter
398
         * and the actual machine cycles. It is extremely unlikely that there is
399
         * any machine where the cycle counter increments faster than the actual
400
         * clock.
401
         */
402
         if(clock_cycle_ratio < 0.0 || clock_cycle_ratio > 1.0) {
28✔
403
            throw CLI_Usage_Error("Unlikely CPU clock ratio of " + clock_ratio);
×
404
         }
405

406
         clock_cycle_ratio = 1.0 / clock_cycle_ratio;
28✔
407

408
#if defined(BOTAN_HAS_OS_UTILS)
409
         if(clock_speed != 0 && Botan::OS::get_cpu_cycle_counter() != 0) {
28✔
410
            error_output() << "The --cpu-clock-speed option is only intended to be used on "
×
411
                              "platforms without access to a cycle counter.\n"
412
                              "Expect incorrect results\n\n";
×
413
         }
414
#endif
415

416
         if(format == "table") {
28✔
417
            m_summary = std::make_unique<Summary>();
1✔
418
         } else if(format == "json") {
27✔
419
            m_json = std::make_unique<JSON_Output>();
1✔
420
         } else if(format != "default") {
26✔
421
            throw CLI_Usage_Error("Unknown --format type '" + format + "'");
×
422
         }
423

424
#if defined(BOTAN_HAS_ECC_GROUP)
425
         if(ecc_groups.empty()) {
28✔
426
            ecc_groups = {"secp256r1", "secp384r1", "secp521r1", "brainpool256r1", "brainpool384r1", "brainpool512r1"};
224✔
427
         } else if(ecc_groups.size() == 1 && ecc_groups[0] == "all") {
×
428
            auto all = Botan::EC_Group::known_named_groups();
×
429
            ecc_groups.assign(all.begin(), all.end());
×
430
         }
×
431
#endif
432

433
         std::vector<std::string> algos = get_arg_list("algos");
31✔
434

435
         const std::vector<size_t> buf_sizes = unique_buffer_sizes(get_arg("buf-size"));
59✔
436

437
#if defined(BOTAN_HAS_CPUID)
438
         for(const std::string& cpuid_to_clear : Command::split_on(get_arg("clear-cpuid"), ',')) {
26✔
439
            auto bits = Botan::CPUID::bit_from_string(cpuid_to_clear);
1✔
440
            if(bits.empty()) {
1✔
441
               error_output() << "Warning don't know CPUID flag '" << cpuid_to_clear << "'\n";
1✔
442
            }
443

444
            for(auto bit : bits) {
1✔
445
               Botan::CPUID::clear_cpuid_bit(bit);
×
446
            }
447
         }
26✔
448
#endif
449

450
         if(verbose() || m_summary) {
25✔
451
#if defined(BOTAN_HAS_CPUID)
452
            output() << Botan::version_string() << "\n"
2✔
453
                     << "CPUID: " << Botan::CPUID::to_string() << "\n\n";
3✔
454
#else
455
            output() << Botan::version_string() << "\n\n";
456
#endif
457
         }
458

459
         const bool using_defaults = (algos.empty());
25✔
460
         if(using_defaults) {
25✔
461
            algos = default_benchmark_list();
×
462
         }
463

464
         PerfConfig perf_config([&](const Timer& t) { this->record_result(t); },
516✔
465
                                clock_speed,
466
                                clock_cycle_ratio,
467
                                msec,
468
                                ecc_groups,
469
                                buf_sizes,
470
                                this->error_output(),
471
                                this->rng());
25✔
472

473
         for(const auto& algo : algos) {
70✔
474
            if(auto perf = PerfTest::get(algo)) {
45✔
475
               perf->go(perf_config);
45✔
476
            } else if(verbose() || !using_defaults) {
×
477
               error_output() << "Unknown algorithm '" << algo << "'\n";
×
478
            }
45✔
479
         }
480

481
         if(m_json) {
25✔
482
            output() << m_json->print();
2✔
483
         }
484
         if(m_summary) {
25✔
485
            output() << m_summary->print() << "\n";
3✔
486
         }
487

488
         if(verbose() && clock_speed == 0 && m_cycles_consumed > 0 && m_ns_taken > 0) {
25✔
489
            const double seconds = static_cast<double>(m_ns_taken) / 1000000000;
×
490
            const double Hz = static_cast<double>(m_cycles_consumed) / seconds;
×
491
            const double MHz = Hz / 1000000;
×
492
            output() << "\nEstimated clock speed " << MHz << " MHz\n";
×
493
         }
494
      }
115✔
495

496
   private:
497
      size_t m_time_unit = 0;
498
      uint64_t m_cycles_consumed = 0;
499
      uint64_t m_ns_taken = 0;
500
      std::unique_ptr<Summary> m_summary;
501
      std::unique_ptr<JSON_Output> m_json;
502

503
      void record_result(const Timer& t) {
491✔
504
         m_ns_taken += t.value();
491✔
505
         m_cycles_consumed += t.cycles_consumed();
491✔
506
         if(m_json) {
491✔
507
            m_json->add(t);
2✔
508
         } else {
509
            output() << format_timer(t, m_time_unit) << std::endl;
978✔
510

511
            if(m_summary) {
489✔
512
               m_summary->add(t);
2✔
513
            }
514
         }
515
      }
491✔
516
};
517

518
BOTAN_REGISTER_COMMAND("speed", Speed);
29✔
519

520
}  // 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

© 2026 Coveralls, Inc