blob: afd32096a590b801cb0f7e095d84c035196d4cc3 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This utility reports power consumption for certain Intel SoCs, calculated by
// averaging the running energy consumption counter provided by Linux powercap
// driver subset of Intel RAPL (Running Average Power Limit) energy report.
// RAPL provides info per Power Domain: DRAM and PKG. PKG refers to the
// processor die, and includes the PP0 (cores) and PP1 (graphics) subdomains.
//
// MSRs reference can be found in "Sec. 14.9 Platform Specific Power Management
// Support" of the "Intel 64 and IA-32 Architectures Software Developer’s
// Manual Volume 3B: System Programming Guide, Part 2" [1].
// Info of Linux powercap driver can be reached in kernel documentation [2].
//
// [1]
// https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3b-part-2-manual.html
// [2]
// https://github.com/torvalds/linux/blob/HEAD/Documentation/power/powercap/powercap.rst
#include <inttypes.h>
#include <math.h>
#include <base/files/file.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/system/sys_info.h>
#include <base/threading/platform_thread.h>
#include <base/time/time.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include "power_manager/common/util.h"
namespace {
// Path to the powercap driver sysfs interface, if it doesn't exist,
// either the kernel is old w/o powercap driver, or it is not configured.
constexpr const char kPowercapPath[] = "/sys/class/powercap";
// Cap of a maximal reasonable power in Watts
constexpr const double kMaxWatts = 1e3;
struct PowerDomain {
base::FilePath file_path;
std::string name;
uint64_t max_energy;
bool operator<(const PowerDomain& that) const {
return file_path < that.file_path;
}
};
} // namespace
int main(int argc, char** argv) {
DEFINE_int32(interval_ms, 1000, "Interval to collect consumption (ms).");
DEFINE_bool(repeat, false, "Repeat forever.");
DEFINE_bool(verbose, false, "Verbose logging.");
brillo::FlagHelper::Init(
argc, argv, "Print average power consumption per domain for Intel SoCs");
brillo::InitLog(brillo::kLogToStderr);
// Kernel v3.13+ supports powercap, it also requires a proper configuration
// enabling it; leave a verbose footprint of the kernel string, and examine
// whether or not the system supports the powercap driver.
if (FLAGS_verbose) {
const base::SysInfo sys;
printf("OS version: %s\n", sys.OperatingSystemVersion().c_str());
}
base::FilePath powercap_file_path(kPowercapPath);
PCHECK(base::PathExists(powercap_file_path))
<< "No powercap driver sysfs interface, couldn't find "
<< powercap_file_path.value();
std::vector<PowerDomain> power_domains;
std::string domain_name;
// Probe the power domains and sub-domains
base::FilePath powercap_path(kPowercapPath);
base::FileEnumerator dirs(powercap_path, false,
base::FileEnumerator::DIRECTORIES,
FILE_PATH_LITERAL("intel-rapl:*"));
for (base::FilePath dir = dirs.Next(); !dir.empty(); dir = dirs.Next()) {
base::FilePath domain_file_path = dir.Append("name");
base::FilePath energy_file_path = dir.Append("energy_uj");
base::FilePath maxeng_file_path = dir.Append("max_energy_range_uj");
uint64_t max_energy_uj;
if (!base::PathExists(domain_file_path)) {
fprintf(stderr, "Unable to find %s\n", domain_file_path.value().c_str());
continue;
}
if (!base::PathExists(energy_file_path)) {
fprintf(stderr, "Unable to find %s\n", energy_file_path.value().c_str());
continue;
}
if (!base::PathExists(maxeng_file_path)) {
fprintf(stderr, "Unable to find %s\n", maxeng_file_path.value().c_str());
continue;
}
power_manager::util::ReadStringFile(domain_file_path, &domain_name);
power_manager::util::ReadUint64File(maxeng_file_path, &max_energy_uj);
power_domains.push_back({energy_file_path, domain_name, max_energy_uj});
if (FLAGS_verbose)
printf("Found domain %-10s (max %" PRIu64 " uj) at %s\n",
domain_name.c_str(), max_energy_uj, dir.value().c_str());
}
PCHECK(!power_domains.empty())
<< "No power domain found at " << powercap_file_path.value();
// As the enumeration above does not guarantee the order, transform the
// paths in lexicographical order, make the collecting data in a style
// of domain follows by sub-domain, it can be done by sorting.
// e.g., package-0 psys core ... -> package-0 core ... psys
sort(power_domains.begin(), power_domains.end());
for (const auto& domain : power_domains)
printf("%10s ", domain.name.c_str());
printf(" (Note: Values in Watts)\n");
uint32_t num_domains = power_domains.size();
std::vector<uint64_t> energy_before(num_domains);
std::vector<uint64_t> energy_after(num_domains);
do {
for (int i = 0; i < num_domains; ++i)
power_manager::util::ReadUint64File(power_domains[i].file_path,
&energy_before[i]);
const base::TimeTicks ticks_before = base::TimeTicks::Now();
base::PlatformThread::Sleep(
base::TimeDelta::FromMilliseconds(FLAGS_interval_ms));
for (int i = 0; i < num_domains; ++i)
power_manager::util::ReadUint64File(power_domains[i].file_path,
&energy_after[i]);
const base::TimeDelta time_delta = base::TimeTicks::Now() - ticks_before;
for (int i = 0; i < num_domains; ++i) {
uint64_t energy_delta = (energy_after[i] >= energy_before[i])
? energy_after[i] - energy_before[i]
: power_domains[i].max_energy -
energy_before[i] + energy_after[i];
double average_power = energy_delta / (time_delta.InSecondsF() * 1e6);
// Skip enormous sample if the counter is reset during suspend-to-RAM
if (average_power > kMaxWatts) {
printf("%10s ", "skip");
continue;
}
printf("%10.6f ", average_power);
}
printf("\n");
} while (FLAGS_repeat);
return 0;
}