blob: a0f67a8fa3179316f16eaf587a4c031f146ac4ce [file] [log] [blame]
// 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