blob: 20236c120f0d693f18bf311bc0f7fdaee8a1ad70 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/schedqos/dbus_schedqos_state_handler.h"
#include <memory>
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "chromeos/ash/components/dbus/resourced/fake_resourced_client.h"
#include "chromeos/ash/components/dbus/resourced/resourced_client.h"
#include "dbus/dbus_result.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/resource_manager/dbus-constants.h"
using ::testing::ElementsAre;
using ::testing::FieldsAre;
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
namespace ash {
namespace {
base::Process LaunchFakeProcess() {
return base::LaunchProcess({"sh", "-c", "while true; do sleep 1; done"},
base::LaunchOptions());
}
} // namespace
class DBusSchedQOSStateHandlerTest : public testing::Test {
protected:
void SetUp() override {
resourced_client_ = ResourcedClient::InitializeFake();
handler_.reset(DBusSchedQOSStateHandler::Create(
base::SequencedTaskRunner::GetCurrentDefault()));
process_ = base::Process::Current();
}
void TearDown() override {
handler_.reset();
resourced_client_ = nullptr;
ResourcedClient::Shutdown();
}
// The handler under test.
base::Process process_;
std::unique_ptr<DBusSchedQOSStateHandler> handler_;
raw_ptr<FakeResourcedClient> resourced_client_ = nullptr;
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
};
TEST_F(DBusSchedQOSStateHandlerTest, CanSetProcessPriority) {
EXPECT_TRUE(handler_->CanSetProcessPriority());
EXPECT_TRUE(process_.CanSetPriority());
}
TEST_F(DBusSchedQOSStateHandlerTest, InitializeProcessPriority) {
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 0ul);
process_.InitializePriority();
task_environment_.RunUntilIdle();
EXPECT_THAT(resourced_client_->GetProcessStateHistory(),
ElementsAre(Pair(process_.Pid(),
resource_manager::ProcessState::kNormal)));
}
TEST_F(DBusSchedQOSStateHandlerTest, RecoverFromUnavailableService) {
// WaitForServiceToBeAvailable() notifies false and then true.
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(false));
task_environment_.RunUntilIdle();
task_environment_.FastForwardBy(base::Seconds(10));
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
process_.InitializePriority();
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 1ul);
}
TEST_F(DBusSchedQOSStateHandlerTest, SetProcessPriority) {
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
process_.InitializePriority();
base::Process dummy_process = base::Process::Open(1);
dummy_process.InitializePriority();
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 2ul);
process_.SetPriority(base::Process::Priority::kUserBlocking);
task_environment_.RunUntilIdle();
dummy_process.SetPriority(base::Process::Priority::kBestEffort);
task_environment_.RunUntilIdle();
dummy_process.SetPriority(base::Process::Priority::kUserVisible);
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 5ul);
EXPECT_THAT(
absl::MakeSpan(resourced_client_->GetProcessStateHistory()).last(3),
ElementsAre(
Pair(process_.Pid(), resource_manager::ProcessState::kNormal),
Pair(dummy_process.Pid(),
resource_manager::ProcessState::kBackground),
Pair(dummy_process.Pid(), resource_manager::ProcessState::kNormal)));
}
TEST_F(DBusSchedQOSStateHandlerTest,
SetProcessPriorityBeforeResourcedAvailable) {
process_.InitializePriority();
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kUserBlocking));
base::Process dummy_process1 = base::Process::Open(1);
dummy_process1.InitializePriority();
ASSERT_TRUE(dummy_process1.SetPriority(base::Process::Priority::kBestEffort));
// A process with InitializePriority() without SetPriority().
base::Process dummy_process2 = base::Process::Open(2);
dummy_process2.InitializePriority();
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 0ul);
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
EXPECT_THAT(
resourced_client_->GetProcessStateHistory(),
UnorderedElementsAre(
Pair(process_.Pid(), resource_manager::ProcessState::kNormal),
Pair(dummy_process1.Pid(),
resource_manager::ProcessState::kBackground),
Pair(dummy_process2.Pid(), resource_manager::ProcessState::kNormal)));
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kBestEffort));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 4ul);
EXPECT_THAT(
resourced_client_->GetProcessStateHistory()[3],
Pair(process_.Pid(), resource_manager::ProcessState::kBackground));
}
TEST_F(DBusSchedQOSStateHandlerTest, SetProcessPriorityBeforeInitialize) {
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 0ul);
EXPECT_FALSE(process_.SetPriority(base::Process::Priority::kUserBlocking));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 0ul);
process_.InitializePriority();
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 1ul);
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kBestEffort));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 2ul);
EXPECT_THAT(
resourced_client_->GetProcessStateHistory()[1],
Pair(process_.Pid(), resource_manager::ProcessState::kBackground));
}
TEST_F(DBusSchedQOSStateHandlerTest, SetProcessPriorityAfterForgetPriority) {
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
process_.InitializePriority();
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 1ul);
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kUserBlocking));
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 2ul);
process_.ForgetPriority();
EXPECT_FALSE(process_.SetPriority(base::Process::Priority::kBestEffort));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 2ul);
}
TEST_F(DBusSchedQOSStateHandlerTest, SetProcessPriorityRetryOnDisconnect) {
process_.InitializePriority();
base::Process dummy_process1 = base::Process::Open(1);
dummy_process1.InitializePriority();
base::Process dummy_process2 = base::Process::Open(2);
dummy_process2.InitializePriority();
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 3ul);
resourced_client_->SetProcessStateResult(
dbus::DBusResult::kErrorServiceUnknown);
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kBestEffort));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 4ul);
EXPECT_THAT(
resourced_client_->GetProcessStateHistory()[3],
Pair(process_.Pid(), resource_manager::ProcessState::kBackground));
EXPECT_EQ(process_.GetPriority(), base::Process::Priority::kBestEffort);
EXPECT_TRUE(dummy_process1.SetPriority(base::Process::Priority::kBestEffort));
task_environment_.RunUntilIdle();
EXPECT_EQ(dummy_process1.GetPriority(), base::Process::Priority::kBestEffort);
EXPECT_TRUE(
dummy_process1.SetPriority(base::Process::Priority::kUserBlocking));
task_environment_.RunUntilIdle();
EXPECT_EQ(dummy_process1.GetPriority(),
base::Process::Priority::kUserBlocking);
// DBus request is not sent until it reconnects to resourced.
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 4ul);
resourced_client_->SetProcessStateResult(dbus::DBusResult::kSuccess);
// When resourced is reconnected, retry the request.
EXPECT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 6ul);
EXPECT_THAT(
absl::MakeSpan(resourced_client_->GetProcessStateHistory()).last(2),
UnorderedElementsAre(
Pair(process_.Pid(), resource_manager::ProcessState::kBackground),
Pair(dummy_process1.Pid(), resource_manager::ProcessState::kNormal)));
}
TEST_F(DBusSchedQOSStateHandlerTest,
SetProcessPriorityDuringServiceUnavailable) {
resourced_client_->SetProcessStateResult(
dbus::DBusResult::kErrorServiceUnknown);
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
process_.InitializePriority();
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 1ul);
// WaitForServiceToBeAvailable() notifies false and then true.
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(false));
task_environment_.RunUntilIdle();
// Schedqos request gets pending.
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kBestEffort));
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 1ul);
// The request is re-sent when the service is ready.
resourced_client_->SetProcessStateResult(dbus::DBusResult::kSuccess);
task_environment_.FastForwardBy(base::Seconds(10));
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 2ul);
EXPECT_THAT(
resourced_client_->GetProcessStateHistory()[1],
Pair(process_.Pid(), resource_manager::ProcessState::kBackground));
}
TEST_F(DBusSchedQOSStateHandlerTest, SetProcessPriorityUMA) {
base::HistogramTester histogram_tester;
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
resourced_client_->DelaySetProcessStateResult(base::Microseconds(123));
process_.InitializePriority();
task_environment_.RunUntilIdle();
task_environment_.FastForwardBy(base::Microseconds(123));
task_environment_.RunUntilIdle();
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.SetProcessStateLatency", 123, 1);
resourced_client_->DelaySetProcessStateResult(base::Microseconds(456));
process_.SetPriority(base::Process::Priority::kUserBlocking);
task_environment_.RunUntilIdle();
task_environment_.FastForwardBy(base::Microseconds(456));
task_environment_.RunUntilIdle();
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.SetProcessStateLatency", 456, 1);
}
TEST_F(DBusSchedQOSStateHandlerTest, SetProcessPriorityPidReuseDetection) {
base::HistogramTester histogram_tester;
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
process_.InitializePriority();
task_environment_.RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"Scheduling.DBusSchedQoS.PidReusedOnSetProcessState",
DBusSchedQOSStateHandler::PidReuseResult::kNotPidReuseOnSuccess, 1);
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kUserBlocking));
task_environment_.RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"Scheduling.DBusSchedQoS.PidReusedOnSetProcessState",
DBusSchedQOSStateHandler::PidReuseResult::kNotPidReuseOnSuccess, 2);
base::Process process2 = LaunchFakeProcess();
process2.InitializePriority();
task_environment_.RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"Scheduling.DBusSchedQoS.PidReusedOnSetProcessState",
DBusSchedQOSStateHandler::PidReuseResult::kNotPidReuseOnSuccess, 3);
ASSERT_TRUE(process2.Terminate(-1, true));
ASSERT_TRUE(process2.SetPriority(base::Process::Priority::kUserBlocking));
// process2 is terminated before it sends D-Bus request, but D-Bus request
// succeeds. It means the PID is reused.
task_environment_.RunUntilIdle();
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetProcessState",
DBusSchedQOSStateHandler::PidReuseResult::kPidReuseOnSuccess, 1);
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetProcessState",
DBusSchedQOSStateHandler::PidReuseResult::kNotPidReuseOnSuccess, 3);
base::Process process3 = LaunchFakeProcess();
process3.InitializePriority();
task_environment_.RunUntilIdle();
resourced_client_->DelaySetProcessStateResult(base::Microseconds(100));
ASSERT_TRUE(process3.SetPriority(base::Process::Priority::kUserBlocking));
task_environment_.RunUntilIdle();
// If the process terminates after resourced updates scheduler settings and
// before the response arrives to Chrome, the case is not considered as PID
// reuse.
ASSERT_TRUE(process3.Terminate(-1, true));
task_environment_.FastForwardBy(base::Microseconds(100));
task_environment_.RunUntilIdle();
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetProcessState",
DBusSchedQOSStateHandler::PidReuseResult::kPidReuseOnSuccess, 1);
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetProcessState",
DBusSchedQOSStateHandler::PidReuseResult::kNotPidReuseOnSuccess, 5);
}
TEST_F(DBusSchedQOSStateHandlerTest,
SetProcessPriorityPidReuseDetectionOnFail) {
base::HistogramTester histogram_tester;
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
process_.InitializePriority();
task_environment_.RunUntilIdle();
resourced_client_->SetProcessStateResult(dbus::DBusResult::kErrorFailed);
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kUserBlocking));
task_environment_.RunUntilIdle();
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetProcessState",
DBusSchedQOSStateHandler::PidReuseResult::kPidReuseOnFail, 0);
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetProcessState",
DBusSchedQOSStateHandler::PidReuseResult::kNotPidReuseOnFail, 1);
// It is hard to reproduce PID reuse.
}
TEST_F(DBusSchedQOSStateHandlerTest, GetProcessPriority) {
// Without initializing the priority, the default priority is returned.
EXPECT_EQ(process_.GetPriority(), base::Process::Priority::kUserBlocking);
process_.InitializePriority();
// Default priority is base::Process::Priority::kUserBlocking.
EXPECT_EQ(process_.GetPriority(), base::Process::Priority::kUserBlocking);
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kBestEffort));
// Even before the D-Bus request is sent, the cached priority is updated.
EXPECT_EQ(process_.GetPriority(), base::Process::Priority::kBestEffort);
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 0ul);
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
base::Process dummy_process = base::Process::Open(1);
dummy_process.InitializePriority();
ASSERT_TRUE(dummy_process.SetPriority(base::Process::Priority::kBestEffort));
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kUserBlocking));
// Caches priorities of multiple processes.
EXPECT_EQ(dummy_process.GetPriority(), base::Process::Priority::kBestEffort);
EXPECT_EQ(process_.GetPriority(), base::Process::Priority::kUserBlocking);
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kUserVisible));
// base::Process::Priority::kUserVisible is translated as
// base::Process::Priority::kUserBlocking on ChromeOS.
EXPECT_EQ(process_.GetPriority(), base::Process::Priority::kUserBlocking);
process_.ForgetPriority();
dummy_process.ForgetPriority();
// On ForgetPriority(), cached priority is cleared.
EXPECT_EQ(process_.GetPriority(), base::Process::Priority::kUserBlocking);
EXPECT_EQ(dummy_process.GetPriority(),
base::Process::Priority::kUserBlocking);
}
TEST_F(DBusSchedQOSStateHandlerTest, SetThreadType) {
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
process_.InitializePriority();
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetThreadStateHistory().size(), 1ul);
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(100),
base::ThreadType::kBackground, base::IsViaIPC(false));
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(101),
base::ThreadType::kUtility, base::IsViaIPC(false));
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(103),
base::ThreadType::kDefault, base::IsViaIPC(false));
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(104),
base::ThreadType::kDisplayCritical, base::IsViaIPC(false));
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(105),
base::ThreadType::kInteractive, base::IsViaIPC(false));
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(106),
base::ThreadType::kRealtimeAudio, base::IsViaIPC(false));
task_environment_.RunUntilIdle();
EXPECT_THAT(
resourced_client_->GetThreadStateHistory(),
ElementsAre(
// InitializePriority() sends request for the main thread.
FieldsAre(process_.Pid(), base::PlatformThreadId(process_.Pid()),
resource_manager::ThreadState::kBalanced),
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(100),
resource_manager::ThreadState::kBackground),
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(101),
resource_manager::ThreadState::kUtility),
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(103),
resource_manager::ThreadState::kBalanced),
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(104),
resource_manager::ThreadState::kUrgent),
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(105),
resource_manager::ThreadState::kUrgent),
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(106),
resource_manager::ThreadState::kUrgentBursty)));
}
TEST_F(DBusSchedQOSStateHandlerTest, SetThreadTypeBeforeResourcedAvailable) {
process_.InitializePriority();
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kBestEffort));
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(100),
base::ThreadType::kBackground, base::IsViaIPC(false));
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(101),
base::ThreadType::kUtility, base::IsViaIPC(false));
base::Process dummy_process1 = base::Process::Open(1);
dummy_process1.InitializePriority();
base::PlatformThread::SetThreadType(
dummy_process1.Pid(), base::PlatformThreadId::ForTest(103),
base::ThreadType::kDefault, base::IsViaIPC(false));
base::Process dummy_process2 = base::Process::Open(2);
dummy_process2.InitializePriority();
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 0ul);
ASSERT_EQ(resourced_client_->GetThreadStateHistory().size(), 0ul);
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
EXPECT_THAT(
resourced_client_->GetProcessStateHistory(),
UnorderedElementsAre(
Pair(process_.Pid(), resource_manager::ProcessState::kBackground),
Pair(dummy_process1.Pid(), resource_manager::ProcessState::kNormal),
Pair(dummy_process2.Pid(), resource_manager::ProcessState::kNormal)));
EXPECT_THAT(
resourced_client_->GetThreadStateHistory(),
UnorderedElementsAre(
FieldsAre(process_.Pid(), base::PlatformThreadId(process_.Pid()),
resource_manager::ThreadState::kBalanced),
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(100),
resource_manager::ThreadState::kBackground),
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(101),
resource_manager::ThreadState::kUtility),
FieldsAre(dummy_process1.Pid(),
base::PlatformThreadId(dummy_process1.Pid()),
resource_manager::ThreadState::kBalanced),
FieldsAre(dummy_process1.Pid(), base::PlatformThreadId::ForTest(103),
resource_manager::ThreadState::kBalanced),
FieldsAre(dummy_process2.Pid(),
base::PlatformThreadId(dummy_process2.Pid()),
resource_manager::ThreadState::kBalanced)));
}
TEST_F(DBusSchedQOSStateHandlerTest, SetThreadTypeBeforeInitialize) {
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetThreadStateHistory().size(), 0ul);
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(100),
base::ThreadType::kBackground, base::IsViaIPC(false));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetThreadStateHistory().size(), 0ul);
process_.InitializePriority();
task_environment_.RunUntilIdle();
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(101),
base::ThreadType::kUtility, base::IsViaIPC(false));
task_environment_.RunUntilIdle();
EXPECT_THAT(
resourced_client_->GetThreadStateHistory(),
ElementsAre(
FieldsAre(process_.Pid(), base::PlatformThreadId(process_.Pid()),
resource_manager::ThreadState::kBalanced),
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(101),
resource_manager::ThreadState::kUtility)));
}
TEST_F(DBusSchedQOSStateHandlerTest, SetThreadTypeAfterForgetPriority) {
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
process_.InitializePriority();
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetThreadStateHistory().size(), 1ul);
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(100),
base::ThreadType::kBackground, base::IsViaIPC(false));
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetThreadStateHistory().size(), 2ul);
process_.ForgetPriority();
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(100),
base::ThreadType::kUtility, base::IsViaIPC(false));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetThreadStateHistory().size(), 2ul);
}
TEST_F(DBusSchedQOSStateHandlerTest, SetThreadTypeRetryOnDisconnect) {
process_.InitializePriority();
base::Process dummy_process1 = base::Process::Open(1);
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(100),
base::ThreadType::kBackground, base::IsViaIPC(false));
dummy_process1.InitializePriority();
base::Process dummy_process2 = base::Process::Open(2);
dummy_process2.InitializePriority();
base::Process dummy_process3 = base::Process::Open(3);
dummy_process3.InitializePriority();
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
ASSERT_EQ(resourced_client_->GetThreadStateHistory().size(), 5ul);
ASSERT_EQ(resourced_client_->GetProcessStateHistory().size(), 4ul);
resourced_client_->SetThreadStateResult(
dbus::DBusResult::kErrorServiceUnknown);
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(101),
base::ThreadType::kUtility, base::IsViaIPC(false));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetThreadStateHistory().size(), 6ul);
EXPECT_THAT(resourced_client_->GetThreadStateHistory()[5],
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(101),
resource_manager::ThreadState::kUtility));
base::PlatformThread::SetThreadType(
dummy_process1.Pid(), base::PlatformThreadId::ForTest(103),
base::ThreadType::kUtility, base::IsViaIPC(false));
base::PlatformThread::SetThreadType(
dummy_process1.Pid(), base::PlatformThreadId::ForTest(103),
base::ThreadType::kRealtimeAudio, base::IsViaIPC(false));
ASSERT_TRUE(process_.SetPriority(base::Process::Priority::kBestEffort));
ASSERT_TRUE(
dummy_process2.SetPriority(base::Process::Priority::kUserBlocking));
task_environment_.RunUntilIdle();
// DBus request is not sent until it reconnects to resourced.
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 4ul);
EXPECT_EQ(resourced_client_->GetThreadStateHistory().size(), 6ul);
resourced_client_->SetProcessStateResult(dbus::DBusResult::kSuccess);
// When resourced is reconnected, retry the request.
EXPECT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
EXPECT_EQ(resourced_client_->GetProcessStateHistory().size(), 6ul);
EXPECT_EQ(resourced_client_->GetThreadStateHistory().size(), 8ul);
EXPECT_THAT(
absl::MakeSpan(resourced_client_->GetProcessStateHistory()).last(2),
UnorderedElementsAre(
Pair(process_.Pid(), resource_manager::ProcessState::kBackground),
Pair(dummy_process2.Pid(), resource_manager::ProcessState::kNormal)));
EXPECT_THAT(
absl::MakeSpan(resourced_client_->GetThreadStateHistory()).last(2),
UnorderedElementsAre(
FieldsAre(process_.Pid(), base::PlatformThreadId::ForTest(101),
resource_manager::ThreadState::kUtility),
FieldsAre(dummy_process1.Pid(), base::PlatformThreadId::ForTest(103),
resource_manager::ThreadState::kUrgentBursty)));
}
TEST_F(DBusSchedQOSStateHandlerTest, SetThreadTypeUMA) {
base::HistogramTester histogram_tester;
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
process_.InitializePriority();
task_environment_.RunUntilIdle();
resourced_client_->DelaySetThreadStateResult(base::Microseconds(123));
base::PlatformThread::SetThreadType(
process_.Pid(), base::PlatformThreadId::ForTest(100),
base::ThreadType::kBackground, base::IsViaIPC(false));
task_environment_.RunUntilIdle();
task_environment_.FastForwardBy(base::Microseconds(123));
task_environment_.RunUntilIdle();
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.SetThreadStateLatency", 123, 1);
}
TEST_F(DBusSchedQOSStateHandlerTest, SetThreadTypePidReuseDetection) {
base::HistogramTester histogram_tester;
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
// Use a thread in another process because using a thread in this process and
// base::PlatformThread::Join() do not guarantee that the thread is
// terminated.
base::Process process = LaunchFakeProcess();
process.InitializePriority();
task_environment_.RunUntilIdle();
base::PlatformThread::SetThreadType(
process.Pid(), base::PlatformThreadId(process.Pid()),
base::ThreadType::kBackground, base::IsViaIPC(false));
task_environment_.RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"Scheduling.DBusSchedQoS.PidReusedOnSetThreadState",
DBusSchedQOSStateHandler::PidReuseResult::kNotPidReuseOnSuccess, 2);
process.Terminate(-1, true);
base::PlatformThread::SetThreadType(
process.Pid(), base::PlatformThreadId(process.Pid()),
base::ThreadType::kDefault, base::IsViaIPC(false));
// fake thread is terminated before it sends D-Bus request, but D-Bus request
// succeeds. It means the PID is reused.
task_environment_.RunUntilIdle();
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetThreadState",
DBusSchedQOSStateHandler::PidReuseResult::kPidReuseOnSuccess, 1);
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetThreadState",
DBusSchedQOSStateHandler::PidReuseResult::kNotPidReuseOnSuccess, 2);
base::Process process2 = LaunchFakeProcess();
process2.InitializePriority();
task_environment_.RunUntilIdle();
resourced_client_->DelaySetThreadStateResult(base::Microseconds(100));
base::PlatformThread::SetThreadType(
process2.Pid(), base::PlatformThreadId(process2.Pid()),
base::ThreadType::kDefault, base::IsViaIPC(false));
task_environment_.RunUntilIdle();
// If the thread terminates after resourced updates scheduler settings and
// before the response arrives to Chrome, the case is not considered as PID
// reuse.
process2.Terminate(-1, true);
task_environment_.FastForwardBy(base::Microseconds(100));
task_environment_.RunUntilIdle();
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetThreadState",
DBusSchedQOSStateHandler::PidReuseResult::kPidReuseOnSuccess, 1);
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetThreadState",
DBusSchedQOSStateHandler::PidReuseResult::kNotPidReuseOnSuccess, 4);
}
TEST_F(DBusSchedQOSStateHandlerTest, SetThreadTypePidReuseDetectionOnFail) {
base::HistogramTester histogram_tester;
ASSERT_TRUE(resourced_client_->TriggerServiceAvailable(true));
task_environment_.RunUntilIdle();
base::Process process = LaunchFakeProcess();
process.InitializePriority();
task_environment_.RunUntilIdle();
resourced_client_->SetThreadStateResult(dbus::DBusResult::kErrorFailed);
base::PlatformThread::SetThreadType(
process.Pid(), base::PlatformThreadId(process.Pid()),
base::ThreadType::kBackground, base::IsViaIPC(false));
task_environment_.RunUntilIdle();
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetThreadState",
DBusSchedQOSStateHandler::PidReuseResult::kPidReuseOnFail, 0);
histogram_tester.ExpectBucketCount(
"Scheduling.DBusSchedQoS.PidReusedOnSetThreadState",
DBusSchedQOSStateHandler::PidReuseResult::kNotPidReuseOnFail, 1);
// It is hard to reproduce PID reuse.
}
} // namespace ash