blob: 20fda057c08b5b10a22757167416f7787408101f [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/worker/worker_thread_scheduler.h"
#include <memory>
#include "base/bind.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequence_manager/sequence_manager.h"
#include "base/task/sequence_manager/task_queue.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/scheduler/common/features.h"
#include "third_party/blink/renderer/platform/scheduler/common/process_state.h"
#include "third_party/blink/renderer/platform/scheduler/common/throttling/task_queue_throttler.h"
#include "third_party/blink/renderer/platform/scheduler/public/worker_scheduler.h"
#include "third_party/blink/renderer/platform/scheduler/worker/non_main_thread_scheduler_helper.h"
#include "third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_proxy.h"
#include "third_party/blink/renderer/platform/scheduler/worker/worker_thread_scheduler.h"
namespace blink {
namespace scheduler {
using base::sequence_manager::TaskQueue;
namespace {
// Workers could be short-lived, set a shorter interval than
// the renderer thread.
constexpr base::TimeDelta kUnspecifiedWorkerThreadLoadTrackerReportingInterval =
base::TimeDelta::FromSeconds(1);
// Worker throttling trial
const char kWorkerThrottlingTrial[] = "BlinkSchedulerDedicatedWorkerThrottling";
const char kWorkerThrottlingMaxBudgetParam[] = "max_budget_ms";
const char kWorkerThrottlingRecoveryRateParam[] = "recovery_rate";
const char kWorkerThrottlingMaxDelayParam[] = "max_delay_ms";
constexpr base::TimeDelta kDefaultMaxBudget = base::TimeDelta::FromSeconds(1);
constexpr double kDefaultRecoveryRate = 0.01;
constexpr base::TimeDelta kDefaultMaxThrottlingDelay =
base::TimeDelta::FromSeconds(60);
void ReportWorkerTaskLoad(base::TimeTicks time, double load) {
int load_percentage = static_cast<int>(load * 100);
DCHECK_LE(load_percentage, 100);
// TODO(kinuko): Maybe we also want to separately log when the associated
// tab is in foreground and when not.
UMA_HISTOGRAM_PERCENTAGE("WorkerScheduler.WorkerThreadLoad", load_percentage);
}
base::Optional<base::TimeDelta> GetMaxBudgetLevel() {
int max_budget_level_ms;
if (!base::StringToInt(
base::GetFieldTrialParamValue(kWorkerThrottlingTrial,
kWorkerThrottlingMaxBudgetParam),
&max_budget_level_ms)) {
return kDefaultMaxBudget;
}
if (max_budget_level_ms < 0)
return base::nullopt;
return base::TimeDelta::FromMilliseconds(max_budget_level_ms);
}
double GetBudgetRecoveryRate() {
double recovery_rate;
if (!base::StringToDouble(
base::GetFieldTrialParamValue(kWorkerThrottlingTrial,
kWorkerThrottlingRecoveryRateParam),
&recovery_rate)) {
return kDefaultRecoveryRate;
}
return recovery_rate;
}
base::Optional<base::TimeDelta> GetMaxThrottlingDelay() {
int max_throttling_delay_ms;
if (!base::StringToInt(
base::GetFieldTrialParamValue(kWorkerThrottlingTrial,
kWorkerThrottlingMaxDelayParam),
&max_throttling_delay_ms)) {
return kDefaultMaxThrottlingDelay;
}
if (max_throttling_delay_ms < 0)
return base::nullopt;
return base::TimeDelta::FromMilliseconds(max_throttling_delay_ms);
}
} // namespace
WorkerThreadScheduler::WorkerThreadScheduler(
WebThreadType thread_type,
std::unique_ptr<base::sequence_manager::SequenceManager> sequence_manager,
WorkerSchedulerProxy* proxy)
: NonMainThreadSchedulerImpl(std::move(sequence_manager),
TaskType::kWorkerThreadTaskQueueDefault),
thread_type_(thread_type),
idle_helper_(helper(),
this,
"WorkerSchedulerIdlePeriod",
base::TimeDelta::FromMilliseconds(300),
helper()->NewTaskQueue(TaskQueue::Spec("worker_idle_tq"))),
load_tracker_(helper()->NowTicks(),
base::BindRepeating(&ReportWorkerTaskLoad),
kUnspecifiedWorkerThreadLoadTrackerReportingInterval),
lifecycle_state_(proxy ? proxy->lifecycle_state()
: SchedulingLifecycleState::kNotThrottled),
worker_metrics_helper_(thread_type, helper()->HasCPUTimingForEachTask()),
initial_frame_status_(proxy ? proxy->initial_frame_status()
: FrameStatus::kNone),
ukm_source_id_(proxy ? proxy->ukm_source_id() : ukm::kInvalidSourceId),
connector_(proxy ? proxy->TakeConnector() : nullptr) {
if (connector_) {
ukm_recorder_ = ukm::MojoUkmRecorder::Create(connector_.get());
}
thread_start_time_ = helper()->NowTicks();
load_tracker_.Resume(thread_start_time_);
helper()->AddTaskTimeObserver(this);
if (proxy && proxy->parent_frame_type())
worker_metrics_helper_.SetParentFrameType(*proxy->parent_frame_type());
if (thread_type == WebThreadType::kDedicatedWorkerThread &&
base::FeatureList::IsEnabled(kDedicatedWorkerThrottling)) {
CreateTaskQueueThrottler();
}
TRACE_EVENT_OBJECT_CREATED_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("worker.scheduler"), "WorkerScheduler", this);
}
WorkerThreadScheduler::~WorkerThreadScheduler() {
TRACE_EVENT_OBJECT_DELETED_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("worker.scheduler"), "WorkerScheduler", this);
helper()->RemoveTaskTimeObserver(this);
DCHECK(worker_schedulers_.empty());
}
scoped_refptr<SingleThreadIdleTaskRunner>
WorkerThreadScheduler::IdleTaskRunner() {
DCHECK(initialized_);
return idle_helper_.IdleTaskRunner();
}
scoped_refptr<base::SingleThreadTaskRunner>
WorkerThreadScheduler::V8TaskRunner() {
DCHECK(initialized_);
return v8_task_runner_;
}
scoped_refptr<base::SingleThreadTaskRunner>
WorkerThreadScheduler::CompositorTaskRunner() {
DCHECK(initialized_);
return compositor_task_runner_;
}
scoped_refptr<base::SingleThreadTaskRunner>
WorkerThreadScheduler::IPCTaskRunner() {
NOTREACHED() << "Not implemented";
return nullptr;
}
bool WorkerThreadScheduler::CanExceedIdleDeadlineIfRequired() const {
DCHECK(initialized_);
return idle_helper_.CanExceedIdleDeadlineIfRequired();
}
bool WorkerThreadScheduler::ShouldYieldForHighPriorityWork() {
// We don't consider any work as being high priority on workers.
return false;
}
void WorkerThreadScheduler::AddTaskObserver(
base::MessageLoop::TaskObserver* task_observer) {
DCHECK(initialized_);
helper()->AddTaskObserver(task_observer);
}
void WorkerThreadScheduler::RemoveTaskObserver(
base::MessageLoop::TaskObserver* task_observer) {
DCHECK(initialized_);
helper()->RemoveTaskObserver(task_observer);
}
void WorkerThreadScheduler::Shutdown() {
DCHECK(initialized_);
load_tracker_.RecordIdle(helper()->NowTicks());
base::TimeTicks end_time = helper()->NowTicks();
base::TimeDelta delta = end_time - thread_start_time_;
// The lifetime could be radically different for different workers,
// some workers could be short-lived (but last at least 1 sec in
// Service Workers case) or could be around as long as the tab is open.
UMA_HISTOGRAM_CUSTOM_TIMES(
"WorkerThread.Runtime", delta, base::TimeDelta::FromSeconds(1),
base::TimeDelta::FromDays(1), 50 /* bucket count */);
task_queue_throttler_.reset();
idle_helper_.Shutdown();
helper()->Shutdown();
}
scoped_refptr<NonMainThreadTaskQueue>
WorkerThreadScheduler::DefaultTaskQueue() {
DCHECK(initialized_);
return helper()->DefaultNonMainThreadTaskQueue();
}
void WorkerThreadScheduler::InitImpl() {
initialized_ = true;
idle_helper_.EnableLongIdlePeriod();
v8_task_runner_ =
DefaultTaskQueue()->CreateTaskRunner(TaskType::kWorkerThreadTaskQueueV8);
compositor_task_runner_ = DefaultTaskQueue()->CreateTaskRunner(
TaskType::kWorkerThreadTaskQueueCompositor);
}
void WorkerThreadScheduler::OnTaskCompleted(
NonMainThreadTaskQueue* task_queue,
const base::sequence_manager::Task& task,
const TaskQueue::TaskTiming& task_timing) {
worker_metrics_helper_.RecordTaskMetrics(task_queue, task, task_timing);
if (task_queue_throttler_) {
task_queue_throttler_->OnTaskRunTimeReported(
task_queue, task_timing.start_time(), task_timing.end_time());
}
RecordTaskUkm(task_queue, task, task_timing);
}
SchedulerHelper* WorkerThreadScheduler::GetSchedulerHelperForTesting() {
return helper();
}
bool WorkerThreadScheduler::CanEnterLongIdlePeriod(base::TimeTicks,
base::TimeDelta*) {
return true;
}
base::TimeTicks WorkerThreadScheduler::CurrentIdleTaskDeadlineForTesting()
const {
return idle_helper_.CurrentIdleTaskDeadline();
}
void WorkerThreadScheduler::WillProcessTask(base::TimeTicks start_time) {}
void WorkerThreadScheduler::DidProcessTask(base::TimeTicks start_time,
base::TimeTicks end_time) {
load_tracker_.RecordTaskTime(start_time, end_time);
}
void WorkerThreadScheduler::OnLifecycleStateChanged(
SchedulingLifecycleState lifecycle_state) {
if (lifecycle_state_ == lifecycle_state)
return;
lifecycle_state_ = lifecycle_state;
for (WorkerScheduler* worker_scheduler : worker_schedulers_)
worker_scheduler->OnLifecycleStateChanged(lifecycle_state);
}
void WorkerThreadScheduler::RegisterWorkerScheduler(
WorkerScheduler* worker_scheduler) {
worker_schedulers_.insert(worker_scheduler);
worker_scheduler->OnLifecycleStateChanged(lifecycle_state_);
}
void WorkerThreadScheduler::UnregisterWorkerScheduler(
WorkerScheduler* worker_scheduler) {
DCHECK(worker_schedulers_.find(worker_scheduler) != worker_schedulers_.end());
worker_schedulers_.erase(worker_scheduler);
}
scoped_refptr<NonMainThreadTaskQueue>
WorkerThreadScheduler::ControlTaskQueue() {
return helper()->ControlNonMainThreadTaskQueue();
}
void WorkerThreadScheduler::CreateTaskQueueThrottler() {
if (task_queue_throttler_)
return;
task_queue_throttler_ = std::make_unique<TaskQueueThrottler>(
this, &traceable_variable_controller_);
wake_up_budget_pool_ =
task_queue_throttler_->CreateWakeUpBudgetPool("worker_wake_up_pool");
cpu_time_budget_pool_ =
task_queue_throttler_->CreateCPUTimeBudgetPool("worker_cpu_time_pool");
base::TimeTicks now = GetTickClock()->NowTicks();
cpu_time_budget_pool_->SetMaxBudgetLevel(now, GetMaxBudgetLevel());
cpu_time_budget_pool_->SetTimeBudgetRecoveryRate(now,
GetBudgetRecoveryRate());
cpu_time_budget_pool_->SetMaxThrottlingDelay(now, GetMaxThrottlingDelay());
}
void WorkerThreadScheduler::RecordTaskUkm(
NonMainThreadTaskQueue* worker_task_queue,
const base::sequence_manager::Task& task,
const base::sequence_manager::TaskQueue::TaskTiming& task_timing) {
if (!ShouldRecordTaskUkm(task_timing.has_thread_time()))
return;
ukm::builders::RendererSchedulerTask builder(ukm_source_id_);
builder.SetVersion(kUkmMetricVersion);
builder.SetThreadType(static_cast<int>(thread_type_));
builder.SetRendererBackgrounded(
internal::ProcessState::Get()->is_process_backgrounded);
builder.SetTaskType(task.task_type);
builder.SetFrameStatus(static_cast<int>(initial_frame_status_));
builder.SetTaskDuration(task_timing.wall_duration().InMicroseconds());
if (task_timing.has_thread_time())
builder.SetTaskCPUDuration(task_timing.thread_duration().InMicroseconds());
builder.Record(ukm_recorder_.get());
}
void WorkerThreadScheduler::SetUkmRecorderForTest(
std::unique_ptr<ukm::UkmRecorder> ukm_recorder) {
ukm_recorder_ = std::move(ukm_recorder);
}
void WorkerThreadScheduler::SetCPUTimeBudgetPoolForTesting(
CPUTimeBudgetPool* cpu_time_budget_pool) {
cpu_time_budget_pool_ = cpu_time_budget_pool;
}
std::unordered_set<WorkerScheduler*>&
WorkerThreadScheduler::GetWorkerSchedulersForTesting() {
return worker_schedulers_;
}
} // namespace scheduler
} // namespace blink