blob: 717c50f6606405196fc373336f296fe481a6a175 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <fbl/mutex.h>
#include <kernel/cpu.h>
#include <kernel/percpu.h>
#include <kernel/scheduler.h>
#include <kernel/thread.h>
#include <ktl/array.h>
#include <ktl/unique_ptr.h>
#include <lib/load_balancer_percpu.h>
#include <lib/unittest/unittest.h>
namespace {
using load_balancer::CpuState;
class TestingContext {
public:
static percpu& Get(cpu_num_t cpu_num) {
ASSERT(cpu_num < percpus_.size());
return *percpus_[cpu_num];
}
static cpu_num_t CurrentCpu() {
return current_cpu_;
}
static ktl::array<ktl::unique_ptr<percpu>, 4> CreatePercpus() {
ktl::array<ktl::unique_ptr<percpu>, 4> out;
int id = 0;
fbl::AllocChecker ac;
for (auto& ptr : out) {
ptr = ktl::make_unique<percpu>(&ac, id);
ASSERT(ac.check());
percpus_[id] = ptr.get();
id++;
}
return out;
}
static void UpdateAll(const CpuState::CpuSet& cpus, zx_duration_t threshold) {
for (auto& percpu : percpus_) {
percpu->load_balancer.Update(cpus, threshold);
}
}
// Use mutex to lock accesses. In theory there should only ever be one instance
// of this test running but since we are using static methods we want to
// ensure that there is no racing happening.
static DECLARE_MUTEX(TestingContext) lock_;
static ktl::array<percpu*, 4> percpus_;
static cpu_num_t current_cpu_;
};
ktl::array<percpu*, 4> TestingContext::percpus_{0};
cpu_num_t TestingContext::current_cpu_ = 0;
decltype(TestingContext::lock_) TestingContext::lock_;
template<typename T>
bool AllEqual(T* a, T* b, size_t count) {
for (size_t i = 0; i < count; i++) {
if (a[i] != b[i]) {
printf("%zu :: expected %u found %u\n", i, a[i], b[i]);
return false;
}
}
return true;
}
} // namespace
// Being static members of this class allow the methods to access private
// members on the Scheduler.
class LoadBalancerTest {
public:
// Simple test, the last cpu is under threshold so we use it.
static bool FindCpuLast() {
BEGIN_TEST;
// Lock the testing context to this testcase.
Guard<fbl::Mutex> guard{&TestingContext::lock_};
auto percpus = TestingContext::CreatePercpus();
Thread thread;
thread.scheduler_state_.last_cpu_ = 1;
percpus[1]->load_balancer.Update({}, 10000);
percpus[1]->scheduler.total_expected_runtime_ns_ = SchedNs(100);
cpu_num_t selected = load_balancer::FindTargetCpuLocked<TestingContext>(&thread);
EXPECT_EQ(1u, selected);
END_TEST;
}
// The last cpu is unset so we will use the first cpu in the current
// processor's list.
static bool FindCpuInitial() {
BEGIN_TEST;
// Lock the testing context to this testcase.
Guard<fbl::Mutex> guard{&TestingContext::lock_};
auto percpus = TestingContext::CreatePercpus();
constexpr auto kCurrCpu = 2;
TestingContext::current_cpu_ = kCurrCpu;
Thread thread;
// thread..last_cpu_ is undefined, like a new thread on the system.
percpus[kCurrCpu]->load_balancer.Update({.cpus = {3,2,1,0}, .cpu_count = 4}, 10000);
percpus[kCurrCpu]->scheduler.total_expected_runtime_ns_ = SchedNs(100);
cpu_num_t selected =
load_balancer::FindTargetCpuLocked<TestingContext, TestingContext::CurrentCpu>(&thread);
EXPECT_EQ(3u, selected);
TestingContext::current_cpu_ = 0;
END_TEST;
}
static bool FindCpuFirstUnderThreshold() {
BEGIN_TEST;
constexpr auto kLastCpu = 1;
const auto kThreshold = 1'000'000;
// Lock the testing context to this testcase.
Guard<fbl::Mutex> guard{&TestingContext::lock_};
auto percpus = TestingContext::CreatePercpus();
Thread thread;
thread.scheduler_state_.last_cpu_ = kLastCpu;
TestingContext::UpdateAll({.cpus = {3,2,1,0}, .cpu_count = 4}, kThreshold);
percpus[3]->scheduler.total_expected_runtime_ns_ = SchedNs(kThreshold + 1);
percpus[2]->scheduler.total_expected_runtime_ns_ = SchedNs(kThreshold + 1);
percpus[1]->scheduler.total_expected_runtime_ns_ = SchedNs(kThreshold + 1);
percpus[0]->scheduler.total_expected_runtime_ns_ = SchedNs(kThreshold - 1);
cpu_num_t selected =
load_balancer::FindTargetCpuLocked<TestingContext, TestingContext::CurrentCpu>(&thread);
EXPECT_EQ(0u, selected);
END_TEST;
}
static bool FindCpuLowestLoad() {
BEGIN_TEST;
constexpr auto kLastCpu = 1;
const auto kThreshold = 1'000'000;
// Lock the testing context to this testcase.
Guard<fbl::Mutex> guard{&TestingContext::lock_};
auto percpus = TestingContext::CreatePercpus();
Thread thread;
thread.scheduler_state_.last_cpu_ = kLastCpu;
TestingContext::UpdateAll({.cpus = {3,2,1,0}, .cpu_count = 4}, kThreshold);
percpus[3]->scheduler.total_expected_runtime_ns_ = SchedNs(kThreshold + 2);
percpus[2]->scheduler.total_expected_runtime_ns_ = SchedNs(kThreshold + 1);
percpus[1]->scheduler.total_expected_runtime_ns_ = SchedNs(kThreshold + 3);
percpus[0]->scheduler.total_expected_runtime_ns_ = SchedNs(kThreshold + 4);
cpu_num_t selected =
load_balancer::FindTargetCpuLocked<TestingContext, TestingContext::CurrentCpu>(&thread);
EXPECT_EQ(2u, selected);
END_TEST;
}
};
UNITTEST_START_TESTCASE(load_balancer_tests)
UNITTEST("Test selecting the last cpu if it is under threshold.",
LoadBalancerTest::FindCpuLast)
UNITTEST("Test selecting the current cpus best match if it is under threshold.",
LoadBalancerTest::FindCpuInitial)
UNITTEST("Test selecting the first cpu from the list that is under the threshold.",
LoadBalancerTest::FindCpuFirstUnderThreshold)
UNITTEST("Test selecting the cpu with the lowest load.",
LoadBalancerTest::FindCpuLowestLoad)
UNITTEST_END_TESTCASE(load_balancer_tests, "load_balancer",
"Tests for the periodic thread load balancer.")