| // Copyright 2019 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/jank_monitor_impl.h" |
| |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/test/bind.h" |
| #include "base/test/test_mock_time_task_runner.h" |
| #include "content/browser/scheduler/responsiveness/native_event_observer.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| namespace responsiveness { |
| |
| class TestObserver : public JankMonitor::Observer { |
| public: |
| void OnJankStarted() override { jank_started_called_++; } |
| void OnJankStopped() override { jank_stopped_called_++; } |
| |
| int jank_started_called() { return jank_started_called_; } |
| int jank_stopped_called() { return jank_stopped_called_; } |
| |
| private: |
| int jank_started_called_ = 0; |
| int jank_stopped_called_ = 0; |
| }; |
| |
| class TestMetricSource : public MetricSource { |
| public: |
| explicit TestMetricSource(Delegate* delegate) : MetricSource(delegate) {} |
| ~TestMetricSource() override {} |
| |
| std::unique_ptr<NativeEventObserver> CreateNativeEventObserver() override { |
| return nullptr; |
| } |
| }; |
| |
| class TestJankMonitor : public JankMonitorImpl { |
| public: |
| TestJankMonitor() {} |
| |
| bool destroy_on_monitor_thread_called() { |
| return destroy_on_monitor_thread_called_; |
| } |
| |
| void SetOnDestroyedCallback(base::OnceClosure on_destroyed) { |
| on_destroyed_ = std::move(on_destroyed); |
| } |
| |
| using JankMonitorImpl::timer_running; |
| |
| protected: |
| ~TestJankMonitor() override { |
| if (on_destroyed_) |
| std::move(on_destroyed_).Run(); |
| } |
| |
| std::unique_ptr<MetricSource> CreateMetricSource() override { |
| return std::make_unique<TestMetricSource>(this); |
| } |
| |
| void DestroyOnMonitorThread() override { |
| destroy_on_monitor_thread_called_ = true; |
| JankMonitorImpl::DestroyOnMonitorThread(); |
| } |
| |
| private: |
| bool destroy_on_monitor_thread_called_ = false; |
| base::OnceClosure on_destroyed_; |
| scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_; |
| }; |
| |
| class JankMonitorTest : public testing::Test { |
| public: |
| JankMonitorTest() |
| : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| ~JankMonitorTest() override {} |
| |
| void SetUp() override { |
| monitor_ = base::MakeRefCounted<TestJankMonitor>(); |
| monitor_->SetUp(); |
| monitor_->AddObserver(&test_observer_); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| void TearDown() override { |
| if (!monitor_) // Already teared down. |
| return; |
| monitor_->RemoveObserver(&test_observer_); |
| monitor_->Destroy(); |
| task_environment_.RunUntilIdle(); |
| monitor_ = nullptr; |
| } |
| |
| protected: |
| content::BrowserTaskEnvironment task_environment_; |
| scoped_refptr<TestJankMonitor> monitor_; |
| TestObserver test_observer_; |
| int expected_jank_started_ = 0; |
| int expected_jank_stopped_ = 0; |
| }; |
| |
| // Test life cycle management of the jank monitor: DestroyOnMonitorThread() |
| // and dtor. |
| TEST_F(JankMonitorTest, LifeCycle) { |
| bool monitor_destroyed = false; |
| auto closure = |
| base::BindLambdaForTesting([&]() { monitor_destroyed = true; }); |
| monitor_->SetOnDestroyedCallback(std::move(closure)); |
| |
| EXPECT_FALSE(monitor_->destroy_on_monitor_thread_called()); |
| |
| // Test that the monitor thread is destroyed. |
| monitor_->RemoveObserver(&test_observer_); |
| monitor_->Destroy(); |
| task_environment_.RunUntilIdle(); |
| EXPECT_TRUE(monitor_->destroy_on_monitor_thread_called()); |
| |
| // Release the last reference to TestJankMonitor. Check that it doesn't leak. |
| monitor_ = nullptr; |
| EXPECT_TRUE(monitor_destroyed); |
| } |
| |
| #define VALIDATE_TEST_OBSERVER_CALLS() \ |
| do { \ |
| EXPECT_EQ(test_observer_.jank_started_called(), expected_jank_started_); \ |
| EXPECT_EQ(test_observer_.jank_stopped_called(), expected_jank_stopped_); \ |
| } while (0) |
| |
| // Test monitor with UI thread janks. |
| TEST_F(JankMonitorTest, JankUIThread) { |
| auto janky_task = [&]() { |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| // This is a janky task that runs for 1.5 seconds. |
| expected_jank_started_++; |
| task_environment_.FastForwardBy(base::Milliseconds(1500)); |
| |
| // Monitor should observe that the jank has started. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| expected_jank_stopped_++; |
| }; |
| |
| // Post a janky task to the UI thread. Number of callback calls should be |
| // incremented by 1. |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, |
| base::BindLambdaForTesting(janky_task)); |
| task_environment_.RunUntilIdle(); |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| |
| // Post a non janky task. Number of callback calls should remain the same. |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, base::DoNothing()); |
| task_environment_.RunUntilIdle(); |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| |
| // Post a janky task again. Monitor thread timer should fire again. Number of |
| // callback calls should be incremented by 1 again. |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, |
| base::BindLambdaForTesting(janky_task)); |
| task_environment_.RunUntilIdle(); |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| } |
| |
| // Test monitor with an IO thread jank. |
| TEST_F(JankMonitorTest, JankIOThread) { |
| auto janky_task = [&]() { |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| |
| // This is a janky task that runs for 1.5 seconds. |
| expected_jank_started_++; |
| task_environment_.FastForwardBy(base::Milliseconds(1500)); |
| |
| // Monitor should observe that the jank has started. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| |
| expected_jank_stopped_++; |
| }; |
| |
| // Post a janky task to the IO thread. This should increment the number of |
| // callback calls by 1. |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindLambdaForTesting(janky_task)); |
| task_environment_.RunUntilIdle(); |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| } |
| |
| // Test monitor with a reentrant UI thread task. The reentrant task shouldn't |
| // be reported by the monitor. |
| TEST_F(JankMonitorTest, JankUIThreadReentrant) { |
| auto janky_task = [&]() { |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| |
| // This is a janky task that runs for 1.5 seconds. |
| expected_jank_started_++; |
| task_environment_.FastForwardBy(base::Milliseconds(1500)); |
| |
| // Monitor should observe that the jank has started. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| |
| auto nested_janky_task = [&]() { |
| // This also janks the current thread. |
| task_environment_.FastForwardBy(base::Milliseconds(1500)); |
| |
| // The callback shouldn't be called. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| }; |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindLambdaForTesting(nested_janky_task)); |
| // Spin a nested run loop to run |nested_janky_task|. |
| base::RunLoop(base::RunLoop::Type::kNestableTasksAllowed).RunUntilIdle(); |
| expected_jank_stopped_++; |
| }; |
| |
| // Post a janky task to the UI thread. Number of callback calls should be |
| // incremented by 1. |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, |
| base::BindLambdaForTesting(janky_task)); |
| task_environment_.RunUntilIdle(); |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| } |
| |
| // Test that the jank monitor shouldn't report a jank if a nested runloop is |
| // responsive. |
| TEST_F(JankMonitorTest, ReentrantResponsive) { |
| auto enclosing_task = [&]() { |
| // Run 5 responsive tasks in the inner runloop. |
| for (int i = 0; i < 5; i++) { |
| auto nested_responsive_task = [&]() { |
| task_environment_.FastForwardBy(base::Milliseconds(999)); |
| |
| // The callback shouldn't be called. |expected_jank_started_| and |
| // |expected_jank_stopped_| should be 0. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| }; |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindLambdaForTesting(nested_responsive_task)); |
| // Spin a nested run loop to run |nested_responsive_task|. |
| base::RunLoop(base::RunLoop::Type::kNestableTasksAllowed).RunUntilIdle(); |
| } |
| }; |
| |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindLambdaForTesting(enclosing_task)); |
| task_environment_.RunUntilIdle(); |
| // |expected_jank_started_| and |expected_jank_stopped_| should be 0 even if |
| // the enclosing task runs much longer than the jank threshold. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| } |
| |
| // Test that the jank monitor reports only the janky task running in the nested |
| // runloop. The enclosing task shouldn't be reported even if its total duration |
| // is longer than the jank threshold. |
| TEST_F(JankMonitorTest, JankNestedRunLoop) { |
| auto enclosing_task = [&]() { |
| // Run 1 responsive tasks in the inner runloop. |
| auto nested_responsive_task = [&]() { |
| task_environment_.FastForwardBy(base::Milliseconds(999)); |
| |
| // The callback shouldn't be called. |expected_jank_started_| should be 0. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| }; |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindLambdaForTesting(nested_responsive_task)); |
| // Spin a nested run loop to run |nested_responsive_task|. |
| base::RunLoop(base::RunLoop::Type::kNestableTasksAllowed).RunUntilIdle(); |
| |
| // Then run 1 responsive tasks in the inner runloop. |
| auto nested_janky_task = [&]() { |
| task_environment_.FastForwardBy(base::Milliseconds(1500)); |
| |
| // We should detect one jank. |
| expected_jank_started_++; |
| // The callback shouldn't be called. |expected_jank_started_| should be 0. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| }; |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindLambdaForTesting(nested_janky_task)); |
| // Spin a nested run loop to run |nested_responsive_task|. |
| base::RunLoop(base::RunLoop::Type::kNestableTasksAllowed).RunUntilIdle(); |
| |
| task_environment_.RunUntilIdle(); |
| expected_jank_stopped_++; |
| // The callback shouldn't be called. |expected_jank_started_| should be 1. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| }; |
| |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindLambdaForTesting(enclosing_task)); |
| task_environment_.RunUntilIdle(); |
| // |expected_jank_started_| and |expected_jank_stopped_| should still be 1. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| } |
| |
| // Test monitor with overlapping janks on both threads. Only the jank started |
| // first should be reported. |
| TEST_F(JankMonitorTest, JankUIAndIOThread) { |
| auto janky_task_ui = [&]() { |
| expected_jank_started_++; |
| |
| // This should trigger the monitor. TestJankMonitor::OnJankStarted() should |
| // be called once. |
| task_environment_.FastForwardBy(base::Milliseconds(1500)); |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| |
| // The IO thread is also janky. |
| auto janky_task_io = [&]() { |
| // This is a janky task that runs for 1.5 seconds, but shouldn't trigger |
| // the monitor. |
| task_environment_.FastForwardBy(base::Milliseconds(1500)); |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| |
| // Monitor should observe that the jank has started. |
| }; |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindLambdaForTesting(janky_task_io)); |
| task_environment_.RunUntilIdle(); |
| // TestJankMonitor::OnJankStopped() shouldn't be called. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| |
| task_environment_.FastForwardBy(base::Milliseconds(500)); |
| expected_jank_stopped_++; |
| }; |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindLambdaForTesting(janky_task_ui)); |
| task_environment_.RunUntilIdle(); |
| // Expect that TestJankMonitor::OnJankStopped() was called. |
| VALIDATE_TEST_OBSERVER_CALLS(); |
| } |
| |
| // Test stopping monitor timer when there is no activity and starting monitor |
| // timer on new activity. |
| TEST_F(JankMonitorTest, StartStopTimer) { |
| // Activity on the UI thread - timer should be running. |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, base::DoNothing()); |
| task_environment_.RunUntilIdle(); |
| EXPECT_TRUE(monitor_->timer_running()); |
| |
| // 11 seconds passed with no activity - timer should be stopped. |
| task_environment_.FastForwardBy(base::Milliseconds(11 * 1000)); |
| EXPECT_FALSE(monitor_->timer_running()); |
| |
| // Activity on IO thread - timer should be restarted. |
| content::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, base::DoNothing()); |
| task_environment_.RunUntilIdle(); |
| EXPECT_TRUE(monitor_->timer_running()); |
| |
| // 11 seconds passed with no activity - timer should be stopped. |
| task_environment_.FastForwardBy(base::Milliseconds(11 * 1000)); |
| EXPECT_FALSE(monitor_->timer_running()); |
| } |
| |
| class TestJankMonitorShutdownRace : public JankMonitorImpl { |
| public: |
| TestJankMonitorShutdownRace(base::WaitableEvent* shutdown_on_monitor_thread, |
| base::WaitableEvent* shutdown_on_ui_thread) |
| : shutdown_on_monitor_thread_(shutdown_on_monitor_thread), |
| shutdown_on_ui_thread_(shutdown_on_ui_thread) {} |
| |
| using JankMonitorImpl::timer_running; |
| |
| protected: |
| ~TestJankMonitorShutdownRace() override = default; |
| |
| std::unique_ptr<MetricSource> CreateMetricSource() override { |
| return std::make_unique<TestMetricSource>(this); |
| } |
| |
| void DestroyOnMonitorThread() override { |
| JankMonitorImpl::DestroyOnMonitorThread(); |
| |
| // Posts a task to the UI thread. Note that we run concurrently with the |
| // destruction of MetricSource. Even if MetricSource is still active and |
| // attempts to start the timer, the attempt should be a no-op since the |
| // the timer is already destroyed. We should still expect timer not running. |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, base::DoNothing()); |
| |
| shutdown_on_monitor_thread_->Signal(); |
| } |
| |
| void FinishDestroyMetricSource() override { |
| JankMonitorImpl::FinishDestroyMetricSource(); |
| |
| shutdown_on_ui_thread_->Signal(); |
| } |
| |
| private: |
| raw_ptr<base::WaitableEvent> shutdown_on_monitor_thread_; |
| raw_ptr<base::WaitableEvent> shutdown_on_ui_thread_; |
| }; |
| |
| // Test that completion of shutdown shouldn't leave the timer in the running |
| // state. |
| TEST(JankMonitorShutdownTest, ShutdownRace_TimerRestarted) { |
| content::BrowserTaskEnvironment task_environment; |
| |
| // Use WaitableEvent to control the progress of shutdown sequence. |
| base::WaitableEvent shutdown_on_monitor_thread; |
| base::WaitableEvent shutdown_on_ui_thread; |
| |
| scoped_refptr<TestJankMonitorShutdownRace> jank_monitor = |
| base::MakeRefCounted<TestJankMonitorShutdownRace>( |
| &shutdown_on_monitor_thread, &shutdown_on_ui_thread); |
| jank_monitor->SetUp(); |
| task_environment.RunUntilIdle(); |
| |
| jank_monitor->Destroy(); |
| task_environment.RunUntilIdle(); |
| |
| if (!shutdown_on_monitor_thread.IsSignaled()) |
| shutdown_on_monitor_thread.Wait(); |
| task_environment.RunUntilIdle(); |
| |
| if (!shutdown_on_ui_thread.IsSignaled()) |
| shutdown_on_ui_thread.Wait(); |
| task_environment.RunUntilIdle(); |
| |
| // After shutdown of both MetricSource and the monitor timer, we should expect |
| // that the timer isn't running even if a task runs on the UI thread during |
| // shutdown. |
| EXPECT_FALSE(jank_monitor->timer_running()); |
| } |
| |
| class TestJankMonitorShutdownRaceTimerFired : public JankMonitorImpl { |
| public: |
| TestJankMonitorShutdownRaceTimerFired( |
| content::BrowserTaskEnvironment* task_environment) |
| : task_environment_(task_environment) {} |
| |
| bool monitor_timer_fired() const { return monitor_timer_fired_; } |
| |
| protected: |
| ~TestJankMonitorShutdownRaceTimerFired() override = default; |
| |
| std::unique_ptr<MetricSource> CreateMetricSource() override { |
| return std::make_unique<TestMetricSource>(this); |
| } |
| |
| void FinishDestroyMetricSource() override { |
| // Forward by 1 ms to trigger the monitor timer. This shouldn't crash even |
| // after MetricSource is destroyed. |
| task_environment_->FastForwardBy(base::Milliseconds(1)); |
| |
| JankMonitorImpl::FinishDestroyMetricSource(); |
| } |
| |
| void OnCheckJankiness() override { |
| JankMonitorImpl::OnCheckJankiness(); |
| monitor_timer_fired_ = true; |
| } |
| |
| private: |
| raw_ptr<content::BrowserTaskEnvironment> task_environment_; |
| bool monitor_timer_fired_ = false; |
| }; |
| |
| // Test that the monitor timer shouldn't race with shutdown of MetricSource and |
| // then crashes. |
| TEST(JankMonitorShutdownTest, ShutdownRace_TimerFired) { |
| content::BrowserTaskEnvironment task_environment( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| scoped_refptr<TestJankMonitorShutdownRaceTimerFired> jank_monitor = |
| base::MakeRefCounted<TestJankMonitorShutdownRaceTimerFired>( |
| &task_environment); |
| jank_monitor->SetUp(); |
| task_environment.RunUntilIdle(); |
| |
| // Fast-forward by 499 ms. This shouldn't trigger the monitor timer. |
| static constexpr base::TimeDelta kCheckInterval = base::Milliseconds(500); |
| task_environment.FastForwardBy(kCheckInterval - base::Milliseconds(1)); |
| |
| EXPECT_FALSE(jank_monitor->monitor_timer_fired()); |
| |
| jank_monitor->Destroy(); |
| task_environment.RunUntilIdle(); |
| |
| // The monitor timer isn't expected to fire. |
| EXPECT_FALSE(jank_monitor->monitor_timer_fired()); |
| } |
| |
| #undef VALIDATE_TEST_OBSERVER_CALLS |
| |
| } // namespace responsiveness. |
| } // namespace content. |