blob: e58e62cbe7cc5d9b598cfa4ae59b03c91b51df50 [file] [log] [blame]
// Copyright 2014 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/renderer_scheduler_impl.h"
#include "base/bind.h"
#include "base/debug/stack_trace.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "cc/output/begin_frame_args.h"
#include "components/scheduler/child/scheduler_task_runner_delegate.h"
#include "components/scheduler/child/task_queue_impl.h"
#include "components/scheduler/child/task_queue_selector.h"
namespace scheduler {
namespace {
const int kLoadingTaskEstimationSampleCount = 200;
const double kLoadingTaskEstimationPercentile = 90;
const int kTimerTaskEstimationSampleCount = 200;
const double kTimerTaskEstimationPercentile = 90;
const int kShortIdlePeriodDurationSampleCount = 10;
const double kShortIdlePeriodDurationPercentile = 20;
}
RendererSchedulerImpl::RendererSchedulerImpl(
scoped_refptr<SchedulerTaskRunnerDelegate> main_task_runner)
: helper_(main_task_runner,
"renderer.scheduler",
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler.debug")),
idle_helper_(&helper_,
this,
"renderer.scheduler",
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerIdlePeriod",
base::TimeDelta()),
control_task_runner_(helper_.ControlTaskRunner()),
compositor_task_runner_(
helper_.NewTaskQueue(TaskQueue::Spec("compositor_tq")
.SetShouldMonitorQuiescence(true))),
loading_task_runner_(
helper_.NewTaskQueue(TaskQueue::Spec("loading_tq")
.SetShouldMonitorQuiescence(true))),
timer_task_runner_(
helper_.NewTaskQueue(TaskQueue::Spec("timer_tq")
.SetShouldMonitorQuiescence(true))),
delayed_update_policy_runner_(
base::Bind(&RendererSchedulerImpl::UpdatePolicy,
base::Unretained(this)),
helper_.ControlTaskRunner()),
policy_may_need_update_(&any_thread_lock_),
weak_factory_(this) {
update_policy_closure_ = base::Bind(&RendererSchedulerImpl::UpdatePolicy,
weak_factory_.GetWeakPtr());
end_renderer_hidden_idle_period_closure_.Reset(base::Bind(
&RendererSchedulerImpl::EndIdlePeriod, weak_factory_.GetWeakPtr()));
suspend_timers_when_backgrounded_closure_.Reset(
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);
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() {
TRACE_EVENT_OBJECT_DELETED_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler",
this);
timer_task_runner_->RemoveTaskObserver(
&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);
}
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()
: 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()
: 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) {}
RendererSchedulerImpl::CompositorThreadOnly::~CompositorThreadOnly() {
}
void RendererSchedulerImpl::Shutdown() {
helper_.Shutdown();
MainThreadOnly().was_shutdown = true;
}
scoped_refptr<TaskQueue> RendererSchedulerImpl::DefaultTaskRunner() {
return helper_.DefaultTaskRunner();
}
scoped_refptr<base::SingleThreadTaskRunner>
RendererSchedulerImpl::CompositorTaskRunner() {
helper_.CheckOnValidThread();
return compositor_task_runner_;
}
scoped_refptr<SingleThreadIdleTaskRunner>
RendererSchedulerImpl::IdleTaskRunner() {
return idle_helper_.IdleTaskRunner();
}
scoped_refptr<base::SingleThreadTaskRunner>
RendererSchedulerImpl::LoadingTaskRunner() {
helper_.CheckOnValidThread();
return loading_task_runner_;
}
scoped_refptr<TaskQueue> RendererSchedulerImpl::TimerTaskRunner() {
helper_.CheckOnValidThread();
return timer_task_runner_;
}
bool RendererSchedulerImpl::CanExceedIdleDeadlineIfRequired() const {
return idle_helper_.CanExceedIdleDeadlineIfRequired();
}
void RendererSchedulerImpl::AddTaskObserver(
base::MessageLoop::TaskObserver* task_observer) {
helper_.AddTaskObserver(task_observer);
}
void RendererSchedulerImpl::RemoveTaskObserver(
base::MessageLoop::TaskObserver* task_observer) {
helper_.RemoveTaskObserver(task_observer);
}
void RendererSchedulerImpl::WillBeginFrame(const cc::BeginFrameArgs& args) {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::WillBeginFrame", "args", args.AsValue());
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return;
EndIdlePeriod();
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;
}
}
void RendererSchedulerImpl::DidCommitFrameToCompositor() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::DidCommitFrameToCompositor");
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return;
base::TimeTicks now(helper_.Now());
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);
} 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() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::BeginFrameNotExpectedSoon");
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return;
idle_helper_.EnableLongIdlePeriod();
}
void RendererSchedulerImpl::OnRendererHidden() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::OnRendererHidden");
helper_.CheckOnValidThread();
if (helper_.IsShutdown() || MainThreadOnly().renderer_hidden)
return;
idle_helper_.EnableLongIdlePeriod();
// Ensure that we stop running idle tasks after a few seconds of being hidden.
end_renderer_hidden_idle_period_closure_.Cancel();
base::TimeDelta end_idle_when_hidden_delay =
base::TimeDelta::FromMilliseconds(kEndIdleWhenHiddenDelayMillis);
control_task_runner_->PostDelayedTask(
FROM_HERE, end_renderer_hidden_idle_period_closure_.callback(),
end_idle_when_hidden_delay);
MainThreadOnly().renderer_hidden = true;
TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler",
this, AsValue(helper_.Now()));
}
void RendererSchedulerImpl::OnRendererVisible() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::OnRendererVisible");
helper_.CheckOnValidThread();
if (helper_.IsShutdown() || !MainThreadOnly().renderer_hidden)
return;
end_renderer_hidden_idle_period_closure_.Cancel();
MainThreadOnly().renderer_hidden = false;
EndIdlePeriod();
TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler",
this, AsValue(helper_.Now()));
}
void RendererSchedulerImpl::OnRendererBackgrounded() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::OnRendererBackgrounded");
helper_.CheckOnValidThread();
if (helper_.IsShutdown() || MainThreadOnly().renderer_backgrounded)
return;
MainThreadOnly().renderer_backgrounded = true;
if (!MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled)
return;
suspend_timers_when_backgrounded_closure_.Cancel();
base::TimeDelta suspend_timers_when_backgrounded_delay =
base::TimeDelta::FromMilliseconds(
kSuspendTimersWhenBackgroundedDelayMillis);
control_task_runner_->PostDelayedTask(
FROM_HERE, suspend_timers_when_backgrounded_closure_.callback(),
suspend_timers_when_backgrounded_delay);
}
void RendererSchedulerImpl::OnRendererForegrounded() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::OnRendererForegrounded");
helper_.CheckOnValidThread();
if (helper_.IsShutdown() || !MainThreadOnly().renderer_backgrounded)
return;
MainThreadOnly().renderer_backgrounded = false;
suspend_timers_when_backgrounded_closure_.Cancel();
ResumeTimerQueueWhenForegrounded();
}
void RendererSchedulerImpl::EndIdlePeriod() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::EndIdlePeriod");
helper_.CheckOnValidThread();
idle_helper_.EndIdlePeriod();
}
// static
bool RendererSchedulerImpl::ShouldPrioritizeInputEvent(
const blink::WebInputEvent& web_input_event) {
// We regard MouseMove events with the left mouse button down as a signal
// that the user is doing something requiring a smooth frame rate.
if (web_input_event.type == blink::WebInputEvent::MouseMove &&
(web_input_event.modifiers & blink::WebInputEvent::LeftButtonDown)) {
return true;
}
// Ignore all other mouse events because they probably don't signal user
// interaction needing a smooth framerate. NOTE isMouseEventType returns false
// for mouse wheel events, hence we regard them as user input.
// Ignore keyboard events because it doesn't really make sense to enter
// compositor priority for them.
if (blink::WebInputEvent::isMouseEventType(web_input_event.type) ||
blink::WebInputEvent::isKeyboardEventType(web_input_event.type)) {
return false;
}
return true;
}
void RendererSchedulerImpl::DidHandleInputEventOnCompositorThread(
const blink::WebInputEvent& web_input_event,
InputEventState event_state) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::DidHandleInputEventOnCompositorThread");
if (!ShouldPrioritizeInputEvent(web_input_event))
return;
UpdateForInputEventOnCompositorThread(web_input_event.type, event_state);
}
void RendererSchedulerImpl::DidAnimateForInputOnCompositorThread() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::DidAnimateForInputOnCompositorThread");
UpdateForInputEventOnCompositorThread(
blink::WebInputEvent::Undefined,
InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
}
void RendererSchedulerImpl::UpdateForInputEventOnCompositorThread(
blink::WebInputEvent::Type type,
InputEventState input_event_state) {
base::AutoLock lock(any_thread_lock_);
base::TimeTicks now = helper_.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<int>(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().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;
break;
case blink::WebInputEvent::TouchMove:
// Observation of consecutive touchmoves is a strong signal that the
// page is consuming the touch sequence, in which case touchstart
// 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 ==
blink::WebInputEvent::TouchMove) {
AnyThread().awaiting_touch_start_response = false;
}
break;
case blink::WebInputEvent::Undefined:
case blink::WebInputEvent::GestureTapDown:
case blink::WebInputEvent::GestureShowPress:
case blink::WebInputEvent::GestureFlingCancel:
case blink::WebInputEvent::GestureScrollEnd:
// With no observable effect, these meta events do not indicate a
// meaningful touchstart response and should not impact task priority.
break;
default:
AnyThread().awaiting_touch_start_response = false;
break;
}
}
// 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) {
EnsureUrgentPolicyUpdatePostedOnMainThread(FROM_HERE);
}
CompositorThreadOnly().last_input_type = type;
}
void RendererSchedulerImpl::DidHandleInputEventOnMainThread(
const blink::WebInputEvent& web_input_event) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererSchedulerImpl::DidHandleInputEventOnMainThread");
helper_.CheckOnValidThread();
if (ShouldPrioritizeInputEvent(web_input_event)) {
base::AutoLock lock(any_thread_lock_);
AnyThread().user_model.DidFinishProcessingInputEvent(helper_.Now());
}
}
bool RendererSchedulerImpl::IsHighPriorityWorkAnticipated() {
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return false;
MaybeUpdatePolicy();
// 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() {
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return false;
MaybeUpdatePolicy();
// 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 UseCase::LOADING:
return false;
default:
NOTREACHED();
return false;
}
}
base::TimeTicks RendererSchedulerImpl::CurrentIdleTaskDeadlineForTesting()
const {
return idle_helper_.CurrentIdleTaskDeadline();
}
void RendererSchedulerImpl::MaybeUpdatePolicy() {
helper_.CheckOnValidThread();
if (policy_may_need_update_.IsSet()) {
UpdatePolicy();
}
}
void RendererSchedulerImpl::EnsureUrgentPolicyUpdatePostedOnMainThread(
const tracked_objects::Location& from_here) {
// TODO(scheduler-dev): Check that this method isn't called from the main
// thread.
any_thread_lock_.AssertAcquired();
if (!policy_may_need_update_.IsSet()) {
policy_may_need_update_.SetWhileLocked(true);
control_task_runner_->PostTask(from_here, update_policy_closure_);
}
}
void RendererSchedulerImpl::UpdatePolicy() {
base::AutoLock lock(any_thread_lock_);
UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED);
}
void RendererSchedulerImpl::ForceUpdatePolicy() {
base::AutoLock lock(any_thread_lock_);
UpdatePolicyLocked(UpdateType::FORCE_UPDATE);
}
void RendererSchedulerImpl::UpdatePolicyLocked(UpdateType update_type) {
helper_.CheckOnValidThread();
any_thread_lock_.AssertAcquired();
if (helper_.IsShutdown())
return;
base::TimeTicks now = helper_.Now();
policy_may_need_update_.SetWhileLocked(false);
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;
}
if (new_policy_duration > base::TimeDelta()) {
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();
}
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 UseCase::MAIN_THREAD_GESTURE:
new_policy.compositor_queue_priority = TaskQueue::HIGH_PRIORITY;
block_expensive_tasks = true;
break;
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 UseCase::NONE:
if (touchstart_expected_soon)
block_expensive_tasks = true;
break;
case UseCase::LOADING:
new_policy.loading_queue_priority = TaskQueue::HIGH_PRIORITY;
new_policy.default_queue_priority = TaskQueue::HIGH_PRIORITY;
break;
default:
NOTREACHED();
}
// Don't block expensive tasks unless we have actually seen something.
if (!MainThreadOnly().have_seen_a_begin_main_frame)
block_expensive_tasks = false;
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;
}
// 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.loading_tasks_seem_expensive",
MainThreadOnly().loading_tasks_seem_expensive);
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"RendererScheduler.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::InputSignalsSuggestGestureInProgress(
base::TimeTicks now) const {
base::TimeDelta unused_policy_duration;
switch (ComputeCurrentUseCase(now, &unused_policy_duration)) {
case UseCase::COMPOSITOR_GESTURE:
case UseCase::MAIN_THREAD_GESTURE:
case UseCase::TOUCHSTART:
return true;
default:
break;
}
return false;
}
RendererSchedulerImpl::UseCase RendererSchedulerImpl::ComputeCurrentUseCase(
base::TimeTicks now,
base::TimeDelta* expected_use_case_duration) const {
any_thread_lock_.AssertAcquired();
// Above all else we want to be responsive to user input.
*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().begin_main_frame_on_critical_path) {
return UseCase::MAIN_THREAD_GESTURE;
} else {
return UseCase::COMPOSITOR_GESTURE;
}
}
// TODO(alexclarke): return UseCase::LOADING if signals suggest the system is
// in the initial 1s of RAIL loading.
return UseCase::NONE;
}
bool RendererSchedulerImpl::CanEnterLongIdlePeriod(
base::TimeTicks now,
base::TimeDelta* next_long_idle_period_delay_out) {
helper_.CheckOnValidThread();
MaybeUpdatePolicy();
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;
return false;
}
return true;
}
SchedulerHelper* RendererSchedulerImpl::GetSchedulerHelperForTesting() {
return &helper_;
}
void RendererSchedulerImpl::SuspendTimerQueue() {
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);
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;
}
scoped_refptr<base::trace_event::ConvertableToTraceFormat>
RendererSchedulerImpl::AsValue(base::TimeTicks optional_now) const {
base::AutoLock lock(any_thread_lock_);
return AsValueLocked(optional_now);
}
scoped_refptr<base::trace_event::ConvertableToTraceFormat>
RendererSchedulerImpl::AsValueLocked(base::TimeTicks optional_now) const {
helper_.CheckOnValidThread();
any_thread_lock_.AssertAcquired();
if (optional_now.is_null())
optional_now = helper_.Now();
scoped_refptr<base::trace_event::TracedValue> state =
new base::trace_event::TracedValue();
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_backgrounded",
MainThreadOnly().renderer_backgrounded);
state->SetBoolean("timer_queue_suspended_when_backgrounded",
MainThreadOnly().timer_queue_suspended_when_backgrounded);
state->SetInteger("timer_queue_suspend_count",
MainThreadOnly().timer_queue_suspend_count);
state->SetDouble("now", (optional_now - 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())
.InMillisecondsF());
state->SetBoolean("awaiting_touch_start_response",
AnyThread().awaiting_touch_start_response);
state->SetBoolean("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()
.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->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;
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;
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 =
helper_.Now() + base::TimeDelta::FromMilliseconds(
kRailsInitialLoadingPrioritizationMillis);
ResetForNavigationLocked();
}
bool RendererSchedulerImpl::HadAnIdlePeriodRecently(base::TimeTicks now) const {
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)
return;
MainThreadOnly().timer_queue_suspended_when_backgrounded = true;
ForceUpdatePolicy();
}
void RendererSchedulerImpl::ResumeTimerQueueWhenForegrounded() {
DCHECK(!MainThreadOnly().renderer_backgrounded);
if (!MainThreadOnly().timer_queue_suspended_when_backgrounded)
return;
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