summaryrefslogtreecommitdiffstats
path: root/tools/profiler/core/PowerCounters-linux.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /tools/profiler/core/PowerCounters-linux.cpp
parentInitial commit. (diff)
downloadthunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz
thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.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.cpp287
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() {}