#include #include "../src/perf_counters.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; namespace { const char kGenericPerfEvent1[] = "CYCLES"; const char kGenericPerfEvent2[] = "BRANCHES"; const char kGenericPerfEvent3[] = "INSTRUCTIONS"; TEST(PerfCountersTest, Init) { EXPECT_EQ(PerfCounters::Initialize(), PerfCounters::kSupported); } TEST(PerfCountersTest, OneCounter) { if (!PerfCounters::kSupported) { GTEST_SKIP() << "Performance counters not supported.\n"; } EXPECT_TRUE(PerfCounters::Initialize()); EXPECT_TRUE(PerfCounters::Create({kGenericPerfEvent1}).IsValid()); } TEST(PerfCountersTest, NegativeTest) { if (!PerfCounters::kSupported) { EXPECT_FALSE(PerfCounters::Initialize()); return; } EXPECT_TRUE(PerfCounters::Initialize()); EXPECT_FALSE(PerfCounters::Create({}).IsValid()); EXPECT_FALSE(PerfCounters::Create({""}).IsValid()); EXPECT_FALSE(PerfCounters::Create({"not a counter name"}).IsValid()); { EXPECT_TRUE(PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2, kGenericPerfEvent3}) .IsValid()); } EXPECT_FALSE( PerfCounters::Create({kGenericPerfEvent2, "", kGenericPerfEvent1}) .IsValid()); EXPECT_FALSE(PerfCounters::Create({kGenericPerfEvent3, "not a counter name", kGenericPerfEvent1}) .IsValid()); { EXPECT_TRUE(PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2, kGenericPerfEvent3}) .IsValid()); } EXPECT_FALSE( PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2, kGenericPerfEvent3, "MISPREDICTED_BRANCH_RETIRED"}) .IsValid()); } TEST(PerfCountersTest, Read1Counter) { if (!PerfCounters::kSupported) { GTEST_SKIP() << "Test skipped because libpfm is not supported.\n"; } EXPECT_TRUE(PerfCounters::Initialize()); auto counters = PerfCounters::Create({kGenericPerfEvent1}); EXPECT_TRUE(counters.IsValid()); PerfCounterValues values1(1); EXPECT_TRUE(counters.Snapshot(&values1)); EXPECT_GT(values1[0], 0); PerfCounterValues values2(1); EXPECT_TRUE(counters.Snapshot(&values2)); EXPECT_GT(values2[0], 0); EXPECT_GT(values2[0], values1[0]); } TEST(PerfCountersTest, Read2Counters) { if (!PerfCounters::kSupported) { GTEST_SKIP() << "Test skipped because libpfm is not supported.\n"; } EXPECT_TRUE(PerfCounters::Initialize()); auto counters = PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2}); EXPECT_TRUE(counters.IsValid()); PerfCounterValues values1(2); EXPECT_TRUE(counters.Snapshot(&values1)); EXPECT_GT(values1[0], 0); EXPECT_GT(values1[1], 0); PerfCounterValues values2(2); EXPECT_TRUE(counters.Snapshot(&values2)); EXPECT_GT(values2[0], 0); EXPECT_GT(values2[1], 0); } TEST(PerfCountersTest, ReopenExistingCounters) { // 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. if (!PerfCounters::kSupported) { GTEST_SKIP() << "Test skipped because libpfm is not supported.\n"; } EXPECT_TRUE(PerfCounters::Initialize()); std::vector counters; counters.reserve(6); for (int i = 0; i < 6; i++) counters.push_back(PerfCounters::Create({kGenericPerfEvent1})); PerfCounterValues values(1); EXPECT_TRUE(counters[0].Snapshot(&values)); EXPECT_FALSE(counters[4].Snapshot(&values)); EXPECT_FALSE(counters[5].Snapshot(&values)); } TEST(PerfCountersTest, CreateExistingMeasurements) { // 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.\n"; } EXPECT_TRUE(PerfCounters::Initialize()); std::vector perf_counter_measurements; std::vector> measurements; perf_counter_measurements.reserve(10); for (int i = 0; i < 10; i++) perf_counter_measurements.emplace_back( std::vector{kGenericPerfEvent1}); perf_counter_measurements[0].Start(); EXPECT_TRUE(perf_counter_measurements[0].Stop(measurements)); measurements.clear(); perf_counter_measurements[8].Start(); EXPECT_FALSE(perf_counter_measurements[8].Stop(measurements)); measurements.clear(); perf_counter_measurements[9].Start(); EXPECT_FALSE(perf_counter_measurements[9].Stop(measurements)); } size_t do_work() { size_t res = 0; for (size_t i = 0; i < 100000000; ++i) res += i * i; return res; } void measure(size_t threadcount, PerfCounterValues* values1, PerfCounterValues* values2) { BM_CHECK_NE(values1, nullptr); BM_CHECK_NE(values2, nullptr); std::vector threads(threadcount); auto work = [&]() { BM_CHECK(do_work() > 1000); }; // 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, kGenericPerfEvent3}); for (auto& t : threads) t = std::thread(work); counters.Snapshot(values1); for (auto& t : threads) t.join(); counters.Snapshot(values2); } TEST(PerfCountersTest, MultiThreaded) { if (!PerfCounters::kSupported) { GTEST_SKIP() << "Test skipped because libpfm is not supported."; } EXPECT_TRUE(PerfCounters::Initialize()); PerfCounterValues values1(2); PerfCounterValues values2(2); measure(2, &values1, &values2); std::vector D1{static_cast(values2[0] - values1[0]), static_cast(values2[1] - values1[1])}; measure(4, &values1, &values2); std::vector D2{static_cast(values2[0] - values1[0]), static_cast(values2[1] - values1[1])}; // Some extra work will happen on the main thread - like joining the threads // - so the ratio won't be quite 2.0, but very close. EXPECT_GE(D2[0], 1.9 * D1[0]); EXPECT_GE(D2[1], 1.9 * D1[1]); } } // namespace