#include <random>
#include <thread>
#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";
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_EQ(PerfCounters::Create({kGenericPerfEvent1}).num_counters(), 1);
}
TEST(PerfCountersTest, NegativeTest) {
if (!PerfCounters::kSupported) {
EXPECT_FALSE(PerfCounters::Initialize());
return;
}
EXPECT_TRUE(PerfCounters::Initialize());
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);
{
auto counter =
PerfCounters::Create({kGenericPerfEvent2, "", kGenericPerfEvent1});
EXPECT_EQ(counter.num_counters(), 2);
EXPECT_EQ(counter.names(), std::vector<std::string>(
{kGenericPerfEvent2, kGenericPerfEvent1}));
}
{
auto counter = PerfCounters::Create(
{kGenericPerfEvent2, "not a counter name", kGenericPerfEvent1});
EXPECT_EQ(counter.num_counters(), 2);
EXPECT_EQ(counter.names(), std::vector<std::string>(
{kGenericPerfEvent2, kGenericPerfEvent1}));
}
{
EXPECT_EQ(PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2})
.num_counters(),
2);
}
{
auto counter = PerfCounters::Create(
{kGenericPerfEvent1, kGenericPerfEvent2, "bad event name"});
EXPECT_EQ(counter.num_counters(), 2);
EXPECT_EQ(counter.names(), std::vector<std::string>(
{kGenericPerfEvent1, kGenericPerfEvent2}));
}
}
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_EQ(counters.num_counters(), 1);
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_EQ(counters.num_counters(), 2);
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) {
if (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.\n";
}
EXPECT_TRUE(PerfCounters::Initialize());
std::vector<std::string> kMetrics({kGenericPerfEvent1});
std::vector<PerfCounters> counters(2);
for (auto& counter : counters) {
counter = PerfCounters::Create(kMetrics);
}
PerfCounterValues values(1);
EXPECT_TRUE(counters[0].Snapshot(&values));
EXPECT_TRUE(counters[1].Snapshot(&values));
}
TEST(PerfCountersTest, CreateExistingMeasurements) {
if (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.\n";
}
EXPECT_TRUE(PerfCounters::Initialize());
const int kMaxCounters = 10;
const int kMinValidCounters = 2;
const std::vector<std::string> kMetrics{"cycles"};
std::vector<std::unique_ptr<PerfCountersMeasurement>>
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<std::pair<std::string, double>> measurements;
size_t max_counters = kMaxCounters;
for (size_t i = 0; i < kMaxCounters; ++i) {
auto& counter(*perf_counter_measurements[i]);
EXPECT_EQ(counter.num_counters(), 1);
if (!counter.Start()) {
max_counters = i;
break;
};
}
ASSERT_GE(max_counters, kMinValidCounters);
for (size_t i = 0; i < max_counters; ++i) {
auto& counter(*perf_counter_measurements[i]);
EXPECT_TRUE(counter.Stop(measurements) || (i >= kMinValidCounters));
}
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));
}
}
BENCHMARK_DONT_OPTIMIZE size_t do_work() {
static std::mt19937 rd{std::random_device{}()};
static std::uniform_int_distribution<size_t> 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, PerfCounterValues* before,
PerfCounterValues* after) {
BM_CHECK_NE(before, nullptr);
BM_CHECK_NE(after, nullptr);
std::vector<std::thread> threads(threadcount);
auto work = [&]() { BM_CHECK(do_work() > 1000); };
auto counters =
PerfCounters::Create({kGenericPerfEvent1, kGenericPerfEvent2});
for (auto& t : threads) t = std::thread(work);
counters.Snapshot(before);
for (auto& t : threads) t.join();
counters.Snapshot(after);
}
TEST(PerfCountersTest, MultiThreaded) {
if (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.";
}
EXPECT_TRUE(PerfCounters::Initialize());
PerfCounterValues before(2);
PerfCounterValues after(2);
measure(2, &before, &after);
std::vector<double> Elapsed2Threads{
static_cast<double>(after[0] - before[0]),
static_cast<double>(after[1] - before[1])};
measure(4, &before, &after);
std::vector<double> Elapsed4Threads{
static_cast<double>(after[0] - before[0]),
static_cast<double>(after[1] - before[1])};
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) {
if (!PerfCounters::kSupported) {
GTEST_SKIP() << "Test skipped because libpfm is not supported.\n";
}
EXPECT_TRUE(PerfCounters::Initialize());
std::vector<std::string> counter_names{
"cycles",
"instructions",
"branch-misses",
};
std::vector<std::string> valid_names;
for (const std::string& name : counter_names) {
if (PerfCounters::IsCounterSupported(name)) {
valid_names.push_back(name);
}
}
PerfCountersMeasurement counter(valid_names);
std::vector<std::pair<std::string, double>> measurements;
counter.Start();
EXPECT_TRUE(counter.Stop(measurements));
}
}