blob: 52c2acfb8f80b7d2a3f0f62446c6e02ce8a26046 [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 "third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h"
#include <memory>
#include "base/bind.h"
#include "base/debug/stack_trace.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "build/build_config.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "third_party/blink/public/common/page/launching_process_state.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/scheduler/renderer_process_type.h"
#include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/blink_resource_coordinator_base.h"
#include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/base/real_time_domain.h"
#include "third_party/blink/renderer/platform/scheduler/base/task_queue_impl.h"
#include "third_party/blink/renderer/platform/scheduler/base/task_queue_selector.h"
#include "third_party/blink/renderer/platform/scheduler/base/virtual_time_domain.h"
#include "third_party/blink/renderer/platform/scheduler/child/features.h"
#include "third_party/blink/renderer/platform/scheduler/child/process_state.h"
#include "third_party/blink/renderer/platform/scheduler/common/throttling/task_queue_throttler.h"
#include "third_party/blink/renderer/platform/scheduler/main_thread/auto_advancing_virtual_time_domain.h"
#include "third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl.h"
#include "third_party/blink/renderer/platform/scheduler/renderer/webthread_impl_for_renderer_scheduler.h"
namespace blink {
namespace scheduler {
namespace {
// The run time of loading tasks is strongly bimodal. The vast majority are
// very cheap, but there are usually a handful of very expensive tasks (e.g ~1
// second on a mobile device) so we take a very pessimistic view when estimating
// the cost of loading tasks.
const int kLoadingTaskEstimationSampleCount = 1000;
const double kLoadingTaskEstimationPercentile = 99;
const int kTimerTaskEstimationSampleCount = 1000;
const double kTimerTaskEstimationPercentile = 99;
const int kShortIdlePeriodDurationSampleCount = 10;
const double kShortIdlePeriodDurationPercentile = 50;
// Amount of idle time left in a frame (as a ratio of the vsync interval) above
// which main thread compositing can be considered fast.
const double kFastCompositingIdleTimeThreshold = .2;
constexpr base::TimeDelta kQueueingTimeWindowDuration =
base::TimeDelta::FromSeconds(1);
const double kSamplingRateForTaskUkm = 0.0001;
// Field trial name.
const char kWakeUpThrottlingTrial[] = "RendererSchedulerWakeUpThrottling";
const char kWakeUpDurationParam[] = "wake_up_duration_ms";
constexpr base::TimeDelta kDefaultWakeUpDuration = base::TimeDelta();
base::TimeDelta GetWakeUpDuration() {
int duration_ms;
if (!base::StringToInt(base::GetFieldTrialParamValue(kWakeUpThrottlingTrial,
kWakeUpDurationParam),
&duration_ms))
return kDefaultWakeUpDuration;
return base::TimeDelta::FromMilliseconds(duration_ms);
}
const char* BackgroundStateToString(bool is_backgrounded) {
if (is_backgrounded) {
return "renderer_backgrounded";
} else {
return "renderer_visible";
}
}
const char* HiddenStateToString(bool is_hidden) {
if (is_hidden) {
return "hidden";
} else {
return "visible";
}
}
const char* AudioPlayingStateToString(bool is_audio_playing) {
if (is_audio_playing) {
return "playing";
} else {
return "silent";
}
}
const char* RendererProcessTypeToString(RendererProcessType process_type) {
switch (process_type) {
case RendererProcessType::kRenderer:
return "normal";
case RendererProcessType::kExtensionRenderer:
return "extension";
}
NOTREACHED();
return ""; // MSVC needs that.
}
const char* TaskTypeToString(TaskType task_type) {
switch (task_type) {
case TaskType::kDeprecatedNone:
return "None";
case TaskType::kDOMManipulation:
return "DOMManipultion";
case TaskType::kUserInteraction:
return "UserInteraction";
case TaskType::kNetworking:
return "Networking";
case TaskType::kNetworkingControl:
return "NetworkingControl";
case TaskType::kHistoryTraversal:
return "HistoryTraversal";
case TaskType::kEmbed:
return "Embed";
case TaskType::kMediaElementEvent:
return "MediaElementEvent";
case TaskType::kCanvasBlobSerialization:
return "CanvasBlobSerialization";
case TaskType::kMicrotask:
return "Microtask";
case TaskType::kJavascriptTimer:
return "JavascriptTimer";
case TaskType::kRemoteEvent:
return "RemoteEvent";
case TaskType::kWebSocket:
return "WebSocket";
case TaskType::kPostedMessage:
return "PostedMessage";
case TaskType::kUnshippedPortMessage:
return "UnshipedPortMessage";
case TaskType::kFileReading:
return "FileReading";
case TaskType::kDatabaseAccess:
return "DatabaseAccess";
case TaskType::kPresentation:
return "Presentation";
case TaskType::kSensor:
return "Sensor";
case TaskType::kPerformanceTimeline:
return "PerformanceTimeline";
case TaskType::kWebGL:
return "WebGL";
case TaskType::kIdleTask:
return "IdleTask";
case TaskType::kMiscPlatformAPI:
return "MiscPlatformAPI";
case TaskType::kInternalDefault:
return "InternalDefault";
case TaskType::kInternalLoading:
return "InternalLoading";
case TaskType::kUnthrottled:
return "Unthrottled";
case TaskType::kInternalTest:
return "InternalTest";
case TaskType::kInternalWebCrypto:
return "InternalWebCrypto";
case TaskType::kInternalIndexedDB:
return "InternalIndexedDB";
case TaskType::kInternalMedia:
return "InternalMedia";
case TaskType::kInternalMediaRealTime:
return "InternalMediaRealTime";
case TaskType::kInternalIPC:
return "InternalIPC";
case TaskType::kInternalUserInteraction:
return "InternalUserInteraction";
case TaskType::kInternalInspector:
return "InternalInspector";
case TaskType::kInternalAnimation:
return "InternalAnimation";
case TaskType::kInternalAccessibility:
return "InternalAccessibility";
case TaskType::kCount:
return "Count";
}
NOTREACHED();
return "";
}
const char* OptionalTaskDescriptionToString(
base::Optional<MainThreadSchedulerImpl::TaskDescriptionForTracing> desc) {
if (!desc)
return nullptr;
if (desc->task_type != TaskType::kDeprecatedNone)
return TaskTypeToString(desc->task_type);
if (!desc->queue_type)
return "detached_tq";
return MainThreadTaskQueue::NameForQueueType(desc->queue_type.value());
}
bool IsUnconditionalHighPriorityInputEnabled() {
return base::FeatureList::IsEnabled(kHighPriorityInput);
}
} // namespace
MainThreadSchedulerImpl::MainThreadSchedulerImpl(
std::unique_ptr<TaskQueueManager> task_queue_manager,
base::Optional<base::Time> initial_virtual_time)
: helper_(std::move(task_queue_manager), this),
idle_helper_(&helper_,
this,
"MainThreadSchedulerIdlePeriod",
base::TimeDelta(),
helper_.NewTaskQueue(
MainThreadTaskQueue::QueueCreationParams(
MainThreadTaskQueue::QueueType::kIdle)
.SetFixedPriority(
TaskQueue::QueuePriority::kBestEffortPriority))),
idle_canceled_delayed_task_sweeper_(&helper_,
idle_helper_.IdleTaskRunner()),
render_widget_scheduler_signals_(this),
control_task_queue_(helper_.ControlMainThreadTaskQueue()),
compositor_task_queue_(
helper_.NewTaskQueue(MainThreadTaskQueue::QueueCreationParams(
MainThreadTaskQueue::QueueType::kCompositor)
.SetShouldMonitorQuiescence(true))),
input_task_queue_(helper_.NewTaskQueue(
MainThreadTaskQueue::QueueCreationParams(
MainThreadTaskQueue::QueueType::kInput)
.SetShouldMonitorQuiescence(true)
.SetFixedPriority(
IsUnconditionalHighPriorityInputEnabled()
? base::make_optional(
TaskQueue::QueuePriority::kHighestPriority)
: base::nullopt))),
compositor_task_queue_enabled_voter_(
compositor_task_queue_->CreateQueueEnabledVoter()),
input_task_queue_enabled_voter_(
input_task_queue_->CreateQueueEnabledVoter()),
delayed_update_policy_runner_(
base::BindRepeating(&MainThreadSchedulerImpl::UpdatePolicy,
base::Unretained(this)),
helper_.ControlMainThreadTaskQueue()),
queueing_time_estimator_(this, kQueueingTimeWindowDuration, 20),
main_thread_only_(this,
compositor_task_queue_,
helper_.GetClock(),
helper_.NowTicks()),
any_thread_(this),
policy_may_need_update_(&any_thread_lock_),
weak_factory_(this) {
task_queue_throttler_.reset(
new TaskQueueThrottler(this, &tracing_controller_));
update_policy_closure_ = base::BindRepeating(
&MainThreadSchedulerImpl::UpdatePolicy, weak_factory_.GetWeakPtr());
end_renderer_hidden_idle_period_closure_.Reset(base::BindRepeating(
&MainThreadSchedulerImpl::EndIdlePeriod, weak_factory_.GetWeakPtr()));
// Compositor task queue and default task queue should be managed by
// WebMainThreadScheduler. Control task queue should not.
task_runners_.insert(
std::make_pair(helper_.DefaultMainThreadTaskQueue(), nullptr));
task_runners_.insert(
std::make_pair(compositor_task_queue_,
compositor_task_queue_->CreateQueueEnabledVoter()));
task_runners_.insert(std::make_pair(
input_task_queue_, input_task_queue_->CreateQueueEnabledVoter()));
v8_task_queue_ = NewTaskQueue(MainThreadTaskQueue::QueueCreationParams(
MainThreadTaskQueue::QueueType::kV8));
ipc_task_queue_ = NewTaskQueue(MainThreadTaskQueue::QueueCreationParams(
MainThreadTaskQueue::QueueType::kIPC));
TRACE_EVENT_OBJECT_CREATED_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "MainThreadScheduler",
this);
helper_.SetObserver(this);
// Register a tracing state observer unless we're running in a test without a
// task runner. Note that it's safe to remove a non-existent observer.
if (base::ThreadTaskRunnerHandle::IsSet()) {
base::trace_event::TraceLog::GetInstance()->AddAsyncEnabledStateObserver(
weak_factory_.GetWeakPtr());
}
int32_t delay_for_background_tab_freezing_millis;
if (!base::StringToInt(
base::GetFieldTrialParamValue("BackgroundTabFreezing",
"DelayForBackgroundTabFreezingMills"),
&delay_for_background_tab_freezing_millis)) {
delay_for_background_tab_freezing_millis =
kDelayForBackgroundTabFreezingMillis;
}
delay_for_background_tab_freezing_ = base::TimeDelta::FromMilliseconds(
delay_for_background_tab_freezing_millis);
internal::ProcessState::Get()->is_process_backgrounded =
main_thread_only().renderer_backgrounded;
if (initial_virtual_time) {
main_thread_only().initial_virtual_time = *initial_virtual_time;
// The real uptime of the machine is irrelevant if we're using virtual time
// we choose an arbitrary initial offset.
main_thread_only().initial_virtual_time_ticks =
base::TimeTicks() + base::TimeDelta::FromSeconds(10);
EnableVirtualTime(BaseTimeOverridePolicy::OVERRIDE);
SetVirtualTimePolicy(VirtualTimePolicy::kPause);
}
}
MainThreadSchedulerImpl::~MainThreadSchedulerImpl() {
TRACE_EVENT_OBJECT_DELETED_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "MainThreadScheduler",
this);
for (auto& pair : task_runners_) {
TaskCostEstimator* observer = nullptr;
switch (pair.first->queue_class()) {
case MainThreadTaskQueue::QueueClass::kLoading:
observer = &main_thread_only().loading_task_cost_estimator;
break;
case MainThreadTaskQueue::QueueClass::kTimer:
observer = &main_thread_only().timer_task_cost_estimator;
break;
default:
observer = nullptr;
}
if (observer)
pair.first->RemoveTaskObserver(observer);
pair.first->ShutdownTaskQueue();
}
if (virtual_time_domain_)
UnregisterTimeDomain(virtual_time_domain_.get());
if (virtual_time_control_task_queue_)
virtual_time_control_task_queue_->ShutdownTaskQueue();
base::trace_event::TraceLog::GetInstance()->RemoveAsyncEnabledStateObserver(
this);
// 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(was_shutdown_);
}
MainThreadSchedulerImpl::MainThreadOnly::MainThreadOnly(
MainThreadSchedulerImpl* main_thread_scheduler_impl,
const scoped_refptr<MainThreadTaskQueue>& compositor_task_runner,
const base::TickClock* time_source,
base::TimeTicks now)
: loading_task_cost_estimator(time_source,
kLoadingTaskEstimationSampleCount,
kLoadingTaskEstimationPercentile),
timer_task_cost_estimator(time_source,
kTimerTaskEstimationSampleCount,
kTimerTaskEstimationPercentile),
idle_time_estimator(compositor_task_runner,
time_source,
kShortIdlePeriodDurationSampleCount,
kShortIdlePeriodDurationPercentile),
current_use_case(UseCase::kNone,
"Scheduler.UseCase",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
UseCaseToString),
longest_jank_free_task_duration(
base::TimeDelta(),
"Scheduler.LongestJankFreeTaskDuration",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
TimeDeltaToMilliseconds),
renderer_pause_count(0,
"Scheduler.PauseCount",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_),
navigation_task_expected_count(
0,
"Scheduler.NavigationTaskExpectedCount",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_),
expensive_task_policy(ExpensiveTaskPolicy::kRun,
"Scheduler.ExpensiveTaskPolicy",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
ExpensiveTaskPolicyToString),
rail_mode_for_tracing(current_policy.rail_mode(),
"Scheduler.RAILMode",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
RAILModeToString),
renderer_hidden(false,
"Scheduler.Hidden",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
HiddenStateToString),
renderer_backgrounded(kLaunchingProcessIsBackgrounded,
"RendererVisibility",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
BackgroundStateToString),
keep_active_fetch_or_worker(
false,
"Scheduler.KeepRendererActive",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
freezing_when_backgrounded_enabled(
false,
"MainThreadScheduler.FreezingWhenBackgroundedEnabled",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
frozen_when_backgrounded(false,
"MainThreadScheduler.FrozenWhenBackgrounded",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
loading_task_estimated_cost(
base::TimeDelta(),
"Scheduler.LoadingTaskEstimatedCostMs",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
TimeDeltaToMilliseconds),
timer_task_estimated_cost(
base::TimeDelta(),
"Scheduler.TimerTaskEstimatedCostMs",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
TimeDeltaToMilliseconds),
loading_tasks_seem_expensive(
false,
"Scheduler.LoadingTasksSeemExpensive",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
timer_tasks_seem_expensive(
false,
"Scheduler.TimerTasksSeemExpensive",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
touchstart_expected_soon(false,
"Scheduler.TouchstartExpectedSoon",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
have_seen_a_begin_main_frame(
false,
"Scheduler.HasSeenBeginMainFrame",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
have_reported_blocking_intervention_in_current_policy(
false,
"Scheduler.HasReportedBlockingInterventionInCurrentPolicy",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
have_reported_blocking_intervention_since_navigation(
false,
"Scheduler.HasReportedBlockingInterventionSinceNavigation",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
has_visible_render_widget_with_touch_handler(
false,
"Scheduler.HasVisibleRenderWidgetWithTouchHandler",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
begin_frame_not_expected_soon(
false,
"Scheduler.BeginFrameNotExpectedSoon",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
in_idle_period_for_testing(
false,
"Scheduler.InIdlePeriod",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
use_virtual_time(false,
"Scheduler.UseVirtualTime",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
is_audio_playing(false,
"RendererAudioState",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
AudioPlayingStateToString),
compositor_will_send_main_frame_not_expected(
false,
"Scheduler.CompositorWillSendMainFrameNotExpected",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
has_navigated(false,
"Scheduler.HasNavigated",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
pause_timers_for_webview(false,
"Scheduler.PauseTimersForWebview",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
background_status_changed_at(now),
rail_mode_observer(nullptr),
wake_up_budget_pool(nullptr),
metrics_helper(main_thread_scheduler_impl, now, renderer_backgrounded),
process_type(RendererProcessType::kRenderer,
"RendererProcessType",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
RendererProcessTypeToString),
task_description_for_tracing(
base::nullopt,
"Scheduler.MainThreadTask",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
OptionalTaskDescriptionToString),
virtual_time_policy(VirtualTimePolicy::kAdvance),
virtual_time_pause_count(0),
max_virtual_time_task_starvation_count(0),
virtual_time_stopped(false),
nested_runloop(false),
uniform_distribution(0.0f, 1.0f) {}
MainThreadSchedulerImpl::MainThreadOnly::~MainThreadOnly() = default;
MainThreadSchedulerImpl::AnyThread::AnyThread(
MainThreadSchedulerImpl* main_thread_scheduler_impl)
: awaiting_touch_start_response(
false,
"Scheduler.AwaitingTouchstartResponse",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
in_idle_period(false,
"Scheduler.InIdlePeriod",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
begin_main_frame_on_critical_path(
false,
"Scheduler.BeginMainFrameOnCriticalPath",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
last_gesture_was_compositor_driven(
false,
"Scheduler.LastGestureWasCompositorDriven",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
default_gesture_prevented(
true,
"Scheduler.DefaultGesturePrevented",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
have_seen_a_potentially_blocking_gesture(
false,
"Scheduler.HaveSeenPotentiallyBlockingGesture",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
waiting_for_meaningful_paint(
false,
"Scheduler.WaitingForMeaningfulPaint",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString),
have_seen_input_since_navigation(
false,
"Scheduler.HaveSeenInputSinceNavigation",
main_thread_scheduler_impl,
&main_thread_scheduler_impl->tracing_controller_,
YesNoStateToString) {}
MainThreadSchedulerImpl::AnyThread::~AnyThread() = default;
MainThreadSchedulerImpl::CompositorThreadOnly::CompositorThreadOnly()
: last_input_type(blink::WebInputEvent::kUndefined) {}
MainThreadSchedulerImpl::CompositorThreadOnly::~CompositorThreadOnly() =
default;
MainThreadSchedulerImpl::RendererPauseHandleImpl::RendererPauseHandleImpl(
MainThreadSchedulerImpl* scheduler)
: scheduler_(scheduler) {
scheduler_->PauseRendererImpl();
}
MainThreadSchedulerImpl::RendererPauseHandleImpl::~RendererPauseHandleImpl() {
scheduler_->ResumeRendererImpl();
}
void MainThreadSchedulerImpl::Shutdown() {
if (was_shutdown_)
return;
base::TimeTicks now = tick_clock()->NowTicks();
main_thread_only().metrics_helper.OnRendererShutdown(now);
task_queue_throttler_.reset();
idle_helper_.Shutdown();
helper_.Shutdown();
main_thread_only().rail_mode_observer = nullptr;
was_shutdown_ = true;
}
std::unique_ptr<blink::WebThread> MainThreadSchedulerImpl::CreateMainThread() {
return std::make_unique<WebThreadImplForRendererScheduler>(this);
}
scoped_refptr<base::SingleThreadTaskRunner>
MainThreadSchedulerImpl::DefaultTaskRunner() {
return helper_.DefaultMainThreadTaskQueue();
}
scoped_refptr<base::SingleThreadTaskRunner>
MainThreadSchedulerImpl::CompositorTaskRunner() {
helper_.CheckOnValidThread();
return compositor_task_queue_;
}
scoped_refptr<base::SingleThreadTaskRunner>
MainThreadSchedulerImpl::InputTaskRunner() {
helper_.CheckOnValidThread();
return input_task_queue_;
}
scoped_refptr<SingleThreadIdleTaskRunner>
MainThreadSchedulerImpl::IdleTaskRunner() {
return idle_helper_.IdleTaskRunner();
}
scoped_refptr<base::SingleThreadTaskRunner>
MainThreadSchedulerImpl::IPCTaskRunner() {
return ipc_task_queue_;
}
scoped_refptr<MainThreadTaskQueue> MainThreadSchedulerImpl::DefaultTaskQueue() {
return helper_.DefaultMainThreadTaskQueue();
}
scoped_refptr<MainThreadTaskQueue>
MainThreadSchedulerImpl::CompositorTaskQueue() {
helper_.CheckOnValidThread();
return compositor_task_queue_;
}
scoped_refptr<MainThreadTaskQueue> MainThreadSchedulerImpl::InputTaskQueue() {
helper_.CheckOnValidThread();
return input_task_queue_;
}
scoped_refptr<MainThreadTaskQueue> MainThreadSchedulerImpl::V8TaskQueue() {
helper_.CheckOnValidThread();
return v8_task_queue_;
}
scoped_refptr<MainThreadTaskQueue> MainThreadSchedulerImpl::ControlTaskQueue() {
helper_.CheckOnValidThread();
return helper_.ControlMainThreadTaskQueue();
}
scoped_refptr<MainThreadTaskQueue>
MainThreadSchedulerImpl::VirtualTimeControlTaskQueue() {
helper_.CheckOnValidThread();
return virtual_time_control_task_queue_;
}
scoped_refptr<MainThreadTaskQueue> MainThreadSchedulerImpl::NewTaskQueue(
const MainThreadTaskQueue::QueueCreationParams& params) {
helper_.CheckOnValidThread();
scoped_refptr<MainThreadTaskQueue> task_queue(helper_.NewTaskQueue(params));
std::unique_ptr<TaskQueue::QueueEnabledVoter> voter;
if (params.can_be_deferred || params.can_be_paused || params.can_be_frozen)
voter = task_queue->CreateQueueEnabledVoter();
auto insert_result =
task_runners_.insert(std::make_pair(task_queue, std::move(voter)));
auto queue_class = task_queue->queue_class();
if (queue_class == MainThreadTaskQueue::QueueClass::kTimer) {
task_queue->AddTaskObserver(&main_thread_only().timer_task_cost_estimator);
} else if (queue_class == MainThreadTaskQueue::QueueClass::kLoading) {
task_queue->AddTaskObserver(
&main_thread_only().loading_task_cost_estimator);
}
ApplyTaskQueuePolicy(
task_queue.get(), insert_result.first->second.get(), TaskQueuePolicy(),
main_thread_only().current_policy.GetQueuePolicy(queue_class));
if (task_queue->CanBeThrottled())
AddQueueToWakeUpBudgetPool(task_queue.get());
// If this is a timer queue, and virtual time is enabled and paused, it should
// be suspended by adding a fence to prevent immediate tasks from running when
// they're not supposed to.
if (queue_class == MainThreadTaskQueue::QueueClass::kTimer &&
main_thread_only().virtual_time_stopped &&
main_thread_only().use_virtual_time) {
task_queue->InsertFence(TaskQueue::InsertFencePosition::kNow);
}
return task_queue;
}
scoped_refptr<MainThreadTaskQueue> MainThreadSchedulerImpl::NewLoadingTaskQueue(
MainThreadTaskQueue::QueueType queue_type) {
DCHECK_EQ(MainThreadTaskQueue::QueueClassForQueueType(queue_type),
MainThreadTaskQueue::QueueClass::kLoading);
return NewTaskQueue(
MainThreadTaskQueue::QueueCreationParams(queue_type)
.SetCanBePaused(true)
.SetCanBeFrozen(
RuntimeEnabledFeatures::StopLoadingInBackgroundEnabled())
.SetCanBeDeferred(true)
.SetUsedForImportantTasks(
queue_type ==
MainThreadTaskQueue::QueueType::kFrameLoadingControl));
}
scoped_refptr<MainThreadTaskQueue> MainThreadSchedulerImpl::NewTimerTaskQueue(
MainThreadTaskQueue::QueueType queue_type) {
DCHECK_EQ(MainThreadTaskQueue::QueueClassForQueueType(queue_type),
MainThreadTaskQueue::QueueClass::kTimer);
return NewTaskQueue(MainThreadTaskQueue::QueueCreationParams(queue_type)
.SetCanBePaused(true)
.SetCanBeFrozen(true)
.SetCanBeDeferred(true)
.SetCanBeThrottled(true));
}
std::unique_ptr<WebRenderWidgetSchedulingState>
MainThreadSchedulerImpl::NewRenderWidgetSchedulingState() {
return render_widget_scheduler_signals_.NewRenderWidgetSchedulingState();
}
void MainThreadSchedulerImpl::OnShutdownTaskQueue(
const scoped_refptr<MainThreadTaskQueue>& task_queue) {
if (was_shutdown_)
return;
if (task_queue_throttler_)
task_queue_throttler_->ShutdownTaskQueue(task_queue.get());
if (task_runners_.erase(task_queue)) {
switch (task_queue->queue_class()) {
case MainThreadTaskQueue::QueueClass::kTimer:
task_queue->RemoveTaskObserver(
&main_thread_only().timer_task_cost_estimator);
break;
case MainThreadTaskQueue::QueueClass::kLoading:
task_queue->RemoveTaskObserver(
&main_thread_only().loading_task_cost_estimator);
break;
default:
break;
}
}
}
bool MainThreadSchedulerImpl::CanExceedIdleDeadlineIfRequired() const {
return idle_helper_.CanExceedIdleDeadlineIfRequired();
}
void MainThreadSchedulerImpl::AddTaskObserver(
base::MessageLoop::TaskObserver* task_observer) {
helper_.AddTaskObserver(task_observer);
}
void MainThreadSchedulerImpl::RemoveTaskObserver(
base::MessageLoop::TaskObserver* task_observer) {
helper_.RemoveTaskObserver(task_observer);
}
void MainThreadSchedulerImpl::WillBeginFrame(const viz::BeginFrameArgs& args) {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::WillBeginFrame", "args",
args.AsValue());
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return;
EndIdlePeriod();
main_thread_only().estimated_next_frame_begin =
args.frame_time + args.interval;
main_thread_only().have_seen_a_begin_main_frame = true;
main_thread_only().begin_frame_not_expected_soon = false;
main_thread_only().compositor_frame_interval = args.interval;
{
base::AutoLock lock(any_thread_lock_);
any_thread().begin_main_frame_on_critical_path = args.on_critical_path;
}
}
void MainThreadSchedulerImpl::DidCommitFrameToCompositor() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::DidCommitFrameToCompositor");
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return;
base::TimeTicks now(helper_.NowTicks());
if (now < main_thread_only().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::kInShortIdlePeriod, now,
main_thread_only().estimated_next_frame_begin);
}
main_thread_only().idle_time_estimator.DidCommitFrameToCompositor();
}
void MainThreadSchedulerImpl::BeginFrameNotExpectedSoon() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::BeginFrameNotExpectedSoon");
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return;
main_thread_only().begin_frame_not_expected_soon = true;
idle_helper_.EnableLongIdlePeriod();
{
base::AutoLock lock(any_thread_lock_);
any_thread().begin_main_frame_on_critical_path = false;
}
}
void MainThreadSchedulerImpl::BeginMainFrameNotExpectedUntil(
base::TimeTicks time) {
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return;
base::TimeTicks now(helper_.NowTicks());
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::BeginMainFrameNotExpectedUntil",
"time_remaining", (time - now).InMillisecondsF());
if (now < time) {
// End any previous idle period.
EndIdlePeriod();
// 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::kInShortIdlePeriod, now, time);
}
}
void MainThreadSchedulerImpl::SetAllRenderWidgetsHidden(bool hidden) {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::SetAllRenderWidgetsHidden", "hidden",
hidden);
helper_.CheckOnValidThread();
if (helper_.IsShutdown() || main_thread_only().renderer_hidden == hidden)
return;
end_renderer_hidden_idle_period_closure_.Cancel();
if (hidden) {
idle_helper_.EnableLongIdlePeriod();
// Ensure that we stop running idle tasks after a few seconds of being
// hidden.
base::TimeDelta end_idle_when_hidden_delay =
base::TimeDelta::FromMilliseconds(kEndIdleWhenHiddenDelayMillis);
control_task_queue_->PostDelayedTask(
FROM_HERE, end_renderer_hidden_idle_period_closure_.GetCallback(),
end_idle_when_hidden_delay);
main_thread_only().renderer_hidden = true;
} else {
main_thread_only().renderer_hidden = false;
EndIdlePeriod();
}
// TODO(alexclarke): Should we update policy here?
CreateTraceEventObjectSnapshot();
}
void MainThreadSchedulerImpl::SetHasVisibleRenderWidgetWithTouchHandler(
bool has_visible_render_widget_with_touch_handler) {
helper_.CheckOnValidThread();
if (has_visible_render_widget_with_touch_handler ==
main_thread_only().has_visible_render_widget_with_touch_handler)
return;
main_thread_only().has_visible_render_widget_with_touch_handler =
has_visible_render_widget_with_touch_handler;
base::AutoLock lock(any_thread_lock_);
UpdatePolicyLocked(UpdateType::kForceUpdate);
}
void MainThreadSchedulerImpl::SetRendererHidden(bool hidden) {
if (hidden) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::OnRendererHidden");
} else {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::OnRendererVisible");
}
helper_.CheckOnValidThread();
main_thread_only().renderer_hidden = hidden;
}
void MainThreadSchedulerImpl::SetRendererBackgrounded(bool backgrounded) {
helper_.CheckOnValidThread();
if (helper_.IsShutdown() ||
main_thread_only().renderer_backgrounded == backgrounded)
return;
if (backgrounded) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::OnRendererBackgrounded");
MainThreadMetricsHelper::RecordBackgroundedTransition(
BackgroundedRendererTransition::kBackgrounded);
} else {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::OnRendererForegrounded");
MainThreadMetricsHelper::RecordBackgroundedTransition(
BackgroundedRendererTransition::kForegrounded);
}
main_thread_only().renderer_backgrounded = backgrounded;
internal::ProcessState::Get()->is_process_backgrounded = backgrounded;
main_thread_only().background_status_changed_at = tick_clock()->NowTicks();
queueing_time_estimator_.OnRendererStateChanged(
backgrounded, main_thread_only().background_status_changed_at);
UpdatePolicy();
base::TimeTicks now = tick_clock()->NowTicks();
if (backgrounded) {
main_thread_only().metrics_helper.OnRendererBackgrounded(now);
} else {
main_thread_only().metrics_helper.OnRendererForegrounded(now);
}
}
void MainThreadSchedulerImpl::SetSchedulerKeepActive(bool keep_active) {
main_thread_only().keep_active_fetch_or_worker = keep_active;
for (PageSchedulerImpl* page_scheduler : main_thread_only().page_schedulers) {
page_scheduler->SetKeepActive(keep_active);
}
}
bool MainThreadSchedulerImpl::SchedulerKeepActive() {
return main_thread_only().keep_active_fetch_or_worker;
}
#if defined(OS_ANDROID)
void MainThreadSchedulerImpl::PauseTimersForAndroidWebView() {
main_thread_only().pause_timers_for_webview = true;
UpdatePolicy();
}
void MainThreadSchedulerImpl::ResumeTimersForAndroidWebView() {
main_thread_only().pause_timers_for_webview = false;
UpdatePolicy();
}
#endif
void MainThreadSchedulerImpl::OnAudioStateChanged() {
bool is_audio_playing = false;
for (PageSchedulerImpl* page_scheduler : main_thread_only().page_schedulers) {
is_audio_playing = is_audio_playing || page_scheduler->IsAudioPlaying();
}
if (is_audio_playing == main_thread_only().is_audio_playing)
return;
main_thread_only().is_audio_playing = is_audio_playing;
}
std::unique_ptr<MainThreadSchedulerImpl::RendererPauseHandle>
MainThreadSchedulerImpl::PauseRenderer() {
return std::make_unique<RendererPauseHandleImpl>(this);
}
void MainThreadSchedulerImpl::PauseRendererImpl() {
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return;
++main_thread_only().renderer_pause_count;
UpdatePolicy();
}
void MainThreadSchedulerImpl::ResumeRendererImpl() {
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return;
--main_thread_only().renderer_pause_count;
DCHECK_GE(main_thread_only().renderer_pause_count.value(), 0);
UpdatePolicy();
}
void MainThreadSchedulerImpl::EndIdlePeriod() {
if (main_thread_only().in_idle_period_for_testing)
return;
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::EndIdlePeriod");
helper_.CheckOnValidThread();
idle_helper_.EndIdlePeriod();
}
void MainThreadSchedulerImpl::EndIdlePeriodForTesting(
const base::Closure& callback,
base::TimeTicks time_remaining) {
main_thread_only().in_idle_period_for_testing = false;
EndIdlePeriod();
callback.Run();
}
bool MainThreadSchedulerImpl::PolicyNeedsUpdateForTesting() {
return policy_may_need_update_.IsSet();
}
// static
bool MainThreadSchedulerImpl::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.GetType() == blink::WebInputEvent::kMouseDown ||
web_input_event.GetType() == blink::WebInputEvent::kMouseMove) &&
(web_input_event.GetModifiers() &
blink::WebInputEvent::kLeftButtonDown)) {
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.GetType()) ||
blink::WebInputEvent::IsKeyboardEventType(web_input_event.GetType())) {
return false;
}
return true;
}
void MainThreadSchedulerImpl::DidHandleInputEventOnCompositorThread(
const blink::WebInputEvent& web_input_event,
InputEventState event_state) {
TRACE_EVENT0(
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::DidHandleInputEventOnCompositorThread");
if (!ShouldPrioritizeInputEvent(web_input_event))
return;
UpdateForInputEventOnCompositorThread(web_input_event.GetType(), event_state);
}
void MainThreadSchedulerImpl::DidAnimateForInputOnCompositorThread() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::DidAnimateForInputOnCompositorThread");
base::AutoLock lock(any_thread_lock_);
any_thread().fling_compositor_escalation_deadline =
helper_.NowTicks() +
base::TimeDelta::FromMilliseconds(kFlingEscalationLimitMillis);
}
void MainThreadSchedulerImpl::UpdateForInputEventOnCompositorThread(
blink::WebInputEvent::Type type,
InputEventState input_event_state) {
base::AutoLock lock(any_thread_lock_);
base::TimeTicks now = helper_.NowTicks();
// 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"),
"MainThreadSchedulerImpl::UpdateForInputEventOnCompositorThread",
"type", static_cast<int>(type), "input_event_state",
InputEventStateToString(input_event_state));
base::TimeDelta unused_policy_duration;
UseCase previous_use_case =
ComputeCurrentUseCase(now, &unused_policy_duration);
bool was_awaiting_touch_start_response =
any_thread().awaiting_touch_start_response;
any_thread().user_model.DidStartProcessingInputEvent(type, now);
any_thread().have_seen_input_since_navigation = true;
if (input_event_state == InputEventState::EVENT_CONSUMED_BY_COMPOSITOR)
any_thread().user_model.DidFinishProcessingInputEvent(now);
switch (type) {
case blink::WebInputEvent::kTouchStart:
any_thread().awaiting_touch_start_response = true;
// This is just a fail-safe to reset the state of
// |last_gesture_was_compositor_driven| to the default. We don't know
// yet where the gesture will run.
any_thread().last_gesture_was_compositor_driven = false;
any_thread().have_seen_a_potentially_blocking_gesture = true;
// Assume the default gesture is prevented until we see evidence
// otherwise.
any_thread().default_gesture_prevented = true;
break;
case blink::WebInputEvent::kTouchMove:
// 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 (any_thread().awaiting_touch_start_response &&
GetCompositorThreadOnly().last_input_type ==
blink::WebInputEvent::kTouchMove) {
any_thread().awaiting_touch_start_response = false;
}
break;
case blink::WebInputEvent::kGesturePinchUpdate:
case blink::WebInputEvent::kGestureScrollUpdate:
// If we see events for an established gesture, we can lock it to the
// appropriate thread as the gesture can no longer be cancelled.
any_thread().last_gesture_was_compositor_driven =
input_event_state == InputEventState::EVENT_CONSUMED_BY_COMPOSITOR;
any_thread().awaiting_touch_start_response = false;
any_thread().default_gesture_prevented = false;
break;
case blink::WebInputEvent::kGestureFlingCancel:
any_thread().fling_compositor_escalation_deadline = base::TimeTicks();
break;
case blink::WebInputEvent::kGestureTapDown:
case blink::WebInputEvent::kGestureShowPress:
case blink::WebInputEvent::kGestureScrollEnd:
// With no observable effect, these meta events do not indicate a
// meaningful touchstart response and should not impact task priority.
break;
case blink::WebInputEvent::kMouseDown:
// Reset tracking state at the start of a new mouse drag gesture.
any_thread().last_gesture_was_compositor_driven = false;
any_thread().default_gesture_prevented = true;
break;
case blink::WebInputEvent::kMouseMove:
// Consider mouse movement with the left button held down (see
// ShouldPrioritizeInputEvent) similarly to a touch gesture.
any_thread().last_gesture_was_compositor_driven =
input_event_state == InputEventState::EVENT_CONSUMED_BY_COMPOSITOR;
any_thread().awaiting_touch_start_response = false;
break;
case blink::WebInputEvent::kMouseWheel:
any_thread().last_gesture_was_compositor_driven =
input_event_state == InputEventState::EVENT_CONSUMED_BY_COMPOSITOR;
any_thread().awaiting_touch_start_response = false;
any_thread().have_seen_a_potentially_blocking_gesture = true;
// If the event was sent to the main thread, assume the default gesture is
// prevented until we see evidence otherwise.
any_thread().default_gesture_prevented =
!any_thread().last_gesture_was_compositor_driven;
break;
case blink::WebInputEvent::kUndefined:
break;
default:
any_thread().awaiting_touch_start_response = false;
break;
}
// Avoid unnecessary policy updates if the use case did not change.
UseCase use_case = ComputeCurrentUseCase(now, &unused_policy_duration);
if (use_case != previous_use_case ||
was_awaiting_touch_start_response !=
any_thread().awaiting_touch_start_response) {
EnsureUrgentPolicyUpdatePostedOnMainThread(FROM_HERE);
}
GetCompositorThreadOnly().last_input_type = type;
}
void MainThreadSchedulerImpl::DidHandleInputEventOnMainThread(
const WebInputEvent& web_input_event,
WebInputEventResult result) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::DidHandleInputEventOnMainThread");
helper_.CheckOnValidThread();
if (ShouldPrioritizeInputEvent(web_input_event)) {
base::AutoLock lock(any_thread_lock_);
any_thread().user_model.DidFinishProcessingInputEvent(helper_.NowTicks());
// If we were waiting for a touchstart response and the main thread has
// prevented the default gesture, consider the gesture established. This
// ensures single-event gestures such as button presses are promptly
// detected.
if (any_thread().awaiting_touch_start_response &&
result == WebInputEventResult::kHandledApplication) {
any_thread().awaiting_touch_start_response = false;
any_thread().default_gesture_prevented = true;
UpdatePolicyLocked(UpdateType::kMayEarlyOutIfPolicyUnchanged);
}
}
}
bool MainThreadSchedulerImpl::IsHighPriorityWorkAnticipated() {
helper_.CheckOnValidThread();
if (helper_.IsShutdown())
return false;
MaybeUpdatePolicy();
// The touchstart, synchronized gesture and main-thread gesture use cases
// indicate a strong likelihood of high-priority work in the near future.
UseCase use_case = main_thread_only().current_use_case;
return main_thread_only().touchstart_expected_soon ||
use_case == UseCase::kTouchstart ||
use_case == UseCase::kMainThreadGesture ||
use_case == UseCase::kMainThreadCustomInputHandling ||
use_case == UseCase::kSynchronizedGesture;
}
bool MainThreadSchedulerImpl::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 (main_thread_only().current_use_case) {
case UseCase::kCompositorGesture:
case UseCase::kNone:
return main_thread_only().touchstart_expected_soon;
case UseCase::kMainThreadGesture:
case UseCase::kMainThreadCustomInputHandling:
case UseCase::kSynchronizedGesture:
return compositor_task_queue_->HasTaskToRunImmediately() ||
main_thread_only().touchstart_expected_soon;
case UseCase::kTouchstart:
return true;
case UseCase::kLoading:
return false;
default:
NOTREACHED();
return false;
}
}
base::TimeTicks MainThreadSchedulerImpl::CurrentIdleTaskDeadlineForTesting()
const {
return idle_helper_.CurrentIdleTaskDeadline();
}
void MainThreadSchedulerImpl::RunIdleTasksForTesting(
const base::Closure& callback) {
main_thread_only().in_idle_period_for_testing = true;
IdleTaskRunner()->PostIdleTask(
FROM_HERE,
base::BindOnce(&MainThreadSchedulerImpl::EndIdlePeriodForTesting,
weak_factory_.GetWeakPtr(), callback));
idle_helper_.EnableLongIdlePeriod();
}
void MainThreadSchedulerImpl::MaybeUpdatePolicy() {
helper_.CheckOnValidThread();
if (policy_may_need_update_.IsSet()) {
UpdatePolicy();
}
}
void MainThreadSchedulerImpl::EnsureUrgentPolicyUpdatePostedOnMainThread(
const base::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_queue_->PostTask(from_here, update_policy_closure_);
}
}
void MainThreadSchedulerImpl::UpdatePolicy() {
base::AutoLock lock(any_thread_lock_);
UpdatePolicyLocked(UpdateType::kMayEarlyOutIfPolicyUnchanged);
}
void MainThreadSchedulerImpl::ForceUpdatePolicy() {
base::AutoLock lock(any_thread_lock_);
UpdatePolicyLocked(UpdateType::kForceUpdate);
}
namespace {
void UpdatePolicyDuration(base::TimeTicks now,
base::TimeTicks policy_expiration,
base::TimeDelta* policy_duration) {
if (policy_expiration <= now)
return;
if (policy_duration->is_zero()) {
*policy_duration = policy_expiration - now;
return;
}
*policy_duration = std::min(*policy_duration, policy_expiration - now);
}
} // namespace
void MainThreadSchedulerImpl::UpdatePolicyLocked(UpdateType update_type) {
helper_.CheckOnValidThread();
any_thread_lock_.AssertAcquired();
if (helper_.IsShutdown())
return;
base::TimeTicks now = helper_.NowTicks();
policy_may_need_update_.SetWhileLocked(false);
base::TimeDelta expected_use_case_duration;
UseCase use_case = ComputeCurrentUseCase(now, &expected_use_case_duration);
main_thread_only().current_use_case = use_case;
base::TimeDelta touchstart_expected_flag_valid_for_duration;
// TODO(skyostil): Consider handlers for all types of blocking gestures (e.g.,
// mouse wheel) instead of just touchstart.
bool touchstart_expected_soon = false;
if (main_thread_only().has_visible_render_widget_with_touch_handler) {
touchstart_expected_soon = any_thread().user_model.IsGestureExpectedSoon(
now, &touchstart_expected_flag_valid_for_duration);
}
main_thread_only().touchstart_expected_soon = touchstart_expected_soon;
base::TimeDelta longest_jank_free_task_duration =
EstimateLongestJankFreeTaskDuration();
main_thread_only().longest_jank_free_task_duration =
longest_jank_free_task_duration;
main_thread_only().loading_task_estimated_cost =
main_thread_only().loading_task_cost_estimator.expected_task_duration();
bool loading_tasks_seem_expensive =
main_thread_only().loading_task_estimated_cost >
longest_jank_free_task_duration;
main_thread_only().timer_task_estimated_cost =
main_thread_only().timer_task_cost_estimator.expected_task_duration();
bool timer_tasks_seem_expensive =
main_thread_only().timer_task_estimated_cost >
longest_jank_free_task_duration;
main_thread_only().timer_tasks_seem_expensive = timer_tasks_seem_expensive;
main_thread_only().loading_tasks_seem_expensive =
loading_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.is_zero() ||
(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;
}
bool previously_frozen_when_backgrounded =
main_thread_only().frozen_when_backgrounded;
bool newly_frozen = false;
if (main_thread_only().renderer_backgrounded &&
main_thread_only().freezing_when_backgrounded_enabled) {
base::TimeTicks stop_at = main_thread_only().background_status_changed_at +
delay_for_background_tab_freezing_;
newly_frozen = !main_thread_only().frozen_when_backgrounded;
main_thread_only().frozen_when_backgrounded = now >= stop_at;
newly_frozen &= main_thread_only().frozen_when_backgrounded;
if (!main_thread_only().frozen_when_backgrounded)
UpdatePolicyDuration(now, stop_at, &new_policy_duration);
} else {
main_thread_only().frozen_when_backgrounded = false;
}
if (new_policy_duration > base::TimeDelta()) {
main_thread_only().current_policy_expiration_time =
now + new_policy_duration;
delayed_update_policy_runner_.SetDeadline(FROM_HERE, new_policy_duration,
now);
} else {
main_thread_only().current_policy_expiration_time = base::TimeTicks();
}
// Avoid prioritizing main thread compositing (e.g., rAF) if it is extremely
// slow, because that can cause starvation in other task sources.
bool main_thread_compositing_is_fast =
main_thread_only().idle_time_estimator.GetExpectedIdleDuration(
main_thread_only().compositor_frame_interval) >
main_thread_only().compositor_frame_interval *
kFastCompositingIdleTimeThreshold;
Policy new_policy;
ExpensiveTaskPolicy expensive_task_policy = ExpensiveTaskPolicy::kRun;
new_policy.rail_mode() = v8::PERFORMANCE_ANIMATION;
switch (use_case) {
case UseCase::kCompositorGesture:
if (touchstart_expected_soon) {
new_policy.rail_mode() = v8::PERFORMANCE_RESPONSE;
expensive_task_policy = ExpensiveTaskPolicy::kBlock;
new_policy.compositor_queue_policy().priority =
TaskQueue::kHighestPriority;
} 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_policy().priority = TaskQueue::kLowPriority;
}
break;
case UseCase::kSynchronizedGesture:
new_policy.compositor_queue_policy().priority =
main_thread_compositing_is_fast ? TaskQueue::kHighestPriority
: TaskQueue::kNormalPriority;
if (touchstart_expected_soon) {
new_policy.rail_mode() = v8::PERFORMANCE_RESPONSE;
expensive_task_policy = ExpensiveTaskPolicy::kBlock;
} else {
expensive_task_policy = ExpensiveTaskPolicy::kThrottle;
}
break;
case UseCase::kMainThreadCustomInputHandling:
// In main thread input handling scenarios we don't have perfect knowledge
// about which things we should be prioritizing, so we don't attempt to
// block expensive tasks because we don't know whether they were integral
// to the page's functionality or not.
new_policy.compositor_queue_policy().priority =
main_thread_compositing_is_fast ? TaskQueue::kHighestPriority
: TaskQueue::kNormalPriority;
break;
case UseCase::kMainThreadGesture:
// A main thread gesture is for example a scroll gesture which is handled
// by the main thread. Since we know the established gesture type, we can
// be a little more aggressive about prioritizing compositing and input
// handling over other tasks.
new_policy.compositor_queue_policy().priority =
TaskQueue::kHighestPriority;
if (touchstart_expected_soon) {
new_policy.rail_mode() = v8::PERFORMANCE_RESPONSE;
expensive_task_policy = ExpensiveTaskPolicy::kBlock;
} else {
expensive_task_policy = ExpensiveTaskPolicy::kThrottle;
}
break;
case UseCase::kTouchstart:
new_policy.rail_mode() = v8::PERFORMANCE_RESPONSE;
new_policy.compositor_queue_policy().priority =
TaskQueue::kHighestPriority;
new_policy.loading_queue_policy().is_blocked = true;
new_policy.timer_queue_policy().is_blocked = true;
// NOTE this is a nop due to the above.
expensive_task_policy = ExpensiveTaskPolicy::kBlock;
break;
case UseCase::kNone:
// It's only safe to block tasks that if we are expecting a compositor
// driven gesture.
if (touchstart_expected_soon &&
any_thread().last_gesture_was_compositor_driven) {
new_policy.rail_mode() = v8::PERFORMANCE_RESPONSE;
expensive_task_policy = ExpensiveTaskPolicy::kBlock;
}
break;
case UseCase::kLoading:
new_policy.rail_mode() = v8::PERFORMANCE_LOAD;
// TODO(skyostil): Experiment with increasing loading and default queue
// priorities and throttling rendering frame rate.
break;
default:
NOTREACHED();
}
// TODO(skyostil): Add an idle state for foreground tabs too.
if (main_thread_only().renderer_hidden)
new_policy.rail_mode() = v8::PERFORMANCE_IDLE;
if (expensive_task_policy == ExpensiveTaskPolicy::kBlock &&
(!main_thread_only().have_seen_a_begin_main_frame ||
main_thread_only().navigation_task_expected_count > 0)) {
expensive_task_policy = ExpensiveTaskPolicy::kRun;
}
switch (expensive_task_policy) {
case ExpensiveTaskPolicy::kRun:
break;
case ExpensiveTaskPolicy::kBlock:
if (loading_tasks_seem_expensive)
new_policy.loading_queue_policy().is_blocked = true;
if (timer_tasks_seem_expensive)
new_policy.timer_queue_policy().is_blocked = true;
break;
case ExpensiveTaskPolicy::kThrottle:
if (loading_tasks_seem_expensive) {
new_policy.loading_queue_policy().is_throttled = true;
}
if (timer_tasks_seem_expensive) {
new_policy.timer_queue_policy().is_throttled = true;
}
break;
}
main_thread_only().expensive_task_policy = expensive_task_policy;
if (main_thread_only().frozen_when_backgrounded) {
// TODO(panicker): Remove this, as it is controlled at
// FrameScheduler. This is currently needed to avoid early out.
new_policy.timer_queue_policy().is_frozen = true;
}
if (main_thread_only().renderer_pause_count != 0) {
new_policy.loading_queue_policy().is_paused = true;
new_policy.timer_queue_policy().is_paused = true;
}
if (main_thread_only().pause_timers_for_webview) {
new_policy.timer_queue_policy().is_paused = true;
}
if (main_thread_only().renderer_backgrounded &&
RuntimeEnabledFeatures::TimerThrottlingForBackgroundTabsEnabled()) {
new_policy.timer_queue_policy().is_throttled = true;
}
if (main_thread_only().use_virtual_time) {
new_policy.compositor_queue_policy().use_virtual_time = true;
new_policy.default_queue_policy().use_virtual_time = true;
new_policy.loading_queue_policy().use_virtual_time = true;
new_policy.timer_queue_policy().use_virtual_time = true;
}
new_policy.should_disable_throttling() =
main_thread_only().use_virtual_time;
// Tracing is done before the early out check, because it's quite possible we
// will otherwise miss this information in traces.
CreateTraceEventObjectSnapshotLocked();
// TODO(alexclarke): Can we get rid of force update now?
if (update_type == UpdateType::kMayEarlyOutIfPolicyUnchanged &&
new_policy == main_thread_only().current_policy) {
return;
}
for (const auto& pair : task_runners_) {
MainThreadTaskQueue::QueueClass queue_class = pair.first->queue_class();
ApplyTaskQueuePolicy(
pair.first.get(), pair.second.get(),
main_thread_only().current_policy.GetQueuePolicy(queue_class),
new_policy.GetQueuePolicy(queue_class));
}
main_thread_only().rail_mode_for_tracing = new_policy.rail_mode();
if (main_thread_only().rail_mode_observer &&
new_policy.rail_mode() != main_thread_only().current_policy.rail_mode()) {
main_thread_only().rail_mode_observer->OnRAILModeChanged(
new_policy.rail_mode());
}
// TODO(skyostil): send these notifications after releasing the scheduler
// lock.
if (main_thread_only().freezing_when_backgrounded_enabled) {
if (main_thread_only().frozen_when_backgrounded !=
previously_frozen_when_backgrounded) {
SetFrozenInBackground(main_thread_only().frozen_when_backgrounded);
MainThreadMetricsHelper::RecordBackgroundedTransition(
main_thread_only().frozen_when_backgrounded
? BackgroundedRendererTransition::kFrozenAfterDelay
: BackgroundedRendererTransition::kResumed);
}
}
if (new_policy.should_disable_throttling() !=
main_thread_only().current_policy.should_disable_throttling()) {
if (new_policy.should_disable_throttling()) {
task_queue_throttler()->DisableThrottling();
} else {
task_queue_throttler()->EnableThrottling();
}
}
DCHECK(compositor_task_queue_->IsQueueEnabled());
main_thread_only().current_policy = new_policy;
if (newly_frozen)
Platform::Current()->RequestPurgeMemory();
}
void MainThreadSchedulerImpl::ApplyTaskQueuePolicy(
MainThreadTaskQueue* task_queue,
TaskQueue::QueueEnabledVoter* task_queue_enabled_voter,
const TaskQueuePolicy& old_task_queue_policy,
const TaskQueuePolicy& new_task_queue_policy) const {
DCHECK(old_task_queue_policy.IsQueueEnabled(task_queue) ||
task_queue_enabled_voter);
if (task_queue_enabled_voter) {
task_queue_enabled_voter->SetQueueEnabled(
new_task_queue_policy.IsQueueEnabled(task_queue));
}
// Make sure if there's no voter that the task queue is enabled.
DCHECK(task_queue_enabled_voter ||
old_task_queue_policy.IsQueueEnabled(task_queue));
task_queue->SetQueuePriority(new_task_queue_policy.GetPriority(task_queue));
TimeDomainType old_time_domain_type =
old_task_queue_policy.GetTimeDomainType(task_queue);
TimeDomainType new_time_domain_type =
new_task_queue_policy.GetTimeDomainType(task_queue);
if (old_time_domain_type != new_time_domain_type) {
if (old_time_domain_type == TimeDomainType::kThrottled) {
task_queue_throttler_->DecreaseThrottleRefCount(task_queue);
} else if (new_time_domain_type == TimeDomainType::kThrottled) {
task_queue_throttler_->IncreaseThrottleRefCount(task_queue);
}
if (new_time_domain_type == TimeDomainType::kVirtual) {
DCHECK(virtual_time_domain_);
task_queue->SetTimeDomain(virtual_time_domain_.get());
} else {
task_queue->SetTimeDomain(real_time_domain());
}
}
}
UseCase MainThreadSchedulerImpl::ComputeCurrentUseCase(
base::TimeTicks now,
base::TimeDelta* expected_use_case_duration) const {
any_thread_lock_.AssertAcquired();
// Special case for flings. This is needed because we don't get notification
// of a fling ending (although we do for cancellation).
if (any_thread().fling_compositor_escalation_deadline > now &&
!any_thread().awaiting_touch_start_response) {
*expected_use_case_duration =
any_thread().fling_compositor_escalation_deadline - now;
return UseCase::kCompositorGesture;
}
// Above all else we want to be responsive to user input.
*expected_use_case_duration =
any_thread().user_model.TimeLeftInUserGesture(now);
if (*expected_use_case_duration > base::TimeDelta()) {
// Has a gesture been fully established?
if (any_thread().awaiting_touch_start_response) {
// No, so arrange for compositor tasks to be run at the highest priority.
return UseCase::kTouchstart;
}
// Yes a gesture has been established. Based on how the gesture is handled
// we need to choose between one of four use cases:
// 1. kCompositorGesture where the gesture is processed only on the
// compositor thread.
// 2. MAIN_THREAD_GESTURE where the gesture is processed only on the main
// thread.
// 3. MAIN_THREAD_CUSTOM_INPUT_HANDLING where the main thread processes a
// stream of input events and has prevented a default gesture from being
// started.
// 4. SYNCHRONIZED_GESTURE where the gesture is processed on both threads.
if (any_thread().last_gesture_was_compositor_driven) {
if (any_thread().begin_main_frame_on_critical_path) {
return UseCase::kSynchronizedGesture;
} else {
return UseCase::kCompositorGesture;
}
}
if (any_thread().default_gesture_prevented) {
return UseCase::kMainThreadCustomInputHandling;
} else {
return UseCase::kMainThreadGesture;
}
}
// Occasionally the meaningful paint fails to be detected, so as a fallback we
// treat the presence of input as an indirect signal that there is meaningful
// content on the page.
if (any_thread().waiting_for_meaningful_paint &&
!any_thread().have_seen_input_since_navigation) {
return UseCase::kLoading;
}
return UseCase::kNone;
}
base::TimeDelta MainThreadSchedulerImpl::EstimateLongestJankFreeTaskDuration()
const {
switch (main_thread_only().current_use_case) {
case UseCase::kTouchstart:
case UseCase::kCompositorGesture:
case UseCase::kLoading:
case UseCase::kNone:
return base::TimeDelta::FromMilliseconds(kRailsResponseTimeMillis);
case UseCase::kMainThreadCustomInputHandling:
case UseCase::kMainThreadGesture:
case UseCase::kSynchronizedGesture:
return main_thread_only().idle_time_estimator.GetExpectedIdleDuration(
main_thread_only().compositor_frame_interval);
default:
NOTREACHED();
return base::TimeDelta::FromMilliseconds(kRailsResponseTimeMillis);
}
}
bool MainThreadSchedulerImpl::CanEnterLongIdlePeriod(
base::TimeTicks now,
base::TimeDelta* next_long_idle_period_delay_out) {
helper_.CheckOnValidThread();
MaybeUpdatePolicy();
if (main_thread_only().current_use_case == UseCase::kTouchstart) {
// 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 =
std::max(base::TimeDelta(),
main_thread_only().current_policy_expiration_time - now);
return false;
}
return true;
}
void MainThreadSchedulerImpl::SetFrozenInBackground(bool frozen) const {
for (PageSchedulerImpl* page_scheduler : main_thread_only().page_schedulers) {
// This moves the page to FROZEN lifecycle state.
page_scheduler->SetPageFrozen(frozen);
}
}
MainThreadSchedulerHelper*
MainThreadSchedulerImpl::GetSchedulerHelperForTesting() {
return &helper_;
}
TaskCostEstimator*
MainThreadSchedulerImpl::GetLoadingTaskCostEstimatorForTesting() {
return &main_thread_only().loading_task_cost_estimator;
}
TaskCostEstimator*
MainThreadSchedulerImpl::GetTimerTaskCostEstimatorForTesting() {
return &main_thread_only().timer_task_cost_estimator;
}
IdleTimeEstimator* MainThreadSchedulerImpl::GetIdleTimeEstimatorForTesting() {
return &main_thread_only().idle_time_estimator;
}
WakeUpBudgetPool* MainThreadSchedulerImpl::GetWakeUpBudgetPoolForTesting() {
return main_thread_only().wake_up_budget_pool;
}
base::TimeTicks MainThreadSchedulerImpl::EnableVirtualTime() {
return EnableVirtualTime(main_thread_only().initial_virtual_time.is_null()
? BaseTimeOverridePolicy::DO_NOT_OVERRIDE
: BaseTimeOverridePolicy::OVERRIDE);
}
base::TimeTicks MainThreadSchedulerImpl::EnableVirtualTime(
BaseTimeOverridePolicy policy) {
if (main_thread_only().use_virtual_time)
return main_thread_only().initial_virtual_time_ticks;
main_thread_only().use_virtual_time = true;
DCHECK(!virtual_time_domain_);
if (main_thread_only().initial_virtual_time.is_null())
main_thread_only().initial_virtual_time = base::Time::Now();
if (main_thread_only().initial_virtual_time_ticks.is_null())
main_thread_only().initial_virtual_time_ticks = tick_clock()->NowTicks();
virtual_time_domain_.reset(new AutoAdvancingVirtualTimeDomain(
main_thread_only().initial_virtual_time +
main_thread_only().initial_virtual_time_offset,
main_thread_only().initial_virtual_time_ticks +
main_thread_only().initial_virtual_time_offset,
&helper_, policy));
RegisterTimeDomain(virtual_time_domain_.get());
virtual_time_domain_->SetObserver(this);
DCHECK(!virtual_time_control_task_queue_);
virtual_time_control_task_queue_ =
helper_.NewTaskQueue(MainThreadTaskQueue::QueueCreationParams(
MainThreadTaskQueue::QueueType::kControl));
virtual_time_control_task_queue_->SetQueuePriority(
TaskQueue::kControlPriority);
virtual_time_control_task_queue_->SetTimeDomain(virtual_time_domain_.get());
main_thread_only().use_virtual_time = true;
ForceUpdatePolicy();
virtual_time_domain_->SetCanAdvanceVirtualTime(
!main_thread_only().virtual_time_stopped);
if (main_thread_only().virtual_time_stopped)
VirtualTimePaused();
return main_thread_only().initial_virtual_time_ticks;
}
bool MainThreadSchedulerImpl::IsVirtualTimeEnabled() const {
return main_thread_only().use_virtual_time;
}
void MainThreadSchedulerImpl::DisableVirtualTimeForTesting() {
if (!main_thread_only().use_virtual_time)
return;
// Reset virtual time and all tasks queues back to their initial state.
main_thread_only().use_virtual_time = false;
if (main_thread_only().virtual_time_stopped) {
main_thread_only().virtual_time_stopped = false;
VirtualTimeResumed();
}
ForceUpdatePolicy();
virtual_time_control_task_queue_->ShutdownTaskQueue();
virtual_time_control_task_queue_ = nullptr;
UnregisterTimeDomain(virtual_time_domain_.get());
virtual_time_domain_.reset();
virtual_time_control_task_queue_ = nullptr;
ApplyVirtualTimePolicy();
// Reset the MetricsHelper because it gets confused by time going backwards.
base::TimeTicks now = tick_clock()->NowTicks();
main_thread_only().metrics_helper.ResetForTest(now);
}
void MainThreadSchedulerImpl::SetVirtualTimeStopped(bool virtual_time_stopped) {
if (main_thread_only().virtual_time_stopped == virtual_time_stopped)
return;
main_thread_only().virtual_time_stopped = virtual_time_stopped;
if (!main_thread_only().use_virtual_time)
return;
virtual_time_domain_->SetCanAdvanceVirtualTime(!virtual_time_stopped);
if (virtual_time_stopped) {
VirtualTimePaused();
} else {
VirtualTimeResumed();
}
}
void MainThreadSchedulerImpl::VirtualTimePaused() {
for (const auto& pair : task_runners_) {
if (pair.first->queue_class() == MainThreadTaskQueue::QueueClass::kTimer) {
DCHECK(!task_queue_throttler_->IsThrottled(pair.first.get()));
pair.first->InsertFence(TaskQueue::InsertFencePosition::kNow);
}
}
for (auto& observer : main_thread_only().virtual_time_observers) {
observer.OnVirtualTimePaused(virtual_time_domain_->Now() -
main_thread_only().initial_virtual_time_ticks);
}
}
void MainThreadSchedulerImpl::VirtualTimeResumed() {
for (const auto& pair : task_runners_) {
if (pair.first->queue_class() == MainThreadTaskQueue::QueueClass::kTimer) {
DCHECK(!task_queue_throttler_->IsThrottled(pair.first.get()));
DCHECK(pair.first->HasActiveFence());
pair.first->RemoveFence();
}
}
}
bool MainThreadSchedulerImpl::VirtualTimeAllowedToAdvance() const {
return !main_thread_only().virtual_time_stopped;
}
base::TimeTicks MainThreadSchedulerImpl::IncrementVirtualTimePauseCount() {
main_thread_only().virtual_time_pause_count++;
ApplyVirtualTimePolicy();
if (virtual_time_domain_)
return virtual_time_domain_->Now();
return tick_clock()->NowTicks();
}
void MainThreadSchedulerImpl::DecrementVirtualTimePauseCount() {
main_thread_only().virtual_time_pause_count--;
DCHECK_GE(main_thread_only().virtual_time_pause_count, 0);
ApplyVirtualTimePolicy();
}
void MainThreadSchedulerImpl::MaybeAdvanceVirtualTime(
base::TimeTicks new_virtual_time) {
if (virtual_time_domain_)
virtual_time_domain_->MaybeAdvanceVirtualTime(new_virtual_time);
}
void MainThreadSchedulerImpl::SetVirtualTimePolicy(VirtualTimePolicy policy) {
main_thread_only().virtual_time_policy = policy;
ApplyVirtualTimePolicy();
}
void MainThreadSchedulerImpl::SetInitialVirtualTime(base::Time time) {
main_thread_only().initial_virtual_time = time;
}
void MainThreadSchedulerImpl::SetInitialVirtualTimeOffset(
base::TimeDelta offset) {
main_thread_only().initial_virtual_time_offset = offset;
}
void MainThreadSchedulerImpl::AddVirtualTimeObserver(
VirtualTimeObserver* observer) {
main_thread_only().virtual_time_observers.AddObserver(observer);
}
void MainThreadSchedulerImpl::RemoveVirtualTimeObserver(
VirtualTimeObserver* observer) {
main_thread_only().virtual_time_observers.RemoveObserver(observer);
}
void MainThreadSchedulerImpl::OnVirtualTimeAdvanced() {
for (auto& observer : main_thread_only().virtual_time_observers) {
observer.OnVirtualTimeAdvanced(
virtual_time_domain_->Now() -
main_thread_only().initial_virtual_time_ticks);
}
}
void MainThreadSchedulerImpl::ApplyVirtualTimePolicy() {
switch (main_thread_only().virtual_time_policy) {
case VirtualTimePolicy::kAdvance:
if (virtual_time_domain_) {
virtual_time_domain_->SetMaxVirtualTimeTaskStarvationCount(
main_thread_only().nested_runloop
? 0
: main_thread_only().max_virtual_time_task_starvation_count);
virtual_time_domain_->SetVirtualTimeFence(base::TimeTicks());
}
SetVirtualTimeStopped(false);
break;
case VirtualTimePolicy::kPause:
if (virtual_time_domain_) {
virtual_time_domain_->SetMaxVirtualTimeTaskStarvationCount(0);
virtual_time_domain_->SetVirtualTimeFence(virtual_time_domain_->Now());
}
SetVirtualTimeStopped(true);
break;
case VirtualTimePolicy::kDeterministicLoading:
if (virtual_time_domain_) {
virtual_time_domain_->SetMaxVirtualTimeTaskStarvationCount(
main_thread_only().nested_runloop
? 0
: main_thread_only().max_virtual_time_task_starvation_count);
}
// We pause virtual time while the run loop is nested because that implies
// something modal is happening such as the DevTools debugger pausing the
// system. We also pause while the renderer is waiting for various
// asynchronous things e.g. resource load or navigation.
SetVirtualTimeStopped(main_thread_only().virtual_time_pause_count != 0 ||
main_thread_only().nested_runloop);
break;
}
}
void MainThreadSchedulerImpl::SetMaxVirtualTimeTaskStarvationCount(
int max_task_starvation_count) {
main_thread_only().max_virtual_time_task_starvation_count =
max_task_starvation_count;
ApplyVirtualTimePolicy();
}
void MainThreadSchedulerImpl::SetFreezingWhenBackgroundedEnabled(bool enabled) {
// Note that this will only take effect for the next backgrounded signal.
main_thread_only().freezing_when_backgrounded_enabled = enabled;
}
std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
MainThreadSchedulerImpl::AsValue(base::TimeTicks optional_now) const {
base::AutoLock lock(any_thread_lock_);
return AsValueLocked(optional_now);
}
void MainThreadSchedulerImpl::CreateTraceEventObjectSnapshot() const {
TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler.debug"),
"MainThreadScheduler", this, AsValue(helper_.NowTicks()));
}
void MainThreadSchedulerImpl::CreateTraceEventObjectSnapshotLocked() const {
TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler.debug"),
"MainThreadScheduler", this, AsValueLocked(helper_.NowTicks()));
}
// static
const char* MainThreadSchedulerImpl::ExpensiveTaskPolicyToString(
ExpensiveTaskPolicy expensive_task_policy) {
switch (expensive_task_policy) {
case ExpensiveTaskPolicy::kRun:
return "run";
case ExpensiveTaskPolicy::kBlock:
return "block";
case ExpensiveTaskPolicy::kThrottle:
return "throttle";
default:
NOTREACHED();
return nullptr;
}
}
std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
MainThreadSchedulerImpl::AsValueLocked(base::TimeTicks optional_now) const {
helper_.CheckOnValidThread();
any_thread_lock_.AssertAcquired();
if (optional_now.is_null())
optional_now = helper_.NowTicks();
std::unique_ptr<base::trace_event::TracedValue> state(
new base::trace_event::TracedValue());
state->SetBoolean(
"has_visible_render_widget_with_touch_handler",
main_thread_only().has_visible_render_widget_with_touch_handler);
state->SetString("current_use_case",
UseCaseToString(main_thread_only().current_use_case));
state->SetBoolean("loading_tasks_seem_expensive",
main_thread_only().loading_tasks_seem_expensive);
state->SetBoolean("timer_tasks_seem_expensive",
main_thread_only().timer_tasks_seem_expensive);
state->SetBoolean("begin_frame_not_expected_soon",
main_thread_only().begin_frame_not_expected_soon);
state->SetBoolean(
"compositor_will_send_main_frame_not_expected",
main_thread_only().compositor_will_send_main_frame_not_expected);
state->SetBoolean("touchstart_expected_soon",
main_thread_only().touchstart_expected_soon);
state->SetString("idle_period_state",
IdleHelper::IdlePeriodStateToString(
idle_helper_.SchedulerIdlePeriodState()));
state->SetBoolean("renderer_hidden", main_thread_only().renderer_hidden);
state->SetBoolean("have_seen_a_begin_main_frame",
main_thread_only().have_seen_a_begin_main_frame);
state->SetBoolean("waiting_for_meaningful_paint",
any_thread().waiting_for_meaningful_paint);
state->SetBoolean("have_seen_input_since_navigation",
any_thread().have_seen_input_since_navigation);
state->SetBoolean(
"have_reported_blocking_intervention_in_current_policy",
main_thread_only().have_reported_blocking_intervention_in_current_policy);
state->SetBoolean(
"have_reported_blocking_intervention_since_navigation",
main_thread_only().have_reported_blocking_intervention_since_navigation);
state->SetBoolean("renderer_backgrounded",
main_thread_only().renderer_backgrounded);
state->SetBoolean("keep_active_fetch_or_worker",
main_thread_only().keep_active_fetch_or_worker);
state->SetBoolean("frozen_when_backgrounded",
main_thread_only().frozen_when_backgrounded);
state->SetDouble("now", (optional_now - base::TimeTicks()).InMillisecondsF());
state->SetDouble(
"fling_compositor_escalation_deadline",
(any_thread().fling_compositor_escalation_deadline - base::TimeTicks())
.InMillisecondsF());
state->SetInteger("navigation_task_expected_count",
main_thread_only().navigation_task_expected_count);
state->SetDouble("last_idle_period_end_time",
(any_thread().last_idle_period_end_time - base::TimeTicks())
.InMillisecondsF());
state->SetBoolean("awaiting_touch_start_response",
any_thread().awaiting_touch_start_response);
state->SetBoolean("begin_main_frame_on_critical_path",
any_thread().begin_main_frame_on_critical_path);
state->SetBoolean("last_gesture_was_compositor_driven",
any_thread().last_gesture_was_compositor_driven);
state->SetBoolean("default_gesture_prevented",
any_thread().default_gesture_prevented);
state->SetDouble("expected_loading_task_duration",
main_thread_only()
.loading_task_cost_estimator.expected_task_duration()
.InMillisecondsF());
state->SetDouble("expected_timer_task_duration",
main_thread_only()
.timer_task_cost_estimator.expected_task_duration()
.InMillisecondsF());
state->SetBoolean("is_audio_playing", main_thread_only().is_audio_playing);
state->SetBoolean("virtual_time_stopped",
main_thread_only().virtual_time_stopped);
state->SetDouble("virtual_time_pause_count",
main_thread_only().virtual_time_pause_count);
state->SetString(
"virtual_time_policy",
VirtualTimePolicyToString(main_thread_only().virtual_time_policy));
state->SetBoolean("virtual_time", main_thread_only().use_virtual_time);
state->BeginDictionary("page_schedulers");
for (PageSchedulerImpl* page_scheduler : main_thread_only().page_schedulers) {
state->BeginDictionaryWithCopiedName(PointerToString(page_scheduler));
page_scheduler->AsValueInto(state.get());
state->EndDictionary();
}
state->EndDictionary();
state->BeginDictionary("policy");
main_thread_only().current_policy.AsValueInto(state.get());
state->EndDictionary();
// TODO(skyostil): Can we somehow trace how accurate these estimates were?
state->SetDouble(
"longest_jank_free_task_duration",
main_thread_only().longest_jank_free_task_duration->InMillisecondsF());
state->SetDouble(
"compositor_frame_interval",
main_thread_only().compositor_frame_interval.InMillisecondsF());
state->SetDouble(
"estimated_next_frame_begin",
(main_thread_only().estimated_next_frame_begin - base::TimeTicks())
.InMillisecondsF());
state->SetBoolean("in_idle_period", any_thread().in_idle_period);
state->SetString(
"expensive_task_policy",
ExpensiveTaskPolicyToString(main_thread_only().expensive_task_policy));
any_thread().user_model.AsValueInto(state.get());
render_widget_scheduler_signals_.AsValueInto(state.get());
state->BeginDictionary("task_queue_throttler");
task_queue_throttler_->AsValueInto(state.get(), optional_now);
state->EndDictionary();
return std::move(state);
}
bool MainThreadSchedulerImpl::TaskQueuePolicy::IsQueueEnabled(
MainThreadTaskQueue* task_queue) const {
if (!is_enabled)
return false;
if (is_paused && task_queue->CanBePaused())
return false;
if (is_blocked && task_queue->CanBeDeferred())
return false;
// TODO(panicker): Remove this, as it is redundant as we stop per-frame
// task_queues in WebFrameScheduler
if (is_frozen && task_queue->CanBeFrozen())
return false;
return true;
}
TaskQueue::QueuePriority MainThreadSchedulerImpl::TaskQueuePolicy::GetPriority(
MainThreadTaskQueue* task_queue) const {
base::Optional<TaskQueue::QueuePriority> fixed_priority =
task_queue->FixedPriority();
if (fixed_priority)
return fixed_priority.value();
return task_queue->UsedForImportantTasks() ? TaskQueue::kHighestPriority
: priority;
}
MainThreadSchedulerImpl::TimeDomainType
MainThreadSchedulerImpl::TaskQueuePolicy::GetTimeDomainType(
MainThreadTaskQueue* task_queue) const {
if (use_virtual_time)
return TimeDomainType::kVirtual;
if (is_throttled && task_queue->CanBeThrottled())
return TimeDomainType::kThrottled;
return TimeDomainType::kReal;
}
void MainThreadSchedulerImpl::TaskQueuePolicy::AsValueInto(
base::trace_event::TracedValue* state) const {
state->SetBoolean("is_enabled", is_enabled);
state->SetBoolean("is_paused", is_paused);
state->SetBoolean("is_throttled", is_throttled);
state->SetBoolean("is_blocked", is_blocked);
state->SetBoolean("is_frozen", is_frozen);
state->SetBoolean("use_virtual_time", use_virtual_time);
state->SetString("priority", TaskQueue::PriorityToString(priority));
}
void MainThreadSchedulerImpl::Policy::AsValueInto(
base::trace_event::TracedValue* state) const {
state->BeginDictionary("compositor_queue_policy");
compositor_queue_policy().AsValueInto(state);
state->EndDictionary();
state->BeginDictionary("loading_queue_policy");
loading_queue_policy().AsValueInto(state);
state->EndDictionary();
state->BeginDictionary("timer_queue_policy");
timer_queue_policy().AsValueInto(state);
state->EndDictionary();
state->BeginDictionary("default_queue_policy");
default_queue_policy().AsValueInto(state);
state->EndDictionary();
state->SetString("rail_mode", RAILModeToString(rail_mode()));
state->SetBoolean("should_disable_throttling", should_disable_throttling());
}
void MainThreadSchedulerImpl::OnIdlePeriodStarted() {
base::AutoLock lock(any_thread_lock_);
any_thread().in_idle_period = true;
UpdatePolicyLocked(UpdateType::kMayEarlyOutIfPolicyUnchanged);
}
void MainThreadSchedulerImpl::OnIdlePeriodEnded() {
base::AutoLock lock(any_thread_lock_);
any_thread().last_idle_period_end_time = helper_.NowTicks();
any_thread().in_idle_period = false;
UpdatePolicyLocked(UpdateType::kMayEarlyOutIfPolicyUnchanged);
}
void MainThreadSchedulerImpl::OnPendingTasksChanged(bool has_tasks) {
if (has_tasks ==
main_thread_only().compositor_will_send_main_frame_not_expected.get())
return;
// Dispatch RequestBeginMainFrameNotExpectedSoon notifications asynchronously.
// This is needed because idle task can be posted (and OnPendingTasksChanged
// called) at any moment, including in the middle of allocating an object,
// when state is not consistent. Posting a task to dispatch notifications
// minimizes the amount of code that runs and sees an inconsistent state .
control_task_queue_->PostTask(
FROM_HERE,
base::BindOnce(
&MainThreadSchedulerImpl::DispatchRequestBeginMainFrameNotExpected,
weak_factory_.GetWeakPtr(), has_tasks));
}
void MainThreadSchedulerImpl::DispatchRequestBeginMainFrameNotExpected(
bool has_tasks) {
if (has_tasks ==
main_thread_only().compositor_will_send_main_frame_not_expected.get())
return;
main_thread_only().compositor_will_send_main_frame_not_expected = has_tasks;
TRACE_EVENT1(
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::DispatchRequestBeginMainFrameNotExpected",
"has_tasks", has_tasks);
for (PageSchedulerImpl* page_scheduler : main_thread_only().page_schedulers) {
page_scheduler->RequestBeginMainFrameNotExpected(has_tasks);
}
}
void MainThreadSchedulerImpl::AddPendingNavigation(NavigatingFrameType type) {
helper_.CheckOnValidThread();
if (type == NavigatingFrameType::kMainFrame) {
main_thread_only().navigation_task_expected_count++;
UpdatePolicy();
}
}
void MainThreadSchedulerImpl::RemovePendingNavigation(
NavigatingFrameType type) {
helper_.CheckOnValidThread();
DCHECK_GT(main_thread_only().navigation_task_expected_count, 0);
if (type == NavigatingFrameType::kMainFrame &&
main_thread_only().navigation_task_expected_count > 0) {
main_thread_only().navigation_task_expected_count--;
UpdatePolicy();
}
}
std::unique_ptr<base::SingleSampleMetric>
MainThreadSchedulerImpl::CreateMaxQueueingTimeMetric() {
return base::SingleSampleMetricsFactory::Get()->CreateCustomCountsMetric(
"RendererScheduler.MaxQueueingTime", 1, 10000, 50);
}
void MainThreadSchedulerImpl::DidStartProvisionalLoad(bool is_main_frame) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::DidStartProvisionalLoad");
if (is_main_frame) {
base::AutoLock lock(any_thread_lock_);
ResetForNavigationLocked();
}
}
void MainThreadSchedulerImpl::DidCommitProvisionalLoad(
bool is_web_history_inert_commit,
bool is_reload,
bool is_main_frame) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::DidCommitProvisionalLoad");
// Initialize |max_queueing_time_metric| lazily so that
// |SingleSampleMetricsFactory::SetFactory()| is called before
// |SingleSampleMetricsFactory::Get()|
if (!main_thread_only().max_queueing_time_metric) {
main_thread_only().max_queueing_time_metric = CreateMaxQueueingTimeMetric();
}
main_thread_only().max_queueing_time_metric.reset();
main_thread_only().max_queueing_time = base::TimeDelta();
main_thread_only().has_navigated = true;
// If this either isn't a history inert commit or it's a reload then we must
// reset the task cost estimators.
if (is_main_frame && (!is_web_history_inert_commit || is_reload)) {
base::AutoLock lock(any_thread_lock_);
ResetForNavigationLocked();
}
}
void MainThreadSchedulerImpl::OnFirstMeaningfulPaint() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::OnFirstMeaningfulPaint");
base::AutoLock lock(any_thread_lock_);
any_thread().waiting_for_meaningful_paint = false;
UpdatePolicyLocked(UpdateType::kMayEarlyOutIfPolicyUnchanged);
}
void MainThreadSchedulerImpl::ResetForNavigationLocked() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"MainThreadSchedulerImpl::ResetForNavigationLocked");
helper_.CheckOnValidThread();
any_thread_lock_.AssertAcquired();
any_thread().user_model.Reset(helper_.NowTicks());
any_thread().have_seen_a_potentially_blocking_gesture = false;
any_thread().waiting_for_meaningful_paint = true;
any_thread().have_seen_input_since_navigation = false;
main_thread_only().loading_task_cost_estimator.Clear();
main_thread_only().timer_task_cost_estimator.Clear();
main_thread_only().idle_time_estimator.Clear();
main_thread_only().have_seen_a_begin_main_frame = false;
main_thread_only().have_reported_blocking_intervention_since_navigation =
false;
for (PageSchedulerImpl* page_scheduler : main_thread_only().page_schedulers) {
page_scheduler->OnNavigation();
}
UpdatePolicyLocked(UpdateType::kMayEarlyOutIfPolicyUnchanged);
UMA_HISTOGRAM_COUNTS_100("RendererScheduler.WebViewsPerScheduler",
main_thread_only().page_schedulers.size());
size_t frame_count = 0;
for (PageSchedulerImpl* page_scheduler : main_thread_only().page_schedulers) {
frame_count += page_scheduler->FrameCount();
}
UMA_HISTOGRAM_COUNTS_100("RendererScheduler.WebFramesPerScheduler",
frame_count);
}
void MainThreadSchedulerImpl::SetTopLevelBlameContext(
base::trace_event::BlameContext* blame_context) {
// Any task that runs in the default task runners belongs to the context of
// all frames (as opposed to a particular frame). Note that the task itself
// may still enter a more specific blame context if necessary.
//
// Per-frame task runners (loading, timers, etc.) are configured with a more
// specific blame context by FrameSchedulerImpl.
//
// TODO(altimin): automatically enter top-level for all task queues associated
// with renderer scheduler which do not have a corresponding frame.
control_task_queue_->SetBlameContext(blame_context);
DefaultTaskQueue()->SetBlameContext(blame_context);
compositor_task_queue_->SetBlameContext(blame_context);
idle_helper_.IdleTaskRunner()->SetBlameContext(blame_context);
v8_task_queue_->SetBlameContext(blame_context);
ipc_task_queue_->SetBlameContext(blame_context);
}
void MainThreadSchedulerImpl::SetRAILModeObserver(RAILModeObserver* observer) {
main_thread_only().rail_mode_observer = observer;
}
void MainThreadSchedulerImpl::SetRendererProcessType(RendererProcessType type) {
main_thread_only().process_type = type;
}
WebScopedVirtualTimePauser
MainThreadSchedulerImpl::CreateWebScopedVirtualTimePauser(
const char* name,
WebScopedVirtualTimePauser::VirtualTaskDuration duration) {
return WebScopedVirtualTimePauser(this, duration,
WebString(WTF::String(name)));
}
void MainThreadSchedulerImpl::RegisterTimeDomain(TimeDomain* time_domain) {
helper_.RegisterTimeDomain(time_domain);
}
void MainThreadSchedulerImpl::UnregisterTimeDomain(TimeDomain* time_domain) {
helper_.UnregisterTimeDomain(time_domain);
}
const base::TickClock* MainThreadSchedulerImpl::tick_clock() const {
return helper_.GetClock();
}
void MainThreadSchedulerImpl::AddPageScheduler(
PageSchedulerImpl* page_scheduler) {
main_thread_only().page_schedulers.insert(page_scheduler);
}
void MainThreadSchedulerImpl::RemovePageScheduler(
PageSchedulerImpl* page_scheduler) {
DCHECK(main_thread_only().page_schedulers.find(page_scheduler) !=
main_thread_only().page_schedulers.end());
main_thread_only().page_schedulers.erase(page_scheduler);
}
void MainThreadSchedulerImpl::BroadcastIntervention(
const std::string& message) {
helper_.CheckOnValidThread();
for (auto* page_scheduler : main_thread_only().page_schedulers)
page_scheduler->ReportIntervention(message);
}
void MainThreadSchedulerImpl::OnTaskStarted(MainThreadTaskQueue* queue,
const TaskQueue::Task& task,
base::TimeTicks start) {
main_thread_only().current_task_start_time = start;
queueing_time_estimator_.OnTopLevelTaskStarted(start, queue);
main_thread_only().task_description_for_tracing = TaskDescriptionForTracing{
static_cast<TaskType>(task.task_type()),
queue
? base::Optional<MainThreadTaskQueue::QueueType>(queue->queue_type())
: base::nullopt};
}
void MainThreadSchedulerImpl::OnTaskCompleted(
MainThreadTaskQueue* queue,
const TaskQueue::Task& task,
base::TimeTicks start,
base::TimeTicks end,
base::Optional<base::TimeDelta> thread_time) {
DCHECK_LE(start, end);
queueing_time_estimator_.OnTopLevelTaskCompleted(end);
if (queue)
task_queue_throttler()->OnTaskRunTimeReported(queue, start, end);
// TODO(altimin): Per-page metrics should also be considered.
main_thread_only().metrics_helper.RecordTaskMetrics(queue, task, start, end,
thread_time);
main_thread_only().task_description_for_tracing = base::nullopt;
RecordTaskUkm(queue, task, start, end, thread_time);
}
void MainThreadSchedulerImpl::RecordTaskUkm(
MainThreadTaskQueue* queue,
const TaskQueue::Task& task,
base::TimeTicks start,
base::TimeTicks end,
base::Optional<base::TimeDelta> thread_time) {
if (!ShouldRecordTaskUkm())
return;
if (queue && queue->GetFrameScheduler()) {
RecordTaskUkmImpl(queue, task, start, end, thread_time,
static_cast<PageSchedulerImpl*>(
queue->GetFrameScheduler()->GetPageScheduler()),
1);
return;
}
for (PageSchedulerImpl* page_scheduler : main_thread_only().page_schedulers) {
RecordTaskUkmImpl(queue, task, start, end, thread_time, page_scheduler,
main_thread_only().page_schedulers.size());
}
}
void MainThreadSchedulerImpl::RecordTaskUkmImpl(
MainThreadTaskQueue* queue,
const TaskQueue::Task& task,
base::TimeTicks start,
base::TimeTicks end,
base::Optional<base::TimeDelta> thread_time,
PageSchedulerImpl* page_scheduler,
size_t page_schedulers_to_attribute) {
// Skip tasks which have deleted the page scheduler.
if (!page_scheduler)
return;
ukm::UkmRecorder* ukm_recorder = page_scheduler->GetUkmRecorder();
// OOPIFs are not supported.
if (!ukm_recorder)
return;
ukm::builders::RendererSchedulerTask builder(
page_scheduler->GetUkmSourceId());
builder.SetPageSchedulers(page_schedulers_to_attribute);
builder.SetRendererBackgrounded(main_thread_only().renderer_backgrounded);
builder.SetRendererHidden(main_thread_only().renderer_hidden);
builder.SetRendererAudible(main_thread_only().is_audio_playing);
builder.SetUseCase(
static_cast<int>(main_thread_only().current_use_case.get()));
builder.SetTaskType(task.task_type());
builder.SetQueueType(static_cast<int>(
queue ? queue->queue_type() : MainThreadTaskQueue::QueueType::kDetached));
builder.SetFrameStatus(static_cast<int>(
GetFrameStatus(queue ? queue->GetFrameScheduler() : nullptr)));
builder.SetTaskDuration((end - start).InMicroseconds());
if (thread_time) {
builder.SetTaskDuration(thread_time->InMicroseconds());
}
builder.Record(ukm_recorder);
}
void MainThreadSchedulerImpl::OnBeginNestedRunLoop() {
queueing_time_estimator_.OnBeginNestedRunLoop();
main_thread_only().nested_runloop = true;
ApplyVirtualTimePolicy();
}
void MainThreadSchedulerImpl::OnExitNestedRunLoop() {
main_thread_only().nested_runloop = false;
ApplyVirtualTimePolicy();
}
void MainThreadSchedulerImpl::AddTaskTimeObserver(
TaskTimeObserver* task_time_observer) {
helper_.AddTaskTimeObserver(task_time_observer);
}
void MainThreadSchedulerImpl::RemoveTaskTimeObserver(
TaskTimeObserver* task_time_observer) {
helper_.RemoveTaskTimeObserver(task_time_observer);
}
bool MainThreadSchedulerImpl::ContainsLocalMainFrame() {
for (auto* page_scheduler : main_thread_only().page_schedulers) {
if (page_scheduler->IsMainFrameLocal())
return true;
}
return false;
}
void MainThreadSchedulerImpl::OnQueueingTimeForWindowEstimated(
base::TimeDelta queueing_time,
bool is_disjoint_window) {
if (main_thread_only().has_navigated) {
if (main_thread_only().max_queueing_time < queueing_time) {
if (!main_thread_only().max_queueing_time_metric) {
main_thread_only().max_queueing_time_metric =
CreateMaxQueueingTimeMetric();
}
main_thread_only().max_queueing_time_metric->SetSample(
queueing_time.InMilliseconds());
main_thread_only().max_queueing_time = queueing_time;
}
}
if (!is_disjoint_window || !ContainsLocalMainFrame())
return;
UMA_HISTOGRAM_TIMES("RendererScheduler.ExpectedTaskQueueingDuration",
queueing_time);
UMA_HISTOGRAM_CUSTOM_COUNTS(
"RendererScheduler.ExpectedTaskQueueingDuration3",
queueing_time.InMicroseconds(), kMinExpectedQueueingTimeBucket,
kMaxExpectedQueueingTimeBucket, kNumberExpectedQueueingTimeBuckets);
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"estimated_queueing_time_for_window",
queueing_time.InMillisecondsF());
if (::resource_coordinator::IsResourceCoordinatorEnabled()) {
RendererResourceCoordinator::Get().SetExpectedTaskQueueingDuration(
queueing_time);
}
}
void MainThreadSchedulerImpl::OnReportFineGrainedExpectedQueueingTime(
const char* split_description,
base::TimeDelta queueing_time) {
if (!ContainsLocalMainFrame())
return;
base::UmaHistogramCustomCounts(
split_description, queueing_time.InMicroseconds(),
kMinExpectedQueueingTimeBucket, kMaxExpectedQueueingTimeBucket,
kNumberExpectedQueueingTimeBuckets);
}
AutoAdvancingVirtualTimeDomain*
MainThreadSchedulerImpl::GetVirtualTimeDomain() {
return virtual_time_domain_.get();
}
void MainThreadSchedulerImpl::AddQueueToWakeUpBudgetPool(
MainThreadTaskQueue* queue) {
if (!main_thread_only().wake_up_budget_pool) {
main_thread_only().wake_up_budget_pool =
task_queue_throttler()->CreateWakeUpBudgetPool("renderer_wake_up_pool");
main_thread_only().wake_up_budget_pool->SetWakeUpRate(1);
main_thread_only().wake_up_budget_pool->SetWakeUpDuration(
GetWakeUpDuration());
}
main_thread_only().wake_up_budget_pool->AddQueue(tick_clock()->NowTicks(),
queue);
}
TimeDomain* MainThreadSchedulerImpl::GetActiveTimeDomain() {
if (main_thread_only().use_virtual_time) {
return GetVirtualTimeDomain();
} else {
return real_time_domain();
}
}
void MainThreadSchedulerImpl::OnTraceLogEnabled() {
CreateTraceEventObjectSnapshot();
tracing_controller_.OnTraceLogEnabled();
for (PageSchedulerImpl* page_scheduler : main_thread_only().page_schedulers) {
page_scheduler->OnTraceLogEnabled();
}
}
void MainThreadSchedulerImpl::OnTraceLogDisabled() {}
base::WeakPtr<MainThreadSchedulerImpl> MainThreadSchedulerImpl::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
bool MainThreadSchedulerImpl::IsAudioPlaying() const {
return main_thread_only().is_audio_playing;
}
bool MainThreadSchedulerImpl::ShouldRecordTaskUkm() {
// This function returns true with probability of kSamplingRateForTaskUkm.
return main_thread_only().uniform_distribution(
main_thread_only().random_generator) < kSamplingRateForTaskUkm;
}
// static
const char* MainThreadSchedulerImpl::UseCaseToString(UseCase use_case) {
switch (use_case) {
case UseCase::kNone:
return "none";
case UseCase::kCompositorGesture:
return "compositor_gesture";
case UseCase::kMainThreadCustomInputHandling:
return "main_thread_custom_input_handling";
case UseCase::kSynchronizedGesture:
return "synchronized_gesture";
case UseCase::kTouchstart:
return "touchstart";
case UseCase::kLoading:
return "loading";
case UseCase::kMainThreadGesture:
return "main_thread_gesture";
default:
NOTREACHED();
return nullptr;
}
}
// static
const char* MainThreadSchedulerImpl::RAILModeToString(v8::RAILMode rail_mode) {
switch (rail_mode) {
case v8::PERFORMANCE_RESPONSE:
return "response";
case v8::PERFORMANCE_ANIMATION:
return "animation";
case v8::PERFORMANCE_IDLE:
return "idle";
case v8::PERFORMANCE_LOAD:
return "load";
default:
NOTREACHED();
return nullptr;
}
}
// static
const char* MainThreadSchedulerImpl::TimeDomainTypeToString(
TimeDomainType domain_type) {
switch (domain_type) {
case TimeDomainType::kReal:
return "real";
case TimeDomainType::kThrottled:
return "throttled";
case TimeDomainType::kVirtual:
return "virtual";
default:
NOTREACHED();
return nullptr;
}
}
// static
const char* MainThreadSchedulerImpl::VirtualTimePolicyToString(
VirtualTimePolicy virtual_time_policy) {
switch (virtual_time_policy) {
case VirtualTimePolicy::kAdvance:
return "ADVANCE";
case VirtualTimePolicy::kPause:
return "PAUSE";
case VirtualTimePolicy::kDeterministicLoading:
return "DETERMINISTIC_LOADING";
default:
NOTREACHED();
return nullptr;
}
}
} // namespace scheduler
} // namespace blink