Add `benchmark_context` flag that allows per-run custom context. (#1127)

* Add `benchmark_context` flag that allows per-run custom context.

Add support for key-value flags in general.
Added test for key-value flags.
Added `benchmark_context` flag.
Output content of `benchmark_context` to base reporter.

Solves the first part of #525.

* Docs and better help
This commit is contained in:
Dominic Hamon 2021-05-04 14:36:11 +01:00 committed by GitHub
parent ba9a763def
commit 33c133a206
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 165 additions and 31 deletions

View File

@ -273,6 +273,8 @@ too (`-lkstat`).
[Result Comparison](#result-comparison) [Result Comparison](#result-comparison)
[Extra Context](#extra-context)
### Library ### Library
[Runtime and Reporting Considerations](#runtime-and-reporting-considerations) [Runtime and Reporting Considerations](#runtime-and-reporting-considerations)
@ -442,6 +444,25 @@ BM_memcpy/32k 1834 ns 1837 ns 357143
It is possible to compare the benchmarking results. It is possible to compare the benchmarking results.
See [Additional Tooling Documentation](docs/tools.md) See [Additional Tooling Documentation](docs/tools.md)
<a name="extra-context" />
### Extra Context
Sometimes it's useful to add extra context to the content printed before the
results. By default this section includes information about the CPU on which
the benchmarks are running. If you do want to add more context, you can use
the `benchmark_context` command line flag:
```bash
$ ./run_benchmarks --benchmark_context=pwd=`pwd`
Run on (1 x 2300 MHz CPU)
pwd: /home/user/benchmark/
Benchmark Time CPU Iterations
----------------------------------------------------
BM_memcpy/32 11 ns 11 ns 79545455
BM_memcpy/32k 2181 ns 2185 ns 324074
```
<a name="runtime-and-reporting-considerations" /> <a name="runtime-and-reporting-considerations" />
### Runtime and Reporting Considerations ### Runtime and Reporting Considerations

View File

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
#include "benchmark/benchmark.h" #include "benchmark/benchmark.h"
#include "benchmark_api_internal.h" #include "benchmark_api_internal.h"
#include "benchmark_runner.h" #include "benchmark_runner.h"
#include "internal_macros.h" #include "internal_macros.h"
@ -104,6 +105,10 @@ DEFINE_string(benchmark_color, "auto");
// Valid values: 'true'/'yes'/1, 'false'/'no'/0. Defaults to false. // Valid values: 'true'/'yes'/1, 'false'/'no'/0. Defaults to false.
DEFINE_bool(benchmark_counters_tabular, false); DEFINE_bool(benchmark_counters_tabular, false);
// Extra context to include in the output formatted as comma-separated key-value
// pairs.
DEFINE_kvpairs(benchmark_context, {});
// The level of verbose logging to output // The level of verbose logging to output
DEFINE_int32(v, 0); DEFINE_int32(v, 0);
@ -446,6 +451,7 @@ void PrintUsageAndExit() {
" [--benchmark_out_format=<json|console|csv>]\n" " [--benchmark_out_format=<json|console|csv>]\n"
" [--benchmark_color={auto|true|false}]\n" " [--benchmark_color={auto|true|false}]\n"
" [--benchmark_counters_tabular={true|false}]\n" " [--benchmark_counters_tabular={true|false}]\n"
" [--benchmark_context=<key>=<value>,...]\n"
" [--v=<verbosity>]\n"); " [--v=<verbosity>]\n");
exit(0); exit(0);
} }
@ -476,9 +482,11 @@ void ParseCommandLineFlags(int* argc, char** argv) {
ParseStringFlag(argv[i], "color_print", &FLAGS_benchmark_color) || ParseStringFlag(argv[i], "color_print", &FLAGS_benchmark_color) ||
ParseBoolFlag(argv[i], "benchmark_counters_tabular", ParseBoolFlag(argv[i], "benchmark_counters_tabular",
&FLAGS_benchmark_counters_tabular) || &FLAGS_benchmark_counters_tabular) ||
ParseInt32Flag(argv[i], "v", &FLAGS_v) ||
ParseStringFlag(argv[i], "benchmark_perf_counters", ParseStringFlag(argv[i], "benchmark_perf_counters",
&FLAGS_benchmark_perf_counters)) { &FLAGS_benchmark_perf_counters) ||
ParseKeyValueFlag(argv[i], "benchmark_context",
&FLAGS_benchmark_context) ||
ParseInt32Flag(argv[i], "v", &FLAGS_v)) {
for (int j = i; j != *argc - 1; ++j) argv[j] = argv[j + 1]; for (int j = i; j != *argc - 1; ++j) argv[j] = argv[j + 1];
--(*argc); --(*argc);

View File

@ -20,6 +20,10 @@
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <limits> #include <limits>
#include <map>
#include <utility>
#include "../src/string_util.h"
namespace benchmark { namespace benchmark {
namespace { namespace {
@ -78,6 +82,30 @@ bool ParseDouble(const std::string& src_text, const char* str, double* value) {
return true; return true;
} }
// Parses 'str' into KV pairs. If successful, writes the result to *value and
// returns true; otherwise leaves *value unchanged and returns false.
bool ParseKvPairs(const std::string& src_text, const char* str,
std::map<std::string, std::string>* value) {
std::map<std::string, std::string> kvs;
for (const auto& kvpair : StrSplit(str, ',')) {
const auto kv = StrSplit(kvpair, '=');
if (kv.size() != 2) {
std::cerr << src_text << " is expected to be a comma-separated list of "
<< "<key>=<value> strings, but actually has value \"" << str
<< "\".\n";
return false;
}
if (!kvs.emplace(kv[0], kv[1]).second) {
std::cerr << src_text << " is expected to contain unique keys but key \""
<< kv[0] << "\" was repeated.\n";
return false;
}
}
*value = kvs;
return true;
}
// Returns the name of the environment variable corresponding to the // Returns the name of the environment variable corresponding to the
// given flag. For example, FlagToEnvVar("foo") will return // given flag. For example, FlagToEnvVar("foo") will return
// "BENCHMARK_FOO" in the open-source version. // "BENCHMARK_FOO" in the open-source version.
@ -129,6 +157,20 @@ const char* StringFromEnv(const char* flag, const char* default_val) {
return value == nullptr ? default_val : value; return value == nullptr ? default_val : value;
} }
std::map<std::string, std::string> KvPairsFromEnv(
const char* flag, std::map<std::string, std::string> default_val) {
const std::string env_var = FlagToEnvVar(flag);
const char* const value_str = getenv(env_var.c_str());
if (value_str == nullptr) return default_val;
std::map<std::string, std::string> value;
if (!ParseKvPairs("Environment variable " + env_var, value_str, &value)) {
return default_val;
}
return value;
}
// Parses a string as a command line flag. The string should have // Parses a string as a command line flag. The string should have
// the format "--flag=value". When def_optional is true, the "=value" // the format "--flag=value". When def_optional is true, the "=value"
// part can be omitted. // part can be omitted.
@ -206,6 +248,22 @@ bool ParseStringFlag(const char* str, const char* flag, std::string* value) {
return true; return true;
} }
bool ParseKeyValueFlag(
const char* str, const char* flag,
std::map<std::string, std::string>* value) {
const char* const value_str = ParseFlagValue(str, flag, false);
if (value_str == nullptr) return false;
for (const auto& kvpair : StrSplit(value_str, ',')) {
const auto kv = StrSplit(kvpair, '=');
if (kv.size() != 2) return false;
value->emplace(kv[0], kv[1]);
}
return true;
}
bool IsFlag(const char* str, const char* flag) { bool IsFlag(const char* str, const char* flag) {
return (ParseFlagValue(str, flag, true) != nullptr); return (ParseFlagValue(str, flag, true) != nullptr);
} }

View File

@ -2,6 +2,7 @@
#define BENCHMARK_COMMANDLINEFLAGS_H_ #define BENCHMARK_COMMANDLINEFLAGS_H_
#include <cstdint> #include <cstdint>
#include <map>
#include <string> #include <string>
// Macro for referencing flags. // Macro for referencing flags.
@ -12,51 +13,59 @@
#define DECLARE_int32(name) extern int32_t FLAG(name) #define DECLARE_int32(name) extern int32_t FLAG(name)
#define DECLARE_double(name) extern double FLAG(name) #define DECLARE_double(name) extern double FLAG(name)
#define DECLARE_string(name) extern std::string FLAG(name) #define DECLARE_string(name) extern std::string FLAG(name)
#define DECLARE_kvpairs(name) \
extern std::map<std::string, std::string> FLAG(name)
// Macros for defining flags. // Macros for defining flags.
#define DEFINE_bool(name, default_val) \ #define DEFINE_bool(name, default_val) \
bool FLAG(name) = \ bool FLAG(name) = benchmark::BoolFromEnv(#name, default_val)
benchmark::BoolFromEnv(#name, default_val) #define DEFINE_int32(name, default_val) \
#define DEFINE_int32(name, default_val) \ int32_t FLAG(name) = benchmark::Int32FromEnv(#name, default_val)
int32_t FLAG(name) = \ #define DEFINE_double(name, default_val) \
benchmark::Int32FromEnv(#name, default_val) double FLAG(name) = benchmark::DoubleFromEnv(#name, default_val)
#define DEFINE_double(name, default_val) \ #define DEFINE_string(name, default_val) \
double FLAG(name) = \ std::string FLAG(name) = benchmark::StringFromEnv(#name, default_val)
benchmark::DoubleFromEnv(#name, default_val) #define DEFINE_kvpairs(name, default_val) \
#define DEFINE_string(name, default_val) \ std::map<std::string, std::string> FLAG(name) = \
std::string FLAG(name) = \ benchmark::KvPairsFromEnv(#name, default_val)
benchmark::StringFromEnv(#name, default_val)
namespace benchmark { namespace benchmark {
// Parses a bool from the environment variable // Parses a bool from the environment variable corresponding to the given flag.
// corresponding to the given flag.
// //
// If the variable exists, returns IsTruthyFlagValue() value; if not, // If the variable exists, returns IsTruthyFlagValue() value; if not,
// returns the given default value. // returns the given default value.
bool BoolFromEnv(const char* flag, bool default_val); bool BoolFromEnv(const char* flag, bool default_val);
// Parses an Int32 from the environment variable // Parses an Int32 from the environment variable corresponding to the given
// corresponding to the given flag. // flag.
// //
// If the variable exists, returns ParseInt32() value; if not, returns // If the variable exists, returns ParseInt32() value; if not, returns
// the given default value. // the given default value.
int32_t Int32FromEnv(const char* flag, int32_t default_val); int32_t Int32FromEnv(const char* flag, int32_t default_val);
// Parses an Double from the environment variable // Parses an Double from the environment variable corresponding to the given
// corresponding to the given flag. // flag.
// //
// If the variable exists, returns ParseDouble(); if not, returns // If the variable exists, returns ParseDouble(); if not, returns
// the given default value. // the given default value.
double DoubleFromEnv(const char* flag, double default_val); double DoubleFromEnv(const char* flag, double default_val);
// Parses a string from the environment variable // Parses a string from the environment variable corresponding to the given
// corresponding to the given flag. // flag.
// //
// If variable exists, returns its value; if not, returns // If variable exists, returns its value; if not, returns
// the given default value. // the given default value.
const char* StringFromEnv(const char* flag, const char* default_val); const char* StringFromEnv(const char* flag, const char* default_val);
// Parses a set of kvpairs from the environment variable corresponding to the
// given flag.
//
// If variable exists, returns its value; if not, returns
// the given default value.
std::map<std::string, std::string> KvPairsFromEnv(
const char* flag, std::map<std::string, std::string> default_val);
// Parses a string for a bool flag, in the form of either // Parses a string for a bool flag, in the form of either
// "--flag=value" or "--flag". // "--flag=value" or "--flag".
// //
@ -68,27 +77,31 @@ const char* StringFromEnv(const char* flag, const char* default_val);
// true. On failure, returns false without changing *value. // true. On failure, returns false without changing *value.
bool ParseBoolFlag(const char* str, const char* flag, bool* value); bool ParseBoolFlag(const char* str, const char* flag, bool* value);
// Parses a string for an Int32 flag, in the form of // Parses a string for an Int32 flag, in the form of "--flag=value".
// "--flag=value".
// //
// On success, stores the value of the flag in *value, and returns // On success, stores the value of the flag in *value, and returns
// true. On failure, returns false without changing *value. // true. On failure, returns false without changing *value.
bool ParseInt32Flag(const char* str, const char* flag, int32_t* value); bool ParseInt32Flag(const char* str, const char* flag, int32_t* value);
// Parses a string for a Double flag, in the form of // Parses a string for a Double flag, in the form of "--flag=value".
// "--flag=value".
// //
// On success, stores the value of the flag in *value, and returns // On success, stores the value of the flag in *value, and returns
// true. On failure, returns false without changing *value. // true. On failure, returns false without changing *value.
bool ParseDoubleFlag(const char* str, const char* flag, double* value); bool ParseDoubleFlag(const char* str, const char* flag, double* value);
// Parses a string for a string flag, in the form of // Parses a string for a string flag, in the form of "--flag=value".
// "--flag=value".
// //
// On success, stores the value of the flag in *value, and returns // On success, stores the value of the flag in *value, and returns
// true. On failure, returns false without changing *value. // true. On failure, returns false without changing *value.
bool ParseStringFlag(const char* str, const char* flag, std::string* value); bool ParseStringFlag(const char* str, const char* flag, std::string* value);
// Parses a string for a kvpairs flag in the form "--flag=key=value,key=value"
//
// On success, stores the value of the flag in *value and returns true. On
// failure returns false, though *value may have been mutated.
bool ParseKeyValueFlag(const char* str, const char* flag,
std::map<std::string, std::string>* value);
// Returns true if the string matches the flag. // Returns true if the string matches the flag.
bool IsFlag(const char* str, const char* flag); bool IsFlag(const char* str, const char* flag);

View File

@ -22,8 +22,11 @@
#include <vector> #include <vector>
#include "check.h" #include "check.h"
#include "commandlineflags.h"
#include "string_util.h" #include "string_util.h"
DECLARE_kvpairs(benchmark_context);
namespace benchmark { namespace benchmark {
BenchmarkReporter::BenchmarkReporter() BenchmarkReporter::BenchmarkReporter()
@ -64,6 +67,10 @@ void BenchmarkReporter::PrintBasicContext(std::ostream *out,
Out << "\n"; Out << "\n";
} }
for (const auto& kv: FLAGS_benchmark_context) {
Out << kv.first << ": " << kv.second << "\n";
}
if (CPUInfo::Scaling::ENABLED == info.scaling) { if (CPUInfo::Scaling::ENABLED == info.scaling) {
Out << "***WARNING*** CPU scaling is enabled, the benchmark " Out << "***WARNING*** CPU scaling is enabled, the benchmark "
"real time measurements may be noisy and will incur extra " "real time measurements may be noisy and will incur extra "

View File

@ -2,6 +2,7 @@
#include "../src/commandlineflags.h" #include "../src/commandlineflags.h"
#include "../src/internal_macros.h" #include "../src/internal_macros.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
namespace benchmark { namespace benchmark {
@ -19,9 +20,7 @@ int setenv(const char* name, const char* value, int overwrite) {
return _putenv_s(name, value); return _putenv_s(name, value);
} }
int unsetenv(const char* name) { int unsetenv(const char* name) { return _putenv_s(name, ""); }
return _putenv_s(name, "");
}
#endif // BENCHMARK_OS_WINDOWS #endif // BENCHMARK_OS_WINDOWS
@ -197,5 +196,33 @@ TEST(StringFromEnv, Valid) {
unsetenv("IN_ENV"); unsetenv("IN_ENV");
} }
TEST(KvPairsFromEnv, Default) {
ASSERT_EQ(unsetenv("NOT_IN_ENV"), 0);
EXPECT_THAT(KvPairsFromEnv("not_in_env", {{"foo", "bar"}}),
testing::ElementsAre(testing::Pair("foo", "bar")));
}
TEST(KvPairsFromEnv, MalformedReturnsDefault) {
ASSERT_EQ(setenv("IN_ENV", "foo", 1), 0);
EXPECT_THAT(KvPairsFromEnv("in_env", {{"foo", "bar"}}),
testing::ElementsAre(testing::Pair("foo", "bar")));
unsetenv("IN_ENV");
}
TEST(KvPairsFromEnv, Single) {
ASSERT_EQ(setenv("IN_ENV", "foo=bar", 1), 0);
EXPECT_THAT(KvPairsFromEnv("in_env", {}),
testing::ElementsAre(testing::Pair("foo", "bar")));
unsetenv("IN_ENV");
}
TEST(KvPairsFromEnv, Multiple) {
ASSERT_EQ(setenv("IN_ENV", "foo=bar,baz=qux", 1), 0);
EXPECT_THAT(KvPairsFromEnv("in_env", {}),
testing::UnorderedElementsAre(testing::Pair("foo", "bar"),
testing::Pair("baz", "qux")));
unsetenv("IN_ENV");
}
} // namespace } // namespace
} // namespace benchmark } // namespace benchmark