Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ Examples are built by default into `build/bin` and are prefixed with `nvbench.ex
<summary>Example output from `nvbench.example.throughput`</summary>

```
# Command Line

./bin/nvbench.example.throughput

# Devices

## [0] `Quadro GV100`
Expand Down
8 changes: 8 additions & 0 deletions nvbench/main.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <exception>
#include <iostream>
#include <string>
#include <utility>
#include <vector>

// Advanced users can rebuild NVBench's `main` function using the macros in this file, or replace
Expand All @@ -57,12 +58,16 @@
// Customization point, called before NVBench parsing. Update argc/argv if needed.
// argc/argv are the usual command line arguments types. The ARGS version of this
// macro is a bit more convenient.
// NVBench captures the command line before this handler runs. Changes made here
// do not alter the reported command line.
#ifndef NVBENCH_MAIN_CUSTOM_ARGC_ARGV_HANDLER
#define NVBENCH_MAIN_CUSTOM_ARGC_ARGV_HANDLER(argc, argv) []() {}()
#endif

// Customization point, called before NVBench parsing. Update args if needed.
// Args is a vector of strings, each element is an argument.
// NVBench captures the command line before this handler runs. Changes made here
// do not alter the reported command line.
#ifndef NVBENCH_MAIN_CUSTOM_ARGS_HANDLER
#define NVBENCH_MAIN_CUSTOM_ARGS_HANDLER(args) []() {}()
#endif
Expand Down Expand Up @@ -132,10 +137,12 @@

#ifndef NVBENCH_MAIN_PARSE
#define NVBENCH_MAIN_PARSE(argc, argv) \
std::vector<std::string> raw_args = nvbench::detail::main_convert_args(argc, argv); \
NVBENCH_MAIN_CUSTOM_ARGC_ARGV_HANDLER(argc, argv); \
std::vector<std::string> args = nvbench::detail::main_convert_args(argc, argv); \
NVBENCH_MAIN_CUSTOM_ARGS_HANDLER(args); \
nvbench::option_parser parser; \
parser.set_raw_args(std::move(raw_args)); \
NVBENCH_MAIN_PARSE_CUSTOM_PRE(parser, args); \
parser.parse(args); \
NVBENCH_MAIN_PARSE_CUSTOM_POST(parser)
Expand Down Expand Up @@ -209,6 +216,7 @@ inline void main_print_preamble(option_parser &parser)
{
auto &printer = parser.get_printer();

printer.print_argv();
Comment thread
Jacobfaib marked this conversation as resolved.
printer.print_device_info();
printer.print_log_preamble();
}
Expand Down
105 changes: 105 additions & 0 deletions nvbench/markdown_printer.cu
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,111 @@
namespace nvbench
{

namespace
{

// Quote an argument for the shell of the current platform, so that the printed
// command line can be copied and pasted.
#ifdef _WIN32

// The Windows command processor (cmd.exe) does not group text inside single
// quotes, so use double quotes with backslash escapes.
std::string shell_quote(const std::string &arg)
{
if (!arg.empty() && arg.find_first_of(" \t\n\v\"^&|<>()%!") == std::string::npos)
{
return arg;
}

// Follow the rules of CommandLineToArgvW: a run of backslashes is only special
// when a double quote comes after it.
std::string result;

result.reserve((4 * arg.size()) + 2);
result += '\'';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
result += '\'';
result += '"';

Windows quoted line should start with " to match closing double quote on line 92.

for (auto iter = arg.begin(); iter != arg.end(); ++iter)
{
std::size_t num_backslashes = 0;
while (iter != arg.end() && *iter == '\\')
{
++num_backslashes;
++iter;
}

if (iter == arg.end())
{ // Double the backslashes that come before the closing quote.
result.append(num_backslashes * 2, '\\');
break;
}

if (*iter == '"')
{ // Double the backslashes that come before a quote, then escape the quote.
result.append(num_backslashes * 2, '\\');
result += "\\\"";
}
else
{
result.append(num_backslashes, '\\');
result += *iter;
}
}
result += '"';
return result;
}

#else

// POSIX shells (sh, bash, zsh) take single quotes.
std::string shell_quote(const std::string &arg)
{
if (!arg.empty() && arg.find_first_of(" \t\n\"'\\$`|&;<>()*?[]{}#~!") == std::string::npos)
{
return arg;
}

std::string result;

result.reserve((4 * arg.size()) + 2);
result += '\'';
for (const char c : arg)
{
if (c == '\'')
{ // A single quote cannot appear inside single quotes; close, escape, reopen.
result += "'\\''";
}
else
{
result += c;
}
}
result += '\'';
return result;
}

#endif // _WIN32

} // namespace

void markdown_printer::do_log_argv(const std::vector<std::string> &argv) { m_argv = argv; }

void markdown_printer::do_print_argv()
{
if (m_argv.empty())
{
return;
}

fmt::memory_buffer buffer;
fmt::format_to(fmt::appender(buffer), "# Command Line\n\n```\n");
for (std::size_t i = 0; i < m_argv.size(); ++i)
{
fmt::format_to(fmt::appender(buffer), "{}{}", i == 0 ? "" : " ", shell_quote(m_argv[i]));
}
fmt::format_to(fmt::appender(buffer), "\n```\n\n");

m_ostream << fmt::to_string(buffer);
}

void markdown_printer::do_print_device_info()
{
fmt::memory_buffer buffer;
Expand Down
4 changes: 4 additions & 0 deletions nvbench/markdown_printer.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <nvbench/printer_base.cuh>

#include <string>
#include <vector>

namespace nvbench
{
Expand Down Expand Up @@ -62,6 +63,8 @@ struct markdown_printer : nvbench::printer_base

protected:
// Virtual API from printer_base:
void do_log_argv(const std::vector<std::string> &argv) override;
void do_print_argv() override;
void do_print_device_info() override;
void do_print_log_preamble() override;
void do_print_log_epilogue() override;
Expand All @@ -80,6 +83,7 @@ protected:
virtual std::string do_format_sample_size(const nvbench::summary &count);
virtual std::string do_format_percentage(const nvbench::summary &percentage);

std::vector<std::string> m_argv;
bool m_color{false};
};

Expand Down
2 changes: 1 addition & 1 deletion nvbench/option_parser.cu
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ void option_parser::parse_impl()

this->update_used_device_state();

m_printer.log_argv(m_args);
m_printer.log_argv(this->get_raw_args());
}

void option_parser::parse_range(option_parser::arg_iterator_t first,
Expand Down
23 changes: 23 additions & 0 deletions nvbench/option_parser.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace nvbench
Expand All @@ -62,11 +63,30 @@ struct option_parser
void parse(int argc, char const *const argv[]);
void parse(std::vector<std::string> args);

/*!
* Set the command line that invoked the executable, before any modification.
*
* Call this before `parse`. `parse` sends these args to the printers instead
* of its own args.
*/
void set_raw_args(std::vector<std::string> raw_args) { m_raw_args = std::move(raw_args); }

[[nodiscard]] benchmark_vector &get_benchmarks() { return m_benchmarks; };
[[nodiscard]] const benchmark_vector &get_benchmarks() const { return m_benchmarks; };

/*!
* The args given to `parse`. A customization handler can modify these.
*/
[[nodiscard]] const std::vector<std::string> &get_args() const { return m_args; }

/*!
* The args given to `set_raw_args`, or `get_args` if it was not called.
*/
[[nodiscard]] const std::vector<std::string> &get_raw_args() const
{
return m_raw_args ? *m_raw_args : m_args;
}

/*!
* Returns the output format requested by the parse options.
*
Expand Down Expand Up @@ -141,6 +161,9 @@ private:
// Command line args
std::vector<std::string> m_args;

// The unmodified command line, if the caller supplied one.
std::optional<std::vector<std::string>> m_raw_args;

// Store benchmark modifiers passed in before any benchmarks are requested as
// "global args". Replay them after every benchmark.
std::vector<std::string> m_global_benchmark_args;
Expand Down
13 changes: 13 additions & 0 deletions nvbench/printer_base.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,21 @@ struct printer_base
/*!
* Called once with the command line arguments used to invoke the current
* executable.
*
* `NVBENCH_MAIN` supplies the command line as the user typed it, not the
* arguments that the customization handlers produce. Use
* `nvbench::option_parser::get_args` for the parsed arguments.
*/
void log_argv(const std::vector<std::string> &argv) { this->do_log_argv(argv); }

/*!
* Print the command line used to invoke the current executable, if supported.
*
* Called before running benchmarks for active terminal output. Must be called
* after `log_argv`.
*/
void print_argv() { this->do_print_argv(); }

/*!
* Print a summary of all detected devices, if supported.
*
Expand Down Expand Up @@ -194,6 +206,7 @@ struct printer_base
protected:
// Implementation hooks for subclasses:
virtual void do_log_argv(const std::vector<std::string> &) {}
virtual void do_print_argv() {}
virtual void do_print_device_info() {}
virtual void do_print_log_preamble() {}
virtual void do_print_log_epilogue() {}
Expand Down
1 change: 1 addition & 0 deletions nvbench/printer_multiplex.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct printer_multiplex : nvbench::printer_base

protected:
void do_log_argv(const std::vector<std::string> &argv) override;
void do_print_argv() override;
void do_print_device_info() override;
void do_print_log_preamble() override;
void do_print_log_epilogue() override;
Expand Down
8 changes: 8 additions & 0 deletions nvbench/printer_multiplex.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ printer_multiplex::printer_multiplex()
: printer_base(std::cerr) // Nothing should write to this.
{}

void printer_multiplex::do_print_argv()
{
for (auto &format_ptr : m_printers)
{
format_ptr->print_argv();
}
}

void printer_multiplex::do_print_device_info()
{
for (auto &format_ptr : m_printers)
Expand Down
6 changes: 6 additions & 0 deletions testing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ set(test_srcs
custom_main_custom_args.cu
custom_main_custom_exceptions.cu
custom_main_global_state_raii.cu
custom_main_raw_argv.cu
enum_type_list.cu
entropy_criterion.cu
exception_safety.cu
Expand Down Expand Up @@ -38,6 +39,7 @@ set(test_srcs
# CTest commands+args can't be modified after creation, so we need to rely on substitution.
set(NVBench_TEST_ARGS_nvbench.test.custom_main_custom_args "--quiet" "--my-custom-arg" "--profile" "-d" "0")
set(NVBench_TEST_ARGS_nvbench.test.custom_main_custom_exceptions "--quiet" "--profile" "-d" "0")
set(NVBench_TEST_ARGS_nvbench.test.custom_main_raw_argv "--my-custom-arg" "-d" "0")

# Metatarget for all tests:
add_custom_target(nvbench.test.all)
Expand All @@ -60,6 +62,10 @@ endforeach()
set_tests_properties(nvbench.test.custom_main_custom_exceptions PROPERTIES
PASS_REGULAR_EXPRESSION "Custom error detected: Expected exception thrown."
)
set_tests_properties(nvbench.test.custom_main_raw_argv PROPERTIES
PASS_REGULAR_EXPRESSION "custom_main_raw_argv --my-custom-arg -d 0"
FAIL_REGULAR_EXPRESSION "custom_main_raw_argv --profile"
)
set_tests_properties(nvbench.test.exception_safety PROPERTIES TIMEOUT 20)

add_subdirectory(cmake)
Expand Down
Loading
Loading