Commit 82c79b1f authored by Sebastien Marchand's avatar Sebastien Marchand Committed by Commit Bot

[PM] Add a freezing vote aggregator

This will be responsible for aggregating freezing votes and upstreaming
a voting decision to a voting consumer (the FreezingDecorator in
practice).

Bug: 1144025
Change-Id: I052fb9d4728ae05943231f326e73906d03f6361b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2505591
Commit-Queue: Sébastien Marchand <sebmarchand@chromium.org>
Reviewed-by: default avatarJoe Mason <joenotcharles@chromium.org>
Reviewed-by: default avatarPatrick Monette <pmonette@chromium.org>
Cr-Commit-Position: refs/heads/master@{#825870}
parent 530031e6
......@@ -30,6 +30,8 @@ static_library("performance_manager") {
"execution_context_priority/max_vote_aggregator.cc",
"execution_context_priority/override_vote_aggregator.cc",
"features.cc",
"freezing/freezing_vote_aggregator.cc",
"freezing/freezing_vote_aggregator.h",
"graph/frame_node.cc",
"graph/frame_node_impl.cc",
"graph/frame_node_impl.h",
......@@ -217,6 +219,7 @@ source_set("unit_tests") {
"execution_context_priority/execution_context_priority_unittest.cc",
"execution_context_priority/max_vote_aggregator_unittest.cc",
"execution_context_priority/override_vote_aggregator_unittest.cc",
"freezing/freezing_vote_aggregator_unittest.cc",
"graph/frame_node_impl_unittest.cc",
"graph/graph_impl_operations_unittest.cc",
"graph/graph_impl_unittest.cc",
......
// Copyright 2020 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/performance_manager/freezing/freezing_vote_aggregator.h"
#include <algorithm>
#include "base/stl_util.h"
namespace performance_manager {
namespace freezing {
FreezingVoteAggregator::FreezingVoteAggregator() : factory_(this) {}
FreezingVoteAggregator::~FreezingVoteAggregator() = default;
FreezingVotingChannel FreezingVoteAggregator::GetVotingChannel() {
return factory_.BuildVotingChannel();
}
void FreezingVoteAggregator::SetUpstreamVotingChannel(
FreezingVotingChannel&& channel) {
DCHECK(channel.IsValid());
DCHECK(vote_data_map_.empty());
DCHECK(!channel_.IsValid());
channel_ = std::move(channel);
}
FreezingVoteReceipt FreezingVoteAggregator::SubmitVote(
util::PassKey<FreezingVotingChannel>,
voting::VoterId<FreezingVote> voter_id,
const PageNode* page_node,
const FreezingVote& vote) {
DCHECK(vote.IsValid());
DCHECK(channel_.IsValid());
auto& vote_data = vote_data_map_[page_node];
AcceptedFreezingVote accepted_vote(this, voter_id, page_node, vote);
auto receipt = accepted_vote.IssueReceipt();
if (vote_data.AddVote(std::move(accepted_vote)) ==
FreezingVoteData::UpstreamVoteImpact::kUpstreamVoteChanged) {
vote_data.UpstreamVote(&channel_);
}
// Return a vote receipt to our voter for the received vote.
return receipt;
}
void FreezingVoteAggregator::ChangeVote(util::PassKey<AcceptedFreezingVote>,
AcceptedFreezingVote* old_vote,
const FreezingVote& new_vote) {
DCHECK(old_vote->IsValid());
auto& vote_data = GetVoteData(old_vote->context())->second;
if (vote_data.UpdateVote(old_vote, new_vote) ==
FreezingVoteData::UpstreamVoteImpact::kUpstreamVoteChanged) {
vote_data.UpstreamVote(&channel_);
}
}
void FreezingVoteAggregator::VoteInvalidated(
util::PassKey<AcceptedFreezingVote>,
AcceptedFreezingVote* vote) {
DCHECK(!vote->IsValid());
auto it = GetVoteData(vote->context());
auto& vote_data = it->second;
auto remove_vote_result = vote_data.RemoveVote(vote);
// Remove the vote, and upstream if necessary.
if (remove_vote_result ==
FreezingVoteData::UpstreamVoteImpact::kUpstreamVoteChanged) {
vote_data.UpstreamVote(&channel_);
}
// If all the votes for this PageNode have disappeared then remove the entry
// entirely. This will release the receipt that it contains and will cancel
// our upstream vote.
if (remove_vote_result ==
FreezingVoteData::UpstreamVoteImpact::kUpstreamVoteRemoved) {
vote_data_map_.erase(it);
}
}
FreezingVoteAggregator::FreezingVoteData::FreezingVoteData() = default;
FreezingVoteAggregator::FreezingVoteData::FreezingVoteData(FreezingVoteData&&) =
default;
FreezingVoteAggregator::FreezingVoteData&
FreezingVoteAggregator::FreezingVoteData::operator=(
FreezingVoteAggregator::FreezingVoteData&& rhs) = default;
FreezingVoteAggregator::FreezingVoteData::~FreezingVoteData() = default;
FreezingVoteAggregator::FreezingVoteData::UpstreamVoteImpact
FreezingVoteAggregator::FreezingVoteData::AddVote(AcceptedFreezingVote&& vote) {
auto current_decision = FreezingVoteValue::kCanFreeze;
if (accepted_votes_.size())
current_decision = GetCurrentVote().vote().value();
AddVoteToDeque(std::move(vote));
// Always report the first vote.
if (accepted_votes_.size() == 1)
return UpstreamVoteImpact::kUpstreamVoteChanged;
return (current_decision != GetCurrentVote().vote().value())
? UpstreamVoteImpact::kUpstreamVoteChanged
: UpstreamVoteImpact::kUpstreamVoteUnchanged;
}
FreezingVoteAggregator::FreezingVoteData::UpstreamVoteImpact
FreezingVoteAggregator::FreezingVoteData::UpdateVote(
AcceptedFreezingVote* old_vote,
const FreezingVote& new_vote) {
auto current_decision = GetCurrentVote().vote().value();
auto it = FindVote(old_vote);
DCHECK(it != accepted_votes_.end());
auto vote = std::move(*it);
accepted_votes_.erase(it);
vote.UpdateVote(new_vote);
AddVoteToDeque(std::move(vote));
return (current_decision != GetCurrentVote().vote().value())
? UpstreamVoteImpact::kUpstreamVoteChanged
: UpstreamVoteImpact::kUpstreamVoteUnchanged;
}
FreezingVoteAggregator::FreezingVoteData::UpstreamVoteImpact
FreezingVoteAggregator::FreezingVoteData::RemoveVote(
AcceptedFreezingVote* vote) {
auto current_decision = GetCurrentVote().vote().value();
accepted_votes_.erase(FindVote(vote));
// Indicate that the upstream vote should be removed.
if (accepted_votes_.empty())
return UpstreamVoteImpact::kUpstreamVoteRemoved;
return (current_decision != GetCurrentVote().vote().value())
? UpstreamVoteImpact::kUpstreamVoteChanged
: UpstreamVoteImpact::kUpstreamVoteUnchanged;
}
void FreezingVoteAggregator::FreezingVoteData::UpstreamVote(
FreezingVotingChannel* channel) {
DCHECK_NE(0u, accepted_votes_.size());
auto& vote = GetCurrentVote();
// Change our existing vote, or create a new one as necessary.
if (receipt_.HasVote()) {
receipt_.ChangeVote(vote.vote().value(), vote.vote().reason());
} else {
receipt_ = channel->SubmitVote(vote.context(), vote.vote());
}
}
const AcceptedFreezingVote&
FreezingVoteAggregator::FreezingVoteData::GetCurrentVote() {
DCHECK(!IsEmpty());
// The set of votes is ordered and the first one in the set is the one that
// should be sent to the consumer.
return *accepted_votes_.begin();
}
FreezingVoteAggregator::FreezingVoteData::AcceptedVotesDeque::iterator
FreezingVoteAggregator::FreezingVoteData::FindVote(AcceptedFreezingVote* vote) {
// TODO(sebmarchand): Consider doing a reverse search for kCanFreeze votes and
// a normal one for kCannotFreeze votes.
auto it = std::find_if(accepted_votes_.begin(), accepted_votes_.end(),
[vote](const auto& rhs) { return &rhs == vote; });
DCHECK(it != accepted_votes_.end());
return it;
}
void FreezingVoteAggregator::FreezingVoteData::AddVoteToDeque(
AcceptedFreezingVote&& vote) {
if (vote.vote().value() == FreezingVoteValue::kCannotFreeze) {
accepted_votes_.push_front(std::move(vote));
} else {
accepted_votes_.push_back(std::move(vote));
}
}
FreezingVoteAggregator::VoteDataMap::iterator
FreezingVoteAggregator::GetVoteData(const PageNode* page_node) {
auto it = vote_data_map_.find(page_node);
DCHECK(it != vote_data_map_.end());
return it;
}
} // namespace freezing
} // namespace performance_manager
\ No newline at end of file
// Copyright 2020 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.
#ifndef COMPONENTS_PERFORMANCE_MANAGER_FREEZING_FREEZING_VOTE_AGGREGATOR_H_
#define COMPONENTS_PERFORMANCE_MANAGER_FREEZING_FREEZING_VOTE_AGGREGATOR_H_
#include "base/compiler_specific.h"
#include "base/containers/circular_deque.h"
#include "base/containers/flat_map.h"
#include "components/performance_manager/public/voting/voting.h"
namespace performance_manager {
class PageNode;
namespace freezing {
enum class FreezingVoteValue {
kCannotFreeze,
kCanFreeze,
};
using FreezingVote =
voting::Vote<PageNode, FreezingVoteValue, FreezingVoteValue::kCannotFreeze>;
using FreezingVoteReceipt = voting::VoteReceipt<FreezingVote>;
using FreezingVotingChannel = voting::VotingChannel<FreezingVote>;
using FreezingVoteConsumer = voting::VoteConsumer<FreezingVote>;
using AcceptedFreezingVote = voting::AcceptedVote<FreezingVote>;
using FreezingVotingChannelFactory = voting::VotingChannelFactory<FreezingVote>;
// An aggregator for freezing votes. It upstreams an aggregated vote to an
// upstream channel every time the freezing decision changes for a PageNode. It
// allows freezing of a given PageNode upon reception of one or several
// kCanFreeze vote for this node. Any kCannotFreeze vote received will have
// priority over the kCanFreeze votes and will prevent the PageNode from being
// frozen.
class FreezingVoteAggregator final : public FreezingVoteConsumer {
public:
FreezingVoteAggregator();
FreezingVoteAggregator(const FreezingVoteAggregator& rhs) = delete;
FreezingVoteAggregator& operator=(const FreezingVoteAggregator& rhs) = delete;
~FreezingVoteAggregator() override;
// Issues a voting channel (effectively registered a voter).
FreezingVotingChannel GetVotingChannel();
// Sets the upstream voting channel. Should only be called once.
void SetUpstreamVotingChannel(FreezingVotingChannel&& channel);
// VoteConsumer implementation:
FreezingVoteReceipt SubmitVote(util::PassKey<FreezingVotingChannel>,
voting::VoterId<FreezingVote> voter_id,
const PageNode* page_node,
const FreezingVote& vote) override;
void ChangeVote(util::PassKey<AcceptedFreezingVote>,
AcceptedFreezingVote* old_vote,
const FreezingVote& new_vote) override;
void VoteInvalidated(util::PassKey<AcceptedFreezingVote>,
AcceptedFreezingVote* vote) override;
private:
friend class FreezingVoteAggregatorTestAccess;
// Contains the freezing votes for a given PageNode.
class FreezingVoteData {
public:
// The consequence that adding, removing or updating a vote has on the
// upstreamed vote. The caller is responsible for calling UpstreamVote or
// invalidating the vote (by destroying the instance of this class that owns
// it).
enum class UpstreamVoteImpact {
// The upstream vote has changed. UpstreamVote should be called.
kUpstreamVoteChanged,
// The upstream vote has been removed and should be invalidated.
kUpstreamVoteRemoved,
// The operation had no impact on the upstreamed vote.
kUpstreamVoteUnchanged,
};
FreezingVoteData();
FreezingVoteData(FreezingVoteData&&);
FreezingVoteData& operator=(FreezingVoteData&&);
FreezingVoteData(const FreezingVoteData& rhs) = delete;
FreezingVoteData& operator=(const FreezingVoteData& rhs) = delete;
~FreezingVoteData();
// Adds a vote. Returns an UpstreamVoteImpact indicating if the upstreamed
// vote should be updated by calling UpstreamVote.
UpstreamVoteImpact AddVote(AcceptedFreezingVote&& vote) WARN_UNUSED_RESULT;
// Updates a vote. Returns an UpstreamVoteImpact indicating if the
// upstreamed vote should be updated by calling UpstreamVote.
UpstreamVoteImpact UpdateVote(AcceptedFreezingVote* old_vote,
const FreezingVote& new_vote)
WARN_UNUSED_RESULT;
// Removes a vote. Returns an UpstreamVoteImpact indicating if the
// upstreamed vote should be updated by calling UpstreamVote or invalidated.
UpstreamVoteImpact RemoveVote(AcceptedFreezingVote* vote)
WARN_UNUSED_RESULT;
// Upstreams the vote for this vote data, using the given voting |channel|.
void UpstreamVote(FreezingVotingChannel* channel);
bool IsEmpty() { return accepted_votes_.empty(); }
// Returns the current aggregated vote.
const AcceptedFreezingVote& GetCurrentVote();
friend class FreezingVoteAggregatorTestAccess;
// The current set of votes.
using AcceptedVotesDeque = base::circular_deque<AcceptedFreezingVote>;
const AcceptedVotesDeque& GetAcceptedVotesForTesting() {
return accepted_votes_;
}
// Returns the iterator of |vote| in |accepted_votes_|. |vote| is expected
// to be in the deque, this is enforced by a DCHECK.
AcceptedVotesDeque::iterator FindVote(AcceptedFreezingVote* vote);
void AddVoteToDeque(AcceptedFreezingVote&& vote);
// kCannotFreeze votes are always at the beginning of the deque.
AcceptedVotesDeque accepted_votes_;
// The receipt for the vote we've upstreamed.
FreezingVoteReceipt receipt_;
};
using VoteDataMap = base::flat_map<const PageNode*, FreezingVoteData>;
// Looks up the VoteData associated with the provided |page_node|. The data is
// expected to already exist (enforced by a DCHECK).
VoteDataMap::iterator GetVoteData(const PageNode* page_node);
// A map that associates a PageNode with a FreezingVoteData structure.
VoteDataMap vote_data_map_;
// The channel for upstreaming our votes.
FreezingVotingChannel channel_;
// The factory for providing FreezingVotingChannels to our input voters.
FreezingVotingChannelFactory factory_;
};
} // namespace freezing
} // namespace performance_manager
#endif // COMPONENTS_PERFORMANCE_MANAGER_FREEZING_FREEZING_VOTE_AGGREGATOR_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment