Skip to content
81 changes: 45 additions & 36 deletions test/perf_counters_gtest.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include <mutex>
#include <random>
#include <set>
#include <string>
#include <thread>
#include <vector>

#include "../src/perf_counters.h"
#include "gmock/gmock.h"
Expand All @@ -24,34 +27,44 @@ namespace {
const char kGenericPerfEvent1[] = "CYCLES";
const char kGenericPerfEvent2[] = "INSTRUCTIONS";

TEST(PerfCountersTest, Init) {
EXPECT_EQ(PerfCounters::Initialize(), PerfCounters::kSupported);
std::set<std::string> UniqueCounterNames(const PerfCounters& counters) {
return {counters.names().begin(), counters.names().end()};
}

// Generic events will have as many counters as there are CPU PMUs, and each
// will have the same name. In order to make these tests independent of the
// number of CPU PMUs in the system, we uniquify the counter names before
// testing them.
static std::set<std::string> UniqueCounterNames(const PerfCounters& pc) {
std::set<std::string> names{pc.names().begin(), pc.names().end()};
return names;
bool HasRequiredPerfCounters(const std::vector<std::string>& 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 (!PerfCounters::kSupported) {
GTEST_SKIP() << "Performance counters not supported.\n";
if (!HasRequiredPerfCounters({kGenericPerfEvent1})) {
GTEST_SKIP() << "Requested performance counters are not available.";
}
EXPECT_TRUE(PerfCounters::Initialize());
EXPECT_EQ(
UniqueCounterNames(PerfCounters::Create({kGenericPerfEvent1})).size(), 1);
auto counter = PerfCounters::Create({kGenericPerfEvent1});
EXPECT_EQ(UniqueCounterNames(counter).size(), 1);
}

TEST(PerfCountersTest, NegativeTest) {
if (!PerfCounters::kSupported) {
EXPECT_FALSE(PerfCounters::Initialize());
return;
}
EXPECT_TRUE(PerfCounters::Initialize());
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
Expand Down Expand Up @@ -110,10 +123,9 @@ static std::map<std::string, uint64_t> SnapshotAndCombine(
}

TEST(PerfCountersTest, Read1Counter) {
if (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.\n";
if (!HasRequiredPerfCounters({kGenericPerfEvent1})) {
GTEST_SKIP() << "Requested performance counters are not available.";
}
EXPECT_TRUE(PerfCounters::Initialize());
auto counters = PerfCounters::Create({kGenericPerfEvent1});
auto values1 = SnapshotAndCombine(counters);
EXPECT_EQ(values1.size(), 1);
Expand All @@ -125,16 +137,14 @@ TEST(PerfCountersTest, Read1Counter) {
}

TEST(PerfCountersTest, Read1CounterEachCPU) {
if (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.\n";
if (!HasRequiredPerfCounters({kGenericPerfEvent1})) {
GTEST_SKIP() << "Requested performance counters are not available.";
}
#ifdef __linux__
EXPECT_TRUE(PerfCounters::Initialize());

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.\n";
GTEST_SKIP() << "Could not save CPU affinity mask.";
}

for (size_t cpu = 0; cpu != CPU_SETSIZE; ++cpu) {
Expand All @@ -157,15 +167,14 @@ TEST(PerfCountersTest, Read1CounterEachCPU) {

EXPECT_EQ(sched_setaffinity(0, sizeof(saved_set), &saved_set), 0);
#else
GTEST_SKIP() << "Test skipped on non-Linux.\n";
GTEST_SKIP() << "Test skipped on non-Linux.";
#endif
}

TEST(PerfCountersTest, Read2Counters) {
if (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.\n";
if (!HasRequiredPerfCounters({kGenericPerfEvent1, kGenericPerfEvent2})) {
GTEST_SKIP() << "Requested performance counters are not available.";
}
EXPECT_TRUE(PerfCounters::Initialize());
auto counters =
PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2});
auto values1 = SnapshotAndCombine(counters);
Expand All @@ -184,10 +193,9 @@ TEST(PerfCountersTest, Read2Counters) {
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 (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.\n";
if (!HasRequiredPerfCounters({kGenericPerfEvent1})) {
GTEST_SKIP() << "Requested performance counters are not available.";
}
EXPECT_TRUE(PerfCounters::Initialize());
std::vector<std::string> kMetrics({kGenericPerfEvent1});
std::vector<PerfCounters> counters(2);
for (auto& counter : counters) {
Expand All @@ -204,9 +212,8 @@ TEST(PerfCountersTest, CreateExistingMeasurements) {
// counters) at this date,
// the same as previous test ReopenExistingCounters.
if (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.\n";
GTEST_SKIP() << "Test skipped because libpfm is not supported.";
}
EXPECT_TRUE(PerfCounters::Initialize());

// 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
Expand All @@ -218,6 +225,9 @@ TEST(PerfCountersTest, CreateExistingMeasurements) {
// Let's use a ubiquitous counter that is guaranteed to work
// on all platforms
const std::vector<std::string> 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
Expand Down Expand Up @@ -315,10 +325,9 @@ void measure(size_t threadcount, std::map<std::string, uint64_t>* before,
}

TEST(PerfCountersTest, MultiThreaded) {
if (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.";
if (!HasRequiredPerfCounters({kGenericPerfEvent1, kGenericPerfEvent2})) {
GTEST_SKIP() << "Requested performance counters are not available.";
}
EXPECT_TRUE(PerfCounters::Initialize());
std::map<std::string, uint64_t> before, after;

// Notice that this test will work even if we taskset it to a single CPU
Expand Down Expand Up @@ -357,7 +366,7 @@ TEST(PerfCountersTest, HardwareLimits) {
// counters) at this date,
// the same as previous test ReopenExistingCounters.
if (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.\n";
GTEST_SKIP() << "Test skipped because libpfm is not supported.";
}
EXPECT_TRUE(PerfCounters::Initialize());

Expand Down
35 changes: 30 additions & 5 deletions test/perf_counters_test.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#include <cstdarg>
#include <set>
#include <string>
#include <vector>
#undef NDEBUG

#include "../src/commandlineflags.h"
Expand All @@ -15,6 +18,27 @@ BM_DECLARE_string(benchmark_perf_counters);

} // namespace benchmark
namespace {
const char kGenericPerfEvent1[] = "CYCLES";
const char kGenericPerfEvent2[] = "INSTRUCTIONS";

std::set<std::string> UniqueCounterNames(
const benchmark::internal::PerfCounters& counters) {
return {counters.names().begin(), counters.names().end()};
}

bool HasRequiredPerfCounters(const std::vector<std::string>& names) {
if (!benchmark::internal::PerfCounters::kSupported) {
return false;
}
auto counters = benchmark::internal::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;
}

void BM_Simple(benchmark::State& state) {
for (auto _ : state) {
Expand Down Expand Up @@ -64,18 +88,18 @@ BENCHMARK(BM_WithPauseResume);
ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_WithPauseResume\",$"}});

static void CheckSimple(Results const& e) {
CHECK_COUNTER_VALUE(e, double, "CYCLES", GT, 0);
CHECK_COUNTER_VALUE(e, double, kGenericPerfEvent1, GT, 0);
}

double withoutPauseResumeInstrCount = 0.0;
double withPauseResumeInstrCount = 0.0;

void SaveInstrCountWithoutResume(Results const& e) {
withoutPauseResumeInstrCount = e.GetAs<double>("INSTRUCTIONS");
withoutPauseResumeInstrCount = e.GetAs<double>(kGenericPerfEvent2);
}

void SaveInstrCountWithResume(Results const& e) {
withPauseResumeInstrCount = e.GetAs<double>("INSTRUCTIONS");
withPauseResumeInstrCount = e.GetAs<double>(kGenericPerfEvent2);
}

CHECK_BENCHMARK_RESULTS("BM_Simple", &CheckSimple);
Expand All @@ -85,10 +109,11 @@ CHECK_BENCHMARK_RESULTS("BM_WithPauseResume", &SaveInstrCountWithResume);

int main(int argc, char* argv[]) {
benchmark::MaybeReenterWithoutASLR(argc, argv);
if (!benchmark::internal::PerfCounters::kSupported) {
if (!HasRequiredPerfCounters({kGenericPerfEvent1, kGenericPerfEvent2})) {
return 0;
}
benchmark::FLAGS_benchmark_perf_counters = "CYCLES,INSTRUCTIONS";
benchmark::FLAGS_benchmark_perf_counters =
std::string(kGenericPerfEvent1) + "," + kGenericPerfEvent2;
benchmark::internal::PerfCounters::Initialize();
RunOutputTests(argc, argv);

Expand Down
Loading