blob: dc9bf1179fa26b7e7e04c5e0e90ed8f65d75b578 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// 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 "base/containers/contains.h"
#include "base/ranges/algorithm.h"
#include "base/strings/stringprintf.h"
#include "components/performance_manager/public/freezing/freezing.h"
#include "components/performance_manager/public/graph/node_data_describer_registry.h"
namespace performance_manager {
namespace freezing {
namespace {
const char kDescriberName[] = "FreezingVoteAggregator";
}
FreezingVoteAggregator::FreezingVoteAggregator() = default;
FreezingVoteAggregator::~FreezingVoteAggregator() = default;
FreezingVotingChannel FreezingVoteAggregator::GetVotingChannel() {
return freezing_voting_channel_factory_.BuildVotingChannel();
}
void FreezingVoteAggregator::SetUpstreamVotingChannel(
FreezingVotingChannel&& channel) {
channel_ = std::move(channel);
}
void FreezingVoteAggregator::OnVoteSubmitted(FreezingVoterId voter_id,
const PageNode* page_node,
const FreezingVote& vote) {
DCHECK(channel_.IsValid());
// Create the VoteData for this page node, if necessary.
auto& vote_data = vote_data_map_[page_node];
// Remember the previous chosen vote before adding the new vote. There
// could be none if this is the first vote submitted for |page_node|.
absl::optional<FreezingVoteValue> old_chosen_vote_value;
if (!vote_data.IsEmpty())
old_chosen_vote_value = vote_data.GetChosenVote().value();
vote_data.AddVote(voter_id, vote);
// If there was no previous chosen vote, the vote must be submitted.
if (!old_chosen_vote_value) {
channel_.SubmitVote(page_node, vote);
return;
}
// Since there is a previous chosen vote, it must be modified if the chosen
// vote changed.
const FreezingVote new_chosen_vote = vote_data.GetChosenVote();
if (*old_chosen_vote_value != new_chosen_vote.value())
channel_.ChangeVote(page_node, new_chosen_vote);
}
void FreezingVoteAggregator::OnVoteChanged(FreezingVoterId voter_id,
const PageNode* page_node,
const FreezingVote& new_vote) {
// The vote data for this page node is guaranteed to exist.
auto& vote_data = GetVoteData(page_node)->second;
// Remember the previous chosen vote before updating the vote for this
// |voter_id|.
const FreezingVoteValue old_chosen_vote_value =
vote_data.GetChosenVote().value();
vote_data.UpdateVote(voter_id, new_vote);
// If the chosen vote changed, the upstream vote must also be changed.
const FreezingVote new_chosen_vote = vote_data.GetChosenVote();
if (old_chosen_vote_value != new_chosen_vote.value())
channel_.ChangeVote(page_node, new_chosen_vote);
}
void FreezingVoteAggregator::OnVoteInvalidated(FreezingVoterId voter_id,
const PageNode* page_node) {
// The VoteData for this page node is guaranteed to exist.
auto it = GetVoteData(page_node);
auto& vote_data = it->second;
// Remember the previous chosen vote before removing the vote for this
// |voter_id|.
const FreezingVoteValue old_chosen_vote_value =
vote_data.GetChosenVote().value();
vote_data.RemoveVote(voter_id);
// In case the last vote for |page_node| was invalidated, the upstream vote
// must also be invalidated.
if (vote_data.IsEmpty()) {
channel_.InvalidateVote(page_node);
// Clean up the VoteData for |page_node| since it is empty.
vote_data_map_.erase(it);
return;
}
// If the chosen vote changed, the upstream vote must also be changed.
const FreezingVote new_chosen_vote = vote_data.GetChosenVote();
if (old_chosen_vote_value != new_chosen_vote.value())
channel_.ChangeVote(page_node, new_chosen_vote);
}
void FreezingVoteAggregator::RegisterNodeDataDescriber(Graph* graph) {
graph->GetNodeDataDescriberRegistry()->RegisterDescriber(this,
kDescriberName);
}
void FreezingVoteAggregator::UnregisterNodeDataDescriber(Graph* graph) {
graph->GetNodeDataDescriberRegistry()->UnregisterDescriber(this);
}
base::Value::Dict FreezingVoteAggregator::DescribePageNodeData(
const PageNode* node) const {
auto votes_for_page = vote_data_map_.find(node);
if (votes_for_page == vote_data_map_.end())
return base::Value::Dict();
base::Value::Dict ret;
votes_for_page->second.DescribeVotes(ret);
return ret;
}
FreezingVoteAggregator::FreezingVoteData::FreezingVoteData() = default;
FreezingVoteAggregator::FreezingVoteData::FreezingVoteData(FreezingVoteData&&) =
default;
FreezingVoteAggregator::FreezingVoteData&
FreezingVoteAggregator::FreezingVoteData::operator=(
FreezingVoteAggregator::FreezingVoteData&& rhs) = default;
FreezingVoteAggregator::FreezingVoteData::~FreezingVoteData() = default;
void FreezingVoteAggregator::FreezingVoteData::AddVote(
FreezingVoterId voter_id,
const FreezingVote& vote) {
AddVoteToDeque(voter_id, vote);
}
void FreezingVoteAggregator::FreezingVoteData::UpdateVote(
FreezingVoterId voter_id,
const FreezingVote& new_vote) {
// The vote is removed from the deque and then re-inserted.
auto it = FindVote(voter_id);
DCHECK(it != votes_.end());
votes_.erase(it);
AddVoteToDeque(voter_id, new_vote);
}
void FreezingVoteAggregator::FreezingVoteData::RemoveVote(
FreezingVoterId voter_id) {
votes_.erase(FindVote(voter_id));
}
const FreezingVote& FreezingVoteAggregator::FreezingVoteData::GetChosenVote() {
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 votes_.begin()->second;
}
void FreezingVoteAggregator::FreezingVoteData::DescribeVotes(
base::Value::Dict& ret) const {
size_t i = 0;
for (const auto& it : votes_) {
ret.Set(base::StringPrintf("Vote %zu (%s)", i++,
FreezingVoteValueToString(it.second.value())),
it.second.reason());
}
}
FreezingVoteAggregator::FreezingVoteData::VotesDeque::iterator
FreezingVoteAggregator::FreezingVoteData::FindVote(FreezingVoterId voter_id) {
// TODO(sebmarchand): Consider doing a reverse search for kCanFreeze votes and
// a normal one for kCannotFreeze votes.
auto it =
base::ranges::find(votes_, voter_id, &VotesDeque::value_type::first);
DCHECK(it != votes_.end());
return it;
}
void FreezingVoteAggregator::FreezingVoteData::AddVoteToDeque(
FreezingVoterId voter_id,
const FreezingVote& vote) {
DCHECK(!base::Contains(votes_, voter_id, &VotesDeque::value_type::first));
if (vote.value() == FreezingVoteValue::kCannotFreeze) {
votes_.emplace_front(voter_id, vote);
} else {
votes_.emplace_back(voter_id, 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