blob: 42bf32fef8796c385c04002a541c656e54789ee9 [file] [log] [blame]
// 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 <stdio.h>
#include <string>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <gtest/gtest.h>
namespace debugd {
namespace {
constexpr char kChromeCPUSubsetSubpath[] = "fs/cgroup/cpuset/chrome/cpus";
constexpr char kChronosContainerCPUSubsetSubpath[] =
"fs/cgroup/cpuset/chronos_containers/cpus";
constexpr char kSessionManagerCPUSubsetSubpath[] =
"fs/cgroup/cpuset/session_manager_containers/cpus";
} // namespace
class SchedulerConfigurationHelperTest : public testing::Test {
public:
std::string GetOnlineOrPresentString(int in_int) {
return std::string("0-" + std::to_string(in_int - 1));
}
std::string GetOfflineString(int online, int total) {
return std::string(std::to_string(online) + "-" +
std::to_string(total - 1));
}
// Create system files to represent different CPU states.
void CreateSysInterface(const base::FilePath& cpu_root_dir,
const base::FilePath& base_sys_path,
int online,
int present,
int total) {
// Set up a fake tempdir mimicking a performance mode CPU.
ASSERT_TRUE(base::CreateDirectory(cpu_root_dir));
// Create online CPUs, and turn them all on.
for (int cur_online_cpu = 0; cur_online_cpu < online; cur_online_cpu++) {
base::FilePath cpu_subroot =
cpu_root_dir.Append("cpu" + std::to_string(cur_online_cpu));
ASSERT_TRUE(base::CreateDirectory(cpu_subroot));
std::string flag = "1";
ASSERT_EQ(flag.size(), base::WriteFile(cpu_subroot.Append("online"),
flag.c_str(), flag.size()));
// Establish odd CPUs as virtual siblings.
base::FilePath topology = cpu_subroot.Append("topology");
ASSERT_TRUE(base::CreateDirectory(topology));
// For cpu_num "0", string should read "0-1".
// For cpu_num "3", string should read "2-3" and so forth.
int lower;
if (cur_online_cpu % 2 == 0) {
lower = cur_online_cpu;
} else {
lower = cur_online_cpu - 1;
}
std::string topology_str =
std::to_string(lower) + "-" + std::to_string(lower + 1);
// Assert cases manually made by previous logic still work.
if (cur_online_cpu == 0 || cur_online_cpu == 1) {
ASSERT_EQ(topology_str, "0-1");
} else if (cur_online_cpu == 2 || cur_online_cpu == 3) {
ASSERT_EQ(topology_str, "2-3");
}
ASSERT_EQ(topology_str.size(),
base::WriteFile(topology.Append("thread_siblings_list"),
topology_str.c_str(), topology_str.size()));
}
// Establish the control files.
base::FilePath online_cpus_file = cpu_root_dir.Append("online");
std::string online_cpus = GetOnlineOrPresentString(online);
ASSERT_EQ(online_cpus.size(),
base::WriteFile(online_cpus_file, online_cpus.c_str(),
online_cpus.size()));
// Create set of present CPUs.
base::FilePath present_cpus_file = cpu_root_dir.Append("present");
std::string present_cpus = GetOnlineOrPresentString(present);
ASSERT_EQ(present_cpus.size(),
base::WriteFile(present_cpus_file, present_cpus.c_str(),
present_cpus.size()));
// Establish the offline CPUs.
base::FilePath offline_cpus_file = cpu_root_dir.Append("offline");
if (online == total) {
// If all CPUs are online, the offline file is empty.
const char terminator = 0xa;
ASSERT_EQ(1, base::WriteFile(offline_cpus_file, &terminator, 1));
} else {
// If not all CPUs are online, offline file is "online-(total - 1)".
// So if 2 CPUs online and 8 CPUs total, online string would be "0-1"
// and offline string would be "2-7".
std::string offline_cpus = GetOfflineString(online, total);
ASSERT_EQ(offline_cpus.size(),
base::WriteFile(offline_cpus_file, offline_cpus.c_str(),
offline_cpus.size()));
}
// Setup the cpu set files.
base::FilePath chrome_cpuset =
base_sys_path.Append(kChromeCPUSubsetSubpath);
base::FilePath chronos_cpuset =
base_sys_path.Append(kChronosContainerCPUSubsetSubpath);
base::FilePath session_manager_cpuset =
base_sys_path.Append(kSessionManagerCPUSubsetSubpath);
for (const auto& cpuset :
{chrome_cpuset, chronos_cpuset, session_manager_cpuset}) {
ASSERT_TRUE(base::CreateDirectory(cpuset.DirName()));
// Sets the range to A to make sure the debugd code updates it.
std::string range = "A";
ASSERT_EQ(range.size(),
base::WriteFile(cpuset, range.data(), range.size()));
}
}
void CheckPerformanceMode(const base::FilePath& cpu_root_dir) {
for (const std::string& cpu_num : {"0", "1", "2", "3"}) {
base::FilePath cpu_control =
cpu_root_dir.Append("cpu" + cpu_num).Append("online");
std::string control_contents;
ASSERT_TRUE(base::ReadFileToString(cpu_control, &control_contents));
EXPECT_EQ("1", control_contents);
}
}
void CheckConservativeModeShared(const base::FilePath& cpu_root_dir,
std::vector<std::string>* cpu_list) {
for (const std::string& cpu_num : *cpu_list) {
base::FilePath cpu_control =
cpu_root_dir.Append("cpu" + cpu_num).Append("online");
std::string control_contents;
ASSERT_TRUE(base::ReadFileToString(cpu_control, &control_contents));
if (cpu_num == "0" || cpu_num == "2") {
EXPECT_EQ("1", control_contents);
} else if (cpu_num == "1" || cpu_num == "3") {
EXPECT_EQ("0", control_contents);
}
}
}
void CheckConservativeMode(const base::FilePath& cpu_root_dir) {
std::vector<std::string> cpu_list;
cpu_list.push_back("0");
cpu_list.push_back("1");
cpu_list.push_back("2");
cpu_list.push_back("3");
CheckConservativeModeShared(cpu_root_dir, &cpu_list);
}
void CheckConservativeModeTwoCpus(const base::FilePath& cpu_root_dir) {
std::vector<std::string> cpu_list;
cpu_list.push_back("0");
cpu_list.push_back("1");
CheckConservativeModeShared(cpu_root_dir, &cpu_list);
}
};
TEST_F(SchedulerConfigurationHelperTest, ParseCPUs) {
// Note the usual security principle in this test: the kernel shouldn't return
// any of these crazy invalid sequences ("0-?", etc.), but it's important to
// handle unexpected input gracefully.
debugd::SchedulerConfigurationUtils utils{base::FilePath("/sys")};
std::vector<std::string> raw_num;
EXPECT_TRUE(utils.ParseCPUNumbers("1", &raw_num));
EXPECT_EQ(std::vector<std::string>({"1"}), raw_num);
// Test a simple range.
std::vector<std::string> range;
EXPECT_TRUE(utils.ParseCPUNumbers("0-3", &range));
EXPECT_EQ(std::vector<std::string>({"0", "1", "2", "3"}), range);
// Test a comma separated list.
std::vector<std::string> list;
EXPECT_TRUE(utils.ParseCPUNumbers("0,3,4,7", &list));
EXPECT_EQ(std::vector<std::string>({"0", "3", "4", "7"}), list);
// Some devices have weird topologies.
std::vector<std::string> complex_range;
EXPECT_TRUE(utils.ParseCPUNumbers("0,2-3,6", &complex_range));
EXPECT_EQ(std::vector<std::string>({"0", "2", "3", "6"}), complex_range);
std::vector<std::string> complex_range2;
EXPECT_TRUE(utils.ParseCPUNumbers("0,2-5,6-10,17-25,32", &complex_range2));
EXPECT_EQ(std::vector<std::string>({"0", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "17", "18", "19", "20",
"21", "22", "23", "24", "25", "32"}),
complex_range2);
// Invalid ranges.
std::vector<std::string> invalid;
EXPECT_FALSE(utils.ParseCPUNumbers("-", &invalid));
EXPECT_FALSE(utils.ParseCPUNumbers(",", &invalid));
EXPECT_FALSE(utils.ParseCPUNumbers("?", &invalid));
EXPECT_FALSE(utils.ParseCPUNumbers("0-?", &invalid));
EXPECT_FALSE(utils.ParseCPUNumbers("1,?", &invalid));
EXPECT_FALSE(utils.ParseCPUNumbers("0-", &invalid));
EXPECT_FALSE(utils.ParseCPUNumbers("-9", &invalid));
EXPECT_FALSE(utils.ParseCPUNumbers("1-1", &invalid));
EXPECT_FALSE(utils.ParseCPUNumbers("5-1", &invalid));
}
TEST_F(SchedulerConfigurationHelperTest, WriteFlag) {
debugd::SchedulerConfigurationUtils utils{base::FilePath("/sys")};
base::FilePath target_file;
ASSERT_TRUE(base::CreateTemporaryFile(&target_file));
base::ScopedFD fd(open(target_file.value().c_str(), O_RDWR | O_CLOEXEC));
ASSERT_LE(0, fd.get());
ASSERT_TRUE(utils.WriteFlagToCPUControlFile(fd, "test"));
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(target_file, &file_contents));
EXPECT_EQ("test", file_contents);
}
TEST_F(SchedulerConfigurationHelperTest, TestSchedulers) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath cpu_root_dir =
temp_dir.GetPath().Append("devices").Append("system").Append("cpu");
// Create a simple system interface, with 4 CPUs online and present.
CreateSysInterface(cpu_root_dir, temp_dir.GetPath(), 4, 4, 4);
size_t num_cpus_disabled = 0;
debugd::SchedulerConfigurationUtils utils(temp_dir.GetPath());
ASSERT_TRUE(utils.GetControlFDs());
ASSERT_TRUE(utils.GetCPUSetFDs());
ASSERT_TRUE(utils.EnablePerformanceConfiguration(&num_cpus_disabled));
ASSERT_EQ(0U, num_cpus_disabled);
CheckPerformanceMode(cpu_root_dir);
// Now enable conservative mode.
SchedulerConfigurationUtils utils2(temp_dir.GetPath());
ASSERT_TRUE(utils2.GetControlFDs());
ASSERT_TRUE(utils2.GetCPUSetFDs());
ASSERT_TRUE(utils2.EnableConservativeConfiguration(&num_cpus_disabled));
ASSERT_EQ(2U, num_cpus_disabled);
CheckConservativeMode(cpu_root_dir);
// Before going back to performance mode, update the control files to mimick
// the kernel's actions.
base::FilePath online_cpus_file = cpu_root_dir.Append("online");
base::FilePath offline_cpus_file = cpu_root_dir.Append("offline");
std::string online_now = "0,2";
std::string offline_now = "1,3";
ASSERT_EQ(
online_now.size(),
base::WriteFile(online_cpus_file, online_now.c_str(), online_now.size()));
ASSERT_EQ(offline_now.size(),
base::WriteFile(offline_cpus_file, offline_now.c_str(),
offline_now.size()));
// Re-enable performance and test.
SchedulerConfigurationUtils utils3(temp_dir.GetPath());
ASSERT_TRUE(utils3.GetControlFDs());
ASSERT_TRUE(utils3.GetCPUSetFDs());
ASSERT_TRUE(utils3.EnablePerformanceConfiguration(&num_cpus_disabled));
ASSERT_EQ(0U, num_cpus_disabled);
CheckPerformanceMode(cpu_root_dir);
// Check the cpuset file. Because this unit test is crudely mocking the
// behavior of the CPU control files, the cpuset file ends out of date. But
// that's OK: it needs to have a valid range and not EINVAL.
base::FilePath chrome_cpuset =
temp_dir.GetPath().Append(kChromeCPUSubsetSubpath);
base::FilePath chronos_cpuset =
temp_dir.GetPath().Append(kChronosContainerCPUSubsetSubpath);
base::FilePath session_manager_cpuset =
temp_dir.GetPath().Append(kSessionManagerCPUSubsetSubpath);
for (const auto& cpuset :
{chrome_cpuset, chronos_cpuset, session_manager_cpuset}) {
std::string cpuset_contents;
ASSERT_TRUE(base::ReadFileToString(cpuset, &cpuset_contents));
EXPECT_EQ("0,2", cpuset_contents);
}
}
TEST_F(SchedulerConfigurationHelperTest, TestSchedulersWithMissingCPUs) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath cpu_root_dir =
temp_dir.GetPath().Append("devices").Append("system").Append("cpu");
// Create a filesystem with two online/present CPUs out of 8.
CreateSysInterface(cpu_root_dir, temp_dir.GetPath(), 2, 2, 8);
size_t num_cpus_disabled = 0;
// Enable conservative mode.
SchedulerConfigurationUtils utils2(temp_dir.GetPath());
ASSERT_TRUE(utils2.GetControlFDs());
ASSERT_TRUE(utils2.GetCPUSetFDs());
ASSERT_TRUE(utils2.EnableConservativeConfiguration(&num_cpus_disabled));
// The second processor (1) is disabled of the available (0-1).
ASSERT_EQ(1U, num_cpus_disabled);
CheckConservativeModeTwoCpus(cpu_root_dir);
}
} // namespace debugd