1 #include "all_benchmarks.h"
8 #include <initializer_list>
14 #include <string_view>
15 #include <system_error>
16 #include <type_traits>
17 #include <unordered_set>
20 using namespace std::literals
;
22 enum class OptionValueKind
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
)>
40 bool has_short_name() const
42 return short_name
!= '\0';
44 bool has_long_name() const
46 return !long_name
.empty();
48 friend std::ostream
&operator<<(std::ostream
&os
, const Option
&option
)
50 if (option
.has_long_name())
52 os
<< "--" << option
.long_name
;
54 else if (option
.has_short_name())
56 os
<< "-" << option
.short_name
;
68 friend class OptionsParser
;
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
;
76 Options(std::initializer_list
<Option
> options_
)
77 : options(options_
), short_options(), long_options()
79 for (std::size_t i
= 0; i
< options
.size(); i
++)
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
;
90 std::vector
<std::string_view
> parse(const char *const *argv
) const;
93 class OptionsParser final
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
;
104 const Option
¤t_option()
106 static const Option default_option
{};
107 if (current_option_index
)
109 return options
.options
[*current_option_index
];
113 return default_option
;
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()
125 static const char *null
[] = {nullptr};
127 help_and_exit("missing program name");
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
)
136 auto &option
= options
.options
[option_index
];
137 switch (option
.value_kind
)
139 case OptionValueKind::None
:
142 help_and_exit("value not allowed for ", prefix
, name
);
145 case OptionValueKind::Required
:
154 help_and_exit("missing value for ", prefix
, name
);
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
;
168 std::vector
<std::string_view
> parse()
170 std::vector
<std::string_view
> retval
;
171 constexpr auto npos
= std::string_view::npos
;
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 `--`
179 arg
.remove_prefix(long_prefix
.size());
180 auto eq
= arg
.find('=');
185 if (arg
.empty()) // just `--`
189 retval
.push_back(*argv
++);
193 auto name
= arg
.substr(0, eq
);
194 std::optional
<std::string_view
> value
;
197 value
= arg
.substr(eq
+ 1);
199 auto iter
= options
.long_options
.find(name
);
200 if (iter
== options
.long_options
.end())
202 help_and_exit("unknown option: ", long_prefix
, name
);
204 auto option_index
= iter
->second
;
205 parse_option(option_index
, long_prefix
, name
, value
);
208 else if (arg
.rfind(short_prefix
, 0) == 0) // it starts with `-`
210 arg
.remove_prefix(short_prefix
.size());
211 if (arg
.empty()) // just `-`
213 retval
.push_back(short_prefix
);
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())
223 help_and_exit("unknown option: ", short_prefix
, name
);
225 auto option_index
= iter
->second
;
226 std::optional
<std::string_view
> value
;
227 switch (options
.options
[option_index
].value_kind
)
229 case OptionValueKind::None
:
231 case OptionValueKind::Required
:
232 auto eq
= arg
.rfind('=', 0);
235 value
= arg
.substr(eq
+ 1);
237 else if (!arg
.empty())
244 parse_option(option_index
, short_prefix
, name
, value
);
250 retval
.push_back(arg
);
255 for (std::size_t i
= 0; i
< options
.options
.size(); i
++)
257 auto &option
= options
.options
[i
];
258 if (option
.required
&& !seen_options
[i
])
260 help_and_exit("missing required option ", option
);
266 template <typename
... Options
>
267 [[noreturn
]] void help_and_exit(const Options
&...error_msg
) const
269 auto &os
= sizeof...(error_msg
) == 0 ? std::cout
: std::cerr
;
270 if (sizeof...(error_msg
) != 0)
272 ((os
<< "Error: ") << ... << error_msg
) << std::endl
;
274 os
<< "Usage: " << argv0
;
275 for (auto &option
: options
.options
)
278 if (!option
.required
)
283 if (option
.has_short_name())
285 os
<< "-" << option
.short_name
;
288 if (option
.has_long_name())
290 os
<< sep
<< "--" << option
.long_name
;
292 switch (option
.value_kind
)
294 case OptionValueKind::None
:
296 // TODO: case OptionValueKind::Optional:
297 case OptionValueKind::Required
:
301 if (!option
.required
)
306 os
<< std::endl
<< "Options:" << std::endl
;
307 for (auto &option
: options
.options
)
310 if (option
.has_short_name())
312 os
<< "-" << option
.short_name
;
315 if (option
.has_long_name())
317 os
<< sep
<< "--" << option
.long_name
;
319 os
<< " " << option
.description
<< std::endl
;
321 std::exit(sizeof...(error_msg
) == 0 ? 0 : 1);
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
327 Int min_value
= std::numeric_limits
<Int
>::min();
328 Int max_value
= std::numeric_limits
<Int
>::max();
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
= {})
338 help_and_exit("missing value for ", current_option());
342 if (0 == str
.rfind("0x", 0) || 0 == str
.rfind("0X", 0))
345 str
.remove_prefix(2);
347 else if (0 == str
.rfind("0o", 0) || 0 == str
.rfind("0O", 0))
350 str
.remove_prefix(2);
352 else if (0 == str
.rfind("0b", 0) || 0 == str
.rfind("0B", 0))
355 str
.remove_prefix(2);
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())
361 result
.ec
= std::errc::invalid_argument
;
363 if (result
.ec
== std::errc())
365 if (i_value
< limits
.min_value
|| i_value
> limits
.max_value
)
367 result
.ec
= std::errc::result_out_of_range
;
370 if (result
.ec
== std::errc::result_out_of_range
)
372 help_and_exit("value out of range: ", current_option(), "=",
375 else if (result
.ec
!= std::errc())
377 help_and_exit("invalid value for: ", current_option());
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
= {})
385 if (!required
&& !value
)
387 i_value
= std::nullopt
;
391 this->parse_int(value
, i_value
.value(), limits
);
395 inline std::vector
<std::string_view
> Options::parse(
396 const char *const *argv
) const
398 return OptionsParser(*this, argv
).parse();
401 int main(int, char **argv
)
404 std::optional
<std::unordered_set
<std::string
>> enabled_benchmarks
;
405 bool json_pretty
= false;
410 .description
= "Display usage and exit.",
411 .all_other_args_not_required
= true,
412 .parse_value
= [](OptionsParser
&parser
,
413 auto) { parser
.help_and_exit(); },
415 Option
{.short_name
= 'j',
416 .long_name
= "thread-count",
417 .description
= "Number of threads to run on",
418 .value_kind
= OptionValueKind::Required
,
420 [&](OptionsParser
&parser
, auto value
) {
421 parser
.parse_int(value
, config
.thread_count
);
423 Option
{.short_name
= 'n',
424 .long_name
= "iter-count",
425 .description
= "Number of iterations to run per thread",
426 .value_kind
= OptionValueKind::Required
,
428 [&](OptionsParser
&parser
, auto value
) {
429 parser
.parse_int(value
, config
.iteration_count
, true,
432 Option
{.long_name
= "log2-mem-loc-count",
434 "Log base 2 of the number of memory locations to access",
435 .value_kind
= OptionValueKind::Required
,
437 [&](OptionsParser
&parser
, auto value
) {
439 value
, config
.log2_memory_location_count
,
441 Config::max_sum_log2_mem_loc_count_and_stride
-
442 config
.log2_stride
});
445 .long_name
= "log2-stride",
447 "Log base 2 of the stride used for accessing memory locations",
448 .value_kind
= OptionValueKind::Required
,
450 [&](OptionsParser
&parser
, auto value
) {
452 value
, config
.log2_stride
,
454 Config::max_sum_log2_mem_loc_count_and_stride
-
455 config
.log2_memory_location_count
});
459 .long_name
= "bench",
460 .description
= "List of benchmarks that should be run",
461 .value_kind
= OptionValueKind::Required
,
463 [&](OptionsParser
&, std::optional
<std::string_view
> value
) {
464 if (!enabled_benchmarks
)
466 enabled_benchmarks
.emplace();
468 enabled_benchmarks
->emplace(value
.value_or(""));
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
,
479 config
.use_json
= true;
483 OptionsParser
parser(options
, argv
);
484 auto args
= parser
.parse();
487 parser
.help_and_exit("unexpected argument");
489 auto benchmarks
= LazyVec(all_benchmarks(config
));
490 if (enabled_benchmarks
)
492 enabled_benchmarks
->erase("");
493 enabled_benchmarks
->erase("help");
494 enabled_benchmarks
->erase("list");
495 if (enabled_benchmarks
->empty())
499 std::cout
<< JsonValue(benchmarks
.map
<JsonValue
>(
500 [](const Benchmark
&benchmark
) {
501 return benchmark
.name();
508 std::cout
<< "Available Benchmarks:\n";
509 for (auto &benchmark
: benchmarks
)
511 std::cout
<< benchmark
.name() << "\n";
513 std::cout
<< std::endl
;
517 std::unordered_set
<std::string
> unknown_benchmarks
=
519 for (auto &benchmark
: benchmarks
)
521 unknown_benchmarks
.erase(benchmark
.name());
523 if (!unknown_benchmarks
.empty())
525 parser
.help_and_exit(
526 "unknown benchmark: ", *unknown_benchmarks
.begin(),
527 "\nrun with `--bench=list` to see all supported benchmarks.");
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()))
541 return benchmark
.run(config
);
545 std::cout
<< JsonValue(benchmark_results
.map
<JsonValue
>(
546 [](const std::shared_ptr
<BenchmarkResult
>
547 &benchmark_results
) -> JsonValue
{
548 return *benchmark_results
;
550 .pretty(json_pretty
);
554 for (const std::shared_ptr
<BenchmarkResult
> &benchmark_results
:
557 benchmark_results
->print();
560 std::cout
<< std::endl
;