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>
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,
43 RaplEventType mRaplEventType
;
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
,
54 "Consumption of the whole processor package",
57 RaplEventType::RAPL_ENERGY_DRAM
,
59 "Consumption of the dram domain",
62 RaplEventType::RAPL_ENERGY_GPU
,
64 "Consumption of the builtin-gpu domain",
67 RaplEventType::RAPL_ENERGY_PSYS
,
69 "Consumption of the builtin-psys domain",
72 static std::string
GetSysfsFileID(RaplEventType aEventType
) {
74 case RaplEventType::RAPL_ENERGY_CORES
:
76 case RaplEventType::RAPL_ENERGY_PKG
:
78 case RaplEventType::RAPL_ENERGY_DRAM
:
80 case RaplEventType::RAPL_ENERGY_GPU
:
82 case RaplEventType::RAPL_ENERGY_PSYS
:
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
);
96 return PERF_EVENT_SCALE_NANOJOULES
;
101 if (sysfsFile
>> scale
) {
102 RAPL_LOG("Read scale from %s: %.22e", sysfsFileName
.c_str(), scale
);
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
);
116 return static_cast<uint64_t>(aEventType
);
120 const std::string key
= "event=";
122 if (!sysfsFile
.get(buffer
, static_cast<std::streamsize
>(key
.length()) + 1) ||
124 return static_cast<uint64_t>(aEventType
);
129 if (sysfsFile
>> std::hex
>> config
) {
130 RAPL_LOG("Read config from %s: 0x%" PRIx64
, sysfsFileName
.c_str(), config
);
134 return static_cast<uint64_t>(aEventType
);
137 class RaplProfilerCount final
: public BaseProfilerCount
{
139 explicit RaplProfilerCount(int aPerfEventType
,
140 const RaplEventType
& aPerfEventConfig
,
141 const char* aLabel
, const char* aDescription
)
142 : BaseProfilerCount(aLabel
, "power", aDescription
),
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
;
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);
165 RAPL_LOG("Event descriptor creation failed for event: %s", mLabel
);
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);
181 RaplProfilerCount(const RaplProfilerCount
&) = delete;
182 RaplProfilerCount
& operator=(const RaplProfilerCount
&) = delete;
184 CountSample
Sample() override
{
185 CountSample result
= {
188 .isSampleNew
= false,
190 mozilla::Maybe
<uint64_t> raplEventResult
= ReadEventFd();
192 if (raplEventResult
.isNothing()) {
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
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.
212 (mLastResult
!= 0 && mLastResult
!= raplEventResult
.value() &&
214 mLastResult
= raplEventResult
.value();
219 bool ValidPerfEventFd() { return mPerfEventFd
>= 0; }
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
;
240 static int GetRaplPerfEventType() {
241 FILE* fp
= fopen(SYSFS_PERF_POWER_TYPE_PATH
, "r");
243 RAPL_LOG("Open of " SYSFS_PERF_POWER_TYPE_PATH
" failed");
247 int readTypeValue
= -1;
248 if (fscanf(fp
, "%d", &readTypeValue
) != 1) {
249 RAPL_LOG("Read of " SYSFS_PERF_POWER_TYPE_PATH
" failed");
253 return readTypeValue
;
256 PowerCounters::PowerCounters() {
257 if (!XRE_IsParentProcess()) {
258 // Energy meters are global, so only sample them on the parent.
262 // Get the value perf_event_attr.type should be set to for RAPL
264 int perfEventType
= GetRaplPerfEventType();
265 if (perfEventType
< 0) {
266 RAPL_LOG("Failed to find the event type for RAPL perf events.");
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
)) {