| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/scheduler/responsiveness/calculator.h" |
| |
| #include <optional> |
| |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "content/public/browser/responsiveness_calculator_delegate.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| namespace responsiveness { |
| |
| using CongestionType = Calculator::CongestionType; |
| using StartupStage = Calculator::StartupStage; |
| using ::testing::_; |
| |
| namespace { |
| // Copied from calculator.cc. |
| constexpr int kMeasurementIntervalInMs = 30 * 1000; |
| constexpr int kCongestionThresholdInMs = 100; |
| |
| class FakeCalculator : public Calculator { |
| public: |
| using Calculator::Calculator; |
| |
| MOCK_METHOD3(EmitResponsivenessMock, |
| void(CongestionType congestion_type, |
| size_t congested_slices, |
| StartupStage startup_stage)); |
| |
| void EmitResponsiveness(CongestionType congestion_type, |
| size_t congested_slices, |
| StartupStage startup_stage, |
| uint64_t event_id) override { |
| EmitResponsivenessMock(congestion_type, congested_slices, startup_stage); |
| // Emit the histograms anyways for verification in some tests. |
| Calculator::EmitResponsiveness(congestion_type, congested_slices, |
| startup_stage, event_id); |
| } |
| |
| MOCK_METHOD5(EmitCongestedIntervalsMeasurementTraceEvent, |
| void(StartupStage startup_stage, |
| base::TimeTicks start_time, |
| base::TimeTicks end_time, |
| size_t num_congested_slices, |
| uint64_t event_id)); |
| |
| MOCK_METHOD3(EmitCongestedIntervalTraceEvent, |
| void(CongestionType congestion_type, |
| base::TimeTicks start_time, |
| base::TimeTicks end_time)); |
| |
| using Calculator::EmitResponsivenessTraceEvents; |
| using Calculator::GetLastCalculationTime; |
| }; |
| |
| } // namespace |
| |
| class MockDelegate : public ResponsivenessCalculatorDelegate { |
| public: |
| MockDelegate() = default; |
| ~MockDelegate() override = default; |
| |
| // ResponsivenessCalculatorDelegate: |
| MOCK_METHOD(void, OnMeasurementIntervalEnded, (), (override)); |
| MOCK_METHOD(void, |
| OnResponsivenessEmitted, |
| (int sample, |
| int min, |
| int exclusive_max, |
| size_t buckets), |
| (override)); |
| }; |
| |
| class ResponsivenessCalculatorTest : public testing::Test { |
| public: |
| void SetUp() override { |
| auto delegate = std::make_unique< |
| testing::NiceMock<MockDelegate>>(); // NiceMock because the delegate is |
| // only verified in one test. |
| delegate_ = delegate.get(); |
| calculator_ = std::make_unique<testing::StrictMock<FakeCalculator>>( |
| std::move(delegate)); |
| last_calculation_time_ = calculator_->GetLastCalculationTime(); |
| #if BUILDFLAG(IS_ANDROID) |
| base::android::ApplicationStatusListener::NotifyApplicationStateChange( |
| base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES); |
| base::RunLoop().RunUntilIdle(); |
| #endif |
| } |
| |
| void AddEventUI(int queue_time_in_ms, |
| int execution_start_time_in_ms, |
| int execution_finish_time_in_ms) { |
| calculator_->TaskOrEventFinishedOnUIThread( |
| last_calculation_time_ + base::Milliseconds(queue_time_in_ms), |
| last_calculation_time_ + base::Milliseconds(execution_start_time_in_ms), |
| last_calculation_time_ + |
| base::Milliseconds(execution_finish_time_in_ms)); |
| } |
| |
| void AddEventIO(int queue_time_in_ms, |
| int execution_start_time_in_ms, |
| int execution_finish_time_in_ms) { |
| calculator_->TaskOrEventFinishedOnIOThread( |
| last_calculation_time_ + base::Milliseconds(queue_time_in_ms), |
| last_calculation_time_ + base::Milliseconds(execution_start_time_in_ms), |
| last_calculation_time_ + |
| base::Milliseconds(execution_finish_time_in_ms)); |
| } |
| |
| void TriggerCalculation() { |
| AddEventUI(kMeasurementIntervalInMs + 1, kMeasurementIntervalInMs + 1, |
| kMeasurementIntervalInMs + 1); |
| last_calculation_time_ = calculator_->GetLastCalculationTime(); |
| } |
| |
| protected: |
| // This member sets up BrowserThread::IO and BrowserThread::UI. It must be the |
| // first member, as other members may depend on these abstractions. |
| content::BrowserTaskEnvironment task_environment_; |
| |
| std::unique_ptr<FakeCalculator> calculator_; |
| raw_ptr<MockDelegate> delegate_; |
| base::TimeTicks last_calculation_time_; |
| }; |
| |
| #define EXPECT_EXECUTION_CONGESTED_SLICES(num_slices, phase) \ |
| EXPECT_CALL(*calculator_, \ |
| EmitResponsivenessMock(CongestionType::kExecutionOnly, \ |
| num_slices, phase)); |
| #define EXPECT_CONGESTED_SLICES(num_slices, phase) \ |
| EXPECT_CALL(*calculator_, \ |
| EmitResponsivenessMock(CongestionType::kQueueAndExecution, \ |
| num_slices, phase)); |
| |
| // A single event executing slightly longer than kCongestionThresholdInMs. |
| TEST_F(ResponsivenessCalculatorTest, ShortExecutionCongestion) { |
| constexpr int kQueueTime = 35; |
| constexpr int kStartTime = 40; |
| constexpr int kFinishTime = kStartTime + kCongestionThresholdInMs + 5; |
| |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_EXECUTION_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // A single event queued slightly longer than kCongestionThresholdInMs. |
| TEST_F(ResponsivenessCalculatorTest, ShortQueueCongestion) { |
| constexpr int kQueueTime = 35; |
| constexpr int kStartTime = kQueueTime + kCongestionThresholdInMs + 5; |
| constexpr int kFinishTime = kStartTime + 5; |
| |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // A single event whose queuing and execution time together take longer than |
| // kCongestionThresholdInMs. |
| TEST_F(ResponsivenessCalculatorTest, ShortCombinedQueueAndExecutionCongestion) { |
| constexpr int kQueueTime = 35; |
| constexpr int kStartTime = kQueueTime + (kCongestionThresholdInMs / 2); |
| constexpr int kFinishTime = kStartTime + (kCongestionThresholdInMs / 2) + 1; |
| |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // A single event executing slightly longer than 10 * kCongestionThresholdInMs. |
| TEST_F(ResponsivenessCalculatorTest, LongExecutionCongestion) { |
| constexpr int kQueueTime = 35; |
| constexpr int kStartTime = 40; |
| constexpr int kFinishTime = kStartTime + 10 * kCongestionThresholdInMs + 5; |
| |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_EXECUTION_CONGESTED_SLICES(10, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(10u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // A single event executing slightly longer than 10 * kCongestionThresholdInMs. |
| TEST_F(ResponsivenessCalculatorTest, LongQueueCongestion) { |
| constexpr int kQueueTime = 35; |
| constexpr int kStartTime = kQueueTime + 10 * kCongestionThresholdInMs + 5; |
| constexpr int kFinishTime = kStartTime + 5; |
| |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(10u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // Events that execute in less than 100ms are not congested, regardless of start |
| // time. |
| TEST_F(ResponsivenessCalculatorTest, NoExecutionCongestion) { |
| int base_time = 30; |
| for (int i = 0; i < kCongestionThresholdInMs; ++i) { |
| AddEventUI(base_time, base_time, base_time + i); |
| } |
| |
| base_time += kCongestionThresholdInMs; |
| for (int i = 0; i < kCongestionThresholdInMs; ++i) { |
| AddEventUI(base_time + i, base_time + i, base_time + 2 * i); |
| } |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // Events that are queued and execute in less than 100ms are not congested, |
| // regardless of start time. |
| TEST_F(ResponsivenessCalculatorTest, NoQueueCongestion) { |
| int base_time = 30; |
| for (int i = 0; i < kCongestionThresholdInMs; ++i) { |
| AddEventUI(base_time, base_time + i, base_time + i); |
| } |
| |
| base_time += kCongestionThresholdInMs; |
| for (int i = 0; i < kCongestionThresholdInMs; ++i) { |
| AddEventUI(base_time + i, base_time + 2 * i, base_time + 2 * i); |
| } |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // 10 execution congestion events, but very closely overlapping. Time slices are |
| // discretized and fixed, e.g. [0 100] [100 200] [200 300]. In this test, the |
| // events all start in the [0 100] slice and end in the [100 200] slice. All of |
| // them end up marking the [100 200] slice as congested. |
| TEST_F(ResponsivenessCalculatorTest, OverlappingExecutionCongestion) { |
| int base_time = 30; |
| for (int i = 0; i < 10; ++i) { |
| const int queue_time = base_time; |
| const int start_time = base_time; |
| const int finish_time = start_time + kCongestionThresholdInMs + i; |
| AddEventUI(queue_time, start_time, finish_time); |
| } |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // 10 queue congestion events, but very closely overlapping. Time slices are |
| // discretized and fixed, e.g. [0 100] [100 200] [200 300]. In this test, the |
| // events are all queued in the [0 100] slice and start executing in the [100 |
| // 200] slice. All of them end up marking the [100 200] slice as congested. |
| TEST_F(ResponsivenessCalculatorTest, OverlappingQueueCongestion) { |
| int base_time = 30; |
| for (int i = 0; i < 10; ++i) { |
| const int queue_time = base_time; |
| const int start_time = base_time + kCongestionThresholdInMs + i; |
| const int finish_time = start_time + 1; |
| AddEventUI(queue_time, start_time, finish_time); |
| } |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // UI thread has 3 execution congestion events on slices 1, 2, 3 |
| // IO thread has 3 execution congestion events on slices 3, 4, 5, |
| // There should be a total of 5 congestion events. |
| TEST_F(ResponsivenessCalculatorTest, |
| OverlappingExecutionCongestionMultipleThreads) { |
| int base_time = 105; |
| for (int i = 0; i < 3; ++i) { |
| const int queue_time = base_time + i * kCongestionThresholdInMs; |
| const int start_time = queue_time; |
| const int finish_time = start_time + kCongestionThresholdInMs + 10; |
| AddEventUI(queue_time, start_time, finish_time); |
| } |
| |
| base_time = 305; |
| for (int i = 0; i < 3; ++i) { |
| const int queue_time = base_time + i * kCongestionThresholdInMs; |
| const int start_time = queue_time; |
| const int finish_time = start_time + kCongestionThresholdInMs + 10; |
| AddEventIO(queue_time, start_time, finish_time); |
| } |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(5u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(5u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // UI thread has 3 queue congestion events on slices 1, 2, 3 |
| // IO thread has 3 queue congestion events on slices 3, 4, 5, |
| // There should be a total of 5 congestion events. |
| TEST_F(ResponsivenessCalculatorTest, |
| OverlappingQueueCongestionMultipleThreads) { |
| int base_time = 105; |
| for (int i = 0; i < 3; ++i) { |
| const int queue_time = base_time + i * kCongestionThresholdInMs; |
| const int start_time = queue_time + kCongestionThresholdInMs + 10; |
| const int finish_time = start_time; |
| AddEventUI(queue_time, start_time, finish_time); |
| } |
| |
| base_time = 305; |
| for (int i = 0; i < 3; ++i) { |
| const int queue_time = base_time + i * kCongestionThresholdInMs; |
| const int start_time = queue_time + kCongestionThresholdInMs + 10; |
| const int finish_time = start_time; |
| AddEventIO(queue_time, start_time, finish_time); |
| } |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(5u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // Three execution congestions, each of length 2, separated by some shorter |
| // events. |
| TEST_F(ResponsivenessCalculatorTest, SeparatedExecutionCongestions) { |
| int base_time = 105; |
| |
| for (int i = 0; i < 3; ++i) { |
| { |
| const int queue_time = base_time; |
| const int start_time = base_time; |
| const int finish_time = base_time + 1; |
| AddEventUI(queue_time, start_time, finish_time); |
| } |
| { |
| const int queue_time = base_time; |
| const int start_time = base_time; |
| const int finish_time = base_time + 2 * kCongestionThresholdInMs + 1; |
| AddEventUI(queue_time, start_time, finish_time); |
| } |
| base_time += 10 * kCongestionThresholdInMs; |
| } |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(6u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(6u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| // Three queue congestions, each of length 2, separated by some shorter events. |
| TEST_F(ResponsivenessCalculatorTest, SeparatedQueueCongestions) { |
| int base_time = 105; |
| |
| for (int i = 0; i < 3; ++i) { |
| { |
| const int queue_time = base_time; |
| const int start_time = base_time + 1; |
| const int finish_time = start_time; |
| AddEventUI(queue_time, start_time, finish_time); |
| } |
| { |
| const int queue_time = base_time; |
| const int start_time = base_time + 2 * kCongestionThresholdInMs + 1; |
| const int finish_time = start_time; |
| AddEventUI(queue_time, start_time, finish_time); |
| } |
| base_time += 10 * kCongestionThresholdInMs; |
| } |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(6u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| TEST_F(ResponsivenessCalculatorTest, MultipleTrigger) { |
| int base_time = 105; |
| |
| // 3 Congestions, then trigger, then repeat. |
| for (int i = 0; i < 10; ++i) { |
| for (int j = 0; j < 3; ++j) { |
| AddEventUI(base_time, base_time, |
| base_time + 3 * kCongestionThresholdInMs + 1); |
| base_time += 3 * kCongestionThresholdInMs; |
| } |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES( |
| 9u, i == 0 ? StartupStage::kFirstInterval |
| : StartupStage::kFirstIntervalDoneWithoutFirstIdle); |
| EXPECT_CONGESTED_SLICES( |
| 9u, i == 0 ? StartupStage::kFirstInterval |
| : StartupStage::kFirstIntervalDoneWithoutFirstIdle); |
| TriggerCalculation(); |
| testing::Mock::VerifyAndClear(calculator_.get()); |
| } |
| } |
| |
| // A long delay means that the machine likely went to sleep. |
| TEST_F(ResponsivenessCalculatorTest, LongDelay) { |
| int base_time = 105; |
| AddEventUI(base_time, base_time, |
| base_time + 3 * kCongestionThresholdInMs + 1); |
| base_time += 10 * kMeasurementIntervalInMs; |
| AddEventUI(base_time, base_time, base_time + 1); |
| |
| EXPECT_CALL(*calculator_, EmitResponsivenessMock(_, _, _)).Times(0); |
| } |
| |
| // A long event means that the machine likely went to sleep. |
| TEST_F(ResponsivenessCalculatorTest, LongEvent) { |
| int base_time = 105; |
| AddEventUI(base_time, base_time, base_time + 10 * kMeasurementIntervalInMs); |
| |
| EXPECT_CALL(*calculator_, EmitResponsivenessMock(_, _, _)).Times(0); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Metric should not be recorded when application is in background. |
| TEST_F(ResponsivenessCalculatorTest, ApplicationInBackground) { |
| constexpr int kQueueTime = 35; |
| constexpr int kStartTime = 40; |
| constexpr int kFinishTime = kStartTime + kCongestionThresholdInMs + 5; |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| |
| base::android::ApplicationStatusListener::NotifyApplicationStateChange( |
| base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES); |
| base::RunLoop().RunUntilIdle(); |
| |
| AddEventUI(kQueueTime, kStartTime + 1, kFinishTime + 1); |
| EXPECT_CALL(*calculator_, EmitResponsivenessMock(_, _, _)).Times(0); |
| TriggerCalculation(); |
| } |
| #endif |
| |
| TEST_F(ResponsivenessCalculatorTest, StartupStages) { |
| constexpr int kQueueTime = 35; |
| constexpr int kStartTime = kQueueTime + 10 * kCongestionThresholdInMs + 5; |
| constexpr int kFinishTime = kStartTime + 5; |
| |
| std::optional<base::HistogramTester> histograms; |
| |
| // Queue congestion event during the first kMeasurementInterval. |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_CALL(*calculator_, |
| EmitResponsivenessMock(CongestionType::kExecutionOnly, 0, |
| StartupStage::kFirstInterval)); |
| EXPECT_CALL(*calculator_, |
| EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u, |
| StartupStage::kFirstInterval)); |
| histograms.emplace(); |
| TriggerCalculation(); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0, |
| 1); |
| histograms->ExpectUniqueSample( |
| "Browser.MainThreadsCongestion.RunningOnly.Initial", 0, 1); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Periodic", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0); |
| |
| // Queue congestion event during a few kMeasurementInterval (without having |
| // seen OnFirstIdle()). Neither .Initial nor .Periodic |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_CALL( |
| *calculator_, |
| EmitResponsivenessMock(CongestionType::kExecutionOnly, 0, |
| StartupStage::kFirstIntervalDoneWithoutFirstIdle)); |
| EXPECT_CALL( |
| *calculator_, |
| EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u, |
| StartupStage::kFirstIntervalDoneWithoutFirstIdle)); |
| histograms.emplace(); |
| TriggerCalculation(); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0, |
| 1); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Initial", 0); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Periodic", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0); |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_CALL( |
| *calculator_, |
| EmitResponsivenessMock(CongestionType::kExecutionOnly, 0, |
| StartupStage::kFirstIntervalDoneWithoutFirstIdle)); |
| EXPECT_CALL( |
| *calculator_, |
| EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u, |
| StartupStage::kFirstIntervalDoneWithoutFirstIdle)); |
| histograms.emplace(); |
| TriggerCalculation(); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0, |
| 1); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Initial", 0); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Periodic", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0); |
| |
| // OnFirstIdle() eventually during a kMeasurementInterval. Same as above, last |
| // one of these. |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| calculator_->OnFirstIdle(); |
| EXPECT_CALL( |
| *calculator_, |
| EmitResponsivenessMock(CongestionType::kExecutionOnly, 0, |
| StartupStage::kFirstIntervalDoneWithoutFirstIdle)); |
| EXPECT_CALL( |
| *calculator_, |
| EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u, |
| StartupStage::kFirstIntervalDoneWithoutFirstIdle)); |
| histograms.emplace(); |
| TriggerCalculation(); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0, |
| 1); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Initial", 0); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Periodic", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0); |
| |
| // Events in intervals after OnFirstIdle(). congested3.Initial still no |
| // .Periodic. |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_CALL(*calculator_, EmitResponsivenessMock( |
| CongestionType::kExecutionOnly, 0, |
| StartupStage::kFirstIntervalAfterFirstIdle)); |
| EXPECT_CALL(*calculator_, EmitResponsivenessMock( |
| CongestionType::kQueueAndExecution, 10u, |
| StartupStage::kFirstIntervalAfterFirstIdle)); |
| histograms.emplace(); |
| TriggerCalculation(); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0, |
| 1); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Initial", 0); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Periodic", 0); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion", 10, 1); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.Initial", 10, |
| 1); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0); |
| |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_CALL(*calculator_, |
| EmitResponsivenessMock(CongestionType::kExecutionOnly, 0, |
| StartupStage::kPeriodic)); |
| EXPECT_CALL(*calculator_, |
| EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u, |
| StartupStage::kPeriodic)); |
| histograms.emplace(); |
| TriggerCalculation(); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0, |
| 1); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Initial", 0); |
| histograms->ExpectUniqueSample( |
| "Browser.MainThreadsCongestion.RunningOnly.Periodic", 0, 1); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion", 10, 1); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.Periodic", 10, |
| 1); |
| } |
| |
| TEST_F(ResponsivenessCalculatorTest, FastStartupStages) { |
| constexpr int kQueueTime = 35; |
| constexpr int kStartTime = kQueueTime + 10 * kCongestionThresholdInMs + 5; |
| constexpr int kFinishTime = kStartTime + 5; |
| |
| std::optional<base::HistogramTester> histograms; |
| |
| // OnFirstIdle() right away during the first kMeasurementInterval. Still |
| // considered as kFirstInterval, but second interval will go straight |
| // to kFirstIntervalAfterFirstIdle. |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| calculator_->OnFirstIdle(); |
| EXPECT_CALL(*calculator_, |
| EmitResponsivenessMock(CongestionType::kExecutionOnly, 0, |
| StartupStage::kFirstInterval)); |
| EXPECT_CALL(*calculator_, |
| EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u, |
| StartupStage::kFirstInterval)); |
| histograms.emplace(); |
| TriggerCalculation(); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0, |
| 1); |
| histograms->ExpectUniqueSample( |
| "Browser.MainThreadsCongestion.RunningOnly.Initial", 0, 1); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Periodic", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0); |
| |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_CALL(*calculator_, EmitResponsivenessMock( |
| CongestionType::kExecutionOnly, 0, |
| StartupStage::kFirstIntervalAfterFirstIdle)); |
| EXPECT_CALL(*calculator_, EmitResponsivenessMock( |
| CongestionType::kQueueAndExecution, 10u, |
| StartupStage::kFirstIntervalAfterFirstIdle)); |
| histograms.emplace(); |
| TriggerCalculation(); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0, |
| 1); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Initial", 0); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Periodic", 0); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion", 10, 1); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.Initial", 10, |
| 1); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0); |
| |
| AddEventUI(kQueueTime, kStartTime, kFinishTime); |
| EXPECT_CALL(*calculator_, |
| EmitResponsivenessMock(CongestionType::kExecutionOnly, 0, |
| StartupStage::kPeriodic)); |
| EXPECT_CALL(*calculator_, |
| EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u, |
| StartupStage::kPeriodic)); |
| histograms.emplace(); |
| TriggerCalculation(); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0, |
| 1); |
| histograms->ExpectTotalCount( |
| "Browser.MainThreadsCongestion.RunningOnly.Initial", 0); |
| histograms->ExpectUniqueSample( |
| "Browser.MainThreadsCongestion.RunningOnly.Periodic", 0, 1); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion", 10, 1); |
| histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0); |
| histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.Periodic", 10, |
| 1); |
| } |
| |
| // An event execution that crosses a measurement interval boundary should count |
| // towards both measurement intervals. |
| TEST_F(ResponsivenessCalculatorTest, ExecutionCrossesBoundary) { |
| // Dummy event so that Calculator doesn't think the process is suspended. |
| { |
| const int kTime = 0.5 * kMeasurementIntervalInMs; |
| AddEventUI(kTime, kTime, kTime); |
| } |
| |
| // The event goes from [29801, 30150]. It should count as 1 congestion in the |
| // first measurement interval and 2 in the second. |
| { |
| EXPECT_EXECUTION_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| const int queue_time = |
| kMeasurementIntervalInMs - 2 * kCongestionThresholdInMs + 1; |
| const int start_time = queue_time; |
| const int finish_time = |
| kMeasurementIntervalInMs + 1.5 * kCongestionThresholdInMs; |
| AddEventUI(queue_time, start_time, finish_time); |
| } |
| |
| // Dummy event so that Calculator doesn't think the process is suspended. |
| { |
| const int kTime = 1.5 * kMeasurementIntervalInMs; |
| AddEventUI(kTime, kTime, kTime); |
| } |
| |
| // Trigger another calculation. |
| EXPECT_EXECUTION_CONGESTED_SLICES( |
| 2u, StartupStage::kFirstIntervalDoneWithoutFirstIdle); |
| EXPECT_CONGESTED_SLICES(2u, StartupStage::kFirstIntervalDoneWithoutFirstIdle); |
| |
| const int kTime = 2 * kMeasurementIntervalInMs + 1; |
| AddEventUI(kTime, kTime, kTime); |
| } |
| |
| // An event queuing that crosses a measurement interval boundary should count |
| // towards both measurement intervals. |
| TEST_F(ResponsivenessCalculatorTest, QueuingCrossesBoundary) { |
| // Dummy event so that Calculator doesn't think the process is suspended. |
| { |
| const int kTime = 0.5 * kMeasurementIntervalInMs; |
| AddEventUI(kTime, kTime, kTime); |
| } |
| |
| // The event goes from [29801, 30150]. It should count as 1 congestion in the |
| // first measurement interval and 2 in the second. |
| { |
| EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| const int queue_time = |
| kMeasurementIntervalInMs - 2 * kCongestionThresholdInMs + 1; |
| const int start_time = |
| kMeasurementIntervalInMs + 1.5 * kCongestionThresholdInMs; |
| const int finish_time = start_time; |
| AddEventUI(queue_time, start_time, finish_time); |
| } |
| |
| // Dummy event so that Calculator doesn't think the process is suspended. |
| { |
| const int kTime = 1.5 * kMeasurementIntervalInMs; |
| AddEventUI(kTime, kTime, kTime); |
| } |
| |
| // Trigger another calculation. |
| EXPECT_EXECUTION_CONGESTED_SLICES( |
| 0u, StartupStage::kFirstIntervalDoneWithoutFirstIdle); |
| EXPECT_CONGESTED_SLICES(2u, StartupStage::kFirstIntervalDoneWithoutFirstIdle); |
| |
| const int kTime = 2 * kMeasurementIntervalInMs + 1; |
| AddEventUI(kTime, kTime, kTime); |
| } |
| |
| // Events may not be ordered by start or end time. |
| TEST_F(ResponsivenessCalculatorTest, UnorderedEvents) { |
| // We add the following tasks: |
| // [100, 100, 250] |
| // [150, 150, 300] |
| // [50, 50, 200] |
| // [50, 50, 390] <- A |
| // |
| // [1100, 1250, 1251] |
| // [1150, 1300, 1301] |
| // [1050, 1200, 1201] |
| // [1050, 1390, 1391] <- B |
| // |
| // The execution congestion in A subsumes all other execution congestions. The |
| // queue congestion in B subsumes all other queue congestions. |
| AddEventUI(100, 100, 250); |
| AddEventUI(150, 150, 300); |
| AddEventUI(50, 50, 200); |
| AddEventUI(50, 50, 390); |
| |
| AddEventUI(1100, 1250, 1251); |
| AddEventUI(1150, 1300, 1301); |
| AddEventUI(1050, 1200, 1201); |
| AddEventUI(1050, 1390, 1391); |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(3u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(6u, StartupStage::kFirstInterval); |
| TriggerCalculation(); |
| } |
| |
| TEST_F(ResponsivenessCalculatorTest, EmitResponsivenessTraceEventsEmpty) { |
| constexpr base::TimeTicks kStartTime = base::TimeTicks(); |
| constexpr base::TimeTicks kFinishTime = |
| kStartTime + base::Milliseconds(kMeasurementIntervalInMs); |
| const std::set<int> congested_slices; |
| |
| uint64_t event_id = 42; |
| EXPECT_CALL(*calculator_, EmitCongestedIntervalsMeasurementTraceEvent( |
| StartupStage::kPeriodic, _, _, _, event_id)) |
| .Times(0); |
| |
| calculator_->EmitResponsivenessTraceEvents( |
| CongestionType::kQueueAndExecution, StartupStage::kPeriodic, kStartTime, |
| kFinishTime, congested_slices, event_id); |
| } |
| |
| TEST_F(ResponsivenessCalculatorTest, |
| EmitResponsivenessTraceEventsExecutionOnly) { |
| constexpr base::TimeDelta kSliceInterval = |
| base::Milliseconds(kCongestionThresholdInMs); |
| constexpr base::TimeTicks kStartTime = base::TimeTicks(); |
| constexpr base::TimeTicks kFinishTime = |
| kStartTime + base::Milliseconds(kMeasurementIntervalInMs); |
| const std::set<int> congested_slices = {1}; |
| |
| uint64_t event_id = 42; |
| EXPECT_CALL(*calculator_, EmitCongestedIntervalsMeasurementTraceEvent( |
| StartupStage::kPeriodic, _, _, _, event_id)) |
| .Times(0); |
| |
| EXPECT_CALL(*calculator_, |
| EmitCongestedIntervalTraceEvent(CongestionType::kExecutionOnly, |
| kStartTime + 1 * kSliceInterval, |
| kStartTime + 2 * kSliceInterval)); |
| |
| calculator_->EmitResponsivenessTraceEvents( |
| CongestionType::kExecutionOnly, StartupStage::kPeriodic, kStartTime, |
| kFinishTime, congested_slices, event_id); |
| } |
| |
| TEST_F(ResponsivenessCalculatorTest, EmitResponsivenessTraceEvents) { |
| constexpr base::TimeDelta kSliceInterval = |
| base::Milliseconds(kCongestionThresholdInMs); |
| constexpr base::TimeTicks kStartTime = base::TimeTicks(); |
| constexpr base::TimeTicks kFinishTime = |
| kStartTime + base::Milliseconds(kMeasurementIntervalInMs); |
| |
| const std::set<int> congested_slices = {3, 4, 5, 12, 15}; |
| |
| uint64_t event_id = 42; |
| EXPECT_CALL(*calculator_, |
| EmitCongestedIntervalsMeasurementTraceEvent( |
| StartupStage::kPeriodic, kStartTime, kFinishTime, 5, 42)); |
| |
| EXPECT_CALL(*calculator_, EmitCongestedIntervalTraceEvent( |
| CongestionType::kQueueAndExecution, |
| kStartTime + 3 * kSliceInterval, |
| kStartTime + 6 * kSliceInterval)); |
| EXPECT_CALL(*calculator_, EmitCongestedIntervalTraceEvent( |
| CongestionType::kQueueAndExecution, |
| kStartTime + 12 * kSliceInterval, |
| kStartTime + 13 * kSliceInterval)); |
| EXPECT_CALL(*calculator_, EmitCongestedIntervalTraceEvent( |
| CongestionType::kQueueAndExecution, |
| kStartTime + 15 * kSliceInterval, |
| kStartTime + 16 * kSliceInterval)); |
| |
| calculator_->EmitResponsivenessTraceEvents( |
| CongestionType::kQueueAndExecution, StartupStage::kPeriodic, kStartTime, |
| kFinishTime, congested_slices, event_id); |
| } |
| |
| TEST_F(ResponsivenessCalculatorTest, Delegate) { |
| calculator_->OnFirstIdle(); |
| |
| // To have a valid interval, there needs to be at least 30 seconds that |
| // passed, during which there was at least one event. |
| int interval_start = 0; |
| int interval_mid = 15000; |
| int interval_end = kMeasurementIntervalInMs + 1000; |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval); |
| |
| // OnResponsivenessEmitted() is not invoked for the first interval. |
| EXPECT_CALL(*delegate_, OnMeasurementIntervalEnded()); |
| EXPECT_CALL(*delegate_, OnResponsivenessEmitted(_, _, _, _)).Times(0); |
| |
| AddEventUI(interval_start, interval_start, interval_start); |
| AddEventUI(interval_mid, interval_mid, |
| interval_mid + kCongestionThresholdInMs + 20); |
| AddEventUI(interval_end, interval_end, interval_end); |
| |
| // Repeat the same interval immediately following the first one. |
| interval_start += interval_end; |
| interval_mid += interval_end; |
| interval_end += interval_end; |
| |
| EXPECT_EXECUTION_CONGESTED_SLICES(1u, |
| StartupStage::kFirstIntervalAfterFirstIdle); |
| EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstIntervalAfterFirstIdle); |
| |
| // OnResponsivenessEmitted() is invoked for subsequent intervals. |
| { |
| testing::InSequence in_sequence; |
| EXPECT_CALL(*delegate_, OnMeasurementIntervalEnded()); |
| EXPECT_CALL(*delegate_, OnResponsivenessEmitted(_, _, _, _)); |
| } |
| |
| AddEventUI(interval_start, interval_start, interval_start); |
| AddEventUI(interval_mid, interval_mid, |
| interval_mid + kCongestionThresholdInMs + 20); |
| AddEventUI(interval_end, interval_end, interval_end); |
| } |
| |
| } // namespace responsiveness |
| } // namespace content |