1 #include "all_benchmarks.h"
7 #include <initializer_list>
13 #include <string_view>
14 #include <system_error>
15 #include <type_traits>
16 #include <unordered_set>
19 using namespace std::literals
;
21 enum class OptionValueKind
31 char short_name
= '\0';
32 std::string_view long_name
= "", description
= "";
33 bool required
= false;
34 bool all_other_args_not_required
= false;
35 OptionValueKind value_kind
= OptionValueKind::None
;
36 std::function
<void(OptionsParser
&parser
,
37 std::optional
<std::string_view
> value
)>
39 bool has_short_name() const
41 return short_name
!= '\0';
43 bool has_long_name() const
45 return !long_name
.empty();
47 friend std::ostream
&operator<<(std::ostream
&os
, const Option
&option
)
49 if (option
.has_long_name())
51 os
<< "--" << option
.long_name
;
53 else if (option
.has_short_name())
55 os
<< "-" << option
.short_name
;
67 friend class OptionsParser
;
70 std::vector
<Option
> options
;
71 std::map
<char, std::size_t> short_options
;
72 std::map
<std::string_view
, std::size_t> long_options
;
75 Options(std::initializer_list
<Option
> options_
)
76 : options(options_
), short_options(), long_options()
78 for (std::size_t i
= 0; i
< options
.size(); i
++)
80 auto &option
= options
[i
];
81 if (option
.has_short_name())
82 short_options
[option
.short_name
] = i
;
83 if (option
.has_long_name())
84 long_options
[option
.long_name
] = i
;
89 std::vector
<std::string_view
> parse(const char *const *argv
) const;
92 class OptionsParser final
95 const Options
&options
;
96 const char *const *argv
;
97 std::string_view argv0
;
98 std::vector
<bool> seen_options
;
99 bool check_required
= true;
100 std::optional
<std::size_t> current_option_index
;
103 const Option
¤t_option()
105 static const Option default_option
{};
106 if (current_option_index
)
108 return options
.options
[*current_option_index
];
112 return default_option
;
117 OptionsParser(const Options
&options
, const char *const *argv
)
118 : options(options
), argv(argv
+ 1),
119 argv0(argv
[0] ? argv
[0] : "<none>"),
120 seen_options(options
.options
.size(), false), current_option_index()
124 static const char *null
[] = {nullptr};
126 help_and_exit("missing program name");
131 void parse_option(std::size_t option_index
, std::string_view prefix
,
132 std::string_view name
,
133 std::optional
<std::string_view
> value
)
135 auto &option
= options
.options
[option_index
];
136 switch (option
.value_kind
)
138 case OptionValueKind::None
:
141 help_and_exit("value not allowed for ", prefix
, name
);
144 case OptionValueKind::Required
:
153 help_and_exit("missing value for ", prefix
, name
);
158 seen_options
[option_index
] = true;
159 if (option
.all_other_args_not_required
)
160 check_required
= false;
161 current_option_index
= option_index
;
162 option
.parse_value(*this, value
);
163 current_option_index
= std::nullopt
;
167 std::vector
<std::string_view
> parse()
169 std::vector
<std::string_view
> retval
;
170 constexpr auto npos
= std::string_view::npos
;
173 std::string_view arg
= *argv
++;
174 static constexpr std::string_view long_prefix
= "--"sv
;
175 static constexpr std::string_view short_prefix
= "-"sv
;
176 if (arg
.rfind(long_prefix
, 0) == 0) // it starts with `--`
178 arg
.remove_prefix(long_prefix
.size());
179 auto eq
= arg
.find('=');
184 if (arg
.empty()) // just `--`
188 retval
.push_back(*argv
++);
192 auto name
= arg
.substr(0, eq
);
193 std::optional
<std::string_view
> value
;
196 value
= arg
.substr(eq
+ 1);
198 auto iter
= options
.long_options
.find(name
);
199 if (iter
== options
.long_options
.end())
201 help_and_exit("unknown option: ", long_prefix
, name
);
203 auto option_index
= iter
->second
;
204 parse_option(option_index
, long_prefix
, name
, value
);
207 else if (arg
.rfind(short_prefix
, 0) == 0) // it starts with `-`
209 arg
.remove_prefix(short_prefix
.size());
210 if (arg
.empty()) // just `-`
212 retval
.push_back(short_prefix
);
217 auto name
= arg
.substr(0, 1);
218 arg
.remove_prefix(1);
219 auto iter
= options
.short_options
.find(name
[0]);
220 if (iter
== options
.short_options
.end())
222 help_and_exit("unknown option: ", short_prefix
, name
);
224 auto option_index
= iter
->second
;
225 std::optional
<std::string_view
> value
;
226 switch (options
.options
[option_index
].value_kind
)
228 case OptionValueKind::None
:
230 case OptionValueKind::Required
:
231 auto eq
= arg
.rfind('=', 0);
234 value
= arg
.substr(eq
+ 1);
236 else if (!arg
.empty())
243 parse_option(option_index
, short_prefix
, name
, value
);
249 retval
.push_back(arg
);
254 for (std::size_t i
= 0; i
< options
.options
.size(); i
++)
256 auto &option
= options
.options
[i
];
257 if (option
.required
&& !seen_options
[i
])
259 help_and_exit("missing required option ", option
);
265 template <typename
... Options
>
266 [[noreturn
]] void help_and_exit(const Options
&...error_msg
) const
268 auto &os
= sizeof...(error_msg
) == 0 ? std::cout
: std::cerr
;
269 if (sizeof...(error_msg
) != 0)
271 ((os
<< "Error: ") << ... << error_msg
) << std::endl
;
273 os
<< "Usage: " << argv0
;
274 for (auto &option
: options
.options
)
277 if (!option
.required
)
282 if (option
.has_short_name())
284 os
<< "-" << option
.short_name
;
287 if (option
.has_long_name())
289 os
<< sep
<< "--" << option
.long_name
;
291 switch (option
.value_kind
)
293 case OptionValueKind::None
:
295 // TODO: case OptionValueKind::Optional:
296 case OptionValueKind::Required
:
300 if (!option
.required
)
305 os
<< std::endl
<< "Options:" << std::endl
;
306 for (auto &option
: options
.options
)
309 if (option
.has_short_name())
311 os
<< "-" << option
.short_name
;
314 if (option
.has_long_name())
316 os
<< sep
<< "--" << option
.long_name
;
318 os
<< " " << option
.description
<< std::endl
;
320 std::exit(sizeof...(error_msg
) == 0 ? 0 : 1);
322 template <typename Int
, typename
= void> struct ParseIntLimits
;
323 template <typename Int
>
324 struct ParseIntLimits
<Int
, std::enable_if_t
<std::is_integral_v
<Int
>>> final
326 Int min_value
= std::numeric_limits
<Int
>::min();
327 Int max_value
= std::numeric_limits
<Int
>::max();
329 template <typename Int
>
330 std::enable_if_t
<std::is_integral_v
<Int
>, void> parse_int(
331 std::optional
<std::string_view
> value
, Int
&i_value
,
332 ParseIntLimits
<Int
> limits
= {})
337 help_and_exit("missing value for ", current_option());
341 if (0 == str
.rfind("0x", 0) || 0 == str
.rfind("0X", 0))
344 str
.remove_prefix(2);
346 else if (0 == str
.rfind("0o", 0) || 0 == str
.rfind("0O", 0))
349 str
.remove_prefix(2);
351 else if (0 == str
.rfind("0b", 0) || 0 == str
.rfind("0B", 0))
354 str
.remove_prefix(2);
356 std::from_chars_result result
= std::from_chars(
357 str
.data(), str
.data() + str
.size(), i_value
, base
);
358 if (result
.ptr
!= str
.data() + str
.size())
360 result
.ec
= std::errc::invalid_argument
;
362 if (result
.ec
== std::errc())
364 if (i_value
< limits
.min_value
|| i_value
> limits
.max_value
)
366 result
.ec
= std::errc::result_out_of_range
;
369 if (result
.ec
== std::errc::result_out_of_range
)
371 help_and_exit("value out of range: ", current_option(), "=",
374 else if (result
.ec
!= std::errc())
376 help_and_exit("invalid value for: ", current_option());
379 template <typename Int
>
380 std::enable_if_t
<std::is_integral_v
<Int
>, void> parse_int(
381 std::optional
<std::string_view
> value
, std::optional
<Int
> &i_value
,
382 bool required
= true, ParseIntLimits
<Int
> limits
= {})
384 if (!required
&& !value
)
386 i_value
= std::nullopt
;
390 this->parse_int(value
, i_value
.value(), limits
);
394 inline std::vector
<std::string_view
> Options::parse(
395 const char *const *argv
) const
397 return OptionsParser(*this, argv
).parse();
400 int main(int, char **argv
)
403 std::optional
<std::unordered_set
<std::string
>> enabled_benchmarks
;
408 .description
= "Display usage and exit.",
409 .all_other_args_not_required
= true,
410 .parse_value
= [](OptionsParser
&parser
,
411 auto) { parser
.help_and_exit(); },
413 Option
{.short_name
= 'j',
414 .long_name
= "thread-count",
415 .description
= "Number of threads to run on",
416 .value_kind
= OptionValueKind::Required
,
418 [&](OptionsParser
&parser
, auto value
) {
419 parser
.parse_int(value
, config
.thread_count
);
421 Option
{.short_name
= 'n',
422 .long_name
= "iter-count",
423 .description
= "Number of iterations to run per thread",
424 .value_kind
= OptionValueKind::Required
,
426 [&](OptionsParser
&parser
, auto value
) {
427 parser
.parse_int(value
, config
.iteration_count
);
429 Option
{.long_name
= "log2-mem-loc-count",
431 "Log base 2 of the number of memory locations to access",
432 .value_kind
= OptionValueKind::Required
,
434 [&](OptionsParser
&parser
, auto value
) {
436 value
, config
.log2_memory_location_count
,
438 Config::max_sum_log2_mem_loc_count_and_stride
-
439 config
.log2_stride
});
442 .long_name
= "log2-stride",
444 "Log base 2 of the stride used for accessing memory locations",
445 .value_kind
= OptionValueKind::Required
,
447 [&](OptionsParser
&parser
, auto value
) {
449 value
, config
.log2_stride
,
451 Config::max_sum_log2_mem_loc_count_and_stride
-
452 config
.log2_memory_location_count
});
456 .long_name
= "bench",
457 .description
= "List of benchmarks that should be run",
458 .value_kind
= OptionValueKind::Required
,
460 [&](OptionsParser
&, std::optional
<std::string_view
> value
) {
461 if (!enabled_benchmarks
)
463 enabled_benchmarks
.emplace();
465 enabled_benchmarks
->emplace(value
.value_or(""));
467 Option
{.long_name
= "json",
468 .description
= "Write the output in JSON format",
469 .value_kind
= OptionValueKind::None
,
470 .parse_value
= [&](auto &, auto) { config
.use_json
= true; }},
472 OptionsParser
parser(options
, argv
);
473 auto args
= parser
.parse();
476 parser
.help_and_exit("unexpected argument");
478 auto benchmarks
= all_benchmarks(config
);
479 if (enabled_benchmarks
)
481 enabled_benchmarks
->erase("");
482 enabled_benchmarks
->erase("help");
483 enabled_benchmarks
->erase("list");
484 if (enabled_benchmarks
->empty())
489 for (auto &benchmark
: benchmarks
)
491 names
.push_back(benchmark
.name());
493 std::cout
<< JsonValue(std::move(names
)) << std::endl
;
497 std::cout
<< "Available Benchmarks:\n";
498 for (auto &benchmark
: benchmarks
)
500 std::cout
<< benchmark
.name() << "\n";
502 std::cout
<< std::endl
;
506 std::unordered_set
<std::string
> unknown_benchmarks
=
508 for (auto &benchmark
: benchmarks
)
510 unknown_benchmarks
.erase(benchmark
.name());
512 if (!unknown_benchmarks
.empty())
514 parser
.help_and_exit(
515 "unknown benchmark: ", *unknown_benchmarks
.begin(),
516 "\nrun with `--bench=list` to see all supported benchmarks.");
519 auto thread_cache
= BenchHarnessBase::get_thread_cache();
520 for (auto &benchmark
: benchmarks
)
522 // FIXME: change output to use JSON when selected
523 if (enabled_benchmarks
&& !enabled_benchmarks
->count(benchmark
.name()))
527 std::cout
<< "Running: " << benchmark
.name() << std::endl
;
528 benchmark
.run(config
);
530 std::cout
<< std::endl
;