#include #include #include #include #include #include #include "../src/perf_counters.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #ifndef GTEST_SKIP struct MsgHandler { void operator=(std::ostream&) {} }; #define GTEST_SKIP() return MsgHandler() = std::cout #endif using benchmark::internal::PerfCounters; using benchmark::internal::PerfCountersMeasurement; using benchmark::internal::PerfCounterValues; using ::testing::AllOf; using ::testing::Gt; using ::testing::Lt; namespace { const char kGenericPerfEvent1[] = "CYCLES"; const char kGenericPerfEvent2[] = "INSTRUCTIONS"; std::set UniqueCounterNames(const PerfCounters& counters) { return {counters.names().begin(), counters.names().end()}; } bool HasRequiredPerfCounters(const std::vector& names) { if (!PerfCounters::kSupported) { return false; } auto counters = PerfCounters::Create(names); auto actual_names = UniqueCounterNames(counters); for (const auto& name : names) { if (actual_names.find(name) == actual_names.end()) { return false; } } return true; } TEST(PerfCountersTest, Init) { EXPECT_EQ(PerfCounters::Initialize(), PerfCounters::kSupported); } TEST(PerfCountersTest, OneCounter) { if (!HasRequiredPerfCounters({kGenericPerfEvent1})) { GTEST_SKIP() << "Requested performance counters are not available."; } auto counter = PerfCounters::Create({kGenericPerfEvent1}); EXPECT_EQ(UniqueCounterNames(counter).size(), 1); } TEST(PerfCountersTest, NegativeTest) { if (!PerfCounters::kSupported) { EXPECT_FALSE(PerfCounters::Initialize()); return; } if (!HasRequiredPerfCounters({kGenericPerfEvent2, kGenericPerfEvent1})) { GTEST_SKIP() << "Requested performance counters are not available."; } // Safety checks // Create() will always create a valid object, even if passed no or // wrong arguments as the new behavior is to warn and drop unsupported // counters EXPECT_EQ(PerfCounters::Create({}).num_counters(), 0); EXPECT_EQ(PerfCounters::Create({""}).num_counters(), 0); EXPECT_EQ(PerfCounters::Create({"not a counter name"}).num_counters(), 0); { // Try sneaking in a bad egg to see if it is filtered out. The // number of counters has to be two, not zero auto counter = PerfCounters::Create({kGenericPerfEvent2, "", kGenericPerfEvent1}); auto names = UniqueCounterNames(counter); EXPECT_EQ(names.size(), 2); EXPECT_EQ(names, std::set({kGenericPerfEvent2, kGenericPerfEvent1})); } { // Try sneaking in an outrageous counter, like a fat finger mistake auto counter = PerfCounters::Create( {kGenericPerfEvent2, "not a counter name", kGenericPerfEvent1}); auto names = UniqueCounterNames(counter); EXPECT_EQ(names.size(), 2); EXPECT_EQ(names, std::set({kGenericPerfEvent2, kGenericPerfEvent1})); } { // Finally try a golden input - it should like both of them EXPECT_EQ(UniqueCounterNames(PerfCounters::Create( {kGenericPerfEvent1, kGenericPerfEvent2})) .size(), 2); } { // Add a bad apple in the end of the chain to check the edges auto counter = PerfCounters::Create( {kGenericPerfEvent1, kGenericPerfEvent2, "bad event name"}); auto names = UniqueCounterNames(counter); EXPECT_EQ(names.size(), 2); EXPECT_EQ(names, std::set({kGenericPerfEvent1, kGenericPerfEvent2})); } } static std::map SnapshotAndCombine( PerfCounters& counters) { PerfCounterValues values(counters.num_counters()); std::map value_map; if (counters.Snapshot(&values)) { for (size_t i = 0; i != counters.num_counters(); ++i) { value_map[counters.names()[i]] += values[i]; } } return value_map; } TEST(PerfCountersTest, Read1Counter) { if (!HasRequiredPerfCounters({kGenericPerfEvent1})) { GTEST_SKIP() << "Requested performance counters are not available."; } auto counters = PerfCounters::Create({kGenericPerfEvent1}); auto values1 = SnapshotAndCombine(counters); EXPECT_EQ(values1.size(), 1); EXPECT_GT(values1.begin()->second, 0); auto values2 = SnapshotAndCombine(counters); EXPECT_EQ(values2.size(), 1); EXPECT_GT(values2.begin()->second, 0); EXPECT_GT(values2.begin()->second, values1.begin()->second); } TEST(PerfCountersTest, Read1CounterEachCPU) { if (!HasRequiredPerfCounters({kGenericPerfEvent1})) { GTEST_SKIP() << "Requested performance counters are not available."; } #ifdef __linux__ cpu_set_t saved_set; if (sched_getaffinity(0, sizeof(saved_set), &saved_set) != 0) { // This can happen e.g. if there are more than CPU_SETSIZE CPUs. GTEST_SKIP() << "Could not save CPU affinity mask."; } for (size_t cpu = 0; cpu != CPU_SETSIZE; ++cpu) { cpu_set_t set; CPU_ZERO(&set); CPU_SET(cpu, &set); if (sched_setaffinity(0, sizeof(set), &set) != 0) { break; } auto counters = PerfCounters::Create({kGenericPerfEvent1}); auto values1 = SnapshotAndCombine(counters); EXPECT_EQ(values1.size(), 1); EXPECT_GT(values1.begin()->second, 0); auto values2 = SnapshotAndCombine(counters); EXPECT_EQ(values2.size(), 1); EXPECT_GT(values2.begin()->second, 0); EXPECT_GT(values2.begin()->second, values1.begin()->second); } EXPECT_EQ(sched_setaffinity(0, sizeof(saved_set), &saved_set), 0); #else GTEST_SKIP() << "Test skipped on non-Linux."; #endif } TEST(PerfCountersTest, Read2Counters) { if (!HasRequiredPerfCounters({kGenericPerfEvent1, kGenericPerfEvent2})) { GTEST_SKIP() << "Requested performance counters are not available."; } auto counters = PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2}); auto values1 = SnapshotAndCombine(counters); EXPECT_EQ(values1.size(), 2); for (auto& kv : values1) { EXPECT_GT(kv.second, 0); } auto values2 = SnapshotAndCombine(counters); EXPECT_EQ(values1.size(), 2); for (auto& kv : values2) { EXPECT_GT(kv.second, 0); EXPECT_GT(kv.second, values1[kv.first]); } } TEST(PerfCountersTest, ReopenExistingCounters) { // This test works in recent and old Intel hardware, Pixel 3, and Pixel 6. // However we cannot make assumptions beyond 2 HW counters due to Pixel 6. if (!HasRequiredPerfCounters({kGenericPerfEvent1})) { GTEST_SKIP() << "Requested performance counters are not available."; } std::vector kMetrics({kGenericPerfEvent1}); std::vector counters(2); for (auto& counter : counters) { counter = PerfCounters::Create(kMetrics); } PerfCounterValues values(counters[0].num_counters()); EXPECT_TRUE(counters[0].Snapshot(&values)); EXPECT_TRUE(counters[1].Snapshot(&values)); } TEST(PerfCountersTest, CreateExistingMeasurements) { // The test works (i.e. causes read to fail) for the assumptions // about hardware capabilities (i.e. small number (2) hardware // counters) at this date, // the same as previous test ReopenExistingCounters. if (!PerfCounters::kSupported) { GTEST_SKIP() << "Test skipped because libpfm is not supported."; } // This means we will try 10 counters but we can only guarantee // for sure at this time that only 3 will work. Perhaps in the future // we could use libpfm to query for the hardware limits on this // particular platform. const int kMaxCounters = 10; const int kMinValidCounters = 2; // Let's use a ubiquitous counter that is guaranteed to work // on all platforms const std::vector kMetrics{"cycles"}; if (!HasRequiredPerfCounters(kMetrics)) { GTEST_SKIP() << "Requested performance counters are not available."; } // Cannot create a vector of actual objects because the // copy constructor of PerfCounters is deleted - and so is // implicitly deleted on PerfCountersMeasurement too std::vector> perf_counter_measurements; perf_counter_measurements.reserve(kMaxCounters); for (int j = 0; j < kMaxCounters; ++j) { perf_counter_measurements.emplace_back( new PerfCountersMeasurement(kMetrics)); } std::vector> measurements; // Start all counters together to see if they hold size_t max_counters = kMaxCounters; for (size_t i = 0; i < kMaxCounters; ++i) { auto& counter(*perf_counter_measurements[i]); std::set names{counter.names().begin(), counter.names().end()}; EXPECT_EQ(names.size(), 1); if (!counter.Start()) { max_counters = i; break; }; } ASSERT_GE(max_counters, kMinValidCounters); // Start all together for (size_t i = 0; i < max_counters; ++i) { auto& counter(*perf_counter_measurements[i]); EXPECT_TRUE(counter.Stop(measurements) || (i >= kMinValidCounters)); } // Start/stop individually for (size_t i = 0; i < max_counters; ++i) { auto& counter(*perf_counter_measurements[i]); measurements.clear(); counter.Start(); EXPECT_TRUE(counter.Stop(measurements) || (i >= kMinValidCounters)); } } // We try to do some meaningful work here but the compiler // insists in optimizing away our loop so we had to add a // no-optimize macro. In case it fails, we added some entropy // to this pool as well. BENCHMARK_DONT_OPTIMIZE size_t do_work() { static std::mt19937 rd{std::random_device{}()}; static std::uniform_int_distribution mrand(0, 10); const size_t kNumLoops = 1000000; size_t sum = 0; for (size_t j = 0; j < kNumLoops; ++j) { sum += mrand(rd); } benchmark::DoNotOptimize(sum); return sum; } void measure(size_t threadcount, std::map* before, std::map* after) { BM_CHECK_NE(before, nullptr); BM_CHECK_NE(after, nullptr); std::vector threads(threadcount); // Because we do not care whether the threads execute concurrently, but we do // care that they do all of their work between the SnapshotAndCombine calls, // we serialize them with a mutex. See // https://github.com/google/benchmark/issues/2173. std::mutex mutex; auto work = [&mutex]() { mutex.lock(); BM_CHECK(do_work() > 1000); mutex.unlock(); }; // We need to first set up the counters, then start the threads, so the // threads would inherit the counters. But later, we need to first destroy // the thread pool (so all the work finishes), then measure the counters. So // the scopes overlap, and we need to explicitly control the scope of the // threadpool. auto counters = PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2}); mutex.lock(); for (auto& t : threads) { t = std::thread(work); } *before = SnapshotAndCombine(counters); mutex.unlock(); for (auto& t : threads) { t.join(); } *after = SnapshotAndCombine(counters); } TEST(PerfCountersTest, MultiThreaded) { if (!HasRequiredPerfCounters({kGenericPerfEvent1, kGenericPerfEvent2})) { GTEST_SKIP() << "Requested performance counters are not available."; } std::map before, after; // Notice that this test will work even if we taskset it to a single CPU // In this case the threads will run sequentially // Start two threads and measure the number of combined cycles and // instructions measure(2, &before, &after); std::vector Elapsed2Threads{ static_cast(after[kGenericPerfEvent1] - before[kGenericPerfEvent1]), static_cast(after[kGenericPerfEvent2] - before[kGenericPerfEvent2])}; // Start four threads and measure the number of combined cycles and // instructions measure(4, &before, &after); std::vector Elapsed4Threads{ static_cast(after[kGenericPerfEvent1] - before[kGenericPerfEvent1]), static_cast(after[kGenericPerfEvent2] - before[kGenericPerfEvent2])}; // The following expectations fail (at least on a beefy workstation with lots // of cpus) - it seems that in some circumstances the runtime of 4 threads // can even be better than with 2. // So instead of expecting 4 threads to be slower, let's just make sure they // do not differ too much in general (one is not more than 10x than the // other). EXPECT_THAT(Elapsed4Threads[0] / Elapsed2Threads[0], AllOf(Gt(0.1), Lt(10))); EXPECT_THAT(Elapsed4Threads[1] / Elapsed2Threads[1], AllOf(Gt(0.1), Lt(10))); } TEST(PerfCountersTest, HardwareLimits) { // The test works (i.e. causes read to fail) for the assumptions // about hardware capabilities (i.e. small number (3-4) hardware // counters) at this date, // the same as previous test ReopenExistingCounters. if (!PerfCounters::kSupported) { GTEST_SKIP() << "Test skipped because libpfm is not supported."; } EXPECT_TRUE(PerfCounters::Initialize()); // Taken from `perf list`, but focusses only on those HW events that actually // were reported when running `sudo perf stat -a sleep 10`, intersected over // several platforms. All HW events listed in the first command not reported // in the second seem to not work. This is sad as we don't really get to test // the grouping here (groups can contain up to 6 members)... std::vector counter_names{ "cycles", // leader "instructions", // "branch-misses", // }; // In the off-chance that some of these values are not supported, // we filter them out so the test will complete without failure // albeit it might not actually test the grouping on that platform std::vector valid_names; for (const std::string& name : counter_names) { if (PerfCounters::IsCounterSupported(name)) { valid_names.push_back(name); } } PerfCountersMeasurement counter(valid_names); std::vector> measurements; counter.Start(); EXPECT_TRUE(counter.Stop(measurements)); } } // namespace