Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / tools / profiler / core / PowerCounters-linux.cpp
blob8e3e6f6163f173be2b3119146f065ba9f4f77643
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "PowerCounters.h"
6 #include "nsXULAppAPI.h"
7 #include "mozilla/Maybe.h"
8 #include "mozilla/Logging.h"
10 #include <sys/syscall.h>
11 #include <sys/ioctl.h>
12 #include <unistd.h>
14 #include <cerrno>
15 #include <cinttypes>
16 #include <cstdio>
17 #include <cstdlib>
18 #include <fstream>
19 #include <string>
21 #include <linux/perf_event.h>
23 // From the kernel rapl_scale() function:
25 // > users must then scale back: count * 1/(1e9*2^32) to get Joules
26 #define PERF_EVENT_SCALE_NANOJOULES 2.3283064365386962890625e-1
27 #define SCALE_NANOJOULES_TO_PICOWATTHOUR 3.6
28 #define SYSFS_PERF_POWER_TYPE_PATH "/sys/bus/event_source/devices/power/type"
30 static mozilla::LazyLogModule sRaplEventLog("profiler.rapl");
31 #define RAPL_LOG(...) \
32 MOZ_LOG(sRaplEventLog, mozilla::LogLevel::Debug, (__VA_ARGS__));
34 enum class RaplEventType : uint64_t {
35 RAPL_ENERGY_CORES = 0x01,
36 RAPL_ENERGY_PKG = 0x02,
37 RAPL_ENERGY_DRAM = 0x03,
38 RAPL_ENERGY_GPU = 0x04,
39 RAPL_ENERGY_PSYS = 0x05,
42 struct RaplDomain {
43 RaplEventType mRaplEventType;
44 const char* mLabel;
45 const char* mDescription;
48 constexpr RaplDomain kSupportedRaplDomains[] = {
49 {RaplEventType::RAPL_ENERGY_CORES, "Power: CPU cores",
50 "Consumption of all physical cores"},
52 RaplEventType::RAPL_ENERGY_PKG,
53 "Power: CPU package",
54 "Consumption of the whole processor package",
57 RaplEventType::RAPL_ENERGY_DRAM,
58 "Power: DRAM",
59 "Consumption of the dram domain",
62 RaplEventType::RAPL_ENERGY_GPU,
63 "Power: iGPU",
64 "Consumption of the builtin-gpu domain",
67 RaplEventType::RAPL_ENERGY_PSYS,
68 "Power: System",
69 "Consumption of the builtin-psys domain",
70 }};
72 static std::string GetSysfsFileID(RaplEventType aEventType) {
73 switch (aEventType) {
74 case RaplEventType::RAPL_ENERGY_CORES:
75 return "cores";
76 case RaplEventType::RAPL_ENERGY_PKG:
77 return "pkg";
78 case RaplEventType::RAPL_ENERGY_DRAM:
79 return "ram";
80 case RaplEventType::RAPL_ENERGY_GPU:
81 return "gpu";
82 case RaplEventType::RAPL_ENERGY_PSYS:
83 return "psys";
86 return "";
89 static double GetRaplPerfEventScale(RaplEventType aEventType) {
90 const std::string sysfsFileName =
91 "/sys/bus/event_source/devices/power/events/energy-" +
92 GetSysfsFileID(aEventType) + ".scale";
93 std::ifstream sysfsFile(sysfsFileName);
95 if (!sysfsFile) {
96 return PERF_EVENT_SCALE_NANOJOULES;
99 double scale;
101 if (sysfsFile >> scale) {
102 RAPL_LOG("Read scale from %s: %.22e", sysfsFileName.c_str(), scale);
103 return scale * 1e9;
106 return PERF_EVENT_SCALE_NANOJOULES;
109 static uint64_t GetRaplPerfEventConfig(RaplEventType aEventType) {
110 const std::string sysfsFileName =
111 "/sys/bus/event_source/devices/power/events/energy-" +
112 GetSysfsFileID(aEventType);
113 std::ifstream sysfsFile(sysfsFileName);
115 if (!sysfsFile) {
116 return static_cast<uint64_t>(aEventType);
119 char buffer[7] = {};
120 const std::string key = "event=";
122 if (!sysfsFile.get(buffer, static_cast<std::streamsize>(key.length()) + 1) ||
123 key != buffer) {
124 return static_cast<uint64_t>(aEventType);
127 uint64_t config;
129 if (sysfsFile >> std::hex >> config) {
130 RAPL_LOG("Read config from %s: 0x%" PRIx64, sysfsFileName.c_str(), config);
131 return config;
134 return static_cast<uint64_t>(aEventType);
137 class RaplProfilerCount final : public BaseProfilerCount {
138 public:
139 explicit RaplProfilerCount(int aPerfEventType,
140 const RaplEventType& aPerfEventConfig,
141 const char* aLabel, const char* aDescription)
142 : BaseProfilerCount(aLabel, "power", aDescription),
143 mLastResult(0),
144 mPerfEventFd(-1) {
145 RAPL_LOG("Creating RAPL Event for type: %s", mLabel);
147 // Optimize for ease of use and do not set an excludes value. This
148 // ensures we do not require PERF_PMU_CAP_NO_EXCLUDE.
149 struct perf_event_attr attr = {0};
150 memset(&attr, 0, sizeof(attr));
151 attr.type = aPerfEventType;
152 attr.size = sizeof(struct perf_event_attr);
153 attr.config = GetRaplPerfEventConfig(aPerfEventConfig);
154 attr.sample_period = 0;
155 attr.sample_type = PERF_SAMPLE_IDENTIFIER;
156 attr.inherit = 1;
158 RAPL_LOG("Config for event %s: 0x%llx", mLabel, attr.config);
160 mEventScale = GetRaplPerfEventScale(aPerfEventConfig);
161 RAPL_LOG("Scale for event %s: %.22e", mLabel, mEventScale);
163 long fd = syscall(__NR_perf_event_open, &attr, -1, 0, -1, 0);
164 if (fd < 0) {
165 RAPL_LOG("Event descriptor creation failed for event: %s", mLabel);
166 mPerfEventFd = -1;
167 return;
170 RAPL_LOG("Created descriptor for event: %s", mLabel)
171 mPerfEventFd = static_cast<int>(fd);
174 ~RaplProfilerCount() {
175 if (ValidPerfEventFd()) {
176 ioctl(mPerfEventFd, PERF_EVENT_IOC_DISABLE, 0);
177 close(mPerfEventFd);
181 RaplProfilerCount(const RaplProfilerCount&) = delete;
182 RaplProfilerCount& operator=(const RaplProfilerCount&) = delete;
184 CountSample Sample() override {
185 CountSample result = {
186 .count = 0,
187 .number = 0,
188 .isSampleNew = false,
190 mozilla::Maybe<uint64_t> raplEventResult = ReadEventFd();
192 if (raplEventResult.isNothing()) {
193 return result;
196 // We need to return picowatthour to be consistent with the Windows
197 // EMI API. As a result, the scale calculation should:
199 // - Convert the returned value to nanojoules
200 // - Convert nanojoules to picowatthour
201 double nanojoules =
202 static_cast<double>(raplEventResult.value()) * mEventScale;
203 double picowatthours = nanojoules / SCALE_NANOJOULES_TO_PICOWATTHOUR;
204 RAPL_LOG("Sample %s { count: %lu, last-result: %lu } = %lfJ", mLabel,
205 raplEventResult.value(), mLastResult, nanojoules * 1e-9);
207 result.count = static_cast<int64_t>(picowatthours);
209 // If the tick count is the same as the returned value or if this is the
210 // first sample, treat this sample as a duplicate.
211 result.isSampleNew =
212 (mLastResult != 0 && mLastResult != raplEventResult.value() &&
213 result.count >= 0);
214 mLastResult = raplEventResult.value();
216 return result;
219 bool ValidPerfEventFd() { return mPerfEventFd >= 0; }
221 private:
222 mozilla::Maybe<uint64_t> ReadEventFd() {
223 MOZ_ASSERT(ValidPerfEventFd());
225 uint64_t eventResult;
226 ssize_t readBytes = read(mPerfEventFd, &eventResult, sizeof(uint64_t));
227 if (readBytes != sizeof(uint64_t)) {
228 RAPL_LOG("Invalid RAPL event read size: %ld", readBytes);
229 return mozilla::Nothing();
232 return mozilla::Some(eventResult);
235 uint64_t mLastResult;
236 int mPerfEventFd;
237 double mEventScale;
240 static int GetRaplPerfEventType() {
241 FILE* fp = fopen(SYSFS_PERF_POWER_TYPE_PATH, "r");
242 if (!fp) {
243 RAPL_LOG("Open of " SYSFS_PERF_POWER_TYPE_PATH " failed");
244 return -1;
247 int readTypeValue = -1;
248 if (fscanf(fp, "%d", &readTypeValue) != 1) {
249 RAPL_LOG("Read of " SYSFS_PERF_POWER_TYPE_PATH " failed");
251 fclose(fp);
253 return readTypeValue;
256 PowerCounters::PowerCounters() {
257 if (!XRE_IsParentProcess()) {
258 // Energy meters are global, so only sample them on the parent.
259 return;
262 // Get the value perf_event_attr.type should be set to for RAPL
263 // perf events.
264 int perfEventType = GetRaplPerfEventType();
265 if (perfEventType < 0) {
266 RAPL_LOG("Failed to find the event type for RAPL perf events.");
267 return;
270 for (const auto& raplEventDomain : kSupportedRaplDomains) {
271 RaplProfilerCount* raplEvent = new RaplProfilerCount(
272 perfEventType, raplEventDomain.mRaplEventType, raplEventDomain.mLabel,
273 raplEventDomain.mDescription);
274 if (!raplEvent->ValidPerfEventFd() || !mCounters.emplaceBack(raplEvent)) {
275 delete raplEvent;