diff options
author | chrisha <chrisha@chromium.org> | 2015-08-24 09:04:23 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-08-24 16:05:04 +0000 |
commit | dfdde660bba559dec7b5baffb5544e634447e0dc (patch) | |
tree | decda15808b86513e7345df19e794411c50e3982 | |
parent | 4fb74d48d8d3001177fdfa0f2455d39be1363552 (diff) | |
download | chromium_src-dfdde660bba559dec7b5baffb5544e634447e0dc.zip chromium_src-dfdde660bba559dec7b5baffb5544e634447e0dc.tar.gz chromium_src-dfdde660bba559dec7b5baffb5544e634447e0dc.tar.bz2 |
Create MemoryPressure component.
This is the first CL in the refactoring of base/memory/memory_pressure_*. Futher CLs will introduce calculators for each platform, the notification and UMA reporting logic.
BUG=520962
Review URL: https://codereview.chromium.org/1293873002
Cr-Commit-Position: refs/heads/master@{#345081}
20 files changed, 1201 insertions, 0 deletions
diff --git a/components/BUILD.gn b/components/BUILD.gn index dde6837..7d2729b 100644 --- a/components/BUILD.gn +++ b/components/BUILD.gn @@ -61,6 +61,7 @@ group("all_components") { "//components/language_usage_metrics", "//components/leveldb_proto", "//components/login", + "//components/memory_pressure", "//components/metrics", "//components/mime_util", "//components/navigation_interception", @@ -333,6 +334,7 @@ test("components_unittests") { "//components/keyed_service/core:unit_tests", "//components/leveldb_proto:unit_tests", "//components/login:unit_tests", + "//components/memory_pressure:unit_tests", "//components/metrics:unit_tests", "//components/mime_util:unit_tests", "//components/offline_pages:unit_tests", diff --git a/components/OWNERS b/components/OWNERS index 47bd329..f4f3813 100644 --- a/components/OWNERS +++ b/components/OWNERS @@ -150,6 +150,10 @@ per-file login*=dzhioev@chromium.org per-file login*=nkostylev@chromium.org per-file login*=achuith@chromium.org +per-file memory_pressure*=chrisha@chromium.org +per-file memory_pressure*=shrike@chromium.org +per-file memory_pressure*=skuhne@chromium.org + per-file metrics*=asvitkine@chromium.org per-file metrics*=mpearson@chromium.org per-file metrics*=jar@chromium.org diff --git a/components/components.gyp b/components/components.gyp index 630c5e6..9990cbf 100644 --- a/components/components.gyp +++ b/components/components.gyp @@ -42,6 +42,7 @@ 'language_usage_metrics.gypi', 'leveldb_proto.gypi', 'login.gypi', + 'memory_pressure.gypi', 'metrics.gypi', 'navigation_metrics.gypi', 'network_hints.gypi', diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 4e4a6bc..f40253e 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -298,6 +298,12 @@ 'login_unittest_sources': [ 'login/screens/screen_context_unittest.cc', ], + 'memory_pressure_unittest_sources': [ + 'memory_pressure/direct_memory_pressure_calculator_win_unittest.cc', + 'memory_pressure/filtered_memory_pressure_calculator_unittest.cc', + 'memory_pressure/test_memory_pressure_calculator.cc', + 'memory_pressure/test_memory_pressure_calculator.h', + ], 'metrics_unittest_sources': [ 'metrics/call_stack_profile_metrics_provider_unittest.cc', 'metrics/compression_utils_unittest.cc', @@ -763,6 +769,7 @@ '<@(language_usage_metrics_unittest_sources)', '<@(leveldb_proto_unittest_sources)', '<@(login_unittest_sources)', + '<@(memory_pressure_unittest_sources)', '<@(metrics_unittest_sources)', '<@(mime_util_unittest_sources)', '<@(network_time_unittest_sources)', @@ -868,6 +875,7 @@ 'components.gyp:leveldb_proto', 'components.gyp:leveldb_proto_test_support', 'components.gyp:login', + 'components.gyp:memory_pressure', 'components.gyp:metrics', 'components.gyp:metrics_gpu', 'components.gyp:metrics_net', diff --git a/components/memory_pressure.gypi b/components/memory_pressure.gypi new file mode 100644 index 0000000..40fb726 --- /dev/null +++ b/components/memory_pressure.gypi @@ -0,0 +1,29 @@ +# Copyright 2015 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. + +{ + 'targets': [ + { + # GN version: //components/memory_pressure + 'target_name': 'memory_pressure', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + '../base/base.gyp:base', + ], + 'sources': [ + 'memory_pressure/direct_memory_pressure_calculator.cc', + 'memory_pressure/direct_memory_pressure_calculator.h', + 'memory_pressure/direct_memory_pressure_calculator_win.cc', + 'memory_pressure/filtered_memory_pressure_calculator.cc', + 'memory_pressure/filtered_memory_pressure_calculator.h', + 'memory_pressure/memory_pressure_calculator.h', + 'memory_pressure/memory_pressure_listener.cc', + 'memory_pressure/memory_pressure_listener.h', + ], + }, + ], +} diff --git a/components/memory_pressure/BUILD.gn b/components/memory_pressure/BUILD.gn new file mode 100644 index 0000000..3828f48 --- /dev/null +++ b/components/memory_pressure/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright 2015 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. + +# GYP version: components/memory_pressure.gypi:memory_pressure +source_set("memory_pressure") { + sources = [ + "direct_memory_pressure_calculator.cc", + "direct_memory_pressure_calculator.h", + "direct_memory_pressure_calculator_win.cc", + "filtered_memory_pressure_calculator.cc", + "filtered_memory_pressure_calculator.h", + "memory_pressure_calculator.h", + "memory_pressure_listener.cc", + "memory_pressure_listener.h", + ] + + deps = [ + "//base", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ + "direct_memory_pressure_calculator_win_unittest.cc", + "filtered_memory_pressure_calculator_unittest.cc", + "test_memory_pressure_calculator.cc", + "test_memory_pressure_calculator.h", + ] + + deps = [ + ":memory_pressure", + "//base/test:test_support", + "//testing/gtest", + ] +} diff --git a/components/memory_pressure/DEPS b/components/memory_pressure/DEPS new file mode 100644 index 0000000..5cd0867 --- /dev/null +++ b/components/memory_pressure/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+base", +] diff --git a/components/memory_pressure/OWNERS b/components/memory_pressure/OWNERS new file mode 100644 index 0000000..1f45750 --- /dev/null +++ b/components/memory_pressure/OWNERS @@ -0,0 +1,3 @@ +chrisha@chromium.org +shrike@chromium.org +skuhne@chromium.org diff --git a/components/memory_pressure/direct_memory_pressure_calculator.cc b/components/memory_pressure/direct_memory_pressure_calculator.cc new file mode 100644 index 0000000..88b5794 --- /dev/null +++ b/components/memory_pressure/direct_memory_pressure_calculator.cc @@ -0,0 +1,18 @@ +// Copyright 2015 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/memory_pressure/direct_memory_pressure_calculator.h" + +namespace memory_pressure { + +#if defined(MEMORY_PRESSURE_IS_POLLING) + +bool DirectMemoryPressureCalculator::GetSystemMemoryInfo( + base::SystemMemoryInfoKB* mem_info) const { + return base::GetSystemMemoryInfo(mem_info); +} + +#endif // defined(MEMORY_PRESSURE_IS_POLLING) + +} // namespace memory_pressure diff --git a/components/memory_pressure/direct_memory_pressure_calculator.h b/components/memory_pressure/direct_memory_pressure_calculator.h new file mode 100644 index 0000000..98d3e16 --- /dev/null +++ b/components/memory_pressure/direct_memory_pressure_calculator.h @@ -0,0 +1,77 @@ +// Copyright 2015 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_MEMORY_PRESSURE_DIRECT_MEMORY_PRESSURE_CALCULATOR_H_ +#define COMPONENTS_MEMORY_PRESSURE_DIRECT_MEMORY_PRESSURE_CALCULATOR_H_ + +#include "components/memory_pressure/memory_pressure_calculator.h" + +#include "base/process/process_metrics.h" + +namespace memory_pressure { + +#if defined(MEMORY_PRESSURE_IS_POLLING) + +// OS-specific implementation of MemoryPressureCalculator. This is only defined +// and used on platforms that do not have native memory pressure signals +// (ChromeOS, Linux, Windows). OSes that do have native signals simply hook into +// the appropriate subsystem (Android, Mac OS X). +class DirectMemoryPressureCalculator : public MemoryPressureCalculator { + public: +#if defined(OS_WIN) + // Exposed for unittesting. See .cc file for detailed discussion of these + // constants. + static const int kLargeMemoryThresholdMb; + static const int kSmallMemoryDefaultModerateThresholdMb; + static const int kSmallMemoryDefaultCriticalThresholdMb; + static const int kLargeMemoryDefaultModerateThresholdMb; + static const int kLargeMemoryDefaultCriticalThresholdMb; +#endif + + // Default constructor. Will choose thresholds automatically based on the + // actual amount of system memory installed. + DirectMemoryPressureCalculator(); + + // Constructor with explicit memory thresholds. These represent the amount of + // free memory below which the applicable memory pressure state applies. + DirectMemoryPressureCalculator(int moderate_threshold_mb, + int critical_threshold_mb); + + virtual ~DirectMemoryPressureCalculator() {} + + // Calculates the current pressure level. + MemoryPressureLevel CalculateCurrentPressureLevel() override; + +#if defined(OS_WIN) + int moderate_threshold_mb() const { return moderate_threshold_mb_; } + int critical_threshold_mb() const { return critical_threshold_mb_; } +#endif + + private: + friend class TestDirectMemoryPressureCalculator; + + // Gets system memory status. This is virtual as a unittesting hook and by + // default this invokes base::GetSystemMemoryInfo. + virtual bool GetSystemMemoryInfo(base::SystemMemoryInfoKB* mem_info) const; + +#if defined(OS_WIN) + // Uses GetSystemMemoryInfo to automatically infer appropriate values for + // moderate_threshold_mb_ and critical_threshold_mb_. + void InferThresholds(); + + // Threshold amounts of available memory that trigger pressure levels. See + // memory_pressure_monitor_win.cc for a discussion of reasonable values for + // these. + int moderate_threshold_mb_; + int critical_threshold_mb_; +#endif + + DISALLOW_COPY_AND_ASSIGN(DirectMemoryPressureCalculator); +}; + +#endif // defined(MEMORY_PRESSURE_IS_POLLING) + +} // namespace memory_pressure + +#endif // COMPONENTS_MEMORY_PRESSURE_DIRECT_MEMORY_PRESSURE_CALCULATOR_H_ diff --git a/components/memory_pressure/direct_memory_pressure_calculator_win.cc b/components/memory_pressure/direct_memory_pressure_calculator_win.cc new file mode 100644 index 0000000..25d7d7b --- /dev/null +++ b/components/memory_pressure/direct_memory_pressure_calculator_win.cc @@ -0,0 +1,104 @@ +// Copyright 2015 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/memory_pressure/direct_memory_pressure_calculator.h" + +namespace memory_pressure { + +namespace { + +static const int kKBperMB = 1024; + +} // namespace + +// A system is considered 'high memory' if it has more than 1.5GB of system +// memory available for use by the memory manager (not reserved for hardware +// and drivers). This is a fuzzy version of the ~2GB discussed below. +const int DirectMemoryPressureCalculator::kLargeMemoryThresholdMb = 1536; + +// These are the default thresholds used for systems with < ~2GB of physical +// memory. Such systems have been observed to always maintain ~100MB of +// available memory, paging until that is the case. To try to avoid paging a +// threshold slightly above this is chosen. The moderate threshold is slightly +// less grounded in reality and chosen as 2.5x critical. +const int + DirectMemoryPressureCalculator::kSmallMemoryDefaultModerateThresholdMb = + 500; +const int + DirectMemoryPressureCalculator::kSmallMemoryDefaultCriticalThresholdMb = + 200; + +// These are the default thresholds used for systems with >= ~2GB of physical +// memory. Such systems have been observed to always maintain ~300MB of +// available memory, paging until that is the case. +const int + DirectMemoryPressureCalculator::kLargeMemoryDefaultModerateThresholdMb = + 1000; +const int + DirectMemoryPressureCalculator::kLargeMemoryDefaultCriticalThresholdMb = + 400; + +DirectMemoryPressureCalculator::DirectMemoryPressureCalculator() + : moderate_threshold_mb_(0), critical_threshold_mb_(0) { + InferThresholds(); +} + +DirectMemoryPressureCalculator::DirectMemoryPressureCalculator( + int moderate_threshold_mb, + int critical_threshold_mb) + : moderate_threshold_mb_(moderate_threshold_mb), + critical_threshold_mb_(critical_threshold_mb) { + DCHECK_GE(moderate_threshold_mb_, critical_threshold_mb_); + DCHECK_LE(0, critical_threshold_mb_); +} + +DirectMemoryPressureCalculator::MemoryPressureLevel +DirectMemoryPressureCalculator::CalculateCurrentPressureLevel() { + base::SystemMemoryInfoKB mem_info = {}; + if (!GetSystemMemoryInfo(&mem_info)) + return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; + + // How much system memory is actively available for use right now, in MBs. + int phys_free = mem_info.free / kKBperMB; + + // TODO(chrisha): This should eventually care about address space pressure, + // but the browser process (where this is running) effectively never runs out + // of address space. Renderers occasionally do, but it does them no good to + // have the browser process monitor address space pressure. Long term, + // renderers should run their own address space pressure monitors and act + // accordingly, with the browser making cross-process decisions based on + // system memory pressure. + + // Determine if the physical memory is under critical memory pressure. + if (phys_free <= critical_threshold_mb_) + return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL; + + // Determine if the physical memory is under moderate memory pressure. + if (phys_free <= moderate_threshold_mb_) + return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE; + + // No memory pressure was detected. + return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; +} + +void DirectMemoryPressureCalculator::InferThresholds() { + // Determine if the memory installed is 'large' or 'small'. Default to 'large' + // on failure, which uses more conservative thresholds. + bool large_memory = true; + base::SystemMemoryInfoKB mem_info = {}; + if (GetSystemMemoryInfo(&mem_info)) { + large_memory = mem_info.total / kKBperMB >= + DirectMemoryPressureCalculator::kLargeMemoryThresholdMb; + } + + if (large_memory) { + moderate_threshold_mb_ = kLargeMemoryDefaultModerateThresholdMb; + critical_threshold_mb_ = kLargeMemoryDefaultCriticalThresholdMb; + } else { + moderate_threshold_mb_ = kSmallMemoryDefaultModerateThresholdMb; + critical_threshold_mb_ = kSmallMemoryDefaultCriticalThresholdMb; + } +} + +} // namespace memory_pressure diff --git a/components/memory_pressure/direct_memory_pressure_calculator_win_unittest.cc b/components/memory_pressure/direct_memory_pressure_calculator_win_unittest.cc new file mode 100644 index 0000000..c4aa962 --- /dev/null +++ b/components/memory_pressure/direct_memory_pressure_calculator_win_unittest.cc @@ -0,0 +1,175 @@ +// Copyright 2015 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/memory_pressure/direct_memory_pressure_calculator.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace memory_pressure { + +namespace { + +static const int kKBperMB = 1024; + +} // namespace + +// This is out of the anonymous namespace space because it is a friend of +// DirectMemoryPressureCalculator. +class TestDirectMemoryPressureCalculator + : public DirectMemoryPressureCalculator { + public: + explicit TestDirectMemoryPressureCalculator(bool large_memory) + : DirectMemoryPressureCalculator(20, 10) { + // The values passed to the MemoryPressureCalculator constructor are dummy + // values that are immediately overwritten by InferTresholds. + + // Generate a plausible amount of memory. + mem_info_.total = GenerateTotalMemoryMb(large_memory) * kKBperMB; + + // Run InferThresholds using the test fixture's GetSystemMemoryStatus. + InferThresholds(); + } + + TestDirectMemoryPressureCalculator(int total_memory_mb, + int moderate_threshold_mb, + int critical_threshold_mb) + : DirectMemoryPressureCalculator(moderate_threshold_mb, + critical_threshold_mb) { + mem_info_.total = total_memory_mb * kKBperMB; + } + + // Generates an amount of total memory that is consistent with the requested + // memory model. + static int GenerateTotalMemoryMb(bool large_memory) { + int total_mb = 64; + while (total_mb < kLargeMemoryThresholdMb) + total_mb *= 2; + if (large_memory) + return total_mb * 2; + return total_mb / 2; + } + + // Sets up the memory status to reflect the provided absolute memory left. + void SetMemoryFree(int phys_left_mb) { + // |total| is set in the constructor and not modified. + + // Set the amount of free memory. + mem_info_.free = phys_left_mb * kKBperMB; + DCHECK_LT(mem_info_.free, mem_info_.total); + } + + void SetNone() { SetMemoryFree(moderate_threshold_mb() + 1); } + + void SetModerate() { SetMemoryFree(moderate_threshold_mb() - 1); } + + void SetCritical() { SetMemoryFree(critical_threshold_mb() - 1); } + + private: + bool GetSystemMemoryInfo(base::SystemMemoryInfoKB* mem_info) const override { + // Simply copy the memory information set by the test fixture. + *mem_info = mem_info_; + return true; + } + + base::SystemMemoryInfoKB mem_info_; +}; + +class DirectMemoryPressureCalculatorTest : public testing::Test { + public: + void CalculateCurrentPressureLevelTest( + TestDirectMemoryPressureCalculator* calc) { + int mod = calc->moderate_threshold_mb(); + calc->SetMemoryFree(mod + 1); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + calc->CalculateCurrentPressureLevel()); + + calc->SetNone(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + calc->CalculateCurrentPressureLevel()); + + calc->SetMemoryFree(mod); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + calc->CalculateCurrentPressureLevel()); + + calc->SetMemoryFree(mod - 1); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + calc->CalculateCurrentPressureLevel()); + + int crit = calc->critical_threshold_mb(); + calc->SetMemoryFree(crit + 1); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + calc->CalculateCurrentPressureLevel()); + + calc->SetModerate(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + calc->CalculateCurrentPressureLevel()); + + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + calc->CalculateCurrentPressureLevel()); + + calc->SetMemoryFree(crit); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + calc->CalculateCurrentPressureLevel()); + + calc->SetMemoryFree(crit - 1); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + calc->CalculateCurrentPressureLevel()); + + calc->SetCritical(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + calc->CalculateCurrentPressureLevel()); + } +}; + +// Tests the fundamental direct calculation of memory pressure with automatic +// small-memory thresholds. +TEST_F(DirectMemoryPressureCalculatorTest, + CalculateCurrentMemoryPressureLevelSmall) { + static const int kModerateMb = + DirectMemoryPressureCalculator::kSmallMemoryDefaultModerateThresholdMb; + static const int kCriticalMb = + DirectMemoryPressureCalculator::kSmallMemoryDefaultCriticalThresholdMb; + + TestDirectMemoryPressureCalculator calc(false); // Small-memory model. + + EXPECT_EQ(kModerateMb, calc.moderate_threshold_mb()); + EXPECT_EQ(kCriticalMb, calc.critical_threshold_mb()); + + ASSERT_NO_FATAL_FAILURE(CalculateCurrentPressureLevelTest(&calc)); +} + +// Tests the fundamental direct calculation of memory pressure with automatic +// large-memory thresholds. +TEST_F(DirectMemoryPressureCalculatorTest, + CalculateCurrentMemoryPressureLevelLarge) { + static const int kModerateMb = + DirectMemoryPressureCalculator::kLargeMemoryDefaultModerateThresholdMb; + static const int kCriticalMb = + DirectMemoryPressureCalculator::kLargeMemoryDefaultCriticalThresholdMb; + + TestDirectMemoryPressureCalculator calc(true); // Large-memory model. + + EXPECT_EQ(kModerateMb, calc.moderate_threshold_mb()); + EXPECT_EQ(kCriticalMb, calc.critical_threshold_mb()); + + ASSERT_NO_FATAL_FAILURE(CalculateCurrentPressureLevelTest(&calc)); +} + +// Tests the fundamental direct calculation of memory pressure with manually +// specified threshold levels. +TEST_F(DirectMemoryPressureCalculatorTest, + CalculateCurrentMemoryPressureLevelCustom) { + static const int kSystemMb = 512; + static const int kModerateMb = 256; + static const int kCriticalMb = 128; + + TestDirectMemoryPressureCalculator calc(kSystemMb, kModerateMb, kCriticalMb); + + EXPECT_EQ(kModerateMb, calc.moderate_threshold_mb()); + EXPECT_EQ(kCriticalMb, calc.critical_threshold_mb()); + + ASSERT_NO_FATAL_FAILURE(CalculateCurrentPressureLevelTest(&calc)); +} + +} // namespace memory_pressure diff --git a/components/memory_pressure/filtered_memory_pressure_calculator.cc b/components/memory_pressure/filtered_memory_pressure_calculator.cc new file mode 100644 index 0000000..9f5aa9d --- /dev/null +++ b/components/memory_pressure/filtered_memory_pressure_calculator.cc @@ -0,0 +1,91 @@ +// Copyright 2015 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/memory_pressure/filtered_memory_pressure_calculator.h" + +#include "base/time/default_tick_clock.h" + +namespace memory_pressure { + +#if defined(MEMORY_PRESSURE_IS_POLLING) + +// 100ms (10Hz) allows a relatively fast respsonse time for rapidly increasing +// memory usage, but limits the amount of work done in the calculator. +const int FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs = 100; + +// These values were experimentally obtained during the initial ChromeOS only +// implementation of this feature. By spending a significant cooldown period in +// at a higher pressure level more time is dedicated to freeing resources and +// less churn occurs in the MemoryPressureListener event stream. +const int FilteredMemoryPressureCalculator::kCriticalPressureCooldownPeriodMs = + 5000; +const int FilteredMemoryPressureCalculator::kModeratePressureCooldownPeriodMs = + 5000; + +FilteredMemoryPressureCalculator::FilteredMemoryPressureCalculator( + scoped_ptr<MemoryPressureCalculator> pressure_calculator) + : tick_clock_(new base::DefaultTickClock()), + pressure_calculator_(pressure_calculator.Pass()), + current_pressure_level_( + MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE), + samples_taken_(false), + cooldown_in_progress_(false) {} + +FilteredMemoryPressureCalculator::MemoryPressureLevel +FilteredMemoryPressureCalculator::CalculateCurrentPressureLevel() { + base::TimeTicks now = tick_clock_->NowTicks(); + + // If its too soon to take a sample then return the precalculated value. + if (samples_taken_ && + (now - last_sample_time_) < + base::TimeDelta::FromMilliseconds(kMinimumTimeBetweenSamplesMs)) { + return current_pressure_level_; + } + + // Take a sample. + samples_taken_ = true; + last_sample_time_ = now; + MemoryPressureLevel level = + pressure_calculator_->CalculateCurrentPressureLevel(); + + // The pressure hasn't changed or has gone up. In either case this is the end + // of a cooldown period if one was in progress. + if (level >= current_pressure_level_) { + cooldown_in_progress_ = false; + current_pressure_level_ = level; + return level; + } + + // The pressure has gone down, so apply cooldown hysteresis. + if (cooldown_in_progress_) { + cooldown_high_tide_ = std::max(cooldown_high_tide_, level); + + // Get the cooldown period for the current level. + int cooldown_ms = kCriticalPressureCooldownPeriodMs; + if (current_pressure_level_ == + MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE) { + cooldown_ms = kModeratePressureCooldownPeriodMs; + } + base::TimeDelta cooldown_period = + base::TimeDelta::FromMilliseconds(cooldown_ms); + + if (now - cooldown_start_time_ >= cooldown_period) { + // The cooldown has completed successfully, so transition the pressure + // level. + cooldown_in_progress_ = false; + current_pressure_level_ = cooldown_high_tide_; + } + } else { + // Start a new cooldown period. + cooldown_in_progress_ = true; + cooldown_start_time_ = now; + cooldown_high_tide_ = level; + } + + return current_pressure_level_; +} + +#endif // defined(MEMORY_PRESSURE_IS_POLLING) + +} // namespace memory_pressure diff --git a/components/memory_pressure/filtered_memory_pressure_calculator.h b/components/memory_pressure/filtered_memory_pressure_calculator.h new file mode 100644 index 0000000..9d9b902 --- /dev/null +++ b/components/memory_pressure/filtered_memory_pressure_calculator.h @@ -0,0 +1,79 @@ +// Copyright 2015 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_MEMORY_PRESSURE_FILTERED_MEMORY_PRESSURE_CALCULATOR_H_ +#define COMPONENTS_MEMORY_PRESSURE_FILTERED_MEMORY_PRESSURE_CALCULATOR_H_ + +#include "base/time/tick_clock.h" +#include "components/memory_pressure/memory_pressure_calculator.h" + +namespace memory_pressure { + +#if defined(MEMORY_PRESSURE_IS_POLLING) + +// A utility class that provides rate-limiting and hysteresis on raw memory +// pressure calculations. This is identical across all platforms, but only used +// on those that do not have native memory pressure signals. +class FilteredMemoryPressureCalculator : public MemoryPressureCalculator { + public: + // The minimum time that must pass between successive polls. This enforces an + // upper bound on the rate of calls to the contained MemoryPressureCalculator. + static const int kMinimumTimeBetweenSamplesMs; + + // The cooldown period when transitioning from critical to moderate/no memory + // pressure, or from moderate to none. + static const int kCriticalPressureCooldownPeriodMs; + static const int kModeratePressureCooldownPeriodMs; + + explicit FilteredMemoryPressureCalculator( + scoped_ptr<MemoryPressureCalculator> pressure_calculator); + ~FilteredMemoryPressureCalculator() {} + + // Calculates the current pressure level. + MemoryPressureLevel CalculateCurrentPressureLevel() override; + + // Testing seam for configuring the tick clock in use. + void set_tick_clock(scoped_ptr<base::TickClock> tick_clock) { + tick_clock_ = tick_clock.Pass(); + } + + // Accessors for unittesting. + bool cooldown_in_progress() const { return cooldown_in_progress_; } + base::TimeTicks cooldown_start_time() const { return cooldown_start_time_; } + MemoryPressureLevel cooldown_high_tide() const { return cooldown_high_tide_; } + + private: + friend class TestFilteredMemoryPressureCalculator; + + // The delegate tick clock. This is settable as a testing seam. + scoped_ptr<base::TickClock> tick_clock_; + + // The delegate pressure calculator. Provided by the constructor. + scoped_ptr<MemoryPressureCalculator> pressure_calculator_; + + // The memory pressure currently being reported. + MemoryPressureLevel current_pressure_level_; + + // The last time a sample was taken. + bool samples_taken_; + base::TimeTicks last_sample_time_; + + // State of an ongoing cooldown period, if any. The high-tide line indicates + // the highest memory pressure level (*below* the current one) that was + // encountered during the cooldown period. This allows a cooldown to + // transition directly from critical to none if *no* moderate pressure signals + // were seen during the period, otherwise it forces it to pass through a + // moderate cooldown as well. + bool cooldown_in_progress_; + base::TimeTicks cooldown_start_time_; + MemoryPressureLevel cooldown_high_tide_; + + DISALLOW_COPY_AND_ASSIGN(FilteredMemoryPressureCalculator); +}; + +#endif // defined(MEMORY_PRESSURE_IS_POLLING) + +} // namespace memory_pressure + +#endif // COMPONENTS_MEMORY_PRESSURE_FILTERED_MEMORY_PRESSURE_CALCULATOR_H_ diff --git a/components/memory_pressure/filtered_memory_pressure_calculator_unittest.cc b/components/memory_pressure/filtered_memory_pressure_calculator_unittest.cc new file mode 100644 index 0000000..81b7723 --- /dev/null +++ b/components/memory_pressure/filtered_memory_pressure_calculator_unittest.cc @@ -0,0 +1,298 @@ +// Copyright 2015 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/memory_pressure/filtered_memory_pressure_calculator.h" + +#include "base/test/simple_test_tick_clock.h" +#include "components/memory_pressure/test_memory_pressure_calculator.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace memory_pressure { + +#if defined(MEMORY_PRESSURE_IS_POLLING) + +class FilteredMemoryPressureCalculatorTest : public testing::Test { + public: + FilteredMemoryPressureCalculatorTest() + : calculator_(nullptr), tick_clock_(nullptr) {} + + void SetUp() override { + // Ownership of the calculator and tick clock are both passed to the filter. + calculator_ = new TestMemoryPressureCalculator(); + tick_clock_ = new base::SimpleTestTickClock(); + filter_.reset(new FilteredMemoryPressureCalculator( + scoped_ptr<MemoryPressureCalculator>(calculator_))); + filter_->set_tick_clock(scoped_ptr<base::TickClock>(tick_clock_)); + } + + void TearDown() override { + calculator_ = nullptr; + tick_clock_ = nullptr; + filter_.reset(); + } + + // Advances the tick clock by the given number of milliseconds. + void Tick(int ms) { + tick_clock_->Advance(base::TimeDelta::FromMilliseconds(ms)); + } + + // Delegates that are inject into the filter under test. + TestMemoryPressureCalculator* calculator_; + base::SimpleTestTickClock* tick_clock_; + + // The object under test. + scoped_ptr<FilteredMemoryPressureCalculator> filter_; + + private: + DISALLOW_COPY_AND_ASSIGN(FilteredMemoryPressureCalculatorTest); +}; + +TEST_F(FilteredMemoryPressureCalculatorTest, FirstCallInvokesCalculator) { + calculator_->SetCritical(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(1, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); +} + +TEST_F(FilteredMemoryPressureCalculatorTest, CalculatorNotAlwaysInvoked) { + calculator_->SetModerate(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(1, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); + + // Change the pressure reported by the caculator, but don't expect the filter + // to report the critical value until a minimum sampling period has passed. + // The level has to be increasing so that hysteresis doesn't come into play. + calculator_->SetCritical(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(1, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); + + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs / 2); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(1, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); + + // A sufficient time has passed so now it should report the new level. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(2, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); +} + +TEST_F(FilteredMemoryPressureCalculatorTest, CooldownCriticalToModerate) { + calculator_->SetCritical(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(1, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); + + // Initiate a cooldown period by jumping sufficiently far ahead for another + // sample. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + calculator_->SetModerate(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(2, calculator_->calls()); + EXPECT_TRUE(filter_->cooldown_in_progress()); + EXPECT_EQ(tick_clock_->NowTicks(), filter_->cooldown_start_time()); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->cooldown_high_tide()); + + // Step another sample interval and it should still not have changed. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(3, calculator_->calls()); + EXPECT_TRUE(filter_->cooldown_in_progress()); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->cooldown_high_tide()); + + // Step the cooldown period and it should change state. + Tick(FilteredMemoryPressureCalculator::kCriticalPressureCooldownPeriodMs); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(4, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); +} + +TEST_F(FilteredMemoryPressureCalculatorTest, + CooldownCriticalToModerateViaNone) { + calculator_->SetCritical(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(1, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); + + // Initiate a cooldown period by jumping sufficiently far ahead for another + // sample. First go directly to no memory pressure before passing back through + // moderate. The final result should be a moderate memory pressure. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + calculator_->SetNone(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(2, calculator_->calls()); + EXPECT_TRUE(filter_->cooldown_in_progress()); + EXPECT_EQ(tick_clock_->NowTicks(), filter_->cooldown_start_time()); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + filter_->cooldown_high_tide()); + + // Step another sample interval and it should still not have changed. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + calculator_->SetModerate(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(3, calculator_->calls()); + EXPECT_TRUE(filter_->cooldown_in_progress()); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->cooldown_high_tide()); + + // Step the cooldown period and it should change state. + Tick(FilteredMemoryPressureCalculator::kCriticalPressureCooldownPeriodMs); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(4, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); +} + +TEST_F(FilteredMemoryPressureCalculatorTest, CooldownCriticalToNone) { + calculator_->SetCritical(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(1, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); + + // Initiate a cooldown period by jumping sufficiently far ahead for another + // sample. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + calculator_->SetNone(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(2, calculator_->calls()); + EXPECT_TRUE(filter_->cooldown_in_progress()); + EXPECT_EQ(tick_clock_->NowTicks(), filter_->cooldown_start_time()); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + filter_->cooldown_high_tide()); + + // Step another sample interval and it should still not have changed. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(3, calculator_->calls()); + EXPECT_TRUE(filter_->cooldown_in_progress()); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + filter_->cooldown_high_tide()); + + // Step the cooldown period and it should change state. + Tick(FilteredMemoryPressureCalculator::kCriticalPressureCooldownPeriodMs); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(4, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); +} + +TEST_F(FilteredMemoryPressureCalculatorTest, CooldownModerateToNone) { + calculator_->SetModerate(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(1, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); + + // Initiate a cooldown period by jumping sufficiently far ahead for another + // sample. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + calculator_->SetNone(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(2, calculator_->calls()); + EXPECT_TRUE(filter_->cooldown_in_progress()); + EXPECT_EQ(tick_clock_->NowTicks(), filter_->cooldown_start_time()); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + filter_->cooldown_high_tide()); + + // Step another sample interval and it should still not have changed. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(3, calculator_->calls()); + EXPECT_TRUE(filter_->cooldown_in_progress()); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + filter_->cooldown_high_tide()); + + // Step the cooldown period and it should change state. + Tick(FilteredMemoryPressureCalculator::kModeratePressureCooldownPeriodMs); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(4, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); +} + +TEST_F(FilteredMemoryPressureCalculatorTest, InterruptedCooldownModerate) { + calculator_->SetModerate(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(1, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); + + // Initiate a cooldown period by jumping sufficiently far ahead for another + // sample. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + calculator_->SetNone(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(2, calculator_->calls()); + EXPECT_TRUE(filter_->cooldown_in_progress()); + EXPECT_EQ(tick_clock_->NowTicks(), filter_->cooldown_start_time()); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + filter_->cooldown_high_tide()); + + // Step another sample interval and it should still not have changed. Since + // the pressure level has increased back to moderate it should also end the + // cooldown period. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + calculator_->SetModerate(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(3, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); +} + +TEST_F(FilteredMemoryPressureCalculatorTest, InterruptedCooldownCritical) { + calculator_->SetModerate(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(1, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); + + // Initiate a cooldown period by jumping sufficiently far ahead for another + // sample. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + calculator_->SetNone(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(2, calculator_->calls()); + EXPECT_TRUE(filter_->cooldown_in_progress()); + EXPECT_EQ(tick_clock_->NowTicks(), filter_->cooldown_start_time()); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, + filter_->cooldown_high_tide()); + + // Step another sample interval and it should still not have changed. Since + // the pressure level has increased to critical it ends the cooldown period + // and immediately reports the critical memory pressure. + Tick(FilteredMemoryPressureCalculator::kMinimumTimeBetweenSamplesMs); + calculator_->SetCritical(); + EXPECT_EQ(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL, + filter_->CalculateCurrentPressureLevel()); + EXPECT_EQ(3, calculator_->calls()); + EXPECT_FALSE(filter_->cooldown_in_progress()); +} + +#endif // defined(MEMORY_PRESSURE_IS_POLLING) + +} // namespace memory_pressure diff --git a/components/memory_pressure/memory_pressure_calculator.h b/components/memory_pressure/memory_pressure_calculator.h new file mode 100644 index 0000000..b139efe --- /dev/null +++ b/components/memory_pressure/memory_pressure_calculator.h @@ -0,0 +1,32 @@ +// Copyright 2015 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_MEMORY_PRESSURE_MEMORY_PRESSURE_CALCULATOR_H_ +#define COMPONENTS_MEMORY_PRESSURE_MEMORY_PRESSURE_CALCULATOR_H_ + +#include "components/memory_pressure/memory_pressure_listener.h" + +namespace memory_pressure { + +#if defined(MEMORY_PRESSURE_IS_POLLING) + +// Interface for a utility class that calculates memory pressure. Only used on +// platforms without native memory pressure signals. +class MemoryPressureCalculator { + public: + using MemoryPressureLevel = MemoryPressureListener::MemoryPressureLevel; + + MemoryPressureCalculator() {} + virtual ~MemoryPressureCalculator() {} + virtual MemoryPressureLevel CalculateCurrentPressureLevel() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(MemoryPressureCalculator); +}; + +#endif // defined(MEMORY_PRESSURE_IS_POLLING) + +} // namespace memory_pressure + +#endif // COMPONENTS_MEMORY_PRESSURE_MEMORY_PRESSURE_CALCULATOR_H_ diff --git a/components/memory_pressure/memory_pressure_listener.cc b/components/memory_pressure/memory_pressure_listener.cc new file mode 100644 index 0000000..3d2fe1a --- /dev/null +++ b/components/memory_pressure/memory_pressure_listener.cc @@ -0,0 +1,61 @@ +// Copyright 2015 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/memory_pressure/memory_pressure_listener.h" + +#include "base/lazy_instance.h" +#include "base/observer_list_threadsafe.h" +#include "base/trace_event/trace_event.h" + +namespace memory_pressure { + +namespace { + +// ObserverListThreadSafe is RefCountedThreadSafe, this traits is needed +// to ensure the LazyInstance will hold a reference to it. +struct LeakyLazyObserverListTraits + : base::internal::LeakyLazyInstanceTraits< + base::ObserverListThreadSafe<MemoryPressureListener>> { + static base::ObserverListThreadSafe<MemoryPressureListener>* New( + void* instance) { + base::ObserverListThreadSafe<MemoryPressureListener>* ret = + base::internal::LeakyLazyInstanceTraits<base::ObserverListThreadSafe< + MemoryPressureListener>>::New(instance); + // Leaky. + ret->AddRef(); + return ret; + } +}; + +base::LazyInstance<base::ObserverListThreadSafe<MemoryPressureListener>, + LeakyLazyObserverListTraits> g_observers = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +MemoryPressureListener::MemoryPressureListener( + const MemoryPressureListener::MemoryPressureCallback& callback) + : callback_(callback) { + g_observers.Get().AddObserver(this); +} + +MemoryPressureListener::~MemoryPressureListener() { + g_observers.Get().RemoveObserver(this); +} + +void MemoryPressureListener::Notify(MemoryPressureLevel memory_pressure_level) { + callback_.Run(memory_pressure_level); +} + +// static +void MemoryPressureListener::NotifyMemoryPressure( + MemoryPressureLevel memory_pressure_level) { + DCHECK_NE(memory_pressure_level, MEMORY_PRESSURE_LEVEL_NONE); + TRACE_EVENT1("memory", "MemoryPressureListener::NotifyMemoryPressure", + "level", memory_pressure_level); + g_observers.Get().Notify(FROM_HERE, &MemoryPressureListener::Notify, + memory_pressure_level); +} + +} // namespace memory_pressure diff --git a/components/memory_pressure/memory_pressure_listener.h b/components/memory_pressure/memory_pressure_listener.h new file mode 100644 index 0000000..b99f8b2 --- /dev/null +++ b/components/memory_pressure/memory_pressure_listener.h @@ -0,0 +1,92 @@ +// Copyright 2015 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. + +// MemoryPressureListener provides static APIs for handling memory pressure on +// platforms that have such signals (Android, ChromeOS, Mac and Windows). When +// such signals are received the app will try to discard buffers that aren't +// deemed essential (individual modules will implement their own policy). + +#ifndef COMPONENTS_MEMORY_PRESSURE_MEMORY_PRESSURE_LISTENER_H_ +#define COMPONENTS_MEMORY_PRESSURE_MEMORY_PRESSURE_LISTENER_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/callback.h" + +// ChromeOS and Linux operating systems will be added to this as they are +// implemented. +#if defined(OS_WIN) +#define MEMORY_PRESSURE_IS_POLLING +#endif + +namespace memory_pressure { + +// To start listening, create a new instance, passing a callback to a +// function that takes a MemoryPressureLevel parameter. To stop listening, +// simply delete the listener object. The implementation guarantees +// that the callback will always be called on the thread that created +// the listener. +// +// Note that even on the same thread, the callback is not guaranteed to be +// called synchronously within the system memory pressure broadcast. +// Please see notes in MemoryPressureLevel enum below: some levels are +// absolutely critical, and if not enough memory is returned to the system, +// it'll potentially kill the app, and then later the app will have to be +// cold-started. +// +// Example: +// +// void OnMemoryPressure(MemoryPressureLevel memory_pressure_level) { +// ... +// } +// +// // Start listening. +// MemoryPressureListener* my_listener = +// new MemoryPressureListener(base::Bind(&OnMemoryPressure)); +// +// ... +// +// // Stop listening. +// delete my_listener; +// +class MemoryPressureListener { + public: + // A Java counterpart will be generated for this enum. + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base + enum MemoryPressureLevel { + // No problems, there is enough memory to use. This event is not sent via + // callback, but the enum is used in other places to find out the current + // state of the system. + MEMORY_PRESSURE_LEVEL_NONE = -1, + + // Modules are advised to free buffers that are cheap to re-allocate and not + // immediately needed. + MEMORY_PRESSURE_LEVEL_MODERATE = 0, + + // At this level, modules are advised to free all possible memory. The + // alternative is to be killed by the system, which means all memory will + // have to be re-created, plus the cost of a cold start. + MEMORY_PRESSURE_LEVEL_CRITICAL = 2, + }; + + typedef base::Callback<void(MemoryPressureLevel)> MemoryPressureCallback; + + explicit MemoryPressureListener( + const MemoryPressureCallback& memory_pressure_callback); + ~MemoryPressureListener(); + + // Intended for use by the platform specific implementation. + static void NotifyMemoryPressure(MemoryPressureLevel memory_pressure_level); + + private: + void Notify(MemoryPressureLevel memory_pressure_level); + + MemoryPressureCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(MemoryPressureListener); +}; + +} // namespace memory_pressure + +#endif // COMPONENTS_MEMORY_PRESSURE_MEMORY_PRESSURE_LISTENER_H_ diff --git a/components/memory_pressure/test_memory_pressure_calculator.cc b/components/memory_pressure/test_memory_pressure_calculator.cc new file mode 100644 index 0000000..2e0aa5b --- /dev/null +++ b/components/memory_pressure/test_memory_pressure_calculator.cc @@ -0,0 +1,38 @@ +// Copyright 2015 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/memory_pressure/test_memory_pressure_calculator.h" + +namespace memory_pressure { + +#if defined(MEMORY_PRESSURE_IS_POLLING) + +TestMemoryPressureCalculator::TestMemoryPressureCalculator() + : level_(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE), calls_(0) {} + +TestMemoryPressureCalculator::MemoryPressureLevel +TestMemoryPressureCalculator::CalculateCurrentPressureLevel() { + ++calls_; + return level_; +} + +void TestMemoryPressureCalculator::SetNone() { + level_ = MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; +} + +void TestMemoryPressureCalculator::SetModerate() { + level_ = MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE; +} + +void TestMemoryPressureCalculator::SetCritical() { + level_ = MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL; +} + +void TestMemoryPressureCalculator::ResetCalls() { + calls_ = 0; +} + +#endif // defined(MEMORY_PRESSURE_IS_POLLING) + +} // namespace memory_pressure diff --git a/components/memory_pressure/test_memory_pressure_calculator.h b/components/memory_pressure/test_memory_pressure_calculator.h new file mode 100644 index 0000000..20ad671 --- /dev/null +++ b/components/memory_pressure/test_memory_pressure_calculator.h @@ -0,0 +1,49 @@ +// Copyright 2015 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_MEMORY_PRESSURE_TEST_MEMORY_PRESSURE_CALCULATOR_H_ +#define COMPONENTS_MEMORY_PRESSURE_TEST_MEMORY_PRESSURE_CALCULATOR_H_ + +#include "components/memory_pressure/memory_pressure_calculator.h" + +namespace memory_pressure { + +#if defined(MEMORY_PRESSURE_IS_POLLING) + +// A mock memory pressure calculator for unittesting. +class TestMemoryPressureCalculator : public MemoryPressureCalculator { + public: + TestMemoryPressureCalculator(); + virtual ~TestMemoryPressureCalculator() {} + + // MemoryPressureCalculator implementation. + MemoryPressureLevel CalculateCurrentPressureLevel() override; + + // Sets the mock calculator to return no pressure. + void SetNone(); + + // Sets the mock calculator to return moderate pressure. + void SetModerate(); + + // Sets the mock calculator to return critical pressure. + void SetCritical(); + + // Resets the call counter to 'CalculateCurrentPressureLevel'. + void ResetCalls(); + + // Returns the number of calls to 'CalculateCurrentPressureLevel'. + int calls() const { return calls_; } + + private: + MemoryPressureLevel level_; + int calls_; + + DISALLOW_COPY_AND_ASSIGN(TestMemoryPressureCalculator); +}; + +#endif // defined(MEMORY_PRESSURE_IS_POLLING) + +} // namespace memory_pressure + +#endif // COMPONENTS_MEMORY_PRESSURE_TEST_MEMORY_PRESSURE_CALCULATOR_H_ |