Skip to content

Commit

Permalink
Create classes for Memory Pressure Voting system.
Browse files Browse the repository at this point in the history
This CL creates the MultiSourceMemoryPressureMonitor,
MemoryPressureVoteAggregator, and MemoryPressureVoter classes described
here: https://docs.google.com/document/d/1W3FPDyjIAKBcFGNYsHA3EKR1FHrJlbBaqT4_RUnxzq0/edit#.
The aggregator class collects votes from voter instances via the OnVote
method. A MemoryPressureVoteAggregator object will be owned by the
MultiSourceMemoryPressureMonitor class, which will be merged with the
MemoryPressureMonitor class after subsequent CL's which will migrate
the OS-specific MemoryPressureMonitors to own a Voter and use that
Voter to inform the Monitor of their votes.

Bug: 980965
Change-Id: Ib84aadca9a4cf74b3c1f8a42787d63a96f5dfd17
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1719109
Commit-Queue: Ryan Powell <ryanpow@google.com>
Reviewed-by: François Doray <fdoray@chromium.org>
Reviewed-by: Sébastien Marchand <sebmarchand@chromium.org>
Cr-Commit-Position: refs/heads/master@{#687282}
  • Loading branch information
Ryan Powell authored and Commit Bot committed Aug 15, 2019
1 parent b932662 commit 30287d4
Show file tree
Hide file tree
Showing 10 changed files with 558 additions and 0 deletions.
1 change: 1 addition & 0 deletions base/util/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import("//testing/test.gni")

test("base_util_unittests") {
deps = [
"memory_pressure:unittests",
"type_safety:tests",
"values:unittests",
"//base/test:run_all_base_unittests",
Expand Down
31 changes: 31 additions & 0 deletions base/util/memory_pressure/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright 2019 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.

source_set("memory_pressure") {
sources = [
"memory_pressure_voter.cc",
"memory_pressure_voter.h",
"multi_source_memory_pressure_monitor.cc",
"multi_source_memory_pressure_monitor.h",
]

deps = [
"//base",
]
}

source_set("unittests") {
testonly = true
sources = [
"memory_pressure_voter_unittest.cc",
"multi_source_memory_pressure_monitor_unittest.cc",
]

deps = [
":memory_pressure",
"//base",
"//base/test:test_support",
"//testing/gtest",
]
}
3 changes: 3 additions & 0 deletions base/util/memory_pressure/OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
chrisha@chromium.org
fdoray@chromium.org
sebmarchand@chromium.org
102 changes: 102 additions & 0 deletions base/util/memory_pressure/memory_pressure_voter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2019 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 "base/util/memory_pressure/memory_pressure_voter.h"

#include <numeric>

#include "base/stl_util.h"

namespace util {

MemoryPressureVoteAggregator::MemoryPressureVoteAggregator(Delegate* delegate)
: current_pressure_level_(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE),
delegate_(delegate),
votes_() {}

MemoryPressureVoteAggregator::~MemoryPressureVoteAggregator() {
DCHECK_EQ(std::accumulate(votes_.begin(), votes_.end(), 0), 0);
}

void MemoryPressureVoteAggregator::OnVoteForTesting(
base::Optional<MemoryPressureLevel> old_vote,
base::Optional<MemoryPressureLevel> new_vote) {
OnVote(old_vote, new_vote);
}

void MemoryPressureVoteAggregator::NotifyListenersForTesting() {
NotifyListeners();
}

base::MemoryPressureListener::MemoryPressureLevel
MemoryPressureVoteAggregator::EvaluateVotesForTesting() {
return EvaluateVotes();
}

void MemoryPressureVoteAggregator::OnVote(
base::Optional<MemoryPressureLevel> old_vote,
base::Optional<MemoryPressureLevel> new_vote) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(old_vote || new_vote);
if (old_vote) {
DCHECK_LT(0u, votes_[old_vote.value()]);
votes_[old_vote.value()]--;
}
if (new_vote)
votes_[new_vote.value()]++;
auto old_pressure_level = current_pressure_level_;
current_pressure_level_ = EvaluateVotes();
if (old_pressure_level != current_pressure_level_)
delegate_->OnMemoryPressureLevelChanged(current_pressure_level_);
}

void MemoryPressureVoteAggregator::NotifyListeners() {
delegate_->OnNotifyListenersRequested();
}

base::MemoryPressureListener::MemoryPressureLevel
MemoryPressureVoteAggregator::EvaluateVotes() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
static_assert(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL == 2,
"Ensure that each memory pressure level is handled by this method.");
if (votes_[2])
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
if (votes_[1])
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
}

void MemoryPressureVoteAggregator::SetVotesForTesting(size_t none_votes,
size_t moderate_votes,
size_t critical_votes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
votes_[0] = none_votes;
votes_[1] = moderate_votes;
votes_[2] = critical_votes;
}

MemoryPressureVoter::MemoryPressureVoter(
MemoryPressureVoteAggregator* aggregator)
: aggregator_(aggregator) {}

MemoryPressureVoter::~MemoryPressureVoter() {
// Remove this voter's vote.
if (vote_)
aggregator_->OnVote(vote_, base::nullopt);
}

void MemoryPressureVoter::SetVote(
base::MemoryPressureListener::MemoryPressureLevel level,
bool notify_listeners) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto old_vote = vote_;
vote_ = level;
aggregator_->OnVote(old_vote, vote_);
if (notify_listeners)
aggregator_->NotifyListeners();
}

} // namespace util
130 changes: 130 additions & 0 deletions base/util/memory_pressure/memory_pressure_voter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2019 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 BASE_UTIL_MEMORY_PRESSURE_MEMORY_PRESSURE_VOTER_H_
#define BASE_UTIL_MEMORY_PRESSURE_MEMORY_PRESSURE_VOTER_H_

#include <array>

#include "base/callback.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/sequence_checker.h"

namespace util {

// Collects votes from MemoryPressureVoters and evaluates them to determine the
// pressure level for the MultiSourceMemoryPressureMonitor, which will own
// and outlive the aggregator. The pressure level is calculated as the most
// critical of all votes collected. This class is not thread safe and should be
// used from a single sequence.
class MemoryPressureVoteAggregator {
public:
class Delegate;

using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel;

explicit MemoryPressureVoteAggregator(Delegate* delegate);
~MemoryPressureVoteAggregator();

void OnVoteForTesting(base::Optional<MemoryPressureLevel> old_vote,
base::Optional<MemoryPressureLevel> new_vote);

void NotifyListenersForTesting();

base::MemoryPressureListener::MemoryPressureLevel EvaluateVotesForTesting();
void SetVotesForTesting(size_t none_votes,
size_t moderate_votes,
size_t critical_votes);

private:
friend class MemoryPressureVoter;

// Invoked by MemoryPressureVoter as it calculates its vote. Optional is
// used so a voter can pass null as |old_vote| if this is their first vote, or
// null as |new_vote| if they are removing their vote (e.g. when the voter is
// being destroyed). |old_vote| and |new_vote| should never both be null.
void OnVote(base::Optional<MemoryPressureLevel> old_vote,
base::Optional<MemoryPressureLevel> new_vote);

// Triggers a notification of the MemoryPressureMonitor's current pressure
// level, allowing each of the various sources of input on MemoryPressureLevel
// to maintain their own signalling behavior.
// TODO(991361): Remove this behavior and standardize across platforms.
void NotifyListeners();

// Returns the highest index of |votes_| with a non-zero value, as a
// MemoryPressureLevel.
MemoryPressureLevel EvaluateVotes() const;

MemoryPressureLevel current_pressure_level_;

Delegate* const delegate_;

// Array with one bucket for each potential MemoryPressureLevel. The overall
// MemoryPressureLevel is calculated as the highest index of a non-zero
// bucket.
// MEMORY_PRESSURE_LEVEL_CRITICAL + 1 is used in place of adding a kCount
// value to the MemoryPressureLevel enum as adding another value would require
// changing every instance of switch(MemoryPressureLevel) in Chromium, and the
// MemoryPressureLevel system will be changing soon regardless.
std::array<size_t,
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL + 1>
votes_;

SEQUENCE_CHECKER(sequence_checker_);

DISALLOW_COPY_AND_ASSIGN(MemoryPressureVoteAggregator);
};

// Interface used to notify MemoryPressureVoteAggregator's owner of changes to
// vote aggregation.
class MemoryPressureVoteAggregator::Delegate {
public:
Delegate() = default;
virtual ~Delegate() = default;

// Invoked when the aggregate vote has changed.
virtual void OnMemoryPressureLevelChanged(
base::MemoryPressureListener::MemoryPressureLevel level) = 0;

// Invoked when a voter has determined that a notification of the current
// pressure level is necessary.
virtual void OnNotifyListenersRequested() = 0;
};

// Handles the forwarding of votes to the MemoryPressureVoteAggregator. Any
// source which should have input on the overall MemoryPressureLevel will
// calculate their vote on their own period, and use their Voter to inform the
// Aggregator whenever their vote has changed or they want to trigger a
// notification to the MemoryPressureListeners. This class is not thread safe
// and should be used from a single sequence.
class MemoryPressureVoter {
public:
// The aggregator should outlive this voter, and both should live on the same
// sequence.
explicit MemoryPressureVoter(MemoryPressureVoteAggregator* aggregator);
~MemoryPressureVoter();

// Called to set a vote / change a vote.
void SetVote(base::MemoryPressureListener::MemoryPressureLevel level,
bool notify_listeners);

private:
// This is the aggregator to which this voter's votes will be cast.
MemoryPressureVoteAggregator* const aggregator_;

// Optional<> is used here as the vote will be null until the voter’s
// first vote calculation.
base::Optional<base::MemoryPressureListener::MemoryPressureLevel> vote_;

SEQUENCE_CHECKER(sequence_checker_);

DISALLOW_COPY_AND_ASSIGN(MemoryPressureVoter);
};

} // namespace util

#endif // BASE_UTIL_MEMORY_PRESSURE_MEMORY_PRESSURE_VOTER_H_
119 changes: 119 additions & 0 deletions base/util/memory_pressure/memory_pressure_voter_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2019 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 "base/util/memory_pressure/memory_pressure_voter.h"

#include "base/bind.h"
#include "base/macros.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace util {

namespace {

class TestDelegate : public util::MemoryPressureVoteAggregator::Delegate {
private:
void OnMemoryPressureLevelChanged(
base::MemoryPressureListener::MemoryPressureLevel level) override {}
void OnNotifyListenersRequested() override {}
};

} // namespace

TEST(MemoryPressureVoterTest, EvaluateVotes) {
TestDelegate delegate;
MemoryPressureVoteAggregator aggregator(&delegate);

aggregator.SetVotesForTesting(1, 2, 3);
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);

aggregator.SetVotesForTesting(1, 20, 1);
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);

aggregator.SetVotesForTesting(0, 0, 0);
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);

aggregator.SetVotesForTesting(0, 2, 0);
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);

// Reset votes so destructor doesn't think there are loose voters.
aggregator.SetVotesForTesting(0, 0, 0);
}

TEST(MemoryPressureVoterTest, OnVote) {
TestDelegate delegate;
MemoryPressureVoteAggregator aggregator(&delegate);

// vote count = 0,0,0
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);

aggregator.OnVoteForTesting(
base::nullopt, base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
// vote count = 1,0,0
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);

aggregator.OnVoteForTesting(
base::nullopt,
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
// vote count = 1,0,1
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);

aggregator.OnVoteForTesting(
base::nullopt,
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
// vote count = 1,1,1
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);

aggregator.OnVoteForTesting(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
// vote count = 1,2,0
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);

aggregator.OnVoteForTesting(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
base::nullopt);
// vote count = 1,1,0
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);

// Reset votes so destructor doesn't think there are loose voters.
aggregator.SetVotesForTesting(0, 0, 0);
}

TEST(MemoryPressureVoterTest, SetVote) {
TestDelegate delegate;
MemoryPressureVoteAggregator aggregator(&delegate);
auto voter_critical = std::make_unique<MemoryPressureVoter>(&aggregator);
auto voter_moderate = std::make_unique<MemoryPressureVoter>(&aggregator);

voter_critical->SetVote(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, false);
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);

voter_moderate->SetVote(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, false);
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);

voter_critical.reset();
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);

voter_moderate.reset();
EXPECT_EQ(aggregator.EvaluateVotesForTesting(),
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
}

} // namespace util

0 comments on commit 30287d4

Please sign in to comment.