blob: 147abc651217dc8a08ea039cfe69f206a5b3467f [file] [log] [blame] [edit]
// Copyright 2019 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.
#include "debugd/src/helpers/scheduler_configuration_utils.h"
#include <fcntl.h>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
namespace debugd {
namespace {
constexpr char kCPUOfflineSubpath[] = "devices/system/cpu/offline";
constexpr char kCPUOnlineSubpath[] = "devices/system/cpu/online";
constexpr char kCPUPresentSubpath[] = "devices/system/cpu/present";
constexpr char kCPUSubpath[] = "devices/system/cpu";
constexpr char kChromeCPUSubsetSubpath[] = "fs/cgroup/cpuset/chrome/cpus";
constexpr char kChronosContainerCPUSubsetSubpath[] =
"fs/cgroup/cpuset/chronos_containers/cpus";
constexpr char kDisableCPUFlag[] = "0";
constexpr char kEnableCPUFlag[] = "1";
constexpr char kLineTerminator = 0xa;
// The maximum size of /sys file to read out on the online CPUs.
// The file is generally 0,2 or 0-3, but this prepares for the future by
// allowing up to 256 cores, and assuming the kernel might enumerate each core
// explicitly (0,1,...,255).
constexpr size_t kMaxCoreBufferSize = 1024;
constexpr char kSessionManagerCPUSubsetSubpath[] =
"fs/cgroup/cpuset/session_manager_containers/cpus";
constexpr base::TimeDelta kWriteRetryDelay =
base::TimeDelta::FromMilliseconds(100);
} // namespace
// static
bool SchedulerConfigurationUtils::WriteFlagToCPUControlFile(
const base::ScopedFD& fd, const std::string& flag) {
// WriteFileDescriptor returns true iff |size| bytes of |data| were written to
// |fd|.
return base::WriteFileDescriptor(fd.get(), flag);
}
// static
bool SchedulerConfigurationUtils::ParseCPUNumbers(
const std::string& cpus, std::vector<std::string>* result) {
DCHECK(result);
std::vector<std::string> tokens = base::SplitString(
cpus, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (tokens.size() < 1)
return false;
for (const auto& token : tokens) {
// If it's a number, push immediately to the list.
unsigned unused;
if (base::StringToUint(token, &unused)) {
result->push_back(token);
continue;
}
// Otherwise it must be a hyphen separated range.
std::vector<std::string> range_tokens = base::SplitString(
token, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (range_tokens.size() != 2) {
return false;
}
unsigned cpu_start, cpu_end;
if (!base::StringToUint(range_tokens[0], &cpu_start) ||
!base::StringToUint(range_tokens[1], &cpu_end)) {
return false;
}
if (cpu_end <= cpu_start) {
return false;
}
for (unsigned i = cpu_start; i <= cpu_end; i++) {
result->push_back(base::NumberToString(i));
}
}
return true;
}
bool SchedulerConfigurationUtils::LookupFDAndWriteFlag(
const std::string& cpu_number, const std::string& flag) {
auto fd = fd_map_.find(cpu_number);
if (fd == fd_map_.end()) {
LOG(ERROR) << "Failed to find CPU control file for CPU: " << cpu_number;
return false;
}
bool result = WriteFlagToCPUControlFile(fd->second, flag);
if (!result)
PLOG(ERROR) << "write";
return result;
}
bool SchedulerConfigurationUtils::DisableCPU(const std::string& cpu_number) {
bool status = LookupFDAndWriteFlag(cpu_number, kDisableCPUFlag);
// Sometimes the CPU control file is busy so sleep and retry.
int retries = 5;
while (!status && errno == EBUSY && retries-- > 0) {
usleep(kWriteRetryDelay.InMicroseconds());
status = LookupFDAndWriteFlag(cpu_number, kDisableCPUFlag);
}
return status;
}
// This writes the flag to enable the given CPU by number.
bool SchedulerConfigurationUtils::EnableCPU(const std::string& cpu_number) {
return LookupFDAndWriteFlag(cpu_number, kEnableCPUFlag);
}
// This enables all cores.
bool SchedulerConfigurationUtils::EnablePerformanceConfiguration(
size_t* num_cores_disabled) {
std::string failed_cpus;
bool result = true;
*num_cores_disabled = offline_cpus_.size();
for (const auto& cpu : offline_cpus_) {
if (EnableCPU(cpu)) {
--(*num_cores_disabled);
} else {
failed_cpus = failed_cpus + " " + cpu;
result = false;
}
}
if (!result)
LOG(ERROR) << "Failed to enable CPU(s): " << failed_cpus;
return result && UpdateAllCPUSets();
}
base::FilePath SchedulerConfigurationUtils::GetSiblingPath(
const std::string& cpu_num) {
return base_path_.Append(kCPUSubpath)
.Append("cpu" + cpu_num)
.Append("topology")
.Append("thread_siblings_list");
}
// This disables sibling threads of physical cores.
SchedulerConfigurationUtils::DisableSiblingsResult
SchedulerConfigurationUtils::DisableSiblings(const std::string& cpu_num) {
base::FilePath path = GetSiblingPath(cpu_num);
std::string siblings_list;
if (!base::ReadFileToString(path, &siblings_list)) {
PLOG(ERROR) << "Failed to read sibling thread list from: " << path.value();
return DisableSiblingsResult::ERROR;
}
std::vector<std::string> sibling_nums;
if (!ParseCPUNumbers(siblings_list, &sibling_nums)) {
LOG(ERROR) << "Unknown range: " << siblings_list;
return DisableSiblingsResult::ERROR;
}
// The physical core is the first number in the range.
if (cpu_num != sibling_nums[0]) {
return DisableCPU(cpu_num) ? DisableSiblingsResult::SUCCESS
: DisableSiblingsResult::ERROR;
}
return DisableSiblingsResult::PHYSICAL_CORE;
}
bool SchedulerConfigurationUtils::EnableConservativeConfiguration(
size_t* num_cores_disabled) {
bool status = true;
*num_cores_disabled = offline_cpus_.size();
for (const auto& cpu_num : online_cpus_) {
switch (DisableSiblings(cpu_num)) {
case DisableSiblingsResult::PHYSICAL_CORE:
break;
case DisableSiblingsResult::SUCCESS:
++(*num_cores_disabled);
break;
case DisableSiblingsResult::ERROR:
status = false;
LOG(ERROR) << "Failed to disable CPU: " << cpu_num;
break;
}
}
return status && UpdateAllCPUSets();
}
bool SchedulerConfigurationUtils::PruneNonPresentCpus(
std::vector<std::string>* cpu_nums) {
// Take the list of incoming cpu_nums from online or offline files
// then prune these to only include cpus listed in the 'present' file.
std::string present_cpus_str;
auto present_path = base_path_.Append(kCPUPresentSubpath);
if (!base::ReadFileToString(present_path, &present_cpus_str)) {
PLOG(ERROR) << "Failed to read present CPU list at "
<< present_path.value();
return false;
}
// The kernel returns 0xa if the file is empty.
if (present_cpus_str == std::string(1, kLineTerminator)) {
LOG(ERROR) << "Present CPUs empty";
return false;
}
std::vector<std::string> present_cpu_nums;
if (!ParseCPUNumbers(present_cpus_str, &present_cpu_nums)) {
LOG(ERROR) << "Unknown range: " << present_cpus_str;
return false;
}
// Then we only take CPUs that are in both in our target list and
// the present list.
std::vector<std::string> cpu_nums_to_keep;
for (const auto& cpu_num : *cpu_nums) {
for (const auto& present_cpu_num : present_cpu_nums) {
if (cpu_num == present_cpu_num) {
cpu_nums_to_keep.push_back(present_cpu_num);
}
}
}
cpu_nums->swap(cpu_nums_to_keep);
return true;
}
bool SchedulerConfigurationUtils::GetFDsFromControlFile(
const base::FilePath& path, std::vector<std::string>* cpu_nums) {
DCHECK(cpu_nums);
std::string cpus_str;
if (!base::ReadFileToString(path, &cpus_str)) {
PLOG(ERROR) << "Failed to read CPU list";
return false;
}
// The kernel returns 0xa if the file is empty.
if (cpus_str == std::string(1, kLineTerminator))
return true;
if (!ParseCPUNumbers(cpus_str, cpu_nums)) {
LOG(ERROR) << "Unknown range: " << cpus_str;
return false;
}
if (!PruneNonPresentCpus(cpu_nums)) {
LOG(ERROR) << "Failed to prune CPUs based on present file";
return false;
}
for (const auto& cpu_num : *cpu_nums) {
// There is no control file for cpu0, which cannot be turned off.
if (cpu_num == "0")
continue;
base::FilePath cpu_path =
base_path_.Append(kCPUSubpath).Append("cpu" + cpu_num).Append("online");
base::ScopedFD cpu_fd(
HANDLE_EINTR(open(cpu_path.value().c_str(), O_RDWR | O_CLOEXEC)));
if (cpu_fd.get() < 0) {
PLOG(ERROR) << "Failed to open: " << cpu_path.value();
return false;
}
if (fd_map_.insert(std::make_pair(cpu_num, std::move(cpu_fd))).second ==
false) {
LOG(ERROR) << "Duplicate control file.";
return false;
}
}
return true;
}
bool SchedulerConfigurationUtils::GetControlFDs() {
return GetFDsFromControlFile(base_path_.Append(kCPUOnlineSubpath),
&online_cpus_) &&
GetFDsFromControlFile(base_path_.Append(kCPUOfflineSubpath),
&offline_cpus_);
}
bool SchedulerConfigurationUtils::GetCPUSetFDs() {
std::vector<const base::FilePath> cpu_sets;
cpu_sets.push_back(base_path_.Append(kChromeCPUSubsetSubpath));
cpu_sets.push_back(base_path_.Append(kChronosContainerCPUSubsetSubpath));
cpu_sets.push_back(base_path_.Append(kSessionManagerCPUSubsetSubpath));
for (const auto& path : cpu_sets) {
base::ScopedFD fd(
HANDLE_EINTR(open(path.value().c_str(), O_RDWR | O_CLOEXEC)));
if (fd.get() < 0) {
PLOG(WARNING) << "Failed to open: " << path.value();
} else {
cpusets_fds_.push_back(std::move(fd));
}
}
base::FilePath online_cpus_path = base_path_.Append(kCPUOnlineSubpath);
base::ScopedFD online_cpus_fd(HANDLE_EINTR(
open(online_cpus_path.value().c_str(), O_RDONLY | O_CLOEXEC)));
if (online_cpus_fd.get() < 0) {
PLOG(ERROR) << "Failed to open: " << online_cpus_path.value();
return false;
}
online_cpus_fd_ = std::move(online_cpus_fd);
return true;
}
bool SchedulerConfigurationUtils::UpdateAllCPUSets() {
if (online_cpus_fd_.get() < 0) {
LOG(ERROR) << "Online CPUs fd is invalid.";
return false;
}
char online_cpus[kMaxCoreBufferSize];
size_t bytes_read = HANDLE_EINTR(
read(online_cpus_fd_.get(), online_cpus, sizeof(online_cpus)));
if (bytes_read < 0) {
PLOG(ERROR) << "Failed to read online CPU range";
return false;
}
std::string online_cpus_str(online_cpus, bytes_read);
std::vector<std::string> unused_nums;
if (!ParseCPUNumbers(online_cpus_str, &unused_nums)) {
LOG(ERROR) << "Failed to parse CPU range, failed coherence check: "
<< online_cpus_str;
return false;
}
for (const auto& scoped_fd : cpusets_fds_) {
// This is a best effort, so if it fails, continue on.
base::WriteFileDescriptor(scoped_fd.get(), online_cpus_str);
}
return true;
}
} // namespace debugd