| // Copyright 2016 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_monitor.h" |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/task_runner.h" |
| #include "base/time/tick_clock.h" |
| #include "components/memory_pressure/memory_pressure_calculator.h" |
| #include "components/memory_pressure/memory_pressure_stats_collector.h" |
| |
| namespace memory_pressure { |
| |
| namespace { |
| |
| using MemoryPressureLevel = MemoryPressureMonitor::MemoryPressureLevel; |
| const MemoryPressureLevel MEMORY_PRESSURE_LEVEL_NONE = |
| MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; |
| const MemoryPressureLevel MEMORY_PRESSURE_LEVEL_MODERATE = |
| MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE; |
| const MemoryPressureLevel MEMORY_PRESSURE_LEVEL_CRITICAL = |
| MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL; |
| |
| // Returns the polling/notification interval for the given pressure level. |
| int GetPollingIntervalMs(MemoryPressureLevel level) { |
| switch (level) { |
| case MEMORY_PRESSURE_LEVEL_NONE: |
| return MemoryPressureMonitor::kDefaultPollingIntervalMs; |
| |
| case MEMORY_PRESSURE_LEVEL_MODERATE: |
| return MemoryPressureMonitor::kNotificationIntervalPressureModerateMs; |
| |
| case MEMORY_PRESSURE_LEVEL_CRITICAL: |
| return MemoryPressureMonitor::kNotificationIntervalPressureCriticalMs; |
| } |
| |
| NOTREACHED(); |
| return 0; |
| } |
| |
| base::TimeDelta GetPollingInterval(MemoryPressureLevel level) { |
| return base::TimeDelta::FromMilliseconds(GetPollingIntervalMs(level)); |
| } |
| |
| // Serial number reserved for unscheduled checks as a result of external calls |
| // to the monitor. |
| const int kUnscheduledCheckSerial = 0; |
| |
| } // namespace |
| |
| #if !defined(MEMORY_PRESSURE_IS_POLLING) |
| // Default definition of this class. |
| // TODO(chrisha): Implement useful versions of this for affected platforms. |
| class MemoryPressureMonitorImpl {}; |
| #endif |
| |
| #if defined(MEMORY_PRESSURE_IS_POLLING) |
| MemoryPressureMonitor::MemoryPressureMonitor( |
| const scoped_refptr<base::TaskRunner>& task_runner, |
| base::TickClock* tick_clock, |
| MemoryPressureStatsCollector* stats_collector, |
| MemoryPressureCalculator* calculator, |
| const DispatchCallback& dispatch_callback) |
| : task_runner_(task_runner), |
| tick_clock_(tick_clock), |
| stats_collector_(stats_collector), |
| calculator_(calculator), |
| dispatch_callback_(dispatch_callback), |
| current_memory_pressure_level_(MEMORY_PRESSURE_LEVEL_NONE), |
| serial_number_(kUnscheduledCheckSerial), |
| weak_ptr_factory_(this) { |
| DCHECK(task_runner_.get()); |
| DCHECK(tick_clock_); |
| DCHECK(stats_collector_); |
| DCHECK(calculator_); |
| DCHECK(!dispatch_callback_.is_null()); |
| |
| Start(); |
| } |
| #else // MEMORY_PRESSURE_IS_POLLING |
| MemoryPressureMonitor::MemoryPressureMonitor( |
| const scoped_refptr<base::TaskRunner>& task_runner, |
| base::TickClock* tick_clock, |
| MemoryPressureStatsCollector* stats_collector, |
| const DispatchCallback& dispatch_callback, |
| MemoryPressureLevel initial_pressure_level) |
| : task_runner_(task_runner), |
| tick_clock_(tick_clock), |
| stats_collector_(stats_collector), |
| dispatch_callback_(dispatch_callback), |
| current_memory_pressure_level_(initial_pressure_level), |
| serial_number_(kUnscheduledCheckSerial), |
| weak_ptr_factory_(this) { |
| DCHECK(task_runner_.get()); |
| DCHECK(tick_clock_); |
| DCHECK(stats_collector_); |
| DCHECK(!dispatch_callback_.is_null()); |
| |
| Start(); |
| } |
| #endif // !MEMORY_PRESSURE_IS_POLLING |
| |
| MemoryPressureMonitor::~MemoryPressureMonitor() {} |
| |
| MemoryPressureLevel MemoryPressureMonitor::GetCurrentPressureLevel() { |
| base::AutoLock lock(lock_); |
| |
| #if defined(MEMORY_PRESSURE_IS_POLLING) |
| // Force an immediate pressure check on polling platforms. On non-polling |
| // platforms the current memory pressure is always valid. |
| CheckPressureAndUpdateStatsLocked(kUnscheduledCheckSerial); |
| #endif |
| |
| return current_memory_pressure_level_; |
| } |
| |
| void MemoryPressureMonitor::CheckMemoryPressureSoon() { |
| // This function is a nop on non-polling platforms. |
| #if defined(MEMORY_PRESSURE_IS_POLLING) |
| // Schedule a check to run as soon as possible. |
| base::AutoLock lock(lock_); |
| base::TimeTicks now = tick_clock_->NowTicks(); |
| ScheduleTaskLocked(now); |
| #endif |
| } |
| |
| #if !defined(MEMORY_PRESSURE_IS_POLLING) |
| // This is the entry point for OS notifications of pressure level changes. |
| void MemoryPressureMonitor::OnMemoryPressureChanged( |
| MemoryPressureLevel level) { |
| base::AutoLock lock(lock_); |
| |
| // Do nothing if the level hasn't changed. |
| if (level == current_memory_pressure_level_) |
| return; |
| |
| // Update the level and the stats. |
| current_memory_pressure_level_ = level; |
| stats_collector_->UpdateStatistics(current_memory_pressure_level_); |
| |
| // Only dispatch notifications if there is memory pressure. |
| if (current_memory_pressure_level_ > MEMORY_PRESSURE_LEVEL_NONE) { |
| last_notification_ = tick_clock_->NowTicks(); |
| dispatch_callback_.Run(current_memory_pressure_level_); |
| } |
| |
| ScheduleTaskIfNeededLocked(kUnscheduledCheckSerial); |
| } |
| #endif |
| |
| void MemoryPressureMonitor::Start() { |
| base::AutoLock lock(lock_); |
| |
| base::TimeTicks now = tick_clock_->NowTicks(); |
| |
| // Get the statistics collector warmed up by measuring the pressure level. |
| // Don't immediately fire a signal if already under memory pressure as the |
| // system is quite busy with startup right now. Wait until the first |
| // renotification is required, allowing the browser time to get started. |
| // Non-polling implementations set the initial memory pressure level in the |
| // constructor. |
| #if defined(MEMORY_PRESSURE_IS_POLLING) |
| current_memory_pressure_level_ = calculator_->CalculateCurrentPressureLevel(); |
| last_check_ = now; |
| #endif |
| |
| last_notification_ = now; |
| stats_collector_->UpdateStatistics(current_memory_pressure_level_); |
| ScheduleTaskIfNeededLocked(kUnscheduledCheckSerial); |
| } |
| |
| void MemoryPressureMonitor::CheckPressureAndUpdateStats(int serial) { |
| // This should only ever be used by scheduled checks. |
| DCHECK_NE(kUnscheduledCheckSerial, serial); |
| base::AutoLock lock(lock_); |
| CheckPressureAndUpdateStatsLocked(serial); |
| } |
| |
| void MemoryPressureMonitor::CheckPressureAndUpdateStatsLocked(int serial) { |
| lock_.AssertAcquired(); |
| |
| base::TimeTicks now = tick_clock_->NowTicks(); |
| MemoryPressureLevel old_level = current_memory_pressure_level_; |
| |
| #if defined(MEMORY_PRESSURE_IS_POLLING) |
| // Don't check again if pressure was calculated too recently. |
| DCHECK(!last_check_.is_null()); |
| if ((now - last_check_) >= |
| base::TimeDelta::FromMilliseconds(kMinimumTimeBetweenSamplesMs)) { |
| // Calculate the current pressure level and update statistics. |
| last_check_ = now; |
| current_memory_pressure_level_ = |
| calculator_->CalculateCurrentPressureLevel(); |
| stats_collector_->UpdateStatistics(current_memory_pressure_level_); |
| } |
| #else // MEMORY_PRESSURE_IS_POLLING |
| // On non-polling platforms this function can only be invoked by scheduled |
| // callbacks for updating statistics and sending renotifications. Simply |
| // update the statistics and move on. |
| DCHECK_NE(kUnscheduledCheckSerial, serial); |
| stats_collector_->UpdateStatistics(current_memory_pressure_level_); |
| #endif // !MEMORY_PRESSURE_IS_POLLING |
| |
| // Check if the level has changed or a renotification is required. |
| DCHECK(!last_notification_.is_null()); |
| if (current_memory_pressure_level_ != old_level || |
| now - last_notification_ >= |
| GetPollingInterval(current_memory_pressure_level_)) { |
| last_notification_ = now; |
| |
| // Only dispatch notifications if there is memory pressure. |
| if (current_memory_pressure_level_ > MEMORY_PRESSURE_LEVEL_NONE) |
| dispatch_callback_.Run(current_memory_pressure_level_); |
| } |
| |
| ScheduleTaskIfNeededLocked(serial); |
| } |
| |
| void MemoryPressureMonitor::ScheduleTaskIfNeededLocked(int serial) { |
| lock_.AssertAcquired(); |
| |
| // Remove this check from the set of scheduled checks. |
| if (serial != kUnscheduledCheckSerial) { |
| size_t erased = scheduled_checks_.erase(serial); |
| DCHECK_EQ(1u, erased); |
| } |
| |
| // Get the time of the soonest scheduled check. This linear scan is quick |
| // because the map will contain at most 1 entry per pressure level, and most |
| // commonly only 1 entry. |
| base::TimeTicks next_check; |
| for (const auto& check : scheduled_checks_) { |
| if (next_check.is_null() || check.second < next_check) |
| next_check = check.second; |
| } |
| |
| // Get the time of the required next check. |
| base::TimeTicks required_check = |
| last_notification_ + GetPollingInterval(current_memory_pressure_level_); |
| |
| // If there's already a check scheduled in time then don't schedule |
| // another. This lets the number of scheduled checks shrink back down to 1 |
| // as pressure changes occur. |
| if (!next_check.is_null() && next_check <= required_check) |
| return; |
| |
| ScheduleTaskLocked(required_check); |
| } |
| |
| void MemoryPressureMonitor::ScheduleTaskLocked(base::TimeTicks when) { |
| lock_.AssertAcquired(); |
| int serial = ++serial_number_; |
| |
| // Handle overflow. For simplicity keep serial numbers positive. 2^31 leaves |
| // room for 20 years of uptime in the worst case scenario (poll every second, |
| // constantly swinging through all the pressure levels). But you know... |
| // better safe the sorry! |
| if (serial < 0) { |
| serial_number_ = 1; |
| serial = 1; |
| } |
| |
| // It's entirely possible for |when| to be in the past relative to |now|, so |
| // bound |delay| from below. |
| base::TimeTicks now = tick_clock_->NowTicks(); |
| base::TimeDelta delay; // Initializes to zero. |
| if (when > now) |
| delay = when - now; |
| |
| // Schedule another check. |
| if (task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&MemoryPressureMonitor::CheckPressureAndUpdateStats, |
| weak_ptr_factory_.GetWeakPtr(), serial), |
| delay)) { |
| // If the task will run then add it to the map of scheduled checks. |
| scheduled_checks_[serial] = when; |
| } |
| } |
| |
| } // namespace memory_pressure |