blob: 23857d612a77702aef558a252f703246f77f7581 [file] [log] [blame]
// 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 "base/memory/memory_pressure_monitor_mac.h"
#include <CoreFoundation/CoreFoundation.h>
#include <dlfcn.h>
#include <sys/sysctl.h>
#include <cmath>
#include "base/bind.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "starboard/types.h"
// Redeclare for partial 10.9 availability.
DISPATCH_EXPORT const struct dispatch_source_type_s
_dispatch_source_type_memorypressure;
namespace {
static const int kUMATickSize = 5;
} // namespace
namespace base {
namespace mac {
MemoryPressureListener::MemoryPressureLevel
MemoryPressureMonitor::MemoryPressureLevelForMacMemoryPressureLevel(
int mac_memory_pressure_level) {
switch (mac_memory_pressure_level) {
case DISPATCH_MEMORYPRESSURE_NORMAL:
return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
case DISPATCH_MEMORYPRESSURE_WARN:
return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
case DISPATCH_MEMORYPRESSURE_CRITICAL:
return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
}
return MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
}
void MemoryPressureMonitor::OnRunLoopExit(CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void* info) {
MemoryPressureMonitor* self = static_cast<MemoryPressureMonitor*>(info);
self->UpdatePressureLevelOnRunLoopExit();
}
MemoryPressureMonitor::MemoryPressureMonitor()
: memory_level_event_source_(dispatch_source_create(
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE,
0,
DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL |
DISPATCH_MEMORYPRESSURE_NORMAL,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))),
dispatch_callback_(
base::Bind(&MemoryPressureListener::NotifyMemoryPressure)),
last_statistic_report_time_(CFAbsoluteTimeGetCurrent()),
last_pressure_level_(MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE),
subtick_seconds_(0) {
// Attach an event handler to the memory pressure event source.
if (memory_level_event_source_.get()) {
dispatch_source_set_event_handler(memory_level_event_source_, ^{
OnMemoryPressureChanged(memory_level_event_source_.get(),
dispatch_callback_);
});
// Start monitoring the event source.
dispatch_resume(memory_level_event_source_);
}
// Create a CFRunLoopObserver to check the memory pressure at the end of
// every pass through the event loop (modulo kUMATickSize).
CFRunLoopObserverContext observer_context = {0, this, NULL, NULL, NULL};
exit_observer_.reset(
CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopExit, true, 0,
OnRunLoopExit, &observer_context));
CFRunLoopRef run_loop = CFRunLoopGetCurrent();
CFRunLoopAddObserver(run_loop, exit_observer_, kCFRunLoopCommonModes);
CFRunLoopAddObserver(run_loop, exit_observer_,
kMessageLoopExclusiveRunLoopMode);
}
MemoryPressureMonitor::~MemoryPressureMonitor() {
// Detach from the run loop.
CFRunLoopRef run_loop = CFRunLoopGetCurrent();
CFRunLoopRemoveObserver(run_loop, exit_observer_, kCFRunLoopCommonModes);
CFRunLoopRemoveObserver(run_loop, exit_observer_,
kMessageLoopExclusiveRunLoopMode);
// Remove the memory pressure event source.
if (memory_level_event_source_.get()) {
dispatch_source_cancel(memory_level_event_source_);
}
}
int MemoryPressureMonitor::GetMacMemoryPressureLevel() {
// Get the raw memory pressure level from macOS.
int mac_memory_pressure_level;
size_t length = sizeof(int);
sysctlbyname("kern.memorystatus_vm_pressure_level",
&mac_memory_pressure_level, &length, nullptr, 0);
return mac_memory_pressure_level;
}
void MemoryPressureMonitor::UpdatePressureLevel() {
// Get the current macOS pressure level and convert to the corresponding
// Chrome pressure level.
int mac_memory_pressure_level = GetMacMemoryPressureLevel();
MemoryPressureListener::MemoryPressureLevel new_pressure_level =
MemoryPressureLevelForMacMemoryPressureLevel(mac_memory_pressure_level);
// Compute the number of "ticks" spent at |last_pressure_level_| (since the
// last report sent to UMA).
CFTimeInterval now = CFAbsoluteTimeGetCurrent();
CFTimeInterval time_since_last_report = now - last_statistic_report_time_;
last_statistic_report_time_ = now;
double accumulated_time = time_since_last_report + subtick_seconds_;
int ticks_to_report = static_cast<int>(accumulated_time / kUMATickSize);
// Save for later the seconds that didn't make it into a full tick.
subtick_seconds_ = std::fmod(accumulated_time, kUMATickSize);
// Round the tick count up on a pressure level change to ensure we capture it.
bool pressure_level_changed = (new_pressure_level != last_pressure_level_);
if (pressure_level_changed && ticks_to_report < 1) {
ticks_to_report = 1;
subtick_seconds_ = 0;
}
// Send elapsed ticks to UMA.
if (ticks_to_report >= 1) {
RecordMemoryPressure(last_pressure_level_, ticks_to_report);
}
// Save the now-current memory pressure level.
last_pressure_level_ = new_pressure_level;
}
void MemoryPressureMonitor::UpdatePressureLevelOnRunLoopExit() {
// Wait until it's time to check the pressure level.
CFTimeInterval now = CFAbsoluteTimeGetCurrent();
if (now >= next_run_loop_update_time_) {
UpdatePressureLevel();
// Update again in kUMATickSize seconds. We can update at any frequency,
// but because we're only checking memory pressure levels for UMA there's
// no need to update more frequently than we're keeping statistics on.
next_run_loop_update_time_ = now + kUMATickSize - subtick_seconds_;
}
}
// Static.
int MemoryPressureMonitor::GetSecondsPerUMATick() {
return kUMATickSize;
}
MemoryPressureListener::MemoryPressureLevel
MemoryPressureMonitor::GetCurrentPressureLevel() {
return last_pressure_level_;
}
void MemoryPressureMonitor::OnMemoryPressureChanged(
dispatch_source_s* event_source,
const MemoryPressureMonitor::DispatchCallback& dispatch_callback) {
// The OS has sent a notification that the memory pressure level has changed.
// Go through the normal memory pressure level checking mechanism so that
// last_pressure_level_ and UMA get updated to the current value.
UpdatePressureLevel();
// Run the callback that's waiting on memory pressure change notifications.
// The convention is to not send notifiations on memory pressure returning to
// normal.
if (last_pressure_level_ !=
MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE)
dispatch_callback.Run(last_pressure_level_);
}
void MemoryPressureMonitor::SetDispatchCallback(
const DispatchCallback& callback) {
dispatch_callback_ = callback;
}
} // namespace mac
} // namespace base