| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "login_manager/scheduler_util.h" | |
| #include <algorithm> | |
| #include <map> | |
| #include <string_view> | |
| #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> | |
| namespace { | |
| // Size of the prefix 'cpu'. | |
| constexpr size_t kCpuPrefixSize = 3; | |
| constexpr char kCpuBusDir[] = "/sys/bus/cpu/devices"; | |
| constexpr const char* kPerfAttributeFiles[] = { | |
| "cpu_capacity", "cpufreq/cpuinfo_max_freq", "acpi_cppc/highest_perf"}; | |
| constexpr char kCpusetNonUrgentDir[] = | |
| "/sys/fs/cgroup/cpuset/chrome/non-urgent"; | |
| } // namespace | |
| namespace login_manager { | |
| std::vector<std::string> GetSmallCoreCpuIdsFromAttr( | |
| const base::FilePath& cpu_bus_dir, std::string_view attribute) { | |
| base::FilePath cpu0_attr_file = cpu_bus_dir.Append("cpu0").Append(attribute); | |
| if (!base::PathExists(cpu0_attr_file)) | |
| return {}; | |
| // Gets attribute values through traversing the attribute of each cpu, and | |
| // stores them into a map. | |
| base::FileEnumerator enumerator(cpu_bus_dir, false /*recursive*/, | |
| base::FileEnumerator::DIRECTORIES); | |
| std::map<int, std::vector<std::string>> attr_to_cpu_ids_map; | |
| for (base::FilePath subdir = enumerator.Next(); !subdir.empty(); | |
| subdir = enumerator.Next()) { | |
| std::string item_str; | |
| if (base::ReadFileToString(subdir.Append(attribute), &item_str)) { | |
| std::string subdir_name = subdir.BaseName().value(); | |
| DCHECK_GT(subdir_name.size(), kCpuPrefixSize); | |
| int item = atoi(item_str.c_str()); | |
| if (item <= 0) { | |
| LOG(ERROR) << "Invalid value read from " << subdir_name | |
| << " attribute file!"; | |
| continue; | |
| } | |
| std::string cpu_id = subdir_name.substr(kCpuPrefixSize); | |
| attr_to_cpu_ids_map[item].emplace_back(cpu_id); | |
| } | |
| } | |
| // If the number of attribute value is 1, the cpu arch is not hybrid, the | |
| // small core cpu id list is empty. | |
| if (attr_to_cpu_ids_map.size() <= 1) | |
| return {}; | |
| auto it = attr_to_cpu_ids_map.begin(); | |
| std::vector<std::string> small_cpu_ids = it->second; | |
| // If the map has more than 2 attribute values, we consider the cpus with two | |
| // smallest capacities / freqs as small cores. | |
| if (attr_to_cpu_ids_map.size() > 2) { | |
| ++it; | |
| small_cpu_ids.insert(small_cpu_ids.end(), it->second.begin(), | |
| it->second.end()); | |
| } | |
| std::sort(small_cpu_ids.begin(), small_cpu_ids.end()); | |
| return small_cpu_ids; | |
| } | |
| std::vector<std::string> CalculateSmallCoreCpusIfHybrid( | |
| const base::FilePath& cpu_bus_dir) { | |
| // sysfs attributes are probed in order they are appear in | |
| // #kPerfAttributeFiles | |
| for (const auto& perf_attr : kPerfAttributeFiles) { | |
| if (std::vector<std::string> small_cpu_ids = | |
| GetSmallCoreCpuIdsFromAttr(cpu_bus_dir, perf_attr); | |
| !small_cpu_ids.empty()) { | |
| return small_cpu_ids; | |
| } | |
| } | |
| return {}; | |
| } | |
| bool ConfigureNonUrgentCpuset(brillo::CrosConfigInterface* cros_config) { | |
| base::FilePath nonurgent_path(kCpusetNonUrgentDir); | |
| if (!base::PathExists(nonurgent_path)) { | |
| LOG(WARNING) << "The path of non-urgent cpuset doesn't exist!"; | |
| return false; | |
| } | |
| std::string cpuset_conf; | |
| // Writes cpuset-nonurgent to non-urgent cpuset if it's specified in | |
| // cros_config. | |
| if (cros_config && | |
| cros_config->GetString("/scheduler-tune", "cpuset-nonurgent", | |
| &cpuset_conf) && | |
| !cpuset_conf.empty()) { | |
| if (!base::WriteFile(nonurgent_path.Append("cpus"), cpuset_conf)) { | |
| LOG(ERROR) << "Error writing non urgent cpuset!"; | |
| return false; | |
| } | |
| LOG(INFO) << "Non-urgent cpuset is " << cpuset_conf << " from cros_config"; | |
| return true; | |
| } | |
| // Use all small cores as non-urgent cpuset, if cpuset-nonurgent isn't | |
| // specified in cros_config. | |
| std::vector<std::string> ecpu_ids = | |
| CalculateSmallCoreCpusIfHybrid(base::FilePath(kCpuBusDir)); | |
| if (ecpu_ids.empty()) | |
| return false; | |
| std::string ecpu_mask = base::JoinString(ecpu_ids, ","); | |
| LOG(INFO) << "The board has hybrid arch cpu, the non-urgent cpuset is " | |
| << ecpu_mask << "."; | |
| if (!base::WriteFile(nonurgent_path.Append("cpus"), ecpu_mask)) { | |
| LOG(ERROR) << "Error writing mask of small cores to non urgent cpuset!"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace login_manager |