/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "PowerCounters.h"
#include "nsXULAppAPI.h"
#include "mozilla/Maybe.h"
#include "mozilla/Logging.h"
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cerrno>
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
#include <linux/perf_event.h>
// From the kernel rapl_scale() function:
//
// > users must then scale back: count * 1/(1e9*2^32) to get Joules
#define PERF_EVENT_SCALE_NANOJOULES 2.3283064365386962890625e-1
#define SCALE_NANOJOULES_TO_PICOWATTHOUR 3.6
#define SYSFS_PERF_POWER_TYPE_PATH
"/sys/bus/event_source/devices/power/type"
static mozilla::LazyLogModule sRaplEventLog(
"profiler.rapl");
#define RAPL_LOG(...) \
MOZ_LOG(sRaplEventLog, mozilla::LogLevel::Debug, (__VA_ARGS__));
enum class RaplEventType : uint64_t {
RAPL_ENERGY_CORES = 0x01,
RAPL_ENERGY_PKG = 0x02,
RAPL_ENERGY_DRAM = 0x03,
RAPL_ENERGY_GPU = 0x04,
RAPL_ENERGY_PSYS = 0x05,
};
struct RaplDomain {
RaplEventType mRaplEventType;
const char* mLabel;
const char* mDescription;
};
constexpr RaplDomain kSupportedRaplDomains[] = {
{RaplEventType::RAPL_ENERGY_CORES,
"Power: CPU cores",
"Consumption of all physical cores"},
{
RaplEventType::RAPL_ENERGY_PKG,
"Power: CPU package",
"Consumption of the whole processor package",
},
{
RaplEventType::RAPL_ENERGY_DRAM,
"Power: DRAM",
"Consumption of the dram domain",
},
{
RaplEventType::RAPL_ENERGY_GPU,
"Power: iGPU",
"Consumption of the builtin-gpu domain",
},
{
RaplEventType::RAPL_ENERGY_PSYS,
"Power: System",
"Consumption of the builtin-psys domain",
}};
static std::string GetSysfsFileID(RaplEventType aEventType) {
switch (aEventType) {
case RaplEventType::RAPL_ENERGY_CORES:
return "cores";
case RaplEventType::RAPL_ENERGY_PKG:
return "pkg";
case RaplEventType::RAPL_ENERGY_DRAM:
return "ram";
case RaplEventType::RAPL_ENERGY_GPU:
return "gpu";
case RaplEventType::RAPL_ENERGY_PSYS:
return "psys";
}
return "";
}
static double GetRaplPerfEventScale(RaplEventType aEventType) {
const std::string sysfsFileName =
"/sys/bus/event_source/devices/power/events/energy-" +
GetSysfsFileID(aEventType) +
".scale";
std::ifstream sysfsFile(sysfsFileName);
if (!sysfsFile) {
return PERF_EVENT_SCALE_NANOJOULES;
}
double scale;
if (sysfsFile >> scale) {
RAPL_LOG(
"Read scale from %s: %.22e", sysfsFileName.c_str(), scale);
return scale * 1e9;
}
return PERF_EVENT_SCALE_NANOJOULES;
}
static uint64_t GetRaplPerfEventConfig(RaplEventType aEventType) {
const std::string sysfsFileName =
"/sys/bus/event_source/devices/power/events/energy-" +
GetSysfsFileID(aEventType);
std::ifstream sysfsFile(sysfsFileName);
if (!sysfsFile) {
return static_cast<uint64_t>(aEventType);
}
char buffer[7] = {};
const std::string key =
"event=";
if (!sysfsFile.get(buffer,
static_cast<std::streamsize>(key.length()) + 1) ||
key != buffer) {
return static_cast<uint64_t>(aEventType);
}
uint64_t config;
if (sysfsFile >> std::hex >> config) {
RAPL_LOG(
"Read config from %s: 0x%" PRIx64, sysfsFileName.c_str(), config);
return config;
}
return static_cast<uint64_t>(aEventType);
}
class RaplProfilerCount final :
public BaseProfilerCount {
public:
explicit RaplProfilerCount(
int aPerfEventType,
const RaplEventType& aPerfEventConfig,
const char* aLabel,
const char* aDescription)
: BaseProfilerCount(aLabel,
"power", aDescription),
mLastResult(0),
mPerfEventFd(-1) {
RAPL_LOG(
"Creating RAPL Event for type: %s", mLabel);
// Optimize for ease of use and do not set an excludes value. This
// ensures we do not require PERF_PMU_CAP_NO_EXCLUDE.
struct perf_event_attr attr = {0};
memset(&attr, 0,
sizeof(attr));
attr.type = aPerfEventType;
attr.size =
sizeof(
struct perf_event_attr);
attr.config = GetRaplPerfEventConfig(aPerfEventConfig);
attr.sample_period = 0;
attr.sample_type = PERF_SAMPLE_IDENTIFIER;
attr.inherit = 1;
RAPL_LOG(
"Config for event %s: 0x%llx", mLabel, attr.config);
mEventScale = GetRaplPerfEventScale(aPerfEventConfig);
RAPL_LOG(
"Scale for event %s: %.22e", mLabel, mEventScale);
long fd = syscall(__NR_perf_event_open, &attr, -1, 0, -1, 0);
if (fd < 0) {
RAPL_LOG(
"Event descriptor creation failed for event: %s", mLabel);
mPerfEventFd = -1;
return;
}
RAPL_LOG(
"Created descriptor for event: %s", mLabel)
mPerfEventFd =
static_cast<
int>(fd);
}
~RaplProfilerCount() {
if (ValidPerfEventFd()) {
ioctl(mPerfEventFd, PERF_EVENT_IOC_DISABLE, 0);
close(mPerfEventFd);
}
}
RaplProfilerCount(
const RaplProfilerCount&) =
delete;
RaplProfilerCount&
operator=(
const RaplProfilerCount&) =
delete;
CountSample Sample() override {
CountSample result = {
.count = 0,
.number = 0,
.isSampleNew =
false,
};
mozilla::Maybe<uint64_t> raplEventResult = ReadEventFd();
if (raplEventResult.isNothing()) {
return result;
}
// We need to return picowatthour to be consistent with the Windows
// EMI API. As a result, the scale calculation should:
//
// - Convert the returned value to nanojoules
// - Convert nanojoules to picowatthour
double nanojoules =
static_cast<
double>(raplEventResult.value()) * mEventScale;
double picowatthours = nanojoules / SCALE_NANOJOULES_TO_PICOWATTHOUR;
RAPL_LOG(
"Sample %s { count: %lu, last-result: %lu } = %lfJ", mLabel,
raplEventResult.value(), mLastResult, nanojoules * 1e-9);
result.count =
static_cast<int64_t>(picowatthours);
// If the tick count is the same as the returned value or if this is the
// first sample, treat this sample as a duplicate.
result.isSampleNew =
(mLastResult != 0 && mLastResult != raplEventResult.value() &&
result.count >= 0);
mLastResult = raplEventResult.value();
return result;
}
bool ValidPerfEventFd() {
return mPerfEventFd >= 0; }
private:
mozilla::Maybe<uint64_t> ReadEventFd() {
MOZ_ASSERT(ValidPerfEventFd());
uint64_t eventResult;
ssize_t readBytes = read(mPerfEventFd, &eventResult,
sizeof(uint64_t));
if (readBytes !=
sizeof(uint64_t)) {
RAPL_LOG(
"Invalid RAPL event read size: %ld", readBytes);
return mozilla::Nothing();
}
return mozilla::Some(eventResult);
}
uint64_t mLastResult;
int mPerfEventFd;
double mEventScale;
};
static int GetRaplPerfEventType() {
FILE* fp = fopen(SYSFS_PERF_POWER_TYPE_PATH,
"r");
if (!fp) {
RAPL_LOG(
"Open of " SYSFS_PERF_POWER_TYPE_PATH
" failed");
return -1;
}
int readTypeValue = -1;
if (fscanf(fp,
"%d", &readTypeValue) != 1) {
RAPL_LOG(
"Read of " SYSFS_PERF_POWER_TYPE_PATH
" failed");
}
fclose(fp);
return readTypeValue;
}
PowerCounters::PowerCounters() {
if (!XRE_IsParentProcess()) {
// Energy meters are global, so only sample them on the parent.
return;
}
// Get the value perf_event_attr.type should be set to for RAPL
// perf events.
int perfEventType = GetRaplPerfEventType();
if (perfEventType < 0) {
RAPL_LOG(
"Failed to find the event type for RAPL perf events.");
return;
}
for (
const auto& raplEventDomain : kSupportedRaplDomains) {
RaplProfilerCount* raplEvent =
new RaplProfilerCount(
perfEventType, raplEventDomain.mRaplEventType, raplEventDomain.mLabel,
raplEventDomain.mDescription);
if (!raplEvent->ValidPerfEventFd() || !mCounters.emplaceBack(raplEvent)) {
delete raplEvent;
}
}
}