From 262700e6bbb78cc2348b5ed86326de9bfaeded21 Mon Sep 17 00:00:00 2001 From: skyostil Date: Tue, 15 Sep 2015 07:54:22 -0700 Subject: [PATCH] scheduler: Optimize for TouchStart responsiveness by modeling what the user is doing. We have come to realize that TouchStart responsiveness is really critical for a good user experience. This patch introduces a (initially very simple) system for modeling what the user is doing and predicting when they are likely to initiate a new gesture (taps are ignored). We order task priority accordingly. In addition we extend the estimation of task cost to loading tasks which are often a significant source of jank. Load tasks can be very long (easily up to 1000ms). There is no good way to cooperatively schedule such a task but the least worst user experience seems to be if we get them out of the way as fast as possible. Ideally we'd prioritize them but the order of IPC and LoadingTasks currently needs to be more or less in lockstep. Instead we deprioritize compositor tasks inside a compositor gesture, which has the effect of prioritizing other work. Expected changes on Perf bots: 1. first_gesture_scroll_update_latency in smoothness.scrolling_tough_ad_cases should go down. This should be highly user visible. 2. It's possible some compositor metrics such as queueing_durations may regress. I don't expect this to be user visible. This is a reland of https://codereview.chromium.org/1320633002. Original author: Alex Clarke BUG=510398,530250 CQ_INCLUDE_TRYBOTS=tryserver.blink:linux_blink_rel TBR=jochen@chromium.org Review URL: https://codereview.chromium.org/1340073003 Cr-Commit-Position: refs/heads/master@{#348886} --- components/components_tests.gyp | 1 + components/scheduler/BUILD.gn | 1 + .../scheduler/renderer/renderer_scheduler.cc | 33 ++ components/scheduler/renderer/renderer_scheduler.h | 15 + .../scheduler/renderer/renderer_scheduler_impl.cc | 556 ++++++++++++--------- .../scheduler/renderer/renderer_scheduler_impl.h | 116 +++-- .../renderer/renderer_scheduler_impl_unittest.cc | 441 +++++++++++----- .../scheduler/renderer/task_cost_estimator.cc | 17 +- .../scheduler/renderer/task_cost_estimator.h | 11 +- .../renderer/task_cost_estimator_unittest.cc | 28 +- components/scheduler/renderer/user_model.cc | 110 ++++ components/scheduler/renderer/user_model.h | 69 +++ .../scheduler/renderer/user_model_unittest.cc | 298 +++++++++++ components/scheduler/scheduler.gypi | 2 + content/renderer/render_frame_impl.cc | 6 + 15 files changed, 1271 insertions(+), 433 deletions(-) create mode 100644 components/scheduler/renderer/user_model.cc create mode 100644 components/scheduler/renderer/user_model.h create mode 100644 components/scheduler/renderer/user_model_unittest.cc diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 9cae71463a7b..3e5a94aabbb9 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -568,6 +568,7 @@ 'scheduler/renderer/deadline_task_runner_unittest.cc', 'scheduler/renderer/renderer_scheduler_impl_unittest.cc', 'scheduler/renderer/task_cost_estimator_unittest.cc', + 'scheduler/renderer/user_model_unittest.cc', 'scheduler/renderer/webthread_impl_for_renderer_scheduler_unittest.cc', 'scheduler/test/test_always_fail_time_source.cc', 'scheduler/test/test_always_fail_time_source.h', diff --git a/components/scheduler/BUILD.gn b/components/scheduler/BUILD.gn index 3d6858f0c37f..26d4ab3ca33e 100644 --- a/components/scheduler/BUILD.gn +++ b/components/scheduler/BUILD.gn @@ -48,6 +48,7 @@ source_set("unit_tests") { "renderer/deadline_task_runner_unittest.cc", "renderer/renderer_scheduler_impl_unittest.cc", "renderer/task_cost_estimator_unittest.cc", + "renderer/user_model_unittest.cc", "renderer/webthread_impl_for_renderer_scheduler_unittest.cc", "test/test_always_fail_time_source.cc", "test/test_always_fail_time_source.h", diff --git a/components/scheduler/renderer/renderer_scheduler.cc b/components/scheduler/renderer/renderer_scheduler.cc index 70459bff292d..20d663437a96 100644 --- a/components/scheduler/renderer/renderer_scheduler.cc +++ b/components/scheduler/renderer/renderer_scheduler.cc @@ -34,4 +34,37 @@ scoped_ptr RendererScheduler::Create() { SchedulerTaskRunnerDelegateImpl::Create(message_loop))); } +// static +const char* RendererScheduler::UseCaseToString(UseCase use_case) { + switch (use_case) { + case UseCase::NONE: + return "none"; + case UseCase::COMPOSITOR_GESTURE: + return "compositor_gesture"; + case UseCase::MAIN_THREAD_GESTURE: + return "main_thread_gesture"; + case UseCase::TOUCHSTART: + return "touchstart"; + case UseCase::LOADING: + return "loading"; + default: + NOTREACHED(); + return nullptr; + } +} + +// static +const char* RendererScheduler::InputEventStateToString( + InputEventState input_event_state) { + switch (input_event_state) { + case InputEventState::EVENT_CONSUMED_BY_COMPOSITOR: + return "event_consumed_by_compositor"; + case InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD: + return "event_forwarded_to_main_thread"; + default: + NOTREACHED(); + return nullptr; + } +} + } // namespace scheduler diff --git a/components/scheduler/renderer/renderer_scheduler.h b/components/scheduler/renderer/renderer_scheduler.h index 58b976f583bd..d605e52ce620 100644 --- a/components/scheduler/renderer/renderer_scheduler.h +++ b/components/scheduler/renderer/renderer_scheduler.h @@ -26,6 +26,19 @@ class SCHEDULER_EXPORT RendererScheduler : public ChildScheduler { virtual scoped_refptr CompositorTaskRunner() = 0; + // Keep RendererScheduler::UseCaseToString in sync with this enum. + enum class UseCase { + NONE, + COMPOSITOR_GESTURE, + MAIN_THREAD_GESTURE, + TOUCHSTART, + LOADING, + // Must be the last entry. + USE_CASE_COUNT, + FIRST_USE_CASE = NONE, + }; + static const char* UseCaseToString(UseCase use_case); + // Returns the loading task runner. This queue is intended for tasks related // to resource dispatch, foreground HTML parsing, etc... virtual scoped_refptr LoadingTaskRunner() = 0; @@ -45,10 +58,12 @@ class SCHEDULER_EXPORT RendererScheduler : public ChildScheduler { // called from the main thread. virtual void DidCommitFrameToCompositor() = 0; + // Keep RendererScheduler::InputEventStateToString in sync with this enum. enum class InputEventState { EVENT_CONSUMED_BY_COMPOSITOR, EVENT_FORWARDED_TO_MAIN_THREAD, }; + static const char* InputEventStateToString(InputEventState input_event_state); // Tells the scheduler that the system processed an input event. Called by the // compositor (impl) thread. Note it's expected that every call to diff --git a/components/scheduler/renderer/renderer_scheduler_impl.cc b/components/scheduler/renderer/renderer_scheduler_impl.cc index f05fc4994421..bb5dc7224f7c 100644 --- a/components/scheduler/renderer/renderer_scheduler_impl.cc +++ b/components/scheduler/renderer/renderer_scheduler_impl.cc @@ -15,8 +15,10 @@ namespace scheduler { namespace { -const int kTimerTaskEstimationSampleCount = 4 * 60; -const double kTimerTaskEstimationPercentile = 80; +const int kLoadingTaskEstimationSampleCount = 200; +const double kLoadingTaskEstimationPercentile = 90; +const int kTimerTaskEstimationSampleCount = 200; +const double kTimerTaskEstimationPercentile = 90; const int kShortIdlePeriodDurationSampleCount = 10; const double kShortIdlePeriodDurationPercentile = 20; } @@ -58,12 +60,19 @@ RendererSchedulerImpl::RendererSchedulerImpl( base::Bind(&RendererSchedulerImpl::SuspendTimerQueueWhenBackgrounded, weak_factory_.GetWeakPtr())); + loading_task_runner_->AddTaskObserver( + &MainThreadOnly().loading_task_cost_estimator); + timer_task_runner_->AddTaskObserver( - &MainThreadOnly().timer_task_cost_estimator_); + &MainThreadOnly().timer_task_cost_estimator); TRACE_EVENT_OBJECT_CREATED_WITH_ID( TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler", this); + + // Make sure that we don't initially assume there is no idle time. + MainThreadOnly().short_idle_period_duration.InsertSample( + cc::BeginFrameArgs::DefaultInterval()); } RendererSchedulerImpl::~RendererSchedulerImpl() { @@ -71,44 +80,55 @@ RendererSchedulerImpl::~RendererSchedulerImpl() { TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler", this); timer_task_runner_->RemoveTaskObserver( - &MainThreadOnly().timer_task_cost_estimator_); + &MainThreadOnly().timer_task_cost_estimator); + loading_task_runner_->RemoveTaskObserver( + &MainThreadOnly().loading_task_cost_estimator); // Ensure the renderer scheduler was shut down explicitly, because otherwise // we could end up having stale pointers to the Blink heap which has been // terminated by this point. - DCHECK(MainThreadOnly().was_shutdown_); + DCHECK(MainThreadOnly().was_shutdown); } +RendererSchedulerImpl::Policy::Policy() + : compositor_queue_priority(TaskQueue::NORMAL_PRIORITY), + loading_queue_priority(TaskQueue::NORMAL_PRIORITY), + timer_queue_priority(TaskQueue::NORMAL_PRIORITY), + default_queue_priority(TaskQueue::NORMAL_PRIORITY) {} + RendererSchedulerImpl::MainThreadOnly::MainThreadOnly() - : timer_task_cost_estimator_(kTimerTaskEstimationSampleCount, - kTimerTaskEstimationPercentile), - short_idle_period_duration_(kShortIdlePeriodDurationSampleCount), - current_policy_(Policy::NORMAL), - timer_queue_suspend_count_(0), - renderer_hidden_(false), - renderer_backgrounded_(false), - timer_queue_suspension_when_backgrounded_enabled_(false), - timer_queue_suspended_when_backgrounded_(false), - was_shutdown_(false) {} + : loading_task_cost_estimator(kLoadingTaskEstimationSampleCount, + kLoadingTaskEstimationPercentile), + timer_task_cost_estimator(kTimerTaskEstimationSampleCount, + kTimerTaskEstimationPercentile), + short_idle_period_duration(kShortIdlePeriodDurationSampleCount), + current_use_case(UseCase::NONE), + timer_queue_suspend_count(0), + renderer_hidden(false), + renderer_backgrounded(false), + timer_queue_suspension_when_backgrounded_enabled(false), + timer_queue_suspended_when_backgrounded(false), + was_shutdown(false), + loading_tasks_seem_expensive(false), + timer_tasks_seem_expensive(false), + touchstart_expected_soon(false), + have_seen_a_begin_main_frame(false) {} RendererSchedulerImpl::MainThreadOnly::~MainThreadOnly() {} RendererSchedulerImpl::AnyThread::AnyThread() - : pending_main_thread_input_event_count_(0), - awaiting_touch_start_response_(false), - in_idle_period_(false), - begin_main_frame_on_critical_path_(false), - timer_tasks_seem_expensive_(false) {} + : awaiting_touch_start_response(false), + in_idle_period(false), + begin_main_frame_on_critical_path(false) {} RendererSchedulerImpl::CompositorThreadOnly::CompositorThreadOnly() - : last_input_type_(blink::WebInputEvent::Undefined) { -} + : last_input_type(blink::WebInputEvent::Undefined) {} RendererSchedulerImpl::CompositorThreadOnly::~CompositorThreadOnly() { } void RendererSchedulerImpl::Shutdown() { helper_.Shutdown(); - MainThreadOnly().was_shutdown_ = true; + MainThreadOnly().was_shutdown = true; } scoped_refptr RendererSchedulerImpl::DefaultTaskRunner() { @@ -159,11 +179,11 @@ void RendererSchedulerImpl::WillBeginFrame(const cc::BeginFrameArgs& args) { return; EndIdlePeriod(); - MainThreadOnly().estimated_next_frame_begin_ = - args.frame_time + args.interval; + MainThreadOnly().estimated_next_frame_begin = args.frame_time + args.interval; + MainThreadOnly().have_seen_a_begin_main_frame = true; { base::AutoLock lock(any_thread_lock_); - AnyThread().begin_main_frame_on_critical_path_ = args.on_critical_path; + AnyThread().begin_main_frame_on_critical_path = args.on_critical_path; } } @@ -175,18 +195,22 @@ void RendererSchedulerImpl::DidCommitFrameToCompositor() { return; base::TimeTicks now(helper_.Now()); - if (now < MainThreadOnly().estimated_next_frame_begin_) { + if (now < MainThreadOnly().estimated_next_frame_begin) { // TODO(rmcilroy): Consider reducing the idle period based on the runtime of // the next pending delayed tasks (as currently done in for long idle times) idle_helper_.StartIdlePeriod( IdleHelper::IdlePeriodState::IN_SHORT_IDLE_PERIOD, now, - MainThreadOnly().estimated_next_frame_begin_); - MainThreadOnly().short_idle_period_duration_.InsertSample( - MainThreadOnly().estimated_next_frame_begin_ - now); - MainThreadOnly().expected_short_idle_period_duration_ = - MainThreadOnly().short_idle_period_duration_.Percentile( - kShortIdlePeriodDurationPercentile); + MainThreadOnly().estimated_next_frame_begin); + MainThreadOnly().short_idle_period_duration.InsertSample( + MainThreadOnly().estimated_next_frame_begin - now); + } else { + // There was no idle time :( + MainThreadOnly().short_idle_period_duration.InsertSample(base::TimeDelta()); } + + MainThreadOnly().expected_short_idle_period_duration = + MainThreadOnly().short_idle_period_duration.Percentile( + kShortIdlePeriodDurationPercentile); } void RendererSchedulerImpl::BeginFrameNotExpectedSoon() { @@ -203,7 +227,7 @@ void RendererSchedulerImpl::OnRendererHidden() { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererSchedulerImpl::OnRendererHidden"); helper_.CheckOnValidThread(); - if (helper_.IsShutdown() || MainThreadOnly().renderer_hidden_) + if (helper_.IsShutdown() || MainThreadOnly().renderer_hidden) return; idle_helper_.EnableLongIdlePeriod(); @@ -215,7 +239,7 @@ void RendererSchedulerImpl::OnRendererHidden() { control_task_runner_->PostDelayedTask( FROM_HERE, end_renderer_hidden_idle_period_closure_.callback(), end_idle_when_hidden_delay); - MainThreadOnly().renderer_hidden_ = true; + MainThreadOnly().renderer_hidden = true; TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler", @@ -226,11 +250,11 @@ void RendererSchedulerImpl::OnRendererVisible() { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererSchedulerImpl::OnRendererVisible"); helper_.CheckOnValidThread(); - if (helper_.IsShutdown() || !MainThreadOnly().renderer_hidden_) + if (helper_.IsShutdown() || !MainThreadOnly().renderer_hidden) return; end_renderer_hidden_idle_period_closure_.Cancel(); - MainThreadOnly().renderer_hidden_ = false; + MainThreadOnly().renderer_hidden = false; EndIdlePeriod(); TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( @@ -242,11 +266,11 @@ void RendererSchedulerImpl::OnRendererBackgrounded() { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererSchedulerImpl::OnRendererBackgrounded"); helper_.CheckOnValidThread(); - if (helper_.IsShutdown() || MainThreadOnly().renderer_backgrounded_) + if (helper_.IsShutdown() || MainThreadOnly().renderer_backgrounded) return; - MainThreadOnly().renderer_backgrounded_ = true; - if (!MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled_) + MainThreadOnly().renderer_backgrounded = true; + if (!MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled) return; suspend_timers_when_backgrounded_closure_.Cancel(); @@ -262,10 +286,10 @@ void RendererSchedulerImpl::OnRendererForegrounded() { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererSchedulerImpl::OnRendererForegrounded"); helper_.CheckOnValidThread(); - if (helper_.IsShutdown() || !MainThreadOnly().renderer_backgrounded_) + if (helper_.IsShutdown() || !MainThreadOnly().renderer_backgrounded) return; - MainThreadOnly().renderer_backgrounded_ = false; + MainThreadOnly().renderer_backgrounded = false; suspend_timers_when_backgrounded_closure_.Cancel(); ResumeTimerQueueWhenForegrounded(); } @@ -322,14 +346,27 @@ void RendererSchedulerImpl::UpdateForInputEventOnCompositorThread( InputEventState input_event_state) { base::AutoLock lock(any_thread_lock_); base::TimeTicks now = helper_.Now(); - bool was_in_compositor_priority = InputSignalsSuggestCompositorPriority(now); + + // TODO(alexclarke): Move WebInputEventTraits where we can access it from here + // and record the name rather than the integer representation. + TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), + "RendererSchedulerImpl::UpdateForInputEventOnCompositorThread", + "type", static_cast(type), "input_event_state", + InputEventStateToString(input_event_state)); + + bool gesture_already_in_progress = InputSignalsSuggestGestureInProgress(now); bool was_awaiting_touch_start_response = - AnyThread().awaiting_touch_start_response_; + AnyThread().awaiting_touch_start_response; + + AnyThread().user_model.DidStartProcessingInputEvent(type, now); + + if (input_event_state == InputEventState::EVENT_CONSUMED_BY_COMPOSITOR) + AnyThread().user_model.DidFinishProcessingInputEvent(now); if (type) { switch (type) { case blink::WebInputEvent::TouchStart: - AnyThread().awaiting_touch_start_response_ = true; + AnyThread().awaiting_touch_start_response = true; break; case blink::WebInputEvent::TouchMove: @@ -338,10 +375,10 @@ void RendererSchedulerImpl::UpdateForInputEventOnCompositorThread( // response prioritization is no longer necessary. Otherwise, the // initial touchmove should preserve the touchstart response pending // state. - if (AnyThread().awaiting_touch_start_response_ && - CompositorThreadOnly().last_input_type_ == + if (AnyThread().awaiting_touch_start_response && + CompositorThreadOnly().last_input_type == blink::WebInputEvent::TouchMove) { - AnyThread().awaiting_touch_start_response_ = false; + AnyThread().awaiting_touch_start_response = false; } break; @@ -355,22 +392,18 @@ void RendererSchedulerImpl::UpdateForInputEventOnCompositorThread( break; default: - AnyThread().awaiting_touch_start_response_ = false; + AnyThread().awaiting_touch_start_response = false; break; } } - // Avoid unnecessary policy updates, while in compositor priority. - if (!was_in_compositor_priority || + // Avoid unnecessary policy updates, while a gesture is already in progress. + if (!gesture_already_in_progress || was_awaiting_touch_start_response != - AnyThread().awaiting_touch_start_response_) { + AnyThread().awaiting_touch_start_response) { EnsureUrgentPolicyUpdatePostedOnMainThread(FROM_HERE); } - AnyThread().last_input_signal_time_ = now; - CompositorThreadOnly().last_input_type_ = type; - - if (input_event_state == InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD) - AnyThread().pending_main_thread_input_event_count_++; + CompositorThreadOnly().last_input_type = type; } void RendererSchedulerImpl::DidHandleInputEventOnMainThread( @@ -380,9 +413,7 @@ void RendererSchedulerImpl::DidHandleInputEventOnMainThread( helper_.CheckOnValidThread(); if (ShouldPrioritizeInputEvent(web_input_event)) { base::AutoLock lock(any_thread_lock_); - AnyThread().last_input_signal_time_ = helper_.Now(); - if (AnyThread().pending_main_thread_input_event_count_ > 0) - AnyThread().pending_main_thread_input_event_count_--; + AnyThread().user_model.DidFinishProcessingInputEvent(helper_.Now()); } } @@ -392,12 +423,12 @@ bool RendererSchedulerImpl::IsHighPriorityWorkAnticipated() { return false; MaybeUpdatePolicy(); - // The touchstart and compositor policies indicate a strong likelihood of - // high-priority work in the near future. - return MainThreadOnly().current_policy_ == Policy::COMPOSITOR_PRIORITY || - MainThreadOnly().current_policy_ == - Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY || - MainThreadOnly().current_policy_ == Policy::TOUCHSTART_PRIORITY; + // The touchstart and main-thread gesture use cases indicate a strong + // likelihood of high-priority work in the near future. + UseCase use_case = MainThreadOnly().current_use_case; + return MainThreadOnly().touchstart_expected_soon || + use_case == UseCase::TOUCHSTART || + use_case == UseCase::MAIN_THREAD_GESTURE; } bool RendererSchedulerImpl::ShouldYieldForHighPriorityWork() { @@ -406,25 +437,26 @@ bool RendererSchedulerImpl::ShouldYieldForHighPriorityWork() { return false; MaybeUpdatePolicy(); - // We only yield if we are in the compositor priority and there is compositor - // work outstanding, or if we are in the touchstart response priority. - // Note: even though the control queue is higher priority we don't yield for - // it since these tasks are not user-provided work and they are only intended - // to run before the next task, not interrupt the tasks. - switch (MainThreadOnly().current_policy_) { - case Policy::NORMAL: - return false; - - case Policy::COMPOSITOR_PRIORITY: - return !compositor_task_runner_->IsQueueEmpty(); - - case Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY: - return !compositor_task_runner_->IsQueueEmpty(); - - case Policy::TOUCHSTART_PRIORITY: + // We only yield if there's a urgent task to be run now, or we are expecting + // one soon (touch start). + // Note: even though the control queue has the highest priority we don't yield + // for it since these tasks are not user-provided work and they are only + // intended to run before the next task, not interrupt the tasks. + switch (MainThreadOnly().current_use_case) { + case UseCase::NONE: + return MainThreadOnly().touchstart_expected_soon; + + case UseCase::COMPOSITOR_GESTURE: + return MainThreadOnly().touchstart_expected_soon; + + case UseCase::MAIN_THREAD_GESTURE: + return !compositor_task_runner_->IsQueueEmpty() || + MainThreadOnly().touchstart_expected_soon; + + case UseCase::TOUCHSTART: return true; - case Policy::LOADING_PRIORITY: + case UseCase::LOADING: return false; default: @@ -475,92 +507,142 @@ void RendererSchedulerImpl::UpdatePolicyLocked(UpdateType update_type) { base::TimeTicks now = helper_.Now(); policy_may_need_update_.SetWhileLocked(false); - AnyThread().timer_tasks_seem_expensive_ = - MainThreadOnly().expected_short_idle_period_duration_ > - base::TimeDelta() && - MainThreadOnly().timer_task_cost_estimator_.expected_task_duration() > - MainThreadOnly().expected_short_idle_period_duration_; + base::TimeDelta expected_use_case_duration; + UseCase use_case = ComputeCurrentUseCase(now, &expected_use_case_duration); + MainThreadOnly().current_use_case = use_case; + + // TODO(alexclarke): We should wire up a signal from blink to let us know if + // there are any touch handlers registerd or not, and only call + // TouchStartExpectedSoon if there is at least one. NOTE a TouchStart will + // only actually get sent if there is a touch handler. + base::TimeDelta touchstart_expected_flag_valid_for_duration; + bool touchstart_expected_soon = AnyThread().user_model.IsGestureExpectedSoon( + use_case, now, &touchstart_expected_flag_valid_for_duration); + MainThreadOnly().touchstart_expected_soon = touchstart_expected_soon; + + bool loading_tasks_seem_expensive = + MainThreadOnly().loading_task_cost_estimator.expected_task_duration() > + MainThreadOnly().expected_short_idle_period_duration; + MainThreadOnly().loading_tasks_seem_expensive = loading_tasks_seem_expensive; + + bool timer_tasks_seem_expensive = + MainThreadOnly().timer_task_cost_estimator.expected_task_duration() > + MainThreadOnly().expected_short_idle_period_duration; + MainThreadOnly().timer_tasks_seem_expensive = timer_tasks_seem_expensive; + + // The |new_policy_duration| is the minimum of |expected_use_case_duration| + // and |touchstart_expected_flag_valid_for_duration| unless one is zero in + // which case we choose the other. + base::TimeDelta new_policy_duration = expected_use_case_duration; + if (new_policy_duration == base::TimeDelta() || + (touchstart_expected_flag_valid_for_duration > base::TimeDelta() && + new_policy_duration > touchstart_expected_flag_valid_for_duration)) { + new_policy_duration = touchstart_expected_flag_valid_for_duration; + } - base::TimeDelta new_policy_duration; - Policy new_policy = ComputeNewPolicy(now, &new_policy_duration); if (new_policy_duration > base::TimeDelta()) { - MainThreadOnly().current_policy_expiration_time_ = - now + new_policy_duration; + MainThreadOnly().current_policy_expiration_time = now + new_policy_duration; delayed_update_policy_runner_.SetDeadline(FROM_HERE, new_policy_duration, now); } else { - MainThreadOnly().current_policy_expiration_time_ = base::TimeTicks(); + MainThreadOnly().current_policy_expiration_time = base::TimeTicks(); } - if (update_type == UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED && - new_policy == MainThreadOnly().current_policy_) - return; - - TaskQueue::QueuePriority compositor_queue_priority = - TaskQueue::NORMAL_PRIORITY; - TaskQueue::QueuePriority loading_queue_priority = TaskQueue::NORMAL_PRIORITY; - TaskQueue::QueuePriority timer_queue_priority = TaskQueue::NORMAL_PRIORITY; - - if (MainThreadOnly().timer_queue_suspend_count_ != 0 || - MainThreadOnly().timer_queue_suspended_when_backgrounded_) { - timer_queue_priority = TaskQueue::DISABLED_PRIORITY; - } - - switch (new_policy) { - case Policy::COMPOSITOR_PRIORITY: - compositor_queue_priority = TaskQueue::HIGH_PRIORITY; + Policy new_policy; + bool block_expensive_tasks = false; + switch (use_case) { + case UseCase::COMPOSITOR_GESTURE: + if (touchstart_expected_soon) { + block_expensive_tasks = true; + } else { + // What we really want to do is priorize loading tasks, but that doesn't + // seem to be safe. Instead we do that by proxy by deprioritizing + // compositor tasks. This should be safe since we've already gone to the + // pain of fixing ordering issues with them. + new_policy.compositor_queue_priority = TaskQueue::BEST_EFFORT_PRIORITY; + } break; - case Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY: - compositor_queue_priority = TaskQueue::HIGH_PRIORITY; - loading_queue_priority = TaskQueue::DISABLED_PRIORITY; - timer_queue_priority = TaskQueue::DISABLED_PRIORITY; + + case UseCase::MAIN_THREAD_GESTURE: + new_policy.compositor_queue_priority = TaskQueue::HIGH_PRIORITY; + block_expensive_tasks = true; break; - case Policy::TOUCHSTART_PRIORITY: - compositor_queue_priority = TaskQueue::HIGH_PRIORITY; - loading_queue_priority = TaskQueue::DISABLED_PRIORITY; - timer_queue_priority = TaskQueue::DISABLED_PRIORITY; + + case UseCase::TOUCHSTART: + new_policy.compositor_queue_priority = TaskQueue::HIGH_PRIORITY; + new_policy.loading_queue_priority = TaskQueue::DISABLED_PRIORITY; + new_policy.timer_queue_priority = TaskQueue::DISABLED_PRIORITY; + block_expensive_tasks = true; // NOTE this is a nop due to the above. break; - case Policy::NORMAL: + + case UseCase::NONE: + if (touchstart_expected_soon) + block_expensive_tasks = true; break; - case Policy::LOADING_PRIORITY: - // We prioritize loading tasks by deprioritizing compositing and timers. - compositor_queue_priority = TaskQueue::BEST_EFFORT_PRIORITY; - timer_queue_priority = TaskQueue::BEST_EFFORT_PRIORITY; - // TODO(alexclarke): See if we can safely mark the loading task queue as - // high priority. + + case UseCase::LOADING: + new_policy.loading_queue_priority = TaskQueue::HIGH_PRIORITY; + new_policy.default_queue_priority = TaskQueue::HIGH_PRIORITY; break; + default: NOTREACHED(); } - compositor_task_runner_->SetQueuePriority(compositor_queue_priority); - loading_task_runner_->SetQueuePriority(loading_queue_priority); - timer_task_runner_->SetQueuePriority(timer_queue_priority); + // Don't block expensive tasks unless we have actually seen something. + if (!MainThreadOnly().have_seen_a_begin_main_frame) + block_expensive_tasks = false; - DCHECK(compositor_task_runner_->IsQueueEnabled()); - if (new_policy != Policy::TOUCHSTART_PRIORITY && - new_policy != Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY) { - DCHECK(loading_task_runner_->IsQueueEnabled()); + if (block_expensive_tasks && loading_tasks_seem_expensive) + new_policy.loading_queue_priority = TaskQueue::DISABLED_PRIORITY; + + if ((block_expensive_tasks && timer_tasks_seem_expensive) || + MainThreadOnly().timer_queue_suspend_count != 0 || + MainThreadOnly().timer_queue_suspended_when_backgrounded) { + new_policy.timer_queue_priority = TaskQueue::DISABLED_PRIORITY; } - MainThreadOnly().current_policy_ = new_policy; + // Tracing is done before the early out check, because it's quite possible we + // will otherwise miss this information in traces. TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler", this, AsValueLocked(now)); + TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "use_case", + use_case); TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), - "RendererScheduler.policy", MainThreadOnly().current_policy_); + "RendererScheduler.loading_tasks_seem_expensive", + MainThreadOnly().loading_tasks_seem_expensive); TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler.timer_tasks_seem_expensive", - AnyThread().timer_tasks_seem_expensive_); + MainThreadOnly().timer_tasks_seem_expensive); + + if (update_type == UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED && + new_policy == MainThreadOnly().current_policy) { + return; + } + + compositor_task_runner_->SetQueuePriority( + new_policy.compositor_queue_priority); + loading_task_runner_->SetQueuePriority(new_policy.loading_queue_priority); + timer_task_runner_->SetQueuePriority(new_policy.timer_queue_priority); + + // TODO(alexclarke): We shouldn't have to prioritize the default queue, but it + // appears to be necessary since the order of loading tasks and IPCs (which + // are mostly dispatched on the default queue) need to be preserved. + helper_.DefaultTaskRunner()->SetQueuePriority( + new_policy.default_queue_priority); + + DCHECK(compositor_task_runner_->IsQueueEnabled()); + MainThreadOnly().current_policy = new_policy; } -bool RendererSchedulerImpl::InputSignalsSuggestCompositorPriority( +bool RendererSchedulerImpl::InputSignalsSuggestGestureInProgress( base::TimeTicks now) const { base::TimeDelta unused_policy_duration; - switch (ComputeNewPolicy(now, &unused_policy_duration)) { - case Policy::TOUCHSTART_PRIORITY: - case Policy::COMPOSITOR_PRIORITY: - case Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY: + switch (ComputeCurrentUseCase(now, &unused_policy_duration)) { + case UseCase::COMPOSITOR_GESTURE: + case UseCase::MAIN_THREAD_GESTURE: + case UseCase::TOUCHSTART: return true; default: @@ -569,52 +651,35 @@ bool RendererSchedulerImpl::InputSignalsSuggestCompositorPriority( return false; } -RendererSchedulerImpl::Policy RendererSchedulerImpl::ComputeNewPolicy( +RendererSchedulerImpl::UseCase RendererSchedulerImpl::ComputeCurrentUseCase( base::TimeTicks now, - base::TimeDelta* new_policy_duration) const { + base::TimeDelta* expected_use_case_duration) const { any_thread_lock_.AssertAcquired(); // Above all else we want to be responsive to user input. - *new_policy_duration = TimeLeftInInputEscalatedPolicy(now); - if (*new_policy_duration > base::TimeDelta()) { - if (AnyThread().awaiting_touch_start_response_) - return Policy::TOUCHSTART_PRIORITY; - // If BeginMainFrame is on the critical path, we want to try and prevent - // timers and loading tasks from running if we think they might be - // expensive. + *expected_use_case_duration = + AnyThread().user_model.TimeLeftInUserGesture(now); + if (*expected_use_case_duration > base::TimeDelta()) { + // Has scrolling been fully established? + if (AnyThread().awaiting_touch_start_response) { + // No, so arrange for compositor tasks to be run at the highest priority. + return UseCase::TOUCHSTART; + } + // Yes scrolling has been established. If BeginMainFrame is on the critical + // path, compositor tasks need to be prioritized, otherwise now might be a + // good time to run potentially expensive work. // TODO(skyostil): Consider removing in_idle_period_ and // HadAnIdlePeriodRecently() unless we need them here. - if (AnyThread().timer_tasks_seem_expensive_ && - AnyThread().begin_main_frame_on_critical_path_) { - return Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY; + if (AnyThread().begin_main_frame_on_critical_path) { + return UseCase::MAIN_THREAD_GESTURE; + } else { + return UseCase::COMPOSITOR_GESTURE; } - return Policy::COMPOSITOR_PRIORITY; - } - - if (AnyThread().rails_loading_priority_deadline_ > now) { - *new_policy_duration = AnyThread().rails_loading_priority_deadline_ - now; - return Policy::LOADING_PRIORITY; } - return Policy::NORMAL; -} - -base::TimeDelta RendererSchedulerImpl::TimeLeftInInputEscalatedPolicy( - base::TimeTicks now) const { - any_thread_lock_.AssertAcquired(); - - base::TimeDelta escalated_priority_duration = - base::TimeDelta::FromMilliseconds(kPriorityEscalationAfterInputMillis); + // TODO(alexclarke): return UseCase::LOADING if signals suggest the system is + // in the initial 1s of RAIL loading. - // If the input event is still pending, go into input prioritized policy - // and check again later. - if (AnyThread().pending_main_thread_input_event_count_ > 0) - return escalated_priority_duration; - if (AnyThread().last_input_signal_time_.is_null() || - AnyThread().last_input_signal_time_ + escalated_priority_duration < now) { - return base::TimeDelta(); - } - return AnyThread().last_input_signal_time_ + escalated_priority_duration - - now; + return UseCase::NONE; } bool RendererSchedulerImpl::CanEnterLongIdlePeriod( @@ -623,11 +688,11 @@ bool RendererSchedulerImpl::CanEnterLongIdlePeriod( helper_.CheckOnValidThread(); MaybeUpdatePolicy(); - if (MainThreadOnly().current_policy_ == Policy::TOUCHSTART_PRIORITY) { + if (MainThreadOnly().current_use_case == UseCase::TOUCHSTART) { // Don't start a long idle task in touch start priority, try again when // the policy is scheduled to end. *next_long_idle_period_delay_out = - MainThreadOnly().current_policy_expiration_time_ - now; + MainThreadOnly().current_policy_expiration_time - now; return false; } return true; @@ -637,41 +702,32 @@ SchedulerHelper* RendererSchedulerImpl::GetSchedulerHelperForTesting() { return &helper_; } +TaskCostEstimator* +RendererSchedulerImpl::GetLoadingTaskCostEstimatorForTesting() { + return &MainThreadOnly().loading_task_cost_estimator; +} + +TaskCostEstimator* +RendererSchedulerImpl::GetTimerTaskCostEstimatorForTesting() { + return &MainThreadOnly().timer_task_cost_estimator; +} + void RendererSchedulerImpl::SuspendTimerQueue() { - MainThreadOnly().timer_queue_suspend_count_++; + MainThreadOnly().timer_queue_suspend_count++; ForceUpdatePolicy(); DCHECK(!timer_task_runner_->IsQueueEnabled()); } void RendererSchedulerImpl::ResumeTimerQueue() { - MainThreadOnly().timer_queue_suspend_count_--; - DCHECK_GE(MainThreadOnly().timer_queue_suspend_count_, 0); + MainThreadOnly().timer_queue_suspend_count--; + DCHECK_GE(MainThreadOnly().timer_queue_suspend_count, 0); ForceUpdatePolicy(); } void RendererSchedulerImpl::SetTimerQueueSuspensionWhenBackgroundedEnabled( bool enabled) { // Note that this will only take effect for the next backgrounded signal. - MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled_ = enabled; -} - -// static -const char* RendererSchedulerImpl::PolicyToString(Policy policy) { - switch (policy) { - case Policy::NORMAL: - return "normal"; - case Policy::COMPOSITOR_PRIORITY: - return "compositor"; - case Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY: - return "compositor_critical_path"; - case Policy::TOUCHSTART_PRIORITY: - return "touchstart"; - case Policy::LOADING_PRIORITY: - return "loading"; - default: - NOTREACHED(); - return nullptr; - } + MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled = enabled; } scoped_refptr @@ -690,95 +746,117 @@ RendererSchedulerImpl::AsValueLocked(base::TimeTicks optional_now) const { scoped_refptr state = new base::trace_event::TracedValue(); - state->SetString("current_policy", - PolicyToString(MainThreadOnly().current_policy_)); + state->SetString("current_use_case", + UseCaseToString(MainThreadOnly().current_use_case)); + state->SetBoolean("loading_tasks_seem_expensive", + MainThreadOnly().loading_tasks_seem_expensive); + state->SetBoolean("timer_tasks_seem_expensive", + MainThreadOnly().timer_tasks_seem_expensive); + state->SetBoolean("touchstart_expected_soon", + MainThreadOnly().touchstart_expected_soon); state->SetString("idle_period_state", IdleHelper::IdlePeriodStateToString( idle_helper_.SchedulerIdlePeriodState())); - state->SetBoolean("renderer_hidden", MainThreadOnly().renderer_hidden_); + state->SetBoolean("renderer_hidden", MainThreadOnly().renderer_hidden); state->SetBoolean("renderer_backgrounded", - MainThreadOnly().renderer_backgrounded_); + MainThreadOnly().renderer_backgrounded); state->SetBoolean("timer_queue_suspended_when_backgrounded", - MainThreadOnly().timer_queue_suspended_when_backgrounded_); + MainThreadOnly().timer_queue_suspended_when_backgrounded); state->SetInteger("timer_queue_suspend_count", - MainThreadOnly().timer_queue_suspend_count_); + MainThreadOnly().timer_queue_suspend_count); state->SetDouble("now", (optional_now - base::TimeTicks()).InMillisecondsF()); - state->SetDouble("last_input_signal_time", - (AnyThread().last_input_signal_time_ - base::TimeTicks()) - .InMillisecondsF()); - state->SetDouble("rails_loading_priority_deadline", - (AnyThread().rails_loading_priority_deadline_ - - base::TimeTicks()).InMillisecondsF()); + state->SetDouble( + "rails_loading_priority_deadline", + (AnyThread().rails_loading_priority_deadline - base::TimeTicks()) + .InMillisecondsF()); state->SetDouble("last_idle_period_end_time", - (AnyThread().last_idle_period_end_time_ - base::TimeTicks()) + (AnyThread().last_idle_period_end_time - base::TimeTicks()) .InMillisecondsF()); - state->SetInteger("pending_main_thread_input_event_count", - AnyThread().pending_main_thread_input_event_count_); state->SetBoolean("awaiting_touch_start_response", - AnyThread().awaiting_touch_start_response_); + AnyThread().awaiting_touch_start_response); state->SetBoolean("begin_main_frame_on_critical_path", - AnyThread().begin_main_frame_on_critical_path_); + AnyThread().begin_main_frame_on_critical_path); + state->SetDouble("expected_loading_task_duration", + MainThreadOnly() + .loading_task_cost_estimator.expected_task_duration() + .InMillisecondsF()); state->SetDouble("expected_timer_task_duration", MainThreadOnly() - .timer_task_cost_estimator_.expected_task_duration() + .timer_task_cost_estimator.expected_task_duration() .InMillisecondsF()); // TODO(skyostil): Can we somehow trace how accurate these estimates were? state->SetDouble( "expected_short_idle_period_duration", - MainThreadOnly().expected_short_idle_period_duration_.InMillisecondsF()); - state->SetBoolean("timer_tasks_seem_expensive", - AnyThread().timer_tasks_seem_expensive_); - state->SetDouble("estimated_next_frame_begin", - (MainThreadOnly().estimated_next_frame_begin_ - - base::TimeTicks()).InMillisecondsF()); - state->SetBoolean("in_idle_period", AnyThread().in_idle_period_); + MainThreadOnly().expected_short_idle_period_duration.InMillisecondsF()); + state->SetDouble( + "estimated_next_frame_begin", + (MainThreadOnly().estimated_next_frame_begin - base::TimeTicks()) + .InMillisecondsF()); + state->SetBoolean("in_idle_period", AnyThread().in_idle_period); + AnyThread().user_model.AsValueInto(state.get()); return state; } void RendererSchedulerImpl::OnIdlePeriodStarted() { base::AutoLock lock(any_thread_lock_); - AnyThread().in_idle_period_ = true; + AnyThread().in_idle_period = true; UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED); } void RendererSchedulerImpl::OnIdlePeriodEnded() { base::AutoLock lock(any_thread_lock_); - AnyThread().last_idle_period_end_time_ = helper_.Now(); - AnyThread().in_idle_period_ = false; + AnyThread().last_idle_period_end_time = helper_.Now(); + AnyThread().in_idle_period = false; UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED); } void RendererSchedulerImpl::OnPageLoadStarted() { + TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), + "RendererSchedulerImpl::OnPageLoadStarted"); base::AutoLock lock(any_thread_lock_); - AnyThread().rails_loading_priority_deadline_ = + AnyThread().rails_loading_priority_deadline = helper_.Now() + base::TimeDelta::FromMilliseconds( kRailsInitialLoadingPrioritizationMillis); - UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED); + ResetForNavigationLocked(); } bool RendererSchedulerImpl::HadAnIdlePeriodRecently(base::TimeTicks now) const { - return (now - AnyThread().last_idle_period_end_time_) <= + return (now - AnyThread().last_idle_period_end_time) <= base::TimeDelta::FromMilliseconds( kIdlePeriodStarvationThresholdMillis); } void RendererSchedulerImpl::SuspendTimerQueueWhenBackgrounded() { - DCHECK(MainThreadOnly().renderer_backgrounded_); - if (MainThreadOnly().timer_queue_suspended_when_backgrounded_) + DCHECK(MainThreadOnly().renderer_backgrounded); + if (MainThreadOnly().timer_queue_suspended_when_backgrounded) return; - MainThreadOnly().timer_queue_suspended_when_backgrounded_ = true; + MainThreadOnly().timer_queue_suspended_when_backgrounded = true; ForceUpdatePolicy(); } void RendererSchedulerImpl::ResumeTimerQueueWhenForegrounded() { - DCHECK(!MainThreadOnly().renderer_backgrounded_); - if (!MainThreadOnly().timer_queue_suspended_when_backgrounded_) + DCHECK(!MainThreadOnly().renderer_backgrounded); + if (!MainThreadOnly().timer_queue_suspended_when_backgrounded) return; - MainThreadOnly().timer_queue_suspended_when_backgrounded_ = false; + MainThreadOnly().timer_queue_suspended_when_backgrounded = false; ForceUpdatePolicy(); } +void RendererSchedulerImpl::ResetForNavigationLocked() { + helper_.CheckOnValidThread(); + any_thread_lock_.AssertAcquired(); + MainThreadOnly().loading_task_cost_estimator.Clear(); + MainThreadOnly().timer_task_cost_estimator.Clear(); + MainThreadOnly().short_idle_period_duration.Clear(); + // Make sure that we don't initially assume there is no idle time. + MainThreadOnly().short_idle_period_duration.InsertSample( + cc::BeginFrameArgs::DefaultInterval()); + AnyThread().user_model.Reset(); + MainThreadOnly().have_seen_a_begin_main_frame = false; + UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED); +} + } // namespace scheduler diff --git a/components/scheduler/renderer/renderer_scheduler_impl.h b/components/scheduler/renderer/renderer_scheduler_impl.h index 1898ca81bd88..bd7d7ad7e978 100644 --- a/components/scheduler/renderer/renderer_scheduler_impl.h +++ b/components/scheduler/renderer/renderer_scheduler_impl.h @@ -13,6 +13,7 @@ #include "components/scheduler/renderer/deadline_task_runner.h" #include "components/scheduler/renderer/renderer_scheduler.h" #include "components/scheduler/renderer/task_cost_estimator.h" +#include "components/scheduler/renderer/user_model.h" #include "components/scheduler/scheduler_export.h" namespace base { @@ -61,23 +62,30 @@ class SCHEDULER_EXPORT RendererSchedulerImpl : public RendererScheduler, void ResumeTimerQueue() override; void SetTimerQueueSuspensionWhenBackgroundedEnabled(bool enabled) override; + // Test helpers. SchedulerHelper* GetSchedulerHelperForTesting(); + TaskCostEstimator* GetLoadingTaskCostEstimatorForTesting(); + TaskCostEstimator* GetTimerTaskCostEstimatorForTesting(); base::TimeTicks CurrentIdleTaskDeadlineForTesting() const; private: friend class RendererSchedulerImplTest; friend class RendererSchedulerImplForTest; - // Keep RendererSchedulerImpl::PolicyToString in sync with this enum. - enum class Policy { - NORMAL, - COMPOSITOR_PRIORITY, - COMPOSITOR_CRITICAL_PATH_PRIORITY, - TOUCHSTART_PRIORITY, - LOADING_PRIORITY, - // Must be the last entry. - POLICY_COUNT, - FIRST_POLICY = NORMAL, + struct Policy { + Policy(); + + TaskQueue::QueuePriority compositor_queue_priority; + TaskQueue::QueuePriority loading_queue_priority; + TaskQueue::QueuePriority timer_queue_priority; + TaskQueue::QueuePriority default_queue_priority; + + bool operator==(const Policy& other) const { + return compositor_queue_priority == other.compositor_queue_priority && + loading_queue_priority == other.loading_queue_priority && + timer_queue_priority == other.timer_queue_priority && + default_queue_priority == other.default_queue_priority; + } }; class PollableNeedsUpdateFlag { @@ -113,14 +121,10 @@ class SCHEDULER_EXPORT RendererSchedulerImpl : public RendererScheduler, base::TimeTicks optional_now) const; scoped_refptr AsValueLocked( base::TimeTicks optional_now) const; - static const char* PolicyToString(Policy policy); static bool ShouldPrioritizeInputEvent( const blink::WebInputEvent& web_input_event); - // The time we should stay in a priority-escalated mode after an input event. - static const int kPriorityEscalationAfterInputMillis = 100; - // The amount of time which idle periods can continue being scheduled when the // renderer has been hidden, before going to sleep for good. static const int kEndIdleWhenHiddenDelayMillis = 10000; @@ -137,7 +141,7 @@ class SCHEDULER_EXPORT RendererSchedulerImpl : public RendererScheduler, static const int kIdlePeriodStarvationThresholdMillis = 10000; // The amount of time to wait before suspending shared timers after the - // renderer has been backgrounded. This is use donly if background suspension + // renderer has been backgrounded. This is used only if background suspension // of shared timers is enabled. static const int kSuspendTimersWhenBackgroundedDelayMillis = 5 * 60 * 1000; @@ -167,20 +171,18 @@ class SCHEDULER_EXPORT RendererSchedulerImpl : public RendererScheduler, // early out if |update_type| is MAY_EARLY_OUT_IF_POLICY_UNCHANGED. virtual void UpdatePolicyLocked(UpdateType update_type); - // Returns the amount of time left in the current input escalated priority - // policy. Can be called from any thread. - base::TimeDelta TimeLeftInInputEscalatedPolicy(base::TimeTicks now) const; - - // Helper for computing the new policy. |new_policy_duration| will be filled - // with the amount of time after which the policy should be updated again. If - // the duration is zero, a new policy update will not be scheduled. Must be - // called with |any_thread_lock_| held. Can be called from any thread. - Policy ComputeNewPolicy(base::TimeTicks now, - base::TimeDelta* new_policy_duration) const; + // Helper for computing the use case. |expected_usecase_duration| will be + // filled with the amount of time after which the use case should be updated + // again. If the duration is zero, a new use case update should not be + // scheduled. Must be called with |any_thread_lock_| held. Can be called from + // any thread. + UseCase ComputeCurrentUseCase( + base::TimeTicks now, + base::TimeDelta* expected_use_case_duration) const; - // Works out if compositor tasks would be prioritized based on the current - // input signals. Can be called from any thread. - bool InputSignalsSuggestCompositorPriority(base::TimeTicks now) const; + // Works out if a gesture appears to be in progress based on the current + // input signals. Can be called from any thread. + bool InputSignalsSuggestGestureInProgress(base::TimeTicks now) const; // An input event of some sort happened, the policy may need updating. void UpdateForInputEventOnCompositorThread(blink::WebInputEvent::Type type, @@ -195,6 +197,10 @@ class SCHEDULER_EXPORT RendererSchedulerImpl : public RendererScheduler, void SuspendTimerQueueWhenBackgrounded(); void ResumeTimerQueueWhenForegrounded(); + // The task cost estimators and the UserModel need to be reset upon page + // nagigation. This function does that. Must be called from the main thread. + void ResetForNavigationLocked(); + SchedulerHelper helper_; IdleHelper idle_helper_; @@ -215,47 +221,51 @@ class SCHEDULER_EXPORT RendererSchedulerImpl : public RendererScheduler, MainThreadOnly(); ~MainThreadOnly(); - TaskCostEstimator timer_task_cost_estimator_; - cc::RollingTimeDeltaHistory short_idle_period_duration_; - Policy current_policy_; - base::TimeTicks current_policy_expiration_time_; - base::TimeTicks estimated_next_frame_begin_; - base::TimeDelta expected_short_idle_period_duration_; - int timer_queue_suspend_count_; // TIMER_TASK_QUEUE suspended if non-zero. - bool renderer_hidden_; - bool renderer_backgrounded_; - bool timer_queue_suspension_when_backgrounded_enabled_; - bool timer_queue_suspended_when_backgrounded_; - bool was_shutdown_; + TaskCostEstimator loading_task_cost_estimator; + TaskCostEstimator timer_task_cost_estimator; + cc::RollingTimeDeltaHistory short_idle_period_duration; + UseCase current_use_case; + Policy current_policy; + base::TimeTicks current_policy_expiration_time; + base::TimeTicks estimated_next_frame_begin; + base::TimeDelta expected_short_idle_period_duration; + int timer_queue_suspend_count; // TIMER_TASK_QUEUE suspended if non-zero. + bool renderer_hidden; + bool renderer_backgrounded; + bool timer_queue_suspension_when_backgrounded_enabled; + bool timer_queue_suspended_when_backgrounded; + bool was_shutdown; + bool loading_tasks_seem_expensive; + bool timer_tasks_seem_expensive; + bool touchstart_expected_soon; + bool have_seen_a_begin_main_frame; }; struct AnyThread { AnyThread(); - base::TimeTicks last_input_signal_time_; - base::TimeTicks last_idle_period_end_time_; - base::TimeTicks rails_loading_priority_deadline_; - int pending_main_thread_input_event_count_; - bool awaiting_touch_start_response_; - bool in_idle_period_; - bool begin_main_frame_on_critical_path_; - bool timer_tasks_seem_expensive_; + base::TimeTicks last_idle_period_end_time; + base::TimeTicks rails_loading_priority_deadline; + UserModel user_model; + bool awaiting_touch_start_response; + bool in_idle_period; + bool begin_main_frame_on_critical_path; }; struct CompositorThreadOnly { CompositorThreadOnly(); ~CompositorThreadOnly(); - blink::WebInputEvent::Type last_input_type_; - scoped_ptr compositor_thread_checker_; + blink::WebInputEvent::Type last_input_type; + scoped_ptr compositor_thread_checker; void CheckOnValidThread() { #if DCHECK_IS_ON() // We don't actually care which thread this called from, just so long as // its consistent. - if (!compositor_thread_checker_) - compositor_thread_checker_.reset(new base::ThreadChecker()); - DCHECK(compositor_thread_checker_->CalledOnValidThread()); + if (!compositor_thread_checker) + compositor_thread_checker.reset(new base::ThreadChecker()); + DCHECK(compositor_thread_checker->CalledOnValidThread()); #endif } }; diff --git a/components/scheduler/renderer/renderer_scheduler_impl_unittest.cc b/components/scheduler/renderer/renderer_scheduler_impl_unittest.cc index f34f2f01fb52..e0caee0ed8f2 100644 --- a/components/scheduler/renderer/renderer_scheduler_impl_unittest.cc +++ b/components/scheduler/renderer/renderer_scheduler_impl_unittest.cc @@ -126,15 +126,46 @@ void PostingYieldingTestTask(RendererSchedulerImpl* scheduler, *should_yield_after = scheduler->ShouldYieldForHighPriorityWork(); } +enum class SimulateInputType { + None, + TouchStart, + TouchEnd, + GestureScrollBegin, + GestureScrollEnd +}; + void AnticipationTestTask(RendererSchedulerImpl* scheduler, - bool simulate_input, + SimulateInputType simulate_input, bool* is_anticipated_before, bool* is_anticipated_after) { *is_anticipated_before = scheduler->IsHighPriorityWorkAnticipated(); - if (simulate_input) { - scheduler->DidHandleInputEventOnCompositorThread( - FakeInputEvent(blink::WebInputEvent::GestureFlingStart), - RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); + switch (simulate_input) { + case SimulateInputType::None: + break; + + case SimulateInputType::TouchStart: + scheduler->DidHandleInputEventOnCompositorThread( + FakeInputEvent(blink::WebInputEvent::TouchStart), + RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); + break; + + case SimulateInputType::TouchEnd: + scheduler->DidHandleInputEventOnCompositorThread( + FakeInputEvent(blink::WebInputEvent::TouchEnd), + RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); + break; + + case SimulateInputType::GestureScrollBegin: + scheduler->DidHandleInputEventOnCompositorThread( + FakeInputEvent(blink::WebInputEvent::GestureScrollBegin), + RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); + break; + + case SimulateInputType::GestureScrollEnd: + scheduler->DidHandleInputEventOnCompositorThread( + FakeInputEvent(blink::WebInputEvent::GestureScrollEnd), + RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); + break; } *is_anticipated_after = scheduler->IsHighPriorityWorkAnticipated(); } @@ -145,8 +176,6 @@ class RendererSchedulerImplForTest : public RendererSchedulerImpl { public: using RendererSchedulerImpl::OnIdlePeriodEnded; using RendererSchedulerImpl::OnIdlePeriodStarted; - using RendererSchedulerImpl::Policy; - using RendererSchedulerImpl::PolicyToString; RendererSchedulerImplForTest( scoped_refptr main_task_runner) @@ -155,6 +184,14 @@ class RendererSchedulerImplForTest : public RendererSchedulerImpl { void UpdatePolicyLocked(UpdateType update_type) override { update_policy_count_++; RendererSchedulerImpl::UpdatePolicyLocked(update_type); + + std::string use_case = + RendererScheduler::UseCaseToString(MainThreadOnly().current_use_case); + if (MainThreadOnly().touchstart_expected_soon) { + use_cases_.push_back(use_case + " scroll expected"); + } else { + use_cases_.push_back(use_case); + } } void EnsureUrgentPolicyUpdatePostedOnMainThread() { @@ -169,21 +206,22 @@ class RendererSchedulerImplForTest : public RendererSchedulerImpl { bool BeginMainFrameOnCriticalPath() { base::AutoLock lock(any_thread_lock_); - return AnyThread().begin_main_frame_on_critical_path_; + return AnyThread().begin_main_frame_on_critical_path; } int update_policy_count_; + std::vector use_cases_; }; // Lets gtest print human readable Policy values. ::std::ostream& operator<<(::std::ostream& os, - const RendererSchedulerImplForTest::Policy& policy) { - return os << RendererSchedulerImplForTest::PolicyToString(policy); + const RendererScheduler::UseCase& use_case) { + return os << RendererScheduler::UseCaseToString(use_case); } class RendererSchedulerImplTest : public testing::Test { public: - using Policy = RendererSchedulerImpl::Policy; + using UseCase = RendererSchedulerImpl::UseCase; RendererSchedulerImplTest() : clock_(new base::SimpleTestTickClock()) { clock_->Advance(base::TimeDelta::FromMicroseconds(5000)); @@ -223,6 +261,11 @@ class RendererSchedulerImplTest : public testing::Test { ->GetTaskQueueManagerForTesting() ->SetTimeSourceForTesting( make_scoped_ptr(new TestTimeSource(clock_.get()))); + scheduler_->GetLoadingTaskCostEstimatorForTesting() + ->SetTimeSourceForTesting( + make_scoped_ptr(new TestTimeSource(clock_.get()))); + scheduler_->GetTimerTaskCostEstimatorForTesting()->SetTimeSourceForTesting( + make_scoped_ptr(new TestTimeSource(clock_.get()))); } void TearDown() override { @@ -249,16 +292,47 @@ class RendererSchedulerImplTest : public testing::Test { } void DoMainFrame() { - scheduler_->WillBeginFrame(cc::BeginFrameArgs::Create( + cc::BeginFrameArgs begin_frame_args = cc::BeginFrameArgs::Create( BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(), - base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL)); + base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL); + begin_frame_args.on_critical_path = false; + scheduler_->WillBeginFrame(begin_frame_args); scheduler_->DidCommitFrameToCompositor(); } + void ForceMainThreadScrollingUseCase() { + cc::BeginFrameArgs begin_frame_args = cc::BeginFrameArgs::Create( + BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(), + base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL); + begin_frame_args.on_critical_path = true; + scheduler_->WillBeginFrame(begin_frame_args); + } + void EnableIdleTasks() { DoMainFrame(); } - Policy CurrentPolicy() { - return scheduler_->MainThreadOnly().current_policy_; + UseCase CurrentUseCase() { + return scheduler_->MainThreadOnly().current_use_case; + } + + UseCase ForceUpdatePolicyAndGetCurrentUseCase() { + scheduler_->ForceUpdatePolicy(); + return scheduler_->MainThreadOnly().current_use_case; + } + + bool TouchStartExpectedSoon() { + return scheduler_->MainThreadOnly().touchstart_expected_soon; + } + + bool HaveSeenABeginMainframe() { + return scheduler_->MainThreadOnly().have_seen_a_begin_main_frame; + } + + bool LoadingTasksSeemExpensive() { + return scheduler_->MainThreadOnly().loading_tasks_seem_expensive; + } + + bool TimerTasksSeemExpensive() { + return scheduler_->MainThreadOnly().timer_tasks_seem_expensive; } // Helper for posting several tasks of specific types. |task_descriptor| is a @@ -306,7 +380,12 @@ class RendererSchedulerImplTest : public testing::Test { protected: static base::TimeDelta priority_escalation_after_input_duration() { return base::TimeDelta::FromMilliseconds( - RendererSchedulerImpl::kPriorityEscalationAfterInputMillis); + UserModel::kGestureEstimationLimitMillis); + } + + static base::TimeDelta subsequent_input_expected_after_input_duration() { + return base::TimeDelta::FromMilliseconds( + UserModel::kExpectSubsequentGestureMillis); } static base::TimeDelta maximum_idle_period_duration() { @@ -339,11 +418,11 @@ class RendererSchedulerImplTest : public testing::Test { } } - static void CheckAllPolicyToString() { - CallForEachEnumValue( - RendererSchedulerImpl::Policy::FIRST_POLICY, - RendererSchedulerImpl::Policy::POLICY_COUNT, - &RendererSchedulerImpl::PolicyToString); + static void CheckAllUseCaseToString() { + CallForEachEnumValue( + RendererSchedulerImpl::UseCase::FIRST_USE_CASE, + RendererSchedulerImpl::UseCase::USE_CASE_COUNT, + &RendererSchedulerImpl::UseCaseToString); } scoped_ptr clock_; @@ -577,6 +656,7 @@ TEST_F(RendererSchedulerImplTest, TestDefaultPolicy) { testing::ElementsAre(std::string("L1"), std::string("D1"), std::string("C1"), std::string("D2"), std::string("C2"), std::string("I1"))); + EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase()); } TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_CompositorHandlesInput) { @@ -589,9 +669,10 @@ TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_CompositorHandlesInput) { EnableIdleTasks(); RunUntilIdle(); EXPECT_THAT(run_order, - testing::ElementsAre(std::string("C1"), std::string("C2"), - std::string("L1"), std::string("D1"), - std::string("D2"), std::string("I1"))); + testing::ElementsAre(std::string("L1"), std::string("D1"), + std::string("C1"), std::string("D2"), + std::string("C2"), std::string("I1"))); + EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase()); } TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_MainThreadHandlesInput) { @@ -604,9 +685,10 @@ TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_MainThreadHandlesInput) { EnableIdleTasks(); RunUntilIdle(); EXPECT_THAT(run_order, - testing::ElementsAre(std::string("C1"), std::string("C2"), - std::string("L1"), std::string("D1"), - std::string("D2"), std::string("I1"))); + testing::ElementsAre(std::string("L1"), std::string("D1"), + std::string("C1"), std::string("D2"), + std::string("C2"), std::string("I1"))); + EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase()); scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::GestureFlingStart)); } @@ -619,9 +701,10 @@ TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_DidAnimateForInput) { EnableIdleTasks(); RunUntilIdle(); EXPECT_THAT(run_order, - testing::ElementsAre(std::string("C1"), std::string("C2"), - std::string("D1"), std::string("D2"), + testing::ElementsAre(std::string("D1"), std::string("C1"), + std::string("D2"), std::string("C2"), std::string("I1"))); + EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase()); } TEST_F( @@ -629,6 +712,10 @@ TEST_F( TestCompositorPolicy_ExpensiveTimersDontRunWhenMainThreadOnCriticalPath) { std::vector run_order; + // RunUntilIdle won't actually run all of the SimpleTestTickClock::Advance + // tasks unless we set AutoAdvanceNow to true :/ + mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true); + // Simulate a bunch of expensive timer tasks for (int i = 0; i < 10; i++) { timer_task_runner_->PostTask( @@ -636,38 +723,66 @@ TEST_F( base::Unretained(clock_.get()), base::TimeDelta::FromMilliseconds(500))); } + RunUntilIdle(); - // Timers should now be disabled during main thread user userinteractions. + // Switch back to not auto-advancing because we want to be in control of when + // time advances. + mock_task_runner_->SetAutoAdvanceNowToPendingTasks(false); + + // Timers should now be disabled during main thread user user interactions. PostTestTasks(&run_order, "C1 T1"); + // Trigger main_thread_gesture UseCase scheduler_->DidAnimateForInputOnCompositorThread(); - scheduler_->WillBeginFrame(cc::BeginFrameArgs::Create( + cc::BeginFrameArgs begin_frame_args1 = cc::BeginFrameArgs::Create( BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(), - base::TimeDelta::FromMilliseconds(16), cc::BeginFrameArgs::NORMAL)); + base::TimeDelta::FromMilliseconds(16), cc::BeginFrameArgs::NORMAL); + begin_frame_args1.on_critical_path = true; + scheduler_->WillBeginFrame(begin_frame_args1); RunUntilIdle(); + EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("C1"))); - clock_->Advance(priority_escalation_after_input_duration() * 2); + clock_->Advance(subsequent_input_expected_after_input_duration() * 2); run_order.clear(); RunUntilIdle(); + EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("T1"))); } -TEST_F(RendererSchedulerImplTest, - TestCompositorPolicy_TimersAlwaysRunIfNoRecentIdlePeriod) { +TEST_F(RendererSchedulerImplTest, Navigation_ResetsTaskCostEstimations) { std::vector run_order; - PostTestTasks(&run_order, "C1 T1"); - // Simulate no recent idle period. - clock_->Advance(idle_period_starvation_threshold() * 2); + // RunUntilIdle won't actually run all of the SimpleTestTickClock::Advance + // tasks unless we set AutoAdvanceNow to true :/ + mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true); + + // Simulate a bunch of expensive timer tasks + for (int i = 0; i < 10; i++) { + timer_task_runner_->PostTask( + FROM_HERE, base::Bind(&base::SimpleTestTickClock::Advance, + base::Unretained(clock_.get()), + base::TimeDelta::FromMilliseconds(500))); + } + + RunUntilIdle(); + + // Switch back to not auto-advancing because we want to be in control of when + // time advances. + mock_task_runner_->SetAutoAdvanceNowToPendingTasks(false); + + scheduler_->OnPageLoadStarted(); + PostTestTasks(&run_order, "C1 T1"); scheduler_->DidAnimateForInputOnCompositorThread(); - scheduler_->WillBeginFrame(cc::BeginFrameArgs::Create( + cc::BeginFrameArgs begin_frame_args1 = cc::BeginFrameArgs::Create( BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(), - base::TimeDelta::FromMilliseconds(16), cc::BeginFrameArgs::NORMAL)); - + base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL); + begin_frame_args1.on_critical_path = true; + scheduler_->WillBeginFrame(begin_frame_args1); + scheduler_->DidCommitFrameToCompositor(); // Starts Idle Period RunUntilIdle(); EXPECT_THAT(run_order, @@ -796,30 +911,38 @@ TEST_F(RendererSchedulerImplTest, TestTouchstartPolicy_MainThread) { std::string("T2"))); } -TEST_F(RendererSchedulerImplTest, LoadingPriorityPolicy) { +// TODO(alexclarke): Reenable once we've reinstaed the Loading UseCase. +TEST_F(RendererSchedulerImplTest, DISABLED_LoadingUseCase) { std::vector run_order; - PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2"); + PostTestTasks(&run_order, "I1 D1 C1 T1 L1 D2 C2 T2 L2"); scheduler_->OnPageLoadStarted(); EnableIdleTasks(); RunUntilIdle(); - // In loading policy compositor tasks are best effort and should be run last. - EXPECT_THAT(run_order, - testing::ElementsAre(std::string("L1"), std::string("D1"), - std::string("D2"), std::string("I1"), - std::string("C1"), std::string("C2"))); - // Advance 1.5s and try again, the loading policy should have ended and the - // task order should return to normal. - clock_->Advance(base::TimeDelta::FromMilliseconds(1500)); + // In loading policy, loading tasks are prioritized other others. + std::string loading_policy_expected[] = { + std::string("D1"), std::string("L1"), std::string("D2"), + std::string("L2"), std::string("C1"), std::string("T1"), + std::string("C2"), std::string("T2"), std::string("I1")}; + EXPECT_THAT(run_order, testing::ElementsAreArray(loading_policy_expected)); + EXPECT_EQ(RendererScheduler::UseCase::LOADING, CurrentUseCase()); + + // Advance 15s and try again, the loading policy should have ended and the + // task order should return to the NONE use case where loading tasks are no + // longer prioritized. + clock_->Advance(base::TimeDelta::FromMilliseconds(150000)); run_order.clear(); - PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2"); + PostTestTasks(&run_order, "I1 D1 C1 T1 L1 D2 C2 T2 L2"); EnableIdleTasks(); RunUntilIdle(); - EXPECT_THAT(run_order, - testing::ElementsAre(std::string("L1"), std::string("D1"), - std::string("C1"), std::string("D2"), - std::string("C2"), std::string("I1"))); + + std::string default_order_expected[] = { + std::string("D1"), std::string("C1"), std::string("T1"), + std::string("L1"), std::string("D2"), std::string("C2"), + std::string("T2"), std::string("L2"), std::string("I1")}; + EXPECT_THAT(run_order, testing::ElementsAreArray(default_order_expected)); + EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase()); } TEST_F(RendererSchedulerImplTest, @@ -827,12 +950,14 @@ TEST_F(RendererSchedulerImplTest, std::vector run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); + ForceMainThreadScrollingUseCase(); + scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks. scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::MouseMove), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); - EnableIdleTasks(); RunUntilIdle(); // Note compositor tasks are not prioritized. + EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"), std::string("C1"), std::string("D2"), std::string("C2"), @@ -844,18 +969,18 @@ TEST_F(RendererSchedulerImplTest, std::vector run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); + ForceMainThreadScrollingUseCase(); + scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks. scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::MouseMove), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); - EnableIdleTasks(); RunUntilIdle(); // Note compositor tasks are not prioritized. + EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"), std::string("C1"), std::string("D2"), std::string("C2"), std::string("I1"))); - scheduler_->DidHandleInputEventOnMainThread( - FakeInputEvent(blink::WebInputEvent::MouseMove)); } TEST_F(RendererSchedulerImplTest, @@ -863,11 +988,12 @@ TEST_F(RendererSchedulerImplTest, std::vector run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); + ForceMainThreadScrollingUseCase(); + scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks. scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::MouseMove, blink::WebInputEvent::LeftButtonDown), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); - EnableIdleTasks(); RunUntilIdle(); // Note compositor tasks are prioritized. EXPECT_THAT(run_order, @@ -881,11 +1007,12 @@ TEST_F(RendererSchedulerImplTest, std::vector run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); + ForceMainThreadScrollingUseCase(); + scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks. scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::MouseMove, blink::WebInputEvent::LeftButtonDown), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); - EnableIdleTasks(); RunUntilIdle(); // Note compositor tasks are prioritized. EXPECT_THAT(run_order, @@ -900,10 +1027,11 @@ TEST_F(RendererSchedulerImplTest, EventConsumedOnCompositorThread_MouseWheel) { std::vector run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); + ForceMainThreadScrollingUseCase(); + scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks. scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::MouseWheel), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); - EnableIdleTasks(); RunUntilIdle(); // Note compositor tasks are prioritized. EXPECT_THAT(run_order, @@ -916,18 +1044,18 @@ TEST_F(RendererSchedulerImplTest, EventForwardedToMainThread_MouseWheel) { std::vector run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); + ForceMainThreadScrollingUseCase(); + scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks. scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::MouseWheel), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); - EnableIdleTasks(); RunUntilIdle(); // Note compositor tasks are prioritized. EXPECT_THAT(run_order, testing::ElementsAre(std::string("C1"), std::string("C2"), std::string("D1"), std::string("D2"), std::string("I1"))); - scheduler_->DidHandleInputEventOnMainThread( - FakeInputEvent(blink::WebInputEvent::MouseWheel)); + EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase()); } TEST_F(RendererSchedulerImplTest, @@ -935,16 +1063,18 @@ TEST_F(RendererSchedulerImplTest, std::vector run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); + ForceMainThreadScrollingUseCase(); + scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks. scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::KeyDown), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); - EnableIdleTasks(); RunUntilIdle(); // Note compositor tasks are not prioritized. EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"), std::string("C1"), std::string("D2"), std::string("C2"), std::string("I1"))); + EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase()); } TEST_F(RendererSchedulerImplTest, @@ -952,22 +1082,28 @@ TEST_F(RendererSchedulerImplTest, std::vector run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); + ForceMainThreadScrollingUseCase(); + scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks. scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::KeyDown), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); - EnableIdleTasks(); RunUntilIdle(); // Note compositor tasks are not prioritized. EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"), std::string("C1"), std::string("D2"), std::string("C2"), std::string("I1"))); + EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase()); + // Note compositor tasks are not prioritized. scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::KeyDown)); } TEST_F(RendererSchedulerImplTest, - TestCompositorPolicyDoesNotStarveDefaultTasks) { + TestMainthreadScrollingUseCaseDoesNotStarveDefaultTasks) { + ForceMainThreadScrollingUseCase(); + scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks. + std::vector run_order; PostTestTasks(&run_order, "D1 C1"); @@ -989,52 +1125,28 @@ TEST_F(RendererSchedulerImplTest, TEST_F(RendererSchedulerImplTest, TestCompositorPolicyEnds_CompositorHandlesInput) { - std::vector run_order; - PostTestTasks(&run_order, "D1 C1 D2 C2"); - scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::GestureFlingStart), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); - RunUntilIdle(); - EXPECT_THAT(run_order, - testing::ElementsAre(std::string("C1"), std::string("C2"), - std::string("D1"), std::string("D2"))); + EXPECT_EQ(UseCase::COMPOSITOR_GESTURE, + ForceUpdatePolicyAndGetCurrentUseCase()); - run_order.clear(); clock_->Advance(base::TimeDelta::FromMilliseconds(1000)); - PostTestTasks(&run_order, "D1 C1 D2 C2"); - - // Compositor policy mode should have ended now that the clock has advanced. - RunUntilIdle(); - EXPECT_THAT(run_order, - testing::ElementsAre(std::string("D1"), std::string("C1"), - std::string("D2"), std::string("C2"))); + EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase()); } TEST_F(RendererSchedulerImplTest, TestCompositorPolicyEnds_MainThreadHandlesInput) { - std::vector run_order; - PostTestTasks(&run_order, "D1 C1 D2 C2"); - scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::GestureFlingStart), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::GestureFlingStart)); - RunUntilIdle(); - EXPECT_THAT(run_order, - testing::ElementsAre(std::string("C1"), std::string("C2"), - std::string("D1"), std::string("D2"))); + EXPECT_EQ(UseCase::COMPOSITOR_GESTURE, + ForceUpdatePolicyAndGetCurrentUseCase()); - run_order.clear(); clock_->Advance(base::TimeDelta::FromMilliseconds(1000)); - PostTestTasks(&run_order, "D1 C1 D2 C2"); - - // Compositor policy mode should have ended now that the clock has advanced. - RunUntilIdle(); - EXPECT_THAT(run_order, - testing::ElementsAre(std::string("D1"), std::string("C1"), - std::string("D2"), std::string("C2"))); + EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase()); } TEST_F(RendererSchedulerImplTest, TestTouchstartPolicyEndsAfterTimeout) { @@ -1098,22 +1210,33 @@ TEST_F(RendererSchedulerImplTest, TestIsHighPriorityWorkAnticipated) { bool is_anticipated_before = false; bool is_anticipated_after = false; - bool simulate_input = false; default_task_runner_->PostTask( - FROM_HERE, - base::Bind(&AnticipationTestTask, scheduler_.get(), simulate_input, - &is_anticipated_before, &is_anticipated_after)); + FROM_HERE, base::Bind(&AnticipationTestTask, scheduler_.get(), + SimulateInputType::None, &is_anticipated_before, + &is_anticipated_after)); RunUntilIdle(); // In its default state, without input receipt, the scheduler should indicate // that no high-priority is anticipated. EXPECT_FALSE(is_anticipated_before); EXPECT_FALSE(is_anticipated_after); - simulate_input = true; + default_task_runner_->PostTask( + FROM_HERE, base::Bind(&AnticipationTestTask, scheduler_.get(), + SimulateInputType::TouchStart, + &is_anticipated_before, &is_anticipated_after)); + bool dummy; + default_task_runner_->PostTask( + FROM_HERE, base::Bind(&AnticipationTestTask, scheduler_.get(), + SimulateInputType::TouchEnd, &dummy, &dummy)); default_task_runner_->PostTask( FROM_HERE, - base::Bind(&AnticipationTestTask, scheduler_.get(), simulate_input, - &is_anticipated_before, &is_anticipated_after)); + base::Bind(&AnticipationTestTask, scheduler_.get(), + SimulateInputType::GestureScrollBegin, &dummy, &dummy)); + default_task_runner_->PostTask( + FROM_HERE, + base::Bind(&AnticipationTestTask, scheduler_.get(), + SimulateInputType::GestureScrollEnd, &dummy, &dummy)); + RunUntilIdle(); // When input is received, the scheduler should indicate that high-priority // work is anticipated. @@ -1121,14 +1244,29 @@ TEST_F(RendererSchedulerImplTest, TestIsHighPriorityWorkAnticipated) { EXPECT_TRUE(is_anticipated_after); clock_->Advance(priority_escalation_after_input_duration() * 2); - simulate_input = false; default_task_runner_->PostTask( - FROM_HERE, - base::Bind(&AnticipationTestTask, scheduler_.get(), simulate_input, - &is_anticipated_before, &is_anticipated_after)); - RunUntilIdle(); - // Without additional input, the scheduler should indicate that high-priority - // work is no longer anticipated. + FROM_HERE, base::Bind(&AnticipationTestTask, scheduler_.get(), + SimulateInputType::None, &is_anticipated_before, + &is_anticipated_after)); + RunUntilIdle(); + // Without additional input, the scheduler should go into NONE + // use case but with scrolling expected where high-priority work is still + // anticipated. + EXPECT_EQ(UseCase::NONE, CurrentUseCase()); + EXPECT_TRUE(TouchStartExpectedSoon()); + EXPECT_TRUE(is_anticipated_before); + EXPECT_TRUE(is_anticipated_after); + + clock_->Advance(subsequent_input_expected_after_input_duration() * 2); + default_task_runner_->PostTask( + FROM_HERE, base::Bind(&AnticipationTestTask, scheduler_.get(), + SimulateInputType::None, &is_anticipated_before, + &is_anticipated_after)); + RunUntilIdle(); + // Eventually the scheduler should go into the default use case where + // high-priority work is no longer anticipated. + EXPECT_EQ(UseCase::NONE, CurrentUseCase()); + EXPECT_FALSE(TouchStartExpectedSoon()); EXPECT_FALSE(is_anticipated_before); EXPECT_FALSE(is_anticipated_after); } @@ -1137,6 +1275,8 @@ TEST_F(RendererSchedulerImplTest, TestShouldYield) { bool should_yield_before = false; bool should_yield_after = false; + ForceMainThreadScrollingUseCase(); + default_task_runner_->PostTask( FROM_HERE, base::Bind(&PostingYieldingTestTask, scheduler_.get(), default_task_runner_, false, &should_yield_before, @@ -1151,7 +1291,7 @@ TEST_F(RendererSchedulerImplTest, TestShouldYield) { compositor_task_runner_, false, &should_yield_before, &should_yield_after)); RunUntilIdle(); - // Posting while not in compositor priority shouldn't cause yielding. + // Posting while not mainthread scrolling shouldn't cause yielding. EXPECT_FALSE(should_yield_before); EXPECT_FALSE(should_yield_after); @@ -1163,7 +1303,9 @@ TEST_F(RendererSchedulerImplTest, TestShouldYield) { // We should be able to switch to compositor priority mid-task. EXPECT_FALSE(should_yield_before); EXPECT_TRUE(should_yield_after); +} +TEST_F(RendererSchedulerImplTest, TestShouldYield_TouchStart) { // Receiving a touchstart should immediately trigger yielding, even if // there's no immediately pending work in the compositor queue. EXPECT_FALSE(scheduler_->ShouldYieldForHighPriorityWork()); @@ -1175,14 +1317,14 @@ TEST_F(RendererSchedulerImplTest, TestShouldYield) { } TEST_F(RendererSchedulerImplTest, SlowMainThreadInputEvent) { - EXPECT_EQ(Policy::NORMAL, CurrentPolicy()); + EXPECT_EQ(UseCase::NONE, CurrentUseCase()); // An input event should bump us into input priority. scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::GestureFlingStart), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); RunUntilIdle(); - EXPECT_EQ(Policy::COMPOSITOR_PRIORITY, CurrentPolicy()); + EXPECT_EQ(UseCase::COMPOSITOR_GESTURE, CurrentUseCase()); // Simulate the input event being queued for a very long time. The compositor // task we post here represents the enqueued input task. @@ -1193,12 +1335,12 @@ TEST_F(RendererSchedulerImplTest, SlowMainThreadInputEvent) { // Even though we exceeded the input priority escalation period, we should // still be in compositor priority since the input remains queued. - EXPECT_EQ(Policy::COMPOSITOR_PRIORITY, CurrentPolicy()); + EXPECT_EQ(UseCase::COMPOSITOR_GESTURE, CurrentUseCase()); // After the escalation period ends we should go back into normal mode. clock_->Advance(priority_escalation_after_input_duration() * 2); RunUntilIdle(); - EXPECT_EQ(Policy::NORMAL, CurrentPolicy()); + EXPECT_EQ(UseCase::NONE, CurrentUseCase()); } class RendererSchedulerImplWithMockSchedulerTest @@ -1376,7 +1518,7 @@ TEST_F(RendererSchedulerImplWithMockSchedulerTest, FakeInputEvent(blink::WebInputEvent::TouchStart), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); scheduler_->DidHandleInputEventOnCompositorThread( - FakeInputEvent(blink::WebInputEvent::TouchMove), + FakeInputEvent(blink::WebInputEvent::GestureScrollBegin), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); // We expect the first call to IsHighPriorityWorkAnticipated to be called @@ -1397,14 +1539,19 @@ TEST_F(RendererSchedulerImplWithMockSchedulerTest, scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::TouchStart)); scheduler_->DidHandleInputEventOnMainThread( - FakeInputEvent(blink::WebInputEvent::TouchMove)); + FakeInputEvent(blink::WebInputEvent::GestureScrollBegin)); EXPECT_EQ(1, mock_scheduler_->update_policy_count_); - RunUntilIdle(); // We expect both the urgent and the delayed updates to run in addition to the - // earlier updated cause by IsHighPriorityWorkAnticipated. - EXPECT_EQ(3, mock_scheduler_->update_policy_count_); + // earlier updated cause by IsHighPriorityWorkAnticipated, a final update + // transitions from 'not_scrolling scroll expected' to 'not_scrolling'. + RunUntilIdle(); + EXPECT_THAT(mock_scheduler_->use_cases_, + testing::ElementsAre(std::string("compositor_gesture"), + std::string("compositor_gesture"), + std::string("none scroll expected"), + std::string("none"))); } class RendererSchedulerImplWithMessageLoopTest @@ -1759,8 +1906,8 @@ TEST_F(RendererSchedulerImplTest, MultipleSuspendsNeedMultipleResumes) { testing::ElementsAre(std::string("T1"), std::string("T2"))); } -TEST_F(RendererSchedulerImplTest, PolicyToString) { - CheckAllPolicyToString(); +TEST_F(RendererSchedulerImplTest, UseCaseToString) { + CheckAllUseCaseToString(); } TEST_F(RendererSchedulerImplTest, MismatchedDidHandleInputEventOnMainThread) { @@ -1834,4 +1981,48 @@ TEST_F(RendererSchedulerImplTest, TestRendererBackgroundedTimerSuspension) { RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("T6"))); } + +TEST_F(RendererSchedulerImplTest, + ExpensiveLoadingTasksNotBlockedTillFirstBeginMainFrame) { + std::vector run_order; + + // RunUntilIdle won't actually run all of the SimpleTestTickClock::Advance + // tasks unless we set AutoAdvanceNow to true :/ + mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true); + + // Simulate a bunch of expensive loading tasks + for (int i = 0; i < 10; i++) { + loading_task_runner_->PostTask( + FROM_HERE, base::Bind(&base::SimpleTestTickClock::Advance, + base::Unretained(clock_.get()), + base::TimeDelta::FromMilliseconds(500))); + } + + RunUntilIdle(); + + PostTestTasks(&run_order, "L1 D1"); + RunUntilIdle(); + + EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase()); + EXPECT_FALSE(HaveSeenABeginMainframe()); + EXPECT_TRUE(LoadingTasksSeemExpensive()); + EXPECT_FALSE(TimerTasksSeemExpensive()); + EXPECT_THAT(run_order, + testing::ElementsAre(std::string("L1"), std::string("D1"))); + + // Emit a BeginMainFrame, and the loading task should get blocked. + DoMainFrame(); + run_order.clear(); + + PostTestTasks(&run_order, "L1 D1"); + RunUntilIdle(); + + EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase()); + EXPECT_TRUE(HaveSeenABeginMainframe()); + EXPECT_TRUE(LoadingTasksSeemExpensive()); + EXPECT_FALSE(TimerTasksSeemExpensive()); + EXPECT_THAT(run_order, + testing::ElementsAre(std::string("L1"), std::string("D1"))); +} + } // namespace scheduler diff --git a/components/scheduler/renderer/task_cost_estimator.cc b/components/scheduler/renderer/task_cost_estimator.cc index cafbdd1b89fe..736c5878e5ec 100644 --- a/components/scheduler/renderer/task_cost_estimator.cc +++ b/components/scheduler/renderer/task_cost_estimator.cc @@ -4,11 +4,14 @@ #include "components/scheduler/renderer/task_cost_estimator.h" +#include "base/time/default_tick_clock.h" + namespace scheduler { TaskCostEstimator::TaskCostEstimator(int sample_count, double estimation_percentile) : rolling_time_delta_history_(sample_count), + time_source_(new base::DefaultTickClock), outstanding_task_count_(0), estimation_percentile_(estimation_percentile) {} @@ -17,12 +20,12 @@ TaskCostEstimator::~TaskCostEstimator() {} void TaskCostEstimator::WillProcessTask(const base::PendingTask& pending_task) { // Avoid measuring the duration in nested run loops. if (++outstanding_task_count_ == 1) - task_start_time_ = Now(); + task_start_time_ = time_source_->NowTicks(); } void TaskCostEstimator::DidProcessTask(const base::PendingTask& pending_task) { if (--outstanding_task_count_ == 0) { - base::TimeDelta duration = Now() - task_start_time_; + base::TimeDelta duration = time_source_->NowTicks() - task_start_time_; rolling_time_delta_history_.InsertSample(duration); // TODO(skyostil): Should we do this less often? @@ -31,8 +34,14 @@ void TaskCostEstimator::DidProcessTask(const base::PendingTask& pending_task) { } } -base::TimeTicks TaskCostEstimator::Now() { - return base::TimeTicks::Now(); +void TaskCostEstimator::Clear() { + rolling_time_delta_history_.Clear(); + expected_task_duration_ = base::TimeDelta(); +} + +void TaskCostEstimator::SetTimeSourceForTesting( + scoped_ptr time_source) { + time_source_ = time_source.Pass(); } } // namespace scheduler diff --git a/components/scheduler/renderer/task_cost_estimator.h b/components/scheduler/renderer/task_cost_estimator.h index 0346ece0f1c4..ac46b03b882b 100644 --- a/components/scheduler/renderer/task_cost_estimator.h +++ b/components/scheduler/renderer/task_cost_estimator.h @@ -10,6 +10,10 @@ #include "cc/base/rolling_time_delta_history.h" #include "components/scheduler/scheduler_export.h" +namespace base { +class TickClock; +} + namespace scheduler { // Estimates the cost of running tasks based on historical timing data. @@ -27,12 +31,13 @@ class SCHEDULER_EXPORT TaskCostEstimator void WillProcessTask(const base::PendingTask& pending_task) override; void DidProcessTask(const base::PendingTask& pending_task) override; - protected: - // Virtual for testing. - virtual base::TimeTicks Now(); + void Clear(); + + void SetTimeSourceForTesting(scoped_ptr time_source); private: cc::RollingTimeDeltaHistory rolling_time_delta_history_; + scoped_ptr time_source_; int outstanding_task_count_; double estimation_percentile_; base::TimeTicks task_start_time_; diff --git a/components/scheduler/renderer/task_cost_estimator_unittest.cc b/components/scheduler/renderer/task_cost_estimator_unittest.cc index 7e104edac5cc..b4bb48611ff0 100644 --- a/components/scheduler/renderer/task_cost_estimator_unittest.cc +++ b/components/scheduler/renderer/task_cost_estimator_unittest.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "base/test/simple_test_tick_clock.h" +#include "components/scheduler/child/test_time_source.h" #include "components/scheduler/renderer/task_cost_estimator.h" #include "testing/gmock/include/gmock/gmock.h" @@ -25,13 +26,9 @@ class TaskCostEstimatorForTest : public TaskCostEstimator { TaskCostEstimatorForTest(base::SimpleTestTickClock* clock, int sample_count, double estimation_percentile) - : TaskCostEstimator(sample_count, estimation_percentile), clock_(clock) {} - - protected: - base::TimeTicks Now() override { return clock_->NowTicks(); } - - private: - base::SimpleTestTickClock* clock_; + : TaskCostEstimator(sample_count, estimation_percentile) { + SetTimeSourceForTesting(make_scoped_ptr(new TestTimeSource(clock))); + } }; TEST_F(TaskCostEstimatorTest, BasicEstimation) { @@ -44,7 +41,20 @@ TEST_F(TaskCostEstimatorTest, BasicEstimation) { EXPECT_EQ(base::TimeDelta::FromMilliseconds(500), estimator.expected_task_duration()); -}; +} + +TEST_F(TaskCostEstimatorTest, Clear) { + TaskCostEstimatorForTest estimator(&clock_, 1, 100); + base::PendingTask task(FROM_HERE, base::Closure()); + + estimator.WillProcessTask(task); + clock_.Advance(base::TimeDelta::FromMilliseconds(500)); + estimator.DidProcessTask(task); + + estimator.Clear(); + + EXPECT_EQ(base::TimeDelta(), estimator.expected_task_duration()); +} TEST_F(TaskCostEstimatorTest, NestedRunLoop) { TaskCostEstimatorForTest estimator(&clock_, 1, 100); @@ -60,6 +70,6 @@ TEST_F(TaskCostEstimatorTest, NestedRunLoop) { EXPECT_EQ(base::TimeDelta::FromMilliseconds(1000), estimator.expected_task_duration()); -}; +} } // namespace scheduler diff --git a/components/scheduler/renderer/user_model.cc b/components/scheduler/renderer/user_model.cc new file mode 100644 index 000000000000..81abada59856 --- /dev/null +++ b/components/scheduler/renderer/user_model.cc @@ -0,0 +1,110 @@ +// Copyright 2015 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 "components/scheduler/renderer/user_model.h" + +namespace scheduler { + +UserModel::UserModel() : pending_input_event_count_(0) {} +UserModel::~UserModel() {} + +void UserModel::DidStartProcessingInputEvent(blink::WebInputEvent::Type type, + const base::TimeTicks now) { + last_input_signal_time_ = now; + if (type == blink::WebInputEvent::TouchStart || + type == blink::WebInputEvent::GestureScrollBegin || + type == blink::WebInputEvent::GesturePinchBegin) { + last_gesture_start_time_ = now; + } + + // We need to track continuous gestures seperatly for scroll detection + // because taps should not be confused with scrolls. + if (type == blink::WebInputEvent::GestureScrollBegin || + type == blink::WebInputEvent::GestureScrollEnd || + type == blink::WebInputEvent::GestureScrollUpdate || + type == blink::WebInputEvent::GestureFlingStart || + type == blink::WebInputEvent::GestureFlingCancel || + type == blink::WebInputEvent::GesturePinchBegin || + type == blink::WebInputEvent::GesturePinchEnd || + type == blink::WebInputEvent::GesturePinchUpdate) { + last_continuous_gesture_time_ = now; + } + + pending_input_event_count_++; +} + +void UserModel::DidFinishProcessingInputEvent(const base::TimeTicks now) { + last_input_signal_time_ = now; + if (pending_input_event_count_ > 0) + pending_input_event_count_--; +} + +base::TimeDelta UserModel::TimeLeftInUserGesture(base::TimeTicks now) const { + base::TimeDelta escalated_priority_duration = + base::TimeDelta::FromMilliseconds(kGestureEstimationLimitMillis); + + // If the input event is still pending, go into input prioritized policy and + // check again later. + if (pending_input_event_count_ > 0) + return escalated_priority_duration; + if (last_input_signal_time_.is_null() || + last_input_signal_time_ + escalated_priority_duration < now) { + return base::TimeDelta(); + } + return last_input_signal_time_ + escalated_priority_duration - now; +} + +bool UserModel::IsGestureExpectedSoon( + RendererScheduler::UseCase use_case, + const base::TimeTicks now, + base::TimeDelta* prediction_valid_duration) const { + if (use_case == RendererScheduler::UseCase::NONE) { + // If we've scrolled recently then future scrolling is likely. + base::TimeDelta expect_subsequent_gesture_for = + base::TimeDelta::FromMilliseconds(kExpectSubsequentGestureMillis); + if (last_continuous_gesture_time_.is_null() || + last_continuous_gesture_time_ + expect_subsequent_gesture_for <= now) { + return false; + } + *prediction_valid_duration = + last_continuous_gesture_time_ + expect_subsequent_gesture_for - now; + return true; + } + + if (use_case == RendererScheduler::UseCase::COMPOSITOR_GESTURE || + use_case == RendererScheduler::UseCase::MAIN_THREAD_GESTURE) { + // If we've only just started scrolling then, then initiating a subsequent + // gesture is unlikely. + base::TimeDelta minimum_typical_scroll_duration = + base::TimeDelta::FromMilliseconds(kMinimumTypicalScrollDurationMillis); + if (last_gesture_start_time_.is_null() || + last_gesture_start_time_ + minimum_typical_scroll_duration <= now) { + return true; + } + *prediction_valid_duration = + last_gesture_start_time_ + minimum_typical_scroll_duration - now; + return false; + } + return false; +} + +void UserModel::Reset() { + last_input_signal_time_ = base::TimeTicks(); + last_gesture_start_time_ = base::TimeTicks(); + last_continuous_gesture_time_ = base::TimeTicks(); +} + +void UserModel::AsValueInto(base::trace_event::TracedValue* state) const { + state->BeginDictionary("user_model"); + state->SetInteger("pending_input_event_count", pending_input_event_count_); + state->SetDouble( + "last_input_signal_time", + (last_input_signal_time_ - base::TimeTicks()).InMillisecondsF()); + state->SetDouble( + "last_touchstart_time", + (last_gesture_start_time_ - base::TimeTicks()).InMillisecondsF()); + state->EndDictionary(); +} + +} // namespace scheduler diff --git a/components/scheduler/renderer/user_model.h b/components/scheduler/renderer/user_model.h new file mode 100644 index 000000000000..4eea8b10f982 --- /dev/null +++ b/components/scheduler/renderer/user_model.h @@ -0,0 +1,69 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_SCHEDULER_RENDERER_USER_MODEL_H_ +#define COMPONENTS_SCHEDULER_RENDERER_USER_MODEL_H_ + +#include "base/macros.h" +#include "base/trace_event/trace_event.h" +#include "base/trace_event/trace_event_argument.h" +#include "components/scheduler/renderer/renderer_scheduler.h" +#include "components/scheduler/scheduler_export.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" + +namespace scheduler { + +class SCHEDULER_EXPORT UserModel { + public: + UserModel(); + ~UserModel(); + + // Tells us that the system started processing an input event. Must be paired + // with a call to DidFinishProcessingInputEvent. + void DidStartProcessingInputEvent(blink::WebInputEvent::Type type, + const base::TimeTicks now); + + // Tells us that the system finished processing an input event. + void DidFinishProcessingInputEvent(const base::TimeTicks now); + + // Returns the estimated amount of time left in the current user gesture, to a + // maximum of |kGestureEstimationLimitMillis|. After that time has elapased + // this function should be called again. + base::TimeDelta TimeLeftInUserGesture(base::TimeTicks now) const; + + // Tries to guess if a user gesture is expected soon. Currently this is + // very simple, but one day I hope to do something more sophisticated here. + // The prediction may change after |prediction_valid_duration| has elapsed. + bool IsGestureExpectedSoon(RendererScheduler::UseCase use_case, + const base::TimeTicks now, + base::TimeDelta* prediction_valid_duration) const; + + void AsValueInto(base::trace_event::TracedValue* state) const; + + // The time we should stay in a priority-escalated mode after an input event. + static const int kGestureEstimationLimitMillis = 100; + + // TODO(alexclarke): Get a real number on actual data. + static const int kMinimumTypicalScrollDurationMillis = 500; + + // We consider further gesture start events to be likely if the user has + // interacted with the device in the past two seconds. + // TODO(alexclarke): Get a real number based on actual data. + static const int kExpectSubsequentGestureMillis = 2000; + + // Clears input signals. + void Reset(); + + private: + int pending_input_event_count_; + base::TimeTicks last_input_signal_time_; + base::TimeTicks last_gesture_start_time_; + base::TimeTicks last_continuous_gesture_time_; // Doesn't include Taps. + + DISALLOW_COPY_AND_ASSIGN(UserModel); +}; + +} // namespace scheduler + +#endif // COMPONENTS_SCHEDULER_RENDERER_USER_MODEL_H_ diff --git a/components/scheduler/renderer/user_model_unittest.cc b/components/scheduler/renderer/user_model_unittest.cc new file mode 100644 index 000000000000..18d2a46455ff --- /dev/null +++ b/components/scheduler/renderer/user_model_unittest.cc @@ -0,0 +1,298 @@ +// Copyright 2015 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 "components/scheduler/renderer/user_model.h" + +#include "base/test/simple_test_tick_clock.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace scheduler { + +class UserModelTest : public testing::Test { + public: + UserModelTest() {} + ~UserModelTest() override {} + + void SetUp() override { + clock_.reset(new base::SimpleTestTickClock()); + clock_->Advance(base::TimeDelta::FromMicroseconds(5000)); + + user_model_.reset(new UserModel()); + } + + protected: + static base::TimeDelta priority_escalation_after_input_duration() { + return base::TimeDelta::FromMilliseconds( + UserModel::kGestureEstimationLimitMillis); + } + + static base::TimeDelta subsequent_input_expected_after_input_duration() { + return base::TimeDelta::FromMilliseconds( + UserModel::kExpectSubsequentGestureMillis); + } + + static base::TimeDelta minimum_typical_scroll_duration_millis() { + return base::TimeDelta::FromMilliseconds( + UserModel::kMinimumTypicalScrollDurationMillis); + } + + scoped_ptr clock_; + scoped_ptr user_model_; +}; + +TEST_F(UserModelTest, TimeLeftInUserGesture_NoInput) { + EXPECT_EQ(base::TimeDelta(), + user_model_->TimeLeftInUserGesture(clock_->NowTicks())); +} + +TEST_F(UserModelTest, TimeLeftInUserGesture_ImmediatelyAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::TouchStart, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + EXPECT_EQ(priority_escalation_after_input_duration(), + user_model_->TimeLeftInUserGesture(clock_->NowTicks())); +} + +TEST_F(UserModelTest, TimeLeftInUserGesture_ShortlyAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::TouchStart, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + base::TimeDelta delta(base::TimeDelta::FromMilliseconds(10)); + clock_->Advance(delta); + EXPECT_EQ(priority_escalation_after_input_duration() - delta, + user_model_->TimeLeftInUserGesture(clock_->NowTicks())); +} + +TEST_F(UserModelTest, TimeLeftInUserGesture_LongAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::TouchStart, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + clock_->Advance(priority_escalation_after_input_duration() * 2); + EXPECT_EQ(base::TimeDelta(), + user_model_->TimeLeftInUserGesture(clock_->NowTicks())); +} + +TEST_F(UserModelTest, DidFinishProcessingInputEvent_Delayed) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::TouchStart, clock_->NowTicks()); + clock_->Advance(priority_escalation_after_input_duration() * 10); + + EXPECT_EQ(priority_escalation_after_input_duration(), + user_model_->TimeLeftInUserGesture(clock_->NowTicks())); + + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + base::TimeDelta delta(base::TimeDelta::FromMilliseconds(10)); + clock_->Advance(delta); + + EXPECT_EQ(priority_escalation_after_input_duration() - delta, + user_model_->TimeLeftInUserGesture(clock_->NowTicks())); +} + +TEST_F(UserModelTest, GestureExpectedSoon_UseCase_NONE_NoRecentInput) { + base::TimeDelta prediction_valid_duration; + EXPECT_FALSE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::NONE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(base::TimeDelta(), prediction_valid_duration); +} + +TEST_F(UserModelTest, GestureExpectedSoon_UseCase_NONE_ImmediatelyAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::GestureScrollEnd, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + base::TimeDelta prediction_valid_duration; + EXPECT_TRUE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::NONE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(subsequent_input_expected_after_input_duration(), + prediction_valid_duration); +} + +TEST_F(UserModelTest, GestureExpectedSoon_UseCase_NONE_ShortlyAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::GestureScrollEnd, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + base::TimeDelta delta(base::TimeDelta::FromMilliseconds(10)); + clock_->Advance(delta); + + base::TimeDelta prediction_valid_duration; + EXPECT_TRUE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::NONE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(subsequent_input_expected_after_input_duration() - delta, + prediction_valid_duration); +} + +TEST_F(UserModelTest, GestureExpectedSoon_UseCase_NONE_LongAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::GestureScrollEnd, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + clock_->Advance(subsequent_input_expected_after_input_duration() * 2); + + base::TimeDelta prediction_valid_duration; + EXPECT_FALSE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::NONE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(base::TimeDelta(), prediction_valid_duration); +} + +TEST_F(UserModelTest, + GestureExpectedSoon_COMPOSITOR_GESTURE_ImmediatelyAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::TouchStart, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + base::TimeDelta prediction_valid_duration; + EXPECT_FALSE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::COMPOSITOR_GESTURE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(minimum_typical_scroll_duration_millis(), + prediction_valid_duration); +} + +TEST_F(UserModelTest, + GestureExpectedSoon_COMPOSITOR_GESTURE_ShortlyAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::TouchStart, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + base::TimeDelta delta(base::TimeDelta::FromMilliseconds(10)); + clock_->Advance(delta); + + base::TimeDelta prediction_valid_duration; + EXPECT_FALSE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::COMPOSITOR_GESTURE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(minimum_typical_scroll_duration_millis() - delta, + prediction_valid_duration); +} + +TEST_F(UserModelTest, GestureExpectedSoon_COMPOSITOR_GESTURE_LongAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::TouchStart, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + clock_->Advance(minimum_typical_scroll_duration_millis() * 2); + + base::TimeDelta prediction_valid_duration; + // Note this isn't a bug, the UseCase will change to NONE eventually so it's + // OK for this to always be true after minimum_typical_scroll_duration_millis + EXPECT_TRUE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::COMPOSITOR_GESTURE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(base::TimeDelta(), prediction_valid_duration); +} + +TEST_F(UserModelTest, + GestureExpectedSoon_MAIN_THREAD_GESTURE_ImmediatelyAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::TouchStart, clock_->NowTicks()); + + // DidFinishProcessingInputEvent is always a little bit delayed. + base::TimeDelta delay(base::TimeDelta::FromMilliseconds(5)); + clock_->Advance(delay); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + base::TimeDelta prediction_valid_duration; + EXPECT_FALSE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::MAIN_THREAD_GESTURE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(minimum_typical_scroll_duration_millis() - delay, + prediction_valid_duration); +} + +TEST_F(UserModelTest, + GestureExpectedSoon_MAIN_THREAD_GESTURE_ShortlyAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::TouchStart, clock_->NowTicks()); + // DidFinishProcessingInputEvent is always a little bit delayed. + base::TimeDelta delay(base::TimeDelta::FromMilliseconds(5)); + clock_->Advance(delay); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + base::TimeDelta delta(base::TimeDelta::FromMilliseconds(10)); + clock_->Advance(delta); + + base::TimeDelta prediction_valid_duration; + EXPECT_FALSE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::MAIN_THREAD_GESTURE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(minimum_typical_scroll_duration_millis() - delta - delay, + prediction_valid_duration); +} + +TEST_F(UserModelTest, GestureExpectedSoon_MAIN_THREAD_GESTURE_LongAfterInput) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::TouchStart, clock_->NowTicks()); + + // DidFinishProcessingInputEvent is always a little bit delayed. + base::TimeDelta delay(base::TimeDelta::FromMilliseconds(5)); + clock_->Advance(delay); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + clock_->Advance(minimum_typical_scroll_duration_millis() * 2); + + base::TimeDelta prediction_valid_duration; + // Note this isn't a bug, the UseCase will change to NONE eventually so it's + // OK for this to always be true after minimum_typical_scroll_duration_millis + EXPECT_TRUE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::MAIN_THREAD_GESTURE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(base::TimeDelta(), prediction_valid_duration); +} + +TEST_F(UserModelTest, + GestureExpectedSoon_UseCase_NONE_ShortlyAfterInput_GestureScrollBegin) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::GestureScrollBegin, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + base::TimeDelta delta(base::TimeDelta::FromMilliseconds(10)); + clock_->Advance(delta); + + base::TimeDelta prediction_valid_duration; + EXPECT_TRUE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::NONE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(subsequent_input_expected_after_input_duration() - delta, + prediction_valid_duration); +} + +TEST_F(UserModelTest, + GestureExpectedSoon_UseCase_NONE_ShortlyAfterInput_GesturePinchBegin) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::GestureScrollBegin, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + base::TimeDelta delta(base::TimeDelta::FromMilliseconds(10)); + clock_->Advance(delta); + + base::TimeDelta prediction_valid_duration; + EXPECT_TRUE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::NONE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(subsequent_input_expected_after_input_duration() - delta, + prediction_valid_duration); +} + +TEST_F(UserModelTest, + GestureExpectedSoon_UseCase_NONE_ShortlyAfterInput_GestureTap) { + user_model_->DidStartProcessingInputEvent( + blink::WebInputEvent::Type::GestureTap, clock_->NowTicks()); + user_model_->DidFinishProcessingInputEvent(clock_->NowTicks()); + + base::TimeDelta delta(base::TimeDelta::FromMilliseconds(10)); + clock_->Advance(delta); + + base::TimeDelta prediction_valid_duration; + EXPECT_FALSE(user_model_->IsGestureExpectedSoon( + RendererScheduler::UseCase::NONE, clock_->NowTicks(), + &prediction_valid_duration)); + EXPECT_EQ(base::TimeDelta(), prediction_valid_duration); +} + +} // namespace scheduler diff --git a/components/scheduler/scheduler.gypi b/components/scheduler/scheduler.gypi index ee51da53b09f..0e65eeabb790 100644 --- a/components/scheduler/scheduler.gypi +++ b/components/scheduler/scheduler.gypi @@ -54,6 +54,8 @@ 'renderer/renderer_web_scheduler_impl.h', 'renderer/task_cost_estimator.cc', 'renderer/task_cost_estimator.h', + 'renderer/user_model.cc', + 'renderer/user_model.h', 'renderer/webthread_impl_for_renderer_scheduler.cc', 'renderer/webthread_impl_for_renderer_scheduler.h', 'scheduler_export.h', diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index b42860a34732..d759e9704203 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -20,6 +20,7 @@ #include "base/thread_task_runner_handle.h" #include "base/time/time.h" #include "cc/base/switches.h" +#include "components/scheduler/renderer/renderer_scheduler.h" #include "content/child/appcache/appcache_dispatcher.h" #include "content/child/permissions/permission_dispatcher.h" #include "content/child/plugin_messages.h" @@ -1144,6 +1145,10 @@ void RenderFrameImpl::OnNavigate( const CommonNavigationParams& common_params, const StartNavigationParams& start_params, const RequestNavigationParams& request_params) { + RenderThreadImpl* render_thread_impl = RenderThreadImpl::current(); + // Can be NULL in tests. + if (render_thread_impl) + render_thread_impl->GetRendererScheduler()->OnPageLoadStarted(); DCHECK(!base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableBrowserSideNavigation)); TRACE_EVENT2("navigation", "RenderFrameImpl::OnNavigate", "id", routing_id_, @@ -2795,6 +2800,7 @@ void RenderFrameImpl::didCommitProvisionalLoad( render_thread_impl->histogram_customizer()-> RenderViewNavigatedToHost(GURL(GetLoadingUrl()).host(), RenderView::GetRenderViewCount()); + render_thread_impl->GetRendererScheduler()->OnPageLoadStarted(); } } -- 2.11.4.GIT