// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "third_party/blink/renderer/platform/scheduler/main_thread/main_thread_metrics_helper.h" #include #include "base/macros.h" #include "base/task/sequence_manager/test/fake_task.h" #include "base/task/sequence_manager/test/sequence_manager_for_test.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_task_environment.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/page/launching_process_state.h" #include "third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h" #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h" #include "third_party/blink/renderer/platform/scheduler/test/fake_frame_scheduler.h" #include "third_party/blink/renderer/platform/scheduler/test/fake_page_scheduler.h" #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" using base::sequence_manager::TaskQueue; using base::sequence_manager::FakeTask; using base::sequence_manager::FakeTaskTiming; namespace blink { namespace scheduler { namespace { class MainThreadSchedulerImplForTest : public MainThreadSchedulerImpl { public: MainThreadSchedulerImplForTest( std::unique_ptr sequence_manager, base::Optional initial_virtual_time) : MainThreadSchedulerImpl(std::move(sequence_manager), initial_virtual_time){}; using MainThreadSchedulerImpl::SetCurrentUseCaseForTest; }; } // namespace using QueueType = MainThreadTaskQueue::QueueType; using base::Bucket; using testing::ElementsAre; using testing::UnorderedElementsAre; class MainThreadMetricsHelperTest : public testing::Test { public: MainThreadMetricsHelperTest() : task_environment_( base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME, base::test::ScopedTaskEnvironment::ExecutionMode::QUEUED) { // Null clock might trigger some assertions. task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1)); } ~MainThreadMetricsHelperTest() override = default; void SetUp() override { histogram_tester_.reset(new base::HistogramTester()); scheduler_ = std::make_unique( base::sequence_manager::SequenceManagerForTest::Create( nullptr, task_environment_.GetMainThreadTaskRunner(), task_environment_.GetMockTickClock()), base::nullopt); metrics_helper_ = &scheduler_->main_thread_only().metrics_helper; } void TearDown() override { scheduler_->Shutdown(); scheduler_.reset(); } base::TimeTicks Now() { return task_environment_.GetMockTickClock()->NowTicks(); } void FastForwardTo(base::TimeTicks time) { CHECK_LE(Now(), time); task_environment_.FastForwardBy(time - Now()); } void RunTask(MainThreadTaskQueue::QueueType queue_type, base::TimeTicks start, base::TimeDelta duration) { DCHECK_LE(Now(), start); FastForwardTo(start + duration); scoped_refptr queue; if (queue_type != MainThreadTaskQueue::QueueType::kDetached) { queue = scoped_refptr( new MainThreadTaskQueueForTest(queue_type)); } metrics_helper_->RecordTaskMetrics(queue.get(), FakeTask(), FakeTaskTiming(start, start + duration)); } void RunTask(FrameSchedulerImpl* scheduler, base::TimeTicks start, base::TimeDelta duration) { DCHECK_LE(Now(), start); FastForwardTo(start + duration); scoped_refptr queue( new MainThreadTaskQueueForTest(QueueType::kDefault)); queue->SetFrameSchedulerForTest(scheduler); metrics_helper_->RecordTaskMetrics(queue.get(), FakeTask(), FakeTaskTiming(start, start + duration)); } void RunTask(UseCase use_case, base::TimeTicks start, base::TimeDelta duration) { DCHECK_LE(Now(), start); FastForwardTo(start + duration); scoped_refptr queue( new MainThreadTaskQueueForTest(QueueType::kDefault)); scheduler_->SetCurrentUseCaseForTest(use_case); metrics_helper_->RecordTaskMetrics(queue.get(), FakeTask(), FakeTaskTiming(start, start + duration)); } base::TimeTicks Milliseconds(int milliseconds) { return base::TimeTicks() + base::TimeDelta::FromMilliseconds(milliseconds); } base::TimeTicks Seconds(int seconds) { return base::TimeTicks() + base::TimeDelta::FromSeconds(seconds); } void ForceUpdatePolicy() { scheduler_->ForceUpdatePolicy(); } std::unique_ptr CreateFakeFrameSchedulerWithType( FrameStatus frame_status) { FakeFrameScheduler::Builder builder; switch (frame_status) { case FrameStatus::kNone: case FrameStatus::kDetached: return nullptr; case FrameStatus::kMainFrameVisible: builder.SetFrameType(FrameScheduler::FrameType::kMainFrame) .SetIsPageVisible(true) .SetIsFrameVisible(true); break; case FrameStatus::kMainFrameVisibleService: builder.SetFrameType(FrameScheduler::FrameType::kMainFrame) .SetPageScheduler(playing_view_.get()) .SetIsFrameVisible(true); break; case FrameStatus::kMainFrameHidden: builder.SetFrameType(FrameScheduler::FrameType::kMainFrame) .SetIsPageVisible(true); break; case FrameStatus::kMainFrameHiddenService: builder.SetFrameType(FrameScheduler::FrameType::kMainFrame) .SetPageScheduler(playing_view_.get()); break; case FrameStatus::kMainFrameBackground: builder.SetFrameType(FrameScheduler::FrameType::kMainFrame); break; case FrameStatus::kMainFrameBackgroundExemptSelf: builder.SetFrameType(FrameScheduler::FrameType::kMainFrame) .SetIsExemptFromThrottling(true); break; case FrameStatus::kMainFrameBackgroundExemptOther: builder.SetFrameType(FrameScheduler::FrameType::kMainFrame) .SetPageScheduler(throtting_exempt_view_.get()); break; case FrameStatus::kSameOriginVisible: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetIsPageVisible(true) .SetIsFrameVisible(true); break; case FrameStatus::kSameOriginVisibleService: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetPageScheduler(playing_view_.get()) .SetIsFrameVisible(true); break; case FrameStatus::kSameOriginHidden: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetIsPageVisible(true); break; case FrameStatus::kSameOriginHiddenService: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetPageScheduler(playing_view_.get()); break; case FrameStatus::kSameOriginBackground: builder.SetFrameType(FrameScheduler::FrameType::kSubframe); break; case FrameStatus::kSameOriginBackgroundExemptSelf: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetIsExemptFromThrottling(true); break; case FrameStatus::kSameOriginBackgroundExemptOther: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetPageScheduler(throtting_exempt_view_.get()); break; case FrameStatus::kCrossOriginVisible: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetIsCrossOrigin(true) .SetIsPageVisible(true) .SetIsFrameVisible(true); break; case FrameStatus::kCrossOriginVisibleService: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetIsCrossOrigin(true) .SetPageScheduler(playing_view_.get()) .SetIsFrameVisible(true); break; case FrameStatus::kCrossOriginHidden: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetIsCrossOrigin(true) .SetIsPageVisible(true); break; case FrameStatus::kCrossOriginHiddenService: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetIsCrossOrigin(true) .SetPageScheduler(playing_view_.get()); break; case FrameStatus::kCrossOriginBackground: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetIsCrossOrigin(true); break; case FrameStatus::kCrossOriginBackgroundExemptSelf: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetIsCrossOrigin(true) .SetIsExemptFromThrottling(true); break; case FrameStatus::kCrossOriginBackgroundExemptOther: builder.SetFrameType(FrameScheduler::FrameType::kSubframe) .SetIsCrossOrigin(true) .SetPageScheduler(throtting_exempt_view_.get()); break; case FrameStatus::kCount: NOTREACHED(); return nullptr; } return builder.Build(); } base::test::ScopedTaskEnvironment task_environment_; std::unique_ptr scheduler_; MainThreadMetricsHelper* metrics_helper_; // NOT OWNED std::unique_ptr histogram_tester_; std::unique_ptr playing_view_ = FakePageScheduler::Builder().SetIsAudioPlaying(true).Build(); std::unique_ptr throtting_exempt_view_ = FakePageScheduler::Builder().SetIsThrottlingExempt(true).Build(); DISALLOW_COPY_AND_ASSIGN(MainThreadMetricsHelperTest); }; TEST_F(MainThreadMetricsHelperTest, Metrics_PerQueueType) { // QueueType::kDefault is checking sub-millisecond task aggregation, // FRAME_* tasks are checking normal task aggregation and other // queue types have a single task. // Make sure that it starts in a foregrounded state. if (kLaunchingProcessIsBackgrounded) scheduler_->SetRendererBackgrounded(false); RunTask(QueueType::kDefault, Seconds(1), base::TimeDelta::FromMilliseconds(700)); RunTask(QueueType::kDefault, Seconds(2), base::TimeDelta::FromMilliseconds(700)); RunTask(QueueType::kDefault, Seconds(3), base::TimeDelta::FromMilliseconds(700)); RunTask(QueueType::kControl, Seconds(4), base::TimeDelta::FromSeconds(3)); RunTask(QueueType::kFrameLoading, Seconds(8), base::TimeDelta::FromSeconds(6)); RunTask(QueueType::kFramePausable, Seconds(16), base::TimeDelta::FromSeconds(2)); RunTask(QueueType::kCompositor, Seconds(19), base::TimeDelta::FromSeconds(2)); RunTask(QueueType::kTest, Seconds(22), base::TimeDelta::FromSeconds(4)); scheduler_->SetRendererBackgrounded(true); // Wait for internally triggered tasks to run. constexpr int kCoolingOfTimeSeconds = 10; RunTask(QueueType::kControl, Seconds(26 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(2)); RunTask(QueueType::kFrameThrottleable, Seconds(28 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(8)); RunTask(QueueType::kUnthrottled, Seconds(38 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(5)); RunTask(QueueType::kFrameLoading, Seconds(45 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(10)); RunTask(QueueType::kFrameThrottleable, Seconds(60 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(5)); RunTask(QueueType::kCompositor, Seconds(70 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(20)); RunTask(QueueType::kIdle, Seconds(90 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(5)); RunTask(QueueType::kFrameLoadingControl, Seconds(100 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(5)); RunTask(QueueType::kControl, Seconds(106 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(6)); RunTask(QueueType::kFrameThrottleable, Seconds(114 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(6)); RunTask(QueueType::kFramePausable, Seconds(120 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(17)); RunTask(QueueType::kIdle, Seconds(140 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(15)); RunTask(QueueType::kDetached, Seconds(156 + kCoolingOfTimeSeconds), base::TimeDelta::FromSeconds(2)); std::vector expected_samples = { {static_cast(QueueType::kControl), 11}, {static_cast(QueueType::kDefault), 2}, {static_cast(QueueType::kUnthrottled), 5}, {static_cast(QueueType::kFrameLoading), 16}, {static_cast(QueueType::kCompositor), 22}, {static_cast(QueueType::kIdle), 20}, {static_cast(QueueType::kTest), 4}, {static_cast(QueueType::kFrameLoadingControl), 5}, {static_cast(QueueType::kFrameThrottleable), 19}, {static_cast(QueueType::kFramePausable), 19}, {static_cast(QueueType::kDetached), 2}, }; EXPECT_THAT(histogram_tester_->GetAllSamples( "RendererScheduler.TaskDurationPerQueueType3"), testing::ContainerEq(expected_samples)); EXPECT_THAT(histogram_tester_->GetAllSamples( "RendererScheduler.TaskDurationPerQueueType3.Foreground"), UnorderedElementsAre( Bucket(static_cast(QueueType::kControl), 3), Bucket(static_cast(QueueType::kDefault), 2), Bucket(static_cast(QueueType::kFrameLoading), 6), Bucket(static_cast(QueueType::kCompositor), 2), Bucket(static_cast(QueueType::kTest), 4), Bucket(static_cast(QueueType::kFramePausable), 2))); EXPECT_THAT(histogram_tester_->GetAllSamples( "RendererScheduler.TaskDurationPerQueueType3.Background"), UnorderedElementsAre( Bucket(static_cast(QueueType::kControl), 8), Bucket(static_cast(QueueType::kUnthrottled), 5), Bucket(static_cast(QueueType::kFrameLoading), 10), Bucket(static_cast(QueueType::kFrameThrottleable), 19), Bucket(static_cast(QueueType::kFramePausable), 17), Bucket(static_cast(QueueType::kCompositor), 20), Bucket(static_cast(QueueType::kIdle), 20), Bucket(static_cast(QueueType::kFrameLoadingControl), 5), Bucket(static_cast(QueueType::kDetached), 2))); } TEST_F(MainThreadMetricsHelperTest, Metrics_PerUseCase) { RunTask(UseCase::kNone, Milliseconds(500), base::TimeDelta::FromMilliseconds(400)); RunTask(UseCase::kTouchstart, Seconds(1), base::TimeDelta::FromSeconds(2)); RunTask(UseCase::kTouchstart, Seconds(3), base::TimeDelta::FromMilliseconds(300)); RunTask(UseCase::kTouchstart, Seconds(4), base::TimeDelta::FromMilliseconds(300)); RunTask(UseCase::kCompositorGesture, Seconds(5), base::TimeDelta::FromSeconds(5)); RunTask(UseCase::kCompositorGesture, Seconds(10), base::TimeDelta::FromSeconds(3)); RunTask(UseCase::kMainThreadCustomInputHandling, Seconds(14), base::TimeDelta::FromSeconds(2)); RunTask(UseCase::kSynchronizedGesture, Seconds(17), base::TimeDelta::FromSeconds(2)); RunTask(UseCase::kMainThreadCustomInputHandling, Seconds(19), base::TimeDelta::FromSeconds(5)); RunTask(UseCase::kLoading, Seconds(25), base::TimeDelta::FromSeconds(6)); RunTask(UseCase::kMainThreadGesture, Seconds(31), base::TimeDelta::FromSeconds(6)); EXPECT_THAT( histogram_tester_->GetAllSamples( "RendererScheduler.TaskDurationPerUseCase2"), UnorderedElementsAre( Bucket(static_cast(UseCase::kTouchstart), 3), Bucket(static_cast(UseCase::kCompositorGesture), 8), Bucket(static_cast(UseCase::kMainThreadCustomInputHandling), 7), Bucket(static_cast(UseCase::kSynchronizedGesture), 2), Bucket(static_cast(UseCase::kLoading), 6), Bucket(static_cast(UseCase::kMainThreadGesture), 6))); } TEST_F(MainThreadMetricsHelperTest, GetFrameStatusTest) { DCHECK_EQ(GetFrameStatus(nullptr), FrameStatus::kNone); FrameStatus frame_statuses_tested[] = { FrameStatus::kMainFrameVisible, FrameStatus::kSameOriginHidden, FrameStatus::kCrossOriginHidden, FrameStatus::kSameOriginBackground, FrameStatus::kMainFrameBackgroundExemptSelf, FrameStatus::kSameOriginVisibleService, FrameStatus::kCrossOriginHiddenService, FrameStatus::kMainFrameBackgroundExemptOther}; for (FrameStatus frame_status : frame_statuses_tested) { std::unique_ptr frame = CreateFakeFrameSchedulerWithType(frame_status); EXPECT_EQ(GetFrameStatus(frame.get()), frame_status); } } TEST_F(MainThreadMetricsHelperTest, TaskCountPerFrameStatus) { int task_count = 0; struct CountPerFrameStatus { FrameStatus frame_status; int count; }; CountPerFrameStatus test_data[] = { {FrameStatus::kNone, 4}, {FrameStatus::kMainFrameVisible, 8}, {FrameStatus::kMainFrameBackgroundExemptSelf, 5}, {FrameStatus::kCrossOriginHidden, 3}, {FrameStatus::kCrossOriginHiddenService, 7}, {FrameStatus::kCrossOriginVisible, 1}, {FrameStatus::kMainFrameBackgroundExemptOther, 2}, {FrameStatus::kSameOriginVisible, 10}, {FrameStatus::kSameOriginBackground, 9}, {FrameStatus::kSameOriginVisibleService, 6}}; for (const auto& data : test_data) { std::unique_ptr frame = CreateFakeFrameSchedulerWithType(data.frame_status); for (int i = 0; i < data.count; ++i) { RunTask(frame.get(), Milliseconds(++task_count), base::TimeDelta::FromMicroseconds(100)); } } EXPECT_THAT( histogram_tester_->GetAllSamples( "RendererScheduler.TaskCountPerFrameType"), UnorderedElementsAre( Bucket(static_cast(FrameStatus::kNone), 4), Bucket(static_cast(FrameStatus::kMainFrameVisible), 8), Bucket(static_cast(FrameStatus::kMainFrameBackgroundExemptSelf), 5), Bucket(static_cast(FrameStatus::kMainFrameBackgroundExemptOther), 2), Bucket(static_cast(FrameStatus::kSameOriginVisible), 10), Bucket(static_cast(FrameStatus::kSameOriginVisibleService), 6), Bucket(static_cast(FrameStatus::kSameOriginBackground), 9), Bucket(static_cast(FrameStatus::kCrossOriginVisible), 1), Bucket(static_cast(FrameStatus::kCrossOriginHidden), 3), Bucket(static_cast(FrameStatus::kCrossOriginHiddenService), 7))); } TEST_F(MainThreadMetricsHelperTest, TaskCountPerFrameTypeLongerThan) { int total_duration = 0; struct TasksPerFrameStatus { FrameStatus frame_status; std::vector durations; }; TasksPerFrameStatus test_data[] = { {FrameStatus::kSameOriginHidden, {2, 15, 16, 20, 25, 30, 49, 50, 73, 99, 100, 110, 140, 150, 800, 1000, 1200}}, {FrameStatus::kCrossOriginVisibleService, {5, 10, 18, 19, 20, 55, 75, 220}}, {FrameStatus::kMainFrameBackground, {21, 31, 41, 51, 61, 71, 81, 91, 101, 1001}}, }; for (const auto& data : test_data) { std::unique_ptr frame = CreateFakeFrameSchedulerWithType(data.frame_status); for (size_t i = 0; i < data.durations.size(); ++i) { RunTask(frame.get(), Milliseconds(++total_duration), base::TimeDelta::FromMilliseconds(data.durations[i])); total_duration += data.durations[i]; } } EXPECT_THAT( histogram_tester_->GetAllSamples( "RendererScheduler.TaskCountPerFrameType"), UnorderedElementsAre( Bucket(static_cast(FrameStatus::kMainFrameBackground), 10), Bucket(static_cast(FrameStatus::kSameOriginHidden), 17), Bucket(static_cast(FrameStatus::kCrossOriginVisibleService), 8))); EXPECT_THAT( histogram_tester_->GetAllSamples( "RendererScheduler.TaskCountPerFrameType." "LongerThan16ms"), UnorderedElementsAre( Bucket(static_cast(FrameStatus::kMainFrameBackground), 10), Bucket(static_cast(FrameStatus::kSameOriginHidden), 15), Bucket(static_cast(FrameStatus::kCrossOriginVisibleService), 6))); EXPECT_THAT( histogram_tester_->GetAllSamples( "RendererScheduler.TaskCountPerFrameType." "LongerThan50ms"), UnorderedElementsAre( Bucket(static_cast(FrameStatus::kMainFrameBackground), 7), Bucket(static_cast(FrameStatus::kSameOriginHidden), 10), Bucket(static_cast(FrameStatus::kCrossOriginVisibleService), 3))); EXPECT_THAT( histogram_tester_->GetAllSamples( "RendererScheduler.TaskCountPerFrameType." "LongerThan100ms"), UnorderedElementsAre( Bucket(static_cast(FrameStatus::kMainFrameBackground), 2), Bucket(static_cast(FrameStatus::kSameOriginHidden), 7), Bucket(static_cast(FrameStatus::kCrossOriginVisibleService), 1))); EXPECT_THAT( histogram_tester_->GetAllSamples( "RendererScheduler.TaskCountPerFrameType." "LongerThan150ms"), UnorderedElementsAre( Bucket(static_cast(FrameStatus::kMainFrameBackground), 1), Bucket(static_cast(FrameStatus::kSameOriginHidden), 4), Bucket(static_cast(FrameStatus::kCrossOriginVisibleService), 1))); EXPECT_THAT( histogram_tester_->GetAllSamples( "RendererScheduler.TaskCountPerFrameType.LongerThan1s"), UnorderedElementsAre( Bucket(static_cast(FrameStatus::kMainFrameBackground), 1), Bucket(static_cast(FrameStatus::kSameOriginHidden), 2))); } // TODO(crbug.com/754656): Add tests for NthMinute and // AfterNthMinute histograms. // TODO(crbug.com/754656): Add tests for // TaskDuration.Hidden/Visible histograms. // TODO(crbug.com/754656): Add tests for non-TaskDuration // histograms. } // namespace scheduler } // namespace blink