1//===-- PerfHelper.cpp ------------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "PerfHelper.h"
10#include "Error.h"
11#include "llvm/Config/config.h"
12#include "llvm/Support/Errc.h"
13#include "llvm/Support/Error.h"
14#include "llvm/Support/raw_ostream.h"
15#ifdef HAVE_LIBPFM
16#include <perfmon/perf_event.h>
17#include <perfmon/pfmlib.h>
18#include <perfmon/pfmlib_perf_event.h>
19#endif
20
21#include <cassert>
22#include <cstddef>
23#include <errno.h> // for erno
24#include <string.h> // for strerror()
25
26namespace llvm {
27namespace exegesis {
28namespace pfm {
29
30#ifdef HAVE_LIBPFM
31static bool isPfmError(int Code) { return Code != PFM_SUCCESS; }
32#endif
33
34bool pfmInitialize() {
35#ifdef HAVE_LIBPFM
36 return isPfmError(pfm_initialize());
37#else
38 return true;
39#endif
40}
41
42void pfmTerminate() {
43#ifdef HAVE_LIBPFM
44 pfm_terminate();
45#endif
46}
47
48// Performance counters may be unavailable for a number of reasons (such as
49// kernel.perf_event_paranoid restriction or CPU being unknown to libpfm).
50//
51// Dummy event can be specified to skip interaction with real performance
52// counters while still passing control to the generated code snippet.
53const char *const PerfEvent::DummyEventString = "not-really-an-event";
54
55PerfEvent::~PerfEvent() {
56#ifdef HAVE_LIBPFM
57 delete Attr;
58 ;
59#endif
60}
61
62PerfEvent::PerfEvent(PerfEvent &&Other)
63 : EventString(std::move(Other.EventString)),
64 FullQualifiedEventString(std::move(Other.FullQualifiedEventString)),
65 Attr(Other.Attr) {
66 Other.Attr = nullptr;
67}
68
69PerfEvent::PerfEvent(StringRef PfmEventString)
70 : EventString(PfmEventString.str()), Attr(nullptr) {
71 if (PfmEventString != DummyEventString)
72 initRealEvent(PfmEventString);
73 else
74 FullQualifiedEventString = PfmEventString;
75}
76
77void PerfEvent::initRealEvent(StringRef PfmEventString) {
78#ifdef HAVE_LIBPFM
79 char *Fstr = nullptr;
80 pfm_perf_encode_arg_t Arg = {};
81 Attr = new perf_event_attr();
82 Arg.attr = Attr;
83 Arg.fstr = &Fstr;
84 Arg.size = sizeof(pfm_perf_encode_arg_t);
85 const int Result = pfm_get_os_event_encoding(EventString.c_str(), PFM_PLM3,
86 PFM_OS_PERF_EVENT, &Arg);
87 if (isPfmError(Result)) {
88 // We don't know beforehand which counters are available (e.g. 6 uops ports
89 // on Sandybridge but 8 on Haswell) so we report the missing counter without
90 // crashing.
91 errs() << pfm_strerror(Result) << " - cannot create event " << EventString
92 << "\n";
93 }
94 if (Fstr) {
95 FullQualifiedEventString = Fstr;
96 free(Fstr);
97 }
98#endif
99}
100
101StringRef PerfEvent::name() const { return EventString; }
102
103bool PerfEvent::valid() const { return !FullQualifiedEventString.empty(); }
104
105const perf_event_attr *PerfEvent::attribute() const { return Attr; }
106
107StringRef PerfEvent::getPfmEventString() const {
108 return FullQualifiedEventString;
109}
110
111ConfiguredEvent::ConfiguredEvent(PerfEvent &&EventToConfigure)
112 : Event(std::move(EventToConfigure)) {
113 assert(Event.valid());
114}
115
116#ifdef HAVE_LIBPFM
117void ConfiguredEvent::initRealEvent(const pid_t ProcessID, const int GroupFD) {
118 const int CPU = -1;
119 const uint32_t Flags = 0;
120 perf_event_attr AttrCopy = *Event.attribute();
121 AttrCopy.read_format =
122 PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
123 FileDescriptor = perf_event_open(&AttrCopy, ProcessID, CPU, GroupFD, Flags);
124 if (FileDescriptor == -1) {
125 errs() << "Unable to open event. ERRNO: " << strerror(errno)
126 << ". Make sure your kernel allows user "
127 "space perf monitoring.\nYou may want to try:\n$ sudo sh "
128 "-c 'echo -1 > /proc/sys/kernel/perf_event_paranoid'.\n"
129 << "If you are debugging and just want to execute the snippet "
130 "without actually reading performance counters, "
131 "pass --use-dummy-perf-counters command line option.\n";
132 }
133 assert(FileDescriptor != -1 && "Unable to open event");
134}
135
136Expected<SmallVector<int64_t>>
137ConfiguredEvent::readOrError(StringRef /*unused*/) const {
138 int64_t EventInfo[3] = {0, 0, 0};
139 ssize_t ReadSize = ::read(FileDescriptor, &EventInfo, sizeof(EventInfo));
140
141 if (ReadSize != sizeof(EventInfo))
142 return make_error<StringError>("Failed to read event counter",
143 errc::io_error);
144
145 int64_t EventTimeEnabled = EventInfo[1];
146 int64_t EventTimeRunning = EventInfo[2];
147 if (EventTimeEnabled != EventTimeRunning)
148 return make_error<PerfCounterNotFullyEnabled>();
149
150 SmallVector<int64_t, 1> Result;
151 Result.push_back(EventInfo[0]);
152 return Result;
153}
154
155ConfiguredEvent::~ConfiguredEvent() { close(FileDescriptor); }
156#else
157void ConfiguredEvent::initRealEvent(pid_t ProcessID, const int GroupFD) {}
158
159Expected<SmallVector<int64_t>>
160ConfiguredEvent::readOrError(StringRef /*unused*/) const {
161 return make_error<StringError>(Args: "Not implemented",
162 Args: errc::function_not_supported);
163}
164
165ConfiguredEvent::~ConfiguredEvent() = default;
166#endif // HAVE_LIBPFM
167
168CounterGroup::CounterGroup(PerfEvent &&E, std::vector<PerfEvent> &&ValEvents,
169 pid_t ProcessID)
170 : EventCounter(std::move(E)) {
171 IsDummyEvent = EventCounter.isDummyEvent();
172
173 for (auto &&ValEvent : ValEvents)
174 ValidationEventCounters.emplace_back(args: std::move(ValEvent));
175
176 if (!IsDummyEvent)
177 initRealEvent(ProcessID);
178}
179
180#ifdef HAVE_LIBPFM
181void CounterGroup::initRealEvent(pid_t ProcessID) {
182 EventCounter.initRealEvent(ProcessID);
183
184 for (auto &ValCounter : ValidationEventCounters)
185 ValCounter.initRealEvent(ProcessID, getFileDescriptor());
186}
187
188void CounterGroup::start() {
189 if (!IsDummyEvent)
190 ioctl(getFileDescriptor(), PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP);
191}
192
193void CounterGroup::stop() {
194 if (!IsDummyEvent)
195 ioctl(getFileDescriptor(), PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
196}
197
198Expected<SmallVector<int64_t, 4>>
199CounterGroup::readOrError(StringRef FunctionBytes) const {
200 if (!IsDummyEvent)
201 return EventCounter.readOrError(FunctionBytes);
202 else
203 return SmallVector<int64_t, 1>(1, 42);
204}
205
206Expected<SmallVector<int64_t>>
207CounterGroup::readValidationCountersOrError() const {
208 SmallVector<int64_t, 4> Result;
209 for (const auto &ValCounter : ValidationEventCounters) {
210 Expected<SmallVector<int64_t>> ValueOrError =
211 ValCounter.readOrError(StringRef());
212
213 if (!ValueOrError)
214 return ValueOrError.takeError();
215
216 // Reading a validation counter will only return a single value, so it is
217 // safe to only append the first value here. Also assert that this is true.
218 assert(ValueOrError->size() == 1 &&
219 "Validation counters should only return a single value");
220 Result.push_back((*ValueOrError)[0]);
221 }
222 return Result;
223}
224
225int CounterGroup::numValues() const { return 1; }
226#else
227
228void CounterGroup::initRealEvent(pid_t ProcessID) {}
229
230void CounterGroup::start() {}
231
232void CounterGroup::stop() {}
233
234Expected<SmallVector<int64_t, 4>>
235CounterGroup::readOrError(StringRef /*unused*/) const {
236 if (IsDummyEvent) {
237 SmallVector<int64_t, 4> Result;
238 Result.push_back(Elt: 42);
239 return Result;
240 }
241 return make_error<StringError>(Args: "Not implemented", Args: errc::io_error);
242}
243
244Expected<SmallVector<int64_t>>
245CounterGroup::readValidationCountersOrError() const {
246 return SmallVector<int64_t>(0);
247}
248
249int CounterGroup::numValues() const { return 1; }
250
251#endif
252
253} // namespace pfm
254} // namespace exegesis
255} // namespace llvm
256