blob: db926ae392c77517fba5ef995ea6a0fdb68e7024 [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_mode_arbiter.h"
#include <map>
#include <memory>
#include "base/no_destructor.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_observer.h"
#include "base/synchronization/lock.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_log.h"
#include "components/power_scheduler/power_mode.h"
#include "components/power_scheduler/power_mode_voter.h"
#include "components/power_scheduler/traced_power_mode.h"
namespace power_scheduler {
using ObserverList = base::ObserverListThreadSafe<PowerModeArbiter::Observer>;
namespace {
class TraceObserver : public PowerModeArbiter::Observer {
public:
~TraceObserver() override = default;
void OnPowerModeChanged(PowerMode old_mode, PowerMode new_mode) override {}
};
} // namespace
// Created and owned by the arbiter on thread pool initialization because there
// has to be exactly one per process, and //base can't depend on the
// power_scheduler component.
class PowerModeArbiter::ChargingPowerModeVoter : base::PowerStateObserver {
public:
ChargingPowerModeVoter()
: charging_voter_(PowerModeArbiter::GetInstance()->NewVoter(
"PowerModeVoter.Charging")) {
const bool on_battery =
base::PowerMonitor::AddPowerStateObserverAndReturnOnBatteryState(this);
if (base::PowerMonitor::IsInitialized())
OnPowerStateChange(on_battery);
}
~ChargingPowerModeVoter() override {
base::PowerMonitor::RemovePowerStateObserver(this);
}
void OnPowerStateChange(bool on_battery_power) override {
charging_voter_->VoteFor(on_battery_power ? PowerMode::kIdle
: PowerMode::kCharging);
}
private:
std::unique_ptr<PowerModeVoter> charging_voter_;
};
PowerModeArbiter::Observer::~Observer() = default;
constexpr base::TimeDelta PowerModeArbiter::kResetVoteTimeResolution;
// static
PowerModeArbiter* PowerModeArbiter::GetInstance() {
static base::NoDestructor<PowerModeArbiter> arbiter;
return arbiter.get();
}
PowerModeArbiter::PowerModeArbiter()
: trace_observer_(std::make_unique<TraceObserver>()),
active_mode_("PowerModeArbiter", this),
observers_(
base::MakeRefCounted<base::ObserverListThreadSafe<Observer>>()) {
base::trace_event::TraceLog::GetInstance()->AddEnabledStateObserver(this);
}
PowerModeArbiter::~PowerModeArbiter() {
base::trace_event::TraceLog::GetInstance()->RemoveEnabledStateObserver(this);
}
void PowerModeArbiter::OnThreadPoolAvailable() {
int sequence_number = 0;
scoped_refptr<base::TaskRunner> task_runner;
{
base::AutoLock lock(lock_);
// May be called multiple times in single-process mode.
if (task_runner_)
return;
// Set task_runner_ under lock to avoid a race with AddObserver().
task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
task_runner = task_runner_;
// Acquire the current sequence number in case it was previously incremented
// by RemoveObserver(). It will be incremented by UpdatePendingResets()
// below.
sequence_number = update_task_sequence_number_;
}
// Check if there are any actionable resets and post another task to handle
// future ones if necessary. If sequence_number is changed concurrently by
// RemoveObserver() or ResetVoteAfterTimeout(), this has call has no effect,
// but a future call to UpdatePendingResets() will take its place.
UpdatePendingResets(sequence_number);
// Create the charging voter on the task runner sequence, so that charging
// state notifications are received there.
task_runner->PostTask(FROM_HERE, base::BindOnce([] {
PowerModeArbiter::GetInstance()->charging_voter_ =
std::make_unique<ChargingPowerModeVoter>();
}));
}
std::unique_ptr<PowerModeVoter> PowerModeArbiter::NewVoter(const char* name) {
std::unique_ptr<PowerModeVoter> voter(new PowerModeVoter(this));
{
base::AutoLock lock(lock_);
votes_.emplace(voter.get(), TracedPowerMode(name, voter.get()));
}
return voter;
}
void PowerModeArbiter::AddObserver(Observer* observer) {
DCHECK(observer);
bool should_update_pending_resets = false;
int sequence_number = 0;
{
base::AutoLock lock(lock_);
observer->OnPowerModeChanged(PowerMode::kIdle, active_mode_.mode());
should_update_pending_resets = task_runner_ && !has_observers_;
// Acquire the current sequence number in case it was previously incremented
// by RemoveObserver(). If necessary, it will be incremented by
// UpdatePendingResets() below.
sequence_number = update_task_sequence_number_;
observers_->AddObserver(observer);
has_observers_ = true;
}
// Reset tasks are disabled until the first observer is registered. If
// sequence_number is changed concurrently by RemoveObserver() or
// ResetVoteAfterTimeout(), this has call has no effect, but a future call to
// UpdatePendingResets() will take its place.
if (should_update_pending_resets)
UpdatePendingResets(sequence_number);
}
void PowerModeArbiter::RemoveObserver(Observer* observer) {
base::AutoLock lock(lock_);
ObserverList::RemoveObserverResult result =
observers_->RemoveObserver(observer);
has_observers_ =
result == ObserverList::RemoveObserverResult::kRemainsNonEmpty;
// Increment update_task_sequence_number_ so that any scheduled update tasks
// are skipped and only restarted if another observer registers.
if (!has_observers_)
++update_task_sequence_number_;
}
void PowerModeArbiter::OnVoterDestroyed(PowerModeVoter* voter) {
{
base::AutoLock lock(lock_);
votes_.erase(voter);
pending_resets_.erase(voter);
}
OnVotesUpdated();
}
void PowerModeArbiter::SetVote(PowerModeVoter* voter, PowerMode mode) {
bool did_change = false;
{
base::AutoLock lock(lock_);
auto it = votes_.find(voter);
DCHECK(it != votes_.end());
TracedPowerMode& voter_mode = it->second;
did_change |= voter_mode.mode() != mode;
voter_mode.SetMode(mode);
pending_resets_.erase(voter);
}
if (did_change)
OnVotesUpdated();
}
void PowerModeArbiter::ResetVoteAfterTimeout(PowerModeVoter* voter,
base::TimeDelta timeout) {
bool should_post_update_task = false;
int sequence_number = 0;
scoped_refptr<base::TaskRunner> task_runner;
{
base::AutoLock lock(lock_);
base::TimeTicks scheduled_time = base::TimeTicks::Now() + timeout;
// Align to the reset task's resolution.
scheduled_time = scheduled_time.SnappedToNextTick(base::TimeTicks(),
kResetVoteTimeResolution);
pending_resets_[voter] = scheduled_time;
// Only post a new task if there isn't one scheduled to run earlier yet.
// This reduces the number of posted callbacks in situations where the
// pending vote is cleared soon after UpdateVoteAfterTimeout() by SetVote().
if (task_runner_ && has_observers_ &&
(next_pending_vote_update_time_.is_null() ||
scheduled_time < next_pending_vote_update_time_)) {
next_pending_vote_update_time_ = scheduled_time;
should_post_update_task = true;
++update_task_sequence_number_;
sequence_number = update_task_sequence_number_;
task_runner = task_runner_;
}
}
if (should_post_update_task) {
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PowerModeArbiter::UpdatePendingResets,
base::Unretained(this), sequence_number),
timeout);
}
}
void PowerModeArbiter::UpdatePendingResets(int sequence_number) {
// Note: This method may run at any point and on any thread. Do not assume
// that there are any resets that have expired, or that any other
// UpdatePendingResets() task is scheduled.
bool did_change = false;
base::TimeTicks now = base::TimeTicks::Now();
base::TimeTicks next_task_time;
int next_sequence_number = 0;
scoped_refptr<base::TaskRunner> task_runner;
{
base::AutoLock lock(lock_);
// Check if this task was cancelled and replaced by another one.
if (update_task_sequence_number_ != sequence_number)
return;
now = base::TimeTicks::Now();
for (auto it = pending_resets_.begin(); it != pending_resets_.end();) {
base::TimeTicks task_time = it->second;
if (task_time <= now) {
PowerModeVoter* voter = it->first;
auto votes_it = votes_.find(voter);
DCHECK(votes_it != votes_.end());
TracedPowerMode& voter_mode = votes_it->second;
did_change |= voter_mode.mode() != PowerMode::kIdle;
voter_mode.SetMode(PowerMode::kIdle);
it = pending_resets_.erase(it);
} else {
if (next_task_time.is_null() || task_time < next_task_time)
next_task_time = task_time;
++it;
}
}
next_pending_vote_update_time_ = next_task_time;
if (!next_task_time.is_null()) {
task_runner = task_runner_;
++update_task_sequence_number_;
next_sequence_number = update_task_sequence_number_;
}
}
if (!next_task_time.is_null()) {
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PowerModeArbiter::UpdatePendingResets,
base::Unretained(this), next_sequence_number),
next_task_time - now);
}
if (did_change)
OnVotesUpdated();
}
void PowerModeArbiter::OnVotesUpdated() {
base::AutoLock lock(lock_);
PowerMode old_mode = active_mode_.mode();
PowerMode new_mode = ComputeActiveModeLocked();
active_mode_.SetMode(new_mode);
if (old_mode == new_mode)
return;
// Notify while holding |lock| to avoid out-of-order observer updates.
observers_->Notify(FROM_HERE, &Observer::OnPowerModeChanged, old_mode,
new_mode);
}
PowerMode PowerModeArbiter::ComputeActiveModeLocked() {
PowerMode mode = PowerMode::kIdle;
bool is_audible = false;
for (const auto& voter_and_vote : votes_) {
PowerMode vote = voter_and_vote.second.mode();
if (vote > mode)
mode = vote;
if (vote == PowerMode::kAudible)
is_audible = true;
}
// In background, audible overrides.
if (mode == PowerMode::kBackground && is_audible)
return PowerMode::kAudible;
return mode;
}
PowerMode PowerModeArbiter::GetActiveModeForTesting() {
base::AutoLock lock(lock_);
return active_mode_.mode();
}
void PowerModeArbiter::OnTraceLogEnabled() {
{
base::AutoLock lock(lock_);
for (const auto& voter_and_vote : votes_)
voter_and_vote.second.OnTraceLogEnabled();
active_mode_.OnTraceLogEnabled();
}
const auto* power_tracing_enabled =
TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("power");
if (*power_tracing_enabled) {
// Add a no-op observer which ensures that reset tasks are executing while
// tracing is enabled.
AddObserver(trace_observer_.get());
}
}
void PowerModeArbiter::OnTraceLogDisabled() {
RemoveObserver(trace_observer_.get());
}
} // namespace power_scheduler