| // 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/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 kLineTerminator = 0xa; |
| constexpr char kCPUSubpath[] = "devices/system/cpu"; |
| constexpr char kCPUOfflineSubath[] = "devices/system/cpu/offline"; |
| constexpr char kCPUOnlineSubpath[] = "devices/system/cpu/online"; |
| constexpr char kDisableCPUFlag[] = "0"; |
| constexpr char kEnableCPUFlag[] = "1"; |
| 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.c_str(), flag.size()); |
| } |
| |
| // 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::UintToString(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; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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(kCPUOfflineSubath), |
| &offline_cpus_); |
| } |
| |
| } // namespace debugd |