1// Copyright 2021 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "perf_counters.h"
16
17#include <cstring>
18#include <vector>
19
20#if defined HAVE_LIBPFM
21#include "perfmon/pfmlib.h"
22#include "perfmon/pfmlib_perf_event.h"
23#endif
24
25namespace benchmark {
26namespace internal {
27
28constexpr size_t PerfCounterValues::kMaxCounters;
29
30#if defined HAVE_LIBPFM
31const bool PerfCounters::kSupported = true;
32
33bool PerfCounters::Initialize() { return pfm_initialize() == PFM_SUCCESS; }
34
35PerfCounters PerfCounters::Create(
36 const std::vector<std::string>& counter_names) {
37 if (counter_names.empty()) {
38 return NoCounters();
39 }
40 if (counter_names.size() > PerfCounterValues::kMaxCounters) {
41 GetErrorLogInstance()
42 << counter_names.size()
43 << " counters were requested. The minimum is 1, the maximum is "
44 << PerfCounterValues::kMaxCounters << "\n";
45 return NoCounters();
46 }
47 std::vector<int> counter_ids(counter_names.size());
48
49 const int mode = PFM_PLM3; // user mode only
50 for (size_t i = 0; i < counter_names.size(); ++i) {
51 const bool is_first = i == 0;
52 struct perf_event_attr attr {};
53 attr.size = sizeof(attr);
54 const int group_id = !is_first ? counter_ids[0] : -1;
55 const auto& name = counter_names[i];
56 if (name.empty()) {
57 GetErrorLogInstance() << "A counter name was the empty string\n";
58 return NoCounters();
59 }
60 pfm_perf_encode_arg_t arg{};
61 arg.attr = &attr;
62
63 const int pfm_get =
64 pfm_get_os_event_encoding(name.c_str(), mode, PFM_OS_PERF_EVENT, &arg);
65 if (pfm_get != PFM_SUCCESS) {
66 GetErrorLogInstance() << "Unknown counter name: " << name << "\n";
67 return NoCounters();
68 }
69 attr.disabled = is_first;
70 // Note: the man page for perf_event_create suggests inerit = true and
71 // read_format = PERF_FORMAT_GROUP don't work together, but that's not the
72 // case.
73 attr.inherit = true;
74 attr.pinned = is_first;
75 attr.exclude_kernel = true;
76 attr.exclude_user = false;
77 attr.exclude_hv = true;
78 // Read all counters in one read.
79 attr.read_format = PERF_FORMAT_GROUP;
80
81 int id = -1;
82 static constexpr size_t kNrOfSyscallRetries = 5;
83 // Retry syscall as it was interrupted often (b/64774091).
84 for (size_t num_retries = 0; num_retries < kNrOfSyscallRetries;
85 ++num_retries) {
86 id = perf_event_open(&attr, 0, -1, group_id, 0);
87 if (id >= 0 || errno != EINTR) {
88 break;
89 }
90 }
91 if (id < 0) {
92 GetErrorLogInstance()
93 << "Failed to get a file descriptor for " << name << "\n";
94 return NoCounters();
95 }
96
97 counter_ids[i] = id;
98 }
99 if (ioctl(counter_ids[0], PERF_EVENT_IOC_ENABLE) != 0) {
100 GetErrorLogInstance() << "Failed to start counters\n";
101 return NoCounters();
102 }
103
104 return PerfCounters(counter_names, std::move(counter_ids));
105}
106
107PerfCounters::~PerfCounters() {
108 if (counter_ids_.empty()) {
109 return;
110 }
111 ioctl(counter_ids_[0], PERF_EVENT_IOC_DISABLE);
112 for (int fd : counter_ids_) {
113 close(fd);
114 }
115}
116#else // defined HAVE_LIBPFM
117const bool PerfCounters::kSupported = false;
118
119bool PerfCounters::Initialize() { return false; }
120
121PerfCounters PerfCounters::Create(
122 const std::vector<std::string>& counter_names) {
123 if (!counter_names.empty()) {
124 GetErrorLogInstance() << "Performance counters not supported.";
125 }
126 return NoCounters();
127}
128
129PerfCounters::~PerfCounters() = default;
130#endif // defined HAVE_LIBPFM
131} // namespace internal
132} // namespace benchmark
133