blob: 6aaf27be50a0c1ab2d6838f6d2381292a4831e81 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/power_scheduler/power_scheduler.h"
#include "base/command_line.h"
#include "base/cpu.h"
#include "base/cpu_affinity_posix.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/process/process_handle.h"
#include "base/task/current_thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/typed_macros.h"
namespace {
perfetto::StaticString TraceEventNameForAffinityMode(
base::CpuAffinityMode affinity) {
if (affinity == base::CpuAffinityMode::kDefault) {
return "ApplyCpuAffinityModeDefault";
} else if (affinity == base::CpuAffinityMode::kLittleCoresOnly) {
return "ApplyCpuAffinityModeLittleCoresOnly";
}
return "ApplyCpuAffinityModeUnknown";
}
void ApplyProcessCpuAffinityMode(base::CpuAffinityMode affinity) {
TRACE_EVENT("power", TraceEventNameForAffinityMode(affinity));
// Restrict affinity of all existing threads of the current process. The
// affinity is inherited by any subsequently created thread. Other threads may
// already exist even during early startup (e.g. Java threads like
// RenderThread), so setting the affinity only for the current thread is not
// enough here.
bool success = base::SetProcessCpuAffinityMode(
base::GetCurrentProcessHandle(), affinity);
base::UmaHistogramBoolean(
"Power.CpuAffinityExperiments.ProcessAffinityUpdateSuccess", success);
}
bool CpuAffinityApplicable() {
// For now, affinity modes only have an effect on big.LITTLE architectures.
return base::HasBigCpuCores();
}
} // anonymous namespace
namespace power_scheduler {
PowerScheduler::PowerScheduler() = default;
PowerScheduler::~PowerScheduler() = default;
// static
PowerScheduler* PowerScheduler::GetInstance() {
static base::NoDestructor<PowerScheduler> instance;
return instance.get();
}
void PowerScheduler::WillProcessTask(const base::PendingTask& pending_task,
bool was_blocked_or_low_priority) {}
void PowerScheduler::DidProcessTask(const base::PendingTask& pending_task) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
++task_counter_;
if (task_counter_ == kUpdateAfterEveryNTasks) {
EnforceCpuAffinity();
task_counter_ = 0;
}
}
void PowerScheduler::OnPowerModeChanged(power_scheduler::PowerMode old_mode,
power_scheduler::PowerMode new_mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
current_power_mode_ = new_mode;
ApplyPolicy();
}
void PowerScheduler::Setup() {
// The setup should be called once from the main thread. Subsequent calls
// from other threads should be ignored.
if (did_call_setup_)
return;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SetupPolicy();
did_call_setup_ = true;
}
void PowerScheduler::SetPolicy(SchedulingPolicy policy) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!CpuAffinityApplicable())
return;
current_policy_ = policy;
// Set up the power affinity observer and apply the policy if it's already
// possible. Otherwise it will be set up after thread initialization via
// Setup() (see app/content_main_runner_impl.cc and child/child_process.cc).
if (base::CurrentThread::IsSet()) {
SetupPolicy();
}
}
void PowerScheduler::SetupPolicy() {
DCHECK(power_scheduler::PowerModeArbiter::GetInstance());
DCHECK(base::CurrentThread::IsSet());
// Reset the power mode in case it contains an obsolete value.
current_power_mode_ = power_scheduler::PowerMode::kMaxValue;
if (current_policy_ == SchedulingPolicy::kThrottleIdle &&
!power_observer_registered_) {
power_scheduler::PowerModeArbiter::GetInstance()->AddObserver(this);
power_observer_registered_ = true;
} else if (current_policy_ != SchedulingPolicy::kThrottleIdle &&
power_observer_registered_) {
power_scheduler::PowerModeArbiter::GetInstance()->RemoveObserver(this);
power_observer_registered_ = false;
}
ApplyPolicy();
}
void PowerScheduler::ApplyPolicy() {
auto new_affinity = base::CpuAffinityMode::kDefault;
if (current_policy_ == SchedulingPolicy::kLittleCoresOnly ||
(current_policy_ == SchedulingPolicy::kThrottleIdle &&
(current_power_mode_ == power_scheduler::PowerMode::kIdle ||
current_power_mode_ == power_scheduler::PowerMode::kBackground))) {
new_affinity = base::CpuAffinityMode::kLittleCoresOnly;
}
if (new_affinity != enforced_affinity_) {
enforced_affinity_ = new_affinity;
EnforceCpuAffinity();
}
}
void PowerScheduler::EnforceCpuAffinity() {
if (enforced_affinity_ == base::CpuAffinityMode::kLittleCoresOnly &&
!task_observer_registered_) {
base::CurrentThread::Get()->AddTaskObserver(this);
task_observer_registered_ = true;
} else if (enforced_affinity_ == base::CpuAffinityMode::kDefault &&
task_observer_registered_) {
// We don't have to enforce the default affinity.
base::CurrentThread::Get()->RemoveTaskObserver(this);
task_observer_registered_ = false;
}
if (enforced_affinity_ != base::CurrentThreadCpuAffinityMode())
ApplyProcessCpuAffinityMode(enforced_affinity_);
}
} // namespace power_scheduler