diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /tools/profiler/core/PowerCounters-linux.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/profiler/core/PowerCounters-linux.cpp')
-rw-r--r-- | tools/profiler/core/PowerCounters-linux.cpp | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/tools/profiler/core/PowerCounters-linux.cpp b/tools/profiler/core/PowerCounters-linux.cpp new file mode 100644 index 0000000000..006cea4867 --- /dev/null +++ b/tools/profiler/core/PowerCounters-linux.cpp @@ -0,0 +1,287 @@ +/* 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, nullptr, nullptr, "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; + } + } +} + +PowerCounters::~PowerCounters() { + for (auto* raplEvent : mCounters) { + delete raplEvent; + } + mCounters.clear(); +} + +void PowerCounters::Sample() {} |