Commit 30287d41 authored by Ryan Powell's avatar Ryan Powell Committed by Commit Bot

Create classes for Memory Pressure Voting system.

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: default avatarFrançois Doray <fdoray@chromium.org>
Reviewed-by: default avatarSébastien Marchand <sebmarchand@chromium.org>
Cr-Commit-Position: refs/heads/master@{#687282}
parent b9326623
......@@ -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",
......
# 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",
]
}
chrisha@chromium.org
fdoray@chromium.org
sebmarchand@chromium.org
\ No newline at end of file
// 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
// 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 voters
// 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_
// 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
// 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/multi_source_memory_pressure_monitor.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
namespace util {
MultiSourceMemoryPressureMonitor::MultiSourceMemoryPressureMonitor()
: current_pressure_level_(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE),
dispatch_callback_(base::BindRepeating(
&base::MemoryPressureListener::NotifyMemoryPressure)),
aggregator_(this) {
StartMetricsTimer();
}
MultiSourceMemoryPressureMonitor::~MultiSourceMemoryPressureMonitor() = default;
void MultiSourceMemoryPressureMonitor::StartMetricsTimer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
metric_timer_.Start(
FROM_HERE, MemoryPressureMonitor::kUMAMemoryPressureLevelPeriod,
BindRepeating(&MemoryPressureMonitor::RecordMemoryPressure,
GetCurrentPressureLevel(), /* ticks = */ 1));
}
void MultiSourceMemoryPressureMonitor::StopMetricsTimer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
metric_timer_.Stop();
}
base::MemoryPressureListener::MemoryPressureLevel
MultiSourceMemoryPressureMonitor::GetCurrentPressureLevel() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return current_pressure_level_;
}
std::unique_ptr<MemoryPressureVoter>
MultiSourceMemoryPressureMonitor::CreateVoter() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return std::make_unique<MemoryPressureVoter>(&aggregator_);
}
void MultiSourceMemoryPressureMonitor::SetDispatchCallback(
const DispatchCallback& callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
dispatch_callback_ = callback;
}
void MultiSourceMemoryPressureMonitor::OnMemoryPressureLevelChanged(
base::MemoryPressureListener::MemoryPressureLevel level) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
current_pressure_level_ = level;
}
void MultiSourceMemoryPressureMonitor::OnNotifyListenersRequested() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
dispatch_callback_.Run(current_pressure_level_);
}
} // namespace util
// 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_MULTI_SOURCE_MEMORY_PRESSURE_MONITOR_H_
#define BASE_UTIL_MEMORY_PRESSURE_MULTI_SOURCE_MEMORY_PRESSURE_MONITOR_H_
#include "base/memory/memory_pressure_monitor.h"
#include "base/timer/timer.h"
#include "base/util/memory_pressure/memory_pressure_voter.h"
namespace util {
// This is a specialization of a MemoryPressureMonitor that relies on a set of
// MemoryPressureVoters to determine the memory pressure state. The
// MemoryPressureVoteAggregator is in charge of receiving votes from these
// voters and notifying MemoryPressureListeners of the MemoryPressureLevel via
// the monitor's |dispatch_callback_|. 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 MultiSourceMemoryPressureMonitor
: public base::MemoryPressureMonitor,
public MemoryPressureVoteAggregator::Delegate {
public:
using MemoryPressureLevel = base::MemoryPressureMonitor::MemoryPressureLevel;
using DispatchCallback = base::MemoryPressureMonitor::DispatchCallback;
MultiSourceMemoryPressureMonitor();
~MultiSourceMemoryPressureMonitor() override;
// MemoryPressureMonitor implementation.
MemoryPressureLevel GetCurrentPressureLevel() const override;
void SetDispatchCallback(const DispatchCallback& callback) override;
// Creates a MemoryPressureVoter to be owned/used by a source that wishes to
// have input on the overall memory pressure level.
std::unique_ptr<MemoryPressureVoter> CreateVoter();
MemoryPressureVoteAggregator* aggregator_for_testing() {
return &aggregator_;
}
protected:
void StartMetricsTimer();
void StopMetricsTimer();
void UpdatePressureLevel(MemoryPressureLevel level);
void NotifyListeners();
private:
// Delegate implementation.
void OnMemoryPressureLevelChanged(MemoryPressureLevel level) override;
void OnNotifyListenersRequested() override;
MemoryPressureLevel current_pressure_level_;
DispatchCallback dispatch_callback_;
MemoryPressureVoteAggregator aggregator_;
// A periodic timer to record UMA metrics.
base::RepeatingTimer metric_timer_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(MultiSourceMemoryPressureMonitor);
};
} // namespace util
#endif // BASE_UTIL_MEMORY_PRESSURE_MULTI_SOURCE_MEMORY_PRESSURE_MONITOR_H_
// 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/multi_source_memory_pressure_monitor.h"
#include "base/macros.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace util {
TEST(MultiSourceMemoryPressureMonitorTest, RunDispatchCallback) {
base::test::ScopedTaskEnvironment task_environment;
MultiSourceMemoryPressureMonitor monitor;
auto* aggregator = monitor.aggregator_for_testing();
bool callback_called = false;
monitor.SetDispatchCallback(base::BindLambdaForTesting(
[&](base::MemoryPressureListener::MemoryPressureLevel) {
callback_called = true;
}));
aggregator->OnVoteForTesting(
base::nullopt, base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
aggregator->NotifyListenersForTesting();
EXPECT_TRUE(callback_called);
// Clear vote so aggregator's destructor doesn't think there are loose voters.
aggregator->OnVoteForTesting(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, base::nullopt);
}
} // namespace util
......@@ -241,6 +241,7 @@ jumbo_source_set("browser") {
public_deps = [
":accessibility_buildflags",
"//base/util/memory_pressure",
"//base/util/type_safety",
"//ipc",
"//media/mojo/mojom:remoting",
......
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