| // 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 |