Add EnergyMetricsProviderLinux
EnergyMetricsProviderLinux can only work on platforms with Intel Processor with RAPL interface. Users need to manually set '/proc/sys/kernel/perf_event_paranoid' to 0 with admin, which grants permission to read perf events. Bug: 1385251 Change-Id: I9cb4706bbe6b44723f4d29a5df6a952688d0c066 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4432591 Reviewed-by: Francois Pierre Doray <fdoray@chromium.org> Commit-Queue: Francois Pierre Doray <fdoray@chromium.org> Cr-Commit-Position: refs/heads/main@{#1132472}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
3a81521d66
commit
5b493b3e10
@ -40,32 +40,33 @@ static_library("power_metrics") {
|
||||
libs = [ "Setupapi.lib" ]
|
||||
ldflags = [ "/DELAYLOAD:setupapi.dll" ]
|
||||
}
|
||||
|
||||
if (is_linux) {
|
||||
sources += [
|
||||
"energy_metrics_provider_linux.cc",
|
||||
"energy_metrics_provider_linux.h",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
source_set("unit_tests") {
|
||||
testonly = true
|
||||
|
||||
sources = [ "energy_metrics_provider_unittest.cc" ]
|
||||
|
||||
deps = [
|
||||
":power_metrics",
|
||||
"//testing/gtest",
|
||||
]
|
||||
|
||||
if (is_mac) {
|
||||
sources = [
|
||||
sources += [
|
||||
"energy_impact_mac_unittest.mm",
|
||||
"resource_coalition_mac_unittest.mm",
|
||||
]
|
||||
|
||||
data = [ "test/data/" ]
|
||||
|
||||
deps = [
|
||||
":power_metrics",
|
||||
"//base",
|
||||
"//testing/gtest",
|
||||
]
|
||||
}
|
||||
|
||||
if (is_win) {
|
||||
sources = [ "energy_metrics_provider_win_unittest.cc" ]
|
||||
|
||||
deps = [
|
||||
":power_metrics",
|
||||
"//testing/gtest",
|
||||
]
|
||||
deps += [ "//base" ]
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include "build/build_config.h"
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include "components/power_metrics/energy_metrics_provider_win.h"
|
||||
#elif BUILDFLAG(IS_LINUX)
|
||||
#include "components/power_metrics/energy_metrics_provider_linux.h"
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
namespace power_metrics {
|
||||
@ -18,6 +20,8 @@ EnergyMetricsProvider::~EnergyMetricsProvider() = default;
|
||||
std::unique_ptr<EnergyMetricsProvider> EnergyMetricsProvider::Create() {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
return EnergyMetricsProviderWin::Create();
|
||||
#elif BUILDFLAG(IS_LINUX)
|
||||
return EnergyMetricsProviderLinux::Create();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
@ -25,6 +25,8 @@ class EnergyMetricsProvider {
|
||||
uint64_t gpu_nanojoules;
|
||||
// The absolute energy of the DRAM (only available in server CPUs).
|
||||
uint64_t dram_nanojoules;
|
||||
// The absolute energy of the entire system.
|
||||
uint64_t psys_nanojoules;
|
||||
// The following metrics are emitted by AMD processors.
|
||||
// We don't know what they measure exactly.
|
||||
uint64_t vdd_nanojoules;
|
||||
|
210
components/power_metrics/energy_metrics_provider_linux.cc
Normal file
210
components/power_metrics/energy_metrics_provider_linux.cc
Normal file
@ -0,0 +1,210 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/power_metrics/energy_metrics_provider_linux.h"
|
||||
|
||||
#include <linux/perf_event.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_util.h"
|
||||
|
||||
namespace power_metrics {
|
||||
|
||||
namespace {
|
||||
|
||||
// Existing metrics that can be read via perf event.
|
||||
constexpr std::array<const char*, 5> kMetrics{
|
||||
"energy-pkg", "energy-cores", "energy-gpu", "energy-ram", "energy-psys"};
|
||||
|
||||
bool ReadUint64FromFile(base::FilePath path, uint64_t* output) {
|
||||
std::string buf;
|
||||
if (!base::ReadFileToString(path, &buf)) {
|
||||
return false;
|
||||
}
|
||||
return base::StringToUint64(base::TrimString(buf, "\n", base::TRIM_TRAILING),
|
||||
output);
|
||||
}
|
||||
|
||||
bool ReadHexFromFile(base::FilePath path, uint64_t* output) {
|
||||
std::string buf;
|
||||
if (!base::ReadFileToString(path, &buf)) {
|
||||
return false;
|
||||
}
|
||||
base::ReplaceFirstSubstringAfterOffset(&buf, 0, "event=", "");
|
||||
return base::HexStringToUInt64(
|
||||
base::TrimString(buf, "\n", base::TRIM_TRAILING), output);
|
||||
}
|
||||
|
||||
bool ReadDoubleFromFile(base::FilePath path, double* output) {
|
||||
std::string buf;
|
||||
if (!base::ReadFileToString(path, &buf)) {
|
||||
return false;
|
||||
}
|
||||
return base::StringToDouble(base::TrimString(buf, "\n", base::TRIM_TRAILING),
|
||||
output);
|
||||
}
|
||||
|
||||
// When pid == -1 and cpu >= 0, perf event measures all processes/threads on the
|
||||
// specified CPU. This requires admin or a /proc/sys/kernel/perf_event_paranoid
|
||||
// value of less than 1. Here, we only consider cpu0. See details in
|
||||
// https://man7.org/linux/man-pages/man2/perf_event_open.2.html.
|
||||
base::ScopedFD OpenPerfEvent(perf_event_attr* perf_attr) {
|
||||
base::ScopedFD perf_fd{syscall(__NR_perf_event_open, perf_attr, /*pid=*/-1,
|
||||
/*cpu=*/0, /*group_fd=*/-1,
|
||||
PERF_FLAG_FD_CLOEXEC)};
|
||||
return perf_fd;
|
||||
}
|
||||
|
||||
void SetEnergyMetric(const std::string& metric_type,
|
||||
EnergyMetricsProvider::EnergyMetrics& energy_metrics,
|
||||
uint64_t absolute_energy) {
|
||||
if (metric_type == "energy-pkg") {
|
||||
energy_metrics.package_nanojoules = absolute_energy;
|
||||
} else if (metric_type == "energy-cores") {
|
||||
energy_metrics.cpu_nanojoules = absolute_energy;
|
||||
} else if (metric_type == "energy-gpu") {
|
||||
energy_metrics.gpu_nanojoules = absolute_energy;
|
||||
} else if (metric_type == "energy-ram") {
|
||||
energy_metrics.dram_nanojoules = absolute_energy;
|
||||
} else if (metric_type == "energy-psys") {
|
||||
energy_metrics.psys_nanojoules = absolute_energy;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EnergyMetricsProviderLinux::PowerEvent::PowerEvent(std::string metric_type,
|
||||
double scale,
|
||||
base::ScopedFD fd)
|
||||
: metric_type(metric_type), scale(scale), fd(std::move(fd)) {}
|
||||
|
||||
EnergyMetricsProviderLinux::PowerEvent::~PowerEvent() = default;
|
||||
|
||||
EnergyMetricsProviderLinux::PowerEvent::PowerEvent(PowerEvent&& other) =
|
||||
default;
|
||||
EnergyMetricsProviderLinux::PowerEvent&
|
||||
EnergyMetricsProviderLinux::PowerEvent::operator=(PowerEvent&& other) = default;
|
||||
|
||||
EnergyMetricsProviderLinux::EnergyMetricsProviderLinux() = default;
|
||||
EnergyMetricsProviderLinux::~EnergyMetricsProviderLinux() = default;
|
||||
|
||||
// static
|
||||
std::unique_ptr<EnergyMetricsProviderLinux>
|
||||
EnergyMetricsProviderLinux::Create() {
|
||||
return base::WrapUnique(new EnergyMetricsProviderLinux());
|
||||
}
|
||||
|
||||
absl::optional<EnergyMetricsProvider::EnergyMetrics>
|
||||
EnergyMetricsProviderLinux::CaptureMetrics() {
|
||||
if (!Initialize()) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
EnergyMetrics energy_metrics = {0};
|
||||
for (const auto& event : events_) {
|
||||
uint64_t absolute_energy;
|
||||
if (!base::ReadFromFD(event.fd.get(),
|
||||
reinterpret_cast<char*>(&absolute_energy),
|
||||
sizeof(absolute_energy))) {
|
||||
LOG(ERROR) << "Failed to read absolute energy of " << event.metric_type;
|
||||
continue;
|
||||
}
|
||||
SetEnergyMetric(event.metric_type, energy_metrics,
|
||||
static_cast<uint64_t>(event.scale * absolute_energy));
|
||||
}
|
||||
return energy_metrics;
|
||||
}
|
||||
|
||||
bool EnergyMetricsProviderLinux::Initialize() {
|
||||
if (is_initialized_) {
|
||||
if (events_.empty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
is_initialized_ = true;
|
||||
|
||||
// Check if perf_event_paranoid is set to 0 as required.
|
||||
uint64_t perf_event_paranoid;
|
||||
if (!ReadUint64FromFile(
|
||||
base::FilePath("/proc/sys/kernel/perf_event_paranoid"),
|
||||
&perf_event_paranoid)) {
|
||||
LOG(WARNING) << "Failed to get perf_event_paranoid";
|
||||
return false;
|
||||
}
|
||||
if (perf_event_paranoid) {
|
||||
LOG(WARNING) << "Permission denied for acquiring energy metrics";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since the power Processor Monitor Unit (PMU) is dynamic, we have to get the
|
||||
// type for perf_event_attr from /sys/bus/event_source/devices/power/type.
|
||||
uint64_t attr_type;
|
||||
if (!ReadUint64FromFile(
|
||||
base::FilePath("/sys/bus/event_source/devices/power/type"),
|
||||
&attr_type)) {
|
||||
LOG(WARNING) << "Failed to get perf event type";
|
||||
return false;
|
||||
}
|
||||
|
||||
// For each metric, get their file descriptors.
|
||||
for (auto* const metric : kMetrics) {
|
||||
base::FilePath config_path =
|
||||
base::FilePath("/sys/bus/event_source/devices/power/events")
|
||||
.Append(FILE_PATH_LITERAL(metric));
|
||||
base::FilePath scale_path =
|
||||
base::FilePath("/sys/bus/event_source/devices/power/events")
|
||||
.Append(FILE_PATH_LITERAL(metric + std::string(".scale")));
|
||||
// Some energy metrics may be unavailable on different platforms, so the
|
||||
// corresponding file path does not exist, which is normal.
|
||||
if (!base::PathExists(config_path) || !base::PathExists(scale_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the specified config for this event.
|
||||
uint64_t attr_config;
|
||||
if (!ReadHexFromFile(config_path, &attr_config)) {
|
||||
LOG(ERROR) << "Failed to get config " << config_path.value();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Each event has its own scale to convert ticks to joules, which is usually
|
||||
// set to 2.3283064365386962890625e-10.
|
||||
double scale;
|
||||
if (!ReadDoubleFromFile(scale_path, &scale)) {
|
||||
LOG(ERROR) << "Failed to get scale of " << metric;
|
||||
continue;
|
||||
}
|
||||
// Convert the unit from joules/tick to nanojoules/tick.
|
||||
scale = scale * 1e9;
|
||||
|
||||
perf_event_attr perf_attr = {0};
|
||||
perf_attr.size = static_cast<uint32_t>(sizeof(perf_attr));
|
||||
perf_attr.type = static_cast<uint32_t>(attr_type);
|
||||
perf_attr.config = attr_config;
|
||||
base::ScopedFD fd = OpenPerfEvent(&perf_attr);
|
||||
if (!fd.is_valid()) {
|
||||
LOG(ERROR) << "Failed to get fd of " << metric;
|
||||
continue;
|
||||
}
|
||||
events_.push_back({metric, scale, std::move(fd)});
|
||||
}
|
||||
|
||||
if (events_.empty()) {
|
||||
LOG(WARNING) << "No available energy metric";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace power_metrics
|
61
components/power_metrics/energy_metrics_provider_linux.h
Normal file
61
components/power_metrics/energy_metrics_provider_linux.h
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef COMPONENTS_POWER_METRICS_ENERGY_METRICS_PROVIDER_LINUX_H_
|
||||
#define COMPONENTS_POWER_METRICS_ENERGY_METRICS_PROVIDER_LINUX_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "components/power_metrics/energy_metrics_provider.h"
|
||||
|
||||
namespace power_metrics {
|
||||
|
||||
// EnergyMetricsProviderLinux can only work on platforms with Intel Processor
|
||||
// with RAPL interface. It also requires at least Linux 3.14 and
|
||||
// /proc/sys/kernel/perf_event_paranoid < 1, which grants permission to read
|
||||
// perf event.
|
||||
class EnergyMetricsProviderLinux : public EnergyMetricsProvider {
|
||||
public:
|
||||
// A PowerEvent corresponds to a metric, which includes the metric type, the
|
||||
// scale from ticks to joules and the file descriptor for reading perf data.
|
||||
struct PowerEvent {
|
||||
std::string metric_type;
|
||||
double scale;
|
||||
base::ScopedFD fd;
|
||||
|
||||
PowerEvent(std::string metric_type, double scale, base::ScopedFD fd);
|
||||
~PowerEvent();
|
||||
|
||||
PowerEvent(PowerEvent&& other);
|
||||
PowerEvent& operator=(PowerEvent&& other);
|
||||
};
|
||||
|
||||
// Factory method for production instances.
|
||||
static std::unique_ptr<EnergyMetricsProviderLinux> Create();
|
||||
|
||||
EnergyMetricsProviderLinux(const EnergyMetricsProviderLinux&) = delete;
|
||||
EnergyMetricsProviderLinux& operator=(const EnergyMetricsProviderLinux&) =
|
||||
delete;
|
||||
|
||||
~EnergyMetricsProviderLinux() override;
|
||||
|
||||
// EnergyMetricsProvider implementation.
|
||||
absl::optional<EnergyMetrics> CaptureMetrics() override;
|
||||
|
||||
private:
|
||||
EnergyMetricsProviderLinux();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
// Used to derive energy consumption data via perf event.
|
||||
std::vector<PowerEvent> events_;
|
||||
bool is_initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace power_metrics
|
||||
|
||||
#endif // COMPONENTS_POWER_METRICS_ENERGY_METRICS_PROVIDER_LINUX_H_
|
@ -2,16 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/power_metrics/energy_metrics_provider_win.h"
|
||||
#include "components/power_metrics/energy_metrics_provider.h"
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace power_metrics {
|
||||
|
||||
TEST(EnergyMetricsProviderWinTest, CaptureMetrics) {
|
||||
auto provider = EnergyMetricsProviderWin::Create();
|
||||
ASSERT_NE(provider, nullptr);
|
||||
provider->CaptureMetrics();
|
||||
TEST(EnergyMetricsProviderTest, CaptureMetrics) {
|
||||
auto provider = EnergyMetricsProvider::Create();
|
||||
if (provider) {
|
||||
provider->CaptureMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace power_metrics
|
Reference in New Issue
Block a user