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

randombit / botan / 11346280461

15 Oct 2024 12:42PM UTC coverage: 91.116% (-0.4%) from 91.512%
11346280461

Pull #4291

github

web-flow
Merge bfb91307b into 41619a286
Pull Request #4291: PQC: SLH-DSA

90991 of 99863 relevant lines covered (91.12%)

9298501.65 hits per line

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

80.11
/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
#include <botan/internal/timer.h>
25

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

30
namespace Botan_CLI {
31

32
using Botan::Timer;
33

34
namespace {
35

36
class JSON_Output final {
2✔
37
   public:
38
      void add(const Timer& timer) { m_results.push_back(timer); }
2✔
39

40
      std::string print() const {
1✔
41
         std::ostringstream out;
1✔
42

43
         out << "[\n";
1✔
44

45
         for(size_t i = 0; i != m_results.size(); ++i) {
3✔
46
            const Timer& t = m_results[i];
2✔
47

48
            out << "{"
2✔
49
                << "\"algo\": \"" << t.get_name() << "\", "
2✔
50
                << "\"op\": \"" << t.doing() << "\", "
2✔
51
                << "\"events\": " << t.events() << ", ";
2✔
52

53
            if(t.cycles_consumed() > 0) {
4✔
54
               out << "\"cycles\": " << t.cycles_consumed() << ", ";
4✔
55
            }
56

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

62
            out << "\"nanos\": " << t.value() << "}";
2✔
63

64
            if(i != m_results.size() - 1) {
2✔
65
               out << ",";
1✔
66
            }
67

68
            out << "\n";
2✔
69
         }
70
         out << "]\n";
1✔
71

72
         return out.str();
2✔
73
      }
1✔
74

75
   private:
76
      std::vector<Timer> m_results;
77
};
78

79
class Summary final {
1✔
80
   public:
81
      Summary() = default;
1✔
82

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

91
      std::string print() {
1✔
92
         const size_t name_padding = 35;
1✔
93
         const size_t op_name_padding = 16;
1✔
94
         const size_t op_padding = 16;
1✔
95

96
         std::ostringstream result_ss;
1✔
97
         result_ss << std::fixed;
1✔
98

99
         if(!m_bps_entries.empty()) {
1✔
100
            result_ss << "\n";
1✔
101

102
            // add table header
103
            result_ss << std::setw(name_padding) << std::left << "algo" << std::setw(op_name_padding) << std::left
1✔
104
                      << "operation";
1✔
105

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

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

117
               result_ss << std::setw(name_padding) << std::left << (entry.first.second) << std::setw(op_name_padding)
2✔
118
                         << std::left << (entry.first.first);
2✔
119

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

129
               result_ss << "\n";
2✔
130
            }
131

132
            result_ss << "\n[results are the number of 1000s bytes processed per second]\n";
1✔
133
         }
134

135
         if(!m_ops_entries.empty()) {
1✔
136
            result_ss << std::setprecision(6) << "\n";
×
137

138
            // sort entries
139
            std::sort(m_ops_entries.begin(), m_ops_entries.end());
×
140

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

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

156
         return result_ss.str();
2✔
157
      }
1✔
158

159
   private:
160
      std::map<std::pair<std::string, std::string>, std::vector<Timer>> m_bps_entries;
161
      std::vector<Timer> m_ops_entries;
162
};
163

164
std::vector<size_t> unique_buffer_sizes(const std::string& cmdline_arg) {
30✔
165
   const size_t MAX_BUF_SIZE = 64 * 1024 * 1024;
30✔
166

167
   std::set<size_t> buf;
30✔
168
   for(const std::string& size_str : Command::split_on(cmdline_arg, ',')) {
58✔
169
      size_t x = 0;
31✔
170
      try {
31✔
171
         size_t converted = 0;
31✔
172
         x = static_cast<size_t>(std::stoul(size_str, &converted, 0));
31✔
173

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

181
      if(x == 0) {
30✔
182
         throw CLI_Usage_Error("Cannot have a zero-sized buffer");
2✔
183
      }
184

185
      if(x > MAX_BUF_SIZE) {
29✔
186
         throw CLI_Usage_Error("Specified buffer size is too large");
2✔
187
      }
188

189
      buf.insert(x);
28✔
190
   }
30✔
191

192
   return std::vector<size_t>(buf.begin(), buf.end());
30✔
193
}
27✔
194

195
}  // namespace
196

197
class Speed final : public Command {
×
198
   public:
199
      Speed() :
31✔
200
            Command(
201
               "speed --msec=500 --format=default --ecc-groups= --buf-size=1024 --clear-cpuid= --cpu-clock-speed=0 --cpu-clock-ratio=1.0 *algos") {
62✔
202
      }
31✔
203

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

234
            /* Cipher modes */
235
            "AES-128/CBC",
236
            "AES-128/CTR-BE",
237
            "AES-128/EAX",
238
            "AES-128/OCB",
239
            "AES-128/GCM",
240
            "AES-128/XTS",
241
            "AES-128/SIV",
242

243
            "Serpent/CBC",
244
            "Serpent/CTR-BE",
245
            "Serpent/EAX",
246
            "Serpent/OCB",
247
            "Serpent/GCM",
248
            "Serpent/XTS",
249
            "Serpent/SIV",
250

251
            "ChaCha20Poly1305",
252

253
            /* Stream ciphers */
254
            "RC4",
255
            "Salsa20",
256
            "ChaCha20",
257

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

269
            /* XOFs */
270
            "SHAKE-128",
271
            "SHAKE-256",
272

273
            /* MACs */
274
            "CMAC(AES-128)",
275
            "HMAC(SHA-256)",
276

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

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

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

300
      void go() override {
30✔
301
         std::chrono::milliseconds msec(get_arg_sz("msec"));
30✔
302
         std::vector<std::string> ecc_groups = Command::split_on(get_arg("ecc-groups"), ',');
60✔
303
         const std::string format = get_arg("format");
30✔
304
         const std::string clock_ratio = get_arg("cpu-clock-ratio");
33✔
305
         m_clock_speed = get_arg_sz("cpu-clock-speed");
30✔
306

307
         m_clock_cycle_ratio = std::strtod(clock_ratio.c_str(), nullptr);
30✔
308

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

319
         m_clock_cycle_ratio = 1.0 / m_clock_cycle_ratio;
30✔
320

321
         if(m_clock_speed != 0 && Botan::OS::get_cpu_cycle_counter() != 0) {
30✔
322
            error_output() << "The --cpu-clock-speed option is only intended to be used on "
×
323
                              "platforms without access to a cycle counter.\n"
324
                              "Expected incorrect results\n\n";
×
325
         }
326

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

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

344
         std::vector<std::string> algos = get_arg_list("algos");
33✔
345

346
         const std::vector<size_t> buf_sizes = unique_buffer_sizes(get_arg("buf-size"));
63✔
347

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

354
            for(auto bit : bits) {
1✔
355
               Botan::CPUID::clear_cpuid_bit(bit);
×
356
            }
357
         }
28✔
358

359
         if(verbose() || m_summary) {
27✔
360
            output() << Botan::version_string() << "\n"
2✔
361
                     << "CPUID: " << Botan::CPUID::to_string() << "\n\n";
3✔
362
         }
363

364
         const bool using_defaults = (algos.empty());
27✔
365
         if(using_defaults) {
27✔
366
            algos = default_benchmark_list();
×
367
         }
368

369
         class PerfConfig_Cli final : public PerfConfig {
27✔
370
            public:
371
               PerfConfig_Cli(std::chrono::milliseconds runtime,
27✔
372
                              const std::vector<std::string>& ecc_groups,
373
                              const std::vector<size_t>& buffer_sizes,
374
                              Speed* speed) :
27✔
375
                     m_runtime(runtime), m_ecc_groups(ecc_groups), m_buffer_sizes(buffer_sizes), m_speed(speed) {}
27✔
376

377
               const std::vector<size_t>& buffer_sizes() const override { return m_buffer_sizes; }
15✔
378

379
               const std::vector<std::string>& ecc_groups() const override { return m_ecc_groups; }
6✔
380

381
               std::chrono::milliseconds runtime() const override { return m_runtime; }
130✔
382

383
               std::ostream& error_output() const override { return m_speed->error_output(); }
×
384

385
               Botan::RandomNumberGenerator& rng() const override { return m_speed->rng(); }
10,704✔
386

387
               void record_result(const Botan::Timer& timer) const override { m_speed->record_result(timer); }
462✔
388

389
               std::unique_ptr<Botan::Timer> make_timer(const std::string& alg,
485✔
390
                                                        uint64_t event_mult,
391
                                                        const std::string& what,
392
                                                        const std::string& provider,
393
                                                        size_t buf_size) const override {
394
                  return m_speed->make_timer(alg, event_mult, what, provider, buf_size);
970✔
395
               }
396

397
            private:
398
               std::chrono::milliseconds m_runtime;
399
               std::vector<std::string> m_ecc_groups;
400
               std::vector<size_t> m_buffer_sizes;
401
               Speed* m_speed;
402
         };
403

404
         PerfConfig_Cli perf_config(msec, ecc_groups, buf_sizes, this);
27✔
405

406
         for(const auto& algo : algos) {
74✔
407
            using namespace std::placeholders;
47✔
408

409
            if(auto perf = PerfTest::get(algo)) {
47✔
410
               perf->go(perf_config);
47✔
411
            } else if(verbose() || !using_defaults) {
×
412
               error_output() << "Unknown algorithm '" << algo << "'\n";
×
413
            }
47✔
414
         }
415

416
         if(m_json) {
27✔
417
            output() << m_json->print();
2✔
418
         }
419
         if(m_summary) {
27✔
420
            output() << m_summary->print() << "\n";
3✔
421
         }
422

423
         if(verbose() && m_clock_speed == 0 && m_cycles_consumed > 0 && m_ns_taken > 0) {
27✔
424
            const double seconds = static_cast<double>(m_ns_taken) / 1000000000;
×
425
            const double Hz = static_cast<double>(m_cycles_consumed) / seconds;
×
426
            const double MHz = Hz / 1000000;
×
427
            output() << "\nEstimated clock speed " << MHz << " MHz\n";
×
428
         }
429
      }
123✔
430

431
   private:
432
      size_t m_clock_speed = 0;
433
      double m_clock_cycle_ratio = 0.0;
434
      uint64_t m_cycles_consumed = 0;
435
      uint64_t m_ns_taken = 0;
436
      std::unique_ptr<Summary> m_summary;
437
      std::unique_ptr<JSON_Output> m_json;
438

439
      void record_result(const Timer& t) {
462✔
440
         m_ns_taken += t.value();
462✔
441
         m_cycles_consumed += t.cycles_consumed();
462✔
442
         if(m_json) {
462✔
443
            m_json->add(t);
2✔
444
         } else {
445
            output() << t.to_string() << std::flush;
920✔
446
            if(m_summary) {
460✔
447
               m_summary->add(t);
2✔
448
            }
449
         }
450
      }
462✔
451

452
      void record_result(const std::unique_ptr<Timer>& t) { record_result(*t); }
453

454
      std::unique_ptr<Timer> make_timer(const std::string& name,
485✔
455
                                        uint64_t event_mult = 1,
456
                                        const std::string& what = "",
457
                                        const std::string& provider = "",
458
                                        size_t buf_size = 0) {
459
         return std::make_unique<Timer>(name, provider, what, event_mult, buf_size, m_clock_cycle_ratio, m_clock_speed);
485✔
460
      }
461
};
462

463
BOTAN_REGISTER_COMMAND("speed", Speed);
31✔
464

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