blob: c8de7cd770e071dec2f455fff7a9a91391c2ae73 [file] [log] [blame]
// Copyright 2021 The Fuchsia 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 <lib/zx/clock.h>
#include <lib/zx/job.h>
#include <lib/zx/profile.h>
#include <lib/zx/resource.h>
#include <lib/zx/status.h>
#include <lib/zx/thread.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/profile.h>
#include <zircon/syscalls/resource.h>
#include <zircon/syscalls/system.h>
#include <zircon/types.h>
#include <iterator>
#include <limits>
#include <thread>
#include <utility>
#include <vector>
#include <zxtest/zxtest.h>
extern "C" zx_handle_t get_root_resource(void);
extern "C" zx_handle_t get_system_root_resource(void);
extern "C" zx_handle_t get_mmio_root_resource(void);
namespace {
constexpr uint32_t kInvalidTopic = std::numeric_limits<uint32_t>::max();
constexpr uint32_t kInvalidCpu = std::numeric_limits<uint32_t>::max();
constexpr size_t kInvalidInfoCount = std::numeric_limits<size_t>::max();
constexpr zx_sched_deadline_params_t kTestThreadDeadlineParams = {
.capacity = ZX_MSEC(5), .relative_deadline = ZX_MSEC(20), .period = ZX_MSEC(20)};
constexpr uint32_t kTestThreadCpu = 1;
static_assert(kTestThreadCpu < ZX_CPU_SET_MAX_CPUS);
constexpr zx_cpu_set_t CpuNumToCpuSet(size_t cpu_num) {
zx_cpu_set_t cpu_set{};
cpu_set.mask[cpu_num / ZX_CPU_SET_BITS_PER_WORD] = 1 << (cpu_num % ZX_CPU_SET_BITS_PER_WORD);
return cpu_set;
}
zx::status<zx_info_thread_stats_t> GetThreadStats(const zx::thread& thread) {
zx_info_thread_stats_t info;
const zx_status_t status =
thread.get_info(ZX_INFO_THREAD_STATS, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(info);
}
zx::status<size_t> GetCpuCount() {
static zx::resource root_resource{get_root_resource()};
size_t actual, available;
const zx_status_t status =
root_resource.get_info(ZX_INFO_CPU_STATS, nullptr, 0, &actual, &available);
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(available);
}
zx::status<zx::resource> GetSystemCpuResource() {
static zx::resource system_root_resource{get_system_root_resource()};
zx::resource system_cpu_resource;
const zx_status_t status =
zx::resource::create(system_root_resource, ZX_RSRC_KIND_SYSTEM, ZX_RSRC_SYSTEM_CPU_BASE, 1,
nullptr, 0, &system_cpu_resource);
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(system_cpu_resource));
}
zx::status<zx::resource> GetSystemInfoResource() {
static zx::resource system_root_resource{get_system_root_resource()};
zx::resource system_info_resource;
const zx_status_t status =
zx::resource::create(system_root_resource, ZX_RSRC_KIND_SYSTEM, ZX_RSRC_SYSTEM_INFO_BASE, 1,
nullptr, 0, &system_info_resource);
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(system_info_resource));
}
zx::status<zx::unowned_resource> GetMmioResource() {
static zx::resource mmio_resource{get_mmio_root_resource()};
return zx::ok(mmio_resource.borrow());
}
template <typename Callable>
zx_status_t RunThread(Callable&& callable) {
zx_profile_info_t info = {};
info.flags = ZX_PROFILE_INFO_FLAG_DEADLINE | ZX_PROFILE_INFO_FLAG_CPU_MASK;
info.deadline_params = kTestThreadDeadlineParams;
info.cpu_affinity_mask = CpuNumToCpuSet(kTestThreadCpu);
zx::unowned_job root_job(zx::job::default_job());
if (!root_job->is_valid()) {
return ZX_ERR_INVALID_ARGS;
}
zx::profile profile;
zx_status_t result = zx::profile::create(*root_job, 0u, &info, &profile);
if (result != ZX_OK) {
return result;
}
std::thread worker([&callable, &result, &profile]() {
result = zx::thread::self()->set_profile(profile, 0);
if (result != ZX_OK) {
return;
}
std::forward<Callable>(callable)();
result = ZX_OK;
});
worker.join();
return result;
}
} // anonymous namespace
TEST(SystemCpu, SetPerformanceInfoArgumentValidation) {
const zx::status resource = GetSystemCpuResource();
ASSERT_TRUE(resource.is_ok());
// Test invalid handle -> ZX_ERR_BAD_HANDLE.
{
zx_cpu_performance_info_t info[] = {{0, {1, 0}}};
const zx_status_t status =
zx_system_set_performance_info(ZX_HANDLE_INVALID, kInvalidTopic, &info, std::size(info));
EXPECT_EQ(ZX_ERR_BAD_HANDLE, status);
}
// Test incorrect resource kind -> ZX_ERR_WRONG_TYPE.
{
const zx::status info_resource = GetMmioResource();
ASSERT_TRUE(info_resource.is_ok());
zx_cpu_performance_info_t info[] = {{0, {1, 0}}};
const zx_status_t status =
zx_system_set_performance_info(info_resource->get(), kInvalidTopic, &info, std::size(info));
EXPECT_EQ(ZX_ERR_WRONG_TYPE, status);
}
// Test incorrect system resource range -> ZX_ERR_OUT_OF_RANGE.
{
const zx::status info_resource = GetSystemInfoResource();
ASSERT_TRUE(info_resource.is_ok());
zx_cpu_performance_info_t info[] = {{0, {1, 0}}};
const zx_status_t status =
zx_system_set_performance_info(info_resource->get(), kInvalidTopic, &info, std::size(info));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
// Test invalid topic -> ZX_ERR_INVALID_ARGS.
{
zx_cpu_performance_info_t info[] = {{0, {1, 0}}};
const zx_status_t status =
zx_system_set_performance_info(resource->get(), kInvalidTopic, &info, std::size(info));
EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
}
// Test invalid info -> ZX_ERR_INVALID_ARGS.
{
zx_cpu_performance_info_t info[] = {{0, {1, 0}}};
const zx_status_t status = zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE,
nullptr, std::size(info));
EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
}
// Test count == 0 -> ZX_ERR_OUT_OF_RANGE.
{
zx_cpu_performance_info_t info[] = {{0, {1, 0}}};
const zx_status_t status =
zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE, &info, 0);
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
// Test info_count > num_cpus -> ZX_ERR_OUT_OF_RANGE.
{
zx_cpu_performance_info_t info[] = {{0, {1, 0}}};
const zx_status_t status = zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE,
&info, kInvalidInfoCount);
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
// Test invalid CPU number -> ZX_ERR_OUT_OF_RANGE.
{
zx_cpu_performance_info_t info[] = {{kInvalidCpu, {1, 0}}};
const zx_status_t status =
zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE, &info, std::size(info));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
// Test invalid perf scale -> ZX_ERR_OUT_OF_RANGE.
{
zx_cpu_performance_info_t info[] = {{0, {0, 0}}};
const zx_status_t status =
zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE, &info, std::size(info));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
// Test invalid sort order -> ZX_ERR_INVALID_ARGS.
if (const zx::status cpu_count = GetCpuCount(); cpu_count.is_ok() && *cpu_count >= 2) {
zx_cpu_performance_info_t info[] = {{0, {1, 0}}, {0, {1, 0}}};
const zx_status_t status =
zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE, &info, std::size(info));
EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
}
}
TEST(SystemCpu, GetPerformanceInfoArgumentValidation) {
const zx::status resource = GetSystemCpuResource();
ASSERT_TRUE(resource.is_ok());
const zx::status cpu_count = GetCpuCount();
ASSERT_TRUE(cpu_count.is_ok());
std::vector<zx_cpu_performance_info_t> info(*cpu_count);
// Test invalid handle -> ZX_ERR_BAD_HANDLE.
{
size_t count;
const zx_status_t status = zx_system_get_performance_info(ZX_HANDLE_INVALID, kInvalidTopic,
info.size(), info.data(), &count);
EXPECT_EQ(ZX_ERR_BAD_HANDLE, status);
}
// Test incorrect resource kind -> ZX_ERR_WRONG_TYPE.
{
const zx::status info_resource = GetMmioResource();
ASSERT_TRUE(info_resource.is_ok());
size_t count;
const zx_status_t status = zx_system_get_performance_info(info_resource->get(), kInvalidTopic,
info.size(), info.data(), &count);
EXPECT_EQ(ZX_ERR_WRONG_TYPE, status);
}
// Test incorrect system resource range -> ZX_ERR_OUT_OF_RANGE.
{
const zx::status info_resource = GetSystemInfoResource();
ASSERT_TRUE(info_resource.is_ok());
size_t count;
const zx_status_t status = zx_system_get_performance_info(info_resource->get(), kInvalidTopic,
info.size(), info.data(), &count);
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
// Test invalid topic -> ZX_ERR_INVALID_ARGS.
{
size_t count;
const zx_status_t status = zx_system_get_performance_info(resource->get(), kInvalidTopic,
info.size(), info.data(), &count);
EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
}
// Test info_count == 0 -> ZX_ERR_OUT_OF_RANGE.
{
size_t count;
const zx_status_t status =
zx_system_get_performance_info(resource->get(), ZX_CPU_PERF_SCALE, 0, info.data(), &count);
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
// Test info_count > num_cpus -> ZX_ERR_OUT_OF_RANGE.
{
size_t count;
const zx_status_t status = zx_system_get_performance_info(resource->get(), ZX_CPU_PERF_SCALE,
info.size() + 1, info.data(), &count);
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
// Test info_count < num_cpus -> ZX_ERR_OUT_OF_RANGE.
{
size_t count;
const zx_status_t status = zx_system_get_performance_info(resource->get(), ZX_CPU_PERF_SCALE,
info.size() - 1, info.data(), &count);
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
// Test invalid output_count -> ZX_ERR_INVALID_ARGS.
{
const zx_status_t status = zx_system_get_performance_info(resource->get(), ZX_CPU_PERF_SCALE,
info.size(), info.data(), nullptr);
EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
}
}
TEST(SystemCpu, GetPerformanceInfo) {
const zx::status resource = GetSystemCpuResource();
ASSERT_TRUE(resource.is_ok());
const zx::status cpu_count = GetCpuCount();
ASSERT_TRUE(cpu_count.is_ok());
std::vector<zx_cpu_performance_info_t> info(*cpu_count);
std::vector<zx_cpu_performance_info_t> default_info(*cpu_count);
{
size_t count = kInvalidInfoCount;
const zx_status_t status =
zx_system_get_performance_info(resource->get(), ZX_CPU_DEFAULT_PERF_SCALE,
default_info.size(), default_info.data(), &count);
ASSERT_OK(status);
ASSERT_EQ(default_info.size(), count);
uint32_t last_cpu = kInvalidCpu;
for (const auto& entry : default_info) {
EXPECT_TRUE(last_cpu == kInvalidCpu || entry.logical_cpu_number > last_cpu);
last_cpu = entry.logical_cpu_number;
EXPECT_FALSE(entry.performance_scale.integral_part == 0 &&
entry.performance_scale.fractional_part == 0);
}
}
{
size_t count = kInvalidInfoCount;
const zx_status_t status = zx_system_get_performance_info(
resource->get(), ZX_CPU_PERF_SCALE, default_info.size(), default_info.data(), &count);
ASSERT_OK(status);
ASSERT_EQ(default_info.size(), count);
uint32_t last_cpu = kInvalidCpu;
for (const auto& entry : default_info) {
EXPECT_TRUE(last_cpu == kInvalidCpu || entry.logical_cpu_number > last_cpu);
last_cpu = entry.logical_cpu_number;
EXPECT_FALSE(entry.performance_scale.integral_part == 0 &&
entry.performance_scale.fractional_part == 0);
}
}
}
// Verify that the scheduler's target preemption time is properly updated when the performance scale
// changes. Failure to maintain consistency will result in a kernel panic. See fxbug.dev/86901.
TEST(SystemCpu, TargetPreemptionTimeAssert) {
if (GetCpuCount() < kTestThreadCpu + 1) {
return;
}
const zx::status resource = GetSystemCpuResource();
ASSERT_TRUE(resource.is_ok());
const zx::status cpu_count = GetCpuCount();
ASSERT_TRUE(cpu_count.is_ok());
std::vector<zx_cpu_performance_info_t> original_performance_info(*cpu_count);
size_t count = 0;
ASSERT_OK(zx_system_get_performance_info(resource->get(), ZX_CPU_PERF_SCALE,
original_performance_info.size(),
original_performance_info.data(), &count));
EXPECT_EQ(count, original_performance_info.size());
const auto spin = [](const zx::duration spin_duration) {
const zx::time time_end = zx::clock::get_monotonic() + spin_duration;
zx::time now;
do {
now = zx::clock::get_monotonic();
} while (now < time_end);
};
ASSERT_OK(RunThread([&] {
for (int i = 0; i < 10; i++) {
// Set the perf scale to 1.0 for the start of the period.
zx_cpu_performance_info_t perf_scale_one[] = {{kTestThreadCpu, {1, 0}}};
ASSERT_OK(zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE, &perf_scale_one,
std::size(perf_scale_one)));
// Yield to start a new period.
zx::nanosleep(zx::time{0});
// Spin until half capacity is exhausted.
spin(zx::duration{kTestThreadDeadlineParams.capacity / 2});
// Set the perf scale to 0.5 for the remainder of the period.
zx_cpu_performance_info_t perf_scale_half[] = {{kTestThreadCpu, {0, 1u << 31}}};
ASSERT_OK(zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE, &perf_scale_half,
std::size(perf_scale_half)));
// Spin until after the scaled capacity is exhausted: C / 2 / 0.5 = C
spin(zx::duration{kTestThreadDeadlineParams.capacity} + zx::usec(100));
}
}));
ASSERT_OK(zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE,
original_performance_info.data(),
original_performance_info.size()));
}
TEST(SystemCpu, ScaleBandwidth) {
// TODO(fxbug.dev/85846): Disabled while flakeds are investigated.
return;
if (GetCpuCount() < kTestThreadCpu + 1) {
return;
}
const zx_status_t run_thread_result = RunThread([&] {
const zx::status resource = GetSystemCpuResource();
ASSERT_TRUE(resource.is_ok());
const zx::status cpu_count = GetCpuCount();
ASSERT_TRUE(cpu_count.is_ok());
std::vector<zx_cpu_performance_info_t> original_info(*cpu_count);
size_t count = kInvalidInfoCount;
zx_status_t result = zx_system_get_performance_info(
resource->get(), ZX_CPU_PERF_SCALE, original_info.size(), original_info.data(), &count);
const zx_cpu_performance_scale_t scale_one_half{0, 1u << 31};
zx_cpu_performance_info_t info[] = {{kTestThreadCpu, scale_one_half}};
result =
zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE, &info, std::size(info));
ASSERT_OK(result);
// Sleep for at least one period to guarantee starting in a new period.
zx::nanosleep(zx::deadline_after(zx::duration(kTestThreadDeadlineParams.period)));
const zx::status<zx_info_thread_stats_t> stats_begin = GetThreadStats(*zx::thread::self());
ASSERT_TRUE(stats_begin.is_ok());
EXPECT_EQ(kTestThreadCpu, stats_begin->last_scheduled_cpu);
// Busy wait to accumulate CPU time over for a little over 10 periods.
const zx::duration spin_duration = zx::duration(kTestThreadDeadlineParams.period) * 10 +
zx::duration(kTestThreadDeadlineParams.capacity) / 2;
const zx::time time_end = zx::clock::get_monotonic() + spin_duration;
zx::time now;
do {
now = zx::clock::get_monotonic();
} while (now < time_end);
const zx::status<zx_info_thread_stats_t> stats_end = GetThreadStats(*zx::thread::self());
ASSERT_TRUE(stats_end.is_ok());
EXPECT_EQ(kTestThreadCpu, stats_end->last_scheduled_cpu);
const zx::duration total_runtime =
zx::time(stats_end->total_runtime) - zx::time(stats_begin->total_runtime);
const zx::duration expected_runtime =
zx::duration(kTestThreadDeadlineParams.capacity) * 2 * 10 +
zx::duration(kTestThreadDeadlineParams.capacity) / 2;
const zx::duration delta_runtime = total_runtime - expected_runtime;
// Accept at most -5% variation from the expected runtime. Emulated environments may produce
// much larger apparent runtimes than expected, however, this is acceptable since this test's
// goal is to detect receiving too little runtime.
const zx::duration min_delta = expected_runtime * -5 / 100;
EXPECT_GE(delta_runtime, min_delta);
// Restore the original performance scale.
result = zx_system_set_performance_info(resource->get(), ZX_CPU_PERF_SCALE,
original_info.data(), original_info.size());
ASSERT_OK(result);
});
EXPECT_OK(run_thread_result);
}