blob: 0224b2946c8385588ad7ee8b8e26f57d970dde59 [file] [log] [blame]
// Copyright 2018 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/fit/defer.h>
#include <lib/standalone-test/standalone.h>
#include <lib/zx/job.h>
#include <lib/zx/pager.h>
#include <lib/zx/profile.h>
#include <lib/zx/resource.h>
#include <lib/zx/result.h>
#include <lib/zx/thread.h>
#include <zircon/errors.h>
#include <zircon/syscalls/profile.h>
#include <zircon/syscalls/resource.h>
#include <zircon/syscalls/types.h>
#include <zircon/time.h>
#include <thread>
#include <zxtest/zxtest.h>
namespace profile {
namespace {
zx::result<zx::resource> GetSystemProfileResource() {
zx::resource system_profile_resource;
const zx_status_t status =
zx::resource::create(*standalone::GetSystemResource(), ZX_RSRC_KIND_SYSTEM,
ZX_RSRC_SYSTEM_PROFILE_BASE, 1, nullptr, 0, &system_profile_resource);
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(system_profile_resource));
}
zx_profile_info_t MakeSchedulerProfileInfo(int32_t priority) {
zx_profile_info_t info = {};
info.flags = ZX_PROFILE_INFO_FLAG_PRIORITY;
info.priority = priority;
return info;
}
zx_profile_info_t MakeSchedulerProfileInfo(const zx_sched_deadline_params_t& params) {
zx_profile_info_t info = {};
info.flags = ZX_PROFILE_INFO_FLAG_DEADLINE;
info.deadline_params = params;
return info;
}
zx_profile_info_t MakeCpuMaskProfile(uint64_t mask) {
zx_profile_info_t info = {};
info.flags = ZX_PROFILE_INFO_FLAG_CPU_MASK;
info.cpu_affinity_mask.mask[0] = mask;
return info;
}
zx_profile_info_t MakeMemoryPriorityProfile(int32_t priority) {
zx_profile_info_t info = {};
info.flags = ZX_PROFILE_INFO_FLAG_MEMORY_PRIORITY;
info.priority = priority;
return info;
}
size_t GetCpuCount() {
size_t actual, available;
auto system_resource = standalone::GetSystemResource();
zx::result<zx::resource> result =
standalone::GetSystemResourceWithBase(system_resource, ZX_RSRC_SYSTEM_INFO_BASE);
ZX_ASSERT(result.status_value() == ZX_OK);
zx::resource info_resource = std::move(result.value());
zx_status_t status = info_resource.get_info(ZX_INFO_CPU_STATS, nullptr, 0, &actual, &available);
ZX_ASSERT(status == ZX_OK);
return available;
}
uint64_t GetAffinityMask(const zx::thread& thread) {
zx_info_thread_t info;
zx_status_t status = thread.get_info(ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr);
ZX_ASSERT(status == ZX_OK);
return info.cpu_affinity_mask.mask[0];
}
uint32_t GetLastScheduledCpu(const zx::thread& thread) {
zx_info_thread_stats_t info;
zx_status_t status = thread.get_info(ZX_INFO_THREAD_STATS, &info, sizeof(info), nullptr, nullptr);
ZX_ASSERT(status == ZX_OK);
return info.last_scheduled_cpu;
}
// Tests in this file rely that the default job is the root job.
TEST(SchedulerProfileTest, CreateProfileWithDefaultPriorityIsOk) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo(ZX_PRIORITY_DEFAULT);
zx::profile profile;
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithLowestPriorityIsOk) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo(ZX_PRIORITY_LOWEST);
zx::profile profile;
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithLowPriorityIsOk) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo(ZX_PRIORITY_LOW);
zx::profile profile;
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithHighPriorityIsOk) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo(ZX_PRIORITY_HIGH);
zx::profile profile;
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithHighestPriorityIsOk) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo(ZX_PRIORITY_HIGHEST);
zx::profile profile;
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateFairProfileWithNoInheritIsOk) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo(ZX_PRIORITY_DEFAULT);
zx::profile profile;
profile_info.flags |= ZX_PROFILE_INFO_FLAG_NO_INHERIT;
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithPriorityExceedingHighestIsInvalidArgs) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo(ZX_PRIORITY_HIGHEST + 1);
zx::profile profile;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithPriorityBelowLowestIsInvalidArgs) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo(ZX_PRIORITY_LOWEST - 1);
zx::profile profile;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithDeadlineIsOk) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo({ZX_MSEC(1), ZX_MSEC(8), ZX_MSEC(10)});
zx::profile profile;
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithZeroCapacityIsInvalidArgs) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo({ZX_MSEC(0), ZX_MSEC(8), ZX_MSEC(10)});
zx::profile profile;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithDeadlineBelowCapacityIsInvalidArgs) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo({ZX_MSEC(8), ZX_MSEC(1), ZX_MSEC(10)});
zx::profile profile;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithPeriodBelowDeadlineIsInvalidArgs) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo({ZX_MSEC(8), ZX_MSEC(10), ZX_MSEC(1)});
zx::profile profile;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileOnNonProfileResourceIsAccessDenied) {
zx_profile_info_t profile_info = MakeSchedulerProfileInfo(ZX_PRIORITY_DEFAULT);
zx::profile profile;
zx::resource power_resource;
zx::resource::create(*standalone::GetSystemResource(), ZX_RSRC_KIND_SYSTEM,
ZX_RSRC_SYSTEM_POWER_BASE, 1, nullptr, 0, &power_resource);
ASSERT_EQ(ZX_ERR_ACCESS_DENIED, zx::profile::create(power_resource, 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithNonZeroOptionsIsInvalidArgs) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo(ZX_PRIORITY_DEFAULT);
zx::profile profile;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 1u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, CreateProfileWithDeadlineAndNoInheritIsInvalidArgs) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = MakeSchedulerProfileInfo({ZX_MSEC(1), ZX_MSEC(8), ZX_MSEC(10)});
zx::profile profile;
profile_info.flags |= ZX_PROFILE_INFO_FLAG_NO_INHERIT;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(SchedulerProfileTest, SetThreadPriorityIsOk) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
std::atomic<const char*> error = nullptr;
std::atomic<zx_status_t> result = ZX_OK;
zx::profile profile_1;
zx_profile_info_t info_1 = MakeSchedulerProfileInfo(ZX_PRIORITY_LOWEST);
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &info_1, &profile_1));
zx::profile profile_2;
zx_profile_info_t info_2 = MakeSchedulerProfileInfo(ZX_PRIORITY_HIGH);
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &info_2, &profile_2));
zx::profile profile_3;
zx_profile_info_t info_3 = MakeSchedulerProfileInfo({ZX_MSEC(8), ZX_MSEC(16), ZX_MSEC(16)});
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &info_3, &profile_3));
// Operate on a background thread, just in case a failure changes the priority of the main
// thread.
std::thread worker(
[](zx::profile first, zx::profile second, zx::profile third, std::atomic<const char*>* error,
std::atomic<zx_status_t>* result) {
*result = zx::thread::self()->set_profile(first, 0);
if (*result != ZX_OK) {
*error = "Failed to set first profile on thread";
return;
}
std::this_thread::yield();
*result = zx::thread::self()->set_profile(second, 0);
if (*result != ZX_OK) {
*error = "Failed to set second profile on thread";
return;
}
std::this_thread::yield();
*result = zx::thread::self()->set_profile(third, 0);
if (*result != ZX_OK) {
*error = "Failed to set third profile on thread";
return;
}
},
std::move(profile_1), std::move(profile_2), std::move(profile_3), &error, &result);
// Wait until is completed.
worker.join();
ASSERT_OK(result.load(), "%s", error.load());
}
TEST(ProfileTest, CreateProfileWithDefaultInitializedProfileInfoIsError) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = {};
zx::profile profile;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(ProfileTest, CreateProfileWithMutuallyExclusiveFlagsIsInvalidArgs) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx_profile_info_t profile_info = {};
profile_info.flags = ZX_PROFILE_INFO_FLAG_PRIORITY | ZX_PROFILE_INFO_FLAG_DEADLINE;
zx::profile profile;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
TEST(ProfileTest, CreateProfileWithNoProfileInfoIsInvalidArgs) {
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
zx::profile profile;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, nullptr, &profile));
}
TEST(ProfileTest, CreateProfileWithInvalidHandleIsBadHandle) {
zx::profile profile;
ASSERT_EQ(ZX_ERR_BAD_HANDLE,
zx::profile::create(zx::resource(ZX_HANDLE_INVALID), 0u, nullptr, &profile));
}
zx_status_t RunThreadWithProfile(const zx::profile& profile,
const std::function<zx_status_t()>& body) {
zx_status_t result;
std::thread worker([&body, &result, &profile]() {
result = zx::thread::self()->set_profile(profile, 0);
if (result != ZX_OK) {
return;
}
result = body();
});
worker.join();
return result;
}
TEST(CpuMaskProfile, EmptyMaskIsValid) {
zx::profile profile;
zx_profile_info_t profile_info = MakeCpuMaskProfile(0);
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
// Ensure that the thread can still run, despite the affinity mask
// having no valid CPUs in it. (The kernel will just fall back to
// its own choice of CPUs if this mask can't be respected.)
ASSERT_OK(RunThreadWithProfile(profile, []() {
EXPECT_EQ(GetAffinityMask(*zx::thread::self()), 0);
EXPECT_NE(GetLastScheduledCpu(*zx::thread::self()), ZX_INFO_INVALID_CPU);
return ZX_OK;
}));
}
TEST(CpuMaskProfile, ApplyProfile) {
const size_t num_cpus = GetCpuCount();
ASSERT_LT(num_cpus, ZX_CPU_SET_BITS_PER_WORD,
"Test assumes system running with less than %d cores.", ZX_CPU_SET_BITS_PER_WORD);
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
for (size_t i = 0; i < num_cpus; i++) {
zx_profile_info_t profile_info = MakeCpuMaskProfile(1 << i);
zx::profile profile;
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
// Ensure that the correct mask was applied.
ASSERT_OK(RunThreadWithProfile(profile, [i]() {
EXPECT_EQ(GetAffinityMask(*zx::thread::self()), (1 << i));
EXPECT_EQ(GetLastScheduledCpu(*zx::thread::self()), i);
return ZX_OK;
}));
}
}
TEST(MemoryPriorityProfile, InvalidPriorities) {
constexpr int32_t kBadPriorities[] = {ZX_PRIORITY_LOWEST, ZX_PRIORITY_LOW, ZX_PRIORITY_HIGHEST};
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
for (const int32_t prio : kBadPriorities) {
zx::profile profile;
zx_profile_info_t profile_info = MakeMemoryPriorityProfile(prio);
EXPECT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
}
TEST(MemoryPriorityProfile, MemoryOrThread) {
const uint32_t kInvalidWith[] = {ZX_PROFILE_INFO_FLAG_PRIORITY, ZX_PROFILE_INFO_FLAG_CPU_MASK,
ZX_PROFILE_INFO_FLAG_DEADLINE};
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
for (const uint32_t invalid_with : kInvalidWith) {
zx_profile_info_t profile_info = {};
profile_info.flags = ZX_PROFILE_INFO_FLAG_MEMORY_PRIORITY | invalid_with;
zx::profile profile;
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
}
}
TEST(MemoryPriorityProfile, ApplyProfile) {
// Create the two profiles we will need.
zx::profile profile_high, profile_default;
zx_profile_info_t profile_info = MakeMemoryPriorityProfile(ZX_PRIORITY_HIGH);
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile_high));
profile_info = MakeMemoryPriorityProfile(ZX_PRIORITY_DEFAULT);
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile_default));
// To ensure there are some candidate reclaimable pages, create and map in a pager backed VMO.
zx::port port;
ASSERT_OK(zx::port::create(0, &port));
zx::pager pager;
ASSERT_OK(zx::pager::create(0, &pager));
zx::vmo pager_vmo;
ASSERT_OK(pager.create_vmo(0, port, 0, zx_system_get_page_size(), &pager_vmo));
zx_vaddr_t addr;
ASSERT_OK(zx::vmar::root_self()->map(0, 0, pager_vmo, 0, zx_system_get_page_size(), &addr));
auto unmap = fit::defer([&addr]() {
// Cleanup the mapping we created.
zx::vmar::root_self()->unmap(addr, zx_system_get_page_size());
});
// Helper to supply pages to the VMO. Since these pages are reclaimable there is a small chance
// that they get evicted during the execution of the test so we re-supply at a few different
// points.
auto supply = [&]() {
zx::vmo aux_vmo;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &aux_vmo));
uint64_t val = 42;
EXPECT_OK(aux_vmo.write(&val, 0, sizeof(val)));
EXPECT_OK(pager.supply_pages(pager_vmo, 0, zx_system_get_page_size(), aux_vmo, 0));
};
// Start with the pages supplied so that they are there for the initial query.
supply();
zx_info_kmem_stats_extended_t stats;
auto system_resource = standalone::GetSystemResource();
zx::result<zx::resource> result =
standalone::GetSystemResourceWithBase(system_resource, ZX_RSRC_SYSTEM_INFO_BASE);
ASSERT_OK(result.status_value());
zx::resource info_resource = std::move(result.value());
EXPECT_OK(
info_resource.get_info(ZX_INFO_KMEM_STATS_EXTENDED, &stats, sizeof(stats), nullptr, nullptr));
const uint64_t prev = stats.vmo_reclaim_disabled_bytes;
EXPECT_OK(zx::vmar::root_self()->set_profile(profile_high, 0));
// Applying the profile should have caused our pages to no longer be reclaimable. The pager backed
// VMO we mapped means we know it's definitely non-zero, but we do not know if there are more.
// In between the previous supply and setting the profile, the pages could have been evicted, so
// re-supply them.
supply();
EXPECT_OK(
info_resource.get_info(ZX_INFO_KMEM_STATS_EXTENDED, &stats, sizeof(stats), nullptr, nullptr));
EXPECT_GT(stats.vmo_reclaim_disabled_bytes, prev);
// Applying the default priority should undo the reclamation change.
EXPECT_OK(zx::vmar::root_self()->set_profile(profile_default, 0));
EXPECT_OK(
info_resource.get_info(ZX_INFO_KMEM_STATS_EXTENDED, &stats, sizeof(stats), nullptr, nullptr));
EXPECT_EQ(stats.vmo_reclaim_disabled_bytes, prev);
}
TEST(MemoryPriorityProfile, Rights) {
zx::profile profile;
zx_profile_info_t profile_info = MakeMemoryPriorityProfile(ZX_PRIORITY_DEFAULT);
zx::result<zx::resource> maybe_profile_rsrc = GetSystemProfileResource();
ASSERT_OK(maybe_profile_rsrc.status_value());
ASSERT_OK(zx::profile::create(maybe_profile_rsrc.value(), 0u, &profile_info, &profile));
// Duplicate the vmar handle to have valid and invalid permissions.
zx::vmar vmar_valid, vmar_invalid;
ASSERT_OK(zx::vmar::root_self()->duplicate(ZX_RIGHT_OP_CHILDREN, &vmar_valid));
ASSERT_OK(zx::vmar::root_self()->duplicate(0, &vmar_invalid));
EXPECT_OK(vmar_valid.set_profile(profile, 0));
EXPECT_EQ(ZX_ERR_ACCESS_DENIED, vmar_invalid.set_profile(profile, 0));
}
} // namespace
} // namespace profile