Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically detect PMU capabilities through libpfm #1380

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 129 additions & 20 deletions src/perf_counters.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,132 @@
#include <vector>

#if defined HAVE_LIBPFM
#include <unordered_map>

#include "perfmon/pfmlib.h"
#include "perfmon/pfmlib_perf_event.h"
#endif

namespace benchmark {
namespace internal {

constexpr size_t PerfCounterValues::kMaxCounters;

#if defined HAVE_LIBPFM

class SinglePMURegistry {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add some comments - purpose of the class, etc?

public:
~SinglePMURegistry() = default;
SinglePMURegistry(SinglePMURegistry&&) = default;
SinglePMURegistry(const SinglePMURegistry&) = delete;
SinglePMURegistry& operator=(SinglePMURegistry&&) noexcept;
SinglePMURegistry& operator=(const SinglePMURegistry&) = delete;

SinglePMURegistry(pfm_pmu_t pmu_id)
: pmu_id_(pmu_id), available_counters_(0), available_fixed_counters_(0) {
pfm_pmu_info_t pmu_info{};
const auto pfm_pmu = pfm_get_pmu_info(pmu_id, &pmu_info);

if (pfm_pmu != PFM_SUCCESS) {
GetErrorLogInstance() << "Unknown pmu: " << pmu_id << "\n";
return;
}

name_ = pmu_info.name;
desc_ = pmu_info.desc;
available_counters_ = pmu_info.num_cntrs;
available_fixed_counters_ = pmu_info.num_fixed_cntrs;

BM_VLOG(1) << "PMU: " << pmu_id << " " << name_ << " " << desc_ << "\n";
BM_VLOG(1) << " counters: " << available_counters_
<< " fixed: " << available_fixed_counters_ << "\n";
}

const char* name() const { return name_; }

bool AddCounter(int event_id) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add tests.

pfm_event_info_t info{};
const auto pfm_event_info =
pfm_get_event_info(event_id, PFM_OS_PERF_EVENT, &info);

if (pfm_event_info != PFM_SUCCESS) {
GetErrorLogInstance() << "Unknown event id: " << event_id << "\n";
return false;
}

assert(info.pmu == pmu_id_);

if (counter_ids_.find(event_id) != counter_ids_.end()) return true;

assert(std::numeric_limits<int>::max() > counter_ids_.size());
if (static_cast<int>(counter_ids_.size()) >= available_counters_ - 1) {
GetErrorLogInstance() << "Maximal number of counters for PMU " << name_
<< " (" << available_counters_ << ") reached.\n";
return false;
}

counter_ids_.emplace(event_id, info.code);

BM_VLOG(2) << "Registered counter: " << event_id << " (" << info.name
<< " - " << info.desc << ") in pmu " << name_ << " ("
<< counter_ids_.size() << "/" << available_counters_ << "\n";

return true;
}

private:
pfm_pmu_t pmu_id_;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can you set pmu_id_ to an invalid value here? Easier to avoid uninitialized values (even if you set it later in the ctor, setting it at decl is easier to maintain)

or could it be const?

const char* name_;
const char* desc_;
std::unordered_map<int, uint64_t> counter_ids_;
damageboy marked this conversation as resolved.
Show resolved Hide resolved
std::unordered_map<int, uint64_t> fixed_counter_ids_;
int available_counters_;
int available_fixed_counters_;
};

class PMURegistry {
public:
~PMURegistry() = default;
PMURegistry(PMURegistry&&) = default;
PMURegistry(const PMURegistry&) = delete;
PMURegistry& operator=(PMURegistry&&) noexcept;
PMURegistry& operator=(const PMURegistry&) = delete;
PMURegistry() {}

bool EnlistCounter(const std::string& name,
damageboy marked this conversation as resolved.
Show resolved Hide resolved
struct perf_event_attr& attr_base) {
attr_base.size = sizeof(attr_base);
pfm_perf_encode_arg_t encoding{};
encoding.attr = &attr_base;

const auto pfm_get = pfm_get_os_event_encoding(
name.c_str(), PFM_PLM3, PFM_OS_PERF_EVENT, &encoding);
if (pfm_get != PFM_SUCCESS) {
GetErrorLogInstance() << "Unknown counter name: " << name << "\n";
return false;
damageboy marked this conversation as resolved.
Show resolved Hide resolved
}

pfm_event_info_t info{};
const auto pfm_info =
pfm_get_event_info(encoding.idx, PFM_OS_PERF_EVENT, &info);
if (pfm_info != PFM_SUCCESS) {
GetErrorLogInstance()
<< "Unknown counter idx: " << encoding.idx << "(" << name << ")\n";
return false;
}

// Spin-up a new per-PMU sub-registry if needed
if (pmu_registry_.find(info.pmu) == pmu_registry_.end()) {
pmu_registry_.emplace(info.pmu, SinglePMURegistry(info.pmu));
}

auto& single_pmu = pmu_registry_.find(info.pmu)->second;

return single_pmu.AddCounter(info.idx);
}

private:
std::unordered_map<pfm_pmu_t, SinglePMURegistry> pmu_registry_;
};

const bool PerfCounters::kSupported = true;

bool PerfCounters::Initialize() { return pfm_initialize() == PFM_SUCCESS; }
Expand All @@ -38,35 +154,28 @@ PerfCounters PerfCounters::Create(
if (counter_names.empty()) {
return NoCounters();
}
if (counter_names.size() > PerfCounterValues::kMaxCounters) {
GetErrorLogInstance()
<< counter_names.size()
<< " counters were requested. The minimum is 1, the maximum is "
<< PerfCounterValues::kMaxCounters << "\n";
return NoCounters();
}

std::vector<int> counter_ids(counter_names.size());
PMURegistry registry{};

const int mode = PFM_PLM3; // user mode only
for (size_t i = 0; i < counter_names.size(); ++i) {
const bool is_first = i == 0;
struct perf_event_attr attr {};
attr.size = sizeof(attr);
const int group_id = !is_first ? counter_ids[0] : -1;
const auto& name = counter_names[i];
if (name.empty()) {
GetErrorLogInstance() << "A counter name was the empty string\n";
return NoCounters();
}
pfm_perf_encode_arg_t arg{};
arg.attr = &attr;

const int pfm_get =
pfm_get_os_event_encoding(name.c_str(), mode, PFM_OS_PERF_EVENT, &arg);
if (pfm_get != PFM_SUCCESS) {
GetErrorLogInstance() << "Unknown counter name: " << name << "\n";
struct perf_event_attr attr {};
auto ok = registry.EnlistCounter(name, attr);

if (!ok) {
GetErrorLogInstance() << "Failed to register counter: " << name << "\n";
return NoCounters();
}

const bool is_first = i == 0;
const int group_id = !is_first ? counter_ids[0] : -1;

attr.disabled = is_first;
// Note: the man page for perf_event_create suggests inerit = true and
// read_format = PERF_FORMAT_GROUP don't work together, but that's not the
Expand Down
7 changes: 3 additions & 4 deletions src/perf_counters.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,11 @@ namespace internal {
class PerfCounterValues {
public:
explicit PerfCounterValues(size_t nr_counters) : nr_counters_(nr_counters) {
BM_CHECK_LE(nr_counters_, kMaxCounters);
BM_CHECK_LE(nr_counters_, kMaxPreAllocatedCounters);
}

uint64_t operator[](size_t pos) const { return values_[kPadding + pos]; }

static constexpr size_t kMaxCounters = 3;
static constexpr size_t kMaxPreAllocatedCounters = 63;

private:
friend class PerfCounters;
Expand All @@ -67,7 +66,7 @@ class PerfCounterValues {
}

static constexpr size_t kPadding = 1;
std::array<uint64_t, kPadding + kMaxCounters> values_;
std::array<uint64_t, kPadding + kMaxPreAllocatedCounters> values_;
const size_t nr_counters_;
};

Expand Down