1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/power_metrics/energy_metrics_provider_linux.h"
7 #include <linux/perf_event.h>
8 #include <sys/syscall.h>
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/files/scoped_file.h"
15 #include "base/logging.h"
16 #include "base/memory/ptr_util.h"
17 #include "base/strings/strcat.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_util.h"
21 namespace power_metrics {
25 constexpr const char* kPowerEventPath = "/sys/bus/event_source/devices/power";
27 // Existing metrics that can be read via perf event.
28 constexpr std::array<const char*, 5> kMetrics{
29 "energy-pkg", "energy-cores", "energy-gpu", "energy-ram", "energy-psys"};
31 bool ReadUint64FromFile(base::FilePath path, uint64_t* output) {
33 if (!base::ReadFileToString(path, &buf)) {
36 return base::StringToUint64(base::TrimString(buf, "\n", base::TRIM_TRAILING),
40 bool ReadHexFromFile(base::FilePath path, uint64_t* output) {
42 if (!base::ReadFileToString(path, &buf)) {
45 base::ReplaceFirstSubstringAfterOffset(&buf, 0, "event=", "");
46 return base::HexStringToUInt64(
47 base::TrimString(buf, "\n", base::TRIM_TRAILING), output);
50 bool ReadDoubleFromFile(base::FilePath path, double* output) {
52 if (!base::ReadFileToString(path, &buf)) {
55 return base::StringToDouble(base::TrimString(buf, "\n", base::TRIM_TRAILING),
59 // When pid == -1 and cpu >= 0, perf event measures all processes/threads on the
60 // specified CPU. This requires admin or a /proc/sys/kernel/perf_event_paranoid
61 // value of less than 1. Here, we only consider cpu0. See details in
62 // https://man7.org/linux/man-pages/man2/perf_event_open.2.html.
63 base::ScopedFD OpenPerfEvent(perf_event_attr* perf_attr) {
64 base::ScopedFD perf_fd{syscall(__NR_perf_event_open, perf_attr, /*pid=*/-1,
65 /*cpu=*/0, /*group_fd=*/-1,
66 PERF_FLAG_FD_CLOEXEC)};
70 void SetEnergyMetric(const std::string& metric_type,
71 EnergyMetricsProvider::EnergyMetrics& energy_metrics,
72 uint64_t absolute_energy) {
73 if (metric_type == "energy-pkg") {
74 energy_metrics.package_nanojoules = absolute_energy;
75 } else if (metric_type == "energy-cores") {
76 energy_metrics.cpu_nanojoules = absolute_energy;
77 } else if (metric_type == "energy-gpu") {
78 energy_metrics.gpu_nanojoules = absolute_energy;
79 } else if (metric_type == "energy-ram") {
80 energy_metrics.dram_nanojoules = absolute_energy;
81 } else if (metric_type == "energy-psys") {
82 energy_metrics.psys_nanojoules = absolute_energy;
88 EnergyMetricsProviderLinux::PowerEvent::PowerEvent(std::string metric_type,
91 : metric_type(metric_type), scale(scale), fd(std::move(fd)) {}
93 EnergyMetricsProviderLinux::PowerEvent::~PowerEvent() = default;
95 EnergyMetricsProviderLinux::PowerEvent::PowerEvent(PowerEvent&& other) =
97 EnergyMetricsProviderLinux::PowerEvent&
98 EnergyMetricsProviderLinux::PowerEvent::operator=(PowerEvent&& other) = default;
100 EnergyMetricsProviderLinux::EnergyMetricsProviderLinux() = default;
101 EnergyMetricsProviderLinux::~EnergyMetricsProviderLinux() = default;
104 std::unique_ptr<EnergyMetricsProviderLinux>
105 EnergyMetricsProviderLinux::Create() {
106 return base::WrapUnique(new EnergyMetricsProviderLinux());
109 absl::optional<EnergyMetricsProvider::EnergyMetrics>
110 EnergyMetricsProviderLinux::CaptureMetrics() {
112 return absl::nullopt;
115 EnergyMetrics energy_metrics = {0};
116 for (const auto& event : events_) {
117 uint64_t absolute_energy;
118 if (!base::ReadFromFD(event.fd.get(),
119 reinterpret_cast<char*>(&absolute_energy),
120 sizeof(absolute_energy))) {
121 LOG(ERROR) << "Failed to read absolute energy of " << event.metric_type;
124 SetEnergyMetric(event.metric_type, energy_metrics,
125 static_cast<uint64_t>(event.scale * absolute_energy));
127 return energy_metrics;
130 bool EnergyMetricsProviderLinux::Initialize() {
131 if (is_initialized_) {
132 if (events_.empty()) {
138 is_initialized_ = true;
140 // Check if there are available power-related events on local platform.
141 if (!base::PathExists(base::FilePath(kPowerEventPath))) {
142 LOG(WARNING) << "No available power event";
146 // Check if perf_event_paranoid is set to 0 as required.
147 uint64_t perf_event_paranoid;
148 if (!ReadUint64FromFile(
149 base::FilePath("/proc/sys/kernel/perf_event_paranoid"),
150 &perf_event_paranoid)) {
151 LOG(WARNING) << "Failed to get perf_event_paranoid";
154 if (perf_event_paranoid) {
155 LOG(WARNING) << "Permission denied for acquiring energy metrics";
159 // Since the power Processor Monitor Unit (PMU) is dynamic, we have to get the
160 // type for perf_event_attr from /sys/bus/event_source/devices/power/type.
162 if (!ReadUint64FromFile(
163 base::FilePath(base::StrCat({kPowerEventPath, "/type"})),
165 LOG(WARNING) << "Failed to get perf event type";
169 // For each metric, get their file descriptors.
170 for (auto* const metric : kMetrics) {
171 base::FilePath config_path =
172 base::FilePath(base::StrCat({kPowerEventPath, "/events/", metric}));
173 base::FilePath scale_path = base::FilePath(
174 base::StrCat({kPowerEventPath, "/events/", metric, ".scale"}));
175 // Some energy metrics may be unavailable on different platforms, so the
176 // corresponding file path does not exist, which is normal.
177 if (!base::PathExists(config_path) || !base::PathExists(scale_path)) {
181 // Get the specified config for this event.
182 uint64_t attr_config;
183 if (!ReadHexFromFile(config_path, &attr_config)) {
184 LOG(ERROR) << "Failed to get config " << config_path.value();
188 // Each event has its own scale to convert ticks to joules, which is usually
189 // set to 2.3283064365386962890625e-10.
191 if (!ReadDoubleFromFile(scale_path, &scale)) {
192 LOG(ERROR) << "Failed to get scale of " << metric;
195 // Convert the unit from joules/tick to nanojoules/tick.
198 perf_event_attr perf_attr = {0};
199 perf_attr.size = static_cast<uint32_t>(sizeof(perf_attr));
200 perf_attr.type = static_cast<uint32_t>(attr_type);
201 perf_attr.config = attr_config;
202 base::ScopedFD fd = OpenPerfEvent(&perf_attr);
203 if (!fd.is_valid()) {
204 LOG(ERROR) << "Failed to get fd of " << metric;
207 events_.push_back({metric, scale, std::move(fd)});
210 if (events_.empty()) {
211 LOG(WARNING) << "No available energy metric";
217 } // namespace power_metrics