diff --git a/AUTHORS b/AUTHORS index ef3051a1..d5cd0078 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,6 +13,7 @@ Andriy Berestovskyy Arne Beer Carto Christopher Seymour +Daniel Harvey David Coeurjolly Deniz Evrenci Dirac Research diff --git a/CONTRIBUTORS b/CONTRIBUTORS index d0c31df1..c86d8735 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -29,6 +29,7 @@ Billy Robert O'Neal III Chris Kennelly Christopher Seymour Cyrille Faucheux +Daniel Harvey David Coeurjolly Deniz Evrenci Dominic Hamon diff --git a/include/benchmark/benchmark.h b/include/benchmark/benchmark.h index e4f39211..2954ff48 100644 --- a/include/benchmark/benchmark.h +++ b/include/benchmark/benchmark.h @@ -1302,6 +1302,23 @@ struct SystemInfo { BENCHMARK_DISALLOW_COPY_AND_ASSIGN(SystemInfo); }; +// BenchmarkName contains the components of the Benchmark's name +// which allows individual fields to be modified or cleared before +// building the final name using 'str()'. +struct BenchmarkName { + std::string function_name; + std::string args; + std::string min_time; + std::string iterations; + std::string repetitions; + std::string time_type; + std::string threads; + + // Return the full name of the benchmark with each non-empty + // field separated by a '/' + std::string str() const; +}; + // Interface for custom benchmark result printers. // By default, benchmark reports are printed to stdout. However an application // can control the destination of the reports by calling @@ -1340,7 +1357,7 @@ class BenchmarkReporter { max_bytes_used(0) {} std::string benchmark_name() const; - std::string run_name; + BenchmarkName run_name; RunType run_type; // is this a measurement, or an aggregate? std::string aggregate_name; std::string report_label; // Empty if not set by benchmark. diff --git a/src/benchmark.cc b/src/benchmark.cc index aab07500..c76346c6 100644 --- a/src/benchmark.cc +++ b/src/benchmark.cc @@ -233,7 +233,7 @@ void RunBenchmarks(const std::vector& benchmarks, size_t stat_field_width = 0; for (const BenchmarkInstance& benchmark : benchmarks) { name_field_width = - std::max(name_field_width, benchmark.name.size()); + std::max(name_field_width, benchmark.name.str().size()); might_have_aggregates |= benchmark.repetitions > 1; for (const auto& Stat : *benchmark.statistics) @@ -393,7 +393,8 @@ size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter, } if (FLAGS_benchmark_list_tests) { - for (auto const& benchmark : benchmarks) Out << benchmark.name << "\n"; + for (auto const& benchmark : benchmarks) + Out << benchmark.name.str() << "\n"; } else { internal::RunBenchmarks(benchmarks, display_reporter, file_reporter); } diff --git a/src/benchmark_api_internal.h b/src/benchmark_api_internal.h index 0524a85c..b220fb0d 100644 --- a/src/benchmark_api_internal.h +++ b/src/benchmark_api_internal.h @@ -16,7 +16,7 @@ namespace internal { // Information kept per benchmark we may want to run struct BenchmarkInstance { - std::string name; + BenchmarkName name; Benchmark* benchmark; AggregationReportMode aggregation_report_mode; std::vector arg; diff --git a/src/benchmark_name.cc b/src/benchmark_name.cc new file mode 100644 index 00000000..2a17ebce --- /dev/null +++ b/src/benchmark_name.cc @@ -0,0 +1,58 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +namespace benchmark { + +namespace { + +// Compute the total size of a pack of std::strings +size_t size_impl() { return 0; } + +template +size_t size_impl(const Head& head, const Tail&... tail) { + return head.size() + size_impl(tail...); +} + +// Join a pack of std::strings using a delimiter +// TODO: use absl::StrJoin +void join_impl(std::string&, char) {} + +template +void join_impl(std::string& s, const char delimiter, const Head& head, + const Tail&... tail) { + if (!s.empty() && !head.empty()) { + s += delimiter; + } + + s += head; + + join_impl(s, delimiter, tail...); +} + +template +std::string join(char delimiter, const Ts&... ts) { + std::string s; + s.reserve(sizeof...(Ts) + size_impl(ts...)); + join_impl(s, delimiter, ts...); + return s; +} +} // namespace + +std::string BenchmarkName::str() const { + return join('/', function_name, args, min_time, iterations, repetitions, + time_type, threads); +} +} // namespace benchmark diff --git a/src/benchmark_register.cc b/src/benchmark_register.cc index f17f5b22..9d775297 100644 --- a/src/benchmark_register.cc +++ b/src/benchmark_register.cc @@ -153,7 +153,7 @@ bool BenchmarkFamilies::FindBenchmarks( for (auto const& args : family->args_) { for (int num_threads : *thread_counts) { BenchmarkInstance instance; - instance.name = family->name_; + instance.name.function_name = family->name_; instance.benchmark = family.get(); instance.aggregation_report_mode = family->aggregation_report_mode_; instance.arg = args; @@ -172,45 +172,51 @@ bool BenchmarkFamilies::FindBenchmarks( // Add arguments to instance name size_t arg_i = 0; for (auto const& arg : args) { - instance.name += "/"; + if (!instance.name.args.empty()) { + instance.name.args += '/'; + } if (arg_i < family->arg_names_.size()) { const auto& arg_name = family->arg_names_[arg_i]; if (!arg_name.empty()) { - instance.name += - StrFormat("%s:", family->arg_names_[arg_i].c_str()); + instance.name.args += StrFormat("%s:", arg_name.c_str()); } } // we know that the args are always non-negative (see 'AddRange()'), // thus print as 'unsigned'. BUT, do a cast due to the 32-bit builds. - instance.name += StrFormat("%lu", static_cast(arg)); + instance.name.args += + StrFormat("%lu", static_cast(arg)); + ++arg_i; } if (!IsZero(family->min_time_)) - instance.name += StrFormat("/min_time:%0.3f", family->min_time_); + instance.name.min_time = + StrFormat("min_time:%0.3f", family->min_time_); if (family->iterations_ != 0) { - instance.name += - StrFormat("/iterations:%lu", + instance.name.iterations = + StrFormat("iterations:%lu", static_cast(family->iterations_)); } if (family->repetitions_ != 0) - instance.name += StrFormat("/repeats:%d", family->repetitions_); + instance.name.repetitions = + StrFormat("repeats:%d", family->repetitions_); if (family->use_manual_time_) { - instance.name += "/manual_time"; + instance.name.time_type = "manual_time"; } else if (family->use_real_time_) { - instance.name += "/real_time"; + instance.name.time_type = "real_time"; } // Add the number of threads used to the name if (!family->thread_counts_.empty()) { - instance.name += StrFormat("/threads:%d", instance.threads); + instance.name.threads = StrFormat("threads:%d", instance.threads); } - if ((re.Match(instance.name) && !isNegativeFilter) || - (!re.Match(instance.name) && isNegativeFilter)) { + const auto full_name = instance.name.str(); + if ((re.Match(full_name) && !isNegativeFilter) || + (!re.Match(full_name) && isNegativeFilter)) { instance.last_benchmark_instance = (&args == &family->args_.back()); benchmarks->push_back(std::move(instance)); } diff --git a/src/benchmark_runner.cc b/src/benchmark_runner.cc index 38faeec8..9e1f6e46 100644 --- a/src/benchmark_runner.cc +++ b/src/benchmark_runner.cc @@ -191,7 +191,7 @@ class BenchmarkRunner { double seconds; }; IterationResults DoNIterations() { - VLOG(2) << "Running " << b.name << " for " << iters << "\n"; + VLOG(2) << "Running " << b.name.str() << " for " << iters << "\n"; std::unique_ptr manager; manager.reset(new internal::ThreadManager(b.threads)); diff --git a/src/complexity.cc b/src/complexity.cc index 6ef17660..4c6fcf6d 100644 --- a/src/complexity.cc +++ b/src/complexity.cc @@ -183,8 +183,9 @@ std::vector ComputeBigO( result_real = MinimalLeastSq(n, real_time, result_cpu.complexity); } - std::string run_name = reports[0].benchmark_name().substr( - 0, reports[0].benchmark_name().find('/')); + // Drop the 'args' when reporting complexity. + auto run_name = reports[0].run_name; + run_name.args.clear(); // Get the data from the accumulator to BenchmarkReporter::Run's. Run big_o; diff --git a/src/json_reporter.cc b/src/json_reporter.cc index 7d01e8e4..26f94c6b 100644 --- a/src/json_reporter.cc +++ b/src/json_reporter.cc @@ -168,7 +168,7 @@ void JSONReporter::PrintRunData(Run const& run) { std::string indent(6, ' '); std::ostream& out = GetOutputStream(); out << indent << FormatKV("name", run.benchmark_name()) << ",\n"; - out << indent << FormatKV("run_name", run.run_name) << ",\n"; + out << indent << FormatKV("run_name", run.run_name.str()) << ",\n"; out << indent << FormatKV("run_type", [&run]() -> const char* { switch (run.run_type) { case BenchmarkReporter::Run::RT_Iteration: diff --git a/src/reporter.cc b/src/reporter.cc index 59bc5f71..4d3e477d 100644 --- a/src/reporter.cc +++ b/src/reporter.cc @@ -83,7 +83,7 @@ BenchmarkReporter::Context::Context() : cpu_info(CPUInfo::Get()), sys_info(SystemInfo::Get()) {} std::string BenchmarkReporter::Run::benchmark_name() const { - std::string name = run_name; + std::string name = run_name.str(); if (run_type == RT_Aggregate) { name += "_" + aggregate_name; } diff --git a/src/statistics.cc b/src/statistics.cc index e821aec1..e9a170f1 100644 --- a/src/statistics.cc +++ b/src/statistics.cc @@ -147,7 +147,7 @@ std::vector ComputeStats( for (const auto& Stat : *reports[0].statistics) { // Get the data from the accumulator to BenchmarkReporter::Run's. Run data; - data.run_name = reports[0].benchmark_name(); + data.run_name = reports[0].run_name; data.run_type = BenchmarkReporter::Run::RT_Aggregate; data.aggregate_name = Stat.name_; data.report_label = report_label; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f15ce208..e7373b43 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -191,6 +191,7 @@ if (BENCHMARK_ENABLE_GTEST_TESTS) endmacro() add_gtest(benchmark_gtest) + add_gtest(benchmark_name_gtest) add_gtest(statistics_gtest) add_gtest(string_util_gtest) endif(BENCHMARK_ENABLE_GTEST_TESTS) diff --git a/test/benchmark_name_gtest.cc b/test/benchmark_name_gtest.cc new file mode 100644 index 00000000..afb401c1 --- /dev/null +++ b/test/benchmark_name_gtest.cc @@ -0,0 +1,74 @@ +#include "benchmark/benchmark.h" +#include "gtest/gtest.h" + +namespace { + +using namespace benchmark; +using namespace benchmark::internal; + +TEST(BenchmarkNameTest, Empty) { + const auto name = BenchmarkName(); + EXPECT_EQ(name.str(), std::string()); +} + +TEST(BenchmarkNameTest, FunctionName) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + EXPECT_EQ(name.str(), "function_name"); +} + +TEST(BenchmarkNameTest, FunctionNameAndArgs) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.args = "some_args:3/4/5"; + EXPECT_EQ(name.str(), "function_name/some_args:3/4/5"); +} + +TEST(BenchmarkNameTest, MinTime) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.args = "some_args:3/4"; + name.min_time = "min_time:3.4s"; + EXPECT_EQ(name.str(), "function_name/some_args:3/4/min_time:3.4s"); +} + +TEST(BenchmarkNameTest, Iterations) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.iterations = "iterations:42"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/iterations:42"); +} + +TEST(BenchmarkNameTest, Repetitions) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.repetitions = "repetitions:24"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/repetitions:24"); +} + +TEST(BenchmarkNameTest, TimeType) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.time_type = "hammer_time"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/hammer_time"); +} + +TEST(BenchmarkNameTest, Threads) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.threads = "threads:256"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/threads:256"); +} + +TEST(BenchmarkNameTest, TestEmptyFunctionName) { + auto name = BenchmarkName(); + name.args = "first:3/second:4"; + name.threads = "threads:22"; + EXPECT_EQ(name.str(), "first:3/second:4/threads:22"); +} + +} // end namespace diff --git a/test/complexity_test.cc b/test/complexity_test.cc index 323ddfe7..b0fd8711 100644 --- a/test/complexity_test.cc +++ b/test/complexity_test.cc @@ -176,6 +176,26 @@ ADD_COMPLEXITY_CASES(n_lg_n_test_name, big_o_n_lg_n_test_name, ADD_COMPLEXITY_CASES(n_lg_n_test_name, big_o_n_lg_n_test_name, rms_o_n_lg_n_test_name, lambda_big_o_n_lg_n); +// ========================================================================= // +// -------- Testing formatting of Complexity with captured args ------------ // +// ========================================================================= // + +void BM_ComplexityCaptureArgs(benchmark::State &state, int n) { + for (auto _ : state) { + } + state.SetComplexityN(n); +} + +BENCHMARK_CAPTURE(BM_ComplexityCaptureArgs, capture_test, 100) + ->Complexity(benchmark::oN) + ->Ranges({{1, 2}, {3, 4}}); + +const std::string complexity_capture_name = + "BM_ComplexityCaptureArgs/capture_test"; + +ADD_COMPLEXITY_CASES(complexity_capture_name, complexity_capture_name + "_BigO", + complexity_capture_name + "_RMS", "N"); + // ========================================================================= // // --------------------------- TEST CASES END ------------------------------ // // ========================================================================= //