1 #include "all_benchmarks.h"
8 #include <initializer_list>
15 #include <string_view>
16 #include <system_error>
17 #include <type_traits>
18 #include <unordered_set>
21 using namespace std::literals
;
23 enum class OptionValueKind
33 char short_name
= '\0';
34 std::string_view long_name
= "", description
= "";
35 bool required
= false;
36 bool all_other_args_not_required
= false;
37 OptionValueKind value_kind
= OptionValueKind::None
;
38 std::function
<void(OptionsParser
&parser
,
39 std::optional
<std::string_view
> value
)>
41 bool has_short_name() const
43 return short_name
!= '\0';
45 bool has_long_name() const
47 return !long_name
.empty();
49 friend std::ostream
&operator<<(std::ostream
&os
, const Option
&option
)
51 if (option
.has_long_name())
53 os
<< "--" << option
.long_name
;
55 else if (option
.has_short_name())
57 os
<< "-" << option
.short_name
;
69 friend class OptionsParser
;
72 std::vector
<Option
> options
;
73 std::map
<char, std::size_t> short_options
;
74 std::map
<std::string_view
, std::size_t> long_options
;
77 Options(std::initializer_list
<Option
> options_
)
78 : options(options_
), short_options(), long_options()
80 for (std::size_t i
= 0; i
< options
.size(); i
++)
82 auto &option
= options
[i
];
83 if (option
.has_short_name())
84 short_options
[option
.short_name
] = i
;
85 if (option
.has_long_name())
86 long_options
[option
.long_name
] = i
;
91 std::vector
<std::string_view
> parse(const char *const *argv
) const;
94 class OptionsParser final
97 const Options
&options
;
98 const char *const *argv
;
99 std::string_view argv0
;
100 std::vector
<bool> seen_options
;
101 bool check_required
= true;
102 std::optional
<std::size_t> current_option_index
;
105 const Option
¤t_option()
107 static const Option default_option
{};
108 if (current_option_index
)
110 return options
.options
[*current_option_index
];
114 return default_option
;
119 OptionsParser(const Options
&options
, const char *const *argv
)
120 : options(options
), argv(argv
+ 1),
121 argv0(argv
[0] ? argv
[0] : "<none>"),
122 seen_options(options
.options
.size(), false), current_option_index()
126 static const char *null
[] = {nullptr};
128 help_and_exit("missing program name");
133 void parse_option(std::size_t option_index
, std::string_view prefix
,
134 std::string_view name
,
135 std::optional
<std::string_view
> value
)
137 auto &option
= options
.options
[option_index
];
138 switch (option
.value_kind
)
140 case OptionValueKind::None
:
143 help_and_exit("value not allowed for ", prefix
, name
);
146 case OptionValueKind::Required
:
155 help_and_exit("missing value for ", prefix
, name
);
160 seen_options
[option_index
] = true;
161 if (option
.all_other_args_not_required
)
162 check_required
= false;
163 current_option_index
= option_index
;
164 option
.parse_value(*this, value
);
165 current_option_index
= std::nullopt
;
169 std::vector
<std::string_view
> parse()
171 std::vector
<std::string_view
> retval
;
172 constexpr auto npos
= std::string_view::npos
;
175 std::string_view arg
= *argv
++;
176 static constexpr std::string_view long_prefix
= "--"sv
;
177 static constexpr std::string_view short_prefix
= "-"sv
;
178 if (arg
.rfind(long_prefix
, 0) == 0) // it starts with `--`
180 arg
.remove_prefix(long_prefix
.size());
181 auto eq
= arg
.find('=');
186 if (arg
.empty()) // just `--`
190 retval
.push_back(*argv
++);
194 auto name
= arg
.substr(0, eq
);
195 std::optional
<std::string_view
> value
;
198 value
= arg
.substr(eq
+ 1);
200 auto iter
= options
.long_options
.find(name
);
201 if (iter
== options
.long_options
.end())
203 help_and_exit("unknown option: ", long_prefix
, name
);
205 auto option_index
= iter
->second
;
206 parse_option(option_index
, long_prefix
, name
, value
);
209 else if (arg
.rfind(short_prefix
, 0) == 0) // it starts with `-`
211 arg
.remove_prefix(short_prefix
.size());
212 if (arg
.empty()) // just `-`
214 retval
.push_back(short_prefix
);
219 auto name
= arg
.substr(0, 1);
220 arg
.remove_prefix(1);
221 auto iter
= options
.short_options
.find(name
[0]);
222 if (iter
== options
.short_options
.end())
224 help_and_exit("unknown option: ", short_prefix
, name
);
226 auto option_index
= iter
->second
;
227 std::optional
<std::string_view
> value
;
228 switch (options
.options
[option_index
].value_kind
)
230 case OptionValueKind::None
:
232 case OptionValueKind::Required
:
233 auto eq
= arg
.rfind('=', 0);
236 value
= arg
.substr(eq
+ 1);
238 else if (!arg
.empty())
245 parse_option(option_index
, short_prefix
, name
, value
);
251 retval
.push_back(arg
);
256 for (std::size_t i
= 0; i
< options
.options
.size(); i
++)
258 auto &option
= options
.options
[i
];
259 if (option
.required
&& !seen_options
[i
])
261 help_and_exit("missing required option ", option
);
267 template <typename
... Options
>
268 [[noreturn
]] void help_and_exit(const Options
&...error_msg
) const
270 auto &os
= sizeof...(error_msg
) == 0 ? std::cout
: std::cerr
;
271 if (sizeof...(error_msg
) != 0)
273 ((os
<< "Error: ") << ... << error_msg
) << std::endl
;
275 os
<< "Usage: " << argv0
;
276 for (auto &option
: options
.options
)
279 if (!option
.required
)
284 if (option
.has_short_name())
286 os
<< "-" << option
.short_name
;
289 if (option
.has_long_name())
291 os
<< sep
<< "--" << option
.long_name
;
293 switch (option
.value_kind
)
295 case OptionValueKind::None
:
297 // TODO: case OptionValueKind::Optional:
298 case OptionValueKind::Required
:
302 if (!option
.required
)
307 os
<< std::endl
<< "Options:" << std::endl
;
308 for (auto &option
: options
.options
)
311 if (option
.has_short_name())
313 os
<< "-" << option
.short_name
;
316 if (option
.has_long_name())
318 os
<< sep
<< "--" << option
.long_name
;
320 os
<< " " << option
.description
<< std::endl
;
322 std::exit(sizeof...(error_msg
) == 0 ? 0 : 1);
324 template <typename Int
, typename
= void> struct ParseIntLimits
;
325 template <typename Int
>
326 struct ParseIntLimits
<Int
, std::enable_if_t
<std::is_integral_v
<Int
>>> final
328 Int min_value
= std::numeric_limits
<Int
>::min();
329 Int max_value
= std::numeric_limits
<Int
>::max();
331 template <typename Int
>
332 std::enable_if_t
<std::is_integral_v
<Int
>, void> parse_int(
333 std::optional
<std::string_view
> value
, Int
&i_value
,
334 ParseIntLimits
<Int
> limits
= {})
339 help_and_exit("missing value for ", current_option());
343 if (0 == str
.rfind("0x", 0) || 0 == str
.rfind("0X", 0))
346 str
.remove_prefix(2);
348 else if (0 == str
.rfind("0o", 0) || 0 == str
.rfind("0O", 0))
351 str
.remove_prefix(2);
353 else if (0 == str
.rfind("0b", 0) || 0 == str
.rfind("0B", 0))
356 str
.remove_prefix(2);
358 std::from_chars_result result
= std::from_chars(
359 str
.data(), str
.data() + str
.size(), i_value
, base
);
360 if (result
.ptr
!= str
.data() + str
.size())
362 result
.ec
= std::errc::invalid_argument
;
364 if (result
.ec
== std::errc())
366 if (i_value
< limits
.min_value
|| i_value
> limits
.max_value
)
368 result
.ec
= std::errc::result_out_of_range
;
371 if (result
.ec
== std::errc::result_out_of_range
)
373 help_and_exit("value out of range: ", current_option(), "=",
376 else if (result
.ec
!= std::errc())
378 help_and_exit("invalid value for: ", current_option());
381 template <typename Int
>
382 std::enable_if_t
<std::is_integral_v
<Int
>, void> parse_int(
383 std::optional
<std::string_view
> value
, std::optional
<Int
> &i_value
,
384 bool required
= true, ParseIntLimits
<Int
> limits
= {})
386 if (!required
&& !value
)
388 i_value
= std::nullopt
;
392 this->parse_int(value
, i_value
.value(), limits
);
394 template <typename Float
>
395 std::enable_if_t
<std::is_floating_point_v
<Float
>, void> parse_float(
396 std::optional
<std::string_view
> value
, Float
&f_value
)
401 help_and_exit("missing value for ", current_option());
404 std::istringstream is
{std::string(str
)};
405 if (!(is
>> f_value
))
407 help_and_exit("invalid value for: ", current_option());
410 template <typename Float
>
411 std::enable_if_t
<std::is_floating_point_v
<Float
>, void> parse_float(
412 std::optional
<std::string_view
> value
, std::optional
<Float
> &f_value
,
413 bool required
= true)
415 if (!required
&& !value
)
417 f_value
= std::nullopt
;
421 this->parse_float(value
, f_value
.value());
425 inline std::vector
<std::string_view
> Options::parse(
426 const char *const *argv
) const
428 return OptionsParser(*this, argv
).parse();
431 int main(int, char **argv
)
434 std::optional
<std::unordered_set
<std::string
>> enabled_benchmarks
;
435 bool json_pretty
= false;
440 .description
= "Display usage and exit.",
441 .all_other_args_not_required
= true,
442 .parse_value
= [](OptionsParser
&parser
,
443 auto) { parser
.help_and_exit(); },
445 Option
{.short_name
= 'j',
446 .long_name
= "thread-count",
447 .description
= "Number of threads to run on",
448 .value_kind
= OptionValueKind::Required
,
450 [&](OptionsParser
&parser
, auto value
) {
451 parser
.parse_int(value
, config
.thread_count
);
453 Option
{.short_name
= 'n',
454 .long_name
= "iter-count",
455 .description
= "Number of iterations to run per thread",
456 .value_kind
= OptionValueKind::Required
,
458 [&](OptionsParser
&parser
, auto value
) {
459 parser
.parse_int(value
, config
.iteration_count
, true,
462 Option
{.long_name
= "log2-mem-loc-count",
464 "Log base 2 of the number of memory locations to access",
465 .value_kind
= OptionValueKind::Required
,
467 [&](OptionsParser
&parser
, auto value
) {
469 value
, config
.log2_memory_location_count
,
471 Config::max_sum_log2_mem_loc_count_and_stride
-
472 config
.log2_stride
});
475 .long_name
= "log2-stride",
477 "Log base 2 of the stride used for accessing memory locations",
478 .value_kind
= OptionValueKind::Required
,
480 [&](OptionsParser
&parser
, auto value
) {
482 value
, config
.log2_stride
,
484 Config::max_sum_log2_mem_loc_count_and_stride
-
485 config
.log2_memory_location_count
});
489 .long_name
= "bench",
490 .description
= "List of benchmarks that should be run",
491 .value_kind
= OptionValueKind::Required
,
493 [&](OptionsParser
&, std::optional
<std::string_view
> value
) {
494 if (!enabled_benchmarks
)
496 enabled_benchmarks
.emplace();
498 enabled_benchmarks
->emplace(value
.value_or(""));
500 Option
{.long_name
= "json",
501 .description
= "Write the output in JSON format",
502 .value_kind
= OptionValueKind::None
,
503 .parse_value
= [&](auto &, auto) { config
.use_json
= true; }},
504 Option
{.long_name
= "json-pretty",
505 .description
= "Write the output in pretty JSON format",
506 .value_kind
= OptionValueKind::None
,
509 config
.use_json
= true;
512 Option
{.short_name
= 'd',
513 .long_name
= "target-duration",
515 "target duration for a single benchmark in seconds",
516 .value_kind
= OptionValueKind::Required
,
518 [&](OptionsParser
&parser
, auto value
) {
519 parser
.parse_float(value
, config
.target_duration
);
520 if (config
.target_duration
&&
521 (!std::isfinite(*config
.target_duration
) ||
522 *config
.target_duration
< 0))
524 parser
.help_and_exit(
525 "value out of range: target-duration=",
526 *config
.target_duration
);
530 OptionsParser
parser(options
, argv
);
531 auto args
= parser
.parse();
534 parser
.help_and_exit("unexpected argument");
536 auto benchmarks
= LazyVec(all_benchmarks(config
));
537 if (enabled_benchmarks
)
539 enabled_benchmarks
->erase("");
540 enabled_benchmarks
->erase("help");
541 enabled_benchmarks
->erase("list");
542 if (enabled_benchmarks
->empty())
546 std::cout
<< JsonValue(benchmarks
.map
<JsonValue
>(
547 [](const Benchmark
&benchmark
) {
548 return benchmark
.name();
555 std::cout
<< "Available Benchmarks:\n";
556 for (auto &benchmark
: benchmarks
)
558 std::cout
<< benchmark
.name() << "\n";
560 std::cout
<< std::endl
;
564 std::unordered_set
<std::string
> unknown_benchmarks
=
566 for (auto &benchmark
: benchmarks
)
568 unknown_benchmarks
.erase(benchmark
.name());
570 if (!unknown_benchmarks
.empty())
572 parser
.help_and_exit(
573 "unknown benchmark: ", *unknown_benchmarks
.begin(),
574 "\nrun with `--bench=list` to see all supported benchmarks.");
577 auto thread_cache
= BenchHarnessBase::get_thread_cache();
578 auto benchmark_results
=
579 benchmarks
.filter_map
<std::shared_ptr
<BenchmarkResult
>>(
580 [&](const Benchmark
&benchmark
)
581 -> std::optional
<std::shared_ptr
<BenchmarkResult
>> {
582 if (enabled_benchmarks
&&
583 !enabled_benchmarks
->count(benchmark
.name()))
588 return benchmark
.run(config
);
592 std::cout
<< JsonValue(benchmark_results
.map
<JsonValue
>(
593 [](const std::shared_ptr
<BenchmarkResult
>
594 &benchmark_results
) -> JsonValue
{
595 return *benchmark_results
;
597 .pretty(json_pretty
);
601 for (const std::shared_ptr
<BenchmarkResult
> &benchmark_results
:
604 benchmark_results
->print();
607 std::cout
<< std::endl
;