f9bde51eea3437d63f615c18096e8a4cae070b39
[benchmarks.git] / src / main.cpp
1 #include "all_benchmarks.h"
2 #include "harness.h"
3 #include "json.h"
4 #include <charconv>
5 #include <cstdint>
6 #include <cstdlib>
7 #include <functional>
8 #include <initializer_list>
9 #include <iostream>
10 #include <limits>
11 #include <map>
12 #include <optional>
13 #include <ostream>
14 #include <string_view>
15 #include <system_error>
16 #include <type_traits>
17 #include <unordered_set>
18 #include <vector>
19
20 using namespace std::literals;
21
22 enum class OptionValueKind
23 {
24 None,
25 Required,
26 };
27
28 class OptionsParser;
29
30 struct Option final
31 {
32 char short_name = '\0';
33 std::string_view long_name = "", description = "";
34 bool required = false;
35 bool all_other_args_not_required = false;
36 OptionValueKind value_kind = OptionValueKind::None;
37 std::function<void(OptionsParser &parser,
38 std::optional<std::string_view> value)>
39 parse_value;
40 bool has_short_name() const
41 {
42 return short_name != '\0';
43 }
44 bool has_long_name() const
45 {
46 return !long_name.empty();
47 }
48 friend std::ostream &operator<<(std::ostream &os, const Option &option)
49 {
50 if (option.has_long_name())
51 {
52 os << "--" << option.long_name;
53 }
54 else if (option.has_short_name())
55 {
56 os << "-" << option.short_name;
57 }
58 else
59 {
60 os << "--<unnamed>";
61 }
62 return os;
63 }
64 };
65
66 class Options final
67 {
68 friend class OptionsParser;
69
70 private:
71 std::vector<Option> options;
72 std::map<char, std::size_t> short_options;
73 std::map<std::string_view, std::size_t> long_options;
74
75 public:
76 Options(std::initializer_list<Option> options_)
77 : options(options_), short_options(), long_options()
78 {
79 for (std::size_t i = 0; i < options.size(); i++)
80 {
81 auto &option = options[i];
82 if (option.has_short_name())
83 short_options[option.short_name] = i;
84 if (option.has_long_name())
85 long_options[option.long_name] = i;
86 }
87 }
88
89 public:
90 std::vector<std::string_view> parse(const char *const *argv) const;
91 };
92
93 class OptionsParser final
94 {
95 private:
96 const Options &options;
97 const char *const *argv;
98 std::string_view argv0;
99 std::vector<bool> seen_options;
100 bool check_required = true;
101 std::optional<std::size_t> current_option_index;
102
103 private:
104 const Option &current_option()
105 {
106 static const Option default_option{};
107 if (current_option_index)
108 {
109 return options.options[*current_option_index];
110 }
111 else
112 {
113 return default_option;
114 }
115 }
116
117 public:
118 OptionsParser(const Options &options, const char *const *argv)
119 : options(options), argv(argv + 1),
120 argv0(argv[0] ? argv[0] : "<none>"),
121 seen_options(options.options.size(), false), current_option_index()
122 {
123 if (!argv[0])
124 {
125 static const char *null[] = {nullptr};
126 this->argv = null;
127 help_and_exit("missing program name");
128 }
129 }
130
131 private:
132 void parse_option(std::size_t option_index, std::string_view prefix,
133 std::string_view name,
134 std::optional<std::string_view> value)
135 {
136 auto &option = options.options[option_index];
137 switch (option.value_kind)
138 {
139 case OptionValueKind::None:
140 if (value)
141 {
142 help_and_exit("value not allowed for ", prefix, name);
143 }
144 break;
145 case OptionValueKind::Required:
146 if (!value)
147 {
148 if (*argv)
149 {
150 value = *argv++;
151 }
152 else
153 {
154 help_and_exit("missing value for ", prefix, name);
155 }
156 }
157 break;
158 }
159 seen_options[option_index] = true;
160 if (option.all_other_args_not_required)
161 check_required = false;
162 current_option_index = option_index;
163 option.parse_value(*this, value);
164 current_option_index = std::nullopt;
165 }
166
167 public:
168 std::vector<std::string_view> parse()
169 {
170 std::vector<std::string_view> retval;
171 constexpr auto npos = std::string_view::npos;
172 while (*argv)
173 {
174 std::string_view arg = *argv++;
175 static constexpr std::string_view long_prefix = "--"sv;
176 static constexpr std::string_view short_prefix = "-"sv;
177 if (arg.rfind(long_prefix, 0) == 0) // it starts with `--`
178 {
179 arg.remove_prefix(long_prefix.size());
180 auto eq = arg.find('=');
181 if (eq == 0)
182 {
183 eq = npos;
184 }
185 if (arg.empty()) // just `--`
186 {
187 while (*argv)
188 {
189 retval.push_back(*argv++);
190 }
191 break;
192 }
193 auto name = arg.substr(0, eq);
194 std::optional<std::string_view> value;
195 if (eq != npos)
196 {
197 value = arg.substr(eq + 1);
198 }
199 auto iter = options.long_options.find(name);
200 if (iter == options.long_options.end())
201 {
202 help_and_exit("unknown option: ", long_prefix, name);
203 }
204 auto option_index = iter->second;
205 parse_option(option_index, long_prefix, name, value);
206 continue;
207 }
208 else if (arg.rfind(short_prefix, 0) == 0) // it starts with `-`
209 {
210 arg.remove_prefix(short_prefix.size());
211 if (arg.empty()) // just `-`
212 {
213 retval.push_back(short_prefix);
214 continue;
215 }
216 while (!arg.empty())
217 {
218 auto name = arg.substr(0, 1);
219 arg.remove_prefix(1);
220 auto iter = options.short_options.find(name[0]);
221 if (iter == options.short_options.end())
222 {
223 help_and_exit("unknown option: ", short_prefix, name);
224 }
225 auto option_index = iter->second;
226 std::optional<std::string_view> value;
227 switch (options.options[option_index].value_kind)
228 {
229 case OptionValueKind::None:
230 break;
231 case OptionValueKind::Required:
232 auto eq = arg.rfind('=', 0);
233 if (eq != npos)
234 {
235 value = arg.substr(eq + 1);
236 }
237 else if (!arg.empty())
238 {
239 value = arg;
240 }
241 arg = "";
242 break;
243 }
244 parse_option(option_index, short_prefix, name, value);
245 }
246 continue;
247 }
248 else
249 {
250 retval.push_back(arg);
251 }
252 }
253 if (check_required)
254 {
255 for (std::size_t i = 0; i < options.options.size(); i++)
256 {
257 auto &option = options.options[i];
258 if (option.required && !seen_options[i])
259 {
260 help_and_exit("missing required option ", option);
261 }
262 }
263 }
264 return retval;
265 }
266 template <typename... Options>
267 [[noreturn]] void help_and_exit(const Options &...error_msg) const
268 {
269 auto &os = sizeof...(error_msg) == 0 ? std::cout : std::cerr;
270 if (sizeof...(error_msg) != 0)
271 {
272 ((os << "Error: ") << ... << error_msg) << std::endl;
273 }
274 os << "Usage: " << argv0;
275 for (auto &option : options.options)
276 {
277 os << " ";
278 if (!option.required)
279 {
280 os << "[";
281 }
282 auto sep = "";
283 if (option.has_short_name())
284 {
285 os << "-" << option.short_name;
286 sep = "|";
287 }
288 if (option.has_long_name())
289 {
290 os << sep << "--" << option.long_name;
291 }
292 switch (option.value_kind)
293 {
294 case OptionValueKind::None:
295 break;
296 // TODO: case OptionValueKind::Optional:
297 case OptionValueKind::Required:
298 os << " <value>";
299 break;
300 }
301 if (!option.required)
302 {
303 os << "]";
304 }
305 }
306 os << std::endl << "Options:" << std::endl;
307 for (auto &option : options.options)
308 {
309 auto sep = "";
310 if (option.has_short_name())
311 {
312 os << "-" << option.short_name;
313 sep = "|";
314 }
315 if (option.has_long_name())
316 {
317 os << sep << "--" << option.long_name;
318 }
319 os << " " << option.description << std::endl;
320 }
321 std::exit(sizeof...(error_msg) == 0 ? 0 : 1);
322 }
323 template <typename Int, typename = void> struct ParseIntLimits;
324 template <typename Int>
325 struct ParseIntLimits<Int, std::enable_if_t<std::is_integral_v<Int>>> final
326 {
327 Int min_value = std::numeric_limits<Int>::min();
328 Int max_value = std::numeric_limits<Int>::max();
329 };
330 template <typename Int>
331 std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
332 std::optional<std::string_view> value, Int &i_value,
333 ParseIntLimits<Int> limits = {})
334 {
335 i_value = Int();
336 if (!value)
337 {
338 help_and_exit("missing value for ", current_option());
339 }
340 auto str = *value;
341 int base = 10;
342 if (0 == str.rfind("0x", 0) || 0 == str.rfind("0X", 0))
343 {
344 base = 16;
345 str.remove_prefix(2);
346 }
347 else if (0 == str.rfind("0o", 0) || 0 == str.rfind("0O", 0))
348 {
349 base = 8;
350 str.remove_prefix(2);
351 }
352 else if (0 == str.rfind("0b", 0) || 0 == str.rfind("0B", 0))
353 {
354 base = 2;
355 str.remove_prefix(2);
356 }
357 std::from_chars_result result = std::from_chars(
358 str.data(), str.data() + str.size(), i_value, base);
359 if (result.ptr != str.data() + str.size())
360 {
361 result.ec = std::errc::invalid_argument;
362 }
363 if (result.ec == std::errc())
364 {
365 if (i_value < limits.min_value || i_value > limits.max_value)
366 {
367 result.ec = std::errc::result_out_of_range;
368 }
369 }
370 if (result.ec == std::errc::result_out_of_range)
371 {
372 help_and_exit("value out of range: ", current_option(), "=",
373 *value);
374 }
375 else if (result.ec != std::errc())
376 {
377 help_and_exit("invalid value for: ", current_option());
378 }
379 }
380 template <typename Int>
381 std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
382 std::optional<std::string_view> value, std::optional<Int> &i_value,
383 bool required = true, ParseIntLimits<Int> limits = {})
384 {
385 if (!required && !value)
386 {
387 i_value = std::nullopt;
388 return;
389 }
390 i_value.emplace();
391 this->parse_int(value, i_value.value(), limits);
392 }
393 };
394
395 inline std::vector<std::string_view> Options::parse(
396 const char *const *argv) const
397 {
398 return OptionsParser(*this, argv).parse();
399 }
400
401 int main(int, char **argv)
402 {
403 Config config{};
404 std::optional<std::unordered_set<std::string>> enabled_benchmarks;
405 bool json_pretty = false;
406 Options options{
407 Option{
408 .short_name = 'h',
409 .long_name = "help",
410 .description = "Display usage and exit.",
411 .all_other_args_not_required = true,
412 .parse_value = [](OptionsParser &parser,
413 auto) { parser.help_and_exit(); },
414 },
415 Option{.short_name = 'j',
416 .long_name = "thread-count",
417 .description = "Number of threads to run on",
418 .value_kind = OptionValueKind::Required,
419 .parse_value =
420 [&](OptionsParser &parser, auto value) {
421 parser.parse_int(value, config.thread_count);
422 }},
423 Option{.short_name = 'n',
424 .long_name = "iter-count",
425 .description = "Number of iterations to run per thread",
426 .value_kind = OptionValueKind::Required,
427 .parse_value =
428 [&](OptionsParser &parser, auto value) {
429 parser.parse_int(value, config.iteration_count, true,
430 {.min_value = 1});
431 }},
432 Option{.long_name = "log2-mem-loc-count",
433 .description =
434 "Log base 2 of the number of memory locations to access",
435 .value_kind = OptionValueKind::Required,
436 .parse_value =
437 [&](OptionsParser &parser, auto value) {
438 parser.parse_int(
439 value, config.log2_memory_location_count,
440 {.max_value =
441 Config::max_sum_log2_mem_loc_count_and_stride -
442 config.log2_stride});
443 }},
444 Option{
445 .long_name = "log2-stride",
446 .description =
447 "Log base 2 of the stride used for accessing memory locations",
448 .value_kind = OptionValueKind::Required,
449 .parse_value =
450 [&](OptionsParser &parser, auto value) {
451 parser.parse_int(
452 value, config.log2_stride,
453 {.max_value =
454 Config::max_sum_log2_mem_loc_count_and_stride -
455 config.log2_memory_location_count});
456 }},
457 Option{
458 .short_name = 'b',
459 .long_name = "bench",
460 .description = "List of benchmarks that should be run",
461 .value_kind = OptionValueKind::Required,
462 .parse_value =
463 [&](OptionsParser &, std::optional<std::string_view> value) {
464 if (!enabled_benchmarks)
465 {
466 enabled_benchmarks.emplace();
467 }
468 enabled_benchmarks->emplace(value.value_or(""));
469 }},
470 Option{.long_name = "json",
471 .description = "Write the output in JSON format",
472 .value_kind = OptionValueKind::None,
473 .parse_value = [&](auto &, auto) { config.use_json = true; }},
474 Option{.long_name = "json-pretty",
475 .description = "Write the output in pretty JSON format",
476 .value_kind = OptionValueKind::None,
477 .parse_value =
478 [&](auto &, auto) {
479 config.use_json = true;
480 json_pretty = true;
481 }},
482 };
483 OptionsParser parser(options, argv);
484 auto args = parser.parse();
485 if (!args.empty())
486 {
487 parser.help_and_exit("unexpected argument");
488 }
489 auto benchmarks = LazyVec(all_benchmarks(config));
490 if (enabled_benchmarks)
491 {
492 enabled_benchmarks->erase("");
493 enabled_benchmarks->erase("help");
494 enabled_benchmarks->erase("list");
495 if (enabled_benchmarks->empty())
496 {
497 if (config.use_json)
498 {
499 std::cout << JsonValue(benchmarks.map<JsonValue>(
500 [](const Benchmark &benchmark) {
501 return benchmark.name();
502 }))
503 .pretty(json_pretty)
504 << std::endl;
505 }
506 else
507 {
508 std::cout << "Available Benchmarks:\n";
509 for (auto &benchmark : benchmarks)
510 {
511 std::cout << benchmark.name() << "\n";
512 }
513 std::cout << std::endl;
514 }
515 return 0;
516 }
517 std::unordered_set<std::string> unknown_benchmarks =
518 *enabled_benchmarks;
519 for (auto &benchmark : benchmarks)
520 {
521 unknown_benchmarks.erase(benchmark.name());
522 }
523 if (!unknown_benchmarks.empty())
524 {
525 parser.help_and_exit(
526 "unknown benchmark: ", *unknown_benchmarks.begin(),
527 "\nrun with `--bench=list` to see all supported benchmarks.");
528 }
529 }
530 auto thread_cache = BenchHarnessBase::get_thread_cache();
531 auto benchmark_results =
532 benchmarks.filter_map<std::shared_ptr<BenchmarkResult>>(
533 [&](const Benchmark &benchmark)
534 -> std::optional<std::shared_ptr<BenchmarkResult>> {
535 if (enabled_benchmarks &&
536 !enabled_benchmarks->count(benchmark.name()))
537 {
538 return std::nullopt;
539 }
540 std::cout.flush();
541 return benchmark.run(config);
542 });
543 if (config.use_json)
544 {
545 std::cout << JsonValue(benchmark_results.map<JsonValue>(
546 [](const std::shared_ptr<BenchmarkResult>
547 &benchmark_results) -> JsonValue {
548 return *benchmark_results;
549 }))
550 .pretty(json_pretty);
551 }
552 else
553 {
554 for (const std::shared_ptr<BenchmarkResult> &benchmark_results :
555 benchmark_results)
556 {
557 benchmark_results->print();
558 }
559 }
560 std::cout << std::endl;
561 return 0;
562 }